##// END OF EJS Templates
add subcommand support
MinRK -
Show More
@@ -1,293 +1,360 b''
1 1 # encoding: utf-8
2 2 """
3 3 A base class for a configurable application.
4 4
5 5 Authors:
6 6
7 7 * Brian Granger
8 8 """
9 9
10 10 #-----------------------------------------------------------------------------
11 11 # Copyright (C) 2008-2011 The IPython Development Team
12 12 #
13 13 # Distributed under the terms of the BSD License. The full license is in
14 14 # the file COPYING, distributed as part of this software.
15 15 #-----------------------------------------------------------------------------
16 16
17 17 #-----------------------------------------------------------------------------
18 18 # Imports
19 19 #-----------------------------------------------------------------------------
20 20
21 21 from copy import deepcopy
22 22 import logging
23 23 import sys
24 24
25 25 from IPython.config.configurable import SingletonConfigurable
26 26 from IPython.config.loader import (
27 27 KeyValueConfigLoader, PyFileConfigLoader, Config
28 28 )
29 29
30 30 from IPython.utils.traitlets import (
31 Unicode, List, Int, Enum, Dict
31 Unicode, List, Int, Enum, Dict, Instance
32 32 )
33 from IPython.utils.importstring import import_item
33 34 from IPython.utils.text import indent
34 35
35 36 #-----------------------------------------------------------------------------
36 37 # Descriptions for the various sections
37 38 #-----------------------------------------------------------------------------
38 39
39 40 flag_description = """
40 41 Flags are command-line arguments passed as '--<flag>'.
41 42 These take no parameters, unlike regular key-value arguments.
42 43 They are typically used for setting boolean flags, or enabling
43 44 modes that involve setting multiple options together.
44 45 """.strip() # trim newlines of front and back
45 46
46 47 alias_description = """
47 48 These are commonly set parameters, given abbreviated aliases for convenience.
48 49 They are set in the same `name=value` way as class parameters, where
49 50 <name> is replaced by the real parameter for which it is an alias.
50 51 """.strip() # trim newlines of front and back
51 52
52 53 keyvalue_description = """
53 54 Parameters are set from command-line arguments of the form:
54 55 `Class.trait=value`. Parameters will *never* be prefixed with '-'.
55 56 This line is evaluated in Python, so simple expressions are allowed, e.g.
56 57 `C.a='range(3)'` For setting C.a=[0,1,2]
57 58 """.strip() # trim newlines of front and back
58 59
59 60 #-----------------------------------------------------------------------------
60 61 # Application class
61 62 #-----------------------------------------------------------------------------
62 63
63 64
64 65 class ApplicationError(Exception):
65 66 pass
66 67
67 68
68 69 class Application(SingletonConfigurable):
69 70 """A singleton application with full configuration support."""
70 71
71 72 # The name of the application, will usually match the name of the command
72 73 # line application
73 74 name = Unicode(u'application')
74 75
75 76 # The description of the application that is printed at the beginning
76 77 # of the help.
77 78 description = Unicode(u'This is an application.')
78 79 # default section descriptions
79 80 flag_description = Unicode(flag_description)
80 81 alias_description = Unicode(alias_description)
81 82 keyvalue_description = Unicode(keyvalue_description)
82 83
83 84
84 85 # A sequence of Configurable subclasses whose config=True attributes will
85 86 # be exposed at the command line.
86 87 classes = List([])
87 88
88 89 # The version string of this application.
89 90 version = Unicode(u'0.0')
90 91
91 92 # The log level for the application
92 93 log_level = Enum((0,10,20,30,40,50), default_value=logging.WARN,
93 94 config=True,
94 95 help="Set the log level.")
95 96
96 97 # the alias map for configurables
97 98 aliases = Dict(dict(log_level='Application.log_level'))
98 99
99 100 # flags for loading Configurables or store_const style flags
100 101 # flags are loaded from this dict by '--key' flags
101 102 # this must be a dict of two-tuples, the first element being the Config/dict
102 103 # and the second being the help string for the flag
103 104 flags = Dict()
104 105
106 # subcommands for launching other applications
107 # if this is not empty, this will be a parent Application
108 # this must be a dict of two-tuples, the first element being the application class/import string
109 # and the second being the help string for the subcommand
110 subcommands = Dict()
111 # parse_command_line will initialize a subapp, if requested
112 subapp = Instance('IPython.config.application.Application', allow_none=True)
113
105 114
106 115 def __init__(self, **kwargs):
107 116 SingletonConfigurable.__init__(self, **kwargs)
108 117 # Add my class to self.classes so my attributes appear in command line
109 118 # options.
110 119 self.classes.insert(0, self.__class__)
111 120
112 121 # ensure self.flags dict is valid
113 122 for key,value in self.flags.iteritems():
114 123 assert len(value) == 2, "Bad flag: %r:%s"%(key,value)
115 124 assert isinstance(value[0], (dict, Config)), "Bad flag: %r:%s"%(key,value)
116 125 assert isinstance(value[1], basestring), "Bad flag: %r:%s"%(key,value)
117 126 self.init_logging()
118 127
119 128 def _config_changed(self, name, old, new):
120 129 SingletonConfigurable._config_changed(self, name, old, new)
121 130 self.log.debug('Config changed:')
122 131 self.log.debug(repr(new))
123 132
124 133 def init_logging(self):
125 134 """Start logging for this application.
126 135
127 136 The default is to log to stdout using a StreaHandler. The log level
128 137 starts at loggin.WARN, but this can be adjusted by setting the
129 138 ``log_level`` attribute.
130 139 """
131 140 self.log = logging.getLogger(self.__class__.__name__)
132 141 self.log.setLevel(self.log_level)
133 142 self._log_handler = logging.StreamHandler()
134 143 self._log_formatter = logging.Formatter("[%(name)s] %(message)s")
135 144 self._log_handler.setFormatter(self._log_formatter)
136 145 self.log.addHandler(self._log_handler)
137
146
147 def initialize(self, argv=None):
148 """Do the basic steps to configure me.
149
150 Override in subclasses.
151 """
152 self.parse_command_line(argv)
153
154
155 def start(self):
156 """Start the app mainloop.
157
158 Override in subclasses.
159 """
160 if self.subapp is not None:
161 return self.subapp.start()
162
138 163 def _log_level_changed(self, name, old, new):
139 164 """Adjust the log level when log_level is set."""
140 165 self.log.setLevel(new)
141 166
142 167 def print_alias_help(self):
143 168 """print the alias part of the help"""
144 169 if not self.aliases:
145 170 return
146 171
147 172 lines = ['Aliases']
148 lines.append('_'*len(lines[0]))
173 lines.append('-'*len(lines[0]))
149 174 lines.append(self.alias_description)
150 175 lines.append('')
151 176
152 177 classdict = {}
153 178 for c in self.classes:
154 179 classdict[c.__name__] = c
155 180
156 181 for alias, longname in self.aliases.iteritems():
157 182 classname, traitname = longname.split('.',1)
158 183 cls = classdict[classname]
159 184
160 185 trait = cls.class_traits(config=True)[traitname]
161 186 help = cls.class_get_trait_help(trait)
162 187 help = help.replace(longname, "%s (%s)"%(alias, longname), 1)
163 188 lines.append(help)
164 # header = "%s (%s) : %s"%(alias, longname, trait.__class__.__name__)
165 # lines.append(header)
166 # help = cls.class_get_trait_help(trait)
167 # if help:
168 # lines.append(indent(help, flatten=True))
169 189 lines.append('')
170 190 print '\n'.join(lines)
171 191
172 192 def print_flag_help(self):
173 193 """print the flag part of the help"""
174 194 if not self.flags:
175 195 return
176 196
177 197 lines = ['Flags']
178 lines.append('_'*len(lines[0]))
198 lines.append('-'*len(lines[0]))
179 199 lines.append(self.flag_description)
180 200 lines.append('')
181 201
182 202 for m, (cfg,help) in self.flags.iteritems():
183 203 lines.append('--'+m)
184 204 lines.append(indent(help, flatten=True))
185 205 lines.append('')
186 206 print '\n'.join(lines)
187 207
208 def print_subcommands(self):
209 """print the subcommand part of the help"""
210 if not self.subcommands:
211 return
212
213 lines = ["Subcommands"]
214 lines.append('-'*len(lines[0]))
215 for subc, cls,help in self.subcommands:
216 lines.append("%s : %s"%(subc, cls))
217 if help:
218 lines.append(indent(help, flatten=True))
219 lines.append('')
220 print '\n'.join(lines)
221
188 222 def print_help(self, classes=False):
189 223 """Print the help for each Configurable class in self.classes.
190 224
191 225 If classes=False (the default), only flags and aliases are printed
192 226 """
193 227 self.print_flag_help()
194 228 self.print_alias_help()
195 229
196 230 if classes:
197 231 if self.classes:
198 232 print "Class parameters"
199 233 print "----------------"
200 234 print self.keyvalue_description
201 235 print
202 236
203 237 for cls in self.classes:
204 238 cls.class_print_help()
205 239 print
206 240 else:
207 241 print "To see all available configurables, use `--help-all`"
208 242 print
209 243
210 244 def print_description(self):
211 245 """Print the application description."""
212 246 print self.description
213 247 print
214 248
215 249 def print_version(self):
216 250 """Print the version string."""
217 251 print self.version
218 252
219 253 def update_config(self, config):
220 254 """Fire the traits events when the config is updated."""
221 255 # Save a copy of the current config.
222 256 newconfig = deepcopy(self.config)
223 257 # Merge the new config into the current one.
224 258 newconfig._merge(config)
225 259 # Save the combined config as self.config, which triggers the traits
226 260 # events.
227 261 self.config = newconfig
228
262
263 def initialize_subcommand(self, subc, argv=None):
264 """Initialize a subcommand with argv"""
265 if '-h' in subc:
266 # requested help
267 self.print_description()
268 self.print_subcommands()
269 self.exit(0)
270 subapp = self.subcommands.get(subc, None)
271 if subapp is None:
272 self.print_description()
273 print "No such subcommand: %r"%subc
274 print
275 self.print_subcommands()
276 self.exit(1)
277
278 if isinstance(subapp, basestring):
279 subapp = import_item(subapp)
280
281 # instantiate
282 self.subapp = subapp()
283 # and initialize subapp
284 self.subapp.initialize(argv)
285
229 286 def parse_command_line(self, argv=None):
230 287 """Parse the command line arguments."""
231 288 argv = sys.argv[1:] if argv is None else argv
232 289
290 if self.subcommands:
291 # we have subcommands
292 if len(argv) == 0:
293 # none specified
294 self.print_description()
295 self.print_subcommands()
296 self.exit(1)
297
298 return self.initialize_subcommand(argv[0], argv[1:])
299
233 300 if '-h' in argv or '--help' in argv or '--help-all' in argv:
234 301 self.print_description()
235 302 self.print_help('--help-all' in argv)
236 303 self.exit(0)
237 304
238 305 if '--version' in argv:
239 306 self.print_version()
240 307 self.exit(0)
241 308
242 309 loader = KeyValueConfigLoader(argv=argv, aliases=self.aliases,
243 310 flags=self.flags)
244 311 config = loader.load_config()
245 312 self.update_config(config)
246 313
247 314 def load_config_file(self, filename, path=None):
248 315 """Load a .py based config file by filename and path."""
249 316 loader = PyFileConfigLoader(filename, path=path)
250 317 config = loader.load_config()
251 318 self.update_config(config)
252 319
253 320 def exit(self, exit_status=0):
254 321 self.log.debug("Exiting application: %s" % self.name)
255 322 sys.exit(exit_status)
256 323
257 324 #-----------------------------------------------------------------------------
258 325 # utility functions, for convenience
259 326 #-----------------------------------------------------------------------------
260 327
261 328 def boolean_flag(name, configurable, set_help='', unset_help=''):
262 329 """helper for building basic --trait, --no-trait flags
263 330
264 331 Parameters
265 332 ----------
266 333
267 334 name : str
268 335 The name of the flag.
269 336 configurable : str
270 337 The 'Class.trait' string of the trait to be set/unset with the flag
271 338 set_help : unicode
272 339 help string for --name flag
273 340 unset_help : unicode
274 341 help string for --no-name flag
275 342
276 343 Returns
277 344 -------
278 345
279 346 cfg : dict
280 347 A dict with two keys: 'name', and 'no-name', for setting and unsetting
281 348 the trait, respectively.
282 349 """
283 350 # default helpstrings
284 351 set_help = set_help or "set %s=True"%configurable
285 352 unset_help = unset_help or "set %s=False"%configurable
286 353
287 354 cls,trait = configurable.split('.')
288 355
289 356 setter = Config()
290 357 setter[cls][trait] = True
291 358 unsetter = Config()
292 359 unsetter[cls][trait] = False
293 360 return {name : (setter, set_help), 'no-'+name : (unsetter, unset_help)}
@@ -1,96 +1,101 b''
1 1 """A simple example of how to use IPython.config.application.Application.
2 2
3 3 This should serve as a simple example that shows how the IPython config
4 4 system works. The main classes are:
5 5
6 6 * IPython.config.configurable.Configurable
7 7 * IPython.config.configurable.SingletonConfigurable
8 8 * IPython.config.loader.Config
9 9 * IPython.config.application.Application
10 10
11 11 To see the command line option help, run this program from the command line::
12 12
13 13 $ python appconfig.py -h
14 14
15 15 To make one of your classes configurable (from the command line and config
16 16 files) inherit from Configurable and declare class attributes as traits (see
17 17 classes Foo and Bar below). To make the traits configurable, you will need
18 18 to set the following options:
19 19
20 20 * ``config``: set to ``True`` to make the attribute configurable.
21 21 * ``shortname``: by default, configurable attributes are set using the syntax
22 22 "Classname.attributename". At the command line, this is a bit verbose, so
23 23 we allow "shortnames" to be declared. Setting a shortname is optional, but
24 24 when you do this, you can set the option at the command line using the
25 25 syntax: "shortname=value".
26 26 * ``help``: set the help string to display a help message when the ``-h``
27 27 option is given at the command line. The help string should be valid ReST.
28 28
29 29 When the config attribute of an Application is updated, it will fire all of
30 30 the trait's events for all of the config=True attributes.
31 31 """
32 32
33 33 import sys
34 34
35 35 from IPython.config.configurable import Configurable
36 36 from IPython.config.application import Application
37 37 from IPython.utils.traitlets import (
38 38 Bool, Unicode, Int, Float, List, Dict
39 39 )
40 40
41 41
42 42 class Foo(Configurable):
43 43 """A class that has configurable, typed attributes.
44 44
45 45 """
46 46
47 47 i = Int(0, config=True, help="The integer i.")
48 48 j = Int(1, config=True, help="The integer j.")
49 49 name = Unicode(u'Brian', config=True, help="First name.")
50 50
51 51
52 52 class Bar(Configurable):
53 53
54 54 enabled = Bool(True, config=True, help="Enable bar.")
55 55
56 56
57 57 class MyApp(Application):
58 58
59 59 name = Unicode(u'myapp')
60 60 running = Bool(False, config=True,
61 61 help="Is the app running?")
62 62 classes = List([Bar, Foo])
63 63 config_file = Unicode(u'', config=True,
64 64 help="Load this config file")
65 65
66 66 aliases = Dict(dict(i='Foo.i',j='Foo.j',name='Foo.name', running='MyApp.running',
67 67 enabled='Bar.enabled', log_level='MyApp.log_level'))
68 68
69 69 flags = Dict(dict(enable=({'Bar': {'enabled' : True}}, "Enable Bar"),
70 70 disable=({'Bar': {'enabled' : False}}, "Disable Bar"),
71 71 debug=({'MyApp':{'log_level':10}}, "Set loglevel to DEBUG")
72 72 ))
73 73
74 74 def init_foo(self):
75 75 # Pass config to other classes for them to inherit the config.
76 76 self.foo = Foo(config=self.config)
77 77
78 78 def init_bar(self):
79 79 # Pass config to other classes for them to inherit the config.
80 80 self.bar = Bar(config=self.config)
81 81
82 def initialize(self, argv=None):
83 self.parse_command_line(argv)
84 if self.config_file:
85 self.load_config_file(self.config_file)
86 self.init_foo()
87 self.init_bar()
88
89 def start(self):
90 print "app.config:"
91 print self.config
82 92
83 93
84 94 def main():
85 95 app = MyApp()
86 app.parse_command_line()
87 if app.config_file:
88 app.load_config_file(app.config_file)
89 app.init_foo()
90 app.init_bar()
91 print "app.config:"
92 print app.config
96 app.initialize()
97 app.start()
93 98
94 99
95 100 if __name__ == "__main__":
96 101 main()
General Comments 0
You need to be logged in to leave comments. Login now