piknik

Unnamed repository; edit this file 'description' to name the repository.
Info | Log | Files | Refs | README | LICENSE

commit edf8eebe26924c9785b5df4325a0fc61cd641fba
parent 1036ecd79a0b127973ea85f0535fa58538c8d08c
Author: lash <dev@holbrook.no>
Date:   Sat, 22 Apr 2023 08:57:23 +0100

Add issue alias for manipulating issue on cli

Diffstat:
M.piknik/.msg/.master | 5+++--
Mpiknik/basket.py | 74+++++++++++++++++++++++++++++++++++++++++++++++++-------------------------
Mpiknik/cli/__init__.py | 1+
Mpiknik/cli/add.py | 3++-
Mpiknik/issue.py | 7+++++--
Mpiknik/store/__init__.py | 41++++++++++++++++++++++++++++++++++++++++-
Mtests/common.py | 8++++++++
Mtests/test_basic.py | 11+++++++++++
8 files changed, 119 insertions(+), 31 deletions(-)

diff --git a/.piknik/.msg/.master b/.piknik/.msg/.master @@ -1 +1,2 @@ -b]Eg'>9l`qJj瀵 -\ No newline at end of file +b]Eg'>9l`qJj瀵D\KmHRWGJEB WcH+UJɰܕYP +L"F)0RdAӷs.oV‚GKGٹ(' +\ No newline at end of file diff --git a/piknik/basket.py b/piknik/basket.py @@ -3,6 +3,7 @@ import logging # external imports import shep +from shep.error import StateItemNotFound # local imports from .error import DeadIssue @@ -34,6 +35,8 @@ class Basket: self.__msg = state_factory.create_messages() self.__msg_wrap = message_wrapper + self.__alias = state_factory.create_aliases() + self.issues_rev = {} @@ -45,19 +48,28 @@ class Basket: def add(self, issue): issue_id = str(issue.id) - self.state.put(issue_id, contents=str(issue)) + j = str(issue) + self.state.put(issue_id, contents=j) self.__tags.put(issue_id) + if issue.alias != None: + self.__alias.put(issue.alias, issue.id) return issue_id def get(self, issue_id): + logg.debug("basked issue get {}".format(issue_id)) r = self.state.get(issue_id) + if r == None: + aliased_issue_id = self.__alias.get(issue_id) + r = self.state.get(aliased_issue_id) + logg.debug("resolved issue {} from alias '{}': {}".format(aliased_issue_id, issue_id, r)) o = Issue.from_str(r) return o def get_state(self, issue_id): - v = self.state.state(issue_id) + o = self.get(issue_id) + v = self.state.state(o.id) return self.state.name(v) @@ -86,7 +98,10 @@ class Basket: def state_finish(self, issue_id): - self.state.move(issue_id, self.state.FINISHED) + o = self.get(issue_id) + self.state.move(o.id, self.state.FINISHED) + if o.alias != None: + self.__alias.purge(o.alias) def advance(self, issue_id): @@ -143,7 +158,8 @@ class Basket: def tags(self, issue_id): - v = self.__tags.state(issue_id) + o = self.get(issue_id) + v = self.__tags.state(o.id) #issue_id) r = self.__tags.elements(v) if r == 'UNTAGGED': r = '(' + r + ')' @@ -151,14 +167,15 @@ class Basket: def __get_msg(self, issue_id, envelope_callback=None, message_callback=None, post_callback=None): - r = self.state.get(issue_id) - o = Issue.from_str(r) + #r = self.state.get(issue_id) + o = self.get(issue_id) + #o = Issue.from_str(r) try: - v = self.__msg.get(issue_id) + v = self.__msg.get(o.id) m = IssueMessage.parse(o, v.decode('utf-8'), envelope_callback=envelope_callback, message_callback=message_callback, post_callback=post_callback) return m except FileNotFoundError as e: - logg.debug('instantiating new message log for {} {}'.format(issue_id, e)) + logg.debug('instantiating new message log for {} {}'.format(o.id, e)) return IssueMessage(o) @@ -168,47 +185,54 @@ class Basket: def dep(self, issue_id, dependency_issue_id): - r = self.state.get(issue_id) - self.state.get(dependency_issue_id) - o = Issue.from_str(r) + #r = self.state.get(issue_id) + o = self.get(issue_id) + #self.state.get(dependency_issue_id) + self.get(dependency_issue_id) + #o = Issue.from_str(r) r = o.dep(dependency_issue_id) - self.state.replace(issue_id, contents=str(o)) + self.state.replace(o.id, contents=str(o)) return r def undep(self, issue_id, dependency_issue_id): - r = self.state.get(issue_id) - self.state.get(dependency_issue_id) - o = Issue.from_str(r) + #r = self.state.get(issue_id) + o = self.get(issue_id) + #self.state.get(dependency_issue_id) + self.get(dependency_issue_id) + #o = Issue.from_str(r) r = o.undep(dependency_issue_id) - self.state.replace(issue_id, contents=str(o)) + self.state.replace(o.id, contents=str(o)) return r def assign(self, issue_id, identity): - r = self.state.get(issue_id) - o = Issue.from_str(r) + #r = self.state.get(issue_id) + o = self.get(issue_id) + #o = Issue.from_str(r) v = Identity(identity) r = o.assign(v) - self.state.replace(issue_id, contents=str(o)) + self.state.replace(o.id, contents=str(o)) return r def owner(self, issue_id, identity): - r = self.state.get(issue_id) - o = Issue.from_str(r) + #r = self.state.get(issue_id) + o = self.get(issue_id) + #o = Issue.from_str(r) v = Identity(identity) r = o.set_owner(v) - self.state.replace(issue_id, contents=str(o)) + self.state.replace(o.id, contents=str(o)) return r def unassign(self, issue_id, identity): - r = self.state.get(issue_id) - o = Issue.from_str(r) + #r = self.state.get(issue_id) + o = self.get(issue_id) + #o = Issue.from_str(r) v = Identity(identity) r = o.unassign(v) - self.state.replace(issue_id, contents=str(o)) + self.state.replace(o.id, contents=str(o)) return r diff --git a/piknik/cli/__init__.py b/piknik/cli/__init__.py @@ -11,6 +11,7 @@ class Context: def __init__(self, arg, assembler, mode=0, gpg_home=os.environ.get('GPGHOME')): self.issue_id = arg.issue_id self.files_dir = arg.files_dir + self.alias = getattr(arg, 'alias', None) self.show_finished = getattr(arg, 'show_finished', False) self.show_states = getattr(arg, 'state', []) #self.store_factory = FileStoreFactory(arg.d) diff --git a/piknik/cli/add.py b/piknik/cli/add.py @@ -8,6 +8,7 @@ ctx = None def subparser(argp): + argp.add_argument('-a', '--alias', dest='alias', type=str, help='alias string to refer to issue to with cli commands') argp.add_argument('title', type=str, nargs='*', help='issue title') return argp @@ -24,6 +25,6 @@ def main(): if title != '': title += ' ' title += s - o = Issue(title) + o = Issue(title, alias=ctx.alias) v = ctx.basket.add(o) sys.stdout.write(v + '\n') diff --git a/piknik/issue.py b/piknik/issue.py @@ -11,7 +11,7 @@ from piknik.error import ExistsError class Issue: - def __init__(self, title, issue_id=None): + def __init__(self, title, issue_id=None, alias=None): if issue_id == None: issue_id = str(uuid.uuid4()) self.id = issue_id @@ -19,6 +19,7 @@ class Issue: self.assigned = [] self.assigned_time = [] self.dependencies = [] + self.alias = alias self.owner_idx = 0 @@ -36,6 +37,7 @@ class Issue: o.owner_idx = i for v in r['dependencies']: o.dep(v) + o.alias = r['alias'] return o @@ -99,14 +101,15 @@ class Issue: return True raise UnknownIdentityError(identity) - + def __str__(self): o = { 'id': str(self.id), 'title': self.title, 'assigned': {}, 'dependencies': self.dependencies, + 'alias': self.alias, 'owner': None, } diff --git a/piknik/store/__init__.py b/piknik/store/__init__.py @@ -28,7 +28,6 @@ class MsgDir(HexDir): def get(self, k): u = uuid.UUID(k) fp = self.to_filepath(u.hex) - f = None f = open(fp, 'rb') r = f.read() f.close() @@ -46,6 +45,41 @@ class MsgDir(HexDir): return self.add(u.bytes, v) +class AliasDir: + + def __init__(self, root_path): + self.dir = root_path + os.makedirs(self.dir, exist_ok=True) + + + def get(self, k): + fp = os.path.join(self.dir, k) + f = open(fp, 'rb') + r = f.read() + f.close() + u = uuid.UUID(r.hex()) + return str(u) + + + def key_to_string(self, k): + u = uuid.UUID(bytes=k) + return u.bytes.hex() + + + def put(self, k, v): + u = uuid.UUID(v) + fp = os.path.join(self.dir, k) + logg.debug('putting {} {}'.format(u.bytes.hex(), fp)) + f = open(fp, 'wb') + f.write(u.bytes) + f.close() + + + def purge(self, k): + fp = os.path.join(self.dir, k) + os.remove(fp) + + class FileStoreFactory: def __init__(self, directory=None): @@ -83,3 +117,8 @@ class FileStoreFactory: def create_messages(self): d = os.path.join(self.directory, '.msg') return MsgDir(d) + + + def create_aliases(self): + d = os.path.join(self.directory, '.alias') + return AliasDir(d) diff --git a/tests/common.py b/tests/common.py @@ -29,6 +29,10 @@ class TestMsgStore: raise FileNotFoundError(k) + def purge(self, k): + del self.store[k] + + class TestStates: def create_states(self, *args, **kwargs): @@ -43,6 +47,10 @@ class TestStates: return TestMsgStore() + def create_aliases(self, *args): + return TestMsgStore() + + def pgp_setup(): from piknik.crypto import PGPSigner gpg_dir = tempfile.mkdtemp() diff --git a/tests/test_basic.py b/tests/test_basic.py @@ -116,5 +116,16 @@ class TestBasic(unittest.TestCase): self.assertEqual(len(r), 7) + def test_alias(self): + o = Issue('The first issue', alias="first") + issue_id = o.id + v = self.b.add(o) + o = self.b.get("first") + self.assertEqual(o.id, issue_id) + self.b.state_finish(issue_id) + with self.assertRaises(FileNotFoundError): + o = self.b.get("first") + + if __name__ == '__main__': unittest.main()