summaryrefslogtreecommitdiff
path: root/web.py
diff options
context:
space:
mode:
Diffstat (limited to 'web.py')
-rw-r--r--web.py229
1 files changed, 153 insertions, 76 deletions
diff --git a/web.py b/web.py
index 35f2e89..26e9233 100644
--- a/web.py
+++ b/web.py
@@ -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()