""" Sub-module :mod:`Config` ======================== Configuration management for ape. Provides the tools to read the information from the ``ape.rc`` files. The format of the ``.rc`` files follows the INI-style: .. code-block:: ini [section] var = value """ import os import os.path import sys import ConfigParser import glob import re import shlex ## Directory where the ape script and packages live. apeDir = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) if not os.path.exists(os.path.join(apeDir, "config", "ape.rc")): raise RuntimeError("Could not determine default configuration.") def addOptions(optParser): """Set command line options processed by the configuration management. The main script is expected to call this function to create all the options which the configuration management expects. """ optParser.add_option("-d", "--debug", action="store_true", default=False, help="Print debugging info into stdout") optParser.add_option("-v", "--verbose", action="store_true", default=False, help="Print more stuff into stdout") optParser.add_option("-n", "--dry-run", action="store_true", dest="dryRun", default=False, help="Do not install anything. Report what would " "be installed") optParser.add_option("-k", "--keep", action="store_true", dest="keep", help="Do not remove build directories after " "successful install") optParser.add_option("--no-keep", action="store_false", dest="keep", help="Remove build directories after " "successful install") optParser.add_option("-j", "--jobs", help="Set number of jobs for parallel build " "(default: %default)") optParser.add_option("-m", "--mirrors", help="Specify ordered list preferred of mirrors") optParser.add_option("--rc", help="Additional ape configuration file") optParser.add_option("-b", "--base", help="Base directory to install into") optParser.add_option("-o", "--option", dest="configVariables", action="append", default=[], help="Set configuration variables") ## Variable to hold the global ConfigParser.SafeConfigParser object. config = None ## Regular expression to split command-line arguments setting variables optionRe = re.compile(r"(\S*?)\.(\S*)\s*=\s*(.*)$") def init(options=None): r"""Prepare the configuration managment by reading the ``.rc`` files. Information from the command line can be passed through the *options* parameter. It will be propagated in to the right variables in the ``[ape]`` and ``[DEFAULT]`` sections. This functions processes first all the files matching :file:`config/*.rc` in the ape distribution and and platform specific configurations :file:`config/ape.rc.{\`sys.platform\`}` and :file:`config/ape.rc.{\`sys.platform\`}.{\`os.uname()[4]\`}`. The later allows for special settings for 32bit and 64bit platforms. It then looks for user specified files: 1. If the environment variable :envvar:`APERC` is set, the file it points to. 2. :file:`~/.aperc`. 3. The file specified on the commandline using the :option:`--rc` option .. note:: Only the first and second case should be used in production settings. The other two options are mainly useful for testing new configurations. :param options: An options object returned by an options parsers. """ global config, apeDir try: rcfiles = [os.environ["APERC"]] except KeyError: rcfiles = [] rcfiles.append(os.path.expanduser("~/.aperc")) if options and options.rc: rcfiles.append(options.rc) config = ConfigParser.SafeConfigParser({"base": apeDir, "apedir": apeDir, "home": os.environ["HOME"], "user": os.environ["USER"], }) configFiles = glob.glob(os.path.join(apeDir, "config", "*.rc")) + \ [os.path.join(apeDir, "config", "ape.rc." + sys.platform)] architectureCfg = os.path.join(apeDir, "config", "ape.rc.%s.%s" % (sys.platform, os.uname()[4])) if os.path.exists(architectureCfg): configFiles.append(architectureCfg) for cfg_file in configFiles: if options and options.debug: print >>sys.stderr, "$$$ System configuration file:", cfg_file f = open(cfg_file) config.readfp(f) f.close() read = config.read(rcfiles) if options and options.debug: print >>sys.stderr, "$$$ Configrations files read:", read if options: for var in options.configVariables: match = optionRe.match(var) if not match: raise RuntimeError("Wrong configuration option format '%s'" % var) section = match.group(1) name = match.group(2) value = match.group(3) config.set(section, name, value) for opt in "verbose debug dryRun".split(): config.set("ape", opt, str(getattr(options, opt))) if options.jobs: config.set("ape", "jobs", options.jobs) if options.mirrors: config.set("ape", "mirrors", options.mirrors.replace(":", " ")) if options.base: config.set("DEFAULT", "base", os.path.abspath(options.base)) if options.keep is not None: config.set("ape", "keep", str(options.keep)) for var in variables("ape"): if var.startswith("env."): name = var[4:].upper() os.environ[name] = get("ape", var) def get(section, tag, fallbacks=[]): """Retrieve the variable `tag` from `section`. The `fallbacks` are provided to reduce the clutter in the ``[DEFAULT]`` section and avoids the contamination of individual sections with irrelevant variables from the ``[DEFAULT]`` section. The :param string section: Config section for variable lookup :param string tag: Name of variable to look up :param list fallbacks: List of sections to look up variables not found in `section` :rtype: string """ for fallback in fallbacks: for name, value in config.items(fallback, raw=True): if not config.has_option(section, name): config.set(section, name, value) return config.get(section, tag, raw=False) def getint(section, tag, fallbacks=[]): """Retrieve the variable *tag* from *section*. Converts the result to integer. :raise: :exc:`ValueError` if the value cannot be converted. :rtype: integer """ return int(get(section, tag, fallbacks)) def getboolean(section, tag, fallbacks=[]): """Retrieve the variable *tag* from *section*. The values ``on``, ``true``, ``1``, and ``yes`` are converted to ``True``. The values ``off``, ``false``, ``0``, and ``no`` are converted to ``False``. :raise: :exc:`ValueError` if the value cannot be converted. :rtype: boolean """ value = get(section, tag, fallbacks).lower() if value in ["on", "true", "1", "yes"]: return True if value in ["off", "false", "0", "no"]: return False raise ValueError("Cannot convert '%s%' to boolean value." % value) def _getExtra(section, tag, fallbacks, extra): try: return shlex.split(get(section, "%s.%s" % (tag, extra), fallbacks)) except ConfigParser.NoOptionError: return [] def getlist(section, tag, fallbacks=[]): """Retrieve the variable *tag* from *section*. Converts the result to a list of strings. The variable ```tag`.delete`` is used to specify values which should be removed from the list. The variables ```tag`.prepend`` and ```tag`.append`` specify items which are prepended and appended to the list. .. note:: The ``.delete``, ``.prepend``, and ``.append`` variables are reserved for customization of the configuration by the user. They should **not** be used in system configurations provided with ``ape``. :rtype: list of strings """ delete = _getExtra(section, tag, fallbacks, "delete") lst = [v for v in shlex.split(get(section, tag, fallbacks)) if v not in delete] return _getExtra(section, tag, fallbacks, "prepend") + \ lst + \ _getExtra(section, tag, fallbacks, "append") def sections(): """Returns the list of known sections in the configuration files. """ return config.sections() def variables(section): """Returns the list of variables in the section *section*. .. note:: This will include *all* the variables defined in the ``[DEFAULT]`` section as well as the variables defined in *section*. """ return config.options(section)