commit 2a344b0820949f98d49375a6c3d9e6affd586b65
parent 4b9d730cbefa7a0cf4100013a44d3c85c48001eb
Author: lash <dev@holbrook.no>
Date: Sat, 28 Mar 2026 19:14:32 -0600
Account searcher, multi-unit ledger
Diffstat:
7 files changed, 66 insertions(+), 21 deletions(-)
diff --git a/dummy/usawa/account.py b/dummy/usawa/account.py
@@ -1,5 +1,6 @@
import enum
import logging
+import re
from .error import AccountError
@@ -40,6 +41,7 @@ class AccountType(enum.Enum):
expense = 'Expense'
imprt = 'Import'
export = 'Export'
+ equity = 'Equity'
class AccountDisplay(enum.IntEnum):
@@ -149,10 +151,14 @@ class AccountIndex:
return self.check(o[0], o[1], o[2])
- def set_filter(self, sym=None, typ=None, display=AccountDisplay.full):
+ def set_filter(self, sym=None, typ=None, path=None, display=AccountDisplay.full):
if typ != None:
typ = typ.value
- self.iterfilter = (sym, typ, display,)
+ self.iterfilter = (sym, typ, path, display,)
+
+
+ def reset_filter(self):
+ self.iterfilter = None
def __iter__(self, fltr=None):
@@ -176,12 +182,15 @@ class AccountIndex:
if fltr[1] != None:
if typ.casefold() != fltr[1].casefold():
continue
+ if fltr[2] != None:
+ if not re.search(fltr[2], v, re.IGNORECASE):
+ continue
if self.iterval == None:
self.iterval = []
if fltr != None:
- if fltr[2] != AccountDisplay.full:
+ if fltr[3] != AccountDisplay.full:
path = v
- if fltr[2] == AccountDisplay.typ:
+ if fltr[3] == AccountDisplay.typ:
path = typ + '/' + path
self.iterval.append(path)
return self
diff --git a/dummy/usawa/asset.py b/dummy/usawa/asset.py
@@ -137,7 +137,7 @@ class Asset(UsawaElement):
"""
@staticmethod
- def from_file(filepath, description=None, slug=None, mimetype=None, ref=None, extref=None):
+ def from_file(filepath, description=None, slug=None, mimetype=None, ref=None, extref=None, w=None):
f = open(filepath, "rb")
return Asset.from_io(
f,
@@ -148,6 +148,7 @@ class Asset(UsawaElement):
mimetype=mimetype,
ref=ref,
extref=extref,
+ w=w,
)
"""Instantiate an asset object from an input stream.
@@ -175,11 +176,13 @@ class Asset(UsawaElement):
@staticmethod
def from_io(
- io, src, closer=None, description=None, slug=None, mimetype=None, extref=None, ref=None
+ io, src, closer=None, description=None, slug=None, mimetype=None, extref=None, ref=None, w=None,
):
o = Asset(ref=ref)
h = hashlib.sha512()
b = io.read(BLOCKSIZE)
+ if w != None:
+ w.write(b)
if mimetype == None:
v = mimetypes.guess_file_type(src, strict=True)
if v != None:
@@ -194,13 +197,16 @@ class Asset(UsawaElement):
b = io.read(BLOCKSIZE)
if len(b) == 0:
break
+ if w != None:
+ w.write(b)
h.update(b)
c += len(b)
if closer != None:
closer()
o.digest = h.digest()
- (o.slug, o.ext) = parse_path(src)
+ if src != None:
+ (o.slug, o.ext) = parse_path(src)
s = mimetypes.guess_extension(o.mime, strict=True)
if s != None:
o.ext = s[1:]
diff --git a/dummy/usawa/entry.py b/dummy/usawa/entry.py
@@ -238,6 +238,7 @@ class Entry(UsawaElement):
self.credit.append(part)
if self.balancer != None:
self.balancer.apply_part(part)
+ logg.debug('add part {} to entry {}'.format(part, self))
"""Append a single media asset to the attachment list for the entry.
diff --git a/dummy/usawa/ledger.py b/dummy/usawa/ledger.py
@@ -572,6 +572,7 @@ class Ledger(UsawaElement):
units = tree.find('units', namespaces=nsmap())
unitindex = UnitIndex.from_tree(units)
+
unit = units.get('base')
part = tree.find('incoming', namespaces=nsmap())
serial = int(part.get('serial'))
@@ -607,7 +608,6 @@ class Ledger(UsawaElement):
liability = int(v.find('liability', namespaces=nsmap()).text)
sym = v.get('unit')
ledger.running[sym] = RunningTotal(sym, unitindex, income=income, expense=expense, asset=asset, liability=liability)
- logg.debug(r.running[sym])
if ledger.running.get(unit) == None:
ledger.running[unit] = RunningTotal(unit, unitindex)
diff --git a/dummy/usawa/runnable/create.py b/dummy/usawa/runnable/create.py
@@ -20,8 +20,8 @@ logg = logging.getLogger()
class Context:
def __init__(self):
- self.unit = None
- self.unit_precision = None
+ #self.unit = None
+ #self.unit_precision = None
self.uidx = None
self.topic = None
self.uri = None
@@ -46,9 +46,32 @@ class Context:
@staticmethod
def from_args(args):
ctx = Context()
- ctx.unit = args.unit
- ctx.unit_precision = args.unit_precision
- ctx.uidx = UnitIndex(ctx.unit, precision=ctx.unit_precision)
+ unitspec = None
+ unit = None
+ unit_precision = None
+ try:
+ unitspec = args.unit[0].split(':')
+ unit = unitspec[0]
+ logg.debug('have unit {}'.format(unit))
+ unit_precision = None
+ try:
+ unit_precision = unitspec[1]
+ except IndexError:
+ unit_precision = UnitIndex.default_precision
+ except IndexError:
+ unit = UnitIndex.default_unit
+ unit_precision = UnitIndex.default_precision
+ ctx.uidx = UnitIndex(unit, precision=unit_precision)
+ for v in args.unit[1:]:
+ unitspec = v.split(':')
+ unit = unitspec[0]
+ unit_precision = None
+ try:
+ unit_precision = unitspec[1]
+ except IndexError:
+ unit_precision = UnitIndex.default_precision
+ ctx.uidx.add(unit, precision=unit_precision)
+
ctx.topic = args.topic
if ctx.topic == None:
ctx.topic = str(uuid.uuid4())
@@ -63,8 +86,8 @@ class Context:
def validate(self):
self.topic = parse_topic(self.topic)
self.uri = parse_uri(self.uri)
- self.unit = parse_unit(self.unit)
- self.unit_precision = parse_unit_precision(self.unit_precision)
+ #self.unit = parse_unit(self.unit)
+ #self.unit_precision = parse_unit_precision(self.unit_precision)
return self
@@ -112,11 +135,10 @@ def input_or_default(prompt, default=None, postfix=': ', validate_fn=None):
argp = argparse.ArgumentParser()
argp.add_argument('-i', action='store_true', help='interactive edit')
argp.add_argument('-t', dest='topic', type=str, help='ledger topic')
-argp.add_argument('-u', '--unit', type=str, default=UnitIndex.default_unit, help='Unit to use for transaction')
+argp.add_argument('-u', '--unit', type=str, action='append', default=[], help='Units to use for transaction')
argp.add_argument('-o', type=str, dest='output', help='output file for updated XML document')
argp.add_argument('-l', type=str, dest='src_uri', help='URI for data source')
argp.add_argument('-c', type=str, help='override config dir')
-argp.add_argument('--unit-precision', type=int, default=UnitIndex.default_precision, help='Unit precision')
arg = argp.parse_args()
ctx = Context.from_args(arg)
cfg = usawa.config.load_config(config_dir=arg.c)
diff --git a/dummy/usawa/runnable/entry.py b/dummy/usawa/runnable/entry.py
@@ -198,7 +198,6 @@ def do_interactive_one(ctx):
def do_interactive_two(ctx, entry):
-
r = True
while r:
try:
diff --git a/dummy/usawa/unit.py b/dummy/usawa/unit.py
@@ -57,6 +57,8 @@ class UnitIndex:
def val(self, unit, amount):
+ if isinstance(amount, float):
+ amount = self.from_float(unit, amount)
r = self.rate[unit] * amount
logg.debug('rate {} {} {}'.format(r, amount, self.rate[unit]))
# TODO: embed in rate
@@ -107,9 +109,10 @@ class UnitIndex:
base = tree.get('base')
r = UnitIndex(base)
for o in tree.iter(NSPREFIX + 'unit'):
- logg.debug('add unit ' + o.get('sym'))
- r.detail[o.get('sym')] = int(o.find('precision', namespaces=nsmap()).text)
- r.check()
+ precision = int(o.find('precision', namespaces=nsmap()).text)
+ logg.debug('add unit {} precision {}'.format(o.get('sym'), precision))
+ r.detail[o.get('sym')] = precision
+ r.check()
return r
@@ -232,6 +235,11 @@ class UnitIndex:
return int(r)
+ def from_float(self, sym, v):
+ adj = 10 ** self.detail[sym]
+ return int(v * adj)
+
+
"""Generate the simple data structure used for rencode serialization.
:returns: data structure