"""Option parsing library for utilities.""" import copy import getopt import inspect import os import re import shlex import sys import grp import pwd import Bcfg2.Client.Tools from Bcfg2.Compat import ConfigParser from Bcfg2.version import __version__ class OptionFailure(Exception): """ raised when malformed Option objects are instantiated """ pass DEFAULT_CONFIG_LOCATION = '/etc/bcfg2.conf' DEFAULT_INSTALL_PREFIX = '/usr' class DefaultConfigParser(ConfigParser.ConfigParser): """ A config parser that can be used to query options with default values in the event that the option is not found """ def __init__(self, *args, **kwargs): """Make configuration options case sensitive""" ConfigParser.ConfigParser.__init__(self, *args, **kwargs) self.optionxform = str def get(self, section, option, **kwargs): """ convenience method for getting config items """ default = None if 'default' in kwargs: default = kwargs['default'] del kwargs['default'] try: return ConfigParser.ConfigParser.get(self, section, option, **kwargs) except (ConfigParser.NoSectionError, ConfigParser.NoOptionError): if default is not None: return default else: raise def getboolean(self, section, option, **kwargs): """ convenience method for getting boolean config items """ default = None if 'default' in kwargs: default = kwargs['default'] del kwargs['default'] try: return ConfigParser.ConfigParser.getboolean(self, section, option, **kwargs) except (ConfigParser.NoSectionError, ConfigParser.NoOptionError, ValueError): if default is not None: return default else: raise class Option(object): """ a single option, which might be read from the command line, environment, or config file """ # pylint: disable=C0103,R0913 def __init__(self, desc, default, cmd=None, odesc=False, env=False, cf=False, cook=False, long_arg=False, deprecated_cf=None): self.desc = desc self.default = default self.cmd = cmd self.long = long_arg if not self.long: if cmd and (cmd[0] != '-' or len(cmd) != 2): raise OptionFailure("Poorly formed command %s" % cmd) elif cmd and not cmd.startswith('--'): raise OptionFailure("Poorly formed command %s" % cmd) self.odesc = odesc self.env = env self.cf = cf self.deprecated_cf = deprecated_cf self.boolean = False if not odesc and not cook and isinstance(self.default, bool): self.boolean = True self.cook = cook self.value = None # pylint: enable=C0103,R0913 def get_cooked_value(self, value): """ get the value of this option after performing any option munging specified in the 'cook' keyword argument to the constructor """ if self.boolean: return True if self.cook: return self.cook(value) else: return value def __str__(self): rv = ["%s: " % self.__class__.__name__, self.desc] if self.cmd or self.cf: rv.append(" (") if self.cmd: if self.odesc: if self.long: rv.append("%s=%s" % (self.cmd, self.odesc)) else: rv.append("%s %s" % (self.cmd, self.odesc)) else: rv.append("%s" % self.cmd) if self.cf: if self.cmd: rv.append("; ") rv.append("[%s].%s" % self.cf) if self.cmd or self.cf: rv.append(")") if hasattr(self, "value"): rv.append(": %s" % self.value) return "".join(rv) def buildHelpMessage(self): """ build the help message for this option """ vals = [] if not self.cmd: return '' if self.odesc: if self.long: vals.append("%s=%s" % (self.cmd, self.odesc)) else: vals.append("%s %s" % (self.cmd, self.odesc)) else: vals.append(self.cmd) vals.append(self.desc) return " %-28s %s\n" % tuple(vals) def buildGetopt(self): """ build a string suitable for describing this short option to getopt """ gstr = '' if self.long: return gstr if self.cmd: gstr = self.cmd[1] if self.odesc: gstr += ':' return gstr def buildLongGetopt(self): """ build a string suitable for describing this long option to getopt """ if self.odesc: return self.cmd[2:] + '=' else: return self.cmd[2:] def parse(self, opts, rawopts, configparser=None): """ parse a single option. try parsing the data out of opts (the results of getopt), rawopts (the raw option string), the environment, and finally the config parser. either opts or rawopts should be provided, but not both """ if self.cmd and opts: # Processing getopted data optinfo = [opt[1] for opt in opts if opt[0] == self.cmd] if optinfo: if optinfo[0]: self.value = self.get_cooked_value(optinfo[0]) else: self.value = True return if self.cmd and self.cmd in rawopts: if self.odesc: data = rawopts[rawopts.index(self.cmd) + 1] else: data = True self.value = self.get_cooked_value(data) return # No command line option found if self.env and self.env in os.environ: self.value = self.get_cooked_value(os.environ[self.env]) return if self.cf and configparser: try: self.value = self.get_cooked_value(configparser.get(*self.cf)) return except (ConfigParser.NoSectionError, ConfigParser.NoOptionError): pass if self.deprecated_cf: try: self.value = self.get_cooked_value( configparser.get(*self.deprecated_cf)) print("Warning: [%s] %s is deprecated, use [%s] %s instead" % (self.deprecated_cf[0], self.deprecated_cf[1], self.cf[0], self.cf[1])) return except (ConfigParser.NoSectionError, ConfigParser.NoOptionError): pass # Default value not cooked self.value = self.default class OptionSet(dict): """ a set of Option objects that interfaces with getopt and DefaultConfigParser to populate a dict of