Statistics
| Branch: | Tag: | Revision:

root / kamaki / cli / argument.py @ e3d4d442

History | View | Annotate | Download (7.2 kB)

1
#A. One-command CLI
2
#    1. Get a command string         DONE
3
#    2. Parse out some Arguments     DONE
4
#        a. We need an Argument "library" for each command-level     DONE
5
#        b. Handle arg errors     
6
#    3. Retrieve and validate command_sequence
7
#        a. For faster responses, first command can be chosen from
8
#            a prefixed list of names, loaded from the config file
9
#        b. Normally, each 1st level command has a file to read
10
#            command specs from. Load command_specs in this file
11
#            i. A dict with command specs is created
12
#                e.g. {'store':{'list':{'all', None}, 'info'}, 'server':{'list', 'info'}}
13
#                but in this case there will be only 'store', or 'server', etc.
14
#        c. Now, loop over the other parsed terms and check them against the commands
15
#            i. That will produce a path of the form ['store', 'list' 'all']
16
#        d. Catch syntax errors
17
#    4. Instaciate object to exec
18
#        a. For path ['store', 'list', 'all'] instatiate store_list_all()
19
#    5. Parse out some more Arguments 
20
#        a. Each command path has an "Argument library" to check your args against
21
#    6. Call object.main() and catch ClientErrors
22
#        a. Now, there are some command-level syntax errors that we should catch
23
#            as syntax errors? Maybe! Why not?
24

    
25
#Shell
26
#    1. Load ALL available command specs in advance
27
#    2. Iimport cmd (and run it ?)
28
#    3. There is probably a way to tell cmd of the command paths you support.
29
#    4. If cmd does not support it, for the sellected path call parse out stuff
30
#        as in One-command
31
#    5. Instatiate, parse_out and run object like in One-command
32
#    6. Run object.main() . Again, catch ClientErrors and, probably, syntax errors
33
import gevent.monkey
34
#Monkey-patch everything for gevent early on
35
gevent.monkey.patch_all()
36

    
37
from sys import exit
38

    
39
from .config import Config
40
from .errors import CLISyntaxError
41

    
42
class Argument(object):
43
    """An argument that can be parsed from command line or otherwise"""
44

    
45
    def __init__(self, arity, help=None, parsed_name=None, default=None):
46
        self.arity = int(arity)
47

    
48
        if help is not None:
49
            self.help = help
50
        if parsed_name is not None:
51
            self.parsed_name = parsed_name
52
        if default is not None:
53
            self.default = default
54

    
55
    @property 
56
    def parsed_name(self):
57
        return getattr(self, '_parsed_name', None)
58
    @parsed_name.setter
59
    def parsed_name(self, newname):
60
        self._parsed_name = getattr(self, '_parsed_name', [])
61
        if isinstance(newname, list) or isinstance(newname, tuple):
62
            self._parsed_name += list(newname)
63
        else:
64
            self._parsed_name.append(unicode(newname))
65

    
66
    @property 
67
    def help(self):
68
        return getattr(self, '_help', None)
69
    @help.setter
70
    def help(self, newhelp):
71
        self._help = unicode(newhelp)
72

    
73
    @property 
74
    def arity(self):
75
        return getattr(self, '_arity', None)
76
    @arity.setter
77
    def arity(self, newarity):
78
        newarity = int(newarity)
79
        assert newarity >= 0
80
        self._arity = newarity
81

    
82
    @property 
83
    def default(self):
84
        if not hasattr(self, '_default'):
85
            self._default = False if self.arity == 0 else None
86
        return self._default
87
    @default.setter
88
    def default(self, newdefault):
89
        self._default = newdefault
90

    
91
    @property 
92
    def value(self):
93
        return getattr(self, '_value', self.default)
94
    @value.setter
95
    def value(self, newvalue):
96
        self._value = newvalue
97

    
98
    def update_parser(self, parser, name):
99
        """Update an argument parser with this argument info"""
100
        action = 'store_true' if self.arity==0 else 'store'
101
        parser.add_argument(*self.parsed_name, dest=name, action=action,
102
            default=self.default, help=self.help)
103

    
104
    def main(self):
105
        """Overide this method to give functionality to ur args"""
106
        raise NotImplementedError
107

    
108
class ConfigArgument(Argument):
109
    @property 
110
    def value(self):
111
        return super(self.__class__, self).value
112
    @value.setter
113
    def value(self, config_file):
114
        self._value = Config(config_file) if config_file is not None else Config()
115

    
116
    def get(self, group, term):
117
        return self.value.get(group, term)
118

    
119
    def get_groups(self):
120
        return self.value.apis()
121

    
122
_config_arg = ConfigArgument(1, 'Path to configuration file', '--config')
123

    
124
class CmdLineConfigArgument(Argument):
125
    def __init__(self, config_arg, help='', parsed_name=None, default=None):
126
        super(self.__class__, self).__init__(1, help, parsed_name, default)
127
        self._config_arg = config_arg
128

    
129
    @property 
130
    def value(self):
131
        return super(self.__class__, self).value
132
    @value.setter
133
    def value(self, options):
134
        if options == self.default:
135
            return
136
        options = [unicode(options)] if not isinstance(options, list) else options
137
        for option in options:
138
            keypath, sep, val = option.partition('=')
139
            if not sep:
140
                raise CLISyntaxError(details='Missing = between key and value: -o section.key=val')
141
            section, sep, key = keypath.partition('.')
142
            if not sep:
143
                raise CLISyntaxError(details='Missing . between section and key: -o section.key=val')
144
        self._config_arg.value.override(section.strip(), key.strip(), val.strip())
145

    
146
class FlagArgument(Argument):
147
    def __init__(self, help='', parsed_name=None, default=None):
148
        super(FlagArgument, self).__init__(0, help, parsed_name, default)
149

    
150
class ValueArgument(Argument):
151
    def __init__(self, help='', parsed_name=None, default=None):
152
        super(ValueArgument, self).__init__(1, help, parsed_name, default)
153

    
154
class IntArgument(ValueArgument):
155
    @property 
156
    def value(self):
157
        return getattr(self, '_value', self.default)
158
    @value.setter
159
    def value(self, newvalue):
160
        if newvalue == self.default:
161
            self._value = self.default
162
            return
163
        try:
164
            self._value = int(newvalue)
165
        except ValueError:
166
            raise CLISyntaxError('IntArgument Error', details='Value %s not an int'%newvalue)
167

    
168
class VersionArgument(FlagArgument):
169
    @property 
170
    def value(self):
171
        return super(self.__class__, self).value
172
    @value.setter
173
    def value(self, newvalue):
174
        self._value = newvalue
175
        self.main()
176

    
177
    def main(self):
178
        if self.value:
179
            import kamaki
180
            print('kamaki %s'%kamaki.__version__)
181

    
182
_arguments = dict(config = _config_arg, help = Argument(0, 'Show help message', ('-h', '--help')),
183
    debug = FlagArgument('Include debug output', ('-d', '--debug')),
184
    include = FlagArgument('Include protocol headers in the output', ('-i', '--include')),
185
    silent = FlagArgument('Do not output anything', ('-s', '--silent')),
186
    verbose = FlagArgument('More info at response', ('-v', '--verbose')),
187
    version = VersionArgument('Print current version', ('-V', '--version')),
188
    options = CmdLineConfigArgument(_config_arg, 'Override a config value', ('-o', '--options'))
189
)
190

    
191
def parse_known_args(parser):
192
    parsed, unparsed = parser.parse_known_args()
193
    for name, arg in _arguments.items():
194
        lala = getattr(parsed, name, arg.default)
195
        arg.value = lala
196
    return parsed, unparsed