11. سطر الأوامر
ثلاثة عشر أمرًا: ستة لملفات PDF، وأربعة للنصوص البرمجية، واثنان للتوزيع، وواحد للصدفة.
| الأمر | الغرض |
|---|---|
run |
يدقّق ملف PDF بنص برمجي |
compare |
يقارن نسختين |
pixelcompare |
يقارن ملفَّي PDF بكسلًا بكسل، مع عارض لرؤية التغيير |
watch |
يراقب مجلدًا ويدقّق ما يصل إليه |
fix |
يطبّق تعديلات ويحفظ ملف PDF جديدًا |
inspect |
نظرة سريعة على ملف PDF |
lint |
يحلّل نصًا برمجيًا دون تنفيذه |
fmt |
ينسّق نصًا برمجيًا |
test |
يشغّل نصًّا على مجلد من ملفات PDF ويقارن كل تقرير |
doc |
يولّد توثيق نص برمجي |
pack |
يحزم الملفات التعريفية والبيانات |
add |
يثبّت حزمة |
completions |
يطبع نصّ الإكمال التلقائي لصدفتك |
رموز الخروج
Section titled “رموز الخروج”مشتركة بين كل الأوامر التي تدقّق.
| الرمز | المعنى |
|---|---|
0 |
نجح كل شيء |
1 |
تحذيرات فقط |
2 |
أخطاء تدقيق |
3 |
خطأ نحوي في النص البرمجي |
10 |
تعذّرت قراءة المستند أو كتابة ملف — ولم يصدر أي حكم |
pdfl run profile.pdfl file.pdf > report.jsoncase $? in 0) echo "approved" ;; 1) echo "approved with warnings" ;; 2) echo "rejected — see report.json" ;; 3) echo "error in the validation script" ;;esacخيارات عامة
Section titled “خيارات عامة”| الخيار | الغرض |
|---|---|
--quiet |
يُسكت رسائل التقدّم والتأكيد على stderr |
يعمل --quiet قبل الأمر الفرعي أو بعده، ومع كل واحد منها. وهو يزيل السطور التي
يريدها الإنسان ولا يريدها خطّ الإنتاج — report saved to … وwatching … ونتيجة
كل ملف في watch. لكنه لا يزيل الأخطاء: التشغيلة الصامتة إذا أخفقت تظلّ
تقول لماذا.
كما أنه لا يُسكت print(). فتلك مخرجات النص البرمجي نفسه، وابتلاعها يغيّر ما
يفعله. أعِد توجيه stderr إن أردت التخلّص منها.
و--quiet يغلب --verbose.
pdfl run
Section titled “pdfl run”يدقّق ملف PDF بنص برمجي.
pdfl run <script.pdfl> <input.pdf> [options]| الخيار | الافتراضي | الغرض |
|---|---|---|
--output json|csv|html|pdf|sarif|junit |
json |
صيغة التقرير |
--output-file <file> |
— | يكتب في ملف بدل المخرج القياسي |
--fail-on error|warning |
error |
مع warning يعطي التحذير أيضًا الرمز 2 |
--verbose |
— | معلومات إضافية على مخرج الأخطاء |
--var الاسم=القيمة |
— | قيمة يقرأها النص البرمجي بوصفها vars.الاسم؛ ويجوز تكرارها |
--tags TAG |
— | لا يشغّل إلا الفحوص الحاملة لهذه السمة؛ ويجوز تكرارها. والسمة التي لا يحملها أي فحص خطأ، لا نجاحًا فارغًا |
pdfl run prepress.pdfl magazine.pdf # JSON في الطرفيةpdfl run prepress.pdfl magazine.pdf --output html --output-file report.htmlpdfl run prepress.pdfl magazine.pdf --output pdf --output-file report.pdfpdfl run prepress.pdfl magazine.pdf --output csv --output-file findings.csvpdfl run prepress.pdfl magazine.pdf --fail-on warning # الوضع الصارمتقرير JSON
Section titled “تقرير JSON”{ "schema_version": 1, "script_name": "prepress.pdfl", "input_file": "magazine.pdf", "profile": "offset-magazine", "status": "FAIL", "total_pages_analyzed": 120, "error_count": 2, "warning_count": 0, "info_count": 0, "diagnostics": [ { "id": "PDFL-093751a2", "severity": "error", "check_name": "Ink coverage", "message": "page 7: 324% ink (limit 300%)", "line": 12 } ], "checks_run": ["Ink coverage", "Fonts", "Bleed"]}ملف PDF نفسه مع النص البرمجي نفسه يعطي دائمًا تقريرًا متطابقًا بايتًا بايت: يمكن حفظه في نظام الإصدارات ومقارنة الفروق في التكامل المستمر.
schema_version هو المفتاح الأول، ليقرر المستهلك قبل أن يحلّل ما بعده. ولا
يرتفع إلا إذا كان قارئ المخرَج السابق سينكسر؛ وإضافة حقل لا ترفعه.
SARIF وJUnit
Section titled “SARIF وJUnit”صيغتان إضافيتان، لتظهر النتيجة حيث ينظر الفريق أصلًا، لا في سجلّ لا يفتحه أحد.
# GitHub code scanning: تتحوّل الملاحظات إلى تعليقات على طلب السحبpdfl run prepress.pdfl magazine.pdf --output sarif --output-file pdfl.sarif
# لوحة الاختبارات في أي تكامل مستمر: اختبار لكل فحص، بما فيها الناجحةpdfl run prepress.pdfl magazine.pdf --output junit --output-file pdfl.xmlفي SARIF تُربط الملاحظة بـالنص البرمجي لا بملف PDF: فالسطر الذي نعرفه هو
سطر الفحص، وملف PDF غالبًا أثرٌ عابر في خطّ التكامل لا ملفًّا في المستودع —
والإشارة إليه تعني التعليق على مسار غير موجود. أما الملف المفحوص فيسافر في
properties.inputFile، ومعرّف التشخيص في partialFingerprints، وهو ما يجعل
GitHub يتعرّف على ملاحظة رآها من قبل بدل أن يفتحها من جديد في كل تشغيلة.
وفي JUnit يصير كل فحص جرى حالة اختبار، بما في ذلك ما لم يجد شيئًا. فالصيغة التي
تسرد الإخفاقات وحدها تصف تشغيلة نظيفة بأنها صفر اختبارات، ويقرأ التكامل المستمر
ذلك على أنه تشغيلة لم تحدث. وملاحظة من نوع info لا تُسقِط حالتها، بل تُكتب في
<system-out>.
- name: Preflight run: pdfl run prepress.pdfl magazine.pdf --output sarif --output-file pdfl.sarif # الرمز 2 يعني ملفًا مرفوضًا، والرفع يجب أن يتم على كل حال continue-on-error: true- uses: github/codeql-action/upload-sarif@v3 with: sarif_file: pdfl.sarifpdfl compare
Section titled “pdfl compare”يقارن نسختين: النص والبنية والبيانات الوصفية.
pdfl compare <v1.pdf> <v2.pdf> [options]| الخيار | الافتراضي | الغرض |
|---|---|---|
--output json|csv|html|pdf|sarif|junit |
json |
الصيغة |
--output-file <file> |
— | يكتب في ملف |
--normalize |
— | يتجاهل حالة الأحرف والمسافات |
--ignore-dates |
— | يحجب التواريخ قبل المقارنة |
--similarity-threshold <0-100> |
100 |
أدنى تشابه مقبول |
pdfl compare approved_v1.pdf new_v2.pdf --normalize --ignore-dates
# يسمح بفارق حتى 1 %، وما دونه خطأpdfl compare v1.pdf v2.pdf --similarity-threshold 99 \ --output html --output-file diff.htmlكيف يعمل
Section titled “كيف يعمل”- تُحاذى الصفحات بالمحتوى لا بالرقم: فإدراج صفحة في الوسط لا يجعل كل ما بعدها فروقًا. ويعمل على مستندات تتجاوز ألف صفحة.
- يحصل كل زوج على درجة تشابه وعيّنة من الأسطر المتغيرة (
-محذوف،+مضاف). - تغيّر البيانات الوصفية تحذير؛ وتغيّر النص دون العتبة خطأ، وفوقها تحذير.
- الدرجة الإجمالية في الحقل
similarityمن التقرير.
page 4 → 4: similarity 97.8% | -original title | +revised titlepdfl pixelcompare
Section titled “pdfl pixelcompare”يقارن ملفَّي PDF بما يبدوان عليه، صفحةً صفحة.
pdfl pixelcompare <original.pdf> <new.pdf> [options]| الخيار | الافتراضي | الغرض |
|---|---|---|
--output json|csv|html|pdf|sarif|junit |
json |
صيغة التقرير |
--output-file <ملف> |
— | يكتب التقرير في ملف |
--viewer <مجلد> |
— | يكتب عارضًا مكتفيًا بذاته: الصفحات والفروق وملف index.html لرؤيتها |
--dpi <n> |
150 |
دقة العرض. كلما زادت رأت أكثر وكلّفت أكثر |
--threshold <0.0-1.0> |
0.05 |
مسافة اللون التي عندها تُعدّ بكسلتان مختلفتين |
--max-diff <نسبة> |
0.0 |
كم يجوز أن تتغيّر الصفحة قبل الإبلاغ عنها |
--pages <مدى> |
الكل | 1-10 أو 1,3,7-12 |
--no-align |
— | لا يعوّض إزاحة عامة بين الصفحتين |
--blur <نصف قطر> |
0 |
تمويه قبل المقارنة، لامتصاص تنعيم الحواف |
--jobs <n> |
واحدة لكل معالج | عدد الصفحات التي تُقارن في آنٍ واحد |
يجيب pdfl compare عن «هل تغيّر النص أو البنية». وهذا يجيب عن سؤال آخر — «هل
ما زال يبدو كما هو» — والاثنان يختلفان أكثر مما يُظن. شعار أُزيح مليمترين، وخط
شعري اختفى، ولون خاص استُبدل بتركيبته من CMYK: النص في الثلاثة متطابق.
# المستند كله، بصيغة JSONpdfl pixelcompare approved.pdf reprint.pdf
# مع مكان يمكن فيه رؤية الفروق فعلًاpdfl pixelcompare approved.pdf reprint.pdf --viewer diff/
# تحمّل قليلًا من الضجيج، ثم دقّق فيما بقيpdfl pixelcompare approved.pdf reprint.pdf --max-diff 0.1 --dpi 300ملاحظة واحدة لكل صفحة تغيّرت، مع نسبة البكسلات وعدد المناطق المنفصلة التي تقع فيها:
page 7: 0.51% of the pixels differ, in 29 area(s)والصفحة الموجودة في ملف دون الآخر ملاحظةٌ بذاتها — إذ لا شيء يُقارَن بها. و
similarity في التقرير هو متوسط الصفحات المقارَنة، فصفحة أُعيد بناؤها من بين
مئتين لا تجعل المستند مستندًا آخر؛ وأرقام كل صفحة في التشخيصات.
المحاذاة، ولماذا هي مفعّلة
Section titled “المحاذاة، ولماذا هي مفعّلة”الملف المُصدَّر من جديد من المصدر نفسه كثيرًا ما يقع بعيدًا ببكسل أو اثنين. وبلا
تعويض ذلك تصير كل حافة حرف في الصفحة «مختلفة»، فيضيع التغيير الوحيد المهم بينها.
يبحث pixelcompare عن إزاحة عامة واحدة — تقريبيًا أولًا على نسخة مصغّرة، ثم
بدقة — ويُبلغ عنها إن وجدها:
page 3: 2.10% of the pixels differ, in 44 area(s) (aligned by 2, -1 px)أطفئها بـ--no-align حين يكون الموضع نفسه هو ما يجري فحصه.
العارض
Section titled “العارض”يكتب --viewer diff/ مجلدًا فيه ثلاث صور PNG لكل صفحة وملف index.html واحد.
ولا يعتمد على شيء البتة — لا CDN ولا مجمِّع حزم ولا خادم. افتح الملف، أو اضغط
المجلد وأرسله لمن يعتمد إعادة الطبع.
ثلاثة ألواح متجاورة، وكلها على الصفحة نفسها دائمًا:
| اللوح | ما يعرضه |
|---|---|
| Original | صفحة الملف الأول، كما هي |
| New | صفحة الملف الثاني، كما هي |
| Difference | كلاهما، وفوقهما ما تغيّر ملوّنًا — اسحب للمسح |
تحمل الألواح الثلاثة الزوج نفسه من الأشرطة — قائم وأفقي — في الموضع نفسه، ويتحركان في الثلاثة دفعة واحدة. وكلاهما يتبع المؤشّر — لا شيء تمسكه ولا شيء تصوّبه، فالتقاطع هو ببساطة حيث تكون الفأرة. وموضع تقاطعهما هو ركن ما يُكشف.
في لوح Difference يقطع الشريطان: يظهر الملف الجديد يمين القائم وأسفل الأفقي، ويظهر الأصل فيما عدا ذلك. وما لم تحرّكه، يبقى الأفقي في الأعلى فيكون القائم مسحًا عاديًا بكامل الارتفاع — أنزِل الأفقي حين يكون التغيّر المطلوب في شريط عرضي لا في عمود. أما في اللوحين الآخرين فالشريطان مسطرتان على العمود نفسه والسطر نفسه من الصفحة.
والموضعان نسبتان من الصفحة لا من اللوح، فيبقيان كما هما عند تغيير الصفحة أو تغيير حجم النافذة.
وتُكبّر عجلة الفأرة حتى ثمانية أضعاف، وتتكبّر الألواح الثلاثة معًا حول النقطة التي تحت المؤشّر — فيبقى ما كنت تنظر إليه في مكانه. ويتوقف التصغير عند الصفحة الملائمة للوح: لا فائدة تحتها، فاللوح مقاس أصلًا ليسع الصفحة كاملة. وتحافظ الأشرطة على سُمكها عند أي تكبير. وبالضغط على أي زر من أزرار الفأرة مع التحريك تسحب الصفحة نفسها، في الألواح الثلاثة معًا وبسرعة المؤشّر مهما كان التكبير — وهكذا تصل إلى ركن بعد التكبير. ويظل الشريطان يتبعان المؤشّر أثناء ذلك. ويعيد Reset view التكبير والإزاحة والأشرطة إلى مواضعها الأولى، ويبقى الزر معطّلًا ما دام لا شيء يُعاد.
تُلوَّن الفروق في مواضعها، واللون يقول من أي نوع هي:
| اللون | المعنى |
|---|---|
| أحمر | حبر اختفى من الملف الجديد |
| أخضر | حبر جديد فيه |
| أزرق | الوزن نفسه بلون مختلف |
تُقاس الألواح الثلاثة على النافذة، فتظهر المقارنة كاملة على الشاشة دون تمرير، وتحافظ على نسب الصفحة مهما كان شكل النافذة. وحين يختلف الملفان في حجم صفحة ما — صارت إحداها عرضية مثلًا — تُعرض كل واحدة كاملة داخل الإطار المشترك بدل أن تُمطّ لملئه.
والانتقال إلى صفحة أخرى يبدأ من الصفحة كاملة من جديد: فالتكبير والإزاحة يخصّان الصفحة التي ضُبطا عليها، وإن حُملا معك حطّا حيث تصادف أن يكون في الصفحة التالية شيء. وهذا يسري مهما كان طريق الانتقال — الشريط أو الأسهم أو الأزرار.
يفتح على الصفحات المختلفة. في مستند من مئتي صفحة تحرّكت منه ثلاث، فتلك
الثلاث هي سبب فتحه أصلًا؛ وAll يعيد البقية. وتتبع الأسهم و← → التصفية،
فتتخطى ما يخفيه الشريط بدل أن تحطّ عليه. وإن لم تختلف أي صفحة، قال زر التصفية
ذلك وبقي معطّلًا بدل أن يفرغ الشريط.
التقدّم
Section titled “التقدّم”تحويل مستند طويل إلى نقاط بدقة 300 نقطة/بوصة يستغرق دقائق، لذا ترسم كل مرحلة شريطًا على stderr: واحد لكل ملف يُحوَّل، وواحد للمقارنة، وواحد لكتابة العارض.
rasterising approved.pdf [############------------] 98/207ولا يُرسم إلا إذا كان stderr طرفية. فالشريط يعمل بالعودة إلى أول السطر والكتابة
فوقه، ولا مؤشّر في ملف السجل يتحرّك، فتتجمّع في التشغيلة المُعاد توجيهها آلاف
الشظايا بدلًا منه. وعند إعادة التوجيه يصمت، وتظل الرسائل العادية تخرج. و--quiet
يُسكته في كل الأحوال.
السرعة
Section titled “السرعة”تعمل المقارنة على كل المعالجات افتراضيًا. على 41 صفحة بدقة 150 نقطة/بوصة:
--jobs |
الزمن |
|---|---|
1 |
3.6 ث |
4 |
1.7 ث |
8 |
1.2 ث |
20 |
1.3 ث |
يتوقّف التحسّن عند ثمانية تقريبًا، لأن ما يقيّد هذه المرحلة هو عرض نطاق الذاكرة لا الحساب — فهي تمرّر صفحات كاملة عبر المعالج — وبعد ذلك تكتفي الخيوط بالوقوف في الطابور أمام الذاكرة نفسها. طلب المزيد ليس ضارًا، لكنه بلا فائدة.
ولاحظ ما لا يجري بالتوازي: التنقيط. يُسلسل pdfium كل استدعاء خلف قفل عام
واحد، فالخيط الثاني أمامه لا يفعل سوى الانتظار. وهذا يضع حدًّا أدنى للتشغيل —
نحو 0.8 ث من الأرقام أعلاه — وهو سبب كون --jobs 8 أسرع ثلاث مرات لا ثماني.
الافتراضي هنا واحدة لكل معالج، بينما يستخدم pdfl test وpdfl watch القيمة
--jobs 1. والفرق حقيقي: هناك تكون المهمة عمليةً ابنة تحمل مستندها الخاص، أي
مستندًا إضافيًا في الذاكرة في كل مرة. أما هنا فالصفحات موجودة في الذاكرة أصلًا
والخيوط تتشاركها، فتكلفة المهمة هي مساحة عمل صفحة واحدة. أنقص القيمة إن كنت
تتشارك الجهاز مع غيرك.
رموز الخروج: 0 لم تتغيّر أي صفحة أكثر من --max-diff، و2 تغيّرت واحدة على
الأقل، و10 تعذّرت قراءة ملف أو كتابة العارض.
ولا يعتمد التقرير على --jobs. تُجمَّع الصفحات مرة أخرى بترتيب أرقامها، فتخرج
التشخيصات وترتيبها وبصماتها متطابقة مهما كانت القيمة — يضمن ذلك اختبار — وتخرج
ملفات العارض متطابقة بايتًا ببايت.
pdfl watch
Section titled “pdfl watch”يراقب مجلدًا ويدقّق كل ملف PDF يصل أو يتغيّر.
pdfl watch <folder> --script <script.pdfl> [options]| الخيار | الافتراضي | الغرض |
|---|---|---|
--pattern <glob> |
*.pdf |
أي الملفات تُعالَج |
--exclude <glob> |
— | أيها يُستثنى |
--output-dir <folder> |
بجوار ملف PDF | أين تُكتب التقارير |
--depth <n> |
1 |
عمق المجلدات الفرعية |
--debounce <ms> |
1000 |
انتظار استقرار الملف |
--report json|csv|html|pdf|sarif|junit |
json |
صيغة التقارير |
--fail-fast |
— | يتوقف عند أول خطأ |
--events |
— | يستيقظ على إشعارات النظام بدل المؤقّت — لا يصلح للمشاركات الشبكية |
--journal <ملف> |
— | سجلّ بالإضافة فقط لما جرى التحقق منه؛ وإعادة التشغيل تتخطّى ما يغطّيه |
--timeout <ث> |
— | يقتل تحليل الملف بعد هذا العدد من الثواني ويُبلغ عنه مرفوضًا |
--var الاسم=القيمة |
— | قيمة يقرؤها كل ملف بصيغة vars.الاسم؛ قابلة للتكرار |
--jobs <n> |
1 |
عدد الملفات التي تُفحص معًا؛ و0 يعني واحدًا لكل معالج |
--once |
— | يعالج الموجود ثم يخرج |
# مجلد استلام في مطبعة، بلا انقطاعpdfl watch inbox/ --script preflight.pdfl --output-dir reports/ --report html
# تشغيل دفعي للتكامل المستمر: يخرج بأسوأ رمز صادفهpdfl watch inbox/ --script preflight.pdfl --onceecho "result: $?"ويسري --jobs على كل ما على الجولة أن تنجزه، في الدفعات وفي موجة الوصول سواء.
فكل ملف يفحصه pdfl في عملية خاصة به — للسبب نفسه في pdfl test — وهذه العملية
هي التي تصوغ التقارير، فالملف المكتوب واحد مهما كانت قيمة --jobs. في ثمانية
ملفات من 41 صفحة: 9.5 ثانية عند --jobs 1، و1.2 ثانية عند --jobs 0.
ومع --fail-fast لا يبدأ ملف جديد بعد أن يُخفق واحد؛ أما ما بدأ فعلًا فيُكمل،
لأن قتله يترك تقارير نصف مكتوبة. وتُكتب التقارير بترتيب العثور على الملفات، فتطبع
الدفعة السطور نفسها مهما كان عدد ما جرى معًا.
وينتهي الانتظار في اللحظة التي يستقرّ فيها أحدث ملف بالضبط، فلا يُحتجَز ملفٌ وصل أثناء الانتظار دورةً كاملة إضافية.
يُسرَد المجلد على مؤقّت افتراضيًا؛ ومع --events ينتظر watch إشعارات نظام
التشغيل بدلًا من ذلك. والافتراضي هو المؤقّت، وهذا مقيس: سرد عشرة آلاف ملف كل 200
مللي ثانية لا يكلّف معالجةً تُذكر، ومدة الاستقرار هي التي تحكم زمن الاستجابة على
الحالين — ففي مجلد محلي ينتهي الوضعان بفارق أقل من جزء من مئة من الثانية.
ولا تستخدم --events على مشاركة شبكية. فـ inotify على وصلة NFS أو SMB لا يبلّغ
إلا عمّا يكتبه الجهاز المحلي، فالملفات الآتية من غيره لن تُلاحَظ أبدًا، ولن يقول
watch شيئًا عن ذلك. أما حيث يفيد فعلى جهاز يراقب مجلدات كثيرة، أو حيث يكون سرد
المجلد مكلفًا. وإن تعذّر إنشاء المراقِب قال ذلك وعاد إلى المؤقّت، بدل أن يصمت.
الـ debounce موجود لأن الملفات الكبيرة تصل قطعًا: فلا يُعالَج إلا ملف كفّ عن التغيّر، وبذلك لا يُقرأ ملف PDF نصف مكتوب.
السجلّ: إتمام دفعة انقطعت
Section titled “السجلّ: إتمام دفعة انقطعت”خمسة آلاف ملف، ويعيد الجهاز الإقلاع عند الأربعة آلاف. وبلا سجلّ تبدأ الجولة التالية من الملف الأول.
pdfl watch inbox/ --script offset.pdfl --once --journal batch.jsonlكائن JSON واحد لكل ملف، يُضاف كلما تحقّق من واحد:
{"input":"inbox/cover.pdf","sha256":"9f2b…","status":"FAIL","errors":2,"warnings":0,"exit":2}أعد التشغيل بالسجلّ نفسه فتُتخطّى الملفات التي يغطّيها. أما أحكامها فلا: الدفعة
المستأنَفة التي تتخطّى ملفًا مرفوضًا تخرج بالرمز 2 كما كانت، لأن السجلّ هو سجلّ
الدفعة والرمز هو حكمها. ودفعةٌ تُبلغ بالسلامة لأنها رأت الإخفاق من قبل هي أسوأ
عطب يمكن أن يصيب هذه الأداة.
ويُطابَق الملف ببايتاته، لا باسمه ولا بتاريخه. استبدل cover.pdf بآخر اسمه
cover.pdf فيُتحقَّق منه من جديد: بصمته ليست المسجَّلة.
ولا يُكتب شيء بغير --journal. فالأداة لا تحتفظ بحالة خاصة بها؛ وهذا ملف طلبتَه
بالاسم، تمامًا كالتقرير. ولا تاريخ في السطر: السجلّ يقول هل تُحقِّق من الملف وما
الذي خرج منه، والتقرير بجانبه يقول ماذا، ونظام الملفات يقول متى — وبهذا تبقى
إعادة التشغيل مطابقة للأولى بايتًا بايت، كسائر ما في هذه الأداة.
وتُكتب السطور واحدًا واحدًا، فما يتركه الانهيار صحيحٌ إلى حيث وصل. والسجلّ الذي لا يمكن قراءته يوقف الجولة ويذكر رقم السطر؛ فتخطّي ملفات بناءً على سطر أُسيئت قراءته أسوأ من البدء من جديد.
--timeout: ملف سيئ واحد يجب ألا يوقف الدفعة كلها
Section titled “--timeout: ملف سيئ واحد يجب ألا يوقف الدفعة كلها”pdfl watch inbox/ --script offset.pdfl --once --timeout 60الملف الذي يتجاوز تحليله 60 ثانية يُقتل ويُبلَّغ عنه بالطريقة نفسها التي يُبلَّغ
بها عن PDF غير قابل للقراءة — تقرير بملاحظة واحدة، check_name: "timeout" —
فيُطبع، ويُكتب على القرص، ويدخل السجلّ تمامًا كأي حكم آخر. لا شيء يُتخطّى بصمت،
وتنتقل الدفعة إلى الملف التالي بدل أن تتوقف عند هذا.
{"input":"inbox/adversarial.pdf","sha256":"7a1c…","status":"FAIL","errors":1,"warnings":0,"exit":2}لا شيء في لغة .pdfl يمكّن نصًّا برمجيًا من إيقاف المفسِّر عمدًا — فالاستدعاء
الذاتي محدود العمق — لذا يوجد --timeout لما لا يستطيع النص البرمجي التسبّب فيه:
أن يدخل pdfium في حلقة أو يتجمّد أمام ملف PDF مشوَّه أو عدائي. وبلا هذا الخيار
ينتظر تحليل الملف قدر ما يلزم، وهذا كان السلوك الوحيد قبل وجود هذا الخيار.
وتصل --var إلى كل ملف دون تغيير — قيمة واحدة للتشغيلة كلها، تفيد فيما يثبت عبر
المجلد (اسم عميل) لا فيما يختلف من ملف لآخر (رقم طلب). وبدونها لا يمكن مراقبة
نص برمجي يقرأ vars.* أبدًا: يُخفق كل ملف بعبارة “was not provided”.
تُكتب التقارير باسم <name>.report.json (أو .csv أو .html أو .pdf).
pdfl fix
Section titled “pdfl fix”يطبّق عمليات fix:: ويحفظ ملف PDF جديدًا. التفاصيل في الفصل 8.
pdfl fix <input.pdf> <script.pdfl> --output <output.pdf> [options]| الخيار | الافتراضي | الغرض |
|---|---|---|
--output <ملف> |
— | ملف PDF الناتج (إلزامي) |
--dry-run |
— | يسرد العمليات دون أن يحفظ |
--report json|csv|html|pdf|sarif|junit |
json |
صيغة التقرير |
--report-file <ملف> |
— | يكتب التقرير في ملف |
pdfl fix original.pdf normalize.pdfl --output out.pdf --dry-run # للمعاينة فقطpdfl fix original.pdf normalize.pdfl --output fixed.pdf # للتطبيقpdfl inspect
Section titled “pdfl inspect”نظرة عامة على ملف PDF بلا نص برمجي.
pdfl inspect <file.pdf>يعطي --json الملخّص نفسه بصيغة بيانات.
File: magazine.pdfSize: 26 KB (27284713 bytes)SHA-256: af1029842e5bfeae338ead82fb449ef851be742b1d63117c12596e3ea123a616
Pages: 120Page size: 496 x 709 ptBoxes: MediaBox, TrimBox, BleedBox
Metadata: Title: Example Magazine Creator: Adobe InDesign 19.3
Fonts: 26 ABCDEF+Helvetica — embedded Arial — NOT embeddedImages: 81 (minimum DPI 136, spaces: DeviceCMYK, Indexed)Max. estimated TAC: 300% (RGB render approximation)
Warnings: ! there are non-embedded fonts ! 3 image(s) below 300 DPIأول أمر يُشغَّل عند وصول ملف جديد: في ثوانٍ تعرف هل يستحق الفتح.
pdfl lint
Section titled “pdfl lint”يحلّل نصًا برمجيًا دون تنفيذه ويبلّغ عن مشكلات الجودة.
pdfl lint <script.pdfl>يعطي --json التحذيرات نفسها بصيغة بيانات.
ما يكشفه:
- المتغيرات ومعاملات الكتل والدوال المصرَّح بها وغير المستعملة قط
(سبقها بـ
_لكتم التحذير:_page) - الفحوص المكرّرة أو الفارغة
- فضاءات الأسماء غير المعروفة (
text::وstruct::وvisual::وprepress::وcodes::وfix::وdata::) assert/requireخارج أي فحص- استعمال
fix::(وهو لا يعمل إلا معpdfl fix)
$ pdfl lint profile.pdflprofile.pdfl: warning: variable 'LIMIT' declared and never usedprofile.pdfl: warning: check "Fonts" declared 2 timesوعند وجود تحذيرات يكون رمز الخروج 1 — صالح للتكامل المستمر.
pdfl fmt
Section titled “pdfl fmt”ينسّق النص البرمجي: إزاحة بمسافتين، ومسافات متسقة، وضغط الأسطر الفارغة.
وتبقى التعليقات والوحدات (3mm تظل 3mm) كما هي.
pdfl fmt <script.pdfl> # ينسّق في مكانهpdfl fmt <script.pdfl> --check # لا يغيّر شيئًا؛ الرمز 1 إن لم يكن منسَّقًا# فرض معيار الفريق في التكامل المستمرfor f in profiles/*.pdfl; do pdfl fmt "$f" --check || exit 1; donepdfl doc
Section titled “pdfl doc”يولّد التوثيق من النص البرمجي نفسه.
pdfl doc <script.pdfl> [--output markdown|html|json]ويُخرج: الملف التعريفي، وجدول الثوابت، والدوال، والاستيرادات، ولكل فحص وسومه
وما يدقّقه (رسائل assert تصير الأوصاف).
pdfl doc prepress.pdfl > docs/prepress-profile.mdpdfl doc prepress.pdfl --output html > profile.htmlوهو المُخرَج الذي يشرح لمسؤول الإنتاج الذي لا يقرأ الشيفرة ما الذي يدقّقه الملف التعريفي.
pdfl pack
Section titled “pdfl pack”يحزم النصوص البرمجية والبيانات في ملف .pdflpkg قابل للتوزيع.
pdfl pack <folder> [--name <name>] [--version <version>] [--output <file>]يجمع تكراريًا ملفات .pdfl و.csv و.txt و.json من المجلد، ويرفق
manifest.json يسجّل بصمة SHA-256 لكل ملف. والحزم حتمي: المجلد نفسه يُنتج
البايتات نفسها.
أما ملفات الجداول (.xlsx و.xls و.ods) فلا تُحزم، وpack يقول أي ملف ترك.
إذ لا تفتحها أي دالة في data::، فحزمها يعني تسليم حزمة تُثبَّت بلا مشكلة ثم
تخفق عند أول بحث.
pdfl pack profiles/print-shop --name print-profile --version 1.0.0pdfl add
Section titled “pdfl add”يثبّت حزمة محلية مع التحقق من بصمات البيان.
pdfl add print-profile.pdflpkg# يثبّت في ./pdfl_profiles/[email protected]/
وإن لم تطابق بصمة أي ملف ما هو مسجَّل رُفض التثبيت — فالحزمة التالفة أو المعبوث بها لا تدخل.
المستودعات البعيدة والتواقيع الرقمية ليست ضمن هذا الإصدار:
addيثبّت من ملف محلي.
pdfl test
Section titled “pdfl test”يشغّل النص البرمجي على كل ملف PDF في مجلد، ويقارن كل تقرير بالتقرير المسجَّل بجانبه. فالملف التعريفي الذي يبدأ بإيجاد شيء مختلف يُسقِط اختبارًا، بدل أن يفاجئ أحدًا في آخر السلسلة.
pdfl test <script.pdfl> [--dir <مجلد>] [--update]| الخيار | الافتراضي | الغرض |
|---|---|---|
--dir <مجلد> |
tests/ بجانب النص البرمجي |
حيث توجد ملفات الحالات |
--update |
— | يسجّل التقارير المتوقعة بدل أن يقارنها |
--jobs <n> |
1 |
عدد الحالات المتزامنة؛ و0 يعني واحدة لكل معالج |
--var الاسم=القيمة |
— | قيمة تقرؤها كل حالة بصيغة vars.الاسم؛ قابلة للتكرار |
الحالة الواحدة ملف PDF والتقرير المتوقع منه، جنبًا إلى جنب:
profiles/print-shop/ prepress.pdfl tests/ approved.pdf approved.expected.json heavy_ink.pdf heavy_ink.expected.json# أول مرة: سجّل ما يجده النص البرمجي اليومpdfl test prepress.pdfl --update
# وبعدهاpdfl test prepress.pdflok approved.pdfFAIL heavy_ink.pdf error_count: expected 1, got 0 missing: PDFL-093751a2 [error] Ink coverage (line 12): page 7: 324% ink (limit 300%)1 passed, 1 failedوالإخفاق يسمّي ما تغيّر — الأعداد والحكم وأي الملاحظات ظهرت أو اختفت — بدل أن يطبع ملفَّي JSON متجاورين.
والتسجيل فعلٌ مقصود دائمًا: التشغيلة التي تحدّث خطّ أساسها بنفسها لا تُخفق أبدًا.
اقرأ الفرق أولًا، ثم أعد التسجيل بـ --update إذا كان التغيير هو ما أردته.
والتقرير المتوقع هو ما ينتجه pdfl run، مع اختصار input_file إلى اسم الملف —
فخطُّ أساس يتغيّر بتغيّر المجلد الذي نُودي منه ليس خطَّ أساس. وملف PDF الذي لا
يُفتح يُسقِط حالته وحدها ويترك البقية تعمل.
رموز الخروج: 0 نجحت كلها، و2 أخفقت واحدة على الأقل، و10 تعذّرت قراءة
المجلد أو لا PDF فيه.
تشغيل الحالات معًا
Section titled “تشغيل الحالات معًا”كل حالة تعمل في عملية pdfl خاصة بها، فـ--jobs يحوّل المجموعة إلى تواز حقيقي:
في ثمانية ملفات من 41 صفحة، استغرق --jobs 1 نحو 8.9 ثانية و--jobs 8 نحو 1.1
ثانية. أما الخيوط داخل عملية واحدة فما كانت لتنجح — إذ يُسلسِل pdfium كل نداء
خلف قفل واحد، وقد قِيست نسخة الخيوط أبطأ من التنفيذ المتسلسل.
والقيمة الافتراضية 1 لأن كل مَهمّة عملية تحمل مستندًا في الذاكرة، وهذه الأداة
موجودة لملفات قد تكون ضخمة. ارفعها إذا كانت حالاتك عادية: --jobs 0 يعطي واحدة
لكل معالج.
ولا يتغيّر ترتيب المخرَج بتغيّر --jobs: تُحكَم الحالات بالترتيب الذي وُجدت به،
أيًّا كانت العملية التي انتهت أولًا.
والحالة التي لا يُقرأ ملفها تُحكَم كغيرها — إذ يحمل تقريرها السبب بوصفه ملاحظة،
فيصير «هذا الملف يجب أن يُرفض لتعذّر قراءته» اختبارًا بذاته. وذلك التقرير يسمّي
الملف كما مُرِّر، فسجّل خطوط الأساس بـ--dir نسبي إن كنت ستحفظها في
المستودع.
تصل --var إلى كل حالة دون تغيير — قيمة واحدة للتشغيلة كلها، لا واحدة لكل ملف.
وبدونها لا يمكن اختبار نص برمجي يقرأ vars.* أبدًا: تُخفق كل حالة بعبارة
“was not provided”، أيًّا كان ملف PDF.
pdfl completions
Section titled “pdfl completions”يطبع على stdout نصّ الإكمال التلقائي لصدفتك.
pdfl completions <bash|zsh|fish|elvish|powershell># bash، للمستخدم الحاليpdfl completions bash > ~/.local/share/bash-completion/completions/pdfl
# zsh — في أي موضع ضمن $fpathpdfl completions zsh > ~/.zfunc/_pdfl
# fishpdfl completions fish > ~/.config/fish/completions/pdfl.fishلا يُكتب شيء آخر على stdout، فيمكن توجيه المخرَج مباشرة إلى مجلّد الإكمال. وأعِد توليده بعد كل ترقية: فالنصّ يُبنى من أوامر الملف التنفيذي الذي طبعه ومن خياراته.