Statistics
| Branch: | Tag: | Revision:

root / kamaki / cli / argument.py @ 852a22e7

History | View | Annotate | Download (9.4 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
    @property
120
    def value(self):
121
        super(self.__class__, self).value
122
        return super(self.__class__, self).value
123

    
124
    @value.setter
125
    def value(self, config_file):
126
        self._value = Config(config_file) if config_file else Config()
127

    
128
    def get(self, group, term):
129
        return self.value.get(group, term)
130

    
131
    def get_groups(self):
132
        return self.value.apis()
133

    
134
_config_arg = ConfigArgument(1, 'Path to configuration file', '--config')
135

    
136

    
137
class CmdLineConfigArgument(Argument):
138
    def __init__(self, config_arg, help='', parsed_name=None, default=None):
139
        super(self.__class__, self).__init__(1, help, parsed_name, default)
140
        self._config_arg = config_arg
141

    
142
    @property
143
    def value(self):
144
        return super(self.__class__, self).value
145

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

    
167

    
168
class FlagArgument(Argument):
169
    def __init__(self, help='', parsed_name=None, default=None):
170
        super(FlagArgument, self).__init__(0, help, parsed_name, default)
171

    
172

    
173
class ValueArgument(Argument):
174
    def __init__(self, help='', parsed_name=None, default=None):
175
        super(ValueArgument, self).__init__(1, help, parsed_name, default)
176

    
177

    
178
class IntArgument(ValueArgument):
179
    @property
180
    def value(self):
181
        return getattr(self, '_value', self.default)
182

    
183
    @value.setter
184
    def value(self, newvalue):
185
        if newvalue == self.default:
186
            self._value = self.default
187
            return
188
        try:
189
            self._value = int(newvalue)
190
        except ValueError:
191
            raise CLISyntaxError('IntArgument Error',
192
                details='Value %s not an int' % newvalue)
193

    
194

    
195
class VersionArgument(FlagArgument):
196
    @property
197
    def value(self):
198
        return super(self.__class__, self).value
199

    
200
    @value.setter
201
    def value(self, newvalue):
202
        self._value = newvalue
203
        self.main()
204

    
205
    def main(self):
206
        if self.value:
207
            import kamaki
208
            print('kamaki %s' % kamaki.__version__)
209

    
210

    
211
class KeyValueArgument(Argument):
212
    def __init__(self, help='', parsed_name=None, default=[]):
213
        super(KeyValueArgument, self).__init__(-1, help, parsed_name, default)
214

    
215
    @property
216
    def value(self):
217
        return super(KeyValueArgument, self).value
218

    
219
    @value.setter
220
    def value(self, keyvalue_pairs):
221
        self._value = {}
222
        for pair in keyvalue_pairs:
223
            key, sep, val = pair.partition('=')
224
            if not sep:
225
                raise CLISyntaxError('Argument syntax error ',
226
                    details='%s is missing a "=" (usage: key1=val1 )\n' % pair)
227
            self._value[key.strip()] = val.strip()
228

    
229

    
230
class ProgressBarArgument(FlagArgument):
231

    
232
    def __init__(self, help='', parsed_name='', default=True):
233
        self.suffix = '%(percent)d%%'
234
        super(ProgressBarArgument, self).__init__(help, parsed_name, default)
235
        try:
236
            IncrementalBar
237
        except NameError:
238
            print('Waring: no progress bar functionality')
239

    
240
    def clone(self):
241
        newarg = ProgressBarArgument(
242
            self.help,
243
            self.parsed_name,
244
            self.default)
245
        newarg._value = self._value
246
        return newarg
247

    
248
    def get_generator(self, message, message_len=25):
249
        if self.value:
250
            return None
251
        self.bar = IncrementalBar()
252
        try:
253
            self.bar.message = message.ljust(message_len)
254
        except NameError:
255
            pass
256
        self.bar.suffix = '%(percent)d%% - %(eta)ds'
257

    
258
        def progress_gen(n):
259
            for i in self.bar.iter(range(int(n))):
260
                yield
261
            yield
262
        return progress_gen
263

    
264
    def finish(self):
265
        if self.value:
266
            return
267
        mybar = getattr(self, 'bar', None)
268
        if mybar:
269
            mybar.finish()
270

    
271

    
272
_arguments = dict(config=_config_arg,
273
    help=Argument(0, 'Show help message', ('-h', '--help')),
274
    debug=FlagArgument('Include debug output', ('-d', '--debug')),
275
    include=FlagArgument('Include protocol headers in the output',
276
        ('-i', '--include')),
277
    silent=FlagArgument('Do not output anything', ('-s', '--silent')),
278
    verbose=FlagArgument('More info at response', ('-v', '--verbose')),
279
    version=VersionArgument('Print current version', ('-V', '--version')),
280
    options=CmdLineConfigArgument(_config_arg,
281
        'Override a config value',
282
        ('-o', '--options'))
283
)
284

    
285

    
286
def parse_known_args(parser, arguments=None):
287
    parsed, unparsed = parser.parse_known_args()
288
    for name, arg in arguments.items():
289
        arg.value = getattr(parsed, name, arg.default)
290
    return parsed, unparsed
291
    # ['"%s"' % s if ' ' in s else s for s in unparsed]
292

    
293

    
294
def init_parser(exe, arguments):
295
    parser = ArgumentParser(add_help=False)
296
    parser.prog = '%s <cmd_group> [<cmd_subbroup> ...] <cmd>' % exe
297
    update_arguments(parser, arguments)
298
    return parser
299

    
300

    
301
def update_arguments(parser, arguments):
302
    for name, argument in arguments.items():
303
        try:
304
            argument.update_parser(parser, name)
305
        except ArgumentError:
306
            pass