From 6c9bffdb2dd47cd9652ff3d05077b9d61d635067 Mon Sep 17 00:00:00 2001 From: Florian Dold Date: Tue, 19 Apr 2016 22:58:42 +0200 Subject: config parsing in pure python --- talerbank/talerconfig.py | 224 ++++++++++++++++++++++++++++++++++------------- 1 file changed, 161 insertions(+), 63 deletions(-) (limited to 'talerbank/talerconfig.py') diff --git a/talerbank/talerconfig.py b/talerbank/talerconfig.py index 9b258e2..4a8d45a 100644 --- a/talerbank/talerconfig.py +++ b/talerbank/talerconfig.py @@ -1,68 +1,166 @@ -# This file is part of TALER -# (C) 2016 INRIA -# -# TALER is free software; you can redistribute it and/or modify it under the -# terms of the GNU Affero General Public License as published by the Free Software -# Foundation; either version 3, or (at your option) any later version. -# -# TALER is distributed in the hope that it will be useful, but WITHOUT ANY -# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR -# A PARTICULAR PURPOSE. See the GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License along with -# TALER; see the file COPYING. If not, If not, see -# -# @author Florian Dold - -""" -Access to GNUnet-style configuration for Taler. - -Requires the taler-config utility from the -GNU Taler exchange to be available in $PATH -""" - -import shutil -import subprocess - -# FIXME: eventually this should use a python -# parser and not shell out to taler-config - -cmd = "taler-config" +import logging +import collections +import os +logger = logging.getLogger(__name__) -class TalerConfig: - def __init__(self, filename=None): - self.filename = filename +__all__ = ["TalerConfig"] + +taler_datadir = None +try: + # not clear if this is a good idea ... + from talerpaths import taler_datadir as t + taler_datadir = t +except ImportError: + pass + + +class OptionDict(collections.defaultdict): + def __init__(self): + super().__init__() + def __missing__(self, key): + raise KeyError(key) - def _check_prog(self): - prog = shutil.which(cmd) - if prog is None: - raise Exception("taler-config command not found in $PATH") - - def _run(self, *args): - p = subprocess.Popen([cmd] + list(args), stdout=subprocess.PIPE) - (out, err) = p.communicate() - r = p.wait() - return r, out.decode('utf-8').rstrip('\n') - - def get_string(self, section, option): - self._check_prog() - args = ['-s', section, '-o', option] - if self.filename is not None: - args.append('-c') - args.append(self.filename) - r, v = self._run(*args) - if r != 0: - raise Exception("error running taler-config") - return v - def get_filename(self, section, option): - self._check_prog() - args = ['-f', '-s', section, '-o', option] - if self.filename is not None: - args.append('-c') - args.append(self.filename) - r, v = self._run(*args) - if r != 0: - raise Exception("error running taler-config") +class SectionDict(collections.defaultdict): + def __init__(self): + super().__init__() + def __missing__(self, key): + v = OptionDict() + self[key] = v return v + +class Entry: + def __init__(self, section, option, value=None, filename=None, lineno=None): + self.value = value + self.filename = filename + self.lineno = lineno + self.section = section + self.option = option + + def __repr__(self): + return "" % (self.section, self.option, repr(self.value),) + + def __str__(self): + return self.value + + def value_string(self, default=None, warn=False): + if self.value is None: + if warn: + if default is not None: + logger.warn("Configuration is missing option %s in section %s, falling back to '%s'", default) + else: + logger.warn("Configuration is missing option %s in section %s", default) + return default + return self.value + + def location(self): + if self.filename is None or self.lineno is None: + return "" + return "%s:%s" % (self.filename, self.lineno) + + +class TalerConfig: + """ + One loaded taler configuration, including base configuration + files and included files. + """ + def __init__(self): + """ + Initialize an empty configuration + """ + self.sections = SectionDict() + + @staticmethod + def from_file(filename=None, load_defaults=True): + cfg = TalerConfig() + if filename is None: + xdg = os.environ.get("XDG_CONFIG_HOME") + if xdg: + filename = os.path.join(xdg, "taler.conf") + else: + filename = os.path.expanduser("~/.config/taler.conf") + if load_defaults: + cfg.load_defaults() + cfg.load_file(filename) + return cfg + + def load_defaults(self): + base_dir = os.environ.get("TALER_BASE_CONFIG") + if base_dir: + self.load_dir(base_dir) + return + prefix = os.environ.get("TALER_PREFIX") + if prefix: + self.load_dir(os.path.join(prefix, "share/config.d")) + return + if taler_datadir: + self.load_dir(os.path.join(taler_datadir, "config.d")) + return + logger.warn("no base directory found") + + def load_dir(self, dirname): + try: + files = os.listdir(dirname) + except FileNotFoundError: + logger.warn("can't read config directory '%s'", dirname) + return + for file in files: + if not file.endswith(".conf"): + continue + self.load_file(os.path.join(dirname, file)) + + def load_file(self, filename): + sections = self.sections + with open(filename, "r") as file: + lineno = 0 + current_section = None + for line in file: + lineno += 1 + line = line.strip() + if len(line) == 0: + # empty line + continue + if line.startswith("#"): + # comment + continue + if line.startswith("["): + if not line.endswith("]"): + logger.error("invalid section header in line %s: %s", lineno, repr(line)) + section_name = line.strip("[]").strip().strip('"') + current_section = section_name + continue + if current_section is None: + logger.error("option outside of section in line %s: %s", lineno, repr(line)) + continue + kv = line.split("=", 2) + if len(kv) != 2: + logger.error("invalid option in line %s: %s", lineno, repr(line)) + key = kv[0].strip() + value = kv[1].strip() + if value.startswith('"'): + value = value[1:] + if not value.endswith('"'): + logger.error("mismatched quotes in line %s: %s", lineno, repr(line)) + else: + value = value[:-1] + e = Entry(current_section, key, value, filename, lineno) + sections[current_section][key] = e + + + def dump(self): + for section_name, section in self.sections.items(): + print("[%s]" % (section_name,)) + for option_name, option in section.items(): + print("%s = %s # %s" % (option_name, option, option.location())) + + def __getitem__(self, slice): + if isinstance(slice, str): + return self.sections[slice] + raise TypeError("index must be string") + + +if __name__ == "__main__": + import sys + tc = TalerConfig.from_file(sys.argv[1]) + tc.dump() -- cgit v1.2.3