##// END OF EJS Templates
add `--help-all` flag, and don't print all configurables by default
MinRK -
Show More
@@ -1,286 +1,293 b''
1 # encoding: utf-8
1 # encoding: utf-8
2 """
2 """
3 A base class for a configurable application.
3 A base class for a configurable application.
4
4
5 Authors:
5 Authors:
6
6
7 * Brian Granger
7 * Brian Granger
8 """
8 """
9
9
10 #-----------------------------------------------------------------------------
10 #-----------------------------------------------------------------------------
11 # Copyright (C) 2008-2011 The IPython Development Team
11 # Copyright (C) 2008-2011 The IPython Development Team
12 #
12 #
13 # Distributed under the terms of the BSD License. The full license is in
13 # Distributed under the terms of the BSD License. The full license is in
14 # the file COPYING, distributed as part of this software.
14 # the file COPYING, distributed as part of this software.
15 #-----------------------------------------------------------------------------
15 #-----------------------------------------------------------------------------
16
16
17 #-----------------------------------------------------------------------------
17 #-----------------------------------------------------------------------------
18 # Imports
18 # Imports
19 #-----------------------------------------------------------------------------
19 #-----------------------------------------------------------------------------
20
20
21 from copy import deepcopy
21 from copy import deepcopy
22 import logging
22 import logging
23 import sys
23 import sys
24
24
25 from IPython.config.configurable import SingletonConfigurable
25 from IPython.config.configurable import SingletonConfigurable
26 from IPython.config.loader import (
26 from IPython.config.loader import (
27 KeyValueConfigLoader, PyFileConfigLoader, Config
27 KeyValueConfigLoader, PyFileConfigLoader, Config
28 )
28 )
29
29
30 from IPython.utils.traitlets import (
30 from IPython.utils.traitlets import (
31 Unicode, List, Int, Enum, Dict
31 Unicode, List, Int, Enum, Dict
32 )
32 )
33 from IPython.utils.text import indent
33 from IPython.utils.text import indent
34
34
35 #-----------------------------------------------------------------------------
35 #-----------------------------------------------------------------------------
36 # Descriptions for the various sections
36 # Descriptions for the various sections
37 #-----------------------------------------------------------------------------
37 #-----------------------------------------------------------------------------
38
38
39 flag_description = """
39 flag_description = """
40 Flags are command-line arguments passed as '--<flag>'.
40 Flags are command-line arguments passed as '--<flag>'.
41 These take no parameters, unlike regular key-value arguments.
41 These take no parameters, unlike regular key-value arguments.
42 They are typically used for setting boolean flags, or enabling
42 They are typically used for setting boolean flags, or enabling
43 modes that involve setting multiple options together.
43 modes that involve setting multiple options together.
44 """.strip() # trim newlines of front and back
44 """.strip() # trim newlines of front and back
45
45
46 alias_description = """
46 alias_description = """
47 These are commonly set parameters, given abbreviated aliases for convenience.
47 These are commonly set parameters, given abbreviated aliases for convenience.
48 They are set in the same `name=value` way as class parameters, where
48 They are set in the same `name=value` way as class parameters, where
49 <name> is replaced by the real parameter for which it is an alias.
49 <name> is replaced by the real parameter for which it is an alias.
50 """.strip() # trim newlines of front and back
50 """.strip() # trim newlines of front and back
51
51
52 keyvalue_description = """
52 keyvalue_description = """
53 Parameters are set from command-line arguments of the form:
53 Parameters are set from command-line arguments of the form:
54 `Class.trait=value`. Parameters will *never* be prefixed with '-'.
54 `Class.trait=value`. Parameters will *never* be prefixed with '-'.
55 This line is evaluated in Python, so simple expressions are allowed, e.g.
55 This line is evaluated in Python, so simple expressions are allowed, e.g.
56 `C.a='range(3)'` For setting C.a=[0,1,2]
56 `C.a='range(3)'` For setting C.a=[0,1,2]
57 """.strip() # trim newlines of front and back
57 """.strip() # trim newlines of front and back
58
58
59 #-----------------------------------------------------------------------------
59 #-----------------------------------------------------------------------------
60 # Application class
60 # Application class
61 #-----------------------------------------------------------------------------
61 #-----------------------------------------------------------------------------
62
62
63
63
64 class ApplicationError(Exception):
64 class ApplicationError(Exception):
65 pass
65 pass
66
66
67
67
68 class Application(SingletonConfigurable):
68 class Application(SingletonConfigurable):
69 """A singleton application with full configuration support."""
69 """A singleton application with full configuration support."""
70
70
71 # The name of the application, will usually match the name of the command
71 # The name of the application, will usually match the name of the command
72 # line application
72 # line application
73 name = Unicode(u'application')
73 name = Unicode(u'application')
74
74
75 # The description of the application that is printed at the beginning
75 # The description of the application that is printed at the beginning
76 # of the help.
76 # of the help.
77 description = Unicode(u'This is an application.')
77 description = Unicode(u'This is an application.')
78 # default section descriptions
78 # default section descriptions
79 flag_description = Unicode(flag_description)
79 flag_description = Unicode(flag_description)
80 alias_description = Unicode(alias_description)
80 alias_description = Unicode(alias_description)
81 keyvalue_description = Unicode(keyvalue_description)
81 keyvalue_description = Unicode(keyvalue_description)
82
82
83
83
84 # A sequence of Configurable subclasses whose config=True attributes will
84 # A sequence of Configurable subclasses whose config=True attributes will
85 # be exposed at the command line.
85 # be exposed at the command line.
86 classes = List([])
86 classes = List([])
87
87
88 # The version string of this application.
88 # The version string of this application.
89 version = Unicode(u'0.0')
89 version = Unicode(u'0.0')
90
90
91 # The log level for the application
91 # The log level for the application
92 log_level = Enum((0,10,20,30,40,50), default_value=logging.WARN,
92 log_level = Enum((0,10,20,30,40,50), default_value=logging.WARN,
93 config=True,
93 config=True,
94 help="Set the log level.")
94 help="Set the log level.")
95
95
96 # the alias map for configurables
96 # the alias map for configurables
97 aliases = Dict(dict(log_level='Application.log_level'))
97 aliases = Dict(dict(log_level='Application.log_level'))
98
98
99 # flags for loading Configurables or store_const style flags
99 # flags for loading Configurables or store_const style flags
100 # flags are loaded from this dict by '--key' flags
100 # flags are loaded from this dict by '--key' flags
101 # this must be a dict of two-tuples, the first element being the Config/dict
101 # this must be a dict of two-tuples, the first element being the Config/dict
102 # and the second being the help string for the flag
102 # and the second being the help string for the flag
103 flags = Dict()
103 flags = Dict()
104
104
105
105
106 def __init__(self, **kwargs):
106 def __init__(self, **kwargs):
107 SingletonConfigurable.__init__(self, **kwargs)
107 SingletonConfigurable.__init__(self, **kwargs)
108 # Add my class to self.classes so my attributes appear in command line
108 # Add my class to self.classes so my attributes appear in command line
109 # options.
109 # options.
110 self.classes.insert(0, self.__class__)
110 self.classes.insert(0, self.__class__)
111
111
112 # ensure self.flags dict is valid
112 # ensure self.flags dict is valid
113 for key,value in self.flags.iteritems():
113 for key,value in self.flags.iteritems():
114 assert len(value) == 2, "Bad flag: %r:%s"%(key,value)
114 assert len(value) == 2, "Bad flag: %r:%s"%(key,value)
115 assert isinstance(value[0], (dict, Config)), "Bad flag: %r:%s"%(key,value)
115 assert isinstance(value[0], (dict, Config)), "Bad flag: %r:%s"%(key,value)
116 assert isinstance(value[1], basestring), "Bad flag: %r:%s"%(key,value)
116 assert isinstance(value[1], basestring), "Bad flag: %r:%s"%(key,value)
117 self.init_logging()
117 self.init_logging()
118
118
119 def _config_changed(self, name, old, new):
119 def _config_changed(self, name, old, new):
120 SingletonConfigurable._config_changed(self, name, old, new)
120 SingletonConfigurable._config_changed(self, name, old, new)
121 self.log.debug('Config changed:')
121 self.log.debug('Config changed:')
122 self.log.debug(repr(new))
122 self.log.debug(repr(new))
123
123
124 def init_logging(self):
124 def init_logging(self):
125 """Start logging for this application.
125 """Start logging for this application.
126
126
127 The default is to log to stdout using a StreaHandler. The log level
127 The default is to log to stdout using a StreaHandler. The log level
128 starts at loggin.WARN, but this can be adjusted by setting the
128 starts at loggin.WARN, but this can be adjusted by setting the
129 ``log_level`` attribute.
129 ``log_level`` attribute.
130 """
130 """
131 self.log = logging.getLogger(self.__class__.__name__)
131 self.log = logging.getLogger(self.__class__.__name__)
132 self.log.setLevel(self.log_level)
132 self.log.setLevel(self.log_level)
133 self._log_handler = logging.StreamHandler()
133 self._log_handler = logging.StreamHandler()
134 self._log_formatter = logging.Formatter("[%(name)s] %(message)s")
134 self._log_formatter = logging.Formatter("[%(name)s] %(message)s")
135 self._log_handler.setFormatter(self._log_formatter)
135 self._log_handler.setFormatter(self._log_formatter)
136 self.log.addHandler(self._log_handler)
136 self.log.addHandler(self._log_handler)
137
137
138 def _log_level_changed(self, name, old, new):
138 def _log_level_changed(self, name, old, new):
139 """Adjust the log level when log_level is set."""
139 """Adjust the log level when log_level is set."""
140 self.log.setLevel(new)
140 self.log.setLevel(new)
141
141
142 def print_alias_help(self):
142 def print_alias_help(self):
143 """print the alias part of the help"""
143 """print the alias part of the help"""
144 if not self.aliases:
144 if not self.aliases:
145 return
145 return
146
146
147 lines = ['Aliases']
147 lines = ['Aliases']
148 lines.append('_'*len(lines[0]))
148 lines.append('_'*len(lines[0]))
149 lines.append(self.alias_description)
149 lines.append(self.alias_description)
150 lines.append('')
150 lines.append('')
151
151
152 classdict = {}
152 classdict = {}
153 for c in self.classes:
153 for c in self.classes:
154 classdict[c.__name__] = c
154 classdict[c.__name__] = c
155
155
156 for alias, longname in self.aliases.iteritems():
156 for alias, longname in self.aliases.iteritems():
157 classname, traitname = longname.split('.',1)
157 classname, traitname = longname.split('.',1)
158 cls = classdict[classname]
158 cls = classdict[classname]
159
159
160 trait = cls.class_traits(config=True)[traitname]
160 trait = cls.class_traits(config=True)[traitname]
161 help = cls.class_get_trait_help(trait)
161 help = cls.class_get_trait_help(trait)
162 help = help.replace(longname, "%s (%s)"%(alias, longname), 1)
162 help = help.replace(longname, "%s (%s)"%(alias, longname), 1)
163 lines.append(help)
163 lines.append(help)
164 # header = "%s (%s) : %s"%(alias, longname, trait.__class__.__name__)
164 # header = "%s (%s) : %s"%(alias, longname, trait.__class__.__name__)
165 # lines.append(header)
165 # lines.append(header)
166 # help = cls.class_get_trait_help(trait)
166 # help = cls.class_get_trait_help(trait)
167 # if help:
167 # if help:
168 # lines.append(indent(help, flatten=True))
168 # lines.append(indent(help, flatten=True))
169 lines.append('')
169 lines.append('')
170 print '\n'.join(lines)
170 print '\n'.join(lines)
171
171
172 def print_flag_help(self):
172 def print_flag_help(self):
173 """print the flag part of the help"""
173 """print the flag part of the help"""
174 if not self.flags:
174 if not self.flags:
175 return
175 return
176
176
177 lines = ['Flags']
177 lines = ['Flags']
178 lines.append('_'*len(lines[0]))
178 lines.append('_'*len(lines[0]))
179 lines.append(self.flag_description)
179 lines.append(self.flag_description)
180 lines.append('')
180 lines.append('')
181
181
182 for m, (cfg,help) in self.flags.iteritems():
182 for m, (cfg,help) in self.flags.iteritems():
183 lines.append('--'+m)
183 lines.append('--'+m)
184 lines.append(indent(help, flatten=True))
184 lines.append(indent(help, flatten=True))
185 lines.append('')
185 lines.append('')
186 print '\n'.join(lines)
186 print '\n'.join(lines)
187
187
188 def print_help(self):
188 def print_help(self, classes=False):
189 """Print the help for each Configurable class in self.classes."""
189 """Print the help for each Configurable class in self.classes.
190
191 If classes=False (the default), only flags and aliases are printed
192 """
190 self.print_flag_help()
193 self.print_flag_help()
191 self.print_alias_help()
194 self.print_alias_help()
192
195
193 if self.classes:
196 if classes:
194 print "Class parameters"
197 if self.classes:
195 print "----------------"
198 print "Class parameters"
196 print self.keyvalue_description
199 print "----------------"
197 print
200 print self.keyvalue_description
201 print
198
202
199 for cls in self.classes:
203 for cls in self.classes:
200 cls.class_print_help()
204 cls.class_print_help()
205 print
206 else:
207 print "To see all available configurables, use `--help-all`"
201 print
208 print
202
209
203 def print_description(self):
210 def print_description(self):
204 """Print the application description."""
211 """Print the application description."""
205 print self.description
212 print self.description
206 print
213 print
207
214
208 def print_version(self):
215 def print_version(self):
209 """Print the version string."""
216 """Print the version string."""
210 print self.version
217 print self.version
211
218
212 def update_config(self, config):
219 def update_config(self, config):
213 """Fire the traits events when the config is updated."""
220 """Fire the traits events when the config is updated."""
214 # Save a copy of the current config.
221 # Save a copy of the current config.
215 newconfig = deepcopy(self.config)
222 newconfig = deepcopy(self.config)
216 # Merge the new config into the current one.
223 # Merge the new config into the current one.
217 newconfig._merge(config)
224 newconfig._merge(config)
218 # Save the combined config as self.config, which triggers the traits
225 # Save the combined config as self.config, which triggers the traits
219 # events.
226 # events.
220 self.config = newconfig
227 self.config = newconfig
221
228
222 def parse_command_line(self, argv=None):
229 def parse_command_line(self, argv=None):
223 """Parse the command line arguments."""
230 """Parse the command line arguments."""
224 argv = sys.argv[1:] if argv is None else argv
231 argv = sys.argv[1:] if argv is None else argv
225
232
226 if '-h' in argv or '--help' in argv:
233 if '-h' in argv or '--help' in argv or '--help-all' in argv:
227 self.print_description()
234 self.print_description()
228 self.print_help()
235 self.print_help('--help-all' in argv)
229 self.exit(0)
236 self.exit(0)
230
237
231 if '--version' in argv:
238 if '--version' in argv:
232 self.print_version()
239 self.print_version()
233 self.exit(0)
240 self.exit(0)
234
241
235 loader = KeyValueConfigLoader(argv=argv, aliases=self.aliases,
242 loader = KeyValueConfigLoader(argv=argv, aliases=self.aliases,
236 flags=self.flags)
243 flags=self.flags)
237 config = loader.load_config()
244 config = loader.load_config()
238 self.update_config(config)
245 self.update_config(config)
239
246
240 def load_config_file(self, filename, path=None):
247 def load_config_file(self, filename, path=None):
241 """Load a .py based config file by filename and path."""
248 """Load a .py based config file by filename and path."""
242 loader = PyFileConfigLoader(filename, path=path)
249 loader = PyFileConfigLoader(filename, path=path)
243 config = loader.load_config()
250 config = loader.load_config()
244 self.update_config(config)
251 self.update_config(config)
245
252
246 def exit(self, exit_status=0):
253 def exit(self, exit_status=0):
247 self.log.debug("Exiting application: %s" % self.name)
254 self.log.debug("Exiting application: %s" % self.name)
248 sys.exit(exit_status)
255 sys.exit(exit_status)
249
256
250 #-----------------------------------------------------------------------------
257 #-----------------------------------------------------------------------------
251 # utility functions, for convenience
258 # utility functions, for convenience
252 #-----------------------------------------------------------------------------
259 #-----------------------------------------------------------------------------
253
260
254 def boolean_flag(name, configurable, set_help='', unset_help=''):
261 def boolean_flag(name, configurable, set_help='', unset_help=''):
255 """helper for building basic --trait, --no-trait flags
262 """helper for building basic --trait, --no-trait flags
256
263
257 Parameters
264 Parameters
258 ----------
265 ----------
259
266
260 name : str
267 name : str
261 The name of the flag.
268 The name of the flag.
262 configurable : str
269 configurable : str
263 The 'Class.trait' string of the trait to be set/unset with the flag
270 The 'Class.trait' string of the trait to be set/unset with the flag
264 set_help : unicode
271 set_help : unicode
265 help string for --name flag
272 help string for --name flag
266 unset_help : unicode
273 unset_help : unicode
267 help string for --no-name flag
274 help string for --no-name flag
268
275
269 Returns
276 Returns
270 -------
277 -------
271
278
272 cfg : dict
279 cfg : dict
273 A dict with two keys: 'name', and 'no-name', for setting and unsetting
280 A dict with two keys: 'name', and 'no-name', for setting and unsetting
274 the trait, respectively.
281 the trait, respectively.
275 """
282 """
276 # default helpstrings
283 # default helpstrings
277 set_help = set_help or "set %s=True"%configurable
284 set_help = set_help or "set %s=True"%configurable
278 unset_help = unset_help or "set %s=False"%configurable
285 unset_help = unset_help or "set %s=False"%configurable
279
286
280 cls,trait = configurable.split('.')
287 cls,trait = configurable.split('.')
281
288
282 setter = Config()
289 setter = Config()
283 setter[cls][trait] = True
290 setter[cls][trait] = True
284 unsetter = Config()
291 unsetter = Config()
285 unsetter[cls][trait] = False
292 unsetter[cls][trait] = False
286 return {name : (setter, set_help), 'no-'+name : (unsetter, unset_help)}
293 return {name : (setter, set_help), 'no-'+name : (unsetter, unset_help)}
General Comments 0
You need to be logged in to leave comments. Login now