piknik

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

commit adb2c7aeeddb2687483d1a24e1b498a1af0daf9e
parent 54bc3b091052fc769897e9cef636986c8a7c85e7
Author: lash <dev@holbrook.no>
Date:   Sat, 19 Nov 2022 09:01:11 +0000

Add render module, stub html rendering of states, issue page

Diffstat:
Ahtml_requirements.txt | 1+
Mpiknik/basket.py | 5+++++
Apiknik/render/base.py | 35+++++++++++++++++++++++++++++++++++
Apiknik/render/html.py | 66++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Apiknik/render/ini.py | 17+++++++++++++++++
Apiknik/render/plain.py | 69+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mpiknik/runnable/list.py | 19++++++++++++++-----
Mpiknik/runnable/show.py | 83+++++++++++++++++++++----------------------------------------------------------
8 files changed, 229 insertions(+), 66 deletions(-)

diff --git a/html_requirements.txt b/html_requirements.txt @@ -0,0 +1 @@ +dominate~=2.7.0 diff --git a/piknik/basket.py b/piknik/basket.py @@ -56,6 +56,11 @@ class Basket: return o + def get_state(self, issue_id): + v = self.state.state(issue_id) + return self.state.name(v) + + def list(self, category=None): if category == None: category = self.state.BACKLOG diff --git a/piknik/render/base.py b/piknik/render/base.py @@ -0,0 +1,35 @@ +import sys + + +class Renderer: + + def apply_begin(self, w=sys.stdout): + pass + + + def apply_state_pre(self, state, w=sys.stdout): + pass + + + def apply_state(self, state, w=sys.stdout): + pass + + + def apply_issue_pre(self, state, issue_id, issue, tags, w=sys.stdout): + pass + + + def apply_issue(self, state, issue_id, issue, tags, w=sys.stdout): + pass + + + def apply_issue_post(self, state, issue_id, issue, tags, w=sys.stdout): + pass + + + def apply_state_post(self, state, w=sys.stdout): + pass + + + def apply_end(self, state, w=sys.stdout): + pass diff --git a/piknik/render/html.py b/piknik/render/html.py @@ -0,0 +1,66 @@ +import sys +import os + +import dominate +from dominate.tags import div, p, a, meta, ul, li, h1, h2, link + +from .base import Renderer as BaseRenderer + + +class Renderer(BaseRenderer): + + def __init__(self, outdir='/home/lash/tmp'): + super(Renderer, self).__init__() + self.issue_buf = [] + self.state_buf = [] + self.outdir = outdir + + + def apply_state_post(self, state, w=sys.stdout): + r = div(_id='state_' + state) + r.add(h2(state)) + r_l = ul(_class='state_listing') + while True: + try: + v = self.issue_buf.pop(0) + r_l.add(v) + except IndexError: + break + r.add(r_l) + self.state_buf.append(r) + + + def apply_issue(self, state, issue_id, issue, tags, w=sys.stdout): + v = li(a(issue.title, href=issue_id + '.html')) + self.issue_buf.append(v) + + + def apply_message(self, state, issue_id, issue, tags, message, w=None): + + + + def apply_issue_post(self, state, issue_id, issue, tags, w=None): + close = False + if w == None: + fp = os.path.join(self.outdir, issue_id + '.html') + w = open(fp, 'w') + close = True + r = dominate.document(title='issue: {} ({})'.format(issue.title, issue_id)) + r.add(h1(issue.title)) + w.write(r.render()) + + if close: + w.close() + + + def apply_end(self, w=sys.stdout): + r = dominate.document(title='issues for ...') + r.head.add(meta(name='generator', content='piknik')) + r.head.add(link(rel='stylesheet', href='style.css')) + while True: + try: + v = self.state_buf.pop(0) + r.add(v) + except IndexError: + break + w.write(r.render()) diff --git a/piknik/render/ini.py b/piknik/render/ini.py @@ -0,0 +1,17 @@ +import sys + +from .base import Renderer as BaseRenderer + + +class Renderer(BaseRenderer): + + def apply_state(self, state, w=sys.stdout): + w.write('[' + state + ']\n') + + + def apply_issue(self, state, issue_id, issue, tags, w=sys.stdout): + w.write('{}\t{}\t{}\n'.format(issue.title, ','.join(tags), issue_id)) + + + def apply_state_post(self, state, w=sys.stdout): + w.write('\n') diff --git a/piknik/render/plain.py b/piknik/render/plain.py @@ -0,0 +1,69 @@ +# standard imports +import sys + +# external imports +from mimeparse import parse_mime_type + +# local imports +from .base import Renderer as BaseRenderer + + +class Renderer(BaseRenderer): + + def apply_issue(self, state, issue_id, issue, tags, w=sys.stdout): + w.write("""id: {} +title: {} +tags: {} + +""".format( + issue.id, + issue.title, + ', '.join(tags), + ) + ) + + assigned = issue.get_assigned() + + if len(assigned) == 0: + w.write('(not assigned)\n') + return + + w.write('assigned to:\n') + owner = issue.owner() + for v in assigned: + o = v[0] + s = o.id() + if o == owner: + s += ' (owner)' + w.write('\t' + str(s)) + + + def apply_message(self, state, issue_id, issue, tags, message, w=sys.stdout): + pass + + + def apply_message_part(self, state, issue, envelope, message, message_date, message_id, dump_dir=None, w=sys.stdout): + m = parse_mime_type(message.get_content_type()) + filename = message.get_filename() + + v = '' + if filename == None: + if m[0] == 'text': + if m[1] == 'plain': + v = message.get_payload() + if message.get('Content-Transfer-Encoding') == 'BASE64': + v = b64decode(v).decode() + else: + v = '[rich text]' + else: + if dump_dir != None: + v = message.get_payload() + if message.get('Content-Transfer-Encoding') == 'BASE64': + v = b64decode(v).decode() + filename = to_suffixed_file(dump_dir, filename, v) + sz = message.get('Content-Length') + if sz == None: + sz = 'unknown' + v = '[file: {}, type {}/{}, size: {}]'.format(filename, m[0], m[1], sz) + + w.write(v) diff --git a/piknik/runnable/list.py b/piknik/runnable/list.py @@ -17,20 +17,26 @@ store_factory = FileStoreFactory(arg.d) basket = Basket(store_factory) -def render_ini(b, r): +def render(b, r, m): + m.apply_begin() + for k in basket.states(): if k == 'FINISHED' and not arg.show_finished: continue - print('[' + k + ']') + + m.apply_state(k) for v in r[k]: if k != 'BLOCKED' and v in r['BLOCKED']: continue o = b.get(v) t = b.tags(v) - print('{}\t{}\t{}'.format(o.title, ','.join(t), v)) + m.apply_issue(k, v, o, t) + m.apply_issue_post(k, v, o, t) + + m.apply_state_post(k) - print() + m.apply_end() def main(): @@ -49,7 +55,10 @@ def main(): for v in basket.list(category=s): results[s].append(v) - globals()['render_' + arg.renderer](basket, results) + import piknik.render.html + m = piknik.render.html.Renderer() + #globals()['render_' + arg.renderer](basket, results, m) + render(basket, results, m) if __name__ == '__main__': diff --git a/piknik/runnable/show.py b/piknik/runnable/show.py @@ -1,4 +1,5 @@ # standard imports +import io import os import sys import argparse @@ -7,14 +8,12 @@ import tempfile from base64 import b64decode from email.utils import parsedate_to_datetime -# external imports -from mimeparse import parse_mime_type - # local imports from piknik import Basket from piknik import Issue from piknik.store import FileStoreFactory from piknik.crypto import PGPSigner +from piknik.render.plain import Renderer #logging.basicConfig(level=logging.DEBUG) logg = logging.getLogger() @@ -49,7 +48,7 @@ def to_suffixed_file(d, s, data): # TODO can implement as email parser api instead? class PGPWrapper(PGPSigner): - def __init__(self, home_dir=None): + def __init__(self, renderer, state, issue, home_dir=None): super(PGPWrapper, self).__init__(home_dir=home_dir) self.message_date = None self.messages = [] @@ -57,40 +56,22 @@ class PGPWrapper(PGPSigner): self.message_id = None self.sender = None self.valid = False + self.renderer = renderer + self.state = state + self.issue = issue def render_message(self, envelope, messages, message_date, message_id, dump_dir=None, w=sys.stdout): r = '' + w.write('\n') for message in messages: - m = parse_mime_type(message.get_content_type()) - filename = message.get_filename() - - v = '' - if filename == None: - if m[0] == 'text': - if m[1] == 'plain': - v = message.get_payload() - if message.get('Content-Transfer-Encoding') == 'BASE64': - v = b64decode(v).decode() - else: - v = '[rich text]' - else: - #filename = message.get_filename() - if dump_dir != None: - v = message.get_payload() - if message.get('Content-Transfer-Encoding') == 'BASE64': - v = b64decode(v).decode() - filename = to_suffixed_file(dump_dir, filename, v) - sz = message.get('Content-Length') - if sz == None: - sz = 'unknown' - #v = '[file: ' + filename + ', type: ' + m[0] + m[1] + ', size: ' + sz + ']' - v = '[file: {}, type {}/{}, size: {}]'.format(filename, m[0], m[1], sz) - + ww = io.StringIO() + self.renderer.apply_message_part(self.state, self.issue, envelope, message, message_date, message_id, dump_dir=dump_dir, w=ww) valid = '[++]' if not self.valid: valid = '[!!]' - r += '\n\t' + v + '\n' + ww.seek(0) + r += '\n\t' + ww.read() + '\n' w.write('\nmessage {} from {} {} - {}\n\t{}\n'.format(message_date, self.sender, valid, message_id, r)) @@ -143,35 +124,11 @@ class PGPWrapper(PGPSigner): gpg_home = os.environ.get('GPGHOME') -verifier = PGPWrapper(home_dir=gpg_home) -def render_default(b, o, t): - print("""id: {} -title: {} -tags: {} -""".format( - o.id, - o.title, - ', '.join(t), - ) - ) - - assigned = o.get_assigned() - - if len(assigned) == 0: - print('(not assigned)') - return - - print('assigned to:') - owner = o.owner() - for v in assigned: - o = v[0] - s = o.id() - if o == owner: - s += ' (owner)' - print('\t' + str(s)) - +def render(renderer, basket, state, issue, tags): + renderer.apply_issue(state, arg.issue_id, issue, tags) + verifier = PGPWrapper(renderer, state, issue, home_dir=gpg_home) m = basket.get_msg( arg.issue_id, envelope_callback=verifier.envelope_callback, @@ -181,10 +138,14 @@ tags: {} def main(): - o = basket.get(arg.issue_id) - t = basket.tags(arg.issue_id) - - globals()['render_' + arg.renderer](basket, o, t) + issue = basket.get(arg.issue_id) + tags = basket.tags(arg.issue_id) + state = basket.get_state(arg.issue_id) + + #globals()['render_' + arg.renderer](basket, state, issue, tags) + import piknik.render.plain + renderer = piknik.render.plain.Renderer() + render(renderer, basket, state, issue, tags) if __name__ == '__main__':