Ein handlicher Commandline-Handler
Da ich derzeit wieder verstärkt Tools schreibe, die bequem
über die Konsole nutzbar sein sollen, habe ich mir eine winzige Library
geschrieben, die mir das Handling von Befehlen und Parametern erleichtert.
Eines der Tools ermöglicht den Zugriff auf Visual SourceSafe - wobei alle
nötigen Parameter wie folgt übergeben werden können.
vssman.exe /get:latest /vss -ini:"\\vss-share\srcsafe.ini" -user:matze -project:"$/vssman" -localPath:"C:\Projects\vssman"
Dabei ist erst einmal nicht so wichtig, was das Tool eigentlich macht, sondern
wie der CommandHandler die übergebenen Argumente aufbereitet. Ich habe dieses
Tool herausgegriffen, weil sich die Funktionsweise daran gut demonstrieren lässt.
Der Handler erzeugt aus den angegebenen Argumenten eine "Kette" - wobei
jedes Argument dem ein "/" vorangestellt ist (Befehl) als neues Glied hinzugefügt wird. Alle
Argumente, die mit einem "-" beginnen, werden als Parameter an das zuletzt
hinzugefügte Glied angehängt. Das sieht dann so aus...
In der Anwendung kann die verarbeitete Auflistung der Argumente so verwendet werden.
static void Main(string[] args)
{
ArgumentCollection arguments = new ArgumentCollection();
arguments.Initialize(args, null);
Argument vssGet = arguments.Find("get");
if ("latest".Equals(vssGet.Value))
{
}
}
Aber es geht natürlich besser; denn der Handler ermöglicht die Verwendung
benutzerdefinierter Argument-Klassen, die eine starke Typisierung und eine Verarbeitung
von Befehlszeilen ohne Fallunterscheidungen ermöglichen. Der Handler eignet sich
außerdem für Anwendungen, die durch Plugins erweitert werden können und deren Funktionen
auch auf der Kommandozeile zur Verfügung stehen sollen.
Benutzerdefinierte Argumentklassen verwenden
Ich stelle hier die Implementierung der Befehle /get und /vss der Beispielanwendung
vssman.exe vor (das Beispielprojekt steht ebenfalls zum Download zur Verfügung).
Die Beispiel-Anwendung ist eine einfache Konsolenanwendung. Das Assembly enthält die
Klassen VssGetArgument und VssDatabaseArgument, welche von der Argument-Basisklasse
erben. Die Zuordnung der Typen zu einem Befehl erfolgt über das Argument-Attribut
mit denen die Klassen versehen sind.
Im Klassendiagramm ist erkennbar, dass durch die starke Typisierung der Argumente, die
einzelnen Parameter über Eigenschaften veröffentlicht werden können. In welcher Reihenfolge
die Parameter an der Konsole übergeben werden spielt dabei keine Rolle.
[Argument("vss", IsExecutable=false)]
internal class VssDatabaseArgument : Argument
{
public VssDatabaseArgument(
string name,
string value) : base(name, value) { }
}
Damit die Typen bei der Verarbeitung der Argumente verwendet werden, müssen diese
noch bekannt gemacht werden. Dazu kann die InitializeArguments()-Methode der
Argument-Klasse verwendet werden. Diese registriert alle Typen, die sich im aufrufenden
Assembly befinden. Diese Variante dürfte jedoch nicht für alle Einsatzzwecke praktikabel
sein, ist jedoch für meine Demo-Anwendung ausreichend, da sich die Argument-Klassen im
Anwendungs-Assembly befinden.
Eine andere (und sicher attraktiviere) Variante ist die Unterstützung von
Dependency-Injection. Wie die Abhängigkeiten dabei den Weg in die Anwendung
finden, bleibt Ihnen überlassen - und das geht so. Beerben Sie die Klasse
CommandLineHandler und überschreiben Sie die GetArgumentType()-Methode.
Eine Instanz dieses Typs kann an die Initialize()-Methode der ArgumentCollection
übergeben werden.
class MyCommandLineHandler : CommandLineHandler
{
public overrides Type GetArgumentType(
string name)
{
return Microkernel.GetImplementationType<IArgument>(name);
}
}
Argumente beliebig kombinieren
Das VssDatabase-Argument nimmt die Verbindungsparameter auf, die für den
/get-Befehl benötigt werden. Die Verbindungsparemeter habe ich bewusst
an einen separaten Befehl geknüpft, weil dieser dann auch mit anderen
denkbaren Befehlen wie /checkout, /checkin oder /label verwendet
werden kann.
Die Kontrollflussabhängigkeiten minimieren...
Ich hatte erwähnt, dass die Ausführung der Befehle mit Hilfe des Handlers möglich
ist, ohne das eine knifflige Verzweigung des Programmcodes nötig ist. Die Argument-Klasse
stellt die Execute()-Methode bereit; diese kann in einer Ableitung überschrieben und
mit Logik ausgestattet werden. Das folgende Beispiel zeigt die wesentliche Implementierung
des /get-Befehls - und wie dieser mit dem /vss-Befehl interagiert.
[Argument("get", IsExecutable=true)]
internal class VssGetArgument : Argument {
public VssGetArgument(
string name,
string value) : base(name, value) { }
public override void Execute(
IArgument caller)
{
VssDatabaseArgument vssParams;
if ((vssParams = Find<VssDatabaseArgument>("vss") == null)
throw new MissingArgumentException("vss");
VSSDatabase vssdb = new VSSDatabaseClass();
vssdb.Open(vssParams.Ini, vssParams.User, vssParams.Password);
vssdb.CurrentProject = vssParams.Project;
VSSItem item = vssdb.get_VSSItem(vssParams.Project, false);
string local = vssParams.LocalPath;
Int32 flags = (Int32) (VSSFlags.VSSFLAG_RECURSYES |
VSSFlags.VSSFLAG_FORCEDIRNO);
item.Get(ref local, flags);
}
}
Bei der Ausführung werden nur die Argumente einbezogen, die durch das Argument-Attribut
als ausführbar gekennzeichnet sind. Im Beispiel trifft dies für den /get-Befehl zu;
der /vss-Befehl stellt hingegen keine ausführbare Logik bereit.
Was bleibt in void main() noch übrig?
static void Main(string[] args)
{
Argument.InitializeArguments();
MyCommandlineHandler handler = new MyCommandlineHandler();
handler.Execute(args);
}
Links
Download: Commandline Handler Library
Downloads/commandline.zip
Download: Visual SourceSafe Manager (vssman.exe) Konsolenanwendung
Download/commandline_demo.zip
Der Commandline-Handler in Aktion