تخطَّ إلى المحتوى

13. سجل التغييرات

ما تغيّر في كل إصدار، وما الذي قد يكسره ذلك عندك.

الإصدار ما زال في 0.x، فالقفزة الصغرى يحقّ لها أن تكسر شيئًا. وحين تكسر، يقول المدخل بالضبط ماذا كُسر وكيف تتكيّف. لا شيء هنا يتغيّر في صمت.


  • كان بإمكان سكربت أن يُنهي العملية إنهاءً غير طبيعي بدل أن يُرفض. إذ كان التداخل في المصدر يتحوّل إلى تداخل في مكدّس الاستدعاء، فنحو ألفَي قوس تكفي لتفيض المكدّس ويموت التشغيل بإشارة SIGABRT — بلا رمز خروج 3 ولا رسالة ولا شيء يمكن التقاطه. وستة أشكال تفعل ذلك: ((((…)))) و!!!!… و----… والمصفوفات المتداخلة والكتل المتداخلة و"#{"#{…}"}". صار المحلّل يعدّ عمقه ويرفض ما تجاوز 128 مستوى، وهو أعمق بكثير من أي سكربت يكتبه إنسان وأدنى بكثير من حدّ المكدّس. وأسوأ ما في الأمر أنه كان يصيب الأمر الذي تشغّله أولًا على سكربت ليس لك: إذ كان pdfl lint يموت بدل أن يبلّغ.
  • كان بإمكان حزمة أن تسمّي ملفًا خارج المجلد الذي تُثبَّت فيه. كان الفحص يرفض .. والشرطة المائلة في البداية، لكنه لا يرفض C:/Windows/evil.dll ولا \\server\share\x — فكلاهما لا يحتوي على ذلك، وكلاهما مطلق على Windows حيث يؤدي ضمّه إلى مجلد التثبيت إلى إسقاط المجلد نفسه. صار كل جزء من كل مسار يجب أن يكون اسمًا بسيطًا، وفق قواعد لا تتغيّر بتغيّر المنصّة التي تفكّ الحزمة.
  • كان بإمكان حزمة أن تستنزف الذاكرة أثناء القراءة. إذ كانت المدخلات تُقرأ كاملة إلى الذاكرة قبل الرجوع إلى البيان — بما فيها مدخلات لا يذكرها البيان أصلًا — فصار 299 كيلوبايت من الأصفار المضغوطة يأخذ 380 ميغابايت، وما هو أكبر يأخذ الجهاز. وصار فكّ الحزمة يتوقف عند 64 ميغابايت، وهو أعلى بكثير من أي حزمة حقيقية من سكربتات وجداول.
  • كان --pages 1-20000000 يحجز أرقام الصفحات قبل فتح أي مستند — 214 ميغابايت ثم لا يقول شيئًا. وتُرفض الآن النطاقات التي تتجاوز مليون صفحة.
  • تضع الإصلاحات أعلاه حدودًا حيث لم تكن هناك حدود، فبعض ما كان يُقبل لم يعد يُقبل: سكربت يتداخل أعمق من 128 مستوى، وحزمة تتمدّد فوق 64 ميغابايت، وحزمة تسمّي ملفًا فيه : أو \، ونطاق صفحات يتجاوز المليون. وكلها بعيدة جدًا عمّا يحويه سكربت أو حزمة أو مستند حقيقي — لكن أيًّا منها لم يكن يفشل من قبل، فدُوّنت هنا بدل أن تُكتشف.

أُزيلت أوضاع العارض. لم يعد هناك Flip ولا Fade ولا منزلق Blend ولا منزلق Differences، ولا مفتاحا space وd اللذان كانا يشغّلانها.

والمتأثر هو العارض المولَّد وحده — فلا يتغيّر تقرير ولا رمز خروج ولا خيار، ولا يلحظ شيءٌ يقرأ مخرجات pdfl أي فرق. والألواح الثلاثة تجيب عمّا كانت تلك الأوضاع تخدمه: فالملفان معًا على الشاشة بدل أن يظهر كل منهما بدوره. وإن كنت قد كتبت تعليمات خاصة تذكر وضعًا أو مفتاحًا، فهي بحاجة إلى مراجعة.

  • صار عارض pixelcompare ثلاثة ألواح متجاورة — الأصل، والملف الجديد، وكلاهما وفوقهما الفروق ملوّنة — بدل لوح واحد وقائمة أوضاع. ويبقى اللوحان المرجعيان ثابتين بينما يمسح الثالث، فترى بين ماذا وماذا يقطع المسح.
  • يحمل كل لوح الزوج نفسه من الأشرطة — قائم وأفقي — في الموضع نفسه، وتتحرك الستة معًا. وهي تتبع المؤشّر: لا شيء تمسكه ولا شيء تصوّبه، فالتقاطع حيث تكون الفأرة. في لوح الفروق يقطع الشريطان، فيظهر الملف الجديد يمين القائم وأسفل الأفقي ويكون تقاطعهما ركن ما يُكشف؛ وفي اللوحين الآخرين هما مسطرتان على العمود نفسه والسطر نفسه من الصفحة. ويبدأ الشريط الأفقي من الأعلى، فيبقى القائم مسحًا عاديًا بكامل الارتفاع إلى أن يدخل المؤشّر أحد الألواح.
  • تُكبّر عجلة الفأرة الألواح الثلاثة معًا حتى ثمانية أضعاف حول النقطة التي تحت المؤشّر، ويتوقف التصغير عند الصفحة الملائمة. والسحب بأي زر من أزرار الفأرة يحرّك الصفحة نفسها، في الثلاثة دفعة واحدة وبسرعة المؤشّر مهما كان التكبير. وتحافظ الأشرطة على سُمكها عند أي تكبير. ويعيد Reset view التكبير والإزاحة والأشرطة إلى حالتها الأولى ويبقى معطّلًا ما دام لا شيء يُعاد؛ والانتقال إلى صفحة أخرى يفعل الشيء نفسه، فالتكبير يخصّ صفحته.
  • تُقاس الألواح على النافذة: تظهر المقارنة كاملة على الشاشة دون تمرير، مهما كان شكل النافذة، ويحافظ كل لوح على نسب الصفحة. وحين يختلف الملفان في حجم صفحة، تُعرض كل واحدة كاملة داخل الإطار المشترك بدل أن تُمطّ.
  • ويفتح على الصفحات المختلفة، وAll يعيد البقية. ففي مستند طويل تكون تلك الصفحات هي سبب فتحه.

يتغيّر معرّف أي نتيجة من نوع loading. فهو مشتق من الرسالة، والرسالة لم تعد تحمل الأسطر الجديدة التي كان pdfium يضعها فيها.

لا يتأثر سوى ما ينتج عن مستند تعذّرت قراءته — ولا ينتج أيٌّ من فحوصك مثل هذا. وإن كان لديك خط أساس يعتمد ذلك المعرّف، فسيلزم تسجيله من جديد. وهذا سبب كون هذا الإصدار ثانويًا لا ترقيعيًا: المعرّف هو ما يقوم عليه خط الأساس، فلا يتغيّر في صمت.

  • كان pdfl pack يسجّل الملف المتداخل بفاصل المسار الخاص بالجهاز الذي بنى الحزمة — وعلى Windows يكون data\batches.csv. والحزمة تُبنى على جهاز وتُثبَّت على آخر، فأي شيء غير / يعني حزمة تُثبَّت ثم لا تعثر على ملفاتها، ولا يعثر عليها تحققنا نفسه. أما الحزم المبنية على Linux وmacOS فلم تتأثر قط.
  • كان --output و--output-file يُتجاهلان عندما يتعذّر قراءة ملف PDF. إذ كانت الأوامر run وfix وcompare تطبع JSON على stdout مهما كان المطلوب، فخط المعالجة الذي يطلب JUnit في ملف لا يحصل على ملف أصلًا، ويذهب التقرير إلى مجرى لا يقرأه — فيبدو الملف التالف وكأن التشغيل لم يحدث قط، مع أن رمز الخروج كان يقول 10 بشكل صحيح. صار تقرير الفشل يخرج من المسار نفسه الذي تخرج منه بقية التقارير.
  • كان خطأ pdfium يصل إلى التقرير على هيئة enum من Rust مطبوع على ثلاثة أسطر. والتشخيص حقلٌ في صف CSV وسمةٌ في XML، فصار يُطوى الآن في سطر واحد.

  • يعرض pdfl pixelcompare شريط تقدّم لكل مرحلة — تحويل كل ملف إلى نقاط، والمقارنة، وكتابة العارض. ولا يُرسم إلا إذا كان stderr طرفية، لأن الشريط يكتب فوق سطره ولا مؤشّر في ملف السجل يتحرّك؛ وعند إعادة التوجيه يصمت. و--quiet يُسكته في كل الأحوال.

  • الخيار --jobs <n> في pdfl pixelcompare يقارن هذا العدد من الصفحات في آنٍ واحد، وقيمته الافتراضية واحدة لكل معالج: 41 صفحة بدقة 150 نقطة/بوصة تنزل من 3.6 ث إلى 1.2 ث. المقارنة وحدها هي المتوازية — إذ يُسلسل pdfium كل استدعاء خلف قفل عام واحد، فالتنقيط لا يمكن أن يتوازى، ولهذا كان المكسب ثلاثة أضعاف لا ثمانية. ولا يتأثر التقرير بالقيمة: تُجمَّع الصفحات بترتيب أرقامها، فتبقى التشخيصات وترتيبها وبصماتها متطابقة، وكذلك ملفات العارض.

    الافتراضي هنا واحدة لكل معالج، بينما يستخدم test وwatch القيمة --jobs 1. فهناك تكون المهمة عمليةً ابنة تحمل مستندها الخاص؛ أما هنا فالصفحات في الذاكرة أصلًا والخيوط تتشاركها.


  • pdfl pixelcompare يقارن ملفَّي PDF بمظهرهما لا بنصّهما، صفحةً صفحة، ويُبلغ عن نسبة البكسلات المختلفة. والصفحة التي انزاحت فحسب تُحاذى قبل المقارنة، كي لا تدفن إزاحةُ بكسل واحد التغييرَ المهم.
  • و--viewer <مجلد> في pixelcompare يكتب تطبيقًا مكتفيًا بذاته — بلا CDN ولا أداة تجميع ولا خادم — للكنس أو التبديل أو المزج بين الملفين، والفروق ملوّنة في مكانها: أحمر للحبر الذي اختفى، وأخضر للجديد، وأزرق للوزن نفسه بلون آخر. ويمكن ترشيح شريط الصفحات إلى Changed only، وتتبع مفاتيح الأسهم الترشيح — ففي مستند طويل يكون المرور على الصفحات التي لم تتغيّر هو الجزء البطيء.

  • if وelse if وelse بوصفها تعبيرًا: قيمتها آخر تعبير في الفرع الذي جرى، وهي القاعدة نفسها التي تتبعها الدالة أصلًا. فتصلح قيمةً (const LIMIT = if coated { 300 } else { 260 }) وحارسًا حول الأوامر، بلا صياغة ثانية. والفرع الذي لا يجري يعطي null، ولكل فرع نطاقه الخاص — أما الإسناد إلى متغير موجود في الخارج فيظل يحدّث ذلك المتغير.

  • صارت --var تصل الآن إلى pdfl test وpdfl watch، لا إلى pdfl run فقط. فلم يكن أيٌّ منهما يمرّرها إلى العمليات الابنة التي يُطلقها، فتعذّر اختبار أو مراقبة أي نص برمجي يقرأ vars.* أصلًا — إذ كانت كل حالة أو ملف تُخفق بعبارة “was not provided”، أيًّا كان محتواه.

  • لم يعد pdfl pack يحزم ملفات الجداول (.xlsx و.xls و.ods)، وهو يسمّي الملف الذي تركه. إذ لا تفتحها أي دالة في data::، فالحزمة التي تحملها كانت تُثبَّت بلا مشكلة ثم تخفق عند أول بحث. وإن كنت تحزم جدولًا، فصدّره أولًا إلى .csv أو .json.
  • --tags TAG في run يرشّح أي الفحوص تُنفَّذ. قابل للتكرار؛ يُنفَّذ الفحص إذا حمل أيًّا من الوسوم المذكورة.
  • --json في inspect وlint، و--output json في doc. كل أمر فرعي صار مقروءًا للبرامج.
  • --output sarif و--output junit، في كل موضع تُختار فيه صيغة تقرير — run وcompare وwatch وfix. فـ SARIF هي ما يقرأه GitHub code scanning، وJUnit هي ما تقرأه لوحة الاختبارات في أي تكامل مستمر.
  • pdfl completions <shell> يطبع نصّ الإكمال التلقائي لـ bash أو zsh أو fish أو elvish أو powershell.
  • --quiet في كل الأوامر يُسكت رسائل التقدّم والتأكيد على stderr. أما الأخطاء فتبقى ظاهرة، وprint() لا يُمسّ — فتلك مخرجات النص البرمجي نفسه، وابتلاعها يغيّر ما يفعله.
  • data::load_dataset وdata::lookup_value يقرآن .json إلى جانب .csv: مصفوفةً من المصفوفات، أو مصفوفةً من الكائنات يسمّي أولُها الأعمدة بالترتيب الذي كُتب في الملف.
  • pdfl test <script> يشغّل نصًّا برمجيًا على مجلد من ملفات PDF ويقارن كل تقرير بالمسجَّل بجانبه، فالملف التعريفي الذي يبدأ بإيجاد شيء مختلف يُسقِط اختبارًا بدل أن يفاجئ أحدًا في آخر السلسلة. و--update يسجّل التقارير المتوقعة.
  • --jobs <n> في pdfl test يشغّل ذلك العدد من الحالات معًا، كلٌّ في عمليتها. في ثمانية ملفات من 41 صفحة: 8.9 ثانية عند --jobs 1، و1.1 ثانية عند --jobs 8. والافتراضي يبقى 1 لأن كل مَهمّة تحمل مستندًا في الذاكرة؛ و--jobs 0 يعطي واحدة لكل معالج.
  • و--jobs <n> في pdfl watch أيضًا: تفحص العمليات الابنة الملفات، فتتوسّع جولة الدفعة بالطريقة نفسها (من 9.5 إلى 1.2 ثانية في ثمانية ملفات من 41 صفحة). والتقرير المكتوب واحد مهما كانت قيمة --jobs.
  • --events في pdfl watch ينتظر إشعارات نظام الملفات بدل المؤقّت. وهو اختياري لا افتراضي: فـ inotify على وصلة NFS أو SMB لا يبلّغ إلا عمّا يكتبه الجهاز المحلي، فيصمت المجلد الشبكي. وإن تعذّر إنشاء المراقِب قال ذلك وعاد إلى المؤقّت.
  • --journal <ملف> في pdfl watch: سجلّ بالإضافة فقط لما جرى التحقق منه، كائن JSON لكل ملف. وإعادة التشغيل بالسجلّ نفسه تتخطّى ما يغطّيه — فالدفعة التي انقطعت عند أربعة آلاف من خمسة تُتمّ الألف الباقية — مع الإبلاغ عن أحكامها، فلا تدّعي دفعةٌ مستأنَفة أن المجلد نظيف.
  • --timeout <s> في pdfl watch يقتل تحليل الملف بعد هذا العدد من الثواني ويُبلغ عنه مرفوضًا — ملاحظة واحدة، check_name: "timeout" — بدل أن يترك الدفعة عالقة. والاستدعاء الذاتي في نص .pdfl محدود العمق أصلًا، فهذا الخيار لما لا يستطيع النص البرمجي التسبّب فيه: أن يدخل pdfium في حلقة أو يتجمّد أمام ملف PDF مشوَّه أو عدائي.
  • وسمٌ لا يحمله أي فحص هو خطأ، لا نجاحًا فارغًا. وإلا لما تحققت خطّ إنتاج كُتب فيها الوسم غلطًا من شيء، ثم أبلغت أن الملف نظيف.
  • الـ rule لا يحمل وسومًا، فـ --tags يتخطّاه — وهو الجواب نفسه الذي يناله فحصٌ بلا وسم.
  • اكتسب تقرير JSON حقل checks_run، أي الفحوص والقواعد التي جرت. وهو لا يرفع schema_version، لأن من يتجاهل الحقول المجهولة يمرّ به سالمًا. وJUnit يحتاجه: فالتشخيصات لا تسمّي إلا الفحوص التي وجدت شيئًا، والتشغيلة النظيفة إذا رُوِيت بصفر اختبارات قرأها التكامل المستمر تشغيلةً لم تحدث.
  • صار pdfl watch يستيقظ حين يستقرّ أحدث ملف، بدل أن ينتظر دورة كاملة بعده. مع --debounce 3000 يُبلَّغ عن الملف الواصل بعد نحو 3 ثوانٍ لا بعد 6.

  • السكربتات تستقبل قيمًا من سطر الأوامر: --var الاسم=القيمة، تُقرأ بصيغة vars.الاسم. والقيمة الغائبة تسمّي الخيار الذي كان سيوفّرها، بدل أن تؤول إلى لا شيء.
  • أربعة أمثلة كاملة لمقارنة ملفّين بـ visual::: proof.pdfl وreprint.pdfl وscope.pdfl وintake.pdfl.

لا شيء. السكربت الذي لا يذكر vars يسلك تمامًا كما كان.


تغيّر شكل معرّفات التشخيص. كانت PDFL-001، أي عدّادًا داخل التشغيلة الواحدة؛ وصارت تُشتقّ من الملاحظة نفسها، مثل PDFL-093751a2.

كل ما كان يطابق PDFL-\d+ لم يعد يطابق. وفي المقابل، صار المعرّف يبقى على حاله إذا أُدرج فحصٌ فوقه — وهذا وحده ما يجعل خطّ أساس معتمدًا أمرًا ممكنًا.

المدخل غير المقروء يخرج بالرمز 10 بدل 2. فقد كان الملف التالف والملف المرفوض لا يتمايزان أمام خطّ الإنتاج.

إن كان تكاملك المستمر يعامل 2 على أنه «هذا الملف رُفض»، فسيرى 10 حين لا يكون الملف قد حوكم أصلًا. الملاحظات تبقى على 0 و1 و2؛ وخطأ الصياغة في السكربت يبقى على 3.

  • يستطيع الفحص أن يعلن شدّة ملاحظاته: check "..." severity: warning { ... } — إما error (الافتراضي) أو warning أو info. وبهذا صار لـ --fail-on warning ما يعمل عليه.
  • تقرير JSON يبدأ بـ schema_version، ليعرف المستهلك أي شكل يقرأ. ولا يرتفع الرقم إلا إذا كان قارئ المخرَج السابق سينكسر؛ وإضافة حقل لا ترفعه.

  • كان تقرير PDF مكتوبًا بالبرتغالية جزئيًا: عنوان القسم يقول Diagnósticos، وكل تشخيص يحمل (linha N). صارا بالإنجليزية الآن، وهو ما وعدت به الوثائق دائمًا.

أهداف الإصدار تغيّر اسمها من x64 إلى amd64، فتغيّر معها اسم كل أصل.

لم تعد الحِزم المحمولة تُنشر، عدا واحدة للينكس amd64.

كل ما ينزّل pdfl-<الإصدار>-linux-x64.tar.gz، أو أي حزمة محمولة غير حزمة لينكس amd64، يحتاج إلى تغيير. في التكامل المستمر، ثبّت من ملف .deb — وقد حُدّثت وصفات هذه الوثائق على هذا الأساس — أو خذ حزمة لينكس amd64 حيث يتعذّر التثبيت.

  • ثغرتان ظهرتا عند مقابلة الوثائق بالشيفرة: text::detect_personal_data وtext::detect_pii تقبلان نصًّا اختياريًا لم يُوثَّق في أي مكان، و fix::reorder_pages كُتب بصيغتين مختلفتين بين اللغات.

  • مثبّتات لكل منصّة: .deb للينكس، و.dmg لماك، و-setup.exe و.msi لويندوز.
  • بناءات ماك على معالج إنتل، مترجَمة تصالبيًا من منصّة Apple Silicon.
  • كان مثبّت ويندوز يُبنى بمسارات تُحلّ من الدليل الخطأ، فلم يكن ينتج ملفًّا قط.
  • كانت حِزم الإصدار تحمل ترويسات C وملفات بناء pdfium، وهي لا تعني إلا من يترجم مقابل pdfium. نحو 550 كيلوبايت في كل حزمة.

  • ويندوز x64 ينضمّ إلى المنصّات المنشورة.

ملف pdfium.dll المرفق يقع في pdfium\bin، لا في pdfium\lib. وإن حزمت pdfl بنفسك فأبقِ التوزيع كما سُلّم: الملف التنفيذي يبحث عن المكتبة إلى جانبه.


أصول الإصدار تحمل رقم الإصدار في اسمها، بصيغة pdfl-<الإصدار>-<الهدف>.tar.gz، والدليل داخلها كذلك.

.../releases/latest/download/<الاسم> لم يعد يُحلّ، لأن هذه النقطة تطلب اسم الملف بالضبط. نزّل بنمطٍ بدل ذلك: gh release download --pattern 'pdfl-*-linux-amd64.*'.

  • الشيفرة وملف README والأمثلة بالإنجليزية. والوثائق تبقى بسبع لغات.

أول إصدار عام. اللغة والمفسّر وعشرة أوامر لسطر الأوامر، مع وثائق بسبع لغات.


← وصفات عملية · الفهرس

DigitalOceanشكرًا لـ DigitalOcean على استضافة هذا الموقع.