Make a Format dataclass, add --quantize option.
This commit is contained in:
parent
ec7df02391
commit
58d37463c6
7 changed files with 83 additions and 47 deletions
|
@ -3,7 +3,7 @@ from datetime import datetime, timedelta
|
||||||
|
|
||||||
from pricehist import outputs, sources
|
from pricehist import outputs, sources
|
||||||
from pricehist import __version__
|
from pricehist import __version__
|
||||||
from pricehist.formatinfo import FormatInfo
|
from pricehist.format import Format
|
||||||
|
|
||||||
|
|
||||||
def cli(args=None):
|
def cli(args=None):
|
||||||
|
@ -62,17 +62,24 @@ def cmd_fetch(args):
|
||||||
for p in prices
|
for p in prices
|
||||||
]
|
]
|
||||||
|
|
||||||
default = FormatInfo()
|
default = Format()
|
||||||
|
|
||||||
fi = FormatInfo(
|
def if_not_none(value, default):
|
||||||
time=(args.renametime or default.time),
|
if value is None:
|
||||||
decimal=(args.formatdecimal or default.decimal),
|
return default
|
||||||
thousands=(args.formatthousands or default.thousands),
|
else:
|
||||||
symbol=(args.formatsymbol or default.symbol),
|
return value
|
||||||
datesep=(args.formatdatesep or default.datesep),
|
|
||||||
|
fmt = Format(
|
||||||
|
time=if_not_none(args.renametime, default.time),
|
||||||
|
decimal=if_not_none(args.formatdecimal, default.decimal),
|
||||||
|
thousands=if_not_none(args.formatthousands, default.thousands),
|
||||||
|
symbol=if_not_none(args.formatsymbol, default.symbol),
|
||||||
|
datesep=if_not_none(args.formatdatesep, default.datesep),
|
||||||
|
decimal_places=if_not_none(args.quantize, default.decimal_places),
|
||||||
)
|
)
|
||||||
|
|
||||||
print(output.format(prices, format_info=fi), end="")
|
print(output.format(prices, fmt=fmt), end="")
|
||||||
|
|
||||||
|
|
||||||
def build_parser():
|
def build_parser():
|
||||||
|
@ -122,7 +129,10 @@ def build_parser():
|
||||||
usage=(
|
usage=(
|
||||||
"pricehist fetch SOURCE PAIR "
|
"pricehist fetch SOURCE PAIR "
|
||||||
"[-h] (-s DATE | -sx DATE) [-e DATE] [-o FMT] "
|
"[-h] (-s DATE | -sx DATE) [-e DATE] [-o FMT] "
|
||||||
"[--rename-base SYM] [--rename-quote SYM] [--rename-time TIME]"
|
"[--rename-base SYM] [--rename-quote SYM] [--rename-time TIME] "
|
||||||
|
"[--format-decimal CHAR] [--format-thousands CHAR] "
|
||||||
|
"[--format-symbol rightspace|right|leftspace|left] [--format-datesep CHAR] "
|
||||||
|
"[--quantize INT]"
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
fetch_parser.add_argument(
|
fetch_parser.add_argument(
|
||||||
|
@ -229,5 +239,12 @@ def build_parser():
|
||||||
type=str,
|
type=str,
|
||||||
help="date separator",
|
help="date separator",
|
||||||
)
|
)
|
||||||
|
fetch_parser.add_argument(
|
||||||
|
"--quantize",
|
||||||
|
dest="quantize",
|
||||||
|
metavar="INT",
|
||||||
|
type=int,
|
||||||
|
help="quantize to given number of decimal places",
|
||||||
|
)
|
||||||
|
|
||||||
return parser
|
return parser
|
||||||
|
|
27
src/pricehist/format.py
Normal file
27
src/pricehist/format.py
Normal file
|
@ -0,0 +1,27 @@
|
||||||
|
from dataclasses import dataclass
|
||||||
|
from decimal import Decimal, getcontext
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass(frozen=True)
|
||||||
|
class Format:
|
||||||
|
time: str = "00:00:00"
|
||||||
|
decimal: str = "."
|
||||||
|
thousands: str = ""
|
||||||
|
symbol: str = "rightspace"
|
||||||
|
datesep: str = "-"
|
||||||
|
decimal_places: int = None
|
||||||
|
|
||||||
|
def quantize(self, num):
|
||||||
|
if self.decimal_places is None:
|
||||||
|
return num
|
||||||
|
else:
|
||||||
|
prec = getcontext().prec
|
||||||
|
digits = len(num.as_tuple().digits)
|
||||||
|
exponent = num.as_tuple().exponent
|
||||||
|
|
||||||
|
fractional_digits = -exponent
|
||||||
|
whole_digits = digits - fractional_digits
|
||||||
|
max_decimal_places = prec - whole_digits
|
||||||
|
chosen_decimal_places = min(self.decimal_places, max_decimal_places)
|
||||||
|
|
||||||
|
return num.quantize(Decimal("0." + ("0" * chosen_decimal_places)))
|
|
@ -1,7 +0,0 @@
|
||||||
from collections import namedtuple
|
|
||||||
|
|
||||||
FormatInfo = namedtuple(
|
|
||||||
"FormatInfo",
|
|
||||||
["time", "decimal", "thousands", "symbol", "datesep"],
|
|
||||||
defaults=["00:00:00", ".", "", "rightspace", "-"],
|
|
||||||
)
|
|
|
@ -1,23 +1,23 @@
|
||||||
from pricehist.formatinfo import FormatInfo
|
from pricehist.format import Format
|
||||||
|
|
||||||
|
|
||||||
class Beancount:
|
class Beancount:
|
||||||
def format(self, prices, format_info=FormatInfo()):
|
def format(self, prices, fmt=Format()):
|
||||||
lines = []
|
lines = []
|
||||||
for price in prices:
|
for price in prices:
|
||||||
|
|
||||||
amount_parts = f"{price.amount:,}".split(".")
|
amount_parts = f"{fmt.quantize(price.amount):,}".split(".")
|
||||||
amount_parts[0] = amount_parts[0].replace(",", format_info.thousands)
|
amount_parts[0] = amount_parts[0].replace(",", fmt.thousands)
|
||||||
amount = ".".join(amount_parts)
|
amount = ".".join(amount_parts)
|
||||||
|
|
||||||
qa_parts = [amount]
|
qa_parts = [amount]
|
||||||
if format_info.symbol == "right":
|
if fmt.symbol == "right":
|
||||||
qa_parts = qa_parts + [price.quote]
|
qa_parts = qa_parts + [price.quote]
|
||||||
else:
|
else:
|
||||||
qa_parts = qa_parts + [" ", price.quote]
|
qa_parts = qa_parts + [" ", price.quote]
|
||||||
quote_amount = "".join(qa_parts)
|
quote_amount = "".join(qa_parts)
|
||||||
|
|
||||||
date = str(price.date).replace("-", format_info.datesep)
|
date = str(price.date).replace("-", fmt.datesep)
|
||||||
lines.append(f"{date} price {price.base} {quote_amount}")
|
lines.append(f"{date} price {price.base} {quote_amount}")
|
||||||
return "\n".join(lines) + "\n"
|
return "\n".join(lines) + "\n"
|
||||||
|
|
||||||
|
|
|
@ -1,14 +1,14 @@
|
||||||
from pricehist.formatinfo import FormatInfo
|
from pricehist.format import Format
|
||||||
|
|
||||||
|
|
||||||
class CSV:
|
class CSV:
|
||||||
def format(self, prices, format_info=FormatInfo()):
|
def format(self, prices, fmt=Format()):
|
||||||
lines = ["date,base,quote,amount"]
|
lines = ["date,base,quote,amount"]
|
||||||
for price in prices:
|
for price in prices:
|
||||||
date = str(price.date).replace("-", format_info.datesep)
|
date = str(price.date).replace("-", fmt.datesep)
|
||||||
amount_parts = f"{price.amount:,}".split(".")
|
amount_parts = f"{fmt.quantize(price.amount):,}".split(".")
|
||||||
amount_parts[0] = amount_parts[0].replace(",", format_info.thousands)
|
amount_parts[0] = amount_parts[0].replace(",", fmt.thousands)
|
||||||
amount = format_info.decimal.join(amount_parts)
|
amount = fmt.decimal.join(amount_parts)
|
||||||
line = ",".join([date, price.base, price.quote, amount])
|
line = ",".join([date, price.base, price.quote, amount])
|
||||||
lines.append(line)
|
lines.append(line)
|
||||||
return "\n".join(lines) + "\n"
|
return "\n".join(lines) + "\n"
|
||||||
|
|
|
@ -3,27 +3,27 @@ from datetime import datetime
|
||||||
from importlib.resources import read_text
|
from importlib.resources import read_text
|
||||||
|
|
||||||
from pricehist import __version__
|
from pricehist import __version__
|
||||||
from pricehist.formatinfo import FormatInfo
|
from pricehist.format import Format
|
||||||
|
|
||||||
|
|
||||||
class GnuCashSQL:
|
class GnuCashSQL:
|
||||||
def format(self, prices, format_info=FormatInfo()):
|
def format(self, prices, fmt=Format()):
|
||||||
fi = format_info
|
|
||||||
source = "pricehist"
|
source = "pricehist"
|
||||||
typ = "unknown"
|
typ = "unknown"
|
||||||
|
|
||||||
values_parts = []
|
values_parts = []
|
||||||
for price in prices:
|
for price in prices:
|
||||||
date = f"{price.date} {fi.time}"
|
date = f"{price.date} {fmt.time}"
|
||||||
|
amount = fmt.quantize(price.amount)
|
||||||
m = hashlib.sha256()
|
m = hashlib.sha256()
|
||||||
m.update(
|
m.update(
|
||||||
"".join(
|
"".join(
|
||||||
[date, price.base, price.quote, source, typ, str(price.amount)]
|
[date, price.base, price.quote, source, typ, str(amount)]
|
||||||
).encode("utf-8")
|
).encode("utf-8")
|
||||||
)
|
)
|
||||||
guid = m.hexdigest()[0:32]
|
guid = m.hexdigest()[0:32]
|
||||||
value_num = str(price.amount).replace(".", "")
|
value_num = str(amount).replace(".", "")
|
||||||
value_denom = 10 ** len(f"{price.amount}.".split(".")[1])
|
value_denom = 10 ** len(f"{amount}.".split(".")[1])
|
||||||
v = (
|
v = (
|
||||||
"("
|
"("
|
||||||
f"'{guid}', "
|
f"'{guid}', "
|
||||||
|
|
|
@ -1,29 +1,28 @@
|
||||||
from pricehist.formatinfo import FormatInfo
|
from pricehist.format import Format
|
||||||
|
|
||||||
|
|
||||||
class Ledger:
|
class Ledger:
|
||||||
def format(self, prices, format_info=FormatInfo()):
|
def format(self, prices, fmt=Format()):
|
||||||
fi = format_info
|
|
||||||
lines = []
|
lines = []
|
||||||
for price in prices:
|
for price in prices:
|
||||||
date = str(price.date).replace("-", fi.datesep)
|
date = str(price.date).replace("-", fmt.datesep)
|
||||||
|
|
||||||
amount_parts = f"{price.amount:,}".split(".")
|
amount_parts = f"{fmt.quantize(price.amount):,}".split(".")
|
||||||
amount_parts[0] = amount_parts[0].replace(",", format_info.thousands)
|
amount_parts[0] = amount_parts[0].replace(",", fmt.thousands)
|
||||||
amount = format_info.decimal.join(amount_parts)
|
amount = fmt.decimal.join(amount_parts)
|
||||||
|
|
||||||
qa_parts = [amount]
|
qa_parts = [amount]
|
||||||
if format_info.symbol == "left":
|
if fmt.symbol == "left":
|
||||||
qa_parts = [price.quote] + qa_parts
|
qa_parts = [price.quote] + qa_parts
|
||||||
elif format_info.symbol == "leftspace":
|
elif fmt.symbol == "leftspace":
|
||||||
qa_parts = [price.quote, " "] + qa_parts
|
qa_parts = [price.quote, " "] + qa_parts
|
||||||
elif format_info.symbol == "right":
|
elif fmt.symbol == "right":
|
||||||
qa_parts = qa_parts + [price.quote]
|
qa_parts = qa_parts + [price.quote]
|
||||||
else:
|
else:
|
||||||
qa_parts = qa_parts + [" ", price.quote]
|
qa_parts = qa_parts + [" ", price.quote]
|
||||||
quote_amount = "".join(qa_parts)
|
quote_amount = "".join(qa_parts)
|
||||||
|
|
||||||
lines.append(f"P {date} {fi.time} {price.base} {quote_amount}")
|
lines.append(f"P {date} {fmt.time} {price.base} {quote_amount}")
|
||||||
return "\n".join(lines) + "\n"
|
return "\n".join(lines) + "\n"
|
||||||
|
|
||||||
# TODO support additional details of the format:
|
# TODO support additional details of the format:
|
||||||
|
|
Loading…
Add table
Reference in a new issue