gnunet-svn
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

[GNUnet-SVN] [taler-taler-util] 04/51: add talerconfig and linted amount


From: gnunet
Subject: [GNUnet-SVN] [taler-taler-util] 04/51: add talerconfig and linted amount
Date: Mon, 23 Sep 2019 22:01:55 +0200

This is an automated email from the git hooks/post-receive script.

ng0 pushed a commit to branch master
in repository taler-util.

commit 5bd2c9460b3fc0bd6eaddff22b6c11cecd926f3d
Author: Marcello Stanisci <address@hidden>
AuthorDate: Tue Dec 5 18:10:52 2017 +0100

    add talerconfig and linted amount
---
 python/amount/amount.py      |  76 +++++-----
 python/config/talerconfig.py | 342 +++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 382 insertions(+), 36 deletions(-)

diff --git a/python/amount/amount.py b/python/amount/amount.py
index 9a6318a..e5ac4a7 100644
--- a/python/amount/amount.py
+++ b/python/amount/amount.py
@@ -16,50 +16,53 @@
 #  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  
USA
 #
 #  @author Marcello Stanisci
-#  @version 0.1
+#  @version 0.2
 #  @repository https://git.taler.net/copylib.git/
 #  This code is "copylib", it is versioned under the Git repository
 #  mentioned above, and it is meant to be manually copied into any project
 #  which might need it.
 
 class CurrencyMismatch(Exception):
-    pass
+    def __init__(self, curr1, curr2):
+        super(CurrencyMismatch, self).__init__(
+            "%s vs %s" % (curr1, curr2))
 
 class BadFormatAmount(Exception):
     def __init__(self, faulty_str):
-        self.faulty_str = faulty_str
+        super(BadFormatAmount, self).__init__(
+            "Bad format amount: " + faulty_str)
 
 class Amount:
     # How many "fraction" units make one "value" unit of currency
     # (Taler requires 10^8).  Do not change this 'constant'.
     @staticmethod
-    def FRACTION():
+    def _fraction():
         return 10 ** 8
 
     @staticmethod
-    def MAX_VALUE():
+    def _max_value():
         return (2 ** 53) - 1
 
     def __init__(self, currency, value=0, fraction=0):
         # type: (str, int, int) -> Amount
-        assert(value >= 0 and fraction >= 0)
+        assert value >= 0 and fraction >= 0
         self.value = value
         self.fraction = fraction
         self.currency = currency
         self.__normalize()
-        assert(self.value <= Amount.MAX_VALUE())
+        assert self.value <= Amount._max_value()
 
     # Normalize amount
     def __normalize(self):
-        if self.fraction >= Amount.FRACTION():
-            self.value += int(self.fraction / Amount.FRACTION())
-            self.fraction = self.fraction % Amount.FRACTION()
+        if self.fraction >= Amount._fraction():
+            self.value += int(self.fraction / Amount._fraction())
+            self.fraction = self.fraction % Amount._fraction()
 
     # Parse a string matching the format "A:B.C"
     # instantiating an amount object.
     @classmethod
     def parse(cls, amount_str):
-        exp = '^\s*([-_*A-Za-z0-9]+):([0-9]+)\.([0-9]+)\s*$'
+        exp = r'^\s*([-_*A-Za-z0-9]+):([0-9]+)\.([0-9]+)\s*$'
         import re
         parsed = re.search(exp, amount_str)
         if not parsed:
@@ -67,7 +70,7 @@ class Amount:
         value = int(parsed.group(2))
         fraction = 0
         for i, digit in enumerate(parsed.group(3)):
-            fraction += int(int(digit) * (Amount.FRACTION() / 10 ** (i+1)))
+            fraction += int(int(digit) * (Amount._fraction() / 10 ** (i+1)))
         return cls(parsed.group(1), value, fraction)
 
     # Comare two amounts, return:
@@ -75,16 +78,16 @@ class Amount:
     # 0 if a == b
     # 1 if a > b
     @staticmethod
-    def cmp(a, b):
-        if a.currency != b.currency:
-            raise CurrencyMismatch()
-        if a.value == b.value:
-            if a.fraction < b.fraction:
+    def cmp(am1, am2):
+        if am1.currency != am2.currency:
+            raise CurrencyMismatch(am1.currency, am2.currency)
+        if am1.value == am2.value:
+            if am1.fraction < am2.fraction:
                 return -1
-            if a.fraction > b.fraction:
+            if am1.fraction > am2.fraction:
                 return 1
             return 0
-        if a.value < b.value:
+        if am1.value < am2.value:
             return -1
         return 1
 
@@ -94,34 +97,35 @@ class Amount:
         self.fraction = fraction
 
     # Add the given amount to this one
-    def add(self, a):
-        if self.currency != a.currency:
-            raise CurrencyMismatch()
-        self.value += a.value
-        self.fraction += a.fraction
+    def add(self, amount):
+        if self.currency != amount.currency:
+            raise CurrencyMismatch(self.currency, amount.currency)
+        self.value += amount.value
+        self.fraction += amount.fraction
         self.__normalize()
 
     # Subtract passed amount from this one
-    def subtract(self, a):
-        if self.currency != a.currency:
-            raise CurrencyMismatch()
-        if self.fraction < a.fraction:
-            self.fraction += Amount.FRACTION()
+    def subtract(self, amount):
+        if self.currency != amount.currency:
+            raise CurrencyMismatch(self.currency, amount.currency)
+        if self.fraction < amount.fraction:
+            self.fraction += Amount._fraction()
             self.value -= 1
-        if self.value < a.value:
+        if self.value < amount.value:
             raise ValueError('self is lesser than amount to be subtracted')
-        self.value -= a.value
-        self.fraction -= a.fraction
+        self.value -= amount.value
+        self.fraction -= amount.fraction
 
     # Dump string from this amount, will put 'ndigits' numbers
     # after the dot.
     def stringify(self, ndigits):
         assert ndigits > 0
         ret = '%s:%s.' % (self.currency, str(self.value))
-        f = self.fraction
-        for i in range(0, ndigits):
-            ret += str(int(f / (Amount.FRACTION() / 10)))
-            f = (f * 10) % (Amount.FRACTION())
+        fraction = self.fraction
+        while ndigits > 0:
+            ret += str(int(fraction / (Amount._fraction() / 10)))
+            fraction = (fraction * 10) % (Amount._fraction())
+            ndigits -= 1
         return ret
 
     # Dump the Taler-compliant 'dict' amount
diff --git a/python/config/talerconfig.py b/python/config/talerconfig.py
new file mode 100644
index 0000000..a7ca065
--- /dev/null
+++ b/python/config/talerconfig.py
@@ -0,0 +1,342 @@
+#  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, see <http://www.gnu.org/licenses/>
+#
+#  @author Florian Dold
+
+"""
+Parse GNUnet-style configurations in pure Python
+"""
+
+import logging
+import collections
+import os
+import weakref
+import sys
+import re
+
+LOGGER = logging.getLogger(__name__)
+
+__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 ConfigurationError(Exception):
+    pass
+
+class ExpansionSyntaxError(Exception):
+    pass
+
+
+def expand(var, getter):
+    """
+    Do shell-style parameter expansion.
+    Supported syntax:
+    - ${X}
+    - ${X:-Y}
+    - $X
+    """
+    pos = 0
+    result = ""
+    while pos != -1:
+        start = var.find("$", pos)
+        if start == -1:
+            break
+        if var[start:].startswith("${"):
+            balance = 1
+            end = start + 2
+            while balance > 0 and end < len(var):
+                balance += {"{": 1, "}": -1}.get(var[end], 0)
+                end += 1
+            if balance != 0:
+                raise ExpansionSyntaxError("unbalanced parentheses")
+            piece = var[start+2:end-1]
+            if piece.find(":-") > 0:
+                varname, alt = piece.split(":-", 1)
+                replace = getter(varname)
+                if replace is None:
+                    replace = expand(alt, getter)
+            else:
+                varname = piece
+                replace = getter(varname)
+                if replace is None:
+                    replace = var[start:end]
+        else:
+            end = start + 2
+            while end < len(var) and var[start+1:end+1].isalnum():
+                end += 1
+            varname = var[start+1:end]
+            replace = getter(varname)
+            if replace is None:
+                replace = var[start:end]
+        result = result + replace
+        pos = end
+
+
+    return result + var[pos:]
+
+
+class OptionDict(collections.defaultdict):
+    def __init__(self, config, section_name):
+        self.config = weakref.ref(config)
+        self.section_name = section_name
+        super().__init__()
+    def __missing__(self, key):
+        entry = Entry(self.config(), self.section_name, key)
+        self[key] = entry
+        return entry
+    def __getitem__(self, chunk):
+        return super().__getitem__(chunk.lower())
+    def __setitem__(self, chunk, value):
+        super().__setitem__(chunk.lower(), value)
+
+
+class SectionDict(collections.defaultdict):
+    def __missing__(self, key):
+        value = OptionDict(self, key)
+        self[key] = value
+        return value
+    def __getitem__(self, chunk):
+        return super().__getitem__(chunk.lower())
+    def __setitem__(self, chunk, value):
+        super().__setitem__(chunk.lower(), value)
+
+
+class Entry:
+    def __init__(self, config, section, option, **kwargs):
+        self.value = kwargs.get("value")
+        self.filename = kwargs.get("filename")
+        self.lineno = kwargs.get("lineno")
+        self.section = section
+        self.option = option
+        self.config = weakref.ref(config)
+
+    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, required=False, warn=False):
+        if required and self.value is None:
+            raise ConfigurationError("Missing required option '%s' in section 
'%s'" \
+                                     % (self.option.upper(), 
self.section.upper()))
+        if self.value is None:
+            if warn:
+                if default is not None:
+                    LOGGER.warning("Configuration is missing option '%s' in 
section '%s',\
+                                   falling back to '%s'", self.option, 
self.section, default)
+                else:
+                    LOGGER.warning("Configuration ** is missing option '%s' in 
section '%s'",
+                                   self.option.upper(), self.section.upper())
+            return default
+        return self.value
+
+    def value_int(self, default=None, required=False, warn=False):
+        value = self.value_string(default, warn, required)
+        if value is None:
+            return None
+        try:
+            return int(value)
+        except ValueError:
+            raise ConfigurationError("Expected number for option '%s' in 
section '%s'" \
+                                     % (self.option.upper(), 
self.section.upper()))
+
+    def _getsubst(self, key):
+        value = self.config()["paths"][key].value
+        if value is not None:
+            return value
+        value = os.environ.get(key)
+        if value is not None:
+            return value
+        return None
+
+    def value_filename(self, default=None, required=False, warn=False):
+        value = self.value_string(default, required, warn)
+        if value is None:
+            return None
+        return expand(value, self._getsubst)
+
+    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()
+
+    # defaults != config file: the first is the 'base'
+    # whereas the second overrides things from the first.
+    @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 value_string(self, section, option, **kwargs):
+        return self.sections[section][option].value_string(
+            kwargs.get("default"), kwargs.get("required"), kwargs.get("warn"))
+
+    def value_filename(self, section, option, **kwargs):
+        return self.sections[section][option].value_filename(
+            kwargs.get("default"), kwargs.get("required"), kwargs.get("warn"))
+
+    def value_int(self, section, option, **kwargs):
+        return self.sections[section][option].value_int(
+            kwargs.get("default"), kwargs.get("required"), kwargs.get("warn"))
+
+    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:
+            tmp = os.path.split(os.path.normpath(prefix))
+            if re.match("lib", tmp[1]):
+                prefix = tmp[0]
+            self.load_dir(os.path.join(prefix, "share/taler/config.d"))
+            return
+        if TALER_DATADIR:
+            self.load_dir(os.path.join(TALER_DATADIR, "share/taler/config.d"))
+            return
+        LOGGER.warning("no base directory found")
+
+    @staticmethod
+    def from_env(*args, **kwargs):
+        """
+        Load configuration from environment variable TALER_CONFIG_FILE
+        or from default location if the variable is not set.
+        """
+        filename = os.environ.get("TALER_CONFIG_FILE")
+        return TalerConfig.from_file(filename, *args, **kwargs)
+
+    def load_dir(self, dirname):
+        try:
+            files = os.listdir(dirname)
+        except FileNotFoundError:
+            LOGGER.warning("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
+        try:
+            with open(filename, "r") as file:
+                lineno = 0
+                current_section = None
+                for line in file:
+                    lineno += 1
+                    line = line.strip()
+                    if line == "":
+                        # 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
+                    pair = line.split("=", 1)
+                    if len(pair) != 2:
+                        LOGGER.error("invalid option in line %s: %s", lineno, 
repr(line))
+                    key = pair[0].strip()
+                    value = pair[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]
+                    entry = Entry(self.sections, current_section, key,
+                                  value=value, filename=filename, 
lineno=lineno)
+                    sections[current_section][key] = entry
+        except FileNotFoundError:
+            LOGGER.error("Configuration file (%s) not found", filename)
+            sys.exit(3)
+
+
+    def dump(self):
+        for kv_section in self.sections.items():
+            print("[%s]" % (kv_section[1].section_name,))
+            for kv_option in kv_section[1].items():
+                print("%s = %s # %s" % \
+                      (kv_option[1].option,
+                       kv_option[1].value,
+                       kv_option[1].location()))
+
+    def __getitem__(self, chunk):
+        if isinstance(chunk, str):
+            return self.sections[chunk]
+        raise TypeError("index must be string")
+
+
+if __name__ == "__main__":
+    import argparse
+
+    PARSER = argparse.ArgumentParser()
+    PARSER.add_argument("--section", "-s", dest="section",
+                        default=None, metavar="SECTION")
+    PARSER.add_argument("--option", "-o", dest="option",
+                        default=None, metavar="OPTION")
+    PARSER.add_argument("--config", "-c", dest="config",
+                        default=None, metavar="FILE")
+    PARSER.add_argument("--filename", "-f", dest="expand_filename",
+                        default=False, action='store_true')
+    ARGS = PARSER.parse_args()
+
+    TC = TalerConfig.from_file(ARGS.config)
+
+    if ARGS.section is not None and ARGS.option is not None:
+        if ARGS.expand_filename:
+            X = TC.value_filename(ARGS.section, ARGS.option)
+        else:
+            X = TC.value_string(ARGS.section, ARGS.option)
+        if X is not None:
+            print(X)
+    else:
+        TC.dump()

-- 
To stop receiving notification emails like this one, please contact
address@hidden.



reply via email to

[Prev in Thread] Current Thread [Next in Thread]