piknik

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

commit bd3966f45276140ebaca933ac669c7eaaa8f0a7d
parent 6f2ccfcab6bba6fd242dca819d532fad240e58d8
Author: lash <dev@holbrook.no>
Date:   Thu,  1 Dec 2022 15:12:04 +0000

Move renderer driver to base class

Diffstat:
MCHANGELOG | 3+++
MROADMAP | 2--
Mpiknik/crypto.py | 17++++++++++++++++-
Mpiknik/render/base.py | 103+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--------
Mpiknik/render/html.py | 29++++++++++-------------------
Mpiknik/render/ini.py | 8++++++++
Mpiknik/runnable/show.py | 19+++++++++++++------
Mtests/test_msg.py | 1+
Atests/test_render.py | 80+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
9 files changed, 224 insertions(+), 38 deletions(-)

diff --git a/CHANGELOG b/CHANGELOG @@ -1,3 +1,6 @@ +- 0.2.1 + * Render browsable HTML version of board, issues and issue logs + * Add sender identity in email on signature - 0.2.0 * GPG signing of issue messages. * Use visitor pattern for messages verifier diff --git a/ROADMAP b/ROADMAP @@ -9,5 +9,3 @@ - target hash above - git tag - arbitrary string identifier -- 0.2.1 - * Render browsable HTML version of board, issues and issue logs diff --git a/piknik/crypto.py b/piknik/crypto.py @@ -27,6 +27,17 @@ class PGPSigner: self.__skip_verify = skip_verify + def set_from(self, msg, passphrase=None): + r = None + for v in self.gpg.list_keys(True): + if self.default_key == None or v['fingerprint'].upper() == self.default_key.upper(): + r = v['uids'][0] + break + if r == None: + raise UnknownIdentityError('no signing keys found') + msg.add_header('From', r) + + def sign(self, msg, passphrase=None): # msg = IssueMessage object m = Message() v = msg.as_string() @@ -37,6 +48,7 @@ class PGPSigner: fn = '{}.asc'.format(msg.get('X-Piknik-Msg-Id')) ms.add_header('Content-Disposition', 'attachment', filename=fn) + self.set_from(msg, passphrase=passphrase) sig = self.gpg.sign(v, keyid=self.default_key, detach=True, passphrase=self.passphrase) ms.set_payload(str(sig)) @@ -58,6 +70,9 @@ class PGPSigner: def message_callback(self, envelope, msg, message_id): + if msg.get('From') != None: + envelope.sender = msg.get('From') + if self.__envelope_state == 0: self.__envelope_state = 1 self.__envelope = msg @@ -87,7 +102,7 @@ class PGPSigner: else: logg.debug('signature ok from {}'.format(r.fingerprint)) envelope.valid = True - envelope.sender = r.fingerprint + #envelope.sender = r.fingerprint self.__envelope_state = 2 return (envelope, msg,) diff --git a/piknik/render/base.py b/piknik/render/base.py @@ -1,35 +1,118 @@ -import sys +#import sys +import logging + +logg = logging.getLogger(__name__) class Renderer: - def apply_begin(self, w=sys.stdout): + def __init__(self, basket, accumulator=None): + self.b = basket + self.e = None + self.a = accumulator + + + def __add(self, v): + if self.a != None: + if v != None: + self.a(v) + + + def apply_envelope_pre(self, state, issue, tags, envelope, accumulator=None): + #logg.debug('entering envelope {}'.format(envelope)) + self.e = envelope + + + def apply_envelope_post(self, state, issue, tags, envelope, accumulator=None): + #logg.debug('leaving envelope {}'.format(self.e)) + self.e = None + + + def apply_envelope(self, state, issue, tags, envelope, accumulator=None): pass - def apply_state_pre(self, state, w=sys.stdout): + def apply_message_pre(self, state, issue, tags, envelope, message, accumulator=None): pass - def apply_state(self, state, w=sys.stdout): + def apply_message_post(self, state, issue, tags, envelope, message, accumulator=None): pass + + def apply_message(self, state, issue, tags, envelope, message, accumulator=None): + pass - def apply_issue_pre(self, state, issue, tags, w=sys.stdout): + + def apply_issue_pre(self, state, issue, tags, accumulator=None): pass - def apply_issue(self, state, issue, tags, w=sys.stdout): + def apply_issue_post(self, state, issue, tags, accumulator=None): pass - - def apply_issue_post(self, state, issue, tags, w=sys.stdout): + + def apply_issue(self, state, issue, tags, accumulator=None): + + def envelope_callback(envelope, envelope_type): + r = self.apply_envelope_pre(state, issue, tags, envelope, accumulator=accumulator) + self.__add(r) + r = self.apply_envelope(state, issue, tags, envelope, accumulator=accumulator) + self.__add(r) + r = self.apply_envelope_post(state, issue, tags, envelope, accumulator=accumulator) + self.__add(r) + + def message_callback(envelope, message, message_id): + r = self.apply_message_pre(state, issue, tags, envelope, message, accumulator=accumulator) + self.__add(r) + r = self.apply_message(state, issue, tags, envelope, message, accumulator=accumulator) + self.__add(r) + r = self.apply_message_post(state, issue, tags, envelope, message, accumulator=accumulator) + self.__add(r) + + #for msg in self.b.get_msg(issue.id, envelope_callback=envelope_callback, message_callback=message_callback): + self.b.get_msg(issue.id, envelope_callback=envelope_callback, message_callback=message_callback) + + + def apply_state_pre(self, state, accumulator=None): + pass + + + def apply_state_post(self, state, accumulator=None): pass - def apply_state_post(self, state, w=sys.stdout): + def apply_state(self, state, accumulator=None): + for issue_id in self.b.list(category=state): + issue = self.b.get(issue_id) + tags = self.b.tags(issue_id=issue_id) + r = self.apply_issue_pre(state, issue, tags) + self.__add(r) + r = self.apply_issue(state, issue, tags) + self.__add(r) + r = self.apply_issue_post(state, issue, tags) + self.__add(r) + + + def apply_begin(self, accumulator=None): pass - def apply_end(self, w=sys.stdout): + def apply_end(self, accumulator=None): pass + + + def apply(self, accumulator=None): + r = self.apply_begin() + self.__add(r) + + for state in self.b.states(): + r = self.apply_state_pre(state) + self.__add(r) + r = self.apply_state(state) + self.__add(r) + r = self.apply_state_post(state) + self.__add(r) + + r = self.apply_end() + self.__add(r) diff --git a/piknik/render/html.py b/piknik/render/html.py @@ -1,6 +1,7 @@ # standard imports import sys import os +import logging # external imports import dominate @@ -10,6 +11,8 @@ from mimeparse import parse_mime_type # local imports from .base import Renderer as BaseRenderer +logg = logging.getLogger(__name__) + class Renderer(BaseRenderer): @@ -20,6 +23,7 @@ class Renderer(BaseRenderer): self.message_buf = [] self.outdir = outdir self.last_message_id = None + self.msg_idx = 0 def apply_state_post(self, state, w=sys.stdout): @@ -42,6 +46,7 @@ class Renderer(BaseRenderer): while True: try: v = self.message_buf.pop(0) + logg.debug('msgd {} {}'.format(issue.id, str(v))) r_l.add(v) except IndexError: break @@ -100,8 +105,7 @@ class Renderer(BaseRenderer): def apply_message_post(self, state, issue, tags, message, message_from, message_date, message_id, message_valid, w=sys.stdout): #r = ol() #w.write(self.message_buf.render()) - - + self.msg_idx = 0 pass @@ -110,30 +114,17 @@ class Renderer(BaseRenderer): filename = message.get_filename() if message_id != self.last_message_id: - self.message_buf.append(div('--- ' + str(message_date), _id=issue.id)) + s = '--- {} @ {}'.format(message_from, message_date) + self.message_buf.append(div(s, _id=issue.id)) self.last_message_id = message_id - r = div(_id=issue.id + '.' + message_id) + r = div(_id=issue.id + '.' + message_id + '.' + str(self.msg_idx)) + self.msg_idx += 1 if filename == None: v = message.get_payload() if message.get('Content-Transfer-Encoding') == 'BASE64': v = b64decode(v).decode() r.add(p(v)) - - else: - v = message.get_payload() - if m[0] == 'image': - img_src = 'data:{}/{};base64,'.format(m[0], m[1]) - img_src += v - r.add(p(img(src=img_src))) - else: - data_src = 'data:application/octet-stream;base64,' + v - r.add(a(filename, download=filename, href=data_src)) - - self.message_buf.append(r) - - #for i, v in enumerate(self.message_buf): - # r.add(p(v)) #w.write(r.render()) diff --git a/piknik/render/ini.py b/piknik/render/ini.py @@ -15,3 +15,11 @@ class Renderer(BaseRenderer): def apply_state_post(self, state, w=sys.stdout): w.write('\n') + + + def apply_message_part(self, state, issue, envelope, message, message_from, message_date, message_id, message_valid, dump_dir=None, w=sys.stdout): + pass + + + def apply_message_post(self, state, issue, tags, message, message_from, message_date, message_id, message_valid, w=sys.stdout): + pass diff --git a/piknik/runnable/show.py b/piknik/runnable/show.py @@ -64,9 +64,9 @@ class PGPWrapper(PGPSigner): self.issue = issue - def render_message(self, envelope, messages, message_date, message_id, dump_dir=None, w=sys.stdout): + def render_message(self, envelope, messages, message_sender, message_date, message_id, dump_dir=None, w=sys.stdout): for message in messages: - self.renderer.apply_message_part(self.state, self.issue, envelope, message, self.sender, message_date, message_id, self.valid, dump_dir=dump_dir, w=w) + self.renderer.apply_message_part(self.state, self.issue, envelope, message, message_sender, message_date, message_id, self.valid, dump_dir=dump_dir, w=w) #valid = '[++]' #if not self.valid: # valid = '[!!]' @@ -83,6 +83,7 @@ class PGPWrapper(PGPSigner): (envelope, message) = super(PGPWrapper, self).message_callback(envelope, message, message_id) if envelope != None and not envelope.resolved: + logg.debug('have sender {}'.format(self.sender)) self.sender = envelope.sender self.valid = envelope.valid self.resolved = True @@ -95,7 +96,7 @@ class PGPWrapper(PGPSigner): if message.get('Content-Type') == 'application/pgp-signature': #self.render_message(envelope, self.message, self.message_id, dump_dir=dump_dir) - self.messages.append((envelope, self.part, self.message_date, self.message_id,)) + self.messages.append((envelope, self.part, self.sender, self.message_date, self.message_id,)) self.part = [] self.message_id = None self.message_date = None @@ -118,7 +119,7 @@ class PGPWrapper(PGPSigner): rg = range(len(self.messages)-1, -1, -1) for i in rg: v = self.messages[i] - self.render_message(v[0], v[1], v[2], v[3], dump_dir=dump_dir) + self.render_message(v[0], v[1], v[2], v[3], v[4], dump_dir=dump_dir) gpg_home = os.environ.get('GPGHOME') @@ -148,6 +149,7 @@ def render_states(renderer, basket, states): renderer.apply_begin() for k in basket.states(): + logg.debug('processing state {}'.format(k)) if k == 'FINISHED' and not arg.show_finished: continue @@ -167,6 +169,7 @@ def render_states(renderer, basket, states): renderer.apply_end() + def process_states(renderer, basket): results = {} states = [] @@ -188,12 +191,16 @@ def process_states(renderer, basket): def main(): if arg.issue_id == None: - import piknik.render.html - renderer = piknik.render.html.Renderer() + #import piknik.render.html + #renderer = piknik.render.html.Renderer() + import piknik.render.ini + renderer = piknik.render.ini.Renderer() return process_states(renderer, basket) import piknik.render.html renderer = piknik.render.html.Renderer() + #import piknik.render.ini + #renderer = piknik.render.ini.Renderer() issue = basket.get(arg.issue_id) tags = basket.tags(arg.issue_id) diff --git a/tests/test_msg.py b/tests/test_msg.py @@ -96,5 +96,6 @@ class TestMsg(unittest.TestCase): m = b.get_msg(v, envelope_callback=render_envelope, message_callback=render_message) + if __name__ == '__main__': unittest.main() diff --git a/tests/test_render.py b/tests/test_render.py @@ -0,0 +1,80 @@ +# standard imports +import unittest +import logging +import json +import shutil +import io +import tempfile +from email.message import Message +from email.utils import localtime as email_localtime + +# external imports +from mimeparse import parse_mime_type + +# local imports +from piknik import ( + Basket, + Issue, + ) +from piknik.msg import IssueMessage +from piknik.render.base import Renderer + +# test imports +from tests.common import TestStates +from tests.common import TestMsgStore +from tests.common import pgp_setup + +logging.basicConfig(level=logging.DEBUG) +logg = logging.getLogger() + + +def test_wrapper(p): + m = Message() + m.add_header('Foo', 'bar') + m.set_type('multipart/relative') + m.set_payload(p) + return m + + +def test_unwrapper(msg, message_callback=None, part_callback=None): + for v in msg.walk(): + if message_callback != None: + message_callback(v) + + + + +class TestMsg(unittest.TestCase): + + def setUp(self): + self.acc = [] + def accumulate(v): + self.acc.append(v) + + (self.crypto, self.gpg, self.gpg_dir) = pgp_setup() + self.store = TestStates() + self.b = Basket(self.store, message_wrapper=self.crypto.sign) + self.render_dir = tempfile.mkdtemp() + self.renderer = Renderer(self.b, accumulator=accumulate) #outdir=self.render_dir) + + + def tearDown(self): + #logg.debug('look in {}'.format(self.render_dir)) + shutil.rmtree(self.render_dir) + + + def test_idlepass(self): + issue_one = Issue('foo') + self.b.add(issue_one) + + issue_two = Issue('bar') + v = self.b.add(issue_two) + + m = self.b.msg(v, 's:foo') + + self.renderer.apply() + print(self.acc) + + +if __name__ == '__main__': + unittest.main()