Du bist hier: Skip Navigation LinksHome > .NET > Artikel

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); 
        }
      
Kick it on dotnet-kicks.de

Links

Download: Commandline Handler Library
Downloads/commandline.zip

Download: Visual SourceSafe Manager (vssman.exe) Konsolenanwendung
Download/commandline_demo.zip
Der Commandline-Handler in Aktion

Kommentar schreiben

Name*:  
Email*:  
Website:
Kommentar: