Wenn ich dem, was ich mache, einen Namen geben müsste, würde ich sagen: Fox-Entwicklern die Dinge leicht machen. Mit den Jahren habe ich gesehen, dass das einen Preis hat, und den bezahlt, wer das Werkzeug baut: Je einfacher etwas von außen aussieht, desto mehr Arbeit steckt innen drin.
FoxCli ist das Beispiel, das mir am nächsten liegt. Als ich angefangen habe, wurde ein Konsolenbefehl mit der HELP-Klausel und einer Zeile mit eigener Grammatik deklariert. Heute ist er eine VFP-Methode mit einem Kommentar darüber. In diesem Artikel schreibe ich denselben Befehl auf beide Arten, kompiliere beide und rufe sie mit denselben Befehlszeilen auf. So sieht man, was sich für Sie geändert hat und was dafür im Hintergrund passieren musste.
Der Befehl
Es ist der Befehl exportar aus dbf, dem Werkzeug aus dem Tutorial Ihre erste CLI in Visual FoxPro mit FoxCli, mit einer Option mehr, damit auch eine Zahl im Spiel ist:
dbf exportar customers.dbf clientes.csv
dbf exportar customers.dbf clientes.xls --formato xls
dbf exportar customers.dbf muestra.csv --max 5Er nimmt zwei Argumente ohne Namen: die Tabelle und die Datei, die angelegt wird. Dazu kommt eine Option --formato, die nur csv oder xls annimmt und ohne Angabe csv ist, und eine Option --max mit einer ganzen Zahl, um nur die ersten Datensätze zu kopieren. Beide Optionen haben eine Kurzform, -f und -m.
Ich habe ihn wie das Tutorial auf Spanisch gebaut, deshalb sind die Namen (exportar heißt „exportieren“, formato „Format“), die Beschreibungen und die Meldungen, die Sie sehen, auf Spanisch. Die Meldungen kommen in Ihrer Sprache, wenn die CLI darauf eingestellt ist.
Die Hilfsmethoden Abrir und Copiar sind in beiden Versionen gleich, deshalb wiederhole ich sie nicht. Was sich ändert, ist, wie der Befehl deklariert wird und wie die Werte bei ihm ankommen.
Früher: die HELP-Klausel
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
ENDFUNCDas funktioniert gut, aber um es zu schreiben, muss man einiges wissen, das kein VFP ist:
- Die Grammatik der Klausel.
<tabla:str!>ist ein Argument ohne Namen, Pflicht wegen des!. Was in eckigen Klammern steht, ist die Beschreibung der Option davor.,-ffügt die Kurzform hinzu,:intlegt den Typ fest,:csv|xlsschließt die Werteliste, und was nach dem Doppelpunkt am Ende steht, beschreibt den Befehl. Dazu bedeuten<>und[]hier nicht dasselbe wie in der Verwendungszeile einer beliebigen Hilfe, wo eckige Klammern das Optionale markieren. - Dass alles in einer einzigen Zeichenkette steht, und die darf 255 Zeichen nicht überschreiten. Bei 256 kompiliert VFP die Datei nicht, und die Fehlermeldung sagt nichts über die Länge. Diese hier hat 202: Mit etwas längeren Beschreibungen würde sie nicht mehr passen.
- Dass man
Runschreiben und die Befehle mit einemDO CASEverteilen muss. Jeder neue Befehl ist ein Zweig mehr, und ein Befehl, den man deklariert und imCASEvergisst, tut nichts. - Wie man jeden Wert liest. Die Argumente ohne Namen über ihre Position, mit
Positional(). Die Optionen über ihren langen Namen, auch wenn der Aufrufer-fgeschrieben hat. Die Zahl mitAsNumber()und den Text mitGet(). - Dass Standardwerte nicht deklariert werden: Sie stehen im Code, in jedem
Get(). Deshalb zeigt die Hilfe sie nicht an.
Heute: eine Methode mit einem Kommentar
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
ENDPROCDas ist eine PROCEDURE wie jede andere, und man versteht sie, ohne etwas über FoxCli zu wissen:
- Eine öffentliche Methode der Klasse ist ein Befehl, und ihr Name in Kleinbuchstaben ist das Verb. Es gibt kein
Runund keinDO CASE. - Jeder Parameter ist eine Option, benannt ohne Präfix:
tcFormatoist--formatoundtnMaxist--max. Der Typ kommt aus demAS. - Der
*!*-Block ergänzt, was die Signatur nicht sagt.<>macht das Argument namenlos,-fist die Kurzform,:csv|xlsdie Werteliste und=csvder Standardwert. Ein Parameter ohne=ist Pflicht, und der Rest der Zeile ist die Beschreibung. - Die Werte kommen in den Parametern an, schon umgewandelt.
tnMaxist eine Zahl, undtcFormatokommt in Kleinbuchstaben an, auch wenn jemand-f XLSschreibt, weil ein Wert aus der Liste so geliefert wird, wie er deklariert ist. - Ohne
RETURNendet der Befehl mit 0.RETURN EXIT_NO_TABLEendet mit der 10, die ich infoxcli.hdeklariert habe.
Die Symbole sind fast dieselben wie bei HELP, aber jetzt steht jedes in seiner eigenen Zeile, neben dem Parameter, zu dem es gehört, und es gibt keine Längengrenze, auf die man achten muss.
Was der Benutzer der CLI sieht
Ich habe beide Versionen kompiliert und mit denselben Befehlszeilen aufgerufen. Die Ausgabe ist dieselbe, mit einem Unterschied zugunsten der neuen: Die Hilfe zeigt die Standardwerte, weil sie jetzt deklariert sind.
> 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)Auch die Fehler sind bei beiden gleich. Eine falsch geschriebene Option („unbekannte Option, meinten Sie --formato“, und ein überzähliges Argument):
> 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 posicionalesEin Wert, der nicht den deklarierten Typ hat („ist keine Zahl“) oder nicht in der Liste steht („ist nicht gültig, Werte: csv, xls“):
> 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, xlsIn all diesen Fällen zeigt die CLI zusätzlich die Hilfe des Befehls und endet mit Code 2, ohne Ihren Code überhaupt auszuführen. Mit einer korrekten Zeile kommt das Ergebnis, und wenn die Tabelle nicht existiert, die 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.dbfDie CSV-Dateien, die beide Versionen erzeugt haben, sind identisch.
Was dahinter steckt
Von früher zu heute schreiben Sie weniger und müssen weniger wissen, und darum ging es mir. Die Arbeit ist noch da, aber in FoxCli, und alles, worüber Sie nicht mehr nachdenken müssen, ist eine Entscheidung, die ich getroffen habe. Fast jede hat ihren Sonderfall:
- Den Namen der Option aus dem Parameter ableiten.
tcNombreergibt--nombre, abertotalbleibt--total, weil das Präfix nur entfernt wird, wenn es wie ein Präfix aussieht. Und weil das Umbenennen des Parameters die Option ändern und die Skripte aller brechen würde, die Ihre CLI schon benutzen, lässt sich der Name im Kommentar festlegen:@tcPersona --nombre. - Den Typ bestimmen. Was Sie im Kommentar sagen, gewinnt; sagen Sie nichts, das
AS; gibt es keinAS, das Präfix; und sonst Text. EintnohneASist eine ganze Zahl, weil das fast immer gemeint ist. - Datumswerte. Über COM kommt ein Datum immer als DateTime an, ein
AS Datewürde es also nicht umwandeln, und FoxCli warnt beim Kompilieren davor. In der Konsole wird nurJJJJ-MM-TTangenommen, weil03/02/2026an einem Ort März ist und an einem anderen Februar, und es anzunehmen hieße, es falsch zu lesen, ohne jemandem Bescheid zu sagen. - Prüfen, bevor irgendetwas läuft. Fehlt ein Argument oder ist eine Option falsch geschrieben, läuft Ihre Methode gar nicht erst. Der Aufrufer bekommt den Grund, einen Vorschlag, wenn er knapp daneben lag, die Hilfe des Befehls und einen Exit-Code, den ein Skript lesen kann.
- Dafür sorgen, dass die Dokumentation nicht hinterherhinkt. Aus demselben Kommentar entstehen die Hilfe in der Konsole, eine
docs\dbf.mdmit den Befehlen, ihren Optionen, ihren Standardwerten und ihren Exit-Codes, die so, wie sie ist, als README taugt, unddbf describe, das das Schema als JSON liefert, damit ein KI-Agent die CLI benutzen kann, ohne sonst etwas zu lesen. - Keine Dialoge. Ein VFP-Fehler, den Sie nicht abfangen, geht mit Datei und Zeile auf den Fehlerkanal, und die CLI endet. Sie wartet nie darauf, dass jemand auf OK klickt.
Die HELP-Klausel gilt weiterhin
Projekte, die sie schon benutzen, kompilieren und laufen weiter wie bisher, und beide Formen lassen sich in derselben Klasse mischen: Befehle mit HELP laufen über Run, die anderen gehen direkt zu ihrer Methode. So lässt sich ein Projekt Befehl für Befehl auf die native Signatur umstellen, jeweils dann, wenn ein Befehl sowieso geändert werden muss.
Wer bei null anfangen will: Das Tutorial Ihre erste CLI in Visual FoxPro mit FoxCli baut ein vollständiges Werkzeug mit der nativen Signatur, Schritt für Schritt aus der IDE. Und wem die Konsole fremd ist, der findet im Kurs CLIs für Fox-Entwickler, was Argumente, Optionen und Exit-Codes sind.