-
Notifications
You must be signed in to change notification settings - Fork 15
/
MultiCommand.py
140 lines (116 loc) · 5.46 KB
/
MultiCommand.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
#!/usr/bin/python3
#
# MultiCommand - Provide an openssl-style multi-command abstraction
# Copyright (C) 2011-2012 Johannes Bauer
#
# This file is part of jpycommon.
#
# jpycommon is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; this program is ONLY licensed under
# version 3 of the License, later versions are explicitly excluded.
#
# jpycommon is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with jpycommon; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
#
# Johannes Bauer <JohannesBauer@gmx.de>
#
# File UUID 4c6b89d0-ec0c-4b19-80d1-4daba7d80967
import sys
import textwrap
import collections
from FriendlyArgumentParser import FriendlyArgumentParser
from PrefixMatcher import PrefixMatcher
class MultiCommand(object):
RegisteredCommand = collections.namedtuple("RegisteredCommand", [ "name", "description", "parsergenerator", "action", "aliases" ])
ParseResult = collections.namedtuple("ParseResults", [ "cmd", "args" ])
def __init__(self, help = None):
self._help = help
self._commands = { }
self._aliases = { }
self._cmdorder = [ ]
def register(self, commandname, description, parsergenerator, **kwargs):
supported_kwargs = set(("aliases", "action"))
if len(set(kwargs.keys()) - supported_kwargs) > 0:
raise Exception("Unsupported kwarg found. Supported: %s" % (", ".join(sorted(list(supported_kwargs)))))
if (commandname in self._commands) or (commandname in self._aliases):
raise Exception("Command '%s' already registered." % (commandname))
aliases = kwargs.get("aliases", [ ])
action = kwargs.get("action")
for alias in aliases:
if (alias in self._commands) or (alias in self._aliases):
raise Exception("Alias '%s' already registered." % (alias))
self._aliases[alias] = commandname
cmd = self.RegisteredCommand(commandname, description, parsergenerator, action, aliases)
self._commands[commandname] = cmd
self._cmdorder.append(commandname)
def _show_syntax(self, msg = None):
if msg is not None:
print("Error: %s" % (msg), file = sys.stderr)
if self._help is not None:
print()
for line in textwrap.wrap(self._help):
print(line)
print()
print("Syntax: %s [command] [options]" % (sys.argv[0]), file = sys.stderr)
print(file = sys.stderr)
print("Available commands:", file = sys.stderr)
for commandname in self._cmdorder:
command = self._commands[commandname]
print(" %-15s %s" % (command.name, command.description))
print(file = sys.stderr)
print("Options vary from command to command. To receive further info, type", file = sys.stderr)
print(" %s [command] --help" % (sys.argv[0]), file = sys.stderr)
def _raise_error(self, msg, silent = False):
if silent:
raise Exception(msg)
else:
self._show_syntax(msg)
sys.exit(1)
def _getcmdnames(self):
return set(self._commands.keys()) | set(self._aliases.keys())
def parse(self, cmdline, silent = False):
if len(cmdline) < 1:
self._raise_error("No command supplied.")
# Check if we can match the command portion
pm = PrefixMatcher(self._getcmdnames())
try:
supplied_cmd = pm.matchunique(cmdline[0])
except Exception as e:
self._raise_error("Invalid command supplied: %s" % (str(e)))
if supplied_cmd in self._aliases:
supplied_cmd = self._aliases[supplied_cmd]
command = self._commands[supplied_cmd]
parser = FriendlyArgumentParser(prog = sys.argv[0] + " " + command.name, description = command.description, add_help = False)
command.parsergenerator(parser)
parser.setsilenterror(silent)
args = parser.parse_args(cmdline[1:])
return self.ParseResult(command, args)
def run(self, cmdline, silent = False):
parseresult = self.parse(cmdline, silent)
if parseresult.cmd.action is None:
raise Exception("Should run command '%s', but no action was registered." % (parseresult.cmd.name))
parseresult.cmd.action(parseresult.cmd.name, parseresult.args)
if __name__ == "__main__":
mc = MultiCommand()
def importaction(cmd, args):
print("Import:", cmd, args)
class ExportAction(object):
def __init__(self, cmd, args):
print("Export:", cmd, args)
def genparser(parser):
parser.add_argument("-i", "--infile", metavar = "filename", type = str, required = True, help = "Specifies the input text file that is to be imported. Mandatory argument.")
parser.add_argument("--verbose", action = "store_true", help = "Increase verbosity during the importing process.")
parser.add_argument("-n", "--world", metavar = "name", type = str, choices = [ "world", "foo", "bar" ], default = "overworld", help = "Specifies the world name. Possible options are %(choices)s. Default is %(default)s.")
mc.register("import", "Import some file from somewhere", genparser, action = importaction, aliases = [ "ymport" ])
def genparser(parser):
parser.add_argument("-o", "--outfile", metavar = "filename", type = str, required = True, help = "Specifies the input text file that is to be imported. Mandatory argument.")
parser.add_argument("--verbose", action = "store_true", help = "Increase verbosity during the importing process.")
mc.register("export", "Export some file to somewhere", genparser, action = ExportAction)
mc.run(sys.argv[1:])