summaryrefslogtreecommitdiff
path: root/watchnews/web.py
diff options
context:
space:
mode:
Diffstat (limited to 'watchnews/web.py')
-rw-r--r--watchnews/web.py207
1 files changed, 207 insertions, 0 deletions
diff --git a/watchnews/web.py b/watchnews/web.py
new file mode 100644
index 0000000..438e649
--- /dev/null
+++ b/watchnews/web.py
@@ -0,0 +1,207 @@
+from . import data, css
+
+from ll.xist import xsc, parse
+from ll.xist.ns import html
+from flask import Flask, url_for
+
+import re
+import difflib
+
+
+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.html(
+ html.head(
+ html.meta(charset='utf-8'),
+ html.title(title),
+ 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 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 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 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),
+ *map(lambda versionAB: html.div(
+ Difftable(versionAB[0], versionAB[1]),
+ html.hr()),
+ versions)))
+
+
+def get_app():
+ app = Flask(__name__)
+
+ @app.route('/')
+ def index():
+ return Template.index(data.Feed.select()).string("utf-8")
+
+ @app.route('/feed/<id>')
+ def feed(id):
+ feed = data.Feed.get(data.Feed.id == id)
+ items = data.Item.select() \
+ .where(data.Item.feed == feed) \
+ .order_by(data.Item.created_date.desc())
+ return Template.feed(feed, items).string("utf-8")
+
+ @app.route('/item/<id>')
+ def item(id):
+ item = data.Item.get(data.Item.id == id)
+ versions = data.Version.select() \
+ .where(data.Version.item == item) \
+ .order_by(data.Version.created_date)
+ return Template.item(item, list(versions)).string("utf-8")
+
+ return app
+