##// END OF EJS Templates
don't run init_profile_dir twice
MinRK -
Show More
@@ -1,369 +1,372
1 # encoding: utf-8
1 # encoding: utf-8
2 """
2 """
3 An application for IPython.
3 An application for IPython.
4
4
5 All top-level applications should use the classes in this module for
5 All top-level applications should use the classes in this module for
6 handling configuration and creating componenets.
6 handling configuration and creating componenets.
7
7
8 The job of an :class:`Application` is to create the master configuration
8 The job of an :class:`Application` is to create the master configuration
9 object and then create the configurable objects, passing the config to them.
9 object and then create the configurable objects, passing the config to them.
10
10
11 Authors:
11 Authors:
12
12
13 * Brian Granger
13 * Brian Granger
14 * Fernando Perez
14 * Fernando Perez
15 * Min RK
15 * Min RK
16
16
17 """
17 """
18
18
19 #-----------------------------------------------------------------------------
19 #-----------------------------------------------------------------------------
20 # Copyright (C) 2008-2011 The IPython Development Team
20 # Copyright (C) 2008-2011 The IPython Development Team
21 #
21 #
22 # Distributed under the terms of the BSD License. The full license is in
22 # Distributed under the terms of the BSD License. The full license is in
23 # the file COPYING, distributed as part of this software.
23 # the file COPYING, distributed as part of this software.
24 #-----------------------------------------------------------------------------
24 #-----------------------------------------------------------------------------
25
25
26 #-----------------------------------------------------------------------------
26 #-----------------------------------------------------------------------------
27 # Imports
27 # Imports
28 #-----------------------------------------------------------------------------
28 #-----------------------------------------------------------------------------
29
29
30 import atexit
30 import atexit
31 import glob
31 import glob
32 import logging
32 import logging
33 import os
33 import os
34 import shutil
34 import shutil
35 import sys
35 import sys
36
36
37 from IPython.config.application import Application, catch_config_error
37 from IPython.config.application import Application, catch_config_error
38 from IPython.config.loader import ConfigFileNotFound
38 from IPython.config.loader import ConfigFileNotFound
39 from IPython.core import release, crashhandler
39 from IPython.core import release, crashhandler
40 from IPython.core.profiledir import ProfileDir, ProfileDirError
40 from IPython.core.profiledir import ProfileDir, ProfileDirError
41 from IPython.utils.path import get_ipython_dir, get_ipython_package_dir
41 from IPython.utils.path import get_ipython_dir, get_ipython_package_dir
42 from IPython.utils.traitlets import List, Unicode, Type, Bool, Dict, Set, Instance
42 from IPython.utils.traitlets import List, Unicode, Type, Bool, Dict, Set, Instance
43
43
44 #-----------------------------------------------------------------------------
44 #-----------------------------------------------------------------------------
45 # Classes and functions
45 # Classes and functions
46 #-----------------------------------------------------------------------------
46 #-----------------------------------------------------------------------------
47
47
48
48
49 #-----------------------------------------------------------------------------
49 #-----------------------------------------------------------------------------
50 # Base Application Class
50 # Base Application Class
51 #-----------------------------------------------------------------------------
51 #-----------------------------------------------------------------------------
52
52
53 # aliases and flags
53 # aliases and flags
54
54
55 base_aliases = {
55 base_aliases = {
56 'profile' : 'BaseIPythonApplication.profile',
56 'profile' : 'BaseIPythonApplication.profile',
57 'ipython-dir' : 'BaseIPythonApplication.ipython_dir',
57 'ipython-dir' : 'BaseIPythonApplication.ipython_dir',
58 'log-level' : 'Application.log_level',
58 'log-level' : 'Application.log_level',
59 'config' : 'BaseIPythonApplication.extra_config_file',
59 'config' : 'BaseIPythonApplication.extra_config_file',
60 }
60 }
61
61
62 base_flags = dict(
62 base_flags = dict(
63 debug = ({'Application' : {'log_level' : logging.DEBUG}},
63 debug = ({'Application' : {'log_level' : logging.DEBUG}},
64 "set log level to logging.DEBUG (maximize logging output)"),
64 "set log level to logging.DEBUG (maximize logging output)"),
65 quiet = ({'Application' : {'log_level' : logging.CRITICAL}},
65 quiet = ({'Application' : {'log_level' : logging.CRITICAL}},
66 "set log level to logging.CRITICAL (minimize logging output)"),
66 "set log level to logging.CRITICAL (minimize logging output)"),
67 init = ({'BaseIPythonApplication' : {
67 init = ({'BaseIPythonApplication' : {
68 'copy_config_files' : True,
68 'copy_config_files' : True,
69 'auto_create' : True}
69 'auto_create' : True}
70 }, """Initialize profile with default config files. This is equivalent
70 }, """Initialize profile with default config files. This is equivalent
71 to running `ipython profile create <profile>` prior to startup.
71 to running `ipython profile create <profile>` prior to startup.
72 """)
72 """)
73 )
73 )
74
74
75
75
76 class BaseIPythonApplication(Application):
76 class BaseIPythonApplication(Application):
77
77
78 name = Unicode(u'ipython')
78 name = Unicode(u'ipython')
79 description = Unicode(u'IPython: an enhanced interactive Python shell.')
79 description = Unicode(u'IPython: an enhanced interactive Python shell.')
80 version = Unicode(release.version)
80 version = Unicode(release.version)
81
81
82 aliases = Dict(base_aliases)
82 aliases = Dict(base_aliases)
83 flags = Dict(base_flags)
83 flags = Dict(base_flags)
84 classes = List([ProfileDir])
84 classes = List([ProfileDir])
85
85
86 # Track whether the config_file has changed,
86 # Track whether the config_file has changed,
87 # because some logic happens only if we aren't using the default.
87 # because some logic happens only if we aren't using the default.
88 config_file_specified = Set()
88 config_file_specified = Set()
89
89
90 config_file_name = Unicode()
90 config_file_name = Unicode()
91 def _config_file_name_default(self):
91 def _config_file_name_default(self):
92 return self.name.replace('-','_') + u'_config.py'
92 return self.name.replace('-','_') + u'_config.py'
93 def _config_file_name_changed(self, name, old, new):
93 def _config_file_name_changed(self, name, old, new):
94 if new != old:
94 if new != old:
95 self.config_file_specified.add(new)
95 self.config_file_specified.add(new)
96
96
97 # The directory that contains IPython's builtin profiles.
97 # The directory that contains IPython's builtin profiles.
98 builtin_profile_dir = Unicode(
98 builtin_profile_dir = Unicode(
99 os.path.join(get_ipython_package_dir(), u'config', u'profile', u'default')
99 os.path.join(get_ipython_package_dir(), u'config', u'profile', u'default')
100 )
100 )
101
101
102 config_file_paths = List(Unicode)
102 config_file_paths = List(Unicode)
103 def _config_file_paths_default(self):
103 def _config_file_paths_default(self):
104 return [os.getcwdu()]
104 return [os.getcwdu()]
105
105
106 extra_config_file = Unicode(config=True,
106 extra_config_file = Unicode(config=True,
107 help="""Path to an extra config file to load.
107 help="""Path to an extra config file to load.
108
108
109 If specified, load this config file in addition to any other IPython config.
109 If specified, load this config file in addition to any other IPython config.
110 """)
110 """)
111 def _extra_config_file_changed(self, name, old, new):
111 def _extra_config_file_changed(self, name, old, new):
112 try:
112 try:
113 self.config_files.remove(old)
113 self.config_files.remove(old)
114 except ValueError:
114 except ValueError:
115 pass
115 pass
116 self.config_file_specified.add(new)
116 self.config_file_specified.add(new)
117 self.config_files.append(new)
117 self.config_files.append(new)
118
118
119 profile = Unicode(u'default', config=True,
119 profile = Unicode(u'default', config=True,
120 help="""The IPython profile to use."""
120 help="""The IPython profile to use."""
121 )
121 )
122
122
123 def _profile_changed(self, name, old, new):
123 def _profile_changed(self, name, old, new):
124 self.builtin_profile_dir = os.path.join(
124 self.builtin_profile_dir = os.path.join(
125 get_ipython_package_dir(), u'config', u'profile', new
125 get_ipython_package_dir(), u'config', u'profile', new
126 )
126 )
127
127
128 ipython_dir = Unicode(get_ipython_dir(), config=True,
128 ipython_dir = Unicode(get_ipython_dir(), config=True,
129 help="""
129 help="""
130 The name of the IPython directory. This directory is used for logging
130 The name of the IPython directory. This directory is used for logging
131 configuration (through profiles), history storage, etc. The default
131 configuration (through profiles), history storage, etc. The default
132 is usually $HOME/.ipython. This options can also be specified through
132 is usually $HOME/.ipython. This options can also be specified through
133 the environment variable IPYTHONDIR.
133 the environment variable IPYTHONDIR.
134 """
134 """
135 )
135 )
136 _in_init_profile_dir = False
136 _in_init_profile_dir = False
137 profile_dir = Instance(ProfileDir)
137 profile_dir = Instance(ProfileDir)
138 def _profile_dir_default(self):
138 def _profile_dir_default(self):
139 # avoid recursion
139 # avoid recursion
140 if self._in_init_profile_dir:
140 if self._in_init_profile_dir:
141 return
141 return
142 # profile_dir requested early, force initialization
142 # profile_dir requested early, force initialization
143 self.init_profile_dir()
143 self.init_profile_dir()
144 return self.profile_dir
144 return self.profile_dir
145
145
146 overwrite = Bool(False, config=True,
146 overwrite = Bool(False, config=True,
147 help="""Whether to overwrite existing config files when copying""")
147 help="""Whether to overwrite existing config files when copying""")
148 auto_create = Bool(False, config=True,
148 auto_create = Bool(False, config=True,
149 help="""Whether to create profile dir if it doesn't exist""")
149 help="""Whether to create profile dir if it doesn't exist""")
150
150
151 config_files = List(Unicode)
151 config_files = List(Unicode)
152 def _config_files_default(self):
152 def _config_files_default(self):
153 return [self.config_file_name]
153 return [self.config_file_name]
154
154
155 copy_config_files = Bool(False, config=True,
155 copy_config_files = Bool(False, config=True,
156 help="""Whether to install the default config files into the profile dir.
156 help="""Whether to install the default config files into the profile dir.
157 If a new profile is being created, and IPython contains config files for that
157 If a new profile is being created, and IPython contains config files for that
158 profile, then they will be staged into the new directory. Otherwise,
158 profile, then they will be staged into the new directory. Otherwise,
159 default config files will be automatically generated.
159 default config files will be automatically generated.
160 """)
160 """)
161
161
162 verbose_crash = Bool(False, config=True,
162 verbose_crash = Bool(False, config=True,
163 help="""Create a massive crash report when IPython encounters what may be an
163 help="""Create a massive crash report when IPython encounters what may be an
164 internal error. The default is to append a short message to the
164 internal error. The default is to append a short message to the
165 usual traceback""")
165 usual traceback""")
166
166
167 # The class to use as the crash handler.
167 # The class to use as the crash handler.
168 crash_handler_class = Type(crashhandler.CrashHandler)
168 crash_handler_class = Type(crashhandler.CrashHandler)
169
169
170 @catch_config_error
170 @catch_config_error
171 def __init__(self, **kwargs):
171 def __init__(self, **kwargs):
172 super(BaseIPythonApplication, self).__init__(**kwargs)
172 super(BaseIPythonApplication, self).__init__(**kwargs)
173 # ensure current working directory exists
173 # ensure current working directory exists
174 try:
174 try:
175 directory = os.getcwdu()
175 directory = os.getcwdu()
176 except:
176 except:
177 # raise exception
177 # raise exception
178 self.log.error("Current working directory doesn't exist.")
178 self.log.error("Current working directory doesn't exist.")
179 raise
179 raise
180
180
181 # ensure even default IPYTHONDIR exists
181 # ensure even default IPYTHONDIR exists
182 if not os.path.exists(self.ipython_dir):
182 if not os.path.exists(self.ipython_dir):
183 self._ipython_dir_changed('ipython_dir', self.ipython_dir, self.ipython_dir)
183 self._ipython_dir_changed('ipython_dir', self.ipython_dir, self.ipython_dir)
184
184
185 #-------------------------------------------------------------------------
185 #-------------------------------------------------------------------------
186 # Various stages of Application creation
186 # Various stages of Application creation
187 #-------------------------------------------------------------------------
187 #-------------------------------------------------------------------------
188
188
189 def init_crash_handler(self):
189 def init_crash_handler(self):
190 """Create a crash handler, typically setting sys.excepthook to it."""
190 """Create a crash handler, typically setting sys.excepthook to it."""
191 self.crash_handler = self.crash_handler_class(self)
191 self.crash_handler = self.crash_handler_class(self)
192 sys.excepthook = self.excepthook
192 sys.excepthook = self.excepthook
193 def unset_crashhandler():
193 def unset_crashhandler():
194 sys.excepthook = sys.__excepthook__
194 sys.excepthook = sys.__excepthook__
195 atexit.register(unset_crashhandler)
195 atexit.register(unset_crashhandler)
196
196
197 def excepthook(self, etype, evalue, tb):
197 def excepthook(self, etype, evalue, tb):
198 """this is sys.excepthook after init_crashhandler
198 """this is sys.excepthook after init_crashhandler
199
199
200 set self.verbose_crash=True to use our full crashhandler, instead of
200 set self.verbose_crash=True to use our full crashhandler, instead of
201 a regular traceback with a short message (crash_handler_lite)
201 a regular traceback with a short message (crash_handler_lite)
202 """
202 """
203
203
204 if self.verbose_crash:
204 if self.verbose_crash:
205 return self.crash_handler(etype, evalue, tb)
205 return self.crash_handler(etype, evalue, tb)
206 else:
206 else:
207 return crashhandler.crash_handler_lite(etype, evalue, tb)
207 return crashhandler.crash_handler_lite(etype, evalue, tb)
208
208
209 def _ipython_dir_changed(self, name, old, new):
209 def _ipython_dir_changed(self, name, old, new):
210 if old in sys.path:
210 if old in sys.path:
211 sys.path.remove(old)
211 sys.path.remove(old)
212 sys.path.append(os.path.abspath(new))
212 sys.path.append(os.path.abspath(new))
213 if not os.path.isdir(new):
213 if not os.path.isdir(new):
214 os.makedirs(new, mode=0o777)
214 os.makedirs(new, mode=0o777)
215 readme = os.path.join(new, 'README')
215 readme = os.path.join(new, 'README')
216 if not os.path.exists(readme):
216 if not os.path.exists(readme):
217 path = os.path.join(get_ipython_package_dir(), u'config', u'profile')
217 path = os.path.join(get_ipython_package_dir(), u'config', u'profile')
218 shutil.copy(os.path.join(path, 'README'), readme)
218 shutil.copy(os.path.join(path, 'README'), readme)
219 self.log.debug("IPYTHONDIR set to: %s" % new)
219 self.log.debug("IPYTHONDIR set to: %s" % new)
220
220
221 def load_config_file(self, suppress_errors=True):
221 def load_config_file(self, suppress_errors=True):
222 """Load the config file.
222 """Load the config file.
223
223
224 By default, errors in loading config are handled, and a warning
224 By default, errors in loading config are handled, and a warning
225 printed on screen. For testing, the suppress_errors option is set
225 printed on screen. For testing, the suppress_errors option is set
226 to False, so errors will make tests fail.
226 to False, so errors will make tests fail.
227 """
227 """
228 self.log.debug("Searching path %s for config files", self.config_file_paths)
228 self.log.debug("Searching path %s for config files", self.config_file_paths)
229 base_config = 'ipython_config.py'
229 base_config = 'ipython_config.py'
230 self.log.debug("Attempting to load config file: %s" %
230 self.log.debug("Attempting to load config file: %s" %
231 base_config)
231 base_config)
232 try:
232 try:
233 Application.load_config_file(
233 Application.load_config_file(
234 self,
234 self,
235 base_config,
235 base_config,
236 path=self.config_file_paths
236 path=self.config_file_paths
237 )
237 )
238 except ConfigFileNotFound:
238 except ConfigFileNotFound:
239 # ignore errors loading parent
239 # ignore errors loading parent
240 self.log.debug("Config file %s not found", base_config)
240 self.log.debug("Config file %s not found", base_config)
241 pass
241 pass
242
242
243 for config_file_name in self.config_files:
243 for config_file_name in self.config_files:
244 if not config_file_name or config_file_name == base_config:
244 if not config_file_name or config_file_name == base_config:
245 continue
245 continue
246 self.log.debug("Attempting to load config file: %s" %
246 self.log.debug("Attempting to load config file: %s" %
247 self.config_file_name)
247 self.config_file_name)
248 try:
248 try:
249 Application.load_config_file(
249 Application.load_config_file(
250 self,
250 self,
251 config_file_name,
251 config_file_name,
252 path=self.config_file_paths
252 path=self.config_file_paths
253 )
253 )
254 except ConfigFileNotFound:
254 except ConfigFileNotFound:
255 # Only warn if the default config file was NOT being used.
255 # Only warn if the default config file was NOT being used.
256 if config_file_name in self.config_file_specified:
256 if config_file_name in self.config_file_specified:
257 msg = self.log.warn
257 msg = self.log.warn
258 else:
258 else:
259 msg = self.log.debug
259 msg = self.log.debug
260 msg("Config file not found, skipping: %s", config_file_name)
260 msg("Config file not found, skipping: %s", config_file_name)
261 except:
261 except:
262 # For testing purposes.
262 # For testing purposes.
263 if not suppress_errors:
263 if not suppress_errors:
264 raise
264 raise
265 self.log.warn("Error loading config file: %s" %
265 self.log.warn("Error loading config file: %s" %
266 self.config_file_name, exc_info=True)
266 self.config_file_name, exc_info=True)
267
267
268 def init_profile_dir(self):
268 def init_profile_dir(self):
269 """initialize the profile dir"""
269 """initialize the profile dir"""
270 self._in_init_profile_dir = True
270 self._in_init_profile_dir = True
271 if self.profile_dir is not None:
272 # already ran
273 return
271 try:
274 try:
272 # location explicitly specified:
275 # location explicitly specified:
273 location = self.config.ProfileDir.location
276 location = self.config.ProfileDir.location
274 except AttributeError:
277 except AttributeError:
275 # location not specified, find by profile name
278 # location not specified, find by profile name
276 try:
279 try:
277 p = ProfileDir.find_profile_dir_by_name(self.ipython_dir, self.profile, self.config)
280 p = ProfileDir.find_profile_dir_by_name(self.ipython_dir, self.profile, self.config)
278 except ProfileDirError:
281 except ProfileDirError:
279 # not found, maybe create it (always create default profile)
282 # not found, maybe create it (always create default profile)
280 if self.auto_create or self.profile == 'default':
283 if self.auto_create or self.profile == 'default':
281 try:
284 try:
282 p = ProfileDir.create_profile_dir_by_name(self.ipython_dir, self.profile, self.config)
285 p = ProfileDir.create_profile_dir_by_name(self.ipython_dir, self.profile, self.config)
283 except ProfileDirError:
286 except ProfileDirError:
284 self.log.fatal("Could not create profile: %r"%self.profile)
287 self.log.fatal("Could not create profile: %r"%self.profile)
285 self.exit(1)
288 self.exit(1)
286 else:
289 else:
287 self.log.info("Created profile dir: %r"%p.location)
290 self.log.info("Created profile dir: %r"%p.location)
288 else:
291 else:
289 self.log.fatal("Profile %r not found."%self.profile)
292 self.log.fatal("Profile %r not found."%self.profile)
290 self.exit(1)
293 self.exit(1)
291 else:
294 else:
292 self.log.info("Using existing profile dir: %r"%p.location)
295 self.log.info("Using existing profile dir: %r"%p.location)
293 else:
296 else:
294 # location is fully specified
297 # location is fully specified
295 try:
298 try:
296 p = ProfileDir.find_profile_dir(location, self.config)
299 p = ProfileDir.find_profile_dir(location, self.config)
297 except ProfileDirError:
300 except ProfileDirError:
298 # not found, maybe create it
301 # not found, maybe create it
299 if self.auto_create:
302 if self.auto_create:
300 try:
303 try:
301 p = ProfileDir.create_profile_dir(location, self.config)
304 p = ProfileDir.create_profile_dir(location, self.config)
302 except ProfileDirError:
305 except ProfileDirError:
303 self.log.fatal("Could not create profile directory: %r"%location)
306 self.log.fatal("Could not create profile directory: %r"%location)
304 self.exit(1)
307 self.exit(1)
305 else:
308 else:
306 self.log.info("Creating new profile dir: %r"%location)
309 self.log.info("Creating new profile dir: %r"%location)
307 else:
310 else:
308 self.log.fatal("Profile directory %r not found."%location)
311 self.log.fatal("Profile directory %r not found."%location)
309 self.exit(1)
312 self.exit(1)
310 else:
313 else:
311 self.log.info("Using existing profile dir: %r"%location)
314 self.log.info("Using existing profile dir: %r"%location)
312
315
313 self.profile_dir = p
316 self.profile_dir = p
314 self.config_file_paths.append(p.location)
317 self.config_file_paths.append(p.location)
315 self._in_init_profile_dir = False
318 self._in_init_profile_dir = False
316
319
317 def init_config_files(self):
320 def init_config_files(self):
318 """[optionally] copy default config files into profile dir."""
321 """[optionally] copy default config files into profile dir."""
319 # copy config files
322 # copy config files
320 path = self.builtin_profile_dir
323 path = self.builtin_profile_dir
321 if self.copy_config_files:
324 if self.copy_config_files:
322 src = self.profile
325 src = self.profile
323
326
324 cfg = self.config_file_name
327 cfg = self.config_file_name
325 if path and os.path.exists(os.path.join(path, cfg)):
328 if path and os.path.exists(os.path.join(path, cfg)):
326 self.log.warn("Staging %r from %s into %r [overwrite=%s]"%(
329 self.log.warn("Staging %r from %s into %r [overwrite=%s]"%(
327 cfg, src, self.profile_dir.location, self.overwrite)
330 cfg, src, self.profile_dir.location, self.overwrite)
328 )
331 )
329 self.profile_dir.copy_config_file(cfg, path=path, overwrite=self.overwrite)
332 self.profile_dir.copy_config_file(cfg, path=path, overwrite=self.overwrite)
330 else:
333 else:
331 self.stage_default_config_file()
334 self.stage_default_config_file()
332 else:
335 else:
333 # Still stage *bundled* config files, but not generated ones
336 # Still stage *bundled* config files, but not generated ones
334 # This is necessary for `ipython profile=sympy` to load the profile
337 # This is necessary for `ipython profile=sympy` to load the profile
335 # on the first go
338 # on the first go
336 files = glob.glob(os.path.join(path, '*.py'))
339 files = glob.glob(os.path.join(path, '*.py'))
337 for fullpath in files:
340 for fullpath in files:
338 cfg = os.path.basename(fullpath)
341 cfg = os.path.basename(fullpath)
339 if self.profile_dir.copy_config_file(cfg, path=path, overwrite=False):
342 if self.profile_dir.copy_config_file(cfg, path=path, overwrite=False):
340 # file was copied
343 # file was copied
341 self.log.warn("Staging bundled %s from %s into %r"%(
344 self.log.warn("Staging bundled %s from %s into %r"%(
342 cfg, self.profile, self.profile_dir.location)
345 cfg, self.profile, self.profile_dir.location)
343 )
346 )
344
347
345
348
346 def stage_default_config_file(self):
349 def stage_default_config_file(self):
347 """auto generate default config file, and stage it into the profile."""
350 """auto generate default config file, and stage it into the profile."""
348 s = self.generate_config_file()
351 s = self.generate_config_file()
349 fname = os.path.join(self.profile_dir.location, self.config_file_name)
352 fname = os.path.join(self.profile_dir.location, self.config_file_name)
350 if self.overwrite or not os.path.exists(fname):
353 if self.overwrite or not os.path.exists(fname):
351 self.log.warn("Generating default config file: %r"%(fname))
354 self.log.warn("Generating default config file: %r"%(fname))
352 with open(fname, 'w') as f:
355 with open(fname, 'w') as f:
353 f.write(s)
356 f.write(s)
354
357
355 @catch_config_error
358 @catch_config_error
356 def initialize(self, argv=None):
359 def initialize(self, argv=None):
357 # don't hook up crash handler before parsing command-line
360 # don't hook up crash handler before parsing command-line
358 self.parse_command_line(argv)
361 self.parse_command_line(argv)
359 self.init_crash_handler()
362 self.init_crash_handler()
360 if self.subapp is not None:
363 if self.subapp is not None:
361 # stop here if subapp is taking over
364 # stop here if subapp is taking over
362 return
365 return
363 cl_config = self.config
366 cl_config = self.config
364 self.init_profile_dir()
367 self.init_profile_dir()
365 self.init_config_files()
368 self.init_config_files()
366 self.load_config_file()
369 self.load_config_file()
367 # enforce cl-opts override configfile opts:
370 # enforce cl-opts override configfile opts:
368 self.update_config(cl_config)
371 self.update_config(cl_config)
369
372
General Comments 0
You need to be logged in to leave comments. Login now