root / kamaki / cli / argument.py @ dfee2caf
History | View | Annotate | Download (6.4 kB)
1 |
#A. One-command CLI
|
---|---|
2 |
# 1. Get a command string DONE
|
3 |
# 2. Parse out some Arguments DONE
|
4 |
# a. We need an Argument "library" for each command-level DONE
|
5 |
# b. Handle arg errors
|
6 |
# 3. Retrieve and validate command_sequence
|
7 |
# a. For faster responses, first command can be chosen from
|
8 |
# a prefixed list of names, loaded from the config file
|
9 |
# b. Normally, each 1st level command has a file to read
|
10 |
# command specs from. Load command_specs in this file
|
11 |
# i. A dict with command specs is created
|
12 |
# e.g. {'store':{'list':{'all', None}, 'info'}, 'server':{'list', 'info'}}
|
13 |
# but in this case there will be only 'store', or 'server', etc.
|
14 |
# c. Now, loop over the other parsed terms and check them against the commands
|
15 |
# i. That will produce a path of the form ['store', 'list' 'all']
|
16 |
# d. Catch syntax errors
|
17 |
# 4. Instaciate object to exec
|
18 |
# a. For path ['store', 'list', 'all'] instatiate store_list_all()
|
19 |
# 5. Parse out some more Arguments
|
20 |
# a. Each command path has an "Argument library" to check your args against
|
21 |
# 6. Call object.main() and catch ClientErrors
|
22 |
# a. Now, there are some command-level syntax errors that we should catch
|
23 |
# as syntax errors? Maybe! Why not?
|
24 |
|
25 |
#Shell
|
26 |
# 1. Load ALL available command specs in advance
|
27 |
# 2. Iimport cmd (and run it ?)
|
28 |
# 3. There is probably a way to tell cmd of the command paths you support.
|
29 |
# 4. If cmd does not support it, for the sellected path call parse out stuff
|
30 |
# as in One-command
|
31 |
# 5. Instatiate, parse_out and run object like in One-command
|
32 |
# 6. Run object.main() . Again, catch ClientErrors and, probably, syntax errors
|
33 |
import gevent.monkey |
34 |
#Monkey-patch everything for gevent early on
|
35 |
gevent.monkey.patch_all() |
36 |
|
37 |
from sys import exit |
38 |
|
39 |
from .config import Config |
40 |
from .errors import CLISyntaxError |
41 |
|
42 |
class Argument(object): |
43 |
"""An argument that can be parsed from command line or otherwise"""
|
44 |
|
45 |
def __init__(self, arity, help=None, parsed_name=None, default=None): |
46 |
self.arity = int(arity) |
47 |
|
48 |
if help is not None: |
49 |
self.help = help
|
50 |
if parsed_name is not None: |
51 |
self.parsed_name = parsed_name
|
52 |
if default is not None: |
53 |
self.default = default
|
54 |
|
55 |
@property
|
56 |
def parsed_name(self): |
57 |
return getattr(self, '_parsed_name', None) |
58 |
@parsed_name.setter
|
59 |
def parsed_name(self, newname): |
60 |
self._parsed_name = getattr(self, '_parsed_name', []) |
61 |
if isinstance(newname, list) or isinstance(newname, tuple): |
62 |
self._parsed_name += list(newname) |
63 |
else:
|
64 |
self._parsed_name.append(unicode(newname)) |
65 |
|
66 |
@property
|
67 |
def help(self): |
68 |
return getattr(self, '_help', None) |
69 |
@help.setter
|
70 |
def help(self, newhelp): |
71 |
self._help = unicode(newhelp) |
72 |
|
73 |
@property
|
74 |
def arity(self): |
75 |
return getattr(self, '_arity', None) |
76 |
@arity.setter
|
77 |
def arity(self, newarity): |
78 |
newarity = int(newarity)
|
79 |
assert newarity >= 0 |
80 |
self._arity = newarity
|
81 |
|
82 |
@property
|
83 |
def default(self): |
84 |
if not hasattr(self, '_default'): |
85 |
self._default = False if self.arity == 0 else None |
86 |
return self._default |
87 |
@default.setter
|
88 |
def default(self, newdefault): |
89 |
self._default = newdefault
|
90 |
|
91 |
@property
|
92 |
def value(self): |
93 |
return getattr(self, '_value', self.default) |
94 |
@value.setter
|
95 |
def value(self, newvalue): |
96 |
self._value = newvalue
|
97 |
|
98 |
def update_parser(self, parser, name): |
99 |
"""Update an argument parser with this argument info"""
|
100 |
action = 'store_true' if self.arity==0 else 'store' |
101 |
parser.add_argument(*self.parsed_name, dest=name, action=action,
|
102 |
default=self.default, help=self.help) |
103 |
|
104 |
def main(self): |
105 |
"""Overide this method to give functionality to ur args"""
|
106 |
raise NotImplementedError |
107 |
|
108 |
@classmethod
|
109 |
def test(self): |
110 |
h = Argument(arity=0, help='Display a help massage', parsed_name=('--help', '-h')) |
111 |
b = Argument(arity=1, help='This is a bbb', parsed_name='--bbb') |
112 |
c = Argument(arity=2, help='This is a ccc', parsed_name='--ccc') |
113 |
|
114 |
from argparse import ArgumentParser |
115 |
parser = ArgumentParser(add_help=False)
|
116 |
h.update_parser(parser, 'hee')
|
117 |
b.update_parser(parser, 'bee')
|
118 |
c.update_parser(parser, 'cee')
|
119 |
|
120 |
args, argv = parser.parse_known_args() |
121 |
print('args: %s\nargv: %s'%(args, argv))
|
122 |
|
123 |
class VersionArgument(Argument): |
124 |
@property
|
125 |
def value(self): |
126 |
return super(self.__class__, self).value |
127 |
@value.setter
|
128 |
def value(self, newvalue): |
129 |
self._value = newvalue
|
130 |
self.main()
|
131 |
|
132 |
def main(self): |
133 |
if self.value: |
134 |
import kamaki |
135 |
print('kamaki %s'%kamaki.__version__)
|
136 |
|
137 |
class ConfigArgument(Argument): |
138 |
@property
|
139 |
def value(self): |
140 |
return super(self.__class__, self).value |
141 |
@value.setter
|
142 |
def value(self, config_file): |
143 |
self._value = Config(config_file) if config_file is not None else Config() |
144 |
|
145 |
class CmdLineConfigArgument(Argument): |
146 |
def __init__(self, config_arg, help='', parsed_name=None, default=None): |
147 |
super(self.__class__, self).__init__(1, help, parsed_name, default) |
148 |
self._config_arg = config_arg
|
149 |
|
150 |
@property
|
151 |
def value(self): |
152 |
return super(self.__class__, self).value |
153 |
@value.setter
|
154 |
def value(self, options): |
155 |
if options == self.default: |
156 |
return
|
157 |
options = [unicode(options)] if not isinstance(options, list) else options |
158 |
for option in options: |
159 |
keypath, sep, val = option.partition('=')
|
160 |
if not sep: |
161 |
raise CLISyntaxError(details='Missing = between key and value: -o section.key=val') |
162 |
section, sep, key = keypath.partition('.')
|
163 |
if not sep: |
164 |
raise CLISyntaxError(details='Missing . between section and key: -o section.key=val') |
165 |
self._config_arg.value.override(section.strip(), key.strip(), val.strip())
|
166 |
|
167 |
_config_arg = ConfigArgument(1, 'Path to configuration file', '--config') |
168 |
_arguments = dict(config = _config_arg,
|
169 |
debug = Argument(0, 'Include debug output', ('-d', '--debug')), |
170 |
include = Argument(0, 'Include protocol headers in the output', ('-i', '--include')), |
171 |
silent = Argument(0, 'Do not output anything', ('-s', '--silent')), |
172 |
verbose = Argument(0, 'More info at response', ('-v', '--verbose')), |
173 |
version = VersionArgument(0, 'Print current version', ('-V', '--version')), |
174 |
options = CmdLineConfigArgument(_config_arg, 'Override a config value', ('-o', '--options')) |
175 |
) |
176 |
|
177 |
def parse_known_args(parser): |
178 |
parsed, unparsed = parser.parse_known_args() |
179 |
for name, arg in _arguments.items(): |
180 |
arg.value = getattr(parsed, name, arg.value)
|
181 |
return parsed, unparsed
|
182 |
|
183 |
|