Statistics
| Branch: | Tag: | Revision:

root / kamaki / cli / argument.py @ edb7fc1a

History | View | Annotate | Download (10.7 kB)

1
# Copyright 2012 GRNET S.A. All rights reserved.
2
#
3
# Redistribution and use in source and binary forms, with or
4
# without modification, are permitted provided that the following
5
# conditions are met:
6
#
7
#   1. Redistributions of source code must retain the above
8
#     copyright notice, this list of conditions and the following
9
#     disclaimer.
10
#
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.
15
#
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.
28
#
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.
33

    
34
from kamaki.cli.config import Config
35
from kamaki.cli.errors import CLISyntaxError
36
from kamaki.cli.utils import split_input
37

    
38
from argparse import ArgumentParser, ArgumentError
39

    
40
try:
41
    from progress.bar import IncrementalBar
42
except ImportError:
43
    # progress not installed - pls, pip install progress
44
    pass
45

    
46

    
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.
51
    """
52

    
53
    def __init__(self, arity, help=None, parsed_name=None, default=None):
54
        if help is not None:
55
            self.help = help
56
        if parsed_name is not None:
57
            self.parsed_name = parsed_name
58
        if default is not None:
59
            self.default = default
60

    
61
    @property
62
    def parsed_name(self):
63
        """the string which will be recognised by the parser as an instance
64
            of this argument
65
        """
66
        return getattr(self, '_parsed_name', None)
67

    
68
    @parsed_name.setter
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)
73
        else:
74
            self._parsed_name.append(unicode(newname))
75

    
76
    @property
77
    def help(self):
78
        """a user friendly help message"""
79
        return getattr(self, '_help', None)
80

    
81
    @help.setter
82
    def help(self, newhelp):
83
        self._help = unicode(newhelp)
84

    
85
    @property
86
    def arity(self):
87
        """negative for repeating, 0 for flag, 1 or more for values"""
88
        return getattr(self, '_arity', None)
89

    
90
    @arity.setter
91
    def arity(self, newarity):
92
        newarity = int(newarity)
93
        self._arity = newarity
94

    
95
    @property
96
    def default(self):
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
100
        return self._default
101

    
102
    @default.setter
103
    def default(self, newdefault):
104
        self._default = newdefault
105

    
106
    @property
107
    def value(self):
108
        """the value of the argument"""
109
        return getattr(self, '_value', self.default)
110

    
111
    @value.setter
112
    def value(self, newvalue):
113
        self._value = newvalue
114

    
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\
119
            else 'store'
120
        parser.add_argument(*self.parsed_name, dest=name, action=action,
121
            default=self.default, help=self.help)
122

    
123
    def main(self):
124
        """Overide this method to give functionality to your args"""
125
        raise NotImplementedError
126

    
127

    
128
class ConfigArgument(Argument):
129
    """Manage a kamaki configuration file"""
130

    
131
    _config_file = None
132
    """The configuration file"""
133

    
134
    @property
135
    def value(self):
136
        super(self.__class__, self).value
137
        return super(self.__class__, self).value
138

    
139
    @value.setter
140
    def value(self, config_file):
141
        if 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)
146
        else:
147
            self._value = Config()
148

    
149
    def get(self, group, term):
150
        return self.value.get(group, term)
151

    
152
    def get_groups(self):
153
        return self.value.apis()
154

    
155
_config_arg = ConfigArgument(1, 'Path to configuration file', '--config')
156

    
157

    
158
class CmdLineConfigArgument(Argument):
159
    """Set a run-time setting option (not persistent)"""
160

    
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
164

    
165
    @property
166
    def value(self):
167
        return super(self.__class__, self).value
168

    
169
    @value.setter
170
    def value(self, options):
171
        if options == self.default:
172
            return
173
        if not isinstance(options, list):
174
            options = [unicode(options)]
175
        for option in options:
176
            keypath, sep, val = option.partition('=')
177
            if not sep:
178
                raise CLISyntaxError('Argument Syntax Error ',
179
                    details='%s is missing a "=" (usage: -o section.key=val)'\
180
                        % option)
181
            section, sep, key = keypath.partition('.')
182
        if not sep:
183
            key = section
184
            section = 'global'
185
        self._config_arg.value.override(
186
            section.strip(),
187
            key.strip(),
188
            val.strip())
189

    
190

    
191
class FlagArgument(Argument):
192
    """
193
    :value type: accepts no values from user
194
    :value returns: true if argument is set, false otherwise
195
    """
196

    
197
    def __init__(self, help='', parsed_name=None, default=None):
198
        super(FlagArgument, self).__init__(0, help, parsed_name, default)
199

    
200

    
201
class ValueArgument(Argument):
202
    """
203
    :value type: string
204
    :value returns: given value or default
205
    """
206

    
207
    def __init__(self, help='', parsed_name=None, default=None):
208
        super(ValueArgument, self).__init__(1, help, parsed_name, default)
209

    
210

    
211
class IntArgument(ValueArgument):
212
    """
213
    :value type: integer (type checking occurs)
214
    :value returns: an integer
215
    """
216

    
217
    @property
218
    def value(self):
219
        return getattr(self, '_value', self.default)
220

    
221
    @value.setter
222
    def value(self, newvalue):
223
        if newvalue == self.default:
224
            self._value = self.default
225
            return
226
        try:
227
            self._value = int(newvalue)
228
        except ValueError:
229
            raise CLISyntaxError('IntArgument Error',
230
                details='Value %s not an int' % newvalue)
231

    
232

    
233
class VersionArgument(FlagArgument):
234
    """A flag argument with that prints current version"""
235

    
236
    @property
237
    def value(self):
238
        return super(self.__class__, self).value
239

    
240
    @value.setter
241
    def value(self, newvalue):
242
        self._value = newvalue
243
        self.main()
244

    
245
    def main(self):
246
        if self.value:
247
            import kamaki
248
            print('kamaki %s' % kamaki.__version__)
249

    
250

    
251
class KeyValueArgument(Argument):
252
    """A Value Argument that can be repeated
253

254
    --<argument> key1=value1 --<argument> key2=value2 ...
255
    """
256

    
257
    def __init__(self, help='', parsed_name=None, default=[]):
258
        super(KeyValueArgument, self).__init__(-1, help, parsed_name, default)
259

    
260
    @property
261
    def value(self):
262
        return super(KeyValueArgument, self).value
263

    
264
    @value.setter
265
    def value(self, keyvalue_pairs):
266
        self._value = {}
267
        for pair in keyvalue_pairs:
268
            key, sep, val = pair.partition('=')
269
            if not sep:
270
                raise CLISyntaxError('Argument syntax error ',
271
                    details='%s is missing a "=" (usage: key1=val1 )\n' % pair)
272
            self._value[key.strip()] = val.strip()
273

    
274

    
275
class ProgressBarArgument(FlagArgument):
276

    
277
    def __init__(self, help='', parsed_name='', default=True):
278
        self.suffix = '%(percent)d%%'
279
        super(ProgressBarArgument, self).__init__(help, parsed_name, default)
280
        try:
281
            IncrementalBar
282
        except NameError:
283
            print('Waring: no progress bar functionality')
284

    
285
    def clone(self):
286
        newarg = ProgressBarArgument(
287
            self.help,
288
            self.parsed_name,
289
            self.default)
290
        newarg._value = self._value
291
        return newarg
292

    
293
    def get_generator(self, message, message_len=25):
294
        if self.value:
295
            return None
296
        try:
297
            self.bar = IncrementalBar()
298
        except NameError:
299
            self.value = None
300
            return self.value
301
        self.bar.message = message.ljust(message_len)
302
        self.bar.suffix = '%(percent)d%% - %(eta)ds'
303

    
304
        def progress_gen(n):
305
            for i in self.bar.iter(range(int(n))):
306
                yield
307
            yield
308
        return progress_gen
309

    
310
    def finish(self):
311
        if self.value:
312
            return
313
        mybar = getattr(self, 'bar', None)
314
        if mybar:
315
            mybar.finish()
316

    
317

    
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',
328
        ('-o', '--options'))
329
)
330

    
331

    
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)
336
    newparsed = []
337
    for term in unparsed:
338
        newparsed += split_input(' \'%s\' ' % term)
339
    return parsed, newparsed
340

    
341

    
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)
346
    return parser
347

    
348

    
349
def update_arguments(parser, arguments):
350
    for name, argument in arguments.items():
351
        try:
352
            argument.update_parser(parser, name)
353
        except ArgumentError:
354
            pass