import decimal import itertools import operator import babel.numbers from .. import rate as oxrrate class Formatter: def __init__(self, rate, signed_currencies=(), base_fmt='#,##0.###'): self.rate = rate self.base_fmt = base_fmt self.base_fmt_noprec = base_fmt.rsplit('.', 1)[0] self.signed_currencies = set(code for code in signed_currencies if self.can_sign_currency(code)) def can_sign_currency(self, code): return len(babel.numbers.get_currency_symbol(code)) == 1 def format_currency(self, amount, code, currency_digits=True): if currency_digits: fmt = self.base_fmt else: fmt = '{}.{}'.format(self.base_fmt_noprec, '#' * -amount.as_tuple().exponent) if code in self.signed_currencies: fmt = '¤' + fmt else: fmt = fmt + ' ¤¤' return babel.numbers.format_currency(amount, code, fmt, currency_digits=currency_digits) def format_rate(self, rate): return "{:g}".format(rate) def format_rate_pair(self, from_curr, to_curr): from_amt = 1 to_amt = self.rate.convert(from_amt, from_curr, to_curr) return "{} {} = {} {}".format( self.format_rate(from_amt), from_curr, self.format_rate(to_amt), to_curr, ) def format_rate_pair_bidir(self, from_curr, to_curr, sep='\n'): return "{}{}{}".format( self.format_rate_pair(from_curr, to_curr), sep, self.format_rate_pair(to_curr, from_curr), ) def format_conversion(self, from_amt, from_curr, to_curr): to_amt = self.rate.convert(from_amt, from_curr, to_curr) return "{} = {}".format( self.format_currency(from_amt, from_curr), self.format_currency(to_amt, to_curr), ) class LedgerFormatter(Formatter): RATE_PREC = 5 def normalize_rate(self, rate, prec=None): # Return prec nonzero digits of precision, if available. if prec is None: prec = self.RATE_PREC _, digits, exponent = rate.normalize().as_tuple() prec -= min(0, exponent + len(digits)) quant_to = '1.{}'.format('0' * prec) try: qrate = rate.quantize(decimal.Decimal(quant_to)) except decimal.InvalidOperation: # The original rate doesn't have that much precision, so use it raw. qrate = rate return qrate.normalize() def format_rate(self, rate, prec=None): return str(self.normalize_rate(rate, prec)) def format_ledger_rate(self, rate, curr, prec=None): nrate = self.normalize_rate(rate, prec) rate_s = self.format_currency(nrate, curr, currency_digits=False) return "{{={0}}} @ {0}".format(rate_s) def format_rate_pair(self, from_curr, to_curr): from_amt = 1 to_amt = self.rate.convert(from_amt, from_curr, to_curr) return "{} {} {}".format( from_amt, from_curr, self.format_ledger_rate(to_amt, to_curr)) def format_conversion(self, from_amt, from_curr, to_curr): to_rate = self.rate.convert(1, from_curr, to_curr) to_amt = self.rate.convert(from_amt, from_curr, to_curr) return "{} {}\n{}".format( self.format_currency(from_amt, from_curr), self.format_ledger_rate(to_rate, to_curr), self.format_currency(to_amt, to_curr), ) def run(config, stdout, stderr): loaders = config.get_loaders() with loaders.historical(config.args.date, config.args.base) as rate_json: rate = oxrrate.Rate.from_json_file(rate_json) if loaders.should_cache(): config.cache.save_rate(rate) if config.args.ledger: formatter = LedgerFormatter(rate, config.args.signed_currencies) else: formatter = Formatter(rate) if not config.args.from_currency: for from_curr in sorted(rate.rates): print(formatter.format_rate_pair_bidir(from_curr, config.args.to_currency), file=stdout) elif config.args.amount is None: print(formatter.format_rate_pair_bidir(config.args.from_currency, config.args.to_currency), file=stdout) else: print(formatter.format_conversion(config.args.amount, config.args.from_currency, config.args.to_currency), file=stdout)