Statistics
| Branch: | Tag: | Revision:

root / kamaki / cli / argument.py @ 329753ae

History | View | Annotate | Download (9.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 argparse import ArgumentParser, ArgumentError
37

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

    
44

    
45
class Argument(object):
46
    """An argument that can be parsed from command line or otherwise"""
47

    
48
    def __init__(self, arity, help=None, parsed_name=None, default=None):
49
        self.arity = int(arity)
50

    
51
        if help is not None:
52
            self.help = help
53
        if parsed_name is not None:
54
            self.parsed_name = parsed_name
55
        if default is not None:
56
            self.default = default
57

    
58
    @property
59
    def parsed_name(self):
60
        return getattr(self, '_parsed_name', None)
61

    
62
    @parsed_name.setter
63
    def parsed_name(self, newname):
64
        self._parsed_name = getattr(self, '_parsed_name', [])
65
        if isinstance(newname, list) or isinstance(newname, tuple):
66
            self._parsed_name += list(newname)
67
        else:
68
            self._parsed_name.append(unicode(newname))
69

    
70
    @property
71
    def help(self):
72
        return getattr(self, '_help', None)
73

    
74
    @help.setter
75
    def help(self, newhelp):
76
        self._help = unicode(newhelp)
77

    
78
    @property
79
    def arity(self):
80
        return getattr(self, '_arity', None)
81

    
82
    @arity.setter
83
    def arity(self, newarity):
84
        newarity = int(newarity)
85
        self._arity = newarity
86

    
87
    @property
88
    def default(self):
89
        if not hasattr(self, '_default'):
90
            self._default = False if self.arity == 0 else None
91
        return self._default
92

    
93
    @default.setter
94
    def default(self, newdefault):
95
        self._default = newdefault
96

    
97
    @property
98
    def value(self):
99
        return getattr(self, '_value', self.default)
100

    
101
    @value.setter
102
    def value(self, newvalue):
103
        self._value = newvalue
104

    
105
    def update_parser(self, parser, name):
106
        """Update an argument parser with this argument info"""
107
        action = 'append' if self.arity < 0\
108
            else 'store_true' if self.arity == 0\
109
            else 'store'
110
        parser.add_argument(*self.parsed_name, dest=name, action=action,
111
            default=self.default, help=self.help)
112

    
113
    def main(self):
114
        """Overide this method to give functionality to ur args"""
115
        raise NotImplementedError
116

    
117

    
118
class ConfigArgument(Argument):
119
    _config_file = None
120

    
121
    @property
122
    def value(self):
123
        super(self.__class__, self).value
124
        return super(self.__class__, self).value
125

    
126
    @value.setter
127
    def value(self, config_file):
128
        if config_file:
129
            self._value = Config(config_file)
130
            self._config_file = config_file
131
        elif self._config_file:
132
            self._value = Config(self._config_file)
133
        else:
134
            self._value = Config()
135

    
136
    def get(self, group, term):
137
        return self.value.get(group, term)
138

    
139
    def get_groups(self):
140
        return self.value.apis()
141

    
142
_config_arg = ConfigArgument(1, 'Path to configuration file', '--config')
143

    
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

    
154
    @value.setter
155
    def value(self, options):
156
        if options == self.default:
157
            return
158
        if not isinstance(options, list):
159
            options = [unicode(options)]
160
        for option in options:
161
            keypath, sep, val = option.partition('=')
162
            if not sep:
163
                raise CLISyntaxError('Argument Syntax Error ',
164
                    details='%s is missing a "=" (usage: -o section.key=val)'\
165
                        % option)
166
            section, sep, key = keypath.partition('.')
167
        if not sep:
168
            key = section
169
            section = 'global'
170
        self._config_arg.value.override(
171
            section.strip(),
172
            key.strip(),
173
            val.strip())
174

    
175

    
176
class FlagArgument(Argument):
177
    def __init__(self, help='', parsed_name=None, default=None):
178
        super(FlagArgument, self).__init__(0, help, parsed_name, default)
179

    
180

    
181
class ValueArgument(Argument):
182
    def __init__(self, help='', parsed_name=None, default=None):
183
        super(ValueArgument, self).__init__(1, help, parsed_name, default)
184

    
185

    
186
class IntArgument(ValueArgument):
187
    @property
188
    def value(self):
189
        return getattr(self, '_value', self.default)
190

    
191
    @value.setter
192
    def value(self, newvalue):
193
        if newvalue == self.default:
194
            self._value = self.default
195
            return
196
        try:
197
            self._value = int(newvalue)
198
        except ValueError:
199
            raise CLISyntaxError('IntArgument Error',
200
                details='Value %s not an int' % newvalue)
201

    
202

    
203
class VersionArgument(FlagArgument):
204
    @property
205
    def value(self):
206
        return super(self.__class__, self).value
207

    
208
    @value.setter
209
    def value(self, newvalue):
210
        self._value = newvalue
211
        self.main()
212

    
213
    def main(self):
214
        if self.value:
215
            import kamaki
216
            print('kamaki %s' % kamaki.__version__)
217

    
218

    
219
class KeyValueArgument(Argument):
220
    def __init__(self, help='', parsed_name=None, default=[]):
221
        super(KeyValueArgument, self).__init__(-1, help, parsed_name, default)
222

    
223
    @property
224
    def value(self):
225
        return super(KeyValueArgument, self).value
226

    
227
    @value.setter
228
    def value(self, keyvalue_pairs):
229
        self._value = {}
230
        for pair in keyvalue_pairs:
231
            key, sep, val = pair.partition('=')
232
            if not sep:
233
                raise CLISyntaxError('Argument syntax error ',
234
                    details='%s is missing a "=" (usage: key1=val1 )\n' % pair)
235
            self._value[key.strip()] = val.strip()
236

    
237

    
238
class ProgressBarArgument(FlagArgument):
239

    
240
    def __init__(self, help='', parsed_name='', default=True):
241
        self.suffix = '%(percent)d%%'
242
        super(ProgressBarArgument, self).__init__(help, parsed_name, default)
243
        try:
244
            IncrementalBar
245
        except NameError:
246
            print('Waring: no progress bar functionality')
247

    
248
    def clone(self):
249
        newarg = ProgressBarArgument(
250
            self.help,
251
            self.parsed_name,
252
            self.default)
253
        newarg._value = self._value
254
        return newarg
255

    
256
    def get_generator(self, message, message_len=25):
257
        if self.value:
258
            return None
259
        try:
260
            self.bar = IncrementalBar()
261
        except NameError:
262
            self.value = None
263
            return self.value
264
        self.bar.message = message.ljust(message_len)
265
        self.bar.suffix = '%(percent)d%% - %(eta)ds'
266

    
267
        def progress_gen(n):
268
            for i in self.bar.iter(range(int(n))):
269
                yield
270
            yield
271
        return progress_gen
272

    
273
    def finish(self):
274
        if self.value:
275
            return
276
        mybar = getattr(self, 'bar', None)
277
        if mybar:
278
            mybar.finish()
279

    
280

    
281
_arguments = dict(config=_config_arg,
282
    help=Argument(0, 'Show help message', ('-h', '--help')),
283
    debug=FlagArgument('Include debug output', ('-d', '--debug')),
284
    include=FlagArgument('Include protocol headers in the output',
285
        ('-i', '--include')),
286
    silent=FlagArgument('Do not output anything', ('-s', '--silent')),
287
    verbose=FlagArgument('More info at response', ('-v', '--verbose')),
288
    version=VersionArgument('Print current version', ('-V', '--version')),
289
    options=CmdLineConfigArgument(_config_arg,
290
        'Override a config value',
291
        ('-o', '--options'))
292
)
293

    
294

    
295
def parse_known_args(parser, arguments=None):
296
    parsed, unparsed = parser.parse_known_args()
297
    for name, arg in arguments.items():
298
        arg.value = getattr(parsed, name, arg.default)
299
    unparsed = ['"%s"' % s if ' ' in s else s for s in unparsed]
300
    return parsed, unparsed
301

    
302

    
303
def init_parser(exe, arguments):
304
    parser = ArgumentParser(add_help=False)
305
    parser.prog = '%s <cmd_group> [<cmd_subbroup> ...] <cmd>' % exe
306
    update_arguments(parser, arguments)
307
    return parser
308

    
309

    
310
def update_arguments(parser, arguments):
311
    for name, argument in arguments.items():
312
        try:
313
            argument.update_parser(parser, name)
314
        except ArgumentError:
315
            pass