diff --git a/oxrlib/config.py b/oxrlib/config.py index c71c6cd..f9d6e0e 100644 --- a/oxrlib/config.py +++ b/oxrlib/config.py @@ -26,7 +26,7 @@ class Configuration: DEFAULT_CONFIG_PATH = pathlib.Path(HOME_PATH, '.config', 'oxrlib.ini') LOCALE = babel.core.Locale.default() SENTINEL = object() - PREPOSITIONS = frozenset(['in', 'to', 'into']) + CURRENCY_PREPOSITIONS = frozenset(['in', 'to', 'into']) TODAY = datetime.date.today() def __init__(self, arglist): @@ -152,7 +152,13 @@ class Configuration: help="Convert or show rates to this currency, in three-letter code format. " "If not specified, defaults to the user's preferred currency.", ) - hist_parser.add_argument('word4', nargs='?', help=argparse.SUPPRESS) + hist_parser.add_argument( + 'word4', nargs='?', metavar='from date', + help="Include source rates for this date, if provided. " + "Raw output format does not show source rates.", + ) + hist_parser.add_argument('word5', nargs='?', help=argparse.SUPPRESS) + hist_parser.add_argument('word6', nargs='?', help=argparse.SUPPRESS) return prog_parser @@ -208,7 +214,7 @@ class Configuration: self._read_from_conffile('signed_currencies', 'Historical', pref_currency, currency_list, convert_fallback=True) self._read_from_conffile('ledger', 'Historical', False, getter='getboolean') - raw_words = iter(getattr(self.args, 'word' + c) for c in '1234') + raw_words = iter(getattr(self.args, 'word' + c) for c in '123456') words = iter(word for word in raw_words if word is not None) try: next_word = next(words) @@ -221,11 +227,35 @@ class Configuration: # If it wasn't, set a value that can't be parsed as a currency. next_word = next(words, 'none given') self.args.from_currency = self._convert_or_error(currency_code, next_word) - next_word = next(words) - if next_word.lower() in self.PREPOSITIONS: - next_word = next(words, next_word) - self.args.to_currency = self._convert_or_error(currency_code, next_word) except StopIteration: + pass + self.args.to_currency = None + self.args.from_date = None + for next_word in words: + next_lower = next_word.lower() + if next_lower in self.CURRENCY_PREPOSITIONS: + attr_to_set = 'to_currency' + next_word = next(words, 'none given') + elif next_lower == 'from': + attr_to_set = 'from_date' + next_word = next(words, 'none given') + elif next_word.isalpha(): + attr_to_set = 'to_currency' + else: + attr_to_set = 'from_date' + have_arg = getattr(self.args, attr_to_set) + if have_arg is not None: + self.error(f"tried to set {attr_to_set.replace('_', ' ')} multiple times") + elif attr_to_set == 'from_date': + convert_func = lambda s: self._date_from_s(s)[0] + typename = 'date' + else: + convert_func = currency_code + typename = None + setattr(self.args, attr_to_set, self._convert_or_error( + convert_func, next_word, attr_to_set, typename, + )) + if self.args.to_currency is None: self.args.to_currency = pref_currency if ((len(date_spec) == 1) and self.args.from_currency diff --git a/tests/test_Configuration.py b/tests/test_Configuration.py index 52756ea..dd4b890 100644 --- a/tests/test_Configuration.py +++ b/tests/test_Configuration.py @@ -46,26 +46,34 @@ def test_historical_default_base(ini_filename, expected_currency, use_switch, an 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), - (None, 'gbp', None, 'Aud'), - (None, 'CHF', 'to', 'eur'), - (decimal.Decimal('1000'), 'chf', None, None), - (decimal.Decimal('999'), 'Eur', None, 'Chf'), - (decimal.Decimal('12.34'), 'gbp', 'IN', 'eur'), +@pytest.mark.parametrize('amount,from_curr,prep1,to_curr,prep2,from_date', [ + (None, 'JPY', None, None, None, None), + (None, 'gbp', None, 'Aud', None, None), + (None, 'CHF', 'to', 'eur', None, None), + (decimal.Decimal('1000'), 'chf', None, None, None, None), + (decimal.Decimal('999'), 'Eur', None, 'Chf', None, None), + (decimal.Decimal('12.34'), 'gbp', 'IN', 'eur', None, None), + (None, 'JPY', None, None, None, '12-15'), + (None, 'gbp', None, 'Aud', 'From', '12.15'), + (None, 'CHF', 'to', 'eur', 'from', '15'), + (decimal.Decimal('1000'), 'chf', None, None, None, '12-15'), + (decimal.Decimal('999'), 'Eur', None, 'Chf', None, '2016.12.15'), + (decimal.Decimal('12.34'), 'gbp', 'IN', 'eur', 'from', '2016-12-15'), ]) -def test_historical_argparsing_success(amount, from_curr, preposition, to_curr, any_date): +def test_historical_argparsing_success(amount, from_curr, prep1, to_curr, prep2, from_date, any_date): oxrlib.config.Configuration.TODAY = datetime.date(2017, 1, 1) # This locale's currency should not be used in any test cases above. oxrlib.config.Configuration.LOCALE = babel.core.Locale('en', 'IN') arglist = ['historical', any_date.isoformat()] - arglist.extend(str(s) for s in [amount, from_curr, preposition, to_curr] + arglist.extend(str(s) for s in [amount, from_curr, prep1, to_curr, prep2, from_date] if s is not None) config = config_from(os.devnull, arglist) expect_to_curr = 'INR' if to_curr is None else to_curr.upper() assert config.args.amount == amount assert config.args.from_currency == from_curr.upper() assert config.args.to_currency == expect_to_curr + expect_from_date = None if from_date is None else datetime.date(2016, 12, 15) + assert config.args.from_date == expect_from_date @pytest.mark.parametrize('arglist', [ ['100'], @@ -76,6 +84,10 @@ def test_historical_argparsing_success(amount, from_curr, preposition, to_curr, ['44', 'eur', 'in', 'chf', 'pronto'], ['eur', 'into'], ['50', 'jpy', 'in'], + ['115', 'usd', 'in', '12-15'], + ['125', 'jpy', 'from', 'chf'], + ['135', 'chf', 'eur', 'gbp'], + ['145', 'brl', '12-16', '2020-12-18'], ]) def test_historical_argparsing_failure(arglist, any_date): arglist = ['historical', any_date.isoformat()] + arglist