File: MultiCommand.py

package info (click to toggle)
nitrotool 0.0.0-1
  • links: PTS, VCS
  • area: main
  • in suites: sid
  • size: 300 kB
  • sloc: python: 689; makefile: 3
file content (140 lines) | stat: -rw-r--r-- 5,772 bytes parent folder | download
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
140
#!/usr/bin/python3
#
#	MultiCommand - Provide an openssl-style multi-command abstraction
#	Copyright (C) 2011-2018 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 collections
import textwrap

from FriendlyArgumentParser import FriendlyArgumentParser
from PrefixMatcher import PrefixMatcher

class MultiCommand(object):
	RegisteredCommand = collections.namedtuple("RegisteredCommand", [ "name", "description", "parsergenerator", "action", "aliases", "visible" ])
	ParseResult = collections.namedtuple("ParseResults", [ "cmd", "args" ])

	def __init__(self):
		self._commands = { }
		self._aliases = { }
		self._cmdorder = [ ]

	def register(self, commandname, description, parsergenerator, **kwargs):
		supported_kwargs = set(("aliases", "action", "visible"))
		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, visible = kwargs.get("visible", True))
		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)
		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]
			if not command.visible:
				continue
			commandname_line = command.name
			for description_line in textwrap.wrap(command.description, width = 56):
				print("    %-15s    %s" % (commandname_line, description_line))
				commandname_line = ""
		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.add_argument("--help", action = "help", help = "Show this help page.")
		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:])