137 lines
5.2 KiB
Python
137 lines
5.2 KiB
Python
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):
|
|
def __init__(self, rate, signed_currencies=(), base_fmt='#,##0.###',
|
|
rate_precision=5, denomination=None):
|
|
super().__init__(rate, signed_currencies, base_fmt)
|
|
self.rate_prec = rate_precision
|
|
self.denomination = denomination
|
|
|
|
def normalize_rate(self, rate):
|
|
_, digits, exponent = rate.normalize().as_tuple()
|
|
# Return ``self.rate_prec`` nonzero digits of precision, if available.
|
|
prec = self.rate_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):
|
|
return str(self.normalize_rate(rate))
|
|
|
|
def format_ledger_rate(self, rate, curr):
|
|
nrate = self.normalize_rate(rate)
|
|
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 _denomination_for(self, currency, default):
|
|
if self.denomination is None:
|
|
return default
|
|
elif self.denomination == currency:
|
|
return None
|
|
else:
|
|
return self.denomination
|
|
|
|
def format_denominated_rate(self, amount, currency, default_denomination):
|
|
denomination = self._denomination_for(currency, default_denomination)
|
|
amt_s = self.format_currency(amount, currency)
|
|
if denomination is None:
|
|
return amt_s
|
|
else:
|
|
rate = self.rate.convert(1, currency, denomination)
|
|
return "{} {}".format(amt_s, self.format_ledger_rate(rate, denomination))
|
|
|
|
def format_conversion(self, from_amt, from_curr, to_curr):
|
|
to_amt = self.rate.convert(from_amt, from_curr, to_curr)
|
|
return "{}\n{}".format(
|
|
self.format_denominated_rate(from_amt, from_curr, to_curr),
|
|
self.format_denominated_rate(to_amt, to_curr, None),
|
|
)
|
|
|
|
|
|
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,
|
|
denomination=config.args.denomination)
|
|
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)
|