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 5

Er 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

exportar mit der HELP-Klausel und einem Run, das verteilt
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
	ENDFUNC

Das 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. ,-f fügt die Kurzform hinzu, :int legt den Typ fest, :csv|xls schließ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 Run schreiben und die Befehle mit einem DO CASE verteilen muss. Jeder neue Befehl ist ein Zweig mehr, und ein Befehl, den man deklariert und im CASE vergisst, 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 -f geschrieben hat. Die Zahl mit AsNumber() und den Text mit Get().
  • 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

Dasselbe exportar, mit der nativen Signatur
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
	ENDPROC

Das 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 Run und kein DO CASE.
  • Jeder Parameter ist eine Option, benannt ohne Präfix: tcFormato ist --formato und tnMax ist --max. Der Typ kommt aus dem AS.
  • Der *!*-Block ergänzt, was die Signatur nicht sagt. <> macht das Argument namenlos, -f ist die Kurzform, :csv|xls die Werteliste und =csv der Standardwert. Ein Parameter ohne = ist Pflicht, und der Rest der Zeile ist die Beschreibung.
  • Die Werte kommen in den Parametern an, schon umgewandelt. tnMax ist eine Zahl, und tcFormato kommt in Kleinbuchstaben an, auch wenn jemand -f XLS schreibt, weil ein Wert aus der Liste so geliefert wird, wie er deklariert ist.
  • Ohne RETURN endet der Befehl mit 0. RETURN EXIT_NO_TABLE endet mit der 10, die ich in foxcli.h deklariert 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 posicionales

Ein 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, xls

In 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.dbf

Die 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. tcNombre ergibt --nombre, aber total bleibt --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 kein AS, das Präfix; und sonst Text. Ein tn ohne AS ist eine ganze Zahl, weil das fast immer gemeint ist.
  • Datumswerte. Über COM kommt ein Datum immer als DateTime an, ein AS Date würde es also nicht umwandeln, und FoxCli warnt beim Kompilieren davor. In der Konsole wird nur JJJJ-MM-TT angenommen, weil 03/02/2026 an 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.md mit den Befehlen, ihren Optionen, ihren Standardwerten und ihren Exit-Codes, die so, wie sie ist, als README taugt, und dbf 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.