1 # Copyright 2012 GRNET S.A. All rights reserved.
3 # Redistribution and use in source and binary forms, with or
4 # without modification, are permitted provided that the following
7 # 1. Redistributions of source code must retain the above
8 # copyright notice, this list of conditions and the following
11 # 2. Redistributions in binary form must reproduce the above
12 # copyright notice, this list of conditions and the following
13 # disclaimer in the documentation and/or other materials
14 # provided with the distribution.
16 # THIS SOFTWARE IS PROVIDED BY GRNET S.A. ``AS IS'' AND ANY EXPRESS
17 # OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
18 # WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
19 # PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GRNET S.A OR
20 # CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
21 # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
22 # LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
23 # USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
24 # AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
25 # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
26 # ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
27 # POSSIBILITY OF SUCH DAMAGE.
29 # The views and conclusions contained in the software and
30 # documentation are those of the authors and should not be
31 # interpreted as representing official policies, either expressed
32 # or implied, of GRNET S.A.
34 from kamaki.cli.config import Config
35 from kamaki.cli.errors import CLISyntaxError
36 from kamaki.cli.utils import split_input
38 from argparse import ArgumentParser, ArgumentError
41 from progress.bar import IncrementalBar
43 # progress not installed - pls, pip install progress
47 class Argument(object):
48 """An argument that can be parsed from command line or otherwise.
49 This is the general Argument class. It is suggested to extent this
50 class into more specific argument types.
53 def __init__(self, arity, help=None, parsed_name=None, default=None):
56 if parsed_name is not None:
57 self.parsed_name = parsed_name
58 if default is not None:
59 self.default = default
62 def parsed_name(self):
63 """the string which will be recognised by the parser as an instance
66 return getattr(self, '_parsed_name', None)
69 def parsed_name(self, newname):
70 self._parsed_name = getattr(self, '_parsed_name', [])
71 if isinstance(newname, list) or isinstance(newname, tuple):
72 self._parsed_name += list(newname)
74 self._parsed_name.append(unicode(newname))
78 """a user friendly help message"""
79 return getattr(self, '_help', None)
82 def help(self, newhelp):
83 self._help = unicode(newhelp)
87 """negative for repeating, 0 for flag, 1 or more for values"""
88 return getattr(self, '_arity', None)
91 def arity(self, newarity):
92 newarity = int(newarity)
93 self._arity = newarity
97 """the value of this argument when not set"""
98 if not hasattr(self, '_default'):
99 self._default = False if self.arity == 0 else None
103 def default(self, newdefault):
104 self._default = newdefault
108 """the value of the argument"""
109 return getattr(self, '_value', self.default)
112 def value(self, newvalue):
113 self._value = newvalue
115 def update_parser(self, parser, name):
116 """Update argument parser with self info"""
117 action = 'append' if self.arity < 0\
118 else 'store_true' if self.arity == 0\
120 parser.add_argument(*self.parsed_name, dest=name, action=action,
121 default=self.default, help=self.help)
124 """Overide this method to give functionality to your args"""
125 raise NotImplementedError
128 class ConfigArgument(Argument):
129 """Manage a kamaki configuration file"""
132 """The configuration file"""
136 super(self.__class__, self).value
137 return super(self.__class__, self).value
140 def value(self, config_file):
142 self._value = Config(config_file)
143 self._config_file = config_file
144 elif self._config_file:
145 self._value = Config(self._config_file)
147 self._value = Config()
149 def get(self, group, term):
150 return self.value.get(group, term)
152 def get_groups(self):
153 return self.value.apis()
155 _config_arg = ConfigArgument(1, 'Path to configuration file', '--config')
158 class CmdLineConfigArgument(Argument):
159 """Set a run-time setting option (not persistent)"""
161 def __init__(self, config_arg, help='', parsed_name=None, default=None):
162 super(self.__class__, self).__init__(1, help, parsed_name, default)
163 self._config_arg = config_arg
167 return super(self.__class__, self).value
170 def value(self, options):
171 if options == self.default:
173 if not isinstance(options, list):
174 options = [unicode(options)]
175 for option in options:
176 keypath, sep, val = option.partition('=')
178 raise CLISyntaxError('Argument Syntax Error ',
179 details='%s is missing a "=" (usage: -o section.key=val)'\
181 section, sep, key = keypath.partition('.')
185 self._config_arg.value.override(
191 class FlagArgument(Argument):
193 :value type: accepts no values from user
194 :value returns: true if argument is set, false otherwise
197 def __init__(self, help='', parsed_name=None, default=None):
198 super(FlagArgument, self).__init__(0, help, parsed_name, default)
201 class ValueArgument(Argument):
204 :value returns: given value or default
207 def __init__(self, help='', parsed_name=None, default=None):
208 super(ValueArgument, self).__init__(1, help, parsed_name, default)
211 class IntArgument(ValueArgument):
213 :value type: integer (type checking occurs)
214 :value returns: an integer
219 return getattr(self, '_value', self.default)
222 def value(self, newvalue):
223 if newvalue == self.default:
224 self._value = self.default
227 self._value = int(newvalue)
229 raise CLISyntaxError('IntArgument Error',
230 details='Value %s not an int' % newvalue)
233 class VersionArgument(FlagArgument):
234 """A flag argument with that prints current version"""
238 return super(self.__class__, self).value
241 def value(self, newvalue):
242 self._value = newvalue
248 print('kamaki %s' % kamaki.__version__)
251 class KeyValueArgument(Argument):
252 """A Value Argument that can be repeated
254 --<argument> key1=value1 --<argument> key2=value2 ...
257 def __init__(self, help='', parsed_name=None, default=[]):
258 super(KeyValueArgument, self).__init__(-1, help, parsed_name, default)
262 return super(KeyValueArgument, self).value
265 def value(self, keyvalue_pairs):
267 for pair in keyvalue_pairs:
268 key, sep, val = pair.partition('=')
270 raise CLISyntaxError('Argument syntax error ',
271 details='%s is missing a "=" (usage: key1=val1 )\n' % pair)
272 self._value[key.strip()] = val.strip()
275 class ProgressBarArgument(FlagArgument):
277 def __init__(self, help='', parsed_name='', default=True):
278 self.suffix = '%(percent)d%%'
279 super(ProgressBarArgument, self).__init__(help, parsed_name, default)
283 print('Waring: no progress bar functionality')
286 newarg = ProgressBarArgument(
290 newarg._value = self._value
293 def get_generator(self, message, message_len=25):
297 self.bar = IncrementalBar()
301 self.bar.message = message.ljust(message_len)
302 self.bar.suffix = '%(percent)d%% - %(eta)ds'
305 for i in self.bar.iter(range(int(n))):
313 mybar = getattr(self, 'bar', None)
318 _arguments = dict(config=_config_arg,
319 help=Argument(0, 'Show help message', ('-h', '--help')),
320 debug=FlagArgument('Include debug output', ('-d', '--debug')),
321 include=FlagArgument('Include protocol headers in the output',
322 ('-i', '--include')),
323 silent=FlagArgument('Do not output anything', ('-s', '--silent')),
324 verbose=FlagArgument('More info at response', ('-v', '--verbose')),
325 version=VersionArgument('Print current version', ('-V', '--version')),
326 options=CmdLineConfigArgument(_config_arg,
327 'Override a config value',
332 def parse_known_args(parser, arguments=None):
333 parsed, unparsed = parser.parse_known_args()
334 for name, arg in arguments.items():
335 arg.value = getattr(parsed, name, arg.default)
337 for term in unparsed:
338 newparsed += split_input(' \'%s\' ' % term)
339 return parsed, newparsed
342 def init_parser(exe, arguments):
343 parser = ArgumentParser(add_help=False)
344 parser.prog = '%s <cmd_group> [<cmd_subbroup> ...] <cmd>' % exe
345 update_arguments(parser, arguments)
349 def update_arguments(parser, arguments):
350 for name, argument in arguments.items():
352 argument.update_parser(parser, name)
353 except ArgumentError: