diff options
Diffstat (limited to 'web.py')
-rw-r--r-- | web.py | 229 |
1 files changed, 153 insertions, 76 deletions
@@ -1,107 +1,184 @@ import data +import css from ll.xist import xsc, parse -from ll.xist.ns import html, xml, meta -from flask import Flask, Response, url_for +from ll.xist.ns import html +from flask import Flask, url_for +import re import difflib -class View: +class DiffSupport: + + def _diff(self, line1, line2): + diff = list(difflib._mdiff([line1], [line2])) + return (self._format_diff(diff[0][0][1]), + self._format_diff(diff[0][1][1])) + + def _diff_lines(self, lines1, lines2): + diff = difflib._mdiff(lines1, lines2) + rows = [] + for ((line1, diff1), (line2, diff2), flag) in diff: + rows.append(html.tr( + html.th(), + html.td(line1), + html.td(*self._format_diff(diff1)), + html.td(line2), + html.td(*self._format_diff(diff2)) + )) + return html.table(rows, **{'class': 'textdiff'}) + + def _format_diff(self, line): + elems = [] + nextpos = line.find("\x00") + while nextpos != -1 and nextpos + 1 < len(line): + actionclass = { + '+': 'diff_add', '-': 'diff_sub', + '^': 'diff_chg'}[line[nextpos + 1]] + endpos = line.find("\x01", nextpos + 2) + + if nextpos != 0: # intermediate unchanged text + elems += [html.span(line[:nextpos])] + + text = line[nextpos + 2:endpos] + elems += [html.span(text, **{'class': actionclass})] + + line = line[endpos:] + nextpos = line.find("\x00") + + if line != "": # trailing unchanged text + elems += [html.span(line)] + return elems + + +class Difftable(html.div, DiffSupport): + + def __init__(self, to_version, from_version=None): + super().__init__() + if from_version == None: + self.single_version(to_version) + else: + self.two_versions(to_version, from_version) + + def single_version(self, version): + self.append(html.table( + html.tr(html.th("Title"), html.td(version.title)), + html.tr(html.th("Date"), html.td( + version.created_date.strftime("%x %X"))), + html.tr(html.th("Link"), html.td( + html.a(version.url, href=version.url))), + html.tr(html.th("Text", colspan=2)), + html.tr(html.td(map(html.p, version.text.split("\n")), colspan=2)) + )) + + def two_versions(self, to_version, from_version): + def prepare_text(text): + return re.sub("\n\n\n*", "\n\n", text).split("\n") + from_text = prepare_text(from_version.text) + to_text = prepare_text(to_version.text) + + from_difftitle, to_difftitle = self._diff( + from_version.title, to_version.title) + + diff = difflib._mdiff(from_text, to_text) + self.append(html.table( + html.tr(html.th("Title"), + html.td(from_difftitle), + html.td(to_difftitle), **{'class': 'textdiff'}), + html.tr(html.th("Date"), + html.td(from_version.created_date.strftime("%x %X")), + html.td(to_version.created_date.strftime("%x %X"))), + html.tr(html.th("Link"), + html.td(html.a(from_version.url, href=from_version.url)), + html.td(html.a(to_version.url, href=to_version.url))), + html.tr(html.th("Text", colspan=3)), + html.tr(html.td(self._diff_lines(from_text, to_text), colspan=3)), + **{'class': "versiondiff"} + )) + + +class ItemWidget(html.div, DiffSupport): + + def __init__(self, item): + super().__init__() + self.append(html.h2(html.a(item.title, + href=url_for('item', id=item.id)))) + versionsFrom = [None] + list(item.versions)[:-1] + versionsTo = item.versions + versions = list(zip(versionsFrom, versionsTo)) + + self.append(html.ul( + *map(self.version, versions))) + + def version(self, versions): + from_version, to_version = versions + if from_version == None: + title = html.span(to_version.title) + else: + from_difftitle, to_difftitle = self._diff( + from_version.title, to_version.title) + title = html.span(to_difftitle, **{'class': 'textdiff'}) + return html.li(html.span(to_version.created_date.strftime("%x %X")), + html.span(" - "), + title) + + +class Template: @staticmethod def template(title, body): return xsc.Frag( - html.DocTypeXHTML10transitional(), - "\n", + html.DocTypeXHTML10transitional(), "\n", html.html( html.head( html.meta(charset='utf-8'), html.title(title), - html.style("""\ -.diff_add { -color: green; -} - -table.diff {font-family:Courier; border:medium;} -.diff_header {background-color:#e0e0e0} -td.diff_header {text-align:right} -.diff_next {background-color:#c0c0c0} -.diff_sub {background-color:#ffaaaa} - -tr td:nth-child(3) .diff_chg { - color: blue; -} -tr td:nth-child(6) .diff_chg { - color: orangered; -}""") - ), + html.style(css.string( + css.Rule(".textdiff", ".diff_add", + backgroundColor="#CEF6CE"), + css.Rule(".textdiff", ".diff_next", + backgroundColor="#c0c0c0"), + css.Rule(".textdiff", ".diff_sub", + backgroundColor="#FFDADA"), + css.Rule(".textdiff", ".diff_chg", + backgroundColor="#E5E1FF") + )) + ), "\n", html.body(body))) @staticmethod def index(feeds): - return View.template("Index", html.ul( - list(map(lambda feed: - html.li( - html.a(feed.title or feed.url, href=url_for('feed', id=feed.id))), - feeds)))) + return Template.template( + "Index", + html.ul( + *map(lambda feed: html.li( + html.a(feed.title or feed.url, href=url_for('feed', id=feed.id))), + feeds))) @staticmethod def feed(feed, items): - return View.template("Feed {}".format(feed.title or feed.url), - html.div( - html.a("Back", href=url_for('index')), - html.h1(feed.title), - *list(map(lambda item: [ - html.h2(html.a(item.title, href=url_for('item', id=item.id))), - html.ul( - *map(lambda version: - html.li("{} {}".format(version.created_date.strftime("%x %X"), - version.title)), - item.versions) - ) - ], items)))) - - @staticmethod - def format_version(a, b): - temp = """\ -Title: {0.title} -Authors: {0.authors} -Url: {0.url} -Text: -{0.text}""" - if a == None: - adata = [] - fromdate = "" - else: - adata = temp.format(a).split("\n") - fromdate = a.created_date.strftime("%x %X") - todate = b.created_date.strftime("%x %X") - - bdata = temp.format(b).split("\n") - table = difflib.HtmlDiff(wrapcolumn=60) \ - .make_table(adata, bdata, - fromdesc=fromdate, todesc=todate) - - table = table.encode('utf-8') - node = parse.tree(table, parse.Expat(), parse.NS( - html), parse.Node(pool=xsc.Pool(html))) - return html.div(node) + return Template.template( + "Feed {}".format(feed.title or feed.url), + html.div( + html.a("Back", href=url_for('index')), + html.h1(feed.title), + *map(ItemWidget, items))) @staticmethod def item(item, versions): versionsA = versions versionsB = [None] + versions[:-1] versions = list(zip(versionsA, versionsB)) - return View.template("Item: {}".format(item.title), - html.div( + return Template.template("Item: {}".format(item.title), + html.div( html.a("Back to {}".format(item.feed.title), href=url_for('feed', id=item.feed.id)), html.h1(item.title), - *list(map(lambda versionAB: - View.format_version(versionAB[1], versionAB[0]), - versions)) - )) + *map(lambda versionAB: html.div( + Difftable(versionAB[0], versionAB[1]), + html.hr()), + versions))) def run(): @@ -109,7 +186,7 @@ def run(): @app.route('/') def index(): - return View.index(data.Feed.select()).string("utf-8") + return Template.index(data.Feed.select()).string("utf-8") @app.route('/feed/<id>') def feed(id): @@ -117,7 +194,7 @@ def run(): items = data.Item.select() \ .where(data.Item.feed == feed) \ .order_by(data.Item.created_date.desc()) - return View.feed(feed, items).string("utf-8") + return Template.feed(feed, items).string("utf-8") @app.route('/item/<id>') def item(id): @@ -125,6 +202,6 @@ def run(): versions = data.Version.select() \ .where(data.Version.item == item) \ .order_by(data.Version.created_date) - return View.item(item, list(versions)).string("utf-8") + return Template.item(item, list(versions)).string("utf-8") app.run() |