summaryrefslogtreecommitdiff
path: root/talerbank/talerconfig.py
diff options
context:
space:
mode:
authorFlorian Dold <florian.dold@gmail.com>2016-04-19 22:58:42 +0200
committerFlorian Dold <florian.dold@gmail.com>2016-04-19 23:05:10 +0200
commit6c9bffdb2dd47cd9652ff3d05077b9d61d635067 (patch)
treebbc1eabe79f50a81d9185d693eb5470f60672072 /talerbank/talerconfig.py
parent3b4257e34f33e62b32cf43113febd363ca6ed482 (diff)
downloadbank-6c9bffdb2dd47cd9652ff3d05077b9d61d635067.tar.gz
bank-6c9bffdb2dd47cd9652ff3d05077b9d61d635067.tar.bz2
bank-6c9bffdb2dd47cd9652ff3d05077b9d61d635067.zip
config parsing in pure python
Diffstat (limited to 'talerbank/talerconfig.py')
-rw-r--r--talerbank/talerconfig.py224
1 files changed, 161 insertions, 63 deletions
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()