Si tuviera que ponerle nombre a lo que hago, diría que es hacerle las cosas fáciles al foxero. Con los años he visto que eso tiene un precio, y lo paga quien hace la herramienta: cuanto más sencillo queda algo por fuera, más trabajo hay por dentro.
FoxCli es el ejemplo que tengo más a mano. Cuando empecé, un comando de consola se declaraba con la cláusula HELP y una línea con su propia gramática. Hoy es un método de VFP con un comentario encima. En este artículo escribo el mismo comando de las dos formas, compilo las dos y las pruebo con las mismas órdenes, para que se vea qué ha cambiado para ti y qué ha tenido que pasar por detrás.
El comando
Es el exportar de dbf, la herramienta del tutorial Tu primera CLI en Visual FoxPro con FoxCli, con una opción más para que haya un número de por medio:
dbf exportar customers.dbf clientes.csv
dbf exportar customers.dbf clientes.xls --formato xls
dbf exportar customers.dbf muestra.csv --max 5Recibe dos argumentos sin nombre, la tabla y el fichero que se crea. Tiene una opción --formato que solo admite csv o xls y vale csv si no se escribe, y otra --max con un número entero, para copiar solo los primeros registros. Las dos opciones tienen su forma corta, -f y -m.
Los métodos auxiliares, Abrir y Copiar, son iguales en las dos versiones, así que no los repito. Lo que cambia es cómo se declara el comando y cómo le llegan los valores.
Antes: la cláusula HELP
DEFINE CLASS DbfCommand AS FoxCliCommand OF foxcli.prg OLEPUBLIC
FUNCTION Run(oCon, oArgs)
LOCAL lnExit
lnExit = EXIT_OK
DO CASE
CASE LOWER(oArgs.Command()) == "exportar"
lnExit = THIS.Exportar(oCon, oArgs)
ENDCASE
RETURN lnExit
ENDFUNC
PROTECTED FUNCTION Exportar(oCon, oArgs) ;
HELP "exportar <tabla:str![La tabla (.dbf)]> <destino:str![El fichero que se crea]> --formato,-f:csv|xls[Formato del fichero] --max,-m:int[Como mucho tantos registros] : Copia una tabla a un fichero CSV o XLS"
LOCAL lcTabla, lcDestino, lcFormato, lnMax
lcTabla = oArgs.Positional(1)
lcDestino = oArgs.Positional(2)
lcFormato = oArgs.Get("formato", "csv")
lnMax = oArgs.AsNumber("max", 0)
IF !THIS.Abrir(lcTabla)
RETURN EXIT_NO_TABLE
ENDIF
THIS.Copiar(lcDestino, lcFormato, lnMax)
USE
RETURN EXIT_OK
ENDFUNCFunciona bien, pero para escribirlo hace falta saber unas cuantas cosas que no son VFP:
- La gramática de la cláusula.
<tabla:str!>es un argumento sin nombre, obligatorio por el!. Lo que va entre corchetes es la descripción de la opción que tiene delante.,-fañade la forma corta,:intpone el tipo,:csv|xlscierra la lista de valores, y lo que va detrás de los dos puntos del final describe el comando. Además,<>y[]no significan aquí lo mismo que en la línea de uso de cualquier ayuda, donde los corchetes marcan lo opcional. - Que todo va en una sola cadena, y que esa cadena no puede pasar de 255 caracteres. Con 256, VFP no compila el fichero, y el error no dice nada de la longitud. Esta tiene 202: con descripciones un poco más largas ya no cabría.
- Que hay que escribir
Runy repartir los comandos con unDO CASE. Cada comando nuevo es una rama más, y un comando que declaras y te olvidas de añadir alCASEno hace nada. - Cómo se lee cada valor. Los argumentos sin nombre, por su posición con
Positional(). Las opciones, por su nombre largo aunque quien llame haya escrito-f. El número conAsNumber()y el texto conGet(). - Que los valores por defecto no se declaran: van en el código, en cada
Get(). Por eso la ayuda no los enseña.
Ahora: un método con un comentario
DEFINE CLASS DbfCommand AS FoxCliCommand OF foxcli.prg OLEPUBLIC
*!* Copia una tabla a un fichero CSV o XLS
*!* @tcTabla <> La tabla (.dbf)
*!* @tcDestino <> El fichero que se crea
*!* @tcFormato -f :csv|xls =csv Formato del fichero
*!* @tnMax -m =0 Como mucho tantos registros
PROCEDURE Exportar(tcTabla AS String, tcDestino AS String, tcFormato AS String, tnMax AS Integer)
IF !THIS.Abrir(tcTabla)
RETURN EXIT_NO_TABLE
ENDIF
THIS.Copiar(tcDestino, tcFormato, tnMax)
USE
ENDPROCEs un PROCEDURE como cualquier otro, y se entiende sin saber nada de FoxCli:
- Un método público de la clase es un comando, y su nombre en minúsculas es el verbo. No hay
RunniDO CASE. - Cada parámetro es una opción, con el nombre sin el prefijo:
tcFormatoes--formatoytnMaxes--max. El tipo sale delAS. - El bloque
*!*añade lo que la firma no dice.<>hace que el argumento vaya sin nombre,-fes la forma corta,:csv|xlses la lista de valores y=csvel valor por defecto. Un parámetro sin=es obligatorio, y el resto de la línea es la descripción. - Los valores llegan en los parámetros, ya convertidos.
tnMaxes un número, ytcFormatollega en minúsculas aunque alguien escriba-f XLS, porque un valor de la lista se entrega tal como está declarado. - Sin
RETURN, el comando termina con 0.RETURN EXIT_NO_TABLEtermina con el 10 que declaré enfoxcli.h.
Los símbolos son casi los mismos que en HELP, pero ahora cada uno va en su línea, junto al parámetro al que se refiere, y no hay ningún límite de longitud que vigilar.
Lo que ve quien usa la CLI
Compilé las dos versiones y les pasé las mismas órdenes. La salida es la misma, con una diferencia a favor de la nueva: la ayuda enseña los valores por defecto, porque ahora están declarados.
> dbf exportar --help
exportar -- Copia una tabla a un fichero CSV o XLS
Uso: dbf exportar <tabla> <destino>
Opciones:
-f, --formato csv|xls Formato del fichero (por defecto: csv)
-m, --max <int> Como mucho tantos registros (por defecto: 0)
Argumentos:
tabla La tabla (.dbf) (requerido)
destino El fichero que se crea (requerido)Los errores también son iguales en las dos. Una opción mal escrita:
> dbf exportar customers.dbf clientes.csv --fromato xls
dbf: opción desconocida '--fromato'. Quizá querías '--formato'
dbf: sobra el argumento 'xls': 'exportar' lleva 2 posicionalesUn valor que no es del tipo declarado, o que no está en la lista:
> dbf exportar customers.dbf clientes.csv --max diez
dbf: '--max diez' no es un número
> dbf exportar customers.dbf clientes.csv -f txt
dbf: '--formato txt' no es válido. Valores: csv, xlsEn todos esos casos la CLI enseña además la ayuda del comando y sale con código 2, sin llegar a ejecutar tu código. Con la línea bien escrita sale el resultado, y si la tabla no existe, el 10:
> dbf exportar customers.dbf muestra.csv -m 5
5 registros copiados a muestra.csv
> dbf exportar facturas.dbf x.csv
dbf: no encuentro la tabla facturas.dbfLos CSV que dejaron las dos versiones son idénticos.
Lo que hay detrás
Del antes al ahora tú escribes menos y tienes que saber menos, que es lo que yo buscaba. El trabajo sigue estando, pero en FoxCli, y cada cosa que ya no tienes que pensar es una decisión que he tomado yo. Casi todas tienen su caso raro:
- Sacar el nombre de la opción del parámetro.
tcNombreda--nombre, perototalse queda en--total, porque el prefijo solo se quita cuando tiene forma de prefijo. Y como renombrar el parámetro cambiaría la opción y rompería los scripts de quien ya use tu CLI, el nombre se puede fijar en el comentario:@tcPersona --nombre. - Decidir el tipo. Gana lo que digas en el comentario; si no dices nada, el
AS; si no hayAS, el prefijo; y si tampoco, texto. UntnsinASes un entero, porque es lo que casi siempre quiere decir quien lo escribe. - Las fechas. Por COM una fecha llega siempre como DateTime, así que un
AS Dateno la convertiría, y FoxCli lo avisa al compilar. En la consola solo se admiteAAAA-MM-DD, porque03/02/2026es marzo en un sitio y febrero en otro, y aceptarlo sería leerlo mal sin avisar a nadie. - Validar antes de ejecutar nada. Si falta un argumento o una opción está mal escrita, tu método no llega a correr. Quien llama recibe el motivo, una sugerencia si se ha equivocado por poco, la ayuda del comando y un código de salida que un script puede leer.
- Que la documentación no se quede atrás. De ese mismo comentario salen la ayuda de la consola, un
docs\dbf.mdcon los comandos, sus opciones, sus valores por defecto y sus códigos de salida, que sirve tal cual como README, ydbf describe, que da el esquema en JSON para que un agente de IA sepa usar la CLI sin leer nada más. - Ningún diálogo. Un error de VFP que no atrapas sale por el canal de error con el fichero y la línea, y la CLI termina. Nunca se queda esperando a que alguien pulse Aceptar.
La cláusula HELP sigue valiendo
Los proyectos que ya la usan siguen compilando y funcionando igual, y las dos formas se pueden mezclar en la misma clase: los comandos con HELP pasan por Run, y los demás van directos a su método. Así un proyecto se puede pasar a la firma nativa comando a comando, a medida que haya que cambiar cada uno.
Para empezar desde cero, el tutorial Tu primera CLI en Visual FoxPro con FoxCli hace una herramienta completa con la firma nativa, paso a paso desde el IDE. Y si la consola te queda lejos, el curso CLIs para foxeros explica qué son los argumentos, las opciones y los códigos de salida.