[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.
- [GNUnet-SVN] [taler-taler-util] branch master created (now 3e2d0fb), gnunet, 2019/09/23
- [GNUnet-SVN] [taler-taler-util] 01/51: readme, gnunet, 2019/09/23
- [GNUnet-SVN] [taler-taler-util] 08/51: Update python config module., gnunet, 2019/09/23
- [GNUnet-SVN] [taler-taler-util] 09/51: empty module, gnunet, 2019/09/23
- [GNUnet-SVN] [taler-taler-util] 03/51: amount version 0.1. Basically has some new exceptions, gnunet, 2019/09/23
- [GNUnet-SVN] [taler-taler-util] 10/51: Very first draft for solving #4453., gnunet, 2019/09/23
- [GNUnet-SVN] [taler-taler-util] 05/51: First commit for #4453., gnunet, 2019/09/23
- [GNUnet-SVN] [taler-taler-util] 02/51: adding amount lib, gnunet, 2019/09/23
- [GNUnet-SVN] [taler-taler-util] 04/51: add talerconfig and linted amount,
gnunet <=
- [GNUnet-SVN] [taler-taler-util] 06/51: remove old config, gnunet, 2019/09/23
- [GNUnet-SVN] [taler-taler-util] 07/51: Update "config" module (with one that implements @INLINE@)., gnunet, 2019/09/23
- [GNUnet-SVN] [taler-taler-util] 14/51: log test, gnunet, 2019/09/23
- [GNUnet-SVN] [taler-taler-util] 12/51: make map static, gnunet, 2019/09/23
- [GNUnet-SVN] [taler-taler-util] 25/51: 4453. Parsing line interval., gnunet, 2019/09/23
- [GNUnet-SVN] [taler-taler-util] 11/51: Towards a usable solution for #4453., gnunet, 2019/09/23
- [GNUnet-SVN] [taler-taler-util] 13/51: Give loglevels dedicate class + test., gnunet, 2019/09/23
- [GNUnet-SVN] [taler-taler-util] 16/51: Updating Amount from bank., gnunet, 2019/09/23
- [GNUnet-SVN] [taler-taler-util] 19/51: Parsing GNUNET_FORCE_LOGFILE., gnunet, 2019/09/23
- [GNUnet-SVN] [taler-taler-util] 26/51: 4453. Check against further fields (filename, func, lineno), gnunet, 2019/09/23