Statistics
| Branch: | Tag: | Revision:

root / kamaki / cli / argument / __init__.py @ 56d84a4e

History | View | Annotate | Download (18.7 kB)

1 e3f01d64 Stavros Sachtouris
# Copyright 2012-2013 GRNET S.A. All rights reserved.
2 b9331a9f Stavros Sachtouris
#
3 b9331a9f Stavros Sachtouris
# Redistribution and use in source and binary forms, with or
4 b9331a9f Stavros Sachtouris
# without modification, are permitted provided that the following
5 b9331a9f Stavros Sachtouris
# conditions are met:
6 b9331a9f Stavros Sachtouris
#
7 b9331a9f Stavros Sachtouris
#   1. Redistributions of source code must retain the above
8 b9331a9f Stavros Sachtouris
#     copyright notice, this list of conditions and the following
9 b9331a9f Stavros Sachtouris
#     disclaimer.
10 b9331a9f Stavros Sachtouris
#
11 b9331a9f Stavros Sachtouris
#   2. Redistributions in binary form must reproduce the above
12 b9331a9f Stavros Sachtouris
#     copyright notice, this list of conditions and the following
13 b9331a9f Stavros Sachtouris
#     disclaimer in the documentation and/or other materials
14 b9331a9f Stavros Sachtouris
#     provided with the distribution.
15 b9331a9f Stavros Sachtouris
#
16 b9331a9f Stavros Sachtouris
# THIS SOFTWARE IS PROVIDED BY GRNET S.A. ``AS IS'' AND ANY EXPRESS
17 b9331a9f Stavros Sachtouris
# OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
18 b9331a9f Stavros Sachtouris
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
19 b9331a9f Stavros Sachtouris
# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GRNET S.A OR
20 b9331a9f Stavros Sachtouris
# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
21 b9331a9f Stavros Sachtouris
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
22 b9331a9f Stavros Sachtouris
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
23 b9331a9f Stavros Sachtouris
# USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
24 b9331a9f Stavros Sachtouris
# AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
25 b9331a9f Stavros Sachtouris
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
26 b9331a9f Stavros Sachtouris
# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
27 b9331a9f Stavros Sachtouris
# POSSIBILITY OF SUCH DAMAGE.
28 b9331a9f Stavros Sachtouris
#
29 b9331a9f Stavros Sachtouris
# The views and conclusions contained in the software and
30 b9331a9f Stavros Sachtouris
# documentation are those of the authors and should not be
31 b9331a9f Stavros Sachtouris
# interpreted as representing official policies, either expressed
32 b9331a9f Stavros Sachtouris
# or implied, of GRNET S.A.
33 9bdc89da Stavros Sachtouris
34 c270fe96 Stavros Sachtouris
from kamaki.cli.config import Config
35 b696ed2c Stavros Sachtouris
from kamaki.cli.errors import CLISyntaxError, raiseCLIError
36 e0f40c94 Stavros Sachtouris
from kamaki.cli.utils import split_input
37 7637d600 Stavros Sachtouris
38 04d01cd4 Stavros Sachtouris
from datetime import datetime as dtm
39 ea4a21b8 Stavros Sachtouris
from time import mktime
40 56d84a4e Stavros Sachtouris
from sys import stderr
41 db8d1766 Stavros Sachtouris
42 7637d600 Stavros Sachtouris
from logging import getLogger
43 af6de846 Stavros Sachtouris
from argparse import ArgumentParser, ArgumentError
44 2703cceb Stavros Sachtouris
from argparse import RawDescriptionHelpFormatter
45 67083ca0 Stavros Sachtouris
from progress.bar import ShadyBar as KamakiProgressBar
46 fd1f1d96 Stavros Sachtouris
47 7637d600 Stavros Sachtouris
log = getLogger(__name__)
48 db8d1766 Stavros Sachtouris
49 fd5db045 Stavros Sachtouris
50 dfee2caf Stavros Sachtouris
class Argument(object):
51 edb7fc1a Stavros Sachtouris
    """An argument that can be parsed from command line or otherwise.
52 5286e2c3 Stavros Sachtouris
    This is the top-level Argument class. It is suggested to extent this
53 edb7fc1a Stavros Sachtouris
    class into more specific argument types.
54 edb7fc1a Stavros Sachtouris
    """
55 dfee2caf Stavros Sachtouris
56 dfee2caf Stavros Sachtouris
    def __init__(self, arity, help=None, parsed_name=None, default=None):
57 606fe15f Stavros Sachtouris
        self.arity = int(arity)
58 5286e2c3 Stavros Sachtouris
        self.help = '%s' % help or ''
59 dfee2caf Stavros Sachtouris
60 f17d6cb5 Stavros Sachtouris
        assert parsed_name, 'No parsed name for argument %s' % self
61 f17d6cb5 Stavros Sachtouris
        self.parsed_name = list(parsed_name) if isinstance(
62 f17d6cb5 Stavros Sachtouris
            parsed_name, list) or isinstance(parsed_name, tuple) else (
63 f17d6cb5 Stavros Sachtouris
                '%s' % parsed_name).split()
64 f17d6cb5 Stavros Sachtouris
        for name in self.parsed_name:
65 f17d6cb5 Stavros Sachtouris
            assert name.count(' ') == 0, '%s: Invalid parse name "%s"' % (
66 f17d6cb5 Stavros Sachtouris
                self, name)
67 f17d6cb5 Stavros Sachtouris
            msg = '%s: Invalid parse name "%s" should start with a "-"' % (
68 f17d6cb5 Stavros Sachtouris
                    self, name)
69 f17d6cb5 Stavros Sachtouris
            assert name.startswith('-'), msg
70 f17d6cb5 Stavros Sachtouris
71 f52b39db Stavros Sachtouris
        self.default = default if (default or self.arity) else False
72 dfee2caf Stavros Sachtouris
73 fd5db045 Stavros Sachtouris
    @property
74 dfee2caf Stavros Sachtouris
    def value(self):
75 dfee2caf Stavros Sachtouris
        return getattr(self, '_value', self.default)
76 fd5db045 Stavros Sachtouris
77 dfee2caf Stavros Sachtouris
    @value.setter
78 dfee2caf Stavros Sachtouris
    def value(self, newvalue):
79 dfee2caf Stavros Sachtouris
        self._value = newvalue
80 dfee2caf Stavros Sachtouris
81 dfee2caf Stavros Sachtouris
    def update_parser(self, parser, name):
82 edb7fc1a Stavros Sachtouris
        """Update argument parser with self info"""
83 f17d6cb5 Stavros Sachtouris
        action = 'append' if self.arity < 0 else (
84 f17d6cb5 Stavros Sachtouris
            'store' if self.arity else 'store_true')
85 de73876b Stavros Sachtouris
        parser.add_argument(
86 de73876b Stavros Sachtouris
            *self.parsed_name,
87 f17d6cb5 Stavros Sachtouris
            dest=name, action=action, default=self.default, help=self.help)
88 dfee2caf Stavros Sachtouris
89 fd5db045 Stavros Sachtouris
90 9bdc89da Stavros Sachtouris
class ConfigArgument(Argument):
91 439926dd Stavros Sachtouris
    """Manage a kamaki configuration (file)"""
92 edb7fc1a Stavros Sachtouris
93 a7aacf12 Stavros Sachtouris
    def __init__(self, help, parsed_name=('-c', '--config')):
94 a7aacf12 Stavros Sachtouris
        super(ConfigArgument, self).__init__(1, help, parsed_name, None)
95 a7aacf12 Stavros Sachtouris
        self.file_path = None
96 e9a92550 Stavros Sachtouris
97 fd5db045 Stavros Sachtouris
    @property
98 c41a86b2 Stavros Sachtouris
    def value(self):
99 a7aacf12 Stavros Sachtouris
        return super(ConfigArgument, self).value
100 fd5db045 Stavros Sachtouris
101 c41a86b2 Stavros Sachtouris
    @value.setter
102 c41a86b2 Stavros Sachtouris
    def value(self, config_file):
103 e9a92550 Stavros Sachtouris
        if config_file:
104 e9a92550 Stavros Sachtouris
            self._value = Config(config_file)
105 a7aacf12 Stavros Sachtouris
            self.file_path = config_file
106 a7aacf12 Stavros Sachtouris
        elif self.file_path:
107 a7aacf12 Stavros Sachtouris
            self._value = Config(self.file_path)
108 e9a92550 Stavros Sachtouris
        else:
109 e9a92550 Stavros Sachtouris
            self._value = Config()
110 9bdc89da Stavros Sachtouris
111 c41a86b2 Stavros Sachtouris
    def get(self, group, term):
112 439926dd Stavros Sachtouris
        """Get a configuration setting from the Config object"""
113 c41a86b2 Stavros Sachtouris
        return self.value.get(group, term)
114 c41a86b2 Stavros Sachtouris
115 a7aacf12 Stavros Sachtouris
    @property
116 a7aacf12 Stavros Sachtouris
    def groups(self):
117 362adf50 Stavros Sachtouris
        suffix = '_cli'
118 362adf50 Stavros Sachtouris
        slen = len(suffix)
119 362adf50 Stavros Sachtouris
        return [term[:-slen] for term in self.value.keys('global') if (
120 362adf50 Stavros Sachtouris
            term.endswith(suffix))]
121 f724cd35 Stavros Sachtouris
122 a7aacf12 Stavros Sachtouris
    @property
123 a7aacf12 Stavros Sachtouris
    def cli_specs(self):
124 362adf50 Stavros Sachtouris
        suffix = '_cli'
125 362adf50 Stavros Sachtouris
        slen = len(suffix)
126 362adf50 Stavros Sachtouris
        return [(k[:-slen], v) for k, v in self.value.items('global') if (
127 362adf50 Stavros Sachtouris
            k.endswith(suffix))]
128 362adf50 Stavros Sachtouris
129 362adf50 Stavros Sachtouris
    def get_global(self, option):
130 534e7bbb Stavros Sachtouris
        return self.value.get('global', option)
131 362adf50 Stavros Sachtouris
132 144b3551 Stavros Sachtouris
    def get_cloud(self, cloud, option):
133 144b3551 Stavros Sachtouris
        return self.value.get_cloud(cloud, option)
134 017d37ce Stavros Sachtouris
135 f17d6cb5 Stavros Sachtouris
136 a7aacf12 Stavros Sachtouris
_config_arg = ConfigArgument('Path to config file')
137 017d37ce Stavros Sachtouris
138 fd5db045 Stavros Sachtouris
139 1bd4f765 Stavros Sachtouris
class RuntimeConfigArgument(Argument):
140 edb7fc1a Stavros Sachtouris
    """Set a run-time setting option (not persistent)"""
141 edb7fc1a Stavros Sachtouris
142 c41a86b2 Stavros Sachtouris
    def __init__(self, config_arg, help='', parsed_name=None, default=None):
143 c41a86b2 Stavros Sachtouris
        super(self.__class__, self).__init__(1, help, parsed_name, default)
144 c41a86b2 Stavros Sachtouris
        self._config_arg = config_arg
145 c41a86b2 Stavros Sachtouris
146 fd5db045 Stavros Sachtouris
    @property
147 c41a86b2 Stavros Sachtouris
    def value(self):
148 34c480f2 Stavros Sachtouris
        return super(RuntimeConfigArgument, self).value
149 fd5db045 Stavros Sachtouris
150 c41a86b2 Stavros Sachtouris
    @value.setter
151 c41a86b2 Stavros Sachtouris
    def value(self, options):
152 c41a86b2 Stavros Sachtouris
        if options == self.default:
153 c41a86b2 Stavros Sachtouris
            return
154 fd5db045 Stavros Sachtouris
        if not isinstance(options, list):
155 a517ff50 Stavros Sachtouris
            options = ['%s' % options]
156 c41a86b2 Stavros Sachtouris
        for option in options:
157 c41a86b2 Stavros Sachtouris
            keypath, sep, val = option.partition('=')
158 c41a86b2 Stavros Sachtouris
            if not sep:
159 de73876b Stavros Sachtouris
                raiseCLIError(
160 de73876b Stavros Sachtouris
                    CLISyntaxError('Argument Syntax Error '),
161 24ff0a35 Stavros Sachtouris
                    details=[
162 24ff0a35 Stavros Sachtouris
                        '%s is missing a "="',
163 24ff0a35 Stavros Sachtouris
                        ' (usage: -o section.key=val)' % option])
164 c41a86b2 Stavros Sachtouris
            section, sep, key = keypath.partition('.')
165 0a0b9fb6 Stavros Sachtouris
        if not sep:
166 0a0b9fb6 Stavros Sachtouris
            key = section
167 0a0b9fb6 Stavros Sachtouris
            section = 'global'
168 0a0b9fb6 Stavros Sachtouris
        self._config_arg.value.override(
169 0a0b9fb6 Stavros Sachtouris
            section.strip(),
170 fd5db045 Stavros Sachtouris
            key.strip(),
171 fd5db045 Stavros Sachtouris
            val.strip())
172 fd5db045 Stavros Sachtouris
173 c41a86b2 Stavros Sachtouris
174 c41a86b2 Stavros Sachtouris
class FlagArgument(Argument):
175 edb7fc1a Stavros Sachtouris
    """
176 439926dd Stavros Sachtouris
    :value: true if set, false otherwise
177 edb7fc1a Stavros Sachtouris
    """
178 edb7fc1a Stavros Sachtouris
179 40a9c357 Stavros Sachtouris
    def __init__(self, help='', parsed_name=None, default=False):
180 c41a86b2 Stavros Sachtouris
        super(FlagArgument, self).__init__(0, help, parsed_name, default)
181 c41a86b2 Stavros Sachtouris
182 fd5db045 Stavros Sachtouris
183 c41a86b2 Stavros Sachtouris
class ValueArgument(Argument):
184 edb7fc1a Stavros Sachtouris
    """
185 edb7fc1a Stavros Sachtouris
    :value type: string
186 edb7fc1a Stavros Sachtouris
    :value returns: given value or default
187 edb7fc1a Stavros Sachtouris
    """
188 edb7fc1a Stavros Sachtouris
189 c41a86b2 Stavros Sachtouris
    def __init__(self, help='', parsed_name=None, default=None):
190 c41a86b2 Stavros Sachtouris
        super(ValueArgument, self).__init__(1, help, parsed_name, default)
191 c41a86b2 Stavros Sachtouris
192 fd5db045 Stavros Sachtouris
193 0155548b Stavros Sachtouris
class CommaSeparatedListArgument(ValueArgument):
194 0155548b Stavros Sachtouris
    """
195 0155548b Stavros Sachtouris
    :value type: string
196 0155548b Stavros Sachtouris
    :value returns: list of the comma separated values
197 0155548b Stavros Sachtouris
    """
198 0155548b Stavros Sachtouris
199 0155548b Stavros Sachtouris
    @property
200 0155548b Stavros Sachtouris
    def value(self):
201 0155548b Stavros Sachtouris
        return self._value or list()
202 0155548b Stavros Sachtouris
203 0155548b Stavros Sachtouris
    @value.setter
204 0155548b Stavros Sachtouris
    def value(self, newvalue):
205 0155548b Stavros Sachtouris
        self._value = newvalue.split(',') if newvalue else list()
206 0155548b Stavros Sachtouris
207 0155548b Stavros Sachtouris
208 e3d4d442 Stavros Sachtouris
class IntArgument(ValueArgument):
209 edb7fc1a Stavros Sachtouris
210 fd5db045 Stavros Sachtouris
    @property
211 e3d4d442 Stavros Sachtouris
    def value(self):
212 439926dd Stavros Sachtouris
        """integer (type checking)"""
213 e3d4d442 Stavros Sachtouris
        return getattr(self, '_value', self.default)
214 fd5db045 Stavros Sachtouris
215 e3d4d442 Stavros Sachtouris
    @value.setter
216 e3d4d442 Stavros Sachtouris
    def value(self, newvalue):
217 4a25486d Stavros Sachtouris
        if newvalue == self.default:
218 4a25486d Stavros Sachtouris
            self._value = newvalue
219 4a25486d Stavros Sachtouris
            return
220 e3d4d442 Stavros Sachtouris
        try:
221 4a25486d Stavros Sachtouris
            if int(newvalue) == float(newvalue):
222 4a25486d Stavros Sachtouris
                self._value = int(newvalue)
223 4a25486d Stavros Sachtouris
            else:
224 4a25486d Stavros Sachtouris
                raise ValueError('Raise int argument error')
225 e3d4d442 Stavros Sachtouris
        except ValueError:
226 24ff0a35 Stavros Sachtouris
            raiseCLIError(CLISyntaxError(
227 24ff0a35 Stavros Sachtouris
                'IntArgument Error',
228 c8e17a67 Stavros Sachtouris
                details=['Value %s not an int' % newvalue]))
229 fd5db045 Stavros Sachtouris
230 e3d4d442 Stavros Sachtouris
231 04d01cd4 Stavros Sachtouris
class DateArgument(ValueArgument):
232 04d01cd4 Stavros Sachtouris
233 b7ff6e0c Stavros Sachtouris
    DATE_FORMAT = '%a %b %d %H:%M:%S %Y'
234 04d01cd4 Stavros Sachtouris
235 b7ff6e0c Stavros Sachtouris
    INPUT_FORMATS = [DATE_FORMAT, '%d-%m-%Y', '%H:%M:%S %d-%m-%Y']
236 04d01cd4 Stavros Sachtouris
237 04d01cd4 Stavros Sachtouris
    @property
238 ea4a21b8 Stavros Sachtouris
    def timestamp(self):
239 ea4a21b8 Stavros Sachtouris
        v = getattr(self, '_value', self.default)
240 ea4a21b8 Stavros Sachtouris
        return mktime(v.timetuple()) if v else None
241 ea4a21b8 Stavros Sachtouris
242 ea4a21b8 Stavros Sachtouris
    @property
243 ea4a21b8 Stavros Sachtouris
    def formated(self):
244 ea4a21b8 Stavros Sachtouris
        v = getattr(self, '_value', self.default)
245 b7ff6e0c Stavros Sachtouris
        return v.strftime(self.DATE_FORMAT) if v else None
246 ea4a21b8 Stavros Sachtouris
247 ea4a21b8 Stavros Sachtouris
    @property
248 04d01cd4 Stavros Sachtouris
    def value(self):
249 ea4a21b8 Stavros Sachtouris
        return self.timestamp
250 04d01cd4 Stavros Sachtouris
251 04d01cd4 Stavros Sachtouris
    @value.setter
252 04d01cd4 Stavros Sachtouris
    def value(self, newvalue):
253 2af87afc Stavros Sachtouris
        self._value = self.format_date(newvalue) if newvalue else self.default
254 04d01cd4 Stavros Sachtouris
255 04d01cd4 Stavros Sachtouris
    def format_date(self, datestr):
256 04d01cd4 Stavros Sachtouris
        for format in self.INPUT_FORMATS:
257 04d01cd4 Stavros Sachtouris
            try:
258 04d01cd4 Stavros Sachtouris
                t = dtm.strptime(datestr, format)
259 04d01cd4 Stavros Sachtouris
            except ValueError:
260 04d01cd4 Stavros Sachtouris
                continue
261 b7ff6e0c Stavros Sachtouris
            return t  # .strftime(self.DATE_FORMAT)
262 b7ff6e0c Stavros Sachtouris
        raiseCLIError(None, 'Date Argument Error', details=[
263 b7ff6e0c Stavros Sachtouris
            '%s not a valid date' % datestr,
264 b7ff6e0c Stavros Sachtouris
            'Correct formats:\n\t%s' % self.INPUT_FORMATS])
265 04d01cd4 Stavros Sachtouris
266 04d01cd4 Stavros Sachtouris
267 c41a86b2 Stavros Sachtouris
class VersionArgument(FlagArgument):
268 edb7fc1a Stavros Sachtouris
    """A flag argument with that prints current version"""
269 edb7fc1a Stavros Sachtouris
270 fd5db045 Stavros Sachtouris
    @property
271 c41a86b2 Stavros Sachtouris
    def value(self):
272 439926dd Stavros Sachtouris
        """bool"""
273 c41a86b2 Stavros Sachtouris
        return super(self.__class__, self).value
274 fd5db045 Stavros Sachtouris
275 c41a86b2 Stavros Sachtouris
    @value.setter
276 c41a86b2 Stavros Sachtouris
    def value(self, newvalue):
277 c41a86b2 Stavros Sachtouris
        self._value = newvalue
278 f17d6cb5 Stavros Sachtouris
        if newvalue:
279 c41a86b2 Stavros Sachtouris
            import kamaki
280 fd5db045 Stavros Sachtouris
            print('kamaki %s' % kamaki.__version__)
281 fd5db045 Stavros Sachtouris
282 9bdc89da Stavros Sachtouris
283 ca5528f1 Stavros Sachtouris
class RepeatableArgument(Argument):
284 ca5528f1 Stavros Sachtouris
    """A value argument that can be repeated"""
285 ca5528f1 Stavros Sachtouris
286 ca5528f1 Stavros Sachtouris
    def __init__(self, help='', parsed_name=None, default=[]):
287 ca5528f1 Stavros Sachtouris
        super(RepeatableArgument, self).__init__(
288 ca5528f1 Stavros Sachtouris
            -1, help, parsed_name, default)
289 ca5528f1 Stavros Sachtouris
290 ca5528f1 Stavros Sachtouris
291 0a0b9fb6 Stavros Sachtouris
class KeyValueArgument(Argument):
292 ca5528f1 Stavros Sachtouris
    """A Key=Value Argument that can be repeated
293 edb7fc1a Stavros Sachtouris

294 439926dd Stavros Sachtouris
    :syntax: --<arg> key1=value1 --<arg> key2=value2 ...
295 edb7fc1a Stavros Sachtouris
    """
296 edb7fc1a Stavros Sachtouris
297 2d1202ee Stavros Sachtouris
    def __init__(self, help='', parsed_name=None, default=[]):
298 0a0b9fb6 Stavros Sachtouris
        super(KeyValueArgument, self).__init__(-1, help, parsed_name, default)
299 f3e94e06 Stavros Sachtouris
300 fd5db045 Stavros Sachtouris
    @property
301 f3e94e06 Stavros Sachtouris
    def value(self):
302 439926dd Stavros Sachtouris
        """
303 8d427cb9 Stavros Sachtouris
        :returns: (dict) {key1: val1, key2: val2, ...}
304 439926dd Stavros Sachtouris
        """
305 f3e94e06 Stavros Sachtouris
        return super(KeyValueArgument, self).value
306 fd5db045 Stavros Sachtouris
307 fd5db045 Stavros Sachtouris
    @value.setter
308 f3e94e06 Stavros Sachtouris
    def value(self, keyvalue_pairs):
309 8d427cb9 Stavros Sachtouris
        """
310 8d427cb9 Stavros Sachtouris
        :param keyvalue_pairs: (str) ['key1=val1', 'key2=val2', ...]
311 8d427cb9 Stavros Sachtouris
        """
312 2d1202ee Stavros Sachtouris
        self._value = getattr(self, '_value', self.value) or {}
313 f52b39db Stavros Sachtouris
        try:
314 f52b39db Stavros Sachtouris
            for pair in keyvalue_pairs:
315 f52b39db Stavros Sachtouris
                key, sep, val = pair.partition('=')
316 f52b39db Stavros Sachtouris
                assert sep, ' %s misses a "=" (usage: key1=val1 )\n' % (pair)
317 a75393fb Stavros Sachtouris
                self._value[key] = val
318 f52b39db Stavros Sachtouris
        except Exception as e:
319 f52b39db Stavros Sachtouris
            raiseCLIError(e, 'KeyValueArgument Syntax Error')
320 f3e94e06 Stavros Sachtouris
321 fd1f1d96 Stavros Sachtouris
322 fd1f1d96 Stavros Sachtouris
class ProgressBarArgument(FlagArgument):
323 439926dd Stavros Sachtouris
    """Manage a progress bar"""
324 fd1f1d96 Stavros Sachtouris
325 fd1f1d96 Stavros Sachtouris
    def __init__(self, help='', parsed_name='', default=True):
326 fd1f1d96 Stavros Sachtouris
        self.suffix = '%(percent)d%%'
327 fd1f1d96 Stavros Sachtouris
        super(ProgressBarArgument, self).__init__(help, parsed_name, default)
328 fd1f1d96 Stavros Sachtouris
329 852a22e7 Stavros Sachtouris
    def clone(self):
330 439926dd Stavros Sachtouris
        """Get a modifiable copy of this bar"""
331 852a22e7 Stavros Sachtouris
        newarg = ProgressBarArgument(
332 c0fbf04c Stavros Sachtouris
            self.help, self.parsed_name, self.default)
333 852a22e7 Stavros Sachtouris
        newarg._value = self._value
334 852a22e7 Stavros Sachtouris
        return newarg
335 852a22e7 Stavros Sachtouris
336 8547cd19 Stavros Sachtouris
    def get_generator(
337 8547cd19 Stavros Sachtouris
            self, message, message_len=25, countdown=False, timeout=100):
338 439926dd Stavros Sachtouris
        """Get a generator to handle progress of the bar (gen.next())"""
339 fd1f1d96 Stavros Sachtouris
        if self.value:
340 fd1f1d96 Stavros Sachtouris
            return None
341 fd1f1d96 Stavros Sachtouris
        try:
342 1d329d27 Stavros Sachtouris
            self.bar = KamakiProgressBar()
343 fd1f1d96 Stavros Sachtouris
        except NameError:
344 329753ae Stavros Sachtouris
            self.value = None
345 329753ae Stavros Sachtouris
            return self.value
346 8547cd19 Stavros Sachtouris
        if countdown:
347 e9c73313 Stavros Sachtouris
            bar_phases = list(self.bar.phases)
348 8547cd19 Stavros Sachtouris
            self.bar.empty_fill, bar_phases[0] = bar_phases[-1], ''
349 8547cd19 Stavros Sachtouris
            bar_phases.reverse()
350 e9c73313 Stavros Sachtouris
            self.bar.phases = bar_phases
351 8547cd19 Stavros Sachtouris
            self.bar.bar_prefix = ' '
352 e9c73313 Stavros Sachtouris
            self.bar.bar_suffix = ' '
353 8547cd19 Stavros Sachtouris
            self.bar.max = timeout or 100
354 8547cd19 Stavros Sachtouris
            self.bar.suffix = '%(remaining)ds to timeout'
355 e9c73313 Stavros Sachtouris
        else:
356 e9c73313 Stavros Sachtouris
            self.bar.suffix = '%(percent)d%% - %(eta)ds'
357 8547cd19 Stavros Sachtouris
        self.bar.eta = timeout or 100
358 329753ae Stavros Sachtouris
        self.bar.message = message.ljust(message_len)
359 a10f5561 Stavros Sachtouris
        self.bar.start()
360 fd1f1d96 Stavros Sachtouris
361 852a22e7 Stavros Sachtouris
        def progress_gen(n):
362 852a22e7 Stavros Sachtouris
            for i in self.bar.iter(range(int(n))):
363 852a22e7 Stavros Sachtouris
                yield
364 852a22e7 Stavros Sachtouris
            yield
365 852a22e7 Stavros Sachtouris
        return progress_gen
366 fd1f1d96 Stavros Sachtouris
367 852a22e7 Stavros Sachtouris
    def finish(self):
368 439926dd Stavros Sachtouris
        """Stop progress bar, return terminal cursor to user"""
369 852a22e7 Stavros Sachtouris
        if self.value:
370 852a22e7 Stavros Sachtouris
            return
371 852a22e7 Stavros Sachtouris
        mybar = getattr(self, 'bar', None)
372 852a22e7 Stavros Sachtouris
        if mybar:
373 852a22e7 Stavros Sachtouris
            mybar.finish()
374 fd1f1d96 Stavros Sachtouris
375 fd1f1d96 Stavros Sachtouris
376 de73876b Stavros Sachtouris
_arguments = dict(
377 de73876b Stavros Sachtouris
    config=_config_arg,
378 144b3551 Stavros Sachtouris
    cloud=ValueArgument('Chose a cloud to connect to', ('--cloud')),
379 fd5db045 Stavros Sachtouris
    help=Argument(0, 'Show help message', ('-h', '--help')),
380 fd5db045 Stavros Sachtouris
    debug=FlagArgument('Include debug output', ('-d', '--debug')),
381 f6822a26 Stavros Sachtouris
    #include=FlagArgument(
382 f6822a26 Stavros Sachtouris
    #    'Include raw connection data in the output', ('-i', '--include')),
383 fd5db045 Stavros Sachtouris
    silent=FlagArgument('Do not output anything', ('-s', '--silent')),
384 fd5db045 Stavros Sachtouris
    verbose=FlagArgument('More info at response', ('-v', '--verbose')),
385 fd5db045 Stavros Sachtouris
    version=VersionArgument('Print current version', ('-V', '--version')),
386 1bd4f765 Stavros Sachtouris
    options=RuntimeConfigArgument(
387 cb4a5d9c Stavros Sachtouris
        _config_arg, 'Override a config value', ('-o', '--options'))
388 9bdc89da Stavros Sachtouris
)
389 cb4a5d9c Stavros Sachtouris
390 cb4a5d9c Stavros Sachtouris
391 cb4a5d9c Stavros Sachtouris
#  Initial command line interface arguments
392 439926dd Stavros Sachtouris
393 439926dd Stavros Sachtouris
394 7c2247a0 Stavros Sachtouris
class ArgumentParseManager(object):
395 e0da0f90 Stavros Sachtouris
    """Manage (initialize and update) an ArgumentParser object"""
396 e0da0f90 Stavros Sachtouris
397 56d84a4e Stavros Sachtouris
    def __init__(self, exe, arguments=None, required=None):
398 e0da0f90 Stavros Sachtouris
        """
399 e0da0f90 Stavros Sachtouris
        :param exe: (str) the basic command (e.g. 'kamaki')
400 e0da0f90 Stavros Sachtouris

401 e0da0f90 Stavros Sachtouris
        :param arguments: (dict) if given, overrides the global _argument as
402 e0da0f90 Stavros Sachtouris
            the parsers arguments specification
403 56d84a4e Stavros Sachtouris
        :param required: (list or tuple) an iterable of argument keys, denoting
404 56d84a4e Stavros Sachtouris
            which arguments are required. A tuple denoted an AND relation,
405 56d84a4e Stavros Sachtouris
            while a list denotes an OR relation e.g., ['a', 'b'] means that
406 56d84a4e Stavros Sachtouris
            either 'a' or 'b' is required, while ('a', 'b') means that both 'a'
407 56d84a4e Stavros Sachtouris
            and 'b' ar required.
408 56d84a4e Stavros Sachtouris
            Nesting is allowed e.g., ['a', ('b', 'c'), ['d', 'e']] means that
409 56d84a4e Stavros Sachtouris
            this command required either 'a', or both 'b' and 'c', or one of
410 56d84a4e Stavros Sachtouris
            'd', 'e'.
411 56d84a4e Stavros Sachtouris
            Repeated arguments are also allowed e.g., [('a', 'b'), ('a', 'c'),
412 56d84a4e Stavros Sachtouris
            ['b', 'c']] means that the command required either 'a' and 'b' or
413 56d84a4e Stavros Sachtouris
            'a' and 'c' or at least one of 'b', 'c' and could be written as
414 56d84a4e Stavros Sachtouris
            [('a', ['b', 'c']), ['b', 'c']]
415 e0da0f90 Stavros Sachtouris
        """
416 de73876b Stavros Sachtouris
        self.parser = ArgumentParser(
417 320aac17 Stavros Sachtouris
            add_help=False, formatter_class=RawDescriptionHelpFormatter)
418 56d84a4e Stavros Sachtouris
        self._exe = exe
419 b3dd8f4b Stavros Sachtouris
        self.syntax = '%s <cmd_group> [<cmd_subbroup> ...] <cmd>' % exe
420 56d84a4e Stavros Sachtouris
        self.required = required
421 e0da0f90 Stavros Sachtouris
        if arguments:
422 e0da0f90 Stavros Sachtouris
            self.arguments = arguments
423 e0da0f90 Stavros Sachtouris
        else:
424 e0da0f90 Stavros Sachtouris
            global _arguments
425 e0da0f90 Stavros Sachtouris
            self.arguments = _arguments
426 631b7c35 Stavros Sachtouris
        self._parser_modified, self._parsed, self._unparsed = False, None, None
427 7c2247a0 Stavros Sachtouris
        self.parse()
428 e0da0f90 Stavros Sachtouris
429 56d84a4e Stavros Sachtouris
    @staticmethod
430 56d84a4e Stavros Sachtouris
    def required2list(required):
431 56d84a4e Stavros Sachtouris
        if isinstance(required, list) or isinstance(required, tuple):
432 56d84a4e Stavros Sachtouris
            terms = []
433 56d84a4e Stavros Sachtouris
            for r in required:
434 56d84a4e Stavros Sachtouris
                terms.append(ArgumentParseManager.required2list(r))
435 56d84a4e Stavros Sachtouris
            return list(set(terms).union())
436 56d84a4e Stavros Sachtouris
        return required
437 56d84a4e Stavros Sachtouris
438 56d84a4e Stavros Sachtouris
    @staticmethod
439 56d84a4e Stavros Sachtouris
    def required2str(required, arguments, tab=''):
440 56d84a4e Stavros Sachtouris
        if isinstance(required, list):
441 56d84a4e Stavros Sachtouris
            return ' %sat least one:\n%s' % (tab, ''.join(
442 56d84a4e Stavros Sachtouris
                [ArgumentParseManager.required2str(
443 56d84a4e Stavros Sachtouris
                    r, arguments, tab + '  ') for r in required]))
444 56d84a4e Stavros Sachtouris
        elif isinstance(required, tuple):
445 56d84a4e Stavros Sachtouris
            return ' %sall:\n%s' % (tab, ''.join(
446 56d84a4e Stavros Sachtouris
                [ArgumentParseManager.required2str(
447 56d84a4e Stavros Sachtouris
                    r, arguments, tab + '  ') for r in required]))
448 56d84a4e Stavros Sachtouris
        else:
449 56d84a4e Stavros Sachtouris
            lt_pn, lt_all, arg = 23, 80, arguments[required]
450 56d84a4e Stavros Sachtouris
            tab2 = ' ' * lt_pn
451 56d84a4e Stavros Sachtouris
            ret = '%s%s' % (tab, ', '.join(arg.parsed_name))
452 56d84a4e Stavros Sachtouris
            if arg.arity != 0:
453 56d84a4e Stavros Sachtouris
                ret += ' %s' % required.upper()
454 56d84a4e Stavros Sachtouris
            ret = ('{:<%s}' % lt_pn).format(ret)
455 56d84a4e Stavros Sachtouris
            prefix = ('\n%s' % tab2) if len(ret) < lt_pn else ' '
456 56d84a4e Stavros Sachtouris
            step, cur = (len(arg.help) / (lt_all - lt_pn)) or len(arg.help), 0
457 56d84a4e Stavros Sachtouris
            while arg.help[cur:]:
458 56d84a4e Stavros Sachtouris
                next = cur + step
459 56d84a4e Stavros Sachtouris
                ret += prefix
460 56d84a4e Stavros Sachtouris
                ret += ('{:<%s}' % (lt_all - lt_pn)).format(arg.help[cur:next])
461 56d84a4e Stavros Sachtouris
                cur, finish = next, '\n%s' % tab2
462 56d84a4e Stavros Sachtouris
            return ret + '\n'
463 56d84a4e Stavros Sachtouris
464 56d84a4e Stavros Sachtouris
    def print_help(self, out=stderr):
465 56d84a4e Stavros Sachtouris
        if self.required:
466 56d84a4e Stavros Sachtouris
            tmp_args = dict(self.arguments)
467 56d84a4e Stavros Sachtouris
            for term in self.required2list(self.required):
468 56d84a4e Stavros Sachtouris
                tmp_args.pop(term)
469 56d84a4e Stavros Sachtouris
            tmp_parser = ArgumentParseManager(self._exe, tmp_args)
470 56d84a4e Stavros Sachtouris
            tmp_parser.syntax = self.syntax
471 56d84a4e Stavros Sachtouris
            tmp_parser.parser.description = '%s\n\nrequired arguments:\n%s' % (
472 56d84a4e Stavros Sachtouris
                self.parser.description,
473 56d84a4e Stavros Sachtouris
                self.required2str(self.required, self.arguments))
474 56d84a4e Stavros Sachtouris
            tmp_parser.update_parser()
475 56d84a4e Stavros Sachtouris
            tmp_parser.parser.print_help()
476 56d84a4e Stavros Sachtouris
        else:
477 56d84a4e Stavros Sachtouris
            self.parser.print_help()
478 56d84a4e Stavros Sachtouris
479 e0da0f90 Stavros Sachtouris
    @property
480 e0da0f90 Stavros Sachtouris
    def syntax(self):
481 b3dd8f4b Stavros Sachtouris
        """The command syntax (useful for help messages, descriptions, etc)"""
482 e0da0f90 Stavros Sachtouris
        return self.parser.prog
483 e0da0f90 Stavros Sachtouris
484 e0da0f90 Stavros Sachtouris
    @syntax.setter
485 e0da0f90 Stavros Sachtouris
    def syntax(self, new_syntax):
486 e0da0f90 Stavros Sachtouris
        self.parser.prog = new_syntax
487 e0da0f90 Stavros Sachtouris
488 b3dd8f4b Stavros Sachtouris
    @property
489 b3dd8f4b Stavros Sachtouris
    def arguments(self):
490 631b7c35 Stavros Sachtouris
        """:returns: (dict) arguments the parser should be aware of"""
491 b3dd8f4b Stavros Sachtouris
        return self._arguments
492 b3dd8f4b Stavros Sachtouris
493 b3dd8f4b Stavros Sachtouris
    @arguments.setter
494 b3dd8f4b Stavros Sachtouris
    def arguments(self, new_arguments):
495 631b7c35 Stavros Sachtouris
        assert isinstance(new_arguments, dict), 'Arguments must be in a dict'
496 b3dd8f4b Stavros Sachtouris
        self._arguments = new_arguments
497 b3dd8f4b Stavros Sachtouris
        self.update_parser()
498 b3dd8f4b Stavros Sachtouris
499 7c2247a0 Stavros Sachtouris
    @property
500 b3dd8f4b Stavros Sachtouris
    def parsed(self):
501 7c2247a0 Stavros Sachtouris
        """(Namespace) parser-matched terms"""
502 b3dd8f4b Stavros Sachtouris
        if self._parser_modified:
503 b3dd8f4b Stavros Sachtouris
            self.parse()
504 b3dd8f4b Stavros Sachtouris
        return self._parsed
505 b3dd8f4b Stavros Sachtouris
506 b3dd8f4b Stavros Sachtouris
    @property
507 b3dd8f4b Stavros Sachtouris
    def unparsed(self):
508 b3dd8f4b Stavros Sachtouris
        """(list) parser-unmatched terms"""
509 b3dd8f4b Stavros Sachtouris
        if self._parser_modified:
510 b3dd8f4b Stavros Sachtouris
            self.parse()
511 b3dd8f4b Stavros Sachtouris
        return self._unparsed
512 b3dd8f4b Stavros Sachtouris
513 e0da0f90 Stavros Sachtouris
    def update_parser(self, arguments=None):
514 e0da0f90 Stavros Sachtouris
        """Load argument specifications to parser
515 e0da0f90 Stavros Sachtouris

516 e0da0f90 Stavros Sachtouris
        :param arguments: if not given, update self.arguments instead
517 e0da0f90 Stavros Sachtouris
        """
518 631b7c35 Stavros Sachtouris
        arguments = arguments or self._arguments
519 e0da0f90 Stavros Sachtouris
520 e0da0f90 Stavros Sachtouris
        for name, arg in arguments.items():
521 e0da0f90 Stavros Sachtouris
            try:
522 e0da0f90 Stavros Sachtouris
                arg.update_parser(self.parser, name)
523 b3dd8f4b Stavros Sachtouris
                self._parser_modified = True
524 e0da0f90 Stavros Sachtouris
            except ArgumentError:
525 e0da0f90 Stavros Sachtouris
                pass
526 e0da0f90 Stavros Sachtouris
527 7c2247a0 Stavros Sachtouris
    def update_arguments(self, new_arguments):
528 7c2247a0 Stavros Sachtouris
        """Add to / update existing arguments
529 7c2247a0 Stavros Sachtouris

530 7c2247a0 Stavros Sachtouris
        :param new_arguments: (dict)
531 7c2247a0 Stavros Sachtouris
        """
532 7c2247a0 Stavros Sachtouris
        if new_arguments:
533 56d84a4e Stavros Sachtouris
            assert isinstance(new_arguments, dict), 'Arguments not in dict !!!'
534 7c2247a0 Stavros Sachtouris
            self._arguments.update(new_arguments)
535 7c2247a0 Stavros Sachtouris
            self.update_parser()
536 7c2247a0 Stavros Sachtouris
537 120126f1 Stavros Sachtouris
    def parse(self, new_args=None):
538 320aac17 Stavros Sachtouris
        """Parse user input"""
539 c8e17a67 Stavros Sachtouris
        try:
540 320aac17 Stavros Sachtouris
            pkargs = (new_args,) if new_args else ()
541 320aac17 Stavros Sachtouris
            self._parsed, unparsed = self.parser.parse_known_args(*pkargs)
542 56d84a4e Stavros Sachtouris
            pdict = vars(self._parsed)
543 56d84a4e Stavros Sachtouris
            diff = set(self.required or []).difference(
544 56d84a4e Stavros Sachtouris
                [k for k in pdict if pdict[k] != None])
545 56d84a4e Stavros Sachtouris
            if diff:
546 56d84a4e Stavros Sachtouris
                self.print_help()
547 56d84a4e Stavros Sachtouris
                miss = ['/'.join(self.arguments[k].parsed_name) for k in diff]
548 56d84a4e Stavros Sachtouris
                raise CLISyntaxError(
549 56d84a4e Stavros Sachtouris
                    'Missing required arguments (%s)' % ', '.join(miss))
550 c8e17a67 Stavros Sachtouris
        except SystemExit:
551 c8e17a67 Stavros Sachtouris
            raiseCLIError(CLISyntaxError('Argument Syntax Error'))
552 b3dd8f4b Stavros Sachtouris
        for name, arg in self.arguments.items():
553 b3dd8f4b Stavros Sachtouris
            arg.value = getattr(self._parsed, name, arg.default)
554 b3dd8f4b Stavros Sachtouris
        self._unparsed = []
555 b3dd8f4b Stavros Sachtouris
        for term in unparsed:
556 b3dd8f4b Stavros Sachtouris
            self._unparsed += split_input(' \'%s\' ' % term)
557 b3dd8f4b Stavros Sachtouris
        self._parser_modified = False