summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--bank.wsgi.in1
-rw-r--r--taler-bank-manage.in7
-rw-r--r--talerbank/settings.py15
-rw-r--r--talerbank/talerconfig.py224
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()