~/ / docs / Praxis / 08 · Praxis
Ein kleines CLI-Tool bauen
Vom leeren Projekt zu einem brauchbaren Kommandozeilen-Tool — mit Argument-Parsing, Dateilesen und sauberer Fehlerbehandlung.
Zeit für ein Endprodukt. In diesem Kapitel entsteht wcount — ein Mini-wc-Klon, der Zeilen, Wörter und Zeichen in einer Datei zählt. Klein, aber realistisch genug, um die Bausteine der bisherigen Kapitel zusammenzubringen.
Gerüst
Neues dune-Projekt anlegen:
dune init project wcount --kind=executable
cd wcount
In bin/dune werden zwei Pakete als Abhängigkeiten ergänzt — cmdliner für Argument-Parsing und stdio für I/O-Hilfsfunktionen:
(executable
(name main)
(public_name wcount)
(libraries cmdliner stdio))
Einmal installieren:
opam install cmdliner stdio
Kernlogik
In bin/main.ml wird zuerst die Zählfunktion definiert. Sie nimmt den Dateiinhalt als String und liefert ein Record mit den drei Zahlen:
type stats = { lines : int; words : int; chars : int }
let count_text text =
let chars = String.length text in
let lines =
String.fold_left
(fun acc c -> if c = '\n' then acc + 1 else acc) 0 text
in
let words =
text
|> String.split_on_char ' '
|> List.filter (fun s -> String.length s > 0)
|> List.length
in
{ lines; words; chars }
|> ist der Pipe-Operator — er reicht den Wert links als letztes Argument an die Funktion rechts weiter. Das macht die Wort-Pipeline gut lesbar von oben nach unten.
Fehlerbehandlung
Lesen kann scheitern. Das Ergebnis kapselt ein result-Wert:
let read_safe path =
try Ok (Stdio.In_channel.read_all path)
with Sys_error msg -> Error msg
let run path =
match read_safe path with
| Ok text ->
let s = count_text text in
Printf.printf "%6d %6d %6d %s\n" s.lines s.words s.chars path;
0
| Error msg ->
Printf.eprintf "wcount: %s\n" msg;
1
Der Rückgabewert von run ist der Exit-Code — Konvention: 0 für Erfolg.
Argumente parsen
cmdliner macht aus Funktionen deklarative CLI-Definitionen. Für ein einziges Positionsargument reicht das hier:
open Cmdliner
let path_arg =
let doc = "Pfad zur Datei." in
Arg.(required & pos 0 (some file) None & info [] ~docv:"FILE" ~doc)
let cmd =
let info = Cmd.info "wcount" ~doc:"Zeilen, Wörter und Zeichen zählen." in
Cmd.v info Term.(const run $ path_arg)
let () = exit (Cmd.eval' cmd)
Bauen und ausprobieren:
dune build
dune exec wcount -- README.md
42 180 1240 README.md
--help ist von cmdliner automatisch dabei:
dune exec wcount -- --help
Das war’s. In gut sechzig Zeilen Code steht ein vorzeigbares Werkzeug — let-Bindungen, Records, Pattern Matching, result-Fehler, dune-Setup und externe Bibliotheken arbeiten zusammen. Damit endet die Einstiegsreihe. Ab hier lohnt der Sprung in echte Projekte: eine Bibliothek veröffentlichen, einen kleinen Webserver mit Dream oder Opium bauen, oder die Standardbibliothek vertiefen.