usawa

Signed, immutable accounting.
Log | Files | Refs | Submodules | LICENSE

commit 7313df604c86ea1f1666a56b9cd8f97b692a5e1e
parent b64223a28abf188ba06699b29315684dd652d666
Author: lash <dev@holbrook.no>
Date:   Fri,  3 Apr 2026 22:22:11 -0600

WIP enable multi account and amount interactive entry

Diffstat:
Mdummy/setup.cfg | 1+
Mdummy/tests/unit.py | 3+++
Mdummy/usawa/balance.py | 2+-
Mdummy/usawa/context.py | 19+++++++++++--------
Mdummy/usawa/data/usawa.ini | 1+
Mdummy/usawa/entry.py | 34+++++++++++++++++++++++++---------
Mdummy/usawa/runnable/asset.py | 46++++++++++++++++------------------------------
Mdummy/usawa/runnable/entry.py | 555+++++++++++++++++++++++++++++++++++++++++++++++++------------------------------
Mdummy/usawa/unit.py | 4+++-
9 files changed, 407 insertions(+), 258 deletions(-)

diff --git a/dummy/setup.cfg b/dummy/setup.cfg @@ -47,3 +47,4 @@ console_scripts = usawa-import = usawa.runnable.import:main usawa-export = usawa.runnable.export:main usawa-revert = usawa.runnable.revert:main + usawa-asset = usawa.runnable.asset:main diff --git a/dummy/tests/unit.py b/dummy/tests/unit.py @@ -47,6 +47,9 @@ class TestUnit(unittest.TestCase): self.assertEqual(v, 123) v = self.uidx_default.from_floatstring('BAR', '1.2345') self.assertEqual(v, 12345) + v = self.uidx_default.from_floatstring('FOO', '-3.390') + self.assertEqual(v, -3390) + def test_unit_rates(self): diff --git a/dummy/usawa/balance.py b/dummy/usawa/balance.py @@ -5,7 +5,7 @@ logg = logging.getLogger('usawa.balancer') class Balancer: - def __init__(self, unitindex): + def __init__(self, unitindex, value=None): self.uidx = unitindex self.r = 0 self.m = 0 diff --git a/dummy/usawa/context.py b/dummy/usawa/context.py @@ -5,7 +5,7 @@ from whee.valkey import ValkeyStore from whee.fs import FsStore from usawa import DemoWallet, Ledger from usawa.account import AccountIndex -from usawa.store import LedgerStore, EntryStore, KeyStore +from usawa.store import LedgerStore, EntryStore, KeyStore, AssetStore from usawa.resolve.fs import FSResolver logg = logging.getLogger('usawa.ctx') @@ -42,7 +42,7 @@ class UsawaContext: def get(self, k): - return self.o[k] + return self.o.get(k) def init(self, args, store_scope=None): @@ -96,7 +96,7 @@ class UsawaContext: if self.wallet != None: raise AttributeError('wallet set') if not self.signing: - self.wallet = self.store.get_default_key(DemoWallet) + self.wallet = self.keystore.get_default_key(DemoWallet) else: ops = int(self.cfg.get('WALLET_OPSLIMIT', 0)) mem = int(self.cfg.get('WALLET_MEMLIMIT', 0)) @@ -116,11 +116,14 @@ class UsawaContext: def load_accounts(self): + if self.uidx == None: + logg.info('no unit index, skip accounts') + return s = self.cfg.get('ACCOUNTS_FILE') if s: self.aidx = AccountIndex.from_file(self.uidx, s) else: - self.aidx = AccountIndex(uidx) + self.aidx = AccountIndex(self.uidx) if self.cfg.true('ACCOUNTS_STRICT'): self.aidx.lock() @@ -145,8 +148,11 @@ class UsawaContext: self.store = LedgerStore(self.db, self.ledger) self.keystore = self.store elif store_scope == 'asset' or store_score == 'entry': - self.store = EntryStore(self.db) self.keystore = KeyStore(self.db) + if store_scope == 'asset': + self.store = AssetStore(self.db) + else: + self.store = EntryStore(self.db) def create_resolver(self, resolver_type='fs'): @@ -158,6 +164,3 @@ class UsawaContext: else: logg.debug('missing resolver') logg.info('created {}'.format(self.resolver)) - - - diff --git a/dummy/usawa/data/usawa.ini b/dummy/usawa/data/usawa.ini @@ -1,6 +1,7 @@ [main] gpg_dir = ledger_file = +timezone = UTC [accounts] file = diff --git a/dummy/usawa/entry.py b/dummy/usawa/entry.py @@ -221,18 +221,19 @@ class Entry(UsawaElement): return Entry(serial, tx_date, ref=ref, description=description, parent=parent, tx_datereg=tx_datereg, unitindex=unitindex, tags=tags) - def clone(self, unitindex=None, description=None, invert_parts=False): + def clone(self, unitindex=None, description=None, invert_parts=False, include_parts=True): if description == None: description = self.description + ' (clone)' o = Entry(-1, self.dt, ref=self.ref, description=self.description, unitindex=unitindex, tags=self.tags) - for v in self.debit: - if invert_parts: - v.amount *= -1 - o.add_part(v) - for v in self.credit: - if invert_parts: - v.amount *= -1 - o.add_part(v) + if include_parts: + for v in self.debit: + if invert_parts: + v.amount *= -1 + o.add_part(v) + for v in self.credit: + if invert_parts: + v.amount *= -1 + o.add_part(v) return o @@ -680,6 +681,21 @@ class Entry(UsawaElement): return b.encode('utf-8') + + def tag(self, tag): + if self.tags == None: + self.tags = [] + elif not tag in self.tags: + self.tags.append(tag) + + + def untag(self, tag): + try: + self.tags.remove(tag) + except ValueError: + pass + + def __iter__(self): self.parts = self.debit + self.credit return self diff --git a/dummy/usawa/runnable/asset.py b/dummy/usawa/runnable/asset.py @@ -6,40 +6,13 @@ from whee.valkey import ValkeyStore import usawa.config from usawa import Asset +from usawa.context import UsawaContext from usawa.store import AssetStore logging.basicConfig(level=logging.WARNING) logg = logging.getLogger() -class Context: - - def __init__(self, args): - self.cfg = usawa.config.load_config(config_dir=args.c) - self.state = 0 - self.asset = None - - if self.cfg.get('STORE_TYPE') == 'valkey': - dbid = self.cfg.get('VALKEY_ID') - host = self.cfg.get('VALKEY_HOST') - port = self.cfg.get('VALKEY_PORT') - self.db = ValkeyStore('', host=host, port=port) - elif self.cfg.get('STORE_TYPE') == 'fs': - base = self.cfg.get('FSSTORE_BASE') - self.db = FsStore(base=base, dbname='usawa') - self.store = LedgerStore(self.db, self.ledger) - - if args.f: - self.asset = Asset.from_file(args.f, extref=args.e, mimetype=args.m, slug=args.n) - elif args.z: - digest = bytes.fromhex(args.z) - self.asset = Asset(digest=digest, ref=args.e, mimetype=args.m, slug=args.n) - else: - raise ValueError('Must provide either file path or digest') - - self.store.add_asset(self.asset, overwrite=True) - - argp = argparse.ArgumentParser() argp.add_argument('-e', type=str, help='unique reference of asset') argp.add_argument('-z', type=str, help='digest of asset') @@ -54,6 +27,19 @@ args = argp.parse_args() if args.v: logg.setLevel(getattr(logging, args.v.upper())) -ctx = Context(args) +cfg = usawa.config.load_config(config_dir=args.c) +ctx = UsawaContext(cfg, signing=False) +ctx.init(args, store_scope='asset') + +asset = None +if args.f: + asset = Asset.from_file(args.f, extref=args.e, mimetype=args.m, slug=args.n) +elif args.z: + digest = bytes.fromhex(args.z) + asset = Asset(digest=digest, ref=args.e, mimetype=args.m, slug=args.n) +else: + raise ValueError('Must provide either file path or digest') + +ctx.store.add_asset(asset, overwrite=True) -print(ctx.asset) +print(asset.ref) diff --git a/dummy/usawa/runnable/entry.py b/dummy/usawa/runnable/entry.py @@ -12,6 +12,7 @@ from whee.valkey import ValkeyStore from whee.fs import FsStore import usawa.config +from usawa.context import UsawaContext from usawa import Entry, Ledger, EntryPart, Asset, DemoWallet from usawa.store import LedgerStore from usawa.resolve.fs import FSResolver @@ -22,144 +23,226 @@ logging.basicConfig(level=logging.WARNING) logg = logging.getLogger() - -class Context: - - def __init__(self, args): - self.cfg = usawa.config.load_config(config_dir=args.c) - self.state = 0 - self.commit = args.commit - - # entry parts - self.description = None - self.src = [] - self.dst = [] - self.amount = None - self.output = None - self.f = None - if args.t: - self.txdate = datetime.datetime.fromisoformat(args.t) +def try_entry_uuid(ctx, v): + v = uuid.UUID(v) + entry = Entry.empty(ref=str(v)) + return ctx.store.get_draft(entry) + + +def try_entry_serial(ctx, v): + v = int(v) + entry = Entry(v, None) + return ctx.store.get_entry(entry) + + +def try_entry_digest(ctx, k): + if isinstance(k, str): + k = bytes.fromhex(k) + if len(k) != 64: + raise ValueError('invalid digest length') + v = ctx.resolver.get(k) + return Entry.from_string(v, ctx.uidx) + + +def try_entry(ctx, args): + if not args.entry: + return Entry.empty(unitindex=ctx.uidx) + + try: + return try_entry_uuid(ctx, args.entry) + except ValueError: + pass + + try: + return try_entry_serial(ctx, args.entry) + except ValueError: + pass + + try: + return try_entry_digest(ctx, args.entry) + except ValueError: + pass + + return None + + +#class Context: +# +# def __init__(self, args): +# self.cfg = usawa.config.load_config(config_dir=args.c) +# self.state = 0 +# self.commit = args.commit +# +# # entry parts +# self.description = None +# self.src = [] +# self.dst = [] +# self.amount = None +# self.output = None +# self.f = None +# if args.t: +# self.txdate = datetime.datetime.fromisoformat(args.t) +# else: +# self.txdate = datetime.datetime.now(datetime.UTC) +# self.attach = [] +# +# # set up ledger +# s = args.ledger_file +# if not s: +# try: +# s = self.cfg.get('MAIN_LEDGER_FILE') +# except KeyError: +# pass +# if not s: +# raise ValueError('ledger file required') +# self.ledger = Ledger.from_file(s) +# self.fp = args.o +# self.fp_bak = None +# if self.fp == None: +# self.fp = s +# self.fp_bak = s + '_' + str(self.ledger.serial) +# self.uidx = self.ledger.uidx +# +# # set up accounts hierarchy, if applicable +# self.accounts = None +# s = self.cfg.get('ACCOUNTS_FILE') +# if s: +# self.accounts = AccountIndex.from_file(self.uidx, s) +# else: +# self.accounts = AccountIndex(self.uidx) +# if self.cfg.true('ACCOUNTS_STRICT'): +# self.accounts.lock() +# +# self.entry = Entry(-1, self.txdate, ref=args.e) +# self.db = None +# if self.cfg.get('STORE_TYPE') == 'valkey': +# dbid = self.cfg.get('VALKEY_ID') +# host = self.cfg.get('VALKEY_HOST') +# port = self.cfg.get('VALKEY_PORT') +# self.db = ValkeyStore('', host=host, port=port) +# elif self.cfg.get('STORE_TYPE') == 'fs': +# base = self.cfg.get('FSSTORE_BASE') +# self.db = FsStore(base=base, dbname='usawa') +# self.store = LedgerStore(self.db, self.ledger) +# ops = int(self.cfg.get('WALLET_OPSLIMIT', 0)) +# mem = int(self.cfg.get('WALLET_MEMLIMIT', 0)) +# pw = self.cfg.get('WALLET_KEY_PASSPHRASE') +# if args.p and pw == None: +# pw = getpass.getpass("passphrase: ") +# self.wallet = self.store.get_key(DemoWallet, passphrase=pw, opslimit=ops, memlimit=mem) +# self.ledger.set_wallet(self.wallet) +# if args.e: +# self.entry = self.store.get_draft(self.entry) +# self.state = 1 +# else: +# self.store.put_draft(self.entry) +# self.ref = self.entry.get_ref() +# +# self.base = self.uidx.base +# self.k = 'src' +# self.havedst = False +# self.i = 0 +# self.resolver = None +# resolver_path = self.cfg.get('FS_RESOLVER_STORE_PATH', args.r) +# if resolver_path != None: +# self.resolver = FSResolver(os.path.realpath(resolver_path)) +# + +def parse_type(ctx, 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(ctx, v): + return ctx.uidx.sym(v) + + +#def parse_account(ctx, 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(ctx, sym, v): + return ctx.uidx.from_floatstring(sym, v) + + +def parse_side(ctx, v): + for k in ['src', 'dst']: + if k.startswith(v): + return k + raise ValueError('invalid side: ' + v) + + +def choose_account(ctx, include_create=False): + r = None + accounts_r = [] + while not r: + a = None + if len(accounts_r) == 0: + a = input('enter account: ') + if a == 'q': + raise AbortMenu() + elif len(accounts_r) == 1: + r = accounts_r[0] + break else: - self.txdate = datetime.datetime.now(datetime.UTC) - self.attach = [] - - # set up ledger - s = args.ledger_file - if not s: + v = input('choose match: ') + i = None + if v == 'q': + raise AbortMenu() try: - s = self.cfg.get('MAIN_LEDGER_FILE') - except KeyError: - pass - if not s: - raise ValueError('ledger file required') - self.ledger = Ledger.from_file(s) - self.fp = args.o - self.fp_bak = None - if self.fp == None: - self.fp = s - self.fp_bak = s + '_' + str(self.ledger.serial) - self.uidx = self.ledger.uidx - - # set up accounts hierarchy, if applicable - self.accounts = None - s = self.cfg.get('ACCOUNTS_FILE') - if s: - self.accounts = AccountIndex.from_file(self.uidx, s) - else: - self.accounts = AccountIndex(self.uidx) - if self.cfg.true('ACCOUNTS_STRICT'): - self.accounts.lock() - - self.entry = Entry(-1, self.txdate, ref=args.e) - self.db = None - if self.cfg.get('STORE_TYPE') == 'valkey': - dbid = self.cfg.get('VALKEY_ID') - host = self.cfg.get('VALKEY_HOST') - port = self.cfg.get('VALKEY_PORT') - self.db = ValkeyStore('', host=host, port=port) - elif self.cfg.get('STORE_TYPE') == 'fs': - base = self.cfg.get('FSSTORE_BASE') - self.db = FsStore(base=base, dbname='usawa') - self.store = LedgerStore(self.db, self.ledger) - ops = int(self.cfg.get('WALLET_OPSLIMIT', 0)) - mem = int(self.cfg.get('WALLET_MEMLIMIT', 0)) - pw = self.cfg.get('WALLET_KEY_PASSPHRASE') - if args.p and pw == None: - pw = getpass.getpass("passphrase: ") - self.wallet = self.store.get_key(DemoWallet, passphrase=pw, opslimit=ops, memlimit=mem) - self.ledger.set_wallet(self.wallet) - if args.e: - self.entry = self.store.get_draft(self.entry) - self.state = 1 - else: - self.store.put_draft(self.entry) - self.ref = self.entry.get_ref() - - self.base = self.uidx.base - self.k = 'src' - self.havedst = False - self.i = 0 - self.resolver = None - resolver_path = self.cfg.get('FS_RESOLVER_STORE_PATH', args.r) - if resolver_path != None: - self.resolver = FSResolver(os.path.realpath(resolver_path)) - - - 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 parse_side(self, v): - for k in ['src', 'dst']: - if k.startswith(v): - return k - raise ValueError('invalid side: ' + v) - - - def add_part(self, part): - logg.info('add part {}'.format(part)) - self.part.append(part) - - - def validate(self): - if len(self.src) == 0: - raise ValueError('no src') - if len(self.dst) == 0: - raise ValueError('no dst') - if self.ref == None: - raise ValueError('invalid ref') - - - def parse_txdate(self, v): - return datetime.date.fromisoformat(v) + i = int(v) + except ValueError: + logg.debug('Invalid number, going back to search') + if i != None: + try: + r = accounts_r[i] + except IndexError: + logg.error('Number out of range') + continue + accounts_r = [] + ctx.aidx.set_filter(path=a) + i = 0 + for a in ctx.aidx: + accounts_r.append(a) + print('{} {}'.format(i, a)) + i += 1 + if i == 0: + logg.debug('no accounts found') + ctx.aidx.reset_filter() + return r + + +def add_part(self, part): + logg.info('add part {}'.format(part)) + self.part.append(part) + + +def validate(self): + if len(self.src) == 0: + raise ValueError('no src') + if len(self.dst) == 0: + raise ValueError('no dst') + if self.ref == None: + raise ValueError('invalid ref') + + +def parse_txdate(ctx, v): + return datetime.date.fromisoformat(v) def input_or_default(prompt, default=None, postfix=': ', validate_fn=None): @@ -176,60 +259,104 @@ def input_or_default(prompt, default=None, postfix=': ', validate_fn=None): def do_interactive_one(ctx): - ctx.description = input_or_default('Entry description', ctx.description) - ctx.ref = input_or_default('External ref', ctx.ref) - dt = datetime.datetime.utcnow() - dt = input_or_default('Transaction date(time)', ctx.txdate) - if isinstance(dt, str): - dt = ctx.parse_txdate(dt) - ctx.txdate = dt + ctx.set('havesrc', False) + ctx.set('havedst', False) + ctx.set('partk', 'src') + v = input_or_default('Entry description', ctx.get('description')) + ctx.set('description', v) + v = input_or_default('External ref', ctx.get('ref')) + ctx.set('ref', v) + v = input_or_default('Transaction date(time)', ctx.get('dt')) + if isinstance(v, str): + v = parse_txdate(ctx, v) + ctx.set('dt', v) + + +def handle_tag(ctx, entry, v): + r = input('Tag: ') + if v == '+': + entry.tag(r) + elif v == '-': + entry.untag(r) + + +def handle_input(ctx, entry, v): + if v == 'i' or v == 'o': + k = 'src' + if v == 'o': + k = 'dst' + enter_part(ctx, entry, side=k) + return True + if v == 'q': + raise StopIteration() + if v == '+' or v == '-': + r = handle_tag(ctx, entry, v) + return True + if v == 'w': + ctx.set('commit', True) + return False + if v == 't': + return False + logg.error('invalid input') + return True def do_interactive_two(ctx, entry): r = True while r: - try: - r = enter_part(ctx, entry) - except Exception as e: - logg.error('err {}'.format(e)) - pass - - -def enter_part(ctx, entry): - if ctx.i > 0: - if ctx.havedst: - ctx.k = '' - else: - ctx.k = 'dst' - v = input_or_default('Entry side', ctx.k) - if v == '': - return False - ctx.k = ctx.parse_side(v) - if v == 'dst': - ctx.havedst = True - - v = input_or_default('Entry {} unit'.format(ctx.k), ctx.base) - unit = ctx.parse_unit(v) - if ctx.base != unit: - ctx.base = unit - - v = input_or_default('Entry {} type'.format(ctx.k)) - typ = ctx.parse_type(v) - - v = input_or_default('Entry {} account'.format(ctx.k)) - account = ctx.parse_account(v, sym=unit, typ=typ) - - amount = None - v = input_or_default('Entry {} amount'.format(ctx.k), amount) - amount = ctx.parse_amount(ctx.uidx, unit, v) - - isdebit = ctx.k=='src' - part = EntryPart(unit, typ.value, account.to_path(display=AccountDisplay.path), amount, debit=isdebit) - entry.add_part(part) - - ctx.i += 1 +# try: +# r = enter_part(ctx, entry) +# except Exception as e: +# logg.error('err {}'.format(e)) +# pass + v = input("> ") + r = handle_input(ctx, entry, v) + + +def enter_part(ctx, entry, side=None): + if side != None: + ctx.set('partk', side) + elif len(entry.debit) > 0: + if ctx.get('havedst'): + ctx.set('partk', 'dst') + else: + ctx.set('partk', 'src') + v = input_or_default('Entry side', ctx.get('partk')) + if v == '': + return False + k = parse_side(ctx, v) + ctx.set('partk', k) + + #v = input_or_default('Entry {} account'.format(ctx.get('partk'))) + #account = parse_account(ctx, v, sym=unit, typ=typ) + v = choose_account(ctx) + account = Account.from_path(v) + + #amount = None + k = ctx.get('partk') + v = input_or_default('Account {} amount'.format(account)) + amount = parse_amount(ctx, account.sym, v) + + isdebit = k=='src' + part = EntryPart(account.sym, account.typ.value.lower(), account.to_path(display=AccountDisplay.path), amount, debit=isdebit) + logg.debug('addpart {}'.format(part)) + entry.add_part(part) + + if ctx.get('partk') == 'src': + ctx.set('havesrc', True) + + if ctx.get('partk') == 'dst': + ctx.set('havedst', True) + + return True + + +def do_prepare(ctx, entry=None): + uu = uuid.uuid4() + ctx.set('ref', str(uu)) + dt = datetime.datetime.now(datetime.UTC) + ctx.set('dt', dt) - return True def main(): def croak(*args, **kwargs): @@ -239,35 +366,40 @@ def main(): signal.signal(signal.SIGTERM, croak) argp = argparse.ArgumentParser() - argp.add_argument('-e', type=str, help='unique reference of entry') argp.add_argument('-x', type=str, action='append', default=[], help='unique reference of attachment') argp.add_argument('-z', type=str, action='append', default=[], help='sum of attachment') argp.add_argument('-v', type=str, choices=['info','debug','warning','error'], help='be verbose') argp.add_argument('-c', type=str, help='override config dir') argp.add_argument('-t', type=str, help='transaction date or datetime') - argp.add_argument('-r', type=str, help='export resolver spec') + #argp.add_argument('-r', type=str, help='export resolver spec') + argp.add_argument('-i', type=str, help='input ledger state') argp.add_argument('-o', type=str, help='output ledger state') argp.add_argument('-p', action='store_true', help='unlock wallet with password') argp.add_argument('--commit', action='store_true', dest='commit', help='commit to ledger') - argp.add_argument('ledger_file', type=str, help='ledger file') + argp.add_argument('entry', nargs='?', type=str, help='entry to edit') args = argp.parse_args() if args.v: logg.setLevel(getattr(logging, args.v.upper())) - ctx = Context(args) - - entry = None - if ctx.state == 0: - do_interactive_one(ctx) - entry = Entry.empty(ref=ctx.ref, unitindex=ctx.uidx, tx_date=ctx.txdate) - entry = ctx.store.get_draft(entry) - entry.description = ctx.description - entry.dt = ctx.txdate - do_interactive_two(ctx, entry) - else: - entry = ctx.entry - + #ctx = Context(args) + + cfg = usawa.config.load_config(config_dir=args.c) + ctx = UsawaContext(cfg) + #dp = os.path.realpath(args.src_dir) + #ctx.set('srcdir', dp) + ctx.init(args) + ctx.set('unitbase', ctx.uidx.base) + ctx.set('commit', False) + + entry = try_entry(ctx, args) + if entry.serial > 0: + raise NotImplementedError('entry edit not yet implemented') + do_prepare(ctx, entry=entry) +# entry = Entry.empty(ref=ctx.get('ref'), unitindex=ctx.uidx, tx_date=ctx.get('dt')) + #entry = ctx.store.get_draft(entry) + #entry.description = ctx.get('description') + #entry.dt = ctx.txdate for v in args.x: k = uuid.UUID(v) @@ -280,23 +412,28 @@ def main(): asset = ctx.store.get_asset(asset) entry.attach(asset) - if ctx.commit: - if entry.serial != -1: - raise AttributeError('entry draft already marked as committed') - entry.serial = ctx.ledger.next_serial() + do_interactive_one(ctx) + try: + do_interactive_two(ctx, entry) + except StopIteration: + sys.exit(0) +# if ctx.commit: +# if entry.serial != -1: +# raise AttributeError('entry draft already marked as committed') +# entry.serial = ctx.ledger.next_serial() - v = input_or_default('Commit? (type YES, any other input is no)', '') - if v == 'YES': +# v = input_or_default('Commit? (type YES, any other input is no)', '') + if ctx.get('commit'): entry.parent = ctx.ledger.cur entry.serial = ctx.ledger.next_serial() entry.sign(ctx.wallet) ctx.store.add_entry(entry, update_ledger=True) ctx.ledger.truncate() ctx.ledger.sign() - if ctx.fp_bak != None: - shutil.copy(ctx.fp, ctx.fp_bak) - f = open(ctx.fp, 'w') +# if ctx.fp_bak != None: +# shutil.copy(ctx.fp, ctx.fp_bak) + f = open(ctx.ledger_path_out, 'w') f.write(ctx.ledger.to_string()) f.close() if ctx.resolver != None: diff --git a/dummy/usawa/unit.py b/dummy/usawa/unit.py @@ -112,6 +112,7 @@ class UnitIndex: 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.rate[o.get('sym')] = 1000000000 r.check() return r @@ -230,9 +231,10 @@ class UnitIndex: if l < c: r += '0' * (c - l) r = s[0] + r + r = int(r) if neg: r *= -1 - return int(r) + return r def from_float(self, sym, v):