Costruire un parser di stringhe per il comando e il controllo dell'utente?

2

Il mio obiettivo è creare un parser di comandi con sintassi di base e più rami possibili in ogni punto. Questi comandi provengono dagli utenti del sistema e sono di input di testo (senza GUI). La sintassi di base è base_command [sub_command [optional_parameters]] , in cui optional_parameters è uno spazio separato elenco di parametri.

Esempi:

add event "Program-a-thon"
add notification program-a-thon meeting
add pattern "follow the white rabbit"
remove event "Program-a-thon"
remove notification meeting
remove pattern "follow the white rabbit"
edit pattern "follow the white rabbit" "follow the white tiger"
reload settings

Come puoi vedere, ho 4 comandi di base ( add , remove , edit e reload ). Tuttavia, prevedo di aggiungerne altri in futuro. Ho anche 4 sottocomandi ( event , notification , pattern e settings ). Tuttavia, edit ha solo 1 sottocomando e settings si applica solo al comando reload base. Non tutti i sottocomandi sono associati a tutti i comandi di base. Infine, ho dei parametri. Per semplicità, sto pensando di rendere queste posizioni basate.

Sto scrivendo questa applicazione in Python (utilizzando il ramo 2.7).

Finora, ho trovato questo:

  • Utilizza il comando .split() e prendi i primi due risultati per ottenere il comando e il comando di base. Il terzo indice (se esiste) sarà gli argomenti posizionali.

    o

  • Utilizzare un'espressione regolare per ottenere il comando di base e il sottocomando. Il terzo indice sarà argomenti posizionali.

Quindi, utilizzando queste informazioni, guarda un dizionario come questo per vedere se l'opzione comando / sottocomando è valida. Se lo è, chiama method descritto nella chiave commands[base_command][sub_command]['method'] per eseguire la mia azione.

Il lato positivo di questo è che posso facilmente capire se la combinazione comando / sottocomando è valida. Il rovescio della medaglia è mantenere un tale dizionario ed eventualmente estenderlo se desidero aggiungere più opzioni o informazioni. Per questi pochi comandi ho già un dizionario come questo (con alcuni attributi di supporto)

commands = 
{
    'add' : 
    {
        'help_text' : "Add a 'event', 'notification', or 'pattern'",
        'sub_commands': {
                'event' : {
                    'help_text' : "Add an event",
                    'method' : "add_event",
                    'permissions' : 'trusted',
                },
                'notification' : {
                    'help_text' : "Recieve a notification for events that are tagged with the values you wish (tag1 [,tag2[,tag3[,tag4]]])",
                    'method' : "add_notification",
                    'permissions' : 'all',
                },
                'pattern' : {
                    'help_text' : "Add a pattern you wish the system to watch for in new event descriptions",
                    'method' : "add_pattern",
                    'permissions' : 'all',
                },
            },
        'sub_command_required' : True,
        'method' : None,
    },
    'remove':
    {
        'help_text': "Remove a 'event', 'notification', or 'pattern'",
        'sub_commands': {
                'event' : {
                    'help_text' : "Remove an event",
                    'method' : "remove_event",
                    'permissions' : 'trusted',
                },
                'notification' : {
                    'help_text' : "Remove a notification for specific tags (tag1 [,tag2[,tag3[,tag4]]])",
                    'method' : "remove_notification",
                    'permissions' : 'all',
                },
                'pattern' : {
                    'help_text' : "Add a pattern you wish the system to watch for in new event descriptions",
                    'method' : "remove_pattern",
                    'permissions' : 'all',
                },
            },
        'sub_command_required' : True,
        'method' : None,            
    },
    'edit':
    {
        'help_text': "Edit an existing 'pattern'",
        'sub_commands': {
                'pattern' : {
                    'help_text' : "Edit a pattern pattern to watch for in new event descriptions (old_pattern, new_pattern)",
                    'method' : "edit_pattern",
                    'permissions' : 'all',
                },
            },
        'sub_command_required' : True,
        'method' : None,
    },
    'reload':
    {
        'help_text': "Reload application options",
        'sub_commands': {
                'settings' : {
                    'help_text' : "Reload the settings of the application",
                    'method' : "reload_settings",
                    'permissions' : 'owner',
                },
            },
        'sub_command_required' : True,
        'method' : None,
    },
}

Le mie domande sono, come faccio a progettare correttamente il parser in modo che io possa

  1. Estendi l'albero dei comandi facilmente in futuro?
  2. Hai una combinazione di comandi secondari che possono essere applicati a tutti i comandi di base?

E

  1. L'utilizzo di un dizionario, come sopra, vale il problema della manutenzione durante l'aggiunta di comandi? Mi rendo conto che i comandi saranno "relativamente" stabili (una volta aggiunte, le modifiche saranno rare al dizionario stesso). Tuttavia, quando li aggiungo, probabilmente arriveranno almeno in coppie ( add e remove ), il che significa che il potenziale per incasinare il dizionario si verifica in più punti.
posta Andy 20.09.2014 - 03:47
fonte

2 risposte

1

Dai un'occhiata al documento argparse e cmd moduli. Se desideri creare un'interfaccia utente interattiva, una sottoclasse di cmd.Cmd e per ciascuno dei comandi, puoi utilizzare argparse per analizzare il sottocomando e le opzioni.

import readline # for history and autocomplete
import cmd

class MyShell(cmd.Cmd):
    def do_add(self, line):
        "adds various things"
        print "add called with arguments", line # here you can use argparse on line


>>> MyShell().cmdloop()
(Cmd) xyz
*** Unknown syntax: xyz
(Cmd) help

Documented commands (type help <topic>):
========================================
add  help

(Cmd) help add
adds various things
(Cmd) add
add called with arguments
(Cmd) add 1 2 3 4
add called with arguments 1 2 3 4
(Cmd)
    
risposta data 02.11.2014 - 11:13
fonte
0

L'analisi di questo tipo di comandi è molto simile all'analisi delle opzioni della riga di comando e il fatto che tu abbia una sola parola comandi e argomenti rende più semplice.

Ciò che in pratica puoi fare è leggere una riga e dividerla usando un'espressione regolare. La ragione per cui sceglierei un'espressione regolare è che rende più semplice dividere la stringa e mantenere il testo tra virgolette.

Quando la linea è divisa, devi cercare il comando, o puoi prima cercare il sottocomando. Poiché un 'evento' può essere aggiunto e rimosso, potrebbe essere logico combinarli nel dizionario sotto 'evento' invece di 'aggiungere' e 'rimuovere', perché si fa l'operazione di aggiunta e rimozione su un evento.

I valori nel dizionario potrebbero essere classi di comando che hanno le conoscenze per analizzare gli argomenti, eseguire il comando e stampare il testo della guida. In questo modo il dizionario principale rimarrà leggibile e manutenibile e i comandi potranno essere testati individualmente.

Un piccolo esempio in Python come pseudo-codice di ciò che intendo:

class commandBase
    def execute(subcommand, args):
        self.subCommands[subcommand].method(args);

class eventCommand : commandBase
    def __init__(self):
        self.subCommands = {
           "add": { "help_text" : "add an event", method: self.add }
           "remove": { "help_text" : "remove an event", method: self.remove }
        }

command = { "event" : new eventCommand }
parsedLine = parseLine(line)
command[parsedLine[1]].execute(parsedLine[0], pardesLine[2])
    
risposta data 03.10.2014 - 11:30
fonte

Leggi altre domande sui tag