1 # -*- coding: utf-8 -*-
2 ##############################################################################
4 # OpenERP, Open Source Management Solution
5 # Copyright (C) 2004-2009 Tiny SPRL (<http://tiny.be>).
7 # This program is free software: you can redistribute it and/or modify
8 # it under the terms of the GNU Affero General Public License as
9 # published by the Free Software Foundation, either version 3 of the
10 # License, or (at your option) any later version.
12 # This program is distributed in the hope that it will be useful,
13 # but WITHOUT ANY WARRANTY; without even the implied warranty of
14 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 # GNU Affero General Public License for more details.
17 # You should have received a copy of the GNU Affero General Public License
18 # along with this program. If not, see <http://www.gnu.org/licenses/>.
20 ##############################################################################
28 import openerp.loglevels as loglevels
30 import openerp.release as release
32 class MyOption (optparse.Option, object):
33 """ optparse Option with two additional attributes.
35 The list of command line options (getopt.Option) is used to create the
36 list of the configuration file options. When reading the file, and then
37 reading the command line arguments, we don't want optparse.parse results
38 to override the configuration file values. But if we provide default
39 values to optparse, optparse will return them and we can't know if they
40 were really provided by the user or not. A solution is to not use
41 optparse's default attribute, but use a custom one (that will be copied
42 to create the default values of the configuration file).
45 def __init__(self, *opts, **attrs):
46 self.my_default = attrs.pop('my_default', None)
47 super(MyOption, self).__init__(*opts, **attrs)
49 #.apidoc title: Server Configuration Loader
53 from OpenSSL import SSL
56 return hasattr(socket, 'ssl') and hasattr(SSL, "Connection")
60 class configmanager(object):
61 def __init__(self, fname=None):
62 # Options not exposed on the command line. Command line options will be added
63 # from optparse's parser.
65 'admin_passwd': 'admin',
66 'csv_internal_sep': ',',
67 'login_message': False,
68 'publisher_warranty_url': 'http://services.openerp.com/publisher-warranty/',
73 # Not exposed in the configuration file.
74 self.blacklist_for_save = set(
75 ['publisher_warranty_url', 'load_language', 'root_path',
76 'init', 'save', 'config', 'update', 'stop_after_init'])
78 # dictionary mapping option destination (keys in self.options) to MyOptions.
82 self.config_file = fname
83 self.has_ssl = check_ssl()
85 self._LOGLEVELS = dict([(getattr(loglevels, 'LOG_%s' % x), getattr(logging, x))
86 for x in ('CRITICAL', 'ERROR', 'WARNING', 'INFO', 'TEST', 'DEBUG', 'DEBUG_RPC', 'DEBUG_SQL', 'DEBUG_RPC_ANSWER','NOTSET')])
88 version = "%s %s" % (release.description, release.version)
89 self.parser = parser = optparse.OptionParser(version=version, option_class=MyOption)
91 # Server startup config
92 group = optparse.OptionGroup(parser, "Common options")
93 group.add_option("-c", "--config", dest="config", help="specify alternate config file")
94 group.add_option("-s", "--save", action="store_true", dest="save", default=False,
95 help="save configuration to ~/.openerp_serverrc")
96 group.add_option("-i", "--init", dest="init", help="install one or more modules (comma-separated list, use \"all\" for all modules), requires -d")
97 group.add_option("-u", "--update", dest="update",
98 help="update one or more modules (comma-separated list, use \"all\" for all modules). Requires -d.")
99 group.add_option("--without-demo", dest="without_demo",
100 help="disable loading demo data for modules to be installed (comma-separated, use \"all\" for all modules). Requires -d and -i. Default is %default",
102 group.add_option("-P", "--import-partial", dest="import_partial", my_default='',
103 help="Use this for big data importation, if it crashes you will be able to continue at the current state. Provide a filename to store intermediate importation states.")
104 group.add_option("--pidfile", dest="pidfile", help="file where the server pid will be stored")
105 group.add_option("--addons-path", dest="addons_path",
106 help="specify additional addons paths (separated by commas).",
107 action="callback", callback=self._check_addons_path, nargs=1, type="string")
108 group.add_option("--load", dest="server_wide_modules", help="Comma-separated list of server-wide modules default=web")
109 parser.add_option_group(group)
112 group = optparse.OptionGroup(parser, "XML-RPC Configuration")
113 group.add_option("--xmlrpc-interface", dest="xmlrpc_interface", my_default='',
114 help="Specify the TCP IP address for the XML-RPC protocol. The empty string binds to all interfaces.")
115 group.add_option("--xmlrpc-port", dest="xmlrpc_port", my_default=8069,
116 help="specify the TCP port for the XML-RPC protocol", type="int")
117 group.add_option("--no-xmlrpc", dest="xmlrpc", action="store_false", my_default=True,
118 help="disable the XML-RPC protocol")
119 parser.add_option_group(group)
122 title = "XML-RPC Secure Configuration"
124 title += " (disabled as ssl is unavailable)"
126 group = optparse.OptionGroup(parser, title)
127 group.add_option("--xmlrpcs-interface", dest="xmlrpcs_interface", my_default='',
128 help="Specify the TCP IP address for the XML-RPC Secure protocol. The empty string binds to all interfaces.")
129 group.add_option("--xmlrpcs-port", dest="xmlrpcs_port", my_default=8071,
130 help="specify the TCP port for the XML-RPC Secure protocol", type="int")
131 group.add_option("--no-xmlrpcs", dest="xmlrpcs", action="store_false", my_default=True,
132 help="disable the XML-RPC Secure protocol")
133 group.add_option("--cert-file", dest="secure_cert_file", my_default='server.cert',
134 help="specify the certificate file for the SSL connection")
135 group.add_option("--pkey-file", dest="secure_pkey_file", my_default='server.pkey',
136 help="specify the private key file for the SSL connection")
137 parser.add_option_group(group)
140 group = optparse.OptionGroup(parser, "NET-RPC Configuration")
141 group.add_option("--netrpc-interface", dest="netrpc_interface", my_default='',
142 help="specify the TCP IP address for the NETRPC protocol")
143 group.add_option("--netrpc-port", dest="netrpc_port", my_default=8070,
144 help="specify the TCP port for the NETRPC protocol", type="int")
145 group.add_option("--no-netrpc", dest="netrpc", action="store_false", my_default=True,
146 help="disable the NETRPC protocol")
147 parser.add_option_group(group)
150 # TODO move to web addons after MetaOption merge
151 group = optparse.OptionGroup(parser, "Web interface Configuration")
152 group.add_option("--db-filter", dest="dbfilter", default='.*',
153 help="Filter listed database", metavar="REGEXP")
154 parser.add_option_group(group)
157 group = optparse.OptionGroup(parser, "Static HTTP service")
158 group.add_option("--static-http-enable", dest="static_http_enable", action="store_true", my_default=False, help="enable static HTTP service for serving plain HTML files")
159 group.add_option("--static-http-document-root", dest="static_http_document_root", help="specify the directory containing your static HTML files (e.g '/var/www/')")
160 group.add_option("--static-http-url-prefix", dest="static_http_url_prefix", help="specify the URL root prefix where you want web browsers to access your static HTML files (e.g '/')")
161 parser.add_option_group(group)
164 group = optparse.OptionGroup(parser, "Testing Configuration")
165 group.add_option("--test-file", dest="test_file", my_default=False,
166 help="Launch a YML test file.")
167 group.add_option("--test-report-directory", dest="test_report_directory", my_default=False,
168 help="If set, will save sample of all reports in this directory.")
169 group.add_option("--test-disable", action="store_true", dest="test_disable",
170 my_default=False, help="Disable loading test files.")
171 group.add_option("--test-commit", action="store_true", dest="test_commit",
172 my_default=False, help="Commit database changes performed by tests.")
173 group.add_option("--assert-exit-level", dest='assert_exit_level', type="choice", choices=self._LOGLEVELS.keys(),
175 help="specify the level at which a failed assertion will stop the server. Accepted values: %s" % (self._LOGLEVELS.keys(),))
176 parser.add_option_group(group)
179 group = optparse.OptionGroup(parser, "Logging Configuration")
180 group.add_option("--logfile", dest="logfile", help="file where the server log will be stored")
181 group.add_option("--no-logrotate", dest="logrotate", action="store_false", my_default=True,
182 help="do not rotate the logfile")
183 group.add_option("--syslog", action="store_true", dest="syslog",
184 my_default=False, help="Send the log to the syslog server")
185 group.add_option('--log-level', dest='log_level', type='choice', choices=self._LOGLEVELS.keys(),
187 help='specify the level of the logging. Accepted values: ' + str(self._LOGLEVELS.keys()))
188 parser.add_option_group(group)
191 group = optparse.OptionGroup(parser, "SMTP Configuration")
192 group.add_option('--email-from', dest='email_from', my_default=False,
193 help='specify the SMTP email address for sending email')
194 group.add_option('--smtp', dest='smtp_server', my_default='localhost',
195 help='specify the SMTP server for sending email')
196 group.add_option('--smtp-port', dest='smtp_port', my_default=25,
197 help='specify the SMTP port', type="int")
198 group.add_option('--smtp-ssl', dest='smtp_ssl', action='store_true', my_default=False,
199 help='specify the SMTP server support SSL or not')
200 group.add_option('--smtp-user', dest='smtp_user', my_default=False,
201 help='specify the SMTP username for sending email')
202 group.add_option('--smtp-password', dest='smtp_password', my_default=False,
203 help='specify the SMTP password for sending email')
204 parser.add_option_group(group)
206 group = optparse.OptionGroup(parser, "Database related options")
207 group.add_option("-d", "--database", dest="db_name", my_default=False,
208 help="specify the database name")
209 group.add_option("-r", "--db_user", dest="db_user", my_default=False,
210 help="specify the database user name")
211 group.add_option("-w", "--db_password", dest="db_password", my_default=False,
212 help="specify the database password")
213 group.add_option("--pg_path", dest="pg_path", help="specify the pg executable path")
214 group.add_option("--db_host", dest="db_host", my_default=False,
215 help="specify the database host")
216 group.add_option("--db_port", dest="db_port", my_default=False,
217 help="specify the database port", type="int")
218 group.add_option("--db_maxconn", dest="db_maxconn", type='int', my_default=64,
219 help="specify the the maximum number of physical connections to posgresql")
220 parser.add_option_group(group)
222 group = optparse.OptionGroup(parser, "Internationalisation options",
223 "Use these options to translate OpenERP to another language."
224 "See i18n section of the user manual. Option '-d' is mandatory."
225 "Option '-l' is mandatory in case of importation"
227 group.add_option('--load-language', dest="load_language",
228 help="specifies the languages for the translations you want to be loaded")
229 group.add_option('-l', "--language", dest="language",
230 help="specify the language of the translation file. Use it with --i18n-export or --i18n-import")
231 group.add_option("--i18n-export", dest="translate_out",
232 help="export all sentences to be translated to a CSV file, a PO file or a TGZ archive and exit")
233 group.add_option("--i18n-import", dest="translate_in",
234 help="import a CSV or a PO file with translations and exit. The '-l' option is required.")
235 group.add_option("--i18n-overwrite", dest="overwrite_existing_translations", action="store_true", my_default=False,
236 help="overwrites existing translation terms on updating a module or importing a CSV or a PO file.")
237 group.add_option("--modules", dest="translate_modules",
238 help="specify modules to export. Use in combination with --i18n-export")
239 parser.add_option_group(group)
241 security = optparse.OptionGroup(parser, 'Security-related options')
242 security.add_option('--no-database-list', action="store_false", dest='list_db', my_default=True,
243 help="disable the ability to return the list of databases")
244 parser.add_option_group(security)
247 group = optparse.OptionGroup(parser, "Advanced options")
248 group.add_option("--cache-timeout", dest="cache_timeout", my_default=100000,
249 help="set the timeout for the cache system", type="int")
250 group.add_option('--debug', dest='debug_mode', action='store_true', my_default=False, help='enable debug mode')
251 group.add_option("--stop-after-init", action="store_true", dest="stop_after_init", my_default=False,
252 help="stop the server after its initialization")
253 group.add_option("-t", "--timezone", dest="timezone", my_default=False,
254 help="specify reference timezone for the server (e.g. Europe/Brussels")
255 group.add_option("--osv-memory-count-limit", dest="osv_memory_count_limit", my_default=False,
256 help="Force a limit on the maximum number of records kept in the virtual "
257 "osv_memory tables. The default is False, which means no count-based limit.",
259 group.add_option("--osv-memory-age-limit", dest="osv_memory_age_limit", my_default=1.0,
260 help="Force a limit on the maximum age of records kept in the virtual "
261 "osv_memory tables. This is a decimal value expressed in hours, "
262 "and the default is 1 hour.",
264 group.add_option("--max-cron-threads", dest="max_cron_threads", my_default=4,
265 help="Maximum number of threads processing concurrently cron jobs.",
267 group.add_option("--unaccent", dest="unaccent", my_default=False, action="store_true",
268 help="Use the unaccent function provided by the database when available.")
270 parser.add_option_group(group)
272 # Copy all optparse options (i.e. MyOption) into self.options.
273 for group in parser.option_groups:
274 for option in group.option_list:
275 self.options[option.dest] = option.my_default
276 self.casts[option.dest] = option
278 self.parse_config(None, False)
280 def parse_config(self, args=None, complete=True):
281 """ Parse the configuration file (if any) and the command-line
284 This method initializes openerp.tools.config and openerp.conf (the
285 former should be removed in the furture) with library-wide
286 configuration values.
288 This method must be called before proper usage of this library can be
291 Typical usage of this method:
293 openerp.tools.config.parse_config(sys.argv[1:])
295 :param complete: this is a hack used in __init__(), leave it to True.
300 opt, args = self.parser.parse_args(args)
304 self.parser.error(msg)
306 # Ensures no illegitimate argument is silently discarded (avoids insidious "hyphen to dash" problem)
307 die(args, "unrecognized parameters: '%s'" % " ".join(args))
309 die(bool(opt.syslog) and bool(opt.logfile),
310 "the syslog and logfile options are exclusive")
312 die(opt.translate_in and (not opt.language or not opt.db_name),
313 "the i18n-import option cannot be used without the language (-l) and the database (-d) options")
315 die(opt.overwrite_existing_translations and not (opt.translate_in or opt.update),
316 "the i18n-overwrite option cannot be used without the i18n-import option or without the update option")
318 die(opt.translate_out and (not opt.db_name),
319 "the i18n-export option cannot be used without the database (-d) option")
321 # Check if the config file exists (-c used, but not -s)
322 die(not opt.save and opt.config and not os.path.exists(opt.config),
323 "The config file '%s' selected with -c/--config doesn't exist, "\
324 "use -s/--save if you want to generate it"%(opt.config))
326 # place/search the config file on Win32 near the server installation
327 # (../etc from the server)
328 # if the server is run by an unprivileged user, he has to specify location of a config file where he has the rights to write,
329 # else he won't be able to save the configurations, or even to start the server...
331 rcfilepath = os.path.join(os.path.abspath(os.path.dirname(sys.argv[0])), 'openerp-server.conf')
333 rcfilepath = os.path.expanduser('~/.openerp_serverrc')
335 self.rcfile = os.path.abspath(
336 self.config_file or opt.config \
337 or os.environ.get('OPENERP_SERVER') or rcfilepath)
341 # Verify that we want to log or not, if not the output will go to stdout
342 if self.options['logfile'] in ('None', 'False'):
343 self.options['logfile'] = False
344 # the same for the pidfile
345 if self.options['pidfile'] in ('None', 'False'):
346 self.options['pidfile'] = False
348 keys = ['xmlrpc_interface', 'xmlrpc_port', 'db_name', 'db_user', 'db_password', 'db_host',
349 'db_port', 'logfile', 'pidfile', 'smtp_port', 'cache_timeout',
350 'email_from', 'smtp_server', 'smtp_user', 'smtp_password',
351 'netrpc_interface', 'netrpc_port', 'db_maxconn', 'import_partial', 'addons_path',
352 'netrpc', 'xmlrpc', 'syslog', 'without_demo', 'timezone',
353 'xmlrpcs_interface', 'xmlrpcs_port', 'xmlrpcs',
354 'static_http_enable', 'static_http_document_root', 'static_http_url_prefix',
355 'secure_cert_file', 'secure_pkey_file', 'dbfilter'
359 # Copy the command-line argument...
360 if getattr(opt, arg):
361 self.options[arg] = getattr(opt, arg)
362 # ... or keep, but cast, the config file value.
363 elif isinstance(self.options[arg], basestring) and self.casts[arg].type in optparse.Option.TYPE_CHECKER:
364 self.options[arg] = optparse.Option.TYPE_CHECKER[self.casts[arg].type](self.casts[arg], arg, self.options[arg])
367 'language', 'translate_out', 'translate_in', 'overwrite_existing_translations',
368 'debug_mode', 'smtp_ssl', 'load_language',
369 'stop_after_init', 'logrotate', 'without_demo', 'netrpc', 'xmlrpc', 'syslog',
370 'list_db', 'xmlrpcs',
371 'test_file', 'test_disable', 'test_commit', 'test_report_directory',
372 'osv_memory_count_limit', 'osv_memory_age_limit', 'max_cron_threads', 'unaccent',
376 # Copy the command-line argument...
377 if getattr(opt, arg) is not None:
378 self.options[arg] = getattr(opt, arg)
379 # ... or keep, but cast, the config file value.
380 elif isinstance(self.options[arg], basestring) and self.casts[arg].type in optparse.Option.TYPE_CHECKER:
381 self.options[arg] = optparse.Option.TYPE_CHECKER[self.casts[arg].type](self.casts[arg], arg, self.options[arg])
383 if opt.assert_exit_level:
384 self.options['assert_exit_level'] = self._LOGLEVELS[opt.assert_exit_level]
386 self.options['assert_exit_level'] = self._LOGLEVELS.get(self.options['assert_exit_level']) or int(self.options['assert_exit_level'])
389 self.options['log_level'] = self._LOGLEVELS[opt.log_level]
391 self.options['log_level'] = self._LOGLEVELS.get(self.options['log_level']) or int(self.options['log_level'])
393 self.options['root_path'] = os.path.abspath(os.path.expanduser(os.path.expandvars(os.path.dirname(openerp.__file__))))
394 if not self.options['addons_path'] or self.options['addons_path']=='None':
395 self.options['addons_path'] = os.path.join(self.options['root_path'], 'addons')
397 self.options['addons_path'] = ",".join(
398 os.path.abspath(os.path.expanduser(os.path.expandvars(x)))
399 for x in self.options['addons_path'].split(','))
401 self.options['init'] = opt.init and dict.fromkeys(opt.init.split(','), 1) or {}
402 self.options["demo"] = not opt.without_demo and self.options['init'] or {}
403 self.options['update'] = opt.update and dict.fromkeys(opt.update.split(','), 1) or {}
404 self.options['translate_modules'] = opt.translate_modules and map(lambda m: m.strip(), opt.translate_modules.split(',')) or ['all']
405 self.options['translate_modules'].sort()
407 # TODO checking the type of the parameters should be done for every
408 # parameters, not just the timezone.
409 # The call to get_server_timezone() sets the timezone; this should
410 # probably done here.
411 if self.options['timezone']:
412 # Prevent the timezone to be True. (The config file parsing changes
413 # the string 'True' to the boolean value True. It would be probably
414 # be better to remove that conversion.)
415 die(not isinstance(self.options['timezone'], basestring),
416 "Invalid timezone value in configuration or environment: %r.\n"
417 "Please fix this in your configuration." %(self.options['timezone']))
419 # If an explicit TZ was provided in the config, make sure it is known
422 pytz.timezone(self.options['timezone'])
423 except pytz.UnknownTimeZoneError:
424 die(True, "The specified timezone (%s) is invalid" % self.options['timezone'])
426 # If pytz is missing, don't check the provided TZ, it will be ignored anyway.
430 self.options['pg_path'] = opt.pg_path
432 if self.options.get('language', False):
433 if len(self.options['language']) > 5:
434 raise Exception('ERROR: The Lang name must take max 5 chars, Eg: -lfr_BE')
436 if not self.options['db_user']:
439 self.options['db_user'] = getpass.getuser()
441 self.options['db_user'] = None
443 die(not self.options['db_user'], 'ERROR: No user specified for the connection to the database')
445 if self.options['db_password']:
446 if sys.platform == 'win32' and not self.options['db_host']:
447 self.options['db_host'] = 'localhost'
448 #if self.options['db_host']:
449 # self._generate_pgpassfile()
454 openerp.conf.max_cron_threads = self.options['max_cron_threads']
456 openerp.conf.addons_paths = self.options['addons_path'].split(',')
457 if opt.server_wide_modules:
458 openerp.conf.server_wide_modules = map(lambda m: m.strip(), opt.server_wide_modules.split(','))
460 openerp.conf.server_wide_modules = ['web']
462 openerp.modules.module.initialize_sys_path()
463 openerp.modules.loading.open_openerp_namespace()
464 # openerp.addons.__path__.extend(openerp.conf.addons_paths) # This
465 # is not compatible with initialize_sys_path(): import crm and
466 # import openerp.addons.crm load twice the module.
468 def _generate_pgpassfile(self):
470 Generate the pgpass file with the parameters from the command line (db_host, db_user,
473 Used because pg_dump and pg_restore can not accept the password on the command line.
475 is_win32 = sys.platform == 'win32'
477 filename = os.path.join(os.environ['APPDATA'], 'pgpass.conf')
479 filename = os.path.join(os.environ['HOME'], '.pgpass')
481 text_to_add = "%(db_host)s:*:*:%(db_user)s:%(db_password)s" % self.options
483 if os.path.exists(filename):
484 content = [x.strip() for x in file(filename, 'r').readlines()]
485 if text_to_add in content:
488 fp = file(filename, 'a+')
489 fp.write(text_to_add + "\n")
497 x=_winreg.ConnectRegistry(None,_winreg.HKEY_LOCAL_MACHINE)
498 y = _winreg.OpenKey(x, r"SYSTEM\CurrentControlSet\Control\Session Manager\Environment", 0,_winreg.KEY_ALL_ACCESS)
499 _winreg.SetValueEx(y,"PGPASSFILE", 0, _winreg.REG_EXPAND_SZ, filename )
504 os.chmod(filename, stat.S_IRUSR + stat.S_IWUSR)
506 def _is_addons_path(self, path):
507 for f in os.listdir(path):
508 modpath = os.path.join(path, f)
509 if os.path.isdir(modpath):
510 def hasfile(filename):
511 return os.path.isfile(os.path.join(modpath, filename))
512 if hasfile('__init__.py') and (hasfile('__openerp__.py') or hasfile('__terp__.py')):
516 def _check_addons_path(self, option, opt, value, parser):
518 for path in value.split(','):
520 res = os.path.abspath(os.path.expanduser(path))
521 if not os.path.isdir(res):
522 raise optparse.OptionValueError("option %s: no such directory: %r" % (opt, path))
523 if not self._is_addons_path(res):
524 raise optparse.OptionValueError("option %s: The addons-path %r does not seem to a be a valid Addons Directory!" % (opt, path))
527 setattr(parser.values, option.dest, ",".join(ad_paths))
530 p = ConfigParser.ConfigParser()
532 p.read([self.rcfile])
533 for (name,value) in p.items('options'):
534 if value=='True' or value=='true':
536 if value=='False' or value=='false':
538 self.options[name] = value
539 #parse the other sections, as well
540 for sec in p.sections():
543 if not self.misc.has_key(sec):
545 for (name, value) in p.items(sec):
546 if value=='True' or value=='true':
548 if value=='False' or value=='false':
550 self.misc[sec][name] = value
553 except ConfigParser.NoSectionError:
557 p = ConfigParser.ConfigParser()
558 loglevelnames = dict(zip(self._LOGLEVELS.values(), self._LOGLEVELS.keys()))
559 p.add_section('options')
560 for opt in sorted(self.options.keys()):
561 if opt in ('version', 'language', 'translate_out', 'translate_in', 'overwrite_existing_translations', 'init', 'update'):
563 if opt in self.blacklist_for_save:
565 if opt in ('log_level', 'assert_exit_level'):
566 p.set('options', opt, loglevelnames.get(self.options[opt], self.options[opt]))
568 p.set('options', opt, self.options[opt])
570 for sec in sorted(self.misc.keys()):
571 for opt in sorted(self.misc[sec].keys()):
572 p.set(sec,opt,self.misc[sec][opt])
574 # try to create the directories and write the file
576 rc_exists = os.path.exists(self.rcfile)
577 if not rc_exists and not os.path.exists(os.path.dirname(self.rcfile)):
578 os.makedirs(os.path.dirname(self.rcfile))
580 p.write(file(self.rcfile, 'w'))
582 os.chmod(self.rcfile, 0600)
584 sys.stderr.write("ERROR: couldn't write the config file\n")
587 # what to do if impossible?
588 sys.stderr.write("ERROR: couldn't create the config directory\n")
590 def get(self, key, default=None):
591 return self.options.get(key, default)
593 def get_misc(self, sect, key, default=None):
594 return self.misc.get(sect,{}).get(key, default)
596 def __setitem__(self, key, value):
597 self.options[key] = value
599 def __getitem__(self, key):
600 return self.options[key]
602 config = configmanager()
605 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: