root / kamaki / cli / argument.py @ e3d4d442
History | View | Annotate | Download (7.2 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 |
class ConfigArgument(Argument): |
109 |
@property
|
110 |
def value(self): |
111 |
return super(self.__class__, self).value |
112 |
@value.setter
|
113 |
def value(self, config_file): |
114 |
self._value = Config(config_file) if config_file is not None else Config() |
115 |
|
116 |
def get(self, group, term): |
117 |
return self.value.get(group, term) |
118 |
|
119 |
def get_groups(self): |
120 |
return self.value.apis() |
121 |
|
122 |
_config_arg = ConfigArgument(1, 'Path to configuration file', '--config') |
123 |
|
124 |
class CmdLineConfigArgument(Argument): |
125 |
def __init__(self, config_arg, help='', parsed_name=None, default=None): |
126 |
super(self.__class__, self).__init__(1, help, parsed_name, default) |
127 |
self._config_arg = config_arg
|
128 |
|
129 |
@property
|
130 |
def value(self): |
131 |
return super(self.__class__, self).value |
132 |
@value.setter
|
133 |
def value(self, options): |
134 |
if options == self.default: |
135 |
return
|
136 |
options = [unicode(options)] if not isinstance(options, list) else options |
137 |
for option in options: |
138 |
keypath, sep, val = option.partition('=')
|
139 |
if not sep: |
140 |
raise CLISyntaxError(details='Missing = between key and value: -o section.key=val') |
141 |
section, sep, key = keypath.partition('.')
|
142 |
if not sep: |
143 |
raise CLISyntaxError(details='Missing . between section and key: -o section.key=val') |
144 |
self._config_arg.value.override(section.strip(), key.strip(), val.strip())
|
145 |
|
146 |
class FlagArgument(Argument): |
147 |
def __init__(self, help='', parsed_name=None, default=None): |
148 |
super(FlagArgument, self).__init__(0, help, parsed_name, default) |
149 |
|
150 |
class ValueArgument(Argument): |
151 |
def __init__(self, help='', parsed_name=None, default=None): |
152 |
super(ValueArgument, self).__init__(1, help, parsed_name, default) |
153 |
|
154 |
class IntArgument(ValueArgument): |
155 |
@property
|
156 |
def value(self): |
157 |
return getattr(self, '_value', self.default) |
158 |
@value.setter
|
159 |
def value(self, newvalue): |
160 |
if newvalue == self.default: |
161 |
self._value = self.default |
162 |
return
|
163 |
try:
|
164 |
self._value = int(newvalue) |
165 |
except ValueError: |
166 |
raise CLISyntaxError('IntArgument Error', details='Value %s not an int'%newvalue) |
167 |
|
168 |
class VersionArgument(FlagArgument): |
169 |
@property
|
170 |
def value(self): |
171 |
return super(self.__class__, self).value |
172 |
@value.setter
|
173 |
def value(self, newvalue): |
174 |
self._value = newvalue
|
175 |
self.main()
|
176 |
|
177 |
def main(self): |
178 |
if self.value: |
179 |
import kamaki |
180 |
print('kamaki %s'%kamaki.__version__)
|
181 |
|
182 |
_arguments = dict(config = _config_arg, help = Argument(0, 'Show help message', ('-h', '--help')), |
183 |
debug = FlagArgument('Include debug output', ('-d', '--debug')), |
184 |
include = FlagArgument('Include protocol headers in the output', ('-i', '--include')), |
185 |
silent = FlagArgument('Do not output anything', ('-s', '--silent')), |
186 |
verbose = FlagArgument('More info at response', ('-v', '--verbose')), |
187 |
version = VersionArgument('Print current version', ('-V', '--version')), |
188 |
options = CmdLineConfigArgument(_config_arg, 'Override a config value', ('-o', '--options')) |
189 |
) |
190 |
|
191 |
def parse_known_args(parser): |
192 |
parsed, unparsed = parser.parse_known_args() |
193 |
for name, arg in _arguments.items(): |
194 |
lala = getattr(parsed, name, arg.default)
|
195 |
arg.value = lala |
196 |
return parsed, unparsed
|