diff --git a/oxrlib/config.py b/oxrlib/config.py index d748861534c8e116a8ef1dc03ef6c4e65650a928..675d410672e26c43a8899724e3f13c9bbeb30ef7 100644 --- a/oxrlib/config.py +++ b/oxrlib/config.py @@ -1,6 +1,7 @@ import argparse import configparser import datetime +import decimal import os.path import pathlib @@ -13,7 +14,7 @@ base=USD """ def currency_code(s): - if not (len(s) == 3) and s.isalpha(): + if not ((len(s) == 3) and s.isalpha()): raise ValueError("bad currency code: {!r}".format(s)) return s.upper() @@ -24,6 +25,7 @@ def date_from(fmt_s): class Configuration: DEFAULT_CONFIG_PATH = pathlib.Path(HOME_PATH, '.config', 'oxrlib.ini') + PREPOSITIONS = frozenset(['in', 'to', 'into']) def __init__(self, arglist): argparser = self._build_argparser() @@ -56,15 +58,24 @@ class Configuration: subparsers = prog_parser.add_subparsers() hist_parser = subparsers.add_parser('historical', aliases=['hist']) - hist_parser.set_defaults(command='historical') + hist_parser.set_defaults( + command='historical', + amount=None, + from_currency=None, + ) hist_parser.add_argument( '--base', + type=currency_code, help="Base currency (default USD)", ) hist_parser.add_argument( 'date', type=date_from('%Y-%m-%d'), metavar='YYYY-MM-DD', ) + hist_parser.add_argument( + 'remainder', + nargs=argparse.REMAINDER, + ) return prog_parser @@ -73,9 +84,42 @@ class Configuration: conffile.read_string(CONFFILE_SEED) return conffile + def _convert_or_error(self, argtype, s_value, argname=None, typename=None): + try: + return argtype(s_value) + except (decimal.InvalidOperation, TypeError, ValueError): + errmsg = [] + if argname: + errmsg.append("argument {}".format(argname)) + if typename is None: + typename = argtype.__name__.replace('_', ' ') + errmsg.append("invalid {} value".format(typename)) + errmsg.append(repr(s_value)) + self.error(': '.join(errmsg)) + def _post_hook_historical(self): if self.args.base is None: self.args.base = self.conffile.get('Historical', 'base') + self.args.to_currency = self.args.base + remain_len = len(self.args.remainder) + if (remain_len > 3) and (self.args.remainder[2].lower() in self.PREPOSITIONS): + del self.args.remainder[2] + remain_len -= 1 + if remain_len == 0: + pass + elif remain_len == 1: + self.args.from_currency = self._convert_or_error( + currency_code, self.args.remainder[0]) + elif remain_len < 4: + self.args.amount = self._convert_or_error( + decimal.Decimal, self.args.remainder[0]) + self.args.from_currency = self._convert_or_error( + currency_code, self.args.remainder[1]) + if remain_len == 3: + self.args.to_currency = self._convert_or_error( + currency_code, self.args.remainder[2]) + else: + self.error("too many arguments") def _build_cache_loader(self): kwargs = dict(self.conffile.items('Cache')) diff --git a/tests/test_Configuration.py b/tests/test_Configuration.py index 00d5ddf7f76ca08f6a46e8719f77b03f56c70d57..b59d3c012747ba13ca39fdbb59175242f3210fdd 100644 --- a/tests/test_Configuration.py +++ b/tests/test_Configuration.py @@ -1,3 +1,4 @@ +import decimal import os import pytest @@ -42,3 +43,36 @@ def test_historical_default_base(ini_filename, expected_currency, use_switch, an arglist.append(any_date.isoformat()) config = config_from(ini_filename, arglist) assert config.args.base == expected_currency + +@pytest.mark.parametrize('amount,from_curr,preposition,to_curr', [ + (None, 'JPY', None, None), + (decimal.Decimal('1000'), 'chf', None, None), + (decimal.Decimal('999'), 'Eur', None, 'Chf'), + (decimal.Decimal('12.34'), 'gbp', 'IN', 'eur'), +]) +def test_historical_argparsing_success(amount, from_curr, preposition, to_curr, any_date): + arglist = ['historical', any_date.isoformat()] + arglist.extend(str(s) for s in [amount, from_curr, preposition, to_curr] + if s is not None) + config = config_from(os.devnull, arglist) + assert config.args.amount == amount + assert config.args.from_currency == from_curr.upper() + if to_curr is not None: + assert config.args.to_currency == to_curr.upper() + +@pytest.mark.parametrize('arglist', [ + ['100'], + ['120', 'dollars'], + ['to', '42', 'usd'], + ['99', 'usd', 'minus', 'jpy'], + ['usdjpy'], + ['44', 'eur', 'in', 'chf', 'pronto'], +]) +def test_historical_argparsing_failure(arglist, any_date): + arglist = ['historical', any_date.isoformat()] + arglist + try: + config = config_from(os.devnull, arglist) + except SystemExit: + pass + else: + assert not vars(config.args), "bad arglist succeeded"