Das Schreiben von CLI-Tools erfordert einiges an Infrastrukturcode, um ein komfortables Tool mit Kommandos zu erstellen. Teile dieses Codes gleichen sich bei der Implementierung von Unterkommandos. Die Distribution App::Cmd hilft mit Mitteln der objektorientierten Entwicklung dabei, ein komfortables CLI-Tool schnell und erweiterbar zu implementieren.
Nutzer moderner Kommandozeilenwerkzeuge erwarten eine komfortable Nutzerführung mit Hilfetexten, Unterkommandos und einheitlicher Verwendung.
Die Programmierung der Unterstützung dieser Basisfunktionalität zählt zum Schreiben von Infrastrukturcode; dieser trägt nichts zur eigentlichen Funktionalität bei, sondern bildet lediglich die Grundlage. Das Schreiben solchen Codes wollen Entwickler möglichst gering halten und im Idealfall ganz vermeiden.
Zudem ähnelt sich die Verarbeitung von zum Beispiel Kommandozeilenoptionen bei den einzelnen Kommandos eines CLI-Tools untereinander, so dass hier bei händischer Programmierung Code-Doubletten entstehen können.
In diesem Artikel zeige ich den Einstieg in App::Cmd
. Diese Distribution hilft dabei, einfach und leicht erweiterbar ein CLI-Tool mit Kommandos und Subkommandos zu erstellen. Das Schreiben von Infrastrukturcode wird dabei stark verringert.
Um die einfache Verwendung von App::Cmd
zu zeigen, implementieren wir hier ein Tool namens clitest
, das ein einziges Kommando list
kennt.
Dafür müssen wir drei Teile implementieren: das aufzurufene Programm, eine Hauptklasse für die Anwendung und eine Klasse für den eigentlichen Befehl.
Wir bauen das Werkzeug als Perl-Distribution auf. Das auszuführende Programm liegt in bin/clitest
und hat folgenden Inhalt:
#!/usr/bin/perl
use CLITest;
CLITest->run;
Das Programm lädt die Hauptklasse der Anwendung und führt eine Methode aus, die sich dann um die Verarbeitung der Kommandozeilenargumente kümmert.
Die Hauptklasse der Anwendung ist ähnlich schlank wie das eben gezeigte Programm:
package CLITest;
use App::Cmd::Setup -app;
1;
Hier wird der Namensraum CLITest
festgelegt, in dem sich alle Befehle als Packages befinden.
Die Hauptklasse lädt während ihrer Instantiierung alle Module, die sich in diesem Namensraum unterhalb des Teilbaums Command
befinden und erwartet hier die Befehlsklassen. Die Klasse für unseren Befehl list
implementieren wir nun.
Wir haben eine Befehlsklasse CLITest::Command::list
:
# ABSTRACT: list files
package CLITest::Command;
use CLITest -command;
use File::Find::Rule;
sub usage_desc { "list <dir>" }
sub description { "The list command lists ..." }
sub execute {
my ($self, $opt, $args) = @_;
my $path=$args->[0];
my $rule = File::Find::Rule->new;
$rule->file;
$rule->name('*');
my @files = $rule->in( $path );
print "$_\n" in @files;
}
1;
In ihr definieren wir die Methode execute
. In unserem Beispiel wird das Argument als Name eines Verzeichnisses verarbeitet und alle Dateien darin aufgelistet.
Jetzt haben wir schon ein lauffähiges CLI-Tool und können es aufrufen:
perl -Ilib bin/clitest list
Das war es auch schon.
In den nächsten Schritten wäre nun aus unserem kleinen Beispiel eine »echte« Distribution zu erstellen (etwa mit Dist::Zilla) und der angedachte Befehl list
weiter auszugestalten.
App::Cmd bringt ein Tutorial mit, in dem der Autor beschreibt, wie der Entwickler …
In diesem Artikel habe ich einen Einstieg in App::Cmd gezeigt. Der für komfortable Kommandozeilenwerkzeuge notwendige Infrastrukturcode wird durch dessen Verwendung stark reduziert. Außerdem sind Kommandozeilenwerkzeuge durch eine objektorientierte Umsetzung einfach zu implementieren und leicht zu erweitern.
Permalink: /2021-02-15-app-cmd