Wie wird eine Befehlszeilenstil-Zeichenfolge analysiert und ausgeführt?

8

Okay, diese Frage wird ein bisschen langatmig sein, aber hoffentlich wird es für Sie interessant sein. Ich habe am Ende eine spezielle Frage, aber ich möchte Ihnen viel Hintergrund und Kontext bieten, so dass Sie so oft wie möglich auf der gleichen Seite sind und mein Ziel verstehen können. Wenn langwierige Fragen nicht dein Stil sind, ist das völlig in Ordnung. Wenn du jedoch alles beisteuern möchtest, frage ich, dass du dein Bestes gibst, um die Vision zu verstehen und so oft wie möglich auf derselben Seite zu sein.

Kopfgeldnotiz

Wenn Sie nur zum Bereich Kopfgeld-Details gelangen möchten, scrollen Sie bitte nach unten und finden Sie die Kopfzeile "Bounty Update".

Hintergrund

Ich baue eine Konsole-Stil-Anwendung mit ASP.NET MVC 3. Es befindet sich bei Ссылка , wenn Sie dorthin gehen müssen und Machen Sie sich ein Bild davon, wie es funktioniert. Das Konzept selbst ist einfach: Erhalte Befehlszeichenfolgen vom Client, überprüfe, ob der angegebene Befehl existiert und wenn die mit dem Befehl gelieferten Argumente gültig sind, führe den Befehl aus und gebe eine Reihe von Ergebnissen zurück.

Innerarbeiten

Mit dieser Anwendung habe ich mich entschieden ein bisschen kreativ zu werden. Die naheliegendste Lösung für eine Terminalanwendung ist die Erstellung der weltweit größten IF-Anweisung. Führen Sie jeden Befehl über die IF-Anweisung aus und rufen Sie die entsprechenden Funktionen von innen auf. Ich mochte diese Idee nicht. In einer älteren Version der Anwendung war das so, wie es funktionierte und es war ein riesiges Durcheinander. Das Hinzufügen von Funktionen zur Anwendung war lächerlich schwierig.

Nach reiflicher Überlegung habe ich beschlossen, ein benutzerdefiniertes Objekt namens Befehlsmodul zu erstellen. Die Idee besteht darin, dieses Befehlsmodul mit jeder Anfrage zu erstellen. Das Modulobjekt würde alle verfügbaren Befehle als Methoden enthalten, und die Site würde dann mithilfe von Reflektion prüfen, ob ein vom Benutzer angegebener Befehl mit einem Methodennamen übereinstimmt. Das Befehlsmodulobjekt befindet sich hinter einer Schnittstelle namens ICommandModule (siehe unten).

%Vor%

Die Methode InvokeCommand() ist die einzige Methode im Befehlsmodul, die meinem MVC-Controller sofort bekannt ist. Es ist dann Aufgabe dieser Methode, Reflektion zu verwenden und die Instanz von sich selbst zu betrachten und alle verfügbaren Befehlsmethoden zu finden.

Ich benutze Ninject für die Abhängigkeitsinjektion. Mein MVC-Controller hat eine Konstruktorabhängigkeit von ICommandModule . Ich habe einen benutzerdefinierten Ninject-Provider erstellt, der dieses Befehlsmodul beim Auflösen der ICommandModule -Abhängigkeit erstellt. Es gibt 4 Arten von Befehlsmodulen, die Ninject bauen kann:

  1. VisitorCommandModule
  2. UserCommandModule
  3. ModeratorCommandModule
  4. AdministratorCommandModule

Es gibt noch eine weitere Klasse BaseCommandModule , von der alle anderen Modulklassen erben. Echt schnell, hier sind die Erbschaftsverhältnisse:

  • BaseCommandModule : ICommandModule
  • VisitorCommandModule : BaseCommandModule
  • UserCommandModule : BaseCommandModule
  • ModeratorCommandModule : UserCommandModule
  • AdministratorCommandModule : ModeratorCommandModule

Hoffentlich könnt ihr sehen, wie das jetzt aufgebaut ist. Basierend auf dem Mitgliedsstatus des Benutzers (nicht eingeloggt, normaler Benutzer, Moderator, etc.) wird Ninject das richtige Befehlsmodul mit nur den Befehlsmethoden bereitstellen, auf die der Benutzer Zugriff haben sollte.

All das funktioniert großartig. Mein Dilemma tritt auf, wenn ich die Befehlszeichenfolge analysieren und herausfinden möchte, wie die Befehlsmethoden des Befehlsmodulobjekts strukturiert werden.

Die Frage

Wie sollte die Befehlszeichenfolge analysiert und ausgeführt werden?

Aktuelle Lösung

Gegenwärtig zerlege ich die Befehlszeichenfolge (die Zeichenfolge, die von dem Benutzer übergeben wurde, der den Befehl und alle Argumente enthält) im MVC-Controller. Ich rufe dann die InvokeCommand() -Methode für mein injected ICommandModule auf und gebe einen string -Befehl und einen List<string> args.

ein

Nehmen wir an, ich habe den folgenden Befehl:

%Vor%

Diese Zeile definiert den TOPIC-Befehl, der eine erforderliche ID-Nummer, eine optionale Seitennummer und einen optionalen Antwortbefehl mit einem Antwortwert akzeptiert.

Ich implementiere derzeit die Befehlsmethode wie folgt (Die Attribute über der Methode sind für Hilfementinformationen. Der HELP-Befehl verwendet Reflektion, um all diese zu lesen und ein organisiertes Hilfemenü anzuzeigen):

%Vor%

Meine derzeitige Implementierung ist ein riesiges Durcheinander. Ich wollte riesige IF-Anweisungen vermeiden, aber alles was ich tat, war eine riesige IF-Anweisung für alle Befehle zu handeln, für eine Tonne etwas weniger riesiger IF-Anweisungen für jeden Befehl und seine Argumente. Das ist nicht einmal die Hälfte davon; Ich habe diesen Befehl für diese Frage vereinfacht. In der tatsächlichen Implementierung gibt es einige weitere Argumente, die mit diesem Befehl zur Verfügung gestellt werden können und die IF-Anweisung ist die hässlichste, die ich je gesehen habe. Es ist sehr redundant und überhaupt nicht DRY (wiederhole dich nicht), da ich "Subcommand Not Found" an drei verschiedenen Stellen anzeigen muss.

Es genügt zu sagen, ich brauche eine bessere Lösung als diese.

Die ideale Implementierung

Idealerweise würde ich gerne meine Befehlsmethoden so etwas wie seine strukturieren:

%Vor%

Dann würde ich gerne tun:

  1. Empfange Befehlszeichenfolge vom Client.
  2. Übergeben Sie die Befehlszeichenfolge direkt an InvokeCommand() für ICommandModule .
  3. InvokeCommand() führt einige magische Parsing- und Reflektionsoperationen durch, um die richtige Befehlsmethode mit den richtigen Argumenten auszuwählen und ruft diese Methode auf, wobei nur die notwendigen Argumente übergeben werden.

Das Dilemma mit der idealen Implementierung

Ich bin mir nicht sicher, wie ich diese Logik strukturieren soll. Ich kratze mich seit Tagen am Kopf. Ich wünschte, ich hätte ein zweites Augenpaar, um mir dabei zu helfen (und mich schließlich auf einen Roman einer SO-Frage zu konzentrieren). In welcher Reihenfolge sollen Dinge passieren?

Soll ich den Befehl herausziehen, alle Methoden mit diesem Befehlsnamen finden, dann alle möglichen Argumente durchlaufen und dann die Argumente meiner Befehlszeile durchlaufen? Wie ermittle ich, was wohin geht und welche Argumente paarweise gehen? Wenn ich zum Beispiel meine Befehlszeichenfolge durchschaue und Reply "reply" finde, wie passe ich den Antwortinhalt mit der Antwortvariablen, während ich <ID> number antreffe und sie für das Id Argument versende?

Ich bin mir sicher, dass ich dich jetzt durcheinander bringe. Ich verwirre die Hölle aus mir heraus. Lassen Sie mich mit einigen Beispielen von Befehlsfolgen veranschaulichen, die der Benutzer weitergeben könnte:

%Vor%

Wie kann ich 36 an den Parameter Id senden? Wie kann ich antworten mit "Hey Leute, was ist los?" und pass "Hey Leute, was ist los?" als der Wert für das Antwortargument der Methode?

Um zu wissen, welche Methode zu überladen ist, muss ich wissen, wie viele Argumente angegeben wurden, damit ich diese Zahl mit der Überladung der Befehlsmethode vergleichen kann, die dieselbe Anzahl von Argumenten verwendet. Das Problem ist, TOP 36 Antwort "Hey Leute, was ist los?" ist eigentlich zwei Argumente, nicht drei als Antwort und "Hey Leute ..." gehen zusammen als ein Argument.

Es macht mir nichts aus, die InvokeCommand() -Methode ein wenig (oder sehr viel) aufzublähen, solange dies bedeutet, dass der komplexe Parsing- und Reflektions-Unsinn dort gehandhabt wird und meine Kommandomethoden schön und sauber bleiben und einfach zu schreiben sind .

Ich denke, ich suche hier wirklich nur nach einem Einblick. Hat jemand kreative Ideen, um dieses Problem zu lösen? Es ist wirklich ein großes Problem, weil das Argument IF-Anweisungen es derzeit sehr kompliziert macht, neue Befehle für die Anwendung zu schreiben. Die Befehle sind der eine Teil der Anwendung, die ich super einfach sein möchte, damit sie leicht erweitert und aktualisiert werden können. So sieht die aktuelle TOPIC-Befehlsmethode in meiner App aus:

%Vor%

Ist das nicht lächerlich? Jeder Befehl hat ein solches Monster und es ist inakzeptabel, aber ich bin am Ende meines Seils. Ich verletze mein Gehirn, das hier sitzt und stundenlang anstarrt. Ich gehe einfach über Szenarien in meinem Kopf und wie Code damit umgehen könnte. Ich war ziemlich stolz auf das Setup meines Befehlsmoduls, jetzt, wenn ich nur stolz auf die Implementierung der Befehlsmethode sein könnte ...

Wenn ich nicht auf Anhieb ein paar wirklich brillante Vorschläge bekomme, dann werde ich diese Frage eine Weile unbeantwortet lassen, damit ich eine Menge Ideen bekommen kann.

Während ich nicht mit meinem ganzen Modell (Befehlsmodule) für die Anwendung springen möchte, bin ich definitiv offen für Vorschläge. Ich bin vor allem an Vorschlägen interessiert, die sich auf das Parsen der Befehlszeile beziehen und ihre Argumente den richtigen Methodenüberladungen zuordnen. Ich bin mir sicher, dass jede Lösung, mit der ich arbeite, eine ordentliche Neugestaltung erfordert, also habe keine Angst davor, etwas zu empfehlen, was du für wertvoll hältst, auch wenn ich deinen Vorschlag nicht unbedingt nutze, um mich auf den richtigen Weg zu bringen / p>

Bearbeiten: Eine lange Frage länger machen

Ich wollte nur schnell klarstellen, dass die Zuordnung von Befehlen zu Befehlsmethoden nicht wirklich etwas ist, worüber ich mir Sorgen mache. Ich bin hauptsächlich besorgt darüber, wie man die Kommandozeilen-Zeichenfolge analysiert und organisiert. Gegenwärtig verwendet die InvokeCommand() -Methode eine sehr einfache C # -Reflexion, um die geeigneten Methoden zu finden:

%Vor%

Wie Sie sehen, mache ich mir keine Gedanken darüber, wie ich die Befehlsmethoden finden kann, da diese bereits funktionieren. Ich denke nur über eine gute Möglichkeit nach, die Befehlszeichenfolge zu analysieren, Argumente zu organisieren und dann diese Informationen zu verwenden, um die richtige Befehlsmethode / Überladung mithilfe der Reflektion auszuwählen.

BOUNTY UPDATE

Ich habe eine Prämie für diese Frage gestartet. Ich bin auf der Suche nach einer wirklich guten Möglichkeit, die Befehlsfolge zu parsen, die ich übergebe. Ich möchte, dass der Parser mehrere Dinge erkennt:

  • Optionen. Identifizieren Sie Optionen in der Befehlszeichenfolge.
  • Name / Wertpaare. Identifizieren Sie Name / Wert-Paare (z. B. [Seitenzahl] & lt; - enthält Stichwort "Seite" und Wert "#")
  • Nur Wert. Identifizieren Sie nur den Wert.

Ich möchte, dass diese über Metadaten bei der ersten Befehlsüberlastung identifiziert werden. Hier ist eine Liste von Beispielmethoden, die ich schreiben möchte und die mit einigen Metadaten versehen sind, die der Parser beim Spiegeln verwendet. Ich gebe Ihnen diese Methodenbeispiele und einige Beispielbefehlsstrings, die dieser Methode zugeordnet werden sollten. Dann überlasse ich es euch, SO Leute, eine gute Parser-Lösung zu finden.

%Vor%

Der Parser muss eine Befehlszeichenfolge aufnehmen, Reflektionen verwenden, um alle möglichen Überladungen der Befehlsmethoden zu finden, die Argumentattribute lesen, um zu ermitteln, wie die Zeichenfolge in eine geeignete Liste von Argumenten aufgeteilt wird, und dann den richtigen Befehl aufrufen Überladung der Methode, Übergabe der richtigen Argumente.

Lassen Sie es mich wissen, wenn Sie eine Klärung brauchen.

Jede Hilfe wird geschätzt. Danke an alle!

PS - Wenn Sie sich überhaupt für den Quellcode der Anwendung interessieren, finden Sie sie unter Ссылка . Zurzeit bin ich der einzige Entwickler, aber wenn Sie sich für das Projekt interessieren, dann schreiben Sie mir eine Zeile wie auf der Projektseite beschrieben.

    
Chev 04.06.2011, 07:08
quelle

4 Antworten

5

Sehen Sie sich Mono.Options an. Es ist derzeit Teil des Mono-Frameworks, kann aber heruntergeladen und als einzelne Bibliothek verwendet werden.

Aus irgendeinem Grund ist die Website, auf der sie früher gehostet wurde , aber Sie können grab aktuelle Version in Mono als einzelne Datei verwendet .

%Vor%     
Dan Abramov 05.06.2011, 09:42
quelle
4

Die Lösung, die ich normalerweise verwende, sieht ungefähr so ​​aus. Bitte ignoriere meine Syntaxfehler ... bin ein paar Monate seit ich C # benutzt habe. Ersetzen Sie im Grunde den Schalter if / else / durch einen System.Collections.Generic.Dictionary<string, /* Blah Blah */> -Lookup und einen virtuellen Funktionsaufruf.

%Vor%     
Billy ONeal 04.06.2011 07:40
quelle
4

Beachten Sie, dass Sie Attribute auf Parameter setzen können, die die Dinge lesbarer machen, z. B.

%Vor%     
Duncan Smart 04.06.2011 08:32
quelle
2

Ich kann hier zwei verschiedene Probleme sehen:

Auflösung des Methodennamens (als string ) für das Befehlsmodul

Sie könnten Dictionary verwenden, um einen String wie in Billys Antwort der Methode zuzuordnen. Wenn Sie nur Methode über Befehlsobjekt bevorzugen, können Sie der Methode direkt in C # eine Zeichenfolge zuordnen.

%Vor%

Befehlszeilenargumente analysieren

Menschen haben sich am Anfang die Köpfe gekratzt, um dieses Problem zu lösen.

Aber jetzt haben wir eine Bibliothek, die speziell mit diesem Problem umgeht. Sehen Sie Ссылка oder NDesk.Optionen . Dies sollte einfacher sein und könnte einige Fallstricke behandeln, anstatt neue einzuführen.

    
Gant 04.06.2011 08:06
quelle

Tags und Links