11. Comandos del CLI

← Biblioteca estándar · Índice · Siguiente: Recetas →

Doce comandos: cuatro que trabajan con PDF, cuatro sobre los scripts, dos de distribución y dos de licencias.

Comando Qué hace
run Valida un PDF con un script
compare Compara dos versiones de un PDF
watch Vigila una carpeta y valida lo que llega
fix Aplica correcciones y guarda un PDF nuevo
inspect Resumen rápido de un PDF
lint Analiza un script sin ejecutarlo
fmt Formatea un script
doc Genera la documentación de un script
pack Empaqueta perfiles y bases
add Instala un paquete
fingerprint Huella del equipo
license Verificación de licencia

Códigos de salida

Todos los comandos que validan usan la misma convención:

Código Significado
0 Todo pasó
1 Solo advertencias
2 Errores de validación, o PDF ilegible
3 Error de sintaxis en el script

En scripts de shell:

pdfl run perfil.pdfl archivo.pdf > informe.json
case $? in
  0) echo "aprobado" ;;
  1) echo "aprobado con salvedades" ;;
  2) echo "rechazado — mira informe.json" ;;
  3) echo "error en el script de validación" ;;
esac

pdfl run

Valida un PDF con un script.

pdfl run <script.pdfl> <entrada.pdf> [opciones]
Opción Por defecto Qué hace
--output json|csv|html|pdf json Formato del informe
--output-file <archivo> Escribe en un archivo en lugar del stdout
--fail-on error|warning error Con warning, las advertencias también dan exit 2
--verbose Información adicional por stderr
# Informe JSON en la terminal
pdfl run prepress.pdfl revista.pdf

# HTML para enviar al cliente
pdfl run prepress.pdfl revista.pdf --output html --output-file informe.html

# PDF de auditoría (el formato pdf siempre escribe en archivo)
pdfl run prepress.pdfl revista.pdf --output pdf --output-file informe.pdf

# CSV para hoja de cálculo
pdfl run prepress.pdfl revista.pdf --output csv --output-file hallazgos.csv

# Estricto: las advertencias también suspenden
pdfl run prepress.pdfl revista.pdf --fail-on warning

El informe JSON

{
  "script_name": "prepress.pdfl",
  "input_file": "revista.pdf",
  "profile": "offset-revista",
  "status": "FAIL",
  "total_pages_analyzed": 120,
  "error_count": 2,
  "warning_count": 0,
  "info_count": 0,
  "diagnostics": [
    {
      "id": "PDFL-001",
      "severity": "error",
      "check_name": "Cobertura de tinta",
      "message": "page 7: 324% ink (limit 300%)",
      "line": 12
    }
  ]
}

El mismo PDF con el mismo script genera siempre el mismo informe, byte a byte: se puede versionar y comparar en CI.


pdfl compare

Compara dos versiones de un PDF: texto, estructura y metadatos.

pdfl compare <v1.pdf> <v2.pdf> [opciones]
Opción Por defecto Qué hace
--output json|csv|html|pdf json Formato
--output-file <archivo> Escribe en un archivo
--normalize Ignora mayúsculas y espaciado
--ignore-dates Enmascara las fechas antes de comparar
--similarity-threshold <0-100> 100 Similitud mínima aceptable
# Comparación simple
pdfl compare aprobado_v1.pdf nuevo_v2.pdf

# Tolerando pequeñas diferencias de formato y de fechas
pdfl compare aprobado_v1.pdf nuevo_v2.pdf --normalize --ignore-dates

# Acepta hasta un 1% de diferencia; por debajo de eso es un error
pdfl compare v1.pdf v2.pdf --similarity-threshold 99 \
  --output html --output-file diff.html

Cómo funciona

  • Las páginas se alinean por contenido, no por número: si se insertó una página en medio, el comparador lo detecta en vez de marcar como distinto todo lo que viene después. Funciona en documentos de más de mil páginas.
  • Cada página alineada recibe una nota de similitud y una muestra de las líneas que cambiaron (- salió, + entró).
  • Los metadatos distintos generan advertencia; el texto alterado genera error si queda por debajo del umbral, y advertencia si queda por encima.
  • El informe trae el campo similarity con la nota general.
page 4 → 4: similarity 97.8% | -título original cuentos | +título revisado

pdfl watch

Vigila una carpeta y valida cada PDF que llega o que cambia.

pdfl watch <carpeta> --script <script.pdfl> [opciones]
Opción Por defecto Qué hace
--pattern <glob> *.pdf Qué archivos procesar
--exclude <glob> Cuáles ignorar
--output-dir <carpeta> junto al PDF Dónde escribir los informes
--depth <n> 1 Niveles de subcarpeta
--debounce <ms> 1000 Espera a que el archivo deje de copiarse
--report json|csv|html|pdf json Formato de los informes
--fail-fast Se detiene en el primer error
--once Procesa lo que ya hay y sale
# Carpeta de entrada de la imprenta, funcionando de continuo
pdfl watch entrada/ --script preflight.pdfl --output-dir informes/ --report html

# Modo lote para CI: procesa todo y sale con el peor código
pdfl watch entrada/ --script preflight.pdfl --once
echo "resultado: $?"

# Ignorando borradores
pdfl watch entrada/ --script preflight.pdfl \
  --pattern "*.pdf" --exclude "*_borrador*"

El debounce existe porque los archivos grandes llegan poco a poco: el watch solo procesa cuando el archivo deja de cambiar, y así evita leer un PDF a medias.

Los informes salen como <nombre>.report.json (o .csv, .html, .pdf).


pdfl fix

Aplica las operaciones fix:: y guarda un PDF nuevo. Los detalles están en el capítulo 8.

pdfl fix <entrada.pdf> <script.pdfl> --output <salida.pdf> [opciones]
Opción Qué hace
--output <archivo> PDF de salida (obligatorio)
--dry-run Lista las operaciones sin guardar
--report json|csv|html|pdf Formato del informe
--report-file <archivo> Escribe el informe en un archivo
# Ver qué se haría, sin tocar nada
pdfl fix original.pdf normalizar.pdfl --output salida.pdf --dry-run

# Aplicarlo de verdad
pdfl fix original.pdf normalizar.pdfl --output corregido.pdf

pdfl inspect

Resumen rápido de un PDF, sin script.

pdfl inspect <archivo.pdf>
File:     revista.pdf
Size:     26 KB (27284713 bytes)
SHA-256:  af1029842e5bfeae338ead82fb449ef851be742b1d63117c12596e3ea123a616

Pages:    120
Page size: 496 x 709 pt
Boxes:    MediaBox, TrimBox, BleedBox

Metadata:
  Title: Revista Ejemplo
  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

Es el primer comando que conviene ejecutar cuando llega un archivo nuevo: en segundos sabes si merece la pena abrirlo.


pdfl lint

Analiza un script sin ejecutarlo y señala problemas de calidad.

pdfl lint <script.pdfl>

Detecta:

  • variables, parámetros de bloque y funciones declarados y nunca usados (ponles el prefijo _ para silenciarlos: _page)
  • checks duplicados o vacíos
  • espacio de nombres desconocido (text::, struct::, visual::, prepress::, codes::, fix::, data::)
  • assert/require fuera de cualquier check
  • uso de fix:: (que solo se ejecuta en pdfl fix)
$ pdfl lint perfil.pdfl
perfil.pdfl: warning: variable 'LIMITE' declared and never used
perfil.pdfl: warning: check "Fuentes" declared 2 times

Sale con el código 1 si hay advertencias, así que sirve en CI.


pdfl fmt

Formatea el script: sangría de 2 espacios, espaciado coherente y líneas en blanco colapsadas. Conserva los comentarios y las unidades (3mm sigue siendo 3mm).

pdfl fmt <script.pdfl>            # formatea en el sitio
pdfl fmt <script.pdfl> --check    # no modifica; sale con 1 si no cumple el formato
# En CI, para garantizar el formato en todo el equipo
for f in perfiles/*.pdfl; do pdfl fmt "$f" --check || exit 1; done

pdfl doc

Genera la documentación de un script a partir del propio código.

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

Produce: el perfil, una tabla de constantes, las funciones, los imports y —para cada check— sus etiquetas y qué valida (los mensajes de los assert se convierten en la descripción).

# Markdown para el repositorio
pdfl doc prepress.pdfl > docs/perfil-prepress.md

# HTML para enviárselo a quien no lee código
pdfl doc prepress.pdfl --output html > perfil.html

Es el artefacto para que el jefe de producción entienda qué valida el perfil sin abrir el script.


pdfl pack

Empaqueta scripts y bases en un archivo .pdflpkg distribuible.

pdfl pack <carpeta> [--name <nombre>] [--version <versión>] [--output <archivo>]

Incluye los .pdfl, .csv, .txt, .json y .xlsx de la carpeta (de forma recursiva), con un manifest.json que registra el SHA-256 de cada archivo. El paquete es determinista: la misma carpeta genera bytes idénticos.

pdfl pack perfiles/imprenta --name perfil-imprenta --version 1.0.0
# crea perfil-imprenta.pdflpkg

pdfl add

Instala un paquete local comprobando los hashes del manifiesto.

pdfl add <paquete.pdflpkg> [--dir <carpeta>]
pdfl add perfil-imprenta.pdflpkg
# instala en ./pdfl_profiles/perfil-imprenta@1.0.0/

pdfl run pdfl_profiles/perfil-imprenta@1.0.0/prepress.pdfl archivo.pdf

Si algún archivo tiene un hash distinto del registrado, la instalación se rechaza: un paquete corrupto o manipulado no entra.

El repositorio remoto y la firma digital no forman parte de esta versión: el add instala desde archivos locales.


pdfl fingerprint

La huella de este equipo: el valor que el cliente envía para recibir una licencia.

pdfl fingerprint generate

Sale sola por el stdout; el texto de apoyo va por stderr, así que pdfl fingerprint generate 2>/dev/null da un valor listo para canalizar.

$ pdfl fingerprint generate
Device fingerprint for this machine:

1a7553e711c53306be083ea8927c9cbe

Send it to your vendor to be issued a license.
It changes if the operating system is reinstalled.

La huella sale del identificador de máquina del sistema (/etc/machine-id, IOPlatformUUID o MachineGuid). No sobrevive a una reinstalación del sistema, y las máquinas virtuales clonadas de la misma imagen comparten el valor. Para CI y contenedores —donde la máquina se destruye en cada ejecución— existe la licencia seatless, que no mira el equipo.


pdfl license

Comprueba una licencia recibida. pdfl solo verifica: quien las emite es el proveedor, con la clave privada, que no acompaña al binario.

pdfl license check <token>
$ pdfl license check "$(cat license.txt)"
signature:  ok
customer:   Example Print Shop
expires:    2027-01-01
features:   all
device:     1a7553e711c53306be083ea8927c9cbe
this machine: matches

Sale con 0 si la licencia vale para esta máquina y con 2 si no, incluso cuando la firma es correcta pero la licencia es de otro equipo, caso en el que el mensaje lo dice exactamente.

Opción Qué hace
--pubkey <base64> Clave pública a usar, en lugar de la compilada en el binario

Dónde se exige la licencia

run, fix, watch, compare e inspect se detienen con el código 2 si no hay una licencia válida para este equipo. fingerprint generate nunca la exige: es el que produce el valor necesario para pedir una licencia. lint, fmt, doc y pack solo tocan scripts, nunca un PDF, y tampoco la exigen.

El token se busca en este orden:

  1. $PDFL_LICENSE — el camino para CI, donde se convierte en un secreto
  2. ~/.config/pdfl/license
  3. pdfl.license junto al ejecutable
$ pdfl run perfil.pdfl archivo.pdf
error: no license found

Run 'pdfl fingerprint generate' and send the value to your vendor
to be issued a license. Then set PDFL_LICENSE, or save the token to
/home/tu_usuario/.config/pdfl/license.

Una licencia seatless tiene un tope de ejecuciones, contado en ~/.config/pdfl/state. El mismo archivo guarda cuándo fue la última ejecución y se niega a ejecutarse si el reloj va hacia atrás, con una hora de tolerancia, para no pelearse con la sincronización horaria.


← Biblioteca estándar · Índice · Siguiente: Recetas →