diff options
author | Florian Dold <florian.dold@gmail.com> | 2016-04-19 22:58:42 +0200 |
---|---|---|
committer | Florian Dold <florian.dold@gmail.com> | 2016-04-19 23:05:10 +0200 |
commit | 6c9bffdb2dd47cd9652ff3d05077b9d61d635067 (patch) | |
tree | bbc1eabe79f50a81d9185d693eb5470f60672072 | |
parent | 3b4257e34f33e62b32cf43113febd363ca6ed482 (diff) | |
download | bank-6c9bffdb2dd47cd9652ff3d05077b9d61d635067.tar.gz bank-6c9bffdb2dd47cd9652ff3d05077b9d61d635067.tar.bz2 bank-6c9bffdb2dd47cd9652ff3d05077b9d61d635067.zip |
config parsing in pure python
-rw-r--r-- | bank.wsgi.in | 1 | ||||
-rw-r--r-- | taler-bank-manage.in | 7 | ||||
-rw-r--r-- | talerbank/settings.py | 15 | ||||
-rw-r--r-- | talerbank/talerconfig.py | 224 |
4 files changed, 172 insertions, 75 deletions
diff --git a/bank.wsgi.in b/bank.wsgi.in index 5687061..6f5fd13 100644 --- a/bank.wsgi.in +++ b/bank.wsgi.in @@ -7,6 +7,7 @@ if sys.version_info.major < 3: sys.exit(1) os.environ.setdefault("DJANGO_SETTINGS_MODULE", "talerbank.settings") +os.environ.setdefault("TALER_PREFIX", "@prefix@") site.addsitedir("%s/lib/python%d.%d/site-packages" % ( "@prefix@", sys.version_info.major, diff --git a/taler-bank-manage.in b/taler-bank-manage.in index 05a847e..fda7523 100644 --- a/taler-bank-manage.in +++ b/taler-bank-manage.in @@ -10,8 +10,8 @@ import sys import os import site - os.environ.setdefault("DJANGO_SETTINGS_MODULE", "talerbank.settings") +os.environ.setdefault("TALER_PREFIX", "@prefix@") site.addsitedir("%s/lib/python%d.%d/site-packages" % ( "@prefix@", sys.version_info.major, @@ -64,6 +64,7 @@ def handle_sampledata(args): parser = argparse.ArgumentParser() parser.set_defaults(func=None) +parser.add_argument('--config', '-c', help="configuration file to use", metavar="CONFIG", type=str, dest="config", default=None) sub = parser.add_subparsers() p = sub.add_parser('django', help="Run django-admin command") @@ -81,9 +82,13 @@ p = sub.add_parser('serve-uwsgi', help="Serve bank over UWSGI") p.add_argument("--port", "-p", dest="port", type=int, default=8000, metavar="PORT") p.set_defaults(func=handle_serve_uwsgi) + args = parser.parse_args() if getattr(args, 'func', None) is None: parser.print_help() sys.exit(1) +if parser.config is not None: + os.environ["TALER_CONFIG_FILE"] = parser.config + args.func(args) diff --git a/talerbank/settings.py b/talerbank/settings.py index 323d744..e0af0ed 100644 --- a/talerbank/settings.py +++ b/talerbank/settings.py @@ -15,7 +15,8 @@ from .talerconfig import TalerConfig logger = logging.getLogger(__name__) -tc = TalerConfig() + +tc = TalerConfig.from_file(os.environ.get("TALER_CONFIG_FILE")) # Build paths inside the project like this: os.path.join(BASE_DIR, ...) @@ -84,10 +85,7 @@ WSGI_APPLICATION = 'talerbank.wsgi.application' # Database # https://docs.djangoproject.com/en/1.9/ref/settings/#databases -dbname = tc.get_string("bank", "database") -if not dbname: - logger.warn("database not defined in configuration section 'bank', defaulting to 'talerbank'") - dbname = 'talerbank' +dbname = tc["bank"]["database"].value_string(default='talerbank', warn=True) DATABASES = { 'default': { @@ -143,12 +141,7 @@ STATIC_ROOT = '/tmp/talerbankstatic/' -curr = tc.get_string("bank", "currency") -if not curr: - logger.warn("currency not configured in taler config, falling back to PUDOS") - TALER_CURRENCY = "PUDOS" -else: - TALER_CURRENCY = curr +TALER_CURRENCY = tc["taler"]["currency"].value_string(default="KUDOS", warn=True) TALER_PREDEFINED_ACCOUNTS = ['Tor', 'GNUnet', 'Taler', 'FSF'] TALER_EXPECTS_DONATIONS = ['Tor', 'GNUnet', 'Taler', 'FSF'] 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 <http://www.gnu.org/licenses/> -# -# @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 "<Entry section=%s, option=%s, value=%s>" % (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 "<unknown>" + 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() |