commit d72a9c8fbb56ab51b6739f37750b68ff57f6fe9f
parent c20eceaa8616fabcc5c8342f4d25192a51f76ec2
Author: lash <dev@holbrook.no>
Date: Sun, 22 Mar 2026 19:36:25 -0600
Integrate account listing in craft tool
Diffstat:
4 files changed, 169 insertions(+), 13 deletions(-)
diff --git a/dummy/usawa/account.py b/dummy/usawa/account.py
@@ -64,12 +64,19 @@ class Account:
return Account(o[0], o[1], o[2])
- def to_path(self):
+ def to_path(self, display=AccountDisplay.full):
path = '/'.join(self.segments)
- path = '{}.{}/{}'.format(self.sym, self.typ.value.lower(), path)
+ if display == AccountDisplay.full:
+ path = '{}.{}/{}'.format(self.sym, self.typ.value.lower(), path)
+ elif display == AccountDisplay.typ:
+ path = '{}/{}'.format(self.typ.value.lower(), path)
return path
+ def __str__(self):
+ return self.to_path()
+
+
class AccountIndex:
def __init__(self, unitindex): #, pathvalidator=default_check):
@@ -118,6 +125,7 @@ class AccountIndex:
path = account.to_path()
logg.info('add account {}'.format(path))
self.accounts[sym].append(path)
+ return account
def lock(self):
@@ -126,10 +134,17 @@ class AccountIndex:
def check(self, sym, typ, path):
s = '{}.{}/{}'.format(sym, typ.value.lower(), path)
+ r = False
try:
- return s in self.accounts[sym]
+ r = s in self.accounts[sym]
except KeyError:
- return False
+ return None
+ return s
+
+
+ def check_path(self, path):
+ o = Account.path_parser(path)
+ return self.check(o[0], o[1], o[2])
def set_filter(self, sym=None, typ=None, display=AccountDisplay.full):
diff --git a/dummy/usawa/data/usawa.ini b/dummy/usawa/data/usawa.ini
@@ -1,8 +1,11 @@
[main]
gpg_dir =
-accounts_file =
legder_file =
+[accounts]
+file =
+strict = 0
+
[valkey]
id = 2434
host = localhost
diff --git a/dummy/usawa/entry.py b/dummy/usawa/entry.py
@@ -207,9 +207,9 @@ class Entry(UsawaElement):
@staticmethod
- def empty():
+ def empty(*args, **kwargs):
dt = datetime.datetime.utcnow()
- return Entry(-1, dt)
+ return Entry(-1, dt, *args, **kwargs)
"""Add an entry part to the entry.
diff --git a/dummy/usawa/runnable/craft.py b/dummy/usawa/runnable/craft.py
@@ -4,9 +4,10 @@ import logging
from whee.valkey import ValkeyStore
import usawa.config
-from usawa import Entry, Ledger
+from usawa import Entry, Ledger, EntryPart
from usawa.store import EntryStore
-from usawa.account import AccountIndex
+from usawa.account import Account, AccountIndex, AccountType, AccountDisplay
+from usawa.constant import CATEGORIES
logging.basicConfig(level=logging.WARNING)
logg = logging.getLogger()
@@ -17,7 +18,18 @@ class Context:
def __init__(self, args):
self.cfg = usawa.config.load_config(config_dir=args.c)
self.cmd = args.cmd
-
+ self.state = 0
+
+ # entry parts
+ self.description = None
+ self.src = [None, None, None]
+ self.dst = [None, None, None]
+ self.amount = None
+ self.part = []
+ self.output = None
+ self.f = None
+ self.attach = []
+
# set up ledger
s = args.l
if not s:
@@ -28,14 +40,19 @@ class Context:
if not s:
raise ValueError('ledger file required')
self.ledger = Ledger.from_file(s)
+ self.uidx = self.ledger.uidx
+ self.src[2] = self.uidx.base
+ self.dst[2] = self.uidx.base
# set up accounts hierarchy, if applicable
self.accounts = None
- s = self.cfg.get('MAIN_ACCOUNTS_FILE')
+ s = self.cfg.get('ACCOUNTS_FILE')
if s:
- self.accounts = AccountIndex.from_file(self.ledger.uidx, s)
+ self.accounts = AccountIndex.from_file(self.uidx, s)
else:
- self.accounts = AccountIndex(self.ledger.uidx)
+ self.accounts = AccountIndex(self.uidx)
+ if self.cfg.true('ACCOUNTS_STRICT'):
+ self.accounts.lock()
self.entry = Entry.empty()
self.db = None
@@ -47,8 +64,59 @@ class Context:
self.store = EntryStore(self.db)
if args.r:
self.entry = self.store.get_draft(self.entry)
+ self.state = 1
else:
self.store.put_draft(self.entry)
+ self.ref = self.entry.get_ref()
+
+
+ def parse_type(self, v):
+ v = v.lower()
+ r = None
+ for k in CATEGORIES:
+ if k.startswith(v):
+ r = k
+ logg.info("expanded input '{}' to category {}".format(v, r))
+ break
+ if not r:
+ raise ValueError('invalid type: ' + v)
+ o = getattr(AccountType, r)
+ logg.debug('accounttype {}'.format(o))
+ return o
+
+
+ def parse_unit(self, v):
+ return self.uidx.sym(v)
+
+
+ def parse_account(self, v, typ, sym):
+ account = self.accounts.check(sym, typ, v)
+ if account:
+ account = Account.from_path(account)
+ else:
+ account = self.accounts.add(v, sym=sym, typ=typ)
+ return account
+
+
+ def parse_amount(self, uidx, sym, v):
+ return uidx.from_floatstring(sym, v)
+
+
+ def add_part(self, part):
+ logg.info('add part {}'.format(part))
+ self.part.append(part)
+
+
+ def validate(self):
+ for v in self.src:
+ if v == None:
+ raise ValueError('invalid src')
+ for v in self.dst:
+ if v == None:
+ raise ValueError('invalid dst')
+ if self.ref == None:
+ raise ValueError('invalid ref')
+
argp = argparse.ArgumentParser()
@@ -63,3 +131,73 @@ if args.v:
logg.setLevel(getattr(logging, args.v.upper()))
ctx = Context(args)
+
+
+
+
+def input_or_default(prompt, default=None, postfix=': ', validate_fn=None):
+ if default != None:
+ postfix = ' [{}]'.format(default) + postfix
+ v = input(prompt + postfix)
+ if len(v) == 0:
+ if default == None:
+ raise ValueError('empty value and no default')
+ v = default
+ if validate_fn != None:
+ validate_fn(v)
+ return v
+
+
+def do_interactive(ctx):
+ v = input_or_default('Entry description', ctx.description)
+ ctx.description = v
+
+ amounts = {
+ 'src': None,
+ 'dst': None,
+ }
+
+ for k in ['src', 'dst']:
+ o = vars(ctx)
+ #v = input('Entry {} type: '.format(k))
+
+ v = input_or_default('Entry {} unit'.format(k), o[k][2])
+ unit = ctx.parse_unit(v)
+ o[k][2] = unit
+
+ v = input_or_default('Entry {} type'.format(k), o[k][0])
+ typ = ctx.parse_type(v)
+ o[k][0] = typ
+
+ v = input_or_default('Entry {} account'.format(k), o[k][1])
+ account = ctx.parse_account(v, sym=unit, typ=typ)
+ o[k][1] = account
+
+ amount = None
+ if k =='dst':
+ amount = amounts['src']
+ v = input_or_default('Entry {} amount'.format(k), amount)
+ amount = ctx.parse_amount(ctx.uidx, unit, v)
+ amount *= -1
+ amounts[k] = str(amount)
+
+ part = EntryPart(unit, typ.value, account.to_path(display=AccountDisplay.path), amount, debit=k=='src')
+ ctx.add_part(part)
+
+ ctx.ref = input_or_default('External ref', ctx.ref)
+
+ #output = input_or_default('Output file', ctx.output)
+ #logg.debug('output {}'.format(output))
+ #return ctx.open(output)
+
+
+if ctx.state == 0:
+ do_interactive(ctx)
+ ctx.validate()
+ #entry = Entry(-1, dt, parent=ledger.current(), description=ctx.description, ref=ctx.ref, unitindex=ctx.uidx)
+ entry = Entry.empty(description=ctx.description, ref=ctx.ref, unitindex=ctx.uidx)
+ entry.add_part(ctx.part[0], debit=True)
+ entry.add_part(ctx.part[1])
+
+ctx.store.put_draft(entry)
+print(entry)