Statistics
| Branch: | Tag: | Revision:

root / kamaki / cli / argument.py @ af6de846

History | View | Annotate | Download (9.1 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
        return super(self.__class__, self).value
122

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

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

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

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

    
135

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

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

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

    
166

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

    
171

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

    
176

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

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

    
193

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

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

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

    
209

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

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

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

    
228

    
229
class ProgressBarArgument(FlagArgument):
230

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

    
239
    def get_generator(self, message, message_len=25):
240
        if self.value:
241
            return None
242
        try:
243
            bar = ProgressBar(message.ljust(message_len))
244
        except NameError:
245
            return None
246
        return bar.get_generator()
247

    
248

    
249
try:
250
    class ProgressBar(IncrementalBar):
251
        suffix = '%(percent)d%% - %(eta)ds'
252

    
253
        def get_generator(self):
254
            def progress_gen(n):
255
                for i in self.iter(range(int(n))):
256
                    yield
257
                yield
258
            return progress_gen
259
except NameError:
260
    pass
261

    
262
_arguments = dict(config=_config_arg,
263
    help=Argument(0, 'Show help message', ('-h', '--help')),
264
    debug=FlagArgument('Include debug output', ('-d', '--debug')),
265
    include=FlagArgument('Include protocol headers in the output',
266
        ('-i', '--include')),
267
    silent=FlagArgument('Do not output anything', ('-s', '--silent')),
268
    verbose=FlagArgument('More info at response', ('-v', '--verbose')),
269
    version=VersionArgument('Print current version', ('-V', '--version')),
270
    options=CmdLineConfigArgument(_config_arg,
271
        'Override a config value',
272
        ('-o', '--options'))
273
)
274

    
275

    
276
def parse_known_args(parser, arguments=None):
277
    parsed, unparsed = parser.parse_known_args()
278
    for name, arg in arguments.items():
279
        arg.value = getattr(parsed, name, arg.default)
280
    return parsed, unparsed
281

    
282

    
283
def init_parser(exe, arguments):
284
    parser = ArgumentParser(add_help=False)
285
    parser.prog = '%s <cmd_group> [<cmd_subbroup> ...] <cmd>' % exe
286
    update_arguments(parser, arguments)
287
    return parser
288

    
289

    
290
def update_arguments(parser, arguments):
291
    for name, argument in arguments.items():
292
        try:
293
            argument.update_parser(parser, name)
294
        except ArgumentError:
295
            pass