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
42 #import dummy_threading as threading
45 class Fail(Exception):
46 def __init__(self, msg):
50 return self.msg + "\n"
52 class Options(UserDict.DictMixin):
53 """Class: mp3togo.conf.Options
54 The Options class obtains configuration information and
55 makes it available to the running program in a dictionary
58 def __init__(self, config_file):
60 self._mutex = threading.Lock()
62 # The name of the program
66 # The option dictionary
69 # Add program options here
70 # ((name, short option, long option, default value, hook, help text), ...)
72 ('conffile', 'c', 'config', config_file, self._conffile,
73 '''The location of the config file. (If it exists.)'''),
74 ('saveconffile', 's', 'save-config', False, self._saveconffile,
75 '''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.'''),
76 ('noconffile', '', 'no-config-file', False, self._noconffile,
77 '''Disable reading from and writing to the config file.'''),
78 ('verbosity', 'v', 'verbose', 1, self._verbosity,
79 '''Set the verbosity level. A value of 0 will produce no output (except option parsing errors).'''),
80 ('help', 'h', 'help', False, self._help,
81 '''Print this message.'''),
82 ('arg_files', '', '', [], self._arg_files, '')]
84 # Binaries to check for:
87 self._help_preamble = ""
89 # Don't save these options to the config file:
90 self._nosave_options = ['saveconffile', 'conffile', 'arg_files', 'help']
100 return self._mutex.locked()
102 def getconf(self, argv=None, conffile=None, readconf=True):
105 #Do any pre configuration chores
111 self._readconf = readconf
116 self[i[0]] = i[3] # hooks run
119 #Read comand line options
120 olist, loose = {}, []
122 self.name = os.path.basename(argv[0])
129 if i[3] is not False:
132 if i[3] is not False:
133 lopts.append(i[2] + '=')
137 olist, loose = getopt.getopt(argv, sopts, lopts)
138 except getopt.GetoptError:
140 raise Fail, "Bad arguments."
141 if '--no-config-file' in olist:
142 self._readconf = False
146 self._d['conffile'] = conffile
147 oldconf = self._d['conffile']
149 self.load_config_file()
152 #Sort out loose files from the command line
156 absf = os.path.expanduser(f)
157 absf = os.path.abspath(absf)
158 if os.path.exists(absf):
160 self._d['arg_files'] = files
163 self._unlock() # may execute a hook function
165 for opt, val in olist:
168 for line in self._set:
169 if opt == line[1] or opt == line[2]:
170 opttype = type(line[3])
172 if type(val) == opttype:
174 elif opttype == type(1):
175 self[line[0]] = int(val)
176 elif opttype == type(1.0):
177 self[line[0]] = float(val)
178 elif opttype == type(False):
181 raise Fail, "Expecting Numeric argument!"
184 #Offer help, if asked:
190 #Save the conf file and exit if so instructed:
192 if self.opt('saveconffile'):
193 self.log(1, "Saving config to '%s'" % self._d['conffile'])
194 self.save_config_file()
195 self.log(1, "Exiting.")
198 #Read in new config file if a new one has been specified:
200 if self.opt('conffile') != oldconf:
201 self.load_config_file()
205 search = os.environ['PATH'].split(':')
206 for bin in self.bin.keys():
207 if not self.bin[bin]:
209 if os.path.exists(os.path.join(spot, bin)):
210 self.bin[bin] = os.path.join(spot, bin)
213 self.log(3, "%s binary found at %s" % (bin, self.bin[bin]))
215 self.log(2, "%s binary not found" % bin)
217 #Do any post configuration chores
221 def save_config_file(self):
222 if self.opt('conffile') and self._readconf:
225 fp = file(self._d['conffile'], 'w')
226 for key in self._d.keys():
227 if key in self._nosave_options:
229 fp.write("%s=%s\n" % (key, self._d[key]))
232 self.log(1, "Failed to save config file")
235 def load_config_file(self):
236 """Load the config file specified by the 'conffile' option, if it exists."""
237 # All state access through __getitem__ and __setitem__
238 # Don't lock, except to directly manipulate state
239 if self.opt('conffile') and \
240 os.path.exists(os.path.expanduser(self['conffile'])) and self._readconf:
241 filename = os.path.expanduser(self['conffile'])
243 fp = file(filename, 'r')
244 lines = fp.readlines()
249 line = line.split('=', 1)
251 conf[line[0].lower()] = line[1].strip()
252 for key in conf.keys():
253 if self.has_key(key):
254 if type(self[key]) == type(conf[key]):
255 self[key] = conf[key]
256 elif type(self[key]) == type(1):
257 self[key] = int(conf[key])
258 elif type(self[key]) == type(1L):
259 self[key] = long(conf[key])
260 elif type(self[key]) == type(1.0):
261 self[key] = float(conf[key])
262 elif type(self[key]) == type(True):
263 if conf[key].lower() in ('no', 'off', 'false', 'disable', '0') or\
269 self.log(2, "Unknown entry in config file: %s" % key)
271 self.log(1, "Error reading config file")
275 if self._d.has_key(name):
282 def __getitem__(self, name):
284 if self._d.has_key(name):
291 def __setitem__(self, name, value):
292 """Set value if key exists and type is correct."""
295 if type(i[3]) != type(value):
305 self._d[name] = value
316 def __delitem__(self, item):
317 """conf items shouldn't be deleted. Object methods shouldn't
318 raise an exception when called. Item deletion will fail fairly
320 self.log(4, "Warning, attempt to delete conf item was foiled.")
322 def log(self, level, msg):
323 """Print a log message. Level must be at least 1. If the level
324 is below the current verbosity level, nothing happens."""
327 raise Fail, "log() called with insufficiently high level."
328 if level <= self._d['verbosity']:
329 print >>sys.stderr, msg
332 """Returns a pretty printed usage message with the available
333 command line options in a string."""
334 msg = "%s version %s\n" % (self.name, self.version)
335 msg += self._help_preamble
336 msg += "\nOptions:\n"
337 for option in self._set:
338 if option[1] or option[2]:
339 if option[3] is not False:
341 line = " -%s %s" % (option[1], option[3])
345 line += " --%s %s" % (option[2], option[3])
348 line = " -%s" % option[1]
351 if option[1] and option[2]:
354 line += " --%s" % option[2]
358 desc = option[5].replace('\n', ' ').split()
362 if len(line) + len(desc[0]) > 60:
367 line += " " + desc[0]
376 """Like usage, but formatted as a docbook sgml fragment for inclusion
377 in a Debian man page.""" # Cheating!
378 msg = "\n<variablelist>\n"
379 for option in self._set:
380 if option[1] or option[2]:
381 msg += " <varlistentry>\n"
384 msg += " <option>-%s</option>\n" % option[1]
386 msg += " <option>--%s</option>\n" % option[2]
388 msg += " <listitem>\n"
390 desc = option[5].replace('\n', ' ').split()
394 if len(line) + len(desc[0]) > 70:
397 line += " " + desc[0]
404 msg += " </listitem>\n"
405 msg += " </varlistentry>\n"
406 msg += "</variablelist>\n\n"
413 # These are stubs to be overridden in subclasses:
414 def _post_hook(self):
420 def _arg_files(self, value):
421 self._d['arg_files'] = value
423 def _conffile(self, value):
424 self._d['conffile'] = value
426 def _saveconffile(self, value):
427 self._d['saveconffile'] = value
429 def _noconffile(self, value):
430 self._d['noconffile'] = value
432 def _verbosity(self, value):
433 self._d['verbosity'] = value
435 def _help(self, value):
436 self._d['help'] = value