usawa

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

commit f0ce3a7a515f68f87ba2daea61eacf2ae2a26e8f
parent 0a1a073f082e51594e6e89f920288506eaccc929
Author: lash <dev@holbrook.no>
Date:   Sat,  4 Apr 2026 10:44:44 -0600

Add attachment opener to interactive entry edit

Diffstat:
Mdummy/usawa/cli/entry.py | 73++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++---------
1 file changed, 64 insertions(+), 9 deletions(-)

diff --git a/dummy/usawa/cli/entry.py b/dummy/usawa/cli/entry.py @@ -1,6 +1,10 @@ import uuid import datetime import logging +import sys +import tempfile +import subprocess +import mimetypes from usawa import Asset, Entry, EntryPart from usawa.account import Account, AccountDisplay @@ -75,7 +79,26 @@ def handle_tag(ctx, entry, v): entry.untag(r) - +def handle_view(ctx, entry, o): + if len(entry.attachment) == 0: + logg.error('no asset to resolve') + return + if ctx.resolver == None: + logg.error('no resolver to provide view') + return + asset = entry.attachment[0] + ext = mimetypes.guess_extension(asset.mime) + (f, fp) = tempfile.mkstemp(suffix=ext) + k = asset.get_digest() + asset_data = ctx.resolver.get(k) + f = open(fp, 'wb') + f.write(asset_data) + f.close() + subprocess.Popen( + ['xdg-open', fp], + stderr=subprocess.DEVNULL, + stdout=subprocess.DEVNULL, + ) def input_or_default(prompt, default=None, postfix=': ', validate_fn=None): @@ -120,6 +143,8 @@ def try_entry(ctx, entry_spec): return try_entry_uuid(ctx, entry_spec) except ValueError: pass + except FileNotFoundError: + pass try: return try_entry_serial(ctx, entry_spec) @@ -130,21 +155,32 @@ def try_entry(ctx, entry_spec): return try_entry_digest(ctx, entry_spec) except ValueError: pass + return None class EntrySession: - def __init__(self, ctx, entry_spec=None): + def __init__(self, ctx, entry=None, description=None, extref=None, dt=None, ref=None): + # context props self.ctx = ctx self.unitbase = ctx.uidx.base + + # override props + self.description = description + self.ref = ref + self.extref = extref + self.dt = dt + + # state props self.commit = False - self.ref = None - self.dt = None self.part_side = 'src' self.have_src = False self.have_dst = False - self.entry = try_entry(self.ctx, entry_spec) + + self.entry = try_entry(self.ctx, entry) + if self.entry == None: + self.entry = Entry.empty(unitindex=self.ctx.uidx, description=self.description, ref=self.ref, extref=self.extref) if self.entry.serial > 0: raise NotImplementedError('entry edit not yet implemented') self._do_prepare() @@ -224,6 +260,9 @@ class EntrySession: return False if v == 't': return False + if v == 'v': + handle_view(self.ctx, self.entry, v) + return True logg.error('invalid input') return True @@ -239,9 +278,10 @@ class EntrySession: self.entry.dt = v - def do_interactive_two(self): + def do_interactive_two(self, w=sys.stdout): r = True while r: + w.write(str(self) + "\n") v = input("> ") r = self.handle_input(v) @@ -264,11 +304,26 @@ class EntrySession: return True - def start(self): - self.do_interactive_one() + def start(self, skip_first=False): + if not skip_first: + self.do_interactive_one() try: self.do_interactive_two() except StopIteration: - return False + return None r = self.finalize() return self.entry + + + def __str__(self): + s = """Date: {} +Description: {} +Ref: {} +Extref: {} +""".format( + self.entry.dt, + self.entry.description, + self.entry.ref, + self.entry.extref, + ) + return s