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}}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/template/tail.jade.mustache	Sun Feb 17 03:56:28 2013 -0800
@@ -0,0 +1,2 @@
+script(src='//ajax.googleapis.com/ajax/libs/jquery/1.4.2/jquery.min.js')
+script(src="{{toRoot}}/static/gui.js")
\ No newline at end of file