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

11. سطر الأوامر

ثلاثة عشر أمرًا: ستة لملفات PDF، وأربعة للنصوص البرمجية، واثنان للتوزيع، وواحد للصدفة.

الأمر الغرض
run يدقّق ملف PDF بنص برمجي
compare يقارن نسختين
pixelcompare يقارن ملفَّي PDF بكسلًا بكسل، مع عارض لرؤية التغيير
watch يراقب مجلدًا ويدقّق ما يصل إليه
fix يطبّق تعديلات ويحفظ ملف PDF جديدًا
inspect نظرة سريعة على ملف PDF
lint يحلّل نصًا برمجيًا دون تنفيذه
fmt ينسّق نصًا برمجيًا
test يشغّل نصًّا على مجلد من ملفات PDF ويقارن كل تقرير
doc يولّد توثيق نص برمجي
pack يحزم الملفات التعريفية والبيانات
add يثبّت حزمة
completions يطبع نصّ الإكمال التلقائي لصدفتك

مشتركة بين كل الأوامر التي تدقّق.

الرمز المعنى
0 نجح كل شيء
1 تحذيرات فقط
2 أخطاء تدقيق
3 خطأ نحوي في النص البرمجي
10 تعذّرت قراءة المستند أو كتابة ملف — ولم يصدر أي حكم
Terminal window
pdfl run profile.pdfl file.pdf > report.json
case $? in
0) echo "approved" ;;
1) echo "approved with warnings" ;;
2) echo "rejected — see report.json" ;;
3) echo "error in the validation script" ;;
esac

الخيار الغرض
--quiet يُسكت رسائل التقدّم والتأكيد على stderr

يعمل --quiet قبل الأمر الفرعي أو بعده، ومع كل واحد منها. وهو يزيل السطور التي يريدها الإنسان ولا يريدها خطّ الإنتاج — report saved to … وwatching … ونتيجة كل ملف في watch. لكنه لا يزيل الأخطاء: التشغيلة الصامتة إذا أخفقت تظلّ تقول لماذا.

كما أنه لا يُسكت print(). فتلك مخرجات النص البرمجي نفسه، وابتلاعها يغيّر ما يفعله. أعِد توجيه stderr إن أردت التخلّص منها.

و--quiet يغلب --verbose.


يدقّق ملف PDF بنص برمجي.

Terminal window
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 — لا يشغّل إلا الفحوص الحاملة لهذه السمة؛ ويجوز تكرارها. والسمة التي لا يحملها أي فحص خطأ، لا نجاحًا فارغًا
Terminal window
pdfl run prepress.pdfl magazine.pdf # JSON في الطرفية
pdfl run prepress.pdfl magazine.pdf --output html --output-file report.html
pdfl run prepress.pdfl magazine.pdf --output pdf --output-file report.pdf
pdfl run prepress.pdfl magazine.pdf --output csv --output-file findings.csv
pdfl run prepress.pdfl magazine.pdf --fail-on warning # الوضع الصارم
{
"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 هو المفتاح الأول، ليقرر المستهلك قبل أن يحلّل ما بعده. ولا يرتفع إلا إذا كان قارئ المخرَج السابق سينكسر؛ وإضافة حقل لا ترفعه.

صيغتان إضافيتان، لتظهر النتيجة حيث ينظر الفريق أصلًا، لا في سجلّ لا يفتحه أحد.

Terminal window
# 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.sarif

يقارن نسختين: النص والبنية والبيانات الوصفية.

Terminal window
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 أدنى تشابه مقبول
Terminal window
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
  • تُحاذى الصفحات بالمحتوى لا بالرقم: فإدراج صفحة في الوسط لا يجعل كل ما بعدها فروقًا. ويعمل على مستندات تتجاوز ألف صفحة.
  • يحصل كل زوج على درجة تشابه وعيّنة من الأسطر المتغيرة (- محذوف، + مضاف).
  • تغيّر البيانات الوصفية تحذير؛ وتغيّر النص دون العتبة خطأ، وفوقها تحذير.
  • الدرجة الإجمالية في الحقل similarity من التقرير.
page 4 → 4: similarity 97.8% | -original title | +revised title

يقارن ملفَّي PDF بما يبدوان عليه، صفحةً صفحة.

Terminal window
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: النص في الثلاثة متطابق.

Terminal window
# المستند كله، بصيغة JSON
pdfl 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 حين يكون الموضع نفسه هو ما يجري فحصه.

يكتب --viewer diff/ مجلدًا فيه ثلاث صور PNG لكل صفحة وملف index.html واحد. ولا يعتمد على شيء البتة — لا CDN ولا مجمِّع حزم ولا خادم. افتح الملف، أو اضغط المجلد وأرسله لمن يعتمد إعادة الطبع.

ثلاثة ألواح متجاورة، وكلها على الصفحة نفسها دائمًا:

اللوح ما يعرضه
Original صفحة الملف الأول، كما هي
New صفحة الملف الثاني، كما هي
Difference كلاهما، وفوقهما ما تغيّر ملوّنًا — اسحب للمسح

تحمل الألواح الثلاثة الزوج نفسه من الأشرطة — قائم وأفقي — في الموضع نفسه، ويتحركان في الثلاثة دفعة واحدة. وكلاهما يتبع المؤشّر — لا شيء تمسكه ولا شيء تصوّبه، فالتقاطع هو ببساطة حيث تكون الفأرة. وموضع تقاطعهما هو ركن ما يُكشف.

في لوح Difference يقطع الشريطان: يظهر الملف الجديد يمين القائم وأسفل الأفقي، ويظهر الأصل فيما عدا ذلك. وما لم تحرّكه، يبقى الأفقي في الأعلى فيكون القائم مسحًا عاديًا بكامل الارتفاع — أنزِل الأفقي حين يكون التغيّر المطلوب في شريط عرضي لا في عمود. أما في اللوحين الآخرين فالشريطان مسطرتان على العمود نفسه والسطر نفسه من الصفحة.

والموضعان نسبتان من الصفحة لا من اللوح، فيبقيان كما هما عند تغيير الصفحة أو تغيير حجم النافذة.

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

تُلوَّن الفروق في مواضعها، واللون يقول من أي نوع هي:

اللون المعنى
أحمر حبر اختفى من الملف الجديد
أخضر حبر جديد فيه
أزرق الوزن نفسه بلون مختلف

تُقاس الألواح الثلاثة على النافذة، فتظهر المقارنة كاملة على الشاشة دون تمرير، وتحافظ على نسب الصفحة مهما كان شكل النافذة. وحين يختلف الملفان في حجم صفحة ما — صارت إحداها عرضية مثلًا — تُعرض كل واحدة كاملة داخل الإطار المشترك بدل أن تُمطّ لملئه.

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

يفتح على الصفحات المختلفة. في مستند من مئتي صفحة تحرّكت منه ثلاث، فتلك الثلاث هي سبب فتحه أصلًا؛ وAll يعيد البقية. وتتبع الأسهم و← → التصفية، فتتخطى ما يخفيه الشريط بدل أن تحطّ عليه. وإن لم تختلف أي صفحة، قال زر التصفية ذلك وبقي معطّلًا بدل أن يفرغ الشريط.

تحويل مستند طويل إلى نقاط بدقة 300 نقطة/بوصة يستغرق دقائق، لذا ترسم كل مرحلة شريطًا على stderr: واحد لكل ملف يُحوَّل، وواحد للمقارنة، وواحد لكتابة العارض.

rasterising approved.pdf [############------------] 98/207

ولا يُرسم إلا إذا كان stderr طرفية. فالشريط يعمل بالعودة إلى أول السطر والكتابة فوقه، ولا مؤشّر في ملف السجل يتحرّك، فتتجمّع في التشغيلة المُعاد توجيهها آلاف الشظايا بدلًا منه. وعند إعادة التوجيه يصمت، وتظل الرسائل العادية تخرج. و--quiet يُسكته في كل الأحوال.

تعمل المقارنة على كل المعالجات افتراضيًا. على 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. تُجمَّع الصفحات مرة أخرى بترتيب أرقامها، فتخرج التشخيصات وترتيبها وبصماتها متطابقة مهما كانت القيمة — يضمن ذلك اختبار — وتخرج ملفات العارض متطابقة بايتًا ببايت.


يراقب مجلدًا ويدقّق كل ملف PDF يصل أو يتغيّر.

Terminal window
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 — يعالج الموجود ثم يخرج
Terminal window
# مجلد استلام في مطبعة، بلا انقطاع
pdfl watch inbox/ --script preflight.pdfl --output-dir reports/ --report html
# تشغيل دفعي للتكامل المستمر: يخرج بأسوأ رمز صادفه
pdfl watch inbox/ --script preflight.pdfl --once
echo "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 “السجلّ: إتمام دفعة انقطعت”

خمسة آلاف ملف، ويعيد الجهاز الإقلاع عند الأربعة آلاف. وبلا سجلّ تبدأ الجولة التالية من الملف الأول.

Terminal window
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: ملف سيئ واحد يجب ألا يوقف الدفعة كلها”
Terminal window
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).


يطبّق عمليات fix:: ويحفظ ملف PDF جديدًا. التفاصيل في الفصل 8.

Terminal window
pdfl fix <input.pdf> <script.pdfl> --output <output.pdf> [options]
الخيار الافتراضي الغرض
--output <ملف> — ملف PDF الناتج (إلزامي)
--dry-run — يسرد العمليات دون أن يحفظ
--report json|csv|html|pdf|sarif|junit json صيغة التقرير
--report-file <ملف> — يكتب التقرير في ملف
Terminal window
pdfl fix original.pdf normalize.pdfl --output out.pdf --dry-run # للمعاينة فقط
pdfl fix original.pdf normalize.pdfl --output fixed.pdf # للتطبيق

نظرة عامة على ملف PDF بلا نص برمجي.

Terminal window
pdfl inspect <file.pdf>

يعطي --json الملخّص نفسه بصيغة بيانات.

File: magazine.pdf
Size: 26 KB (27284713 bytes)
SHA-256: af1029842e5bfeae338ead82fb449ef851be742b1d63117c12596e3ea123a616
Pages: 120
Page size: 496 x 709 pt
Boxes: MediaBox, TrimBox, BleedBox
Metadata:
Title: Example Magazine
Creator: Adobe InDesign 19.3
Fonts: 26
ABCDEF+Helvetica — embedded
Arial — NOT embedded
Images: 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

أول أمر يُشغَّل عند وصول ملف جديد: في ثوانٍ تعرف هل يستحق الفتح.


يحلّل نصًا برمجيًا دون تنفيذه ويبلّغ عن مشكلات الجودة.

Terminal window
pdfl lint <script.pdfl>

يعطي --json التحذيرات نفسها بصيغة بيانات.

ما يكشفه:

  • المتغيرات ومعاملات الكتل والدوال المصرَّح بها وغير المستعملة قط (سبقها بـ _ لكتم التحذير: _page)
  • الفحوص المكرّرة أو الفارغة
  • فضاءات الأسماء غير المعروفة (text:: وstruct:: وvisual:: وprepress:: وcodes:: وfix:: وdata::)
  • assert / require خارج أي فحص
  • استعمال fix:: (وهو لا يعمل إلا مع pdfl fix)
Terminal window
$ pdfl lint profile.pdfl
profile.pdfl: warning: variable 'LIMIT' declared and never used
profile.pdfl: warning: check "Fonts" declared 2 times

وعند وجود تحذيرات يكون رمز الخروج 1 — صالح للتكامل المستمر.


ينسّق النص البرمجي: إزاحة بمسافتين، ومسافات متسقة، وضغط الأسطر الفارغة. وتبقى التعليقات والوحدات (3mm تظل 3mm) كما هي.

Terminal window
pdfl fmt <script.pdfl> # ينسّق في مكانه
pdfl fmt <script.pdfl> --check # لا يغيّر شيئًا؛ الرمز 1 إن لم يكن منسَّقًا
Terminal window
# فرض معيار الفريق في التكامل المستمر
for f in profiles/*.pdfl; do pdfl fmt "$f" --check || exit 1; done

يولّد التوثيق من النص البرمجي نفسه.

Terminal window
pdfl doc <script.pdfl> [--output markdown|html|json]

ويُخرج: الملف التعريفي، وجدول الثوابت، والدوال، والاستيرادات، ولكل فحص وسومه وما يدقّقه (رسائل assert تصير الأوصاف).

Terminal window
pdfl doc prepress.pdfl > docs/prepress-profile.md
pdfl doc prepress.pdfl --output html > profile.html

وهو المُخرَج الذي يشرح لمسؤول الإنتاج الذي لا يقرأ الشيفرة ما الذي يدقّقه الملف التعريفي.


يحزم النصوص البرمجية والبيانات في ملف .pdflpkg قابل للتوزيع.

Terminal window
pdfl pack <folder> [--name <name>] [--version <version>] [--output <file>]

يجمع تكراريًا ملفات .pdfl و.csv و.txt و.json من المجلد، ويرفق manifest.json يسجّل بصمة SHA-256 لكل ملف. والحزم حتمي: المجلد نفسه يُنتج البايتات نفسها.

أما ملفات الجداول (.xlsx و.xls و.ods) فلا تُحزم، وpack يقول أي ملف ترك. إذ لا تفتحها أي دالة في data::، فحزمها يعني تسليم حزمة تُثبَّت بلا مشكلة ثم تخفق عند أول بحث.

Terminal window
pdfl pack profiles/print-shop --name print-profile --version 1.0.0

يثبّت حزمة محلية مع التحقق من بصمات البيان.

Terminal window
pdfl add print-profile.pdflpkg
# يثبّت في ./pdfl_profiles/[email protected]/
pdfl run pdfl_profiles/[email protected]/prepress.pdfl file.pdf

وإن لم تطابق بصمة أي ملف ما هو مسجَّل رُفض التثبيت — فالحزمة التالفة أو المعبوث بها لا تدخل.

المستودعات البعيدة والتواقيع الرقمية ليست ضمن هذا الإصدار: add يثبّت من ملف محلي.


يشغّل النص البرمجي على كل ملف PDF في مجلد، ويقارن كل تقرير بالتقرير المسجَّل بجانبه. فالملف التعريفي الذي يبدأ بإيجاد شيء مختلف يُسقِط اختبارًا، بدل أن يفاجئ أحدًا في آخر السلسلة.

Terminal window
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
Terminal window
# أول مرة: سجّل ما يجده النص البرمجي اليوم
pdfl test prepress.pdfl --update
# وبعدها
pdfl test prepress.pdfl
ok approved.pdf
FAIL 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 فيه.

كل حالة تعمل في عملية pdfl خاصة بها، فـ--jobs يحوّل المجموعة إلى تواز حقيقي: في ثمانية ملفات من 41 صفحة، استغرق --jobs 1 نحو 8.9 ثانية و--jobs 8 نحو 1.1 ثانية. أما الخيوط داخل عملية واحدة فما كانت لتنجح — إذ يُسلسِل pdfium كل نداء خلف قفل واحد، وقد قِيست نسخة الخيوط أبطأ من التنفيذ المتسلسل.

والقيمة الافتراضية 1 لأن كل مَهمّة عملية تحمل مستندًا في الذاكرة، وهذه الأداة موجودة لملفات قد تكون ضخمة. ارفعها إذا كانت حالاتك عادية: --jobs 0 يعطي واحدة لكل معالج.

ولا يتغيّر ترتيب المخرَج بتغيّر --jobs: تُحكَم الحالات بالترتيب الذي وُجدت به، أيًّا كانت العملية التي انتهت أولًا.

والحالة التي لا يُقرأ ملفها تُحكَم كغيرها — إذ يحمل تقريرها السبب بوصفه ملاحظة، فيصير «هذا الملف يجب أن يُرفض لتعذّر قراءته» اختبارًا بذاته. وذلك التقرير يسمّي الملف كما مُرِّر، فسجّل خطوط الأساس بـ--dir نسبي إن كنت ستحفظها في المستودع.

تصل --var إلى كل حالة دون تغيير — قيمة واحدة للتشغيلة كلها، لا واحدة لكل ملف. وبدونها لا يمكن اختبار نص برمجي يقرأ vars.* أبدًا: تُخفق كل حالة بعبارة “was not provided”، أيًّا كان ملف PDF.


يطبع على stdout نصّ الإكمال التلقائي لصدفتك.

Terminal window
pdfl completions <bash|zsh|fish|elvish|powershell>
Terminal window
# bash، للمستخدم الحالي
pdfl completions bash > ~/.local/share/bash-completion/completions/pdfl
# zsh — في أي موضع ضمن ‎$fpath
pdfl completions zsh > ~/.zfunc/_pdfl
# fish
pdfl completions fish > ~/.config/fish/completions/pdfl.fish

لا يُكتب شيء آخر على stdout، فيمكن توجيه المخرَج مباشرة إلى مجلّد الإكمال. وأعِد توليده بعد كل ترقية: فالنصّ يُبنى من أوامر الملف التنفيذي الذي طبعه ومن خياراته.


← المكتبة القياسية · الفهرس · التالي: وصفات عملية →

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