2 # This file is part of mp3togo
4 # Manage command line and config file program options
6 # (c) Simeon Veldstra 2006 <reallifesim@gmail.com>
8 # This software is free.
10 # This program is distributed in the hope that it will be useful,
11 # but WITHOUT ANY WARRANTY; without even the implied warranty of
12 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 # GNU General Public License for more details.
15 # You may redistribute this program under the terms of the
16 # GNU General Public Licence version 2
17 # Available in this package or at http://www.fsf.org
19 # requires python-pyvorbis and python-id3 and lame
22 """Configuration manager for command line programs.
24 Instantiate Options with argv as an argument and command
25 line options will be read in. With no argv a default set
26 of options will be initialized. If ~/.mp3togo exists, it
27 will be read in before command line options are
28 processed. The command line takes precedence over the
29 config file unless a config file is specified on the
30 command line or through the conffile argument to the
31 constructor. In this case, the file is processed last
32 and overrides any command line options. To totaly
33 disable reading in config files, set readconf=False in
44 class Fail(Exception):
45 def __init__(self, msg):
49 return self.msg + "\n"
51 class Options(UserDict.DictMixin):
52 """Class: mp3togo.conf.Options
53 The Options class obtains configuration information and
54 makes it available to the running program in a dictionary
57 def __init__(self, config_file):
59 self._mutex = threading.Lock()
61 # The name of the program
68 # The option dictionary
71 # Add program options here
72 # ((name, short option, long option, default value, hook, help text), ...)
74 ('conffile', 'c', 'config', config_file, self._conffile,
75 '''The location of the config file. (If it exists.)'''),
76 ('saveconffile', 's', 'save-config', False, self._saveconffile,
77 '''Write out a new config file with the options given and exit. To save the configuration to a different file, set --config to the desired filename. The file, if it exists, will be overwritten.'''),
78 ('noconffile', '', 'no-config-file', False, self._noconffile,
79 '''Disable reading from and writing to the config file.'''),
80 ('verbosity', 'v', 'verbose', 2, self._verbosity,
81 '''Set the verbosity level. A value of 0 will produce no output (except option parsing errors).'''),
82 ('help', 'h', 'help', False, self._help,
83 '''Print this message.'''),
84 ('arg_files', '', '', [], self._arg_files, '')]
86 # Binaries to check for:
89 # Third party modules to check for:
92 self._help_preamble = ""
94 # Don't save these options to the config file:
95 self._nosave_options = ['saveconffile', 'conffile', 'arg_files', 'help']
102 self._mutex.release()
105 return self._mutex.locked()
107 def getconf(self, argv=None, conffile=None, readconf=True):
110 #Do any pre configuration chores
116 self._readconf = readconf
121 self[i[0]] = i[3] # hooks run
124 #Read comand line options
125 olist, loose = {}, []
127 self.name = os.path.basename(argv[0])
134 if i[3] is not False:
137 if i[3] is not False:
138 lopts.append(i[2] + '=')
142 olist, loose = getopt.getopt(argv, sopts, lopts)
143 except getopt.GetoptError:
145 raise Fail, "Bad arguments."
146 if '--no-config-file' in olist:
147 self._readconf = False
151 self._d['conffile'] = conffile
152 oldconf = self._d['conffile']
154 self.load_config_file()
157 #Sort out loose files from the command line
161 absf = os.path.expanduser(f)
162 absf = os.path.abspath(absf)
163 if os.path.exists(absf):
165 self._d['arg_files'] = files
168 self._unlock() # may execute a hook function
170 for opt, val in olist:
173 for line in self._set:
174 if opt == line[1] or opt == line[2]:
175 opttype = type(line[3])
177 if type(val) == opttype:
179 elif opttype == type(1):
180 self[line[0]] = int(val)
181 elif opttype == type(1.0):
182 self[line[0]] = float(val)
183 elif opttype == type(False):
186 raise Fail, "Expecting Numeric argument!"
189 #Offer help, if asked:
195 #Save the conf file and exit if so instructed:
197 if self.opt('saveconffile'):
198 self.log(1, "Saving config to '%s'" % self._d['conffile'])
199 self.save_config_file()
200 self.log(1, "Exiting.")
203 #Read in new config file if a new one has been specified:
205 if self.opt('conffile') != oldconf:
206 self.load_config_file()
210 search = os.environ['PATH'].split(':')
211 for bin in self.bin.keys():
212 if not self.bin[bin]:
214 if os.path.exists(os.path.join(spot, bin)):
215 self.bin[bin] = os.path.join(spot, bin)
218 self.log(4, "%s binary found at %s" % (bin, self.bin[bin]))
220 self.log(2, "%s binary not found" % bin)
222 #Check for third party modules:
223 for name in self.mod.keys():
226 __import__(name, [], [], [name.split('.')[1]])
227 self.mod[name] = True
231 msg = "Third party module: " + name
232 msg += "is not available. Doing without"
233 self.log(2, "Third party module: %s is not available. Doing without" % name)
234 self.mod[name] = False
236 self.log(4, "Third party module: %s found" % name)
237 self.mod[name] = True
239 #Do any post configuration chores
243 def save_config_file(self):
244 if self.opt('conffile') and self._readconf:
247 fn = os.path.expanduser(self._d['conffile'])
249 for key in self._d.keys():
250 if key in self._nosave_options:
252 fp.write("%s=%s\n" % (key, self._d[key]))
255 self.log(1, "Failed to save config file")
258 def load_config_file(self):
259 """Load the config file specified by the 'conffile' option, if it exists."""
260 # All state access through __getitem__ and __setitem__
261 # Don't lock, except to directly manipulate state
262 if self.opt('conffile') and \
263 os.path.exists(os.path.expanduser(self['conffile'])) and self._readconf:
264 filename = os.path.expanduser(self['conffile'])
266 fp = file(filename, 'r')
267 lines = fp.readlines()
272 line = line.split('=', 1)
274 conf[line[0].lower()] = line[1].strip()
275 for key in conf.keys():
276 if self.has_key(key):
277 if type(self[key]) == type(conf[key]):
278 self[key] = conf[key]
279 elif type(self[key]) == type(1):
280 self[key] = int(conf[key])
281 elif type(self[key]) == type(1L):
282 self[key] = long(conf[key])
283 elif type(self[key]) == type(1.0):
284 self[key] = float(conf[key])
285 elif type(self[key]) == type(True):
286 if conf[key].lower() in ('no', 'off', 'false', 'disable', '0') or\
292 self.log(2, "Unknown entry in config file: %s" % key)
294 self.log(1, "Error reading config file")
298 if self._d.has_key(name):
305 def __getitem__(self, name):
307 if self._d.has_key(name):
314 def __setitem__(self, name, value):
315 """Set value if key exists and type is correct."""
318 if type(i[3]) != type(value):
323 apply(i[4], [name, value])
328 self._d[name] = value
339 def __delitem__(self, item):
340 """conf items shouldn't be deleted. Object methods shouldn't
341 raise an exception when called. Item deletion will fail fairly
343 self.log(4, "Warning, attempt to delete conf item was foiled.")
345 def log(self, level, msg):
346 """Print a log message. Level must be at least 1. If the level
347 is below the current verbosity level, nothing happens."""
348 self._log.append((level, msg))
351 raise Fail, "log() called with insufficiently high level."
352 if level <= self._d['verbosity']:
354 print >>sys.stderr, msg
359 """Returns a pretty printed usage message with the available
360 command line options in a string."""
361 msg = "%s version %s\n" % (self.name, self.version)
362 msg += self._help_preamble
363 msg += "\nOptions:\n"
364 for option in self._set:
365 if option[1] or option[2]:
366 if option[3] is not False:
368 line = " -%s %s" % (option[1], option[3])
372 line += " --%s %s" % (option[2], option[3])
375 line = " -%s" % option[1]
378 if option[1] and option[2]:
381 line += " --%s" % option[2]
385 desc = option[5].replace('<br>', ' <br> ')
386 desc = desc.replace('<li>', ' <li> ')
387 desc = desc.replace('\n', ' ').split()
390 if desc[0] == '<li>':
393 if desc[0] == '<br>':
396 if len(line) + len(desc[0]) > 60 or desc[0] in ('<br>', '<li>'):
401 line += " " + desc[0]
410 """Like usage, but formatted as a docbook sgml fragment for inclusion
411 in a Debian man page.""" # Cheating!
412 msg = "\n<variablelist>\n"
413 for option in self._set:
414 if option[1] or option[2]:
415 msg += " <varlistentry>\n"
418 msg += " <option>-%s</option>\n" % option[1]
420 msg += " <option>--%s</option>\n" % option[2]
422 msg += " <listitem>\n"
424 desc = option[5].replace('\n', ' ').split()
428 if len(line) + len(desc[0]) > 70:
431 line += " " + desc[0]
438 msg += " </listitem>\n"
439 msg += " </varlistentry>\n"
440 msg += "</variablelist>\n\n"
447 # These are callback stubs to be overridden in subclasses:
448 def _post_hook(self):
454 def _arg_files(self, name, value):
455 self._d['arg_files'] = value
457 def _conffile(self, name, value):
458 self._d['conffile'] = value
460 def _saveconffile(self, name, value):
461 self._d['saveconffile'] = value
463 def _noconffile(self, name, value):
464 self._d['noconffile'] = value
466 def _verbosity(self, name, value):
467 self._d['verbosity'] = value
469 def _help(self, name, value):
470 self._d['help'] = value