PDFLang ist so entworfen, dass Menschen es lesen können, die keine Programme
schreiben. Keine Klassen, keine Vererbung, keine Typdeklarationen, keine
Semikolons. Ein Skript ist eine Sammlung von Prüfungen, fast in natürlicher
Sprache geschrieben.
Standardmäßig ist ein fehlschlagender Check ein Fehler und der Lauf endet
mit 2. Ein Check kann sich stattdessen als beratend deklarieren:
check"Bildauflösung"severity:warning {
require!visual::detect_low_resolution(300)
}
error (Vorgabe), warning und info. Warnung und Hinweis lassen den Lauf
nicht scheitern — sie enden mit 1 und 0 — außer mit --fail-on warning, womit
die CI die Strenge bestimmt, ohne das Skript zu ändern.
tags: und severity: dürfen in beliebiger Reihenfolge stehen.
Ein Laufzeitfehler im Check — ein Tippfehler in einer Variablen, eine fehlende
Datei — bleibt ein Fehler, ganz gleich was der Check deklariert hat. Ein
kaputtes Skript ist nicht beratend.
Jede Prüfung wird mit require oder assert geschrieben. Der einzige
Unterschied ist die Meldung, die bei einem Fehlschlag im Bericht steht.
check"Comparing both forms" {
// require: Die Meldung entsteht aus dem Ausdruck selbst.
// Bei einem Fehlschlag zeigt der Bericht:
// "requirement not met: doc.page_count > 0"
requiredoc.page_count >0
// assert: Sie schreiben die Meldung, die die empfangende Person liest.
// Bei einem Fehlschlag erscheint sie unverändert:
// "PDF has no title in its metadata"
assertdoc.title !="", "PDF has no title in its metadata"
}
Faustregel:require, wenn der Ausdruck für sich spricht; assert, wenn
die Person, die den Bericht liest, das Problem verstehen soll, ohne das Skript
zu kennen.
Ein Fehlschlag hält die übrigen Prüfungen nicht auf
assertdoc.page_count >100, "too few pages"// schlägt fehl
assertdoc.title !="", "no title"// läuft trotzdem
assertdoc.author !="", "no author"// diese auch
}
Der Bericht listet alle Probleme auf einmal. Das ist Absicht: Wer die Datei
bekommt, will die vollständige Korrekturliste, nicht eine Korrektur nach der
anderen.
Zwischen den checks gilt dasselbe — trifft ein check auf einen Laufzeitfehler
(etwa eine unbekannte Variable), wird daraus eine Diagnose, und die übrigen
checks laufen weiter.
// Längeneinheiten werden in Punkt umgerechnet (1 pt = 1/72 Zoll):
a=3mm// 8,5039... pt
b=2.5cm // 70,866... pt
c=1in// 72 pt
d=10pt// 10 pt
// Prozent behält den Zahlenwert:
grenze=300%// 300
requirea<b// alles in Punkt, direkt vergleichbar
requirec==72.0
requiregrenze==300
}
3mm statt 8.504 schreiben zu können, ist genau der Punkt: Es liest sich
natürlich für jemanden, der in Millimetern denkt, und die Umrechnung geht nicht
daneben.
"Seite #{page.number}: #{page.tac}% Farbe, Grenze #{LIMIT}%"
}
}
und als Wächter um Anweisungen, ohne else:
check"Den Umschlag nur prüfen, wenn es einen gibt" {
ifdoc.page_count >1 {
requiredoc.pages.first().width >0
}
}
else if verkettet ohne zusätzliche Klammern:
groesse=ifdoc.page_count >500 { "groß" }
elseifdoc.page_count >50 { "mittel" }
else { "klein" }
Drei Dinge, die man wissen sollte:
Ein Zweig, der nicht läuft, liefert null. Ein if ohne else mit
falscher Bedingung ist null — und das ist falsch, also direkt prüfbar.
Jeder Zweig hat einen eigenen Gültigkeitsbereich, wie ein Block oder eine
Funktion. Eine im Zweig angelegte Variable existiert danach nicht mehr. Einer
Variablen zuzuweisen, die außerhalb schon existiert, ändert weiterhin jene.
Die { nach der Bedingung öffnet den Rumpf. Endet die Bedingung selbst in
einem Block, setzen Sie sie in Klammern, sonst würde die Klammer des Rumpfes
als die jenes Blocks gelesen:
// falsch: die { wird als Rumpf des if genommen
// if doc.pages.all { |p| p.width > 0 } { ... }
// richtig
if (doc.pages.all { |p|p.width >0 }) {
requiredoc.page_count >0
}
if dient dazu, einen Wert zu wählen oder eine Anweisung zu schützen — nicht
dazu, eine gescheiterte Prüfung durch eine stille zu ersetzen. Eine Validierung,
die scheitern soll, scheitert weiterhin; siehe 1.2.
"page #{page.number} has no page number in the footer"
}
Zur Syntax: Endet der Ausdruck nach on mit einer Eigenschaft (etwa
on doc.pages), setzen Sie ihn in Klammern; sonst würde die geschweifte
Klammer des Rumpfes als Block dieses Aufrufs gelesen:
Üblich sind Großbuchstaben für Konstanten und Kleinbuchstaben für Variablen. Die
Sprache erzwingt das nicht, aber die Beispiele und die mitgelieferten Profile
halten sich daran.
--var name=wert bei pdfl run, pdfl test und pdfl watch erreicht das Skript
als vars.name, immer als Text. test und watch reichen denselben Wert an
jeden Fall oder jede Datei weiter — ein Kundenname für den ganzen Lauf, nicht
einer je Datei. Das verhindert, dass aus einem Profil fünf fast gleiche Kopien werden:
check"Auftrag passt zur Bestellung" {
assertdoc.title.contains(vars.auftrag),
"die Datei sagt \"#{doc.title}\", der Auftrag ist #{vars.auftrag}"
Ein nicht übergebener Name ist ein Fehler, der das Flag nennt, das ihn liefern
würde — keine leere Zeichenkette: ein Check, der gegen nichts vergleicht,
bestünde sonst und meldete eine Datei, die niemand geprüft hat.