In diesem Tutorial bauen wir dbf, ein Konsolenwerkzeug in Visual FoxPro, das Tabellen ansieht und exportiert, ohne VFP zu öffnen:

dbf info customers.dbf
dbf campos customers.dbf --format json
dbf exportar customers.dbf clientes.csv

Es hat drei Befehle, eine Hilfe, die sich von selbst schreibt, Fehler, die erklären, was los ist, und Exit-Codes, die ein Skript lesen kann. Weitergegeben wird es durch Kopieren eines Ordners, auf einen Rechner ohne Visual FoxPro. Und der gesamte Code ist eine VFP-.prg mit 76 Zeilen.

Ich habe es auf Spanisch gebaut, deshalb sind die Befehlsnamen (campos heißt „Felder“, exportar „exportieren“), die Meldungen und die Screenshots auf Spanisch. FoxForge selbst spricht Ihre Sprache: Die Menünamen gebe ich auf Deutsch an. Wie Ihre Befehle heißen, entscheiden Sie.

Wir arbeiten mit customers.dbf, der Kundentabelle aus den Northwind-Beispieldaten, die VFP mitbringt. Was Sie auf den Screenshots sehen, können Sie also genau so nachmachen. dbf liest nur: Es schreibt nie in die Tabelle.

Wenn Ihnen die Konsole fremd ist, gibt es vorher den Kurs CLIs für Fox-Entwickler: was ein Befehl ist, was Argumente und Optionen sind und was ein Exit-Code ist. Hier setze ich das alles voraus.

Was Sie brauchen

  • Visual FoxPro 9 (oder VFP Advanced).
  • FoxCli installiert. Es bringt FoxForge mit, die Werkstatt, die sich in die VFP-IDE einklinkt. Ohne Lizenz funktioniert alles genauso; den Unterschied sehen wir am Ende.
  • Windows 10 oder 11.

Öffnen Sie VFP wie immer, ohne „Als Administrator ausführen“. Beim Kompilieren als Administrator registriert VFP die Komponente in HKLM, und diese Registrierung stört die folgenden Kompilierungen. Und benutzen Sie nicht die Schaltfläche Build im Projekt-Manager: Kompilieren Sie mit Strg+F7, denn nur so wird die CLI vollständig.

Schritt 1. FoxForge laden

Im Befehlsfenster von VFP:

FoxForge in die IDE laden
DO "C:\Program Files (x86)\FoxForge\FoxForge.app"

Passen Sie den Pfad an, wenn Sie FoxForge in einen anderen Ordner installiert haben. In der Menüleiste von VFP erscheint ein neues Menü, FoxForge, und von dort aus wird alles Weitere gemacht.

Schritt 2. Das Projekt anlegen

FoxForge > Neues Projekt > Konsolenanwendung (CLI). Im Assistenten:

  • Name: dbf. Das ist der Name des Programms, den man in der Konsole tippt.
  • Ordner: Ihr Projektordner. Ich habe C:\a1\tutorial-b benutzt.
  • Minimale Vorlage, ohne Kommentare: angehakt. Die andere Vorlage bringt den Code Zeile für Zeile erklärt mit, was man einmal gelesen haben sollte, aber heute schreiben wir unseren eigenen.

Der FoxForge-Assistent „Neue Konsolenanwendung“ (auf Spanisch), mit dem Namen dbf, dem Ordner und angehakter minimaler Vorlage

Klicken Sie auf Anlegen. FoxForge legt das Projekt an und öffnet es im Projekt-Manager:

dbf\
├── dbf.pjx          das VFP-Projekt
├── foxforge.json    kennzeichnet es als CLI-Projekt
├── pack.ps1         packt, was weitergegeben wird
├── src\
│   ├── main.prg     Ihre Befehle: die einzige Datei, die Sie bearbeiten
│   ├── foxcli.h     benannte Exit-Codes
│   └── foxcli.prg   die Basisklasse, die zu FoxCli gehört
└── dist\            hier landet die .exe beim Kompilieren

Schritt 3. Was die Vorlage mitbringt

Öffnen Sie src\main.prg:

main.prg frisch angelegt, mit der minimalen Vorlage
#INCLUDE foxcli.h

*!* dbf: describe aquí lo que hace tu CLI
DEFINE CLASS DbfCommand AS FoxCliCommand OF foxcli.prg OLEPUBLIC

    *!* Saluda a alguien
    *!* @tcNombre  -n  A quién saludar
    PROCEDURE Saludar(tcNombre AS String)
        Console.WriteLine("Hola " + tcNombre + "!")
    ENDPROC

ENDDEFINE

Daran sieht man schon, wie alles funktioniert:

  • Jede öffentliche Methode der Klasse ist ein Befehl. Saludar („grüßen“) wird als dbf saludar benutzt.
  • Jeder Parameter ist eine Option, benannt ohne sein Präfix: tcNombre ist --nombre. Der Typ kommt aus dem AS.
  • Der *!*-Block darüber ist die Dokumentation. Die erste Zeile ist die Beschreibung des Befehls, und jede @-Zeile beschreibt einen Parameter: -n gibt ihm einen kurzen Alias, der Rest ist seine Beschreibung. Daraus entsteht die Hilfe.
  • Console ist die Konsole. Console.WriteLine() schreibt auf die Ausgabe und Console.Error() auf den Fehlerkanal.

Schritt 4. Die Befehle schreiben

Ersetzen Sie den Inhalt von main.prg durch diesen Code:

main.prg von dbf: drei Befehle zum Ansehen und Exportieren von Tabellen
#INCLUDE foxcli.h

*!* dbf: mira y exporta tablas de VFP desde la consola, sin abrir Visual FoxPro
*!* @exit 10 La tabla no existe
DEFINE CLASS DbfCommand AS FoxCliCommand OF foxcli.prg OLEPUBLIC

	*!* Cuántos registros y campos tiene una tabla
	*!* @tcTabla <>  La tabla (.dbf)
	*!* @example dbf info customers.dbf
	PROCEDURE Info(tcTabla AS String)
		IF !THIS.Abrir(tcTabla)
			RETURN EXIT_NO_TABLE
		ENDIF
		Console.WriteLine("Registros:  " + TRANSFORM(RECCOUNT()))
		Console.WriteLine("Campos:     " + TRANSFORM(FCOUNT()))
		Console.Write("Modificada: ")
		Console.WriteLine(LUPDATE())
		USE
	ENDPROC

	*!* La estructura de una tabla: nombre, tipo y ancho de cada campo
	*!* @tcTabla <>  La tabla (.dbf)
	*!* @example dbf campos customers.dbf --format json
	PROCEDURE Campos(tcTabla AS String)
		LOCAL laCampos[1], lnCampos, lnI, lcJson
		IF !THIS.Abrir(tcTabla)
			RETURN EXIT_NO_TABLE
		ENDIF
		lnCampos = AFIELDS(laCampos)
		USE
		IF Console.IsJson()
			lcJson = ""
			FOR lnI = 1 TO lnCampos
				lcJson = lcJson + IIF(lnI > 1, ",", "") + ;
					'{"nombre":"' + LOWER(laCampos[lnI, 1]) + '","tipo":"' + laCampos[lnI, 2] + ;
					'","ancho":' + TRANSFORM(laCampos[lnI, 3]) + '}'
			ENDFOR
			Console.WriteLine("[" + lcJson + "]")
		ELSE
			FOR lnI = 1 TO lnCampos
				Console.WriteLine(PADR(LOWER(laCampos[lnI, 1]), 14) + laCampos[lnI, 2] + ;
					STR(laCampos[lnI, 3], 5))
			ENDFOR
		ENDIF
	ENDPROC

	*!* Copia una tabla a un fichero CSV o XLS
	*!* @tcTabla   <>             La tabla (.dbf)
	*!* @tcDestino <>             El fichero que se crea
	*!* @tcFormato :csv|xls =csv  Formato del fichero
	*!* @example dbf exportar customers.dbf clientes.csv
	*!* @example dbf exportar customers.dbf clientes.xls --formato xls
	PROCEDURE Exportar(tcTabla AS String, tcDestino AS String, tcFormato AS String)
		IF !THIS.Abrir(tcTabla)
			RETURN EXIT_NO_TABLE
		ENDIF
		IF tcFormato == "xls"
			COPY TO (tcDestino) TYPE XL5
		ELSE
			COPY TO (tcDestino) TYPE CSV
		ENDIF
		Console.Error(TRANSFORM(RECCOUNT()) + " registros copiados a " + tcDestino)
		USE
	ENDPROC

	*-- Abre la tabla solo para leer. Si no existe, lo dice por el canal de error.
	PROTECTED FUNCTION Abrir(tcTabla)
		IF !FILE(FORCEEXT(tcTabla, "dbf"))
			Console.Error("dbf: no encuentro la tabla " + tcTabla)
			RETURN .F.
		ENDIF
		USE (FORCEEXT(tcTabla, "dbf")) AGAIN SHARED NOUPDATE ALIAS tabla
		RETURN .T.
	ENDFUNC

ENDDEFINE

Und fügen Sie am Ende von src\foxcli.h den eigenen Exit-Code hinzu:

Der Exit-Code von dbf, in foxcli.h
#DEFINE EXIT_NO_TABLE 10    && la tabla no existe

Worauf es in diesem Code ankommt:

  • <> macht ein Positionsargument. @tcTabla <> sagt, dass die Tabelle ohne Namen davor geschrieben wird: dbf info customers.dbf und nicht dbf info --tabla customers.dbf.
  • :csv|xls =csv ist eine feste Liste mit Standardwert. --formato akzeptiert nur csv oder xls, und ohne Angabe gilt csv. Das prüft FoxCli, bevor Ihr Code überhaupt erreicht wird.
  • Was die Methode zurückgibt, ist der Exit-Code. Ohne RETURN endet der Befehl mit 0. RETURN EXIT_NO_TABLE endet mit 10, und das @exit 10 oben dokumentiert es. Ihre eigenen reichen von 10 bis 125: Die niedrigen Zahlen gehören FoxCli.
  • Console.IsJson() sagt, ob der Aufrufer --format json verlangt hat. Diese Option deklarieren Sie nicht selbst: FoxCli bringt sie in jedem Befehl mit.
  • Das Ergebnis geht auf die Ausgabe, Meldungen auf den Fehlerkanal. exportar meldet mit Console.Error(), wie viele Datensätze kopiert wurden. Leitet jemand die Ausgabe in eine Datei um, mischt sich die Meldung also nicht hinein.
  • Abrir („öffnen“) ist PROTECTED: eine Hilfsmethode wie jede andere, die nicht in der Befehlsliste erscheint. Deshalb hat ihr Kommentar das übliche *--: *!* ist für das, was in der Hilfe der CLI erscheint, und eine Hilfsmethode wird nie aus der Konsole aufgerufen.
  • Console.WriteLine(LUPDATE()) schreibt das Datum auf jedem Rechner im ISO-Format (2004-12-09), unabhängig von SET DATE. Die Ausgabe einer CLI lesen andere Programme, und sie muss überall gleich sein.

Schritt 5. Kompilieren

FoxForge > Aktuelles Projekt > CLI kompilieren oder Strg+F7. Ohne FoxCli-Lizenz erscheint dieser Hinweis:

Hinweis von FoxForge nach dem Kompilieren (auf Spanisch): CLI kompiliert nach C:\a1\tutorial-b\dbf\dist\dbf.exe; ohne FoxCli-Lizenz läuft sie als Evaluierung und schreibt eine Zeile auf stderr

Jetzt haben Sie dist\dbf.exe. Nebenbei hat FoxForge die Dokumentation in docs\dbf.md erzeugt, mit der Beschreibung jedes Befehls aus den *!*-Blöcken.

Schritt 6. Aus der IDE ausprobieren

Bevor Sie eine Konsole öffnen, können Sie es ausprobieren, ohne VFP zu verlassen. FoxForge > Aktuelles Projekt > Argumente… oder Umschalt+F8, und tippen Sie die Zeile so, wie Sie sie in einer Konsole tippen würden, ohne den Programmnamen:

Der Argumente-Dialog von FoxForge (auf Spanisch), mit info und dem vollständigen Pfad von customers.dbf, darunter der Befehl, der ausgeführt wird

Er läuft in dist\, deshalb steht hier der vollständige Pfad der Tabelle. Ausführen (oder beim nächsten Mal Strg+F8, weil es sich die Argumente merkt) öffnet eine Konsole mit dem Ergebnis und darunter dem Exit-Code:

Eine Konsole mit der Ausgabe von dbf info: 91 Datensätze, 11 Felder, geändert am 2004-12-09, die Evaluierungszeile von FoxCli und exit 0

Wenn etwas schiefgeht, führt FoxForge > Aktuelles Projekt > Prüfstand einen Befehl direkt aus Ihrem Quelltext aus, mit dem VFP-Debugger zur Hand und ohne zu kompilieren.

Schritt 7. Weitergeben und aus der Konsole benutzen

Im Projektordner packt pack.ps1 ein Zip mit dem, was zum Ausführen der CLI nötig ist, und mit nichts sonst. Aus einer Konsole in diesem Ordner:

powershell -ExecutionPolicy Bypass -File pack.ps1
9 ficheros -> C:\a1\tutorial-b\dbf\dbf-dist.zip (3.634.677 bytes)
Se despliega descomprimiendo: no hace falta registrar nada ni ser administrador.

Diese 9 Dateien sind Ihre CLI und die Laufzeit von VFP 9, damit sie auf einem Rechner ohne Visual FoxPro läuft. Entpacken Sie sie, wohin Sie wollen (ich in einen Werkzeugordner), und schon ist sie installiert. Es wird nichts registriert, also muss auch nichts deinstalliert werden: Man löscht den Ordner.

Um den VFP-Ordner nicht anzufassen, kopieren Sie die Northwind-Daten in Ihren Testordner. Achtung: den ganzen Ordner, denn customers.dbf gehört zur Datenbank northwind.dbc:

Die Konsole kopiert den Northwind-Ordner mit xcopy nach C:\pruebas-cli: 33 Dateien kopiert

Wechseln Sie in die Kopie und fügen Sie den Ordner von dbf zum PATH dieser Konsole hinzu, damit Sie dbf ohne Pfad tippen können:

cd C:\pruebas-cli\northwind
set PATH=%PATH%;C:\herramientas\dbf

(Ersetzen Sie C:\herramientas\dbf durch den Ordner, in den Sie entpackt haben. Dieses set gilt nur für dieses Fenster.)

Die Hilfe haben Sie nebenbei geschrieben, in den *!*-Blöcken:

Die allgemeine Hilfe von dbf: die Beschreibung der CLI, die Verwendung und die Liste der Befehle info, campos, exportar und describe

Auch describe bringt FoxCli mit: Es gibt alle Befehle mit ihren Optionen als JSON aus, damit ein KI-Agent die CLI entdecken kann, ohne sonst etwas zu lesen.

Fehlt ein Argument, erreicht dbf Ihren Code gar nicht erst: Es sagt, was fehlt, zeigt die Hilfe des Befehls und endet mit 2. Mit der Tabelle liefert es das Ergebnis:

dbf info ohne Argumente: fehlende Positionsargumente und die Hilfe des Befehls; danach dbf info customers.dbf mit 91 Datensätzen und 11 Feldern

campos zeigt die Struktur als Tabelle zum Lesen am Bildschirm:

dbf campos customers.dbf: die Liste der Felder mit Typ und Breite

Und mit --format json als eine Zeile, die ein anderes Programm lesen kann:

dbf campos customers.dbf --format json
[{"nombre":"customerid","tipo":"C","ancho":5},{"nombre":"companyname","tipo":"C","ancho":40},{"nombre":"contactname","tipo":"C","ancho":30},...,{"nombre":"fax","tipo":"C","ancho":24}]

exportar legt die Datei an und meldet sich auf dem Fehlerkanal. Mit einer Tabelle, die es nicht gibt, endet es mit der 10, die wir deklariert haben:

dbf exportar nach ..\clientes.csv: 91 Datensätze kopiert und Code 0; dbf info facturas.dbf: Tabelle nicht gefunden und Code 10

Im CSV stehen die Akzente an ihrem Platz (Avda. de la Constitución 2222, México D.F.), weil VFP es mit der Codepage der Tabelle schreibt.

Und ein Format, das nicht in der Liste steht, erreicht Ihren Code nie: FoxCli sagt, welche gültig sind, zeigt die Hilfe von exportar und endet mit 2.

dbf exportar mit dem Format txt: nicht gültig, die Werte sind csv und xls, die Hilfe des Befehls und Code 2

Eine Tabelle fern ihrer Datenbank. Kopieren Sie nur customers.dbf in einen anderen Ordner, ohne northwind.dbc, will VFP Sie fragen, wo die Datenbank ist. In einer CLI ist niemand da, den man fragen könnte, also endet dbf mit Code 1 und dem VFP-Fehler auf dem Fehlerkanal (Fehler 2031, eine Operation der Benutzeroberfläche ist gerade nicht erlaubt). Es bleibt nicht hängen und wartet auf eine Antwort: FoxCli lässt nie einen Dialog offen.

Schritt 8. Die Lizenz

Ohne Lizenz funktioniert die CLI genauso und ohne Ablaufdatum. Der einzige Unterschied ist die Zeile, die Sie am Ende jeder Ausgabe gesehen haben (bei mir auf Spanisch):

Hecho con FoxCli (evaluación) - irwinrodriguez.dev/foxcli

Sie geht auf den Fehlerkanal, landet also in keinem CSV und keinem JSON, auch wenn Sie die Ausgabe umleiten. Mit einer FoxCli-Lizenz, geladen unter FoxForge > FoxCli-Lizenz…, siegelt Strg+F7 die CLI mit Ihrem Namen: Die Zeile verschwindet, und --version sagt, an wen sie lizenziert ist.

dbf --version
dbf (FoxCli 1.3.0, con licencia de Irwin Rodríguez)

Die CLIs, die Sie kompilieren, geben Sie kostenlos weiter, an wen Sie wollen: Die Lizenz gehört dem Entwickler, der kompiliert, nicht jeder Kopie.

Wie es weitergeht

  • Die FoxCli-Anleitung finden Sie unter FoxForge > Hilfe > FoxCli-Anleitung: Eingaben lesen, Datumsangaben, das Ausgabeformat, die HELP-Klausel und alles, was heute nicht vorkam.
  • Die Dokumentation Ihrer CLI liegt in docs\dbf.md und wird bei jeder Kompilierung neu erzeugt: Sie taugt so, wie sie ist, als README.
  • Ein großes Beispiel: FoxPack, der Bibliotheksmanager für VFP-Projekte, ist eine mit FoxCli gebaute CLI, und sein Quellcode ist veröffentlicht.
  • Ideen, wie es mit dbf weitergeht: ein Befehl contar („zählen“) mit Filter (--donde "country = 'Spain'"), oder dass exportar ein --campos annimmt, um die Spalten auszuwählen.