Mercurial > code > home > repos > href
changeset 2:80b11112c9e0
web app for query urls like /user and /user/tag+tag
Ignore-this: bb1f40dd6bbff4c3ee463b440738726d
author | Drew Perttula <drewp@bigasterisk.com> |
---|---|
date | Sun, 17 Feb 2013 03:56:28 -0800 |
parents | 7cecda055fae |
children | 656583326f40 |
files | jadestache.py lookup.py run run_debug static/gui.js static/style.css template/head.jade.mustache template/links.jade.mustache template/tail.jade.mustache |
diffstat | 9 files changed, 286 insertions(+), 0 deletions(-) [+] |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/jadestache.py Sun Feb 17 03:56:28 2013 -0800 @@ -0,0 +1,55 @@ +import pyjade, pyjade.exceptions, pystache + + +class _JadeLoader(pystache.loader.Loader): + """ + expands jade of incoming files. Also includes a cache so it doesn't + read the same file twice + """ + def __init__(self, *args, **kw): + pystache.renderer.Loader.__init__(self, *args, **kw) + self.seen = {} # path : expanded jade + + def read(self, path, encoding=None): + if path in self.seen: + return self.seen[path] + + b = pystache.common.read(path) + + if encoding is None: + encoding = self.file_encoding + + src = self.unicode(b, encoding) + + expanded = pyjade.utils.process(src) + self.seen[path] = expanded + return expanded + +class Renderer(pystache.renderer.Renderer): + """ + pystache renderer that expands base jade syntax on its input + files. No jade data interpolation happens, so you could use these + same templates in the browser with js mustache. + + Files need to end with .mustache since it's the mustache loader + that's going to look for them. + """ + def __init__(self, *args, **kw): + debug = False + if 'debug' in kw: + debug = kw['debug'] + del kw['debug'] + pystache.renderer.Renderer.__init__(self, *args, **kw) + self._loader = None if debug else self._new_loader() + + def _new_loader(self): + return _JadeLoader( + file_encoding=self.file_encoding, extension=self.file_extension, + to_unicode=self.unicode, search_dirs=self.search_dirs) + + def _make_loader(self): + if self._loader is not None: + return self._loader + else: + # this is for debug mode, to make the templates get reread + return self._new_loader()
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/lookup.py Sun Feb 17 03:56:28 2013 -0800 @@ -0,0 +1,75 @@ +#!bin/python +""" +serve some queries over bookmarks: + +/user +/user/tag+tag+tag + +""" +import pymongo, bottle, time, urllib +from urllib2 import urlparse +from dateutil.tz import tzlocal +from bottle import static_file +from jadestache import Renderer +db = pymongo.Connection('bang', tz_aware=True)['href'] + +renderer = Renderer(search_dirs=['template'], debug=bottle.DEBUG) + +@bottle.route('/static/<filename>') +def server_static(filename): + return static_file(filename, root='static') + +def recentTags(user, tags=None): + out = {'links':[]} + t1 = time.time() + spec = {'user':user} + if tags: + spec['extracted.tags'] = {'$all' : tags} + for doc in db['links'].find(spec, sort=[('t', -1)], limit=50): + del doc['_id'] + doc['t'] = doc['t'].astimezone(tzlocal()).isoformat() + if not doc['description'].strip(): + doc['displayDescription'] = doc['href'] + else: + doc['displayDescription'] = doc['description'] + + doc['tagWords'] = [{'word' : w} for w in doc['tag'].split(None)] + doc['domain'] = urlparse.urlparse(doc['href']).netloc + + out['links'].append(doc) + out['stats'] = {'queryTimeMs' : round((time.time() - t1) * 1000, 2)} + return out + +def renderWithTime(name, data): + t1 = time.time() + rendered = renderer.render_name("links.jade", data) + dt = (time.time() - t1) * 1000 + rendered = rendered.replace('TEMPLATETIME', "%.02f ms" % dt) + return rendered + +@bottle.route('/<user>/') +def userSlash(user): + bottle.redirect("/%s" % urllib.quote(user)) + +@bottle.route('/<user>') +def userAll(user): + data = recentTags(user, tags=None) + + data['desc'] = "%s's recent links" % user + data['toRoot'] = "." + data['stats']['template'] = 'TEMPLATETIME' + return renderWithTime('links.jade', data) + +@bottle.route('/<user>/<tags>') +def userLinks(user, tags): + tags = tags.split('+') + data = recentTags(user, tags) + data['desc'] = "%s's recent links tagged %s" % (user, tags) + data['toRoot'] = ".." + + data['pageTags'] = [{"word":t} for t in tags] + data['stats']['template'] = 'TEMPLATETIME' + return renderWithTime('links.jade', data) + +if __name__ == '__main__': + bottle.run(server='gunicorn', host='0.0.0.0', port=10002, workers=4)
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/run Sun Feb 17 03:56:28 2013 -0800 @@ -0,0 +1,2 @@ +#!/bin/sh +exec bin/python lookup.py
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/run_debug Sun Feb 17 03:56:28 2013 -0800 @@ -0,0 +1,2 @@ +#!/bin/sh +exec bin/bottle.py --debug --reload -b 0.0.0.0:10002 lookup
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/static/gui.js Sun Feb 17 03:56:28 2013 -0800 @@ -0,0 +1,52 @@ + +function toggleTag(tag) { + var p = window.location.pathname; + var comps = p.split("/"); + var selected = (comps[2] || "").split("+"); + var add = true; + var others = []; + for (var i=0; i < selected.length; i++) { + if (!selected[i]) { + continue; + } + if (selected[i] == tag) { + add = false; + } else { + others.push(selected[i]); + } + } + if (add) { + others.push(tag); + } + window.location.pathname = "/" + comps[1] + "/" + others.join("+"); +} + +function backspaceLastTag() { + var p = window.location.pathname; + var comps = p.split("/"); + var selected = (comps[2] || "").split("+"); + if (selected.length == 0) { + return; + } + toggleTag(selected[selected.length-1]); +} + +$("a.tag").click(function () { + var tag = $(this).text(); + toggleTag(tag); + return false; +}); + +$("#filterTag").change(function () { + var tag = $(this).val(); + toggleTag(tag); + return false; +}); + +$("#filterTag").keydown(function (ev) { + if ($(this).val() == "" && ev.which == 8) { + backspaceLastTag(); + } +}); + +$("#filterTag").focus();
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/static/style.css Sun Feb 17 03:56:28 2013 -0800 @@ -0,0 +1,56 @@ +body { + font-family: helvetica; + font-size: 12px; +} +h3 { + margin: 0; + margin-bottom: 7px; +} +.link { + width: 600px; + border: 1px solid #D8D8D8; + border-radius: 6px; + padding: 5px; + margin: 10px; +} + +.favicon { +width: 16px; +height: 16px; +display: inline-block; + } +h3 .favicon { +margin-right: 5px; +vertical-align: middle; + } +.notes { + margin: 5px; + color: #2A4E2B; +} +.tags { + margin-left: 24px; + color: black; +} +.tag { + text-decoration: none; + color: black; + display: inline-block; + border: 1px solid #707070; + border-radius: 5px; + background: #AAA; + padding: 0 8px; + + +} +.hrefShown { + margin: 4px 0 4px 24px; + color: #5E6A6F; +} +.hrefShown a { + text-decoration: none; + color: #027902; +} +.modified { + margin-left: 24px; + color: #5E6A6F; +} \ No newline at end of file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/template/head.jade.mustache Sun Feb 17 03:56:28 2013 -0800 @@ -0,0 +1,2 @@ +meta(name="viewport", content="width=650") +link(rel="stylesheet", href="{{toRoot}}/static/style.css") \ No newline at end of file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/template/links.jade.mustache Sun Feb 17 03:56:28 2013 -0800 @@ -0,0 +1,40 @@ +doctype html +html + head + title {{desc}} + {{> head.jade}} + body + h2 Showing {{desc}} + + | Filter to: + {{#pageTags}} + a.tag(href="{{word}}") {{word}} + | + {{/pageTags}} + input(type="text", id="filterTag") + + {{#links}} + .link + h3 + span.favicon(style="background-image: url(//www.google.com/s2/favicons?domain={{domain}})") + a(href="{{href}}") {{displayDescription}} + {{#extended}} + .notes + | {{extended}} + {{/extended}} + .tags + | Tags: + {{#tagWords}} + a.tag(href="{{word}}") {{word}} + | + {{/tagWords}} + .hrefShown + a(href="{{href}}") {{href}} + .modified + | Modified: {{t}} + + {{/links}} + + p {{stats}} + + {{> tail.jade}}