Move dirtag.xul and *.rdf generation into CherryPy.
[dirtag.git] / dirtag / web.py
diff --git a/dirtag/web.py b/dirtag/web.py
new file mode 100755 (executable)
index 0000000..fee3bb8
--- /dev/null
@@ -0,0 +1,160 @@
+#!/usr/bin/env python
+
+from optparse import OptionParser
+from os import path
+from urllib import urlencode
+
+import cherrypy
+from jinja2 import Environment, FileSystemLoader
+
+from dirtag import Dirtag, Tree, dir_tree
+
+
+class WebInterface:
+    """The web interface to Dirtag.
+    """
+    def __init__(self, dirtag, template_dir, repository_name='Dirtag'):
+        """Initialize the bug repository for this web interface."""
+        self.dirtag = dirtag
+        self.env = Environment(loader=FileSystemLoader(template_dir))
+        self.repository_name = repository_name    
+        self.rdf_root = 'http://dirtag.com/'
+    
+    @cherrypy.expose
+    def index(self):
+        template = self.env.get_template('dirtag.xul')
+        cherrypy.response.headers['Content-Type'] = \
+            'application/vnd.mozilla.xul+xml'
+        return template.render(repository_name=self.repository_name)
+
+    @cherrypy.expose
+    def raw_rdf(self):
+        return self.tree_rdf(self.dirtag.raw_dir, 'raw')
+
+    @cherrypy.expose
+    def tag_rdf(self):
+        return self.tree_rdf(self.dirtag.tag_dir, 'tag', with_files=False)
+
+    def tree_rdf(self, root_dir, name, with_files=True):
+        cherrypy.response.headers['Content-Type'] = \
+            'application/rdf+xml'
+        tree = dir_tree(root_dir)
+        lines = [
+            '<?xml version="1.0"?>',
+            '',
+            '<RDF:RDF xmlns:RDF="http://www.w3.org/1999/02/22-rdf-syntax-ns#"',
+            '         xmlns:FILES="%srdf#">' % self.rdf_root,
+            ]
+        if with_files == True:
+            nodes = tree.traverse(depth=1, dirtag=self.dirtag)
+        else:
+            nodes = tree.traverse(depth=1, type='dirs', dirtag=self.dirtag)
+        for node in nodes:
+            lines.extend([
+                    '  <RDF:Description RDF:about="%s%s">'
+                    % (self.rdf_root, '/'.join([name]+node)),
+                    '    <FILES:name>%s</FILES:name>' % node[-1],
+                    ])
+            if node.type == 'file':
+                lines.append('    <FILES:tags>%s</FILES:tags>'
+                             % ','.join(['/'.join(t) for t in node.tags]))
+            lines.append('  </RDF:Description>')
+
+        lines.append('  <RDF:Seq RDF:about="%s%s/files">'
+                     % (self.rdf_root, name))
+        stack = []
+        for node in tree.traverse(depth=1,):
+            while (len(stack) > 0 and
+                   not self.list_startswith(stack[-1], node[:-1])):
+                lines.extend([
+                        ' '*(4*len(stack)+2) + '</RDF:Seq>',
+                        ' '*(4*len(stack)) + '</RDF:li>',
+                        ])
+                stack.pop()
+            assert len(node) == len(stack)+1, (
+                'Floating %s -> %s (%d -> %d)'
+                % (stack[-1], node, len(stack), len(node)))
+            stack.append(node)
+            if node.type == 'file':
+                raw_node = self.dirtag.raw_node(node)
+                lines.append(' '*(4*len(stack))
+                             + '<RDF:li RDF:resource="%s%s"/>'
+                             % (self.rdf_root, '/'.join(['raw']+raw_node)))
+                stack.pop()
+            else:
+                lines.extend([
+                        ' '*(4*len(stack)) + '<RDF:li>',
+                        ' '*(4*len(stack)+2) + '<RDF:Seq RDF:about="%s%s">'
+                        % (self.rdf_root, '/'.join([name]+node)),
+                        ])
+        while len(stack) > 0:
+            lines.extend([
+                    ' '*(4*len(stack)+2) + '</RDF:Seq>',
+                    ' '*(4*len(stack)) + '</RDF:li>',
+                    ])
+            stack.pop()
+        lines.extend([
+                '  </RDF:Seq>',
+                '',
+                '</RDF:RDF>',
+                '',
+                ])
+        return '\n'.join(lines)
+
+    def list_startswith(self, a, b):
+        """
+        >>> x = WebInterface(None, 'template')
+        >>> x.list_startswith([1, 2, 3], [1, 2, 3, 4])
+        True
+        >>> x.list_startswith([1, 2, 3], [1, 2, 3])
+        True
+        >>> x.list_startswith([1, 2, 3], [1, 2])
+        False
+        >>> x.list_startswith([1, 2, 3], [2, 2, 3])
+        False
+        """
+        return len([x for x,y in zip(a,b) if x==y]) == len(a)
+
+    #raise cherrypy.HTTPRedirect('/static/dirtag.xul', status=302)
+    
+if __name__ == '__main__':
+    parser = OptionParser('%prog [options]', epilog=WebInterface.__doc__)
+    parser.add_option('-r', '--raw-dir', dest='raw_dir', metavar='DIR',
+                      default='raw',
+                      help='Location of raw files (%default)')
+    parser.add_option('-t', '--tag-dir', dest='tag_dir', metavar='DIR',
+                      default='tag',
+                      help='Location of tag links (%default)')
+    parser.add_option('-T', '--template-dir', dest='template_dir',
+                      metavar='DIR', default='template',
+                      help='Location of template files (%default)')
+    parser.add_option('-s', '--static-dir', dest='static_dir', metavar='DIR',
+                      default='static',
+                      help='Location of static files (%default)')
+    parser.add_option('-n', '--name', dest='name', metavar='STRING',
+                      default='dirtag',
+                      help='Name of the dirtag repository')
+    parser.add_option('--test', dest='test', default=False,
+                      action='store_true',
+                      help='Run unittests and exit')
+    options,args = parser.parse_args()
+
+    if options.test:
+        import doctest, sys
+        doctest.testmod()
+        sys.exit(0)
+
+    d = Dirtag(raw_dir=options.raw_dir, tag_dir=options.tag_dir)
+    w = WebInterface(
+        d, template_dir=options.template_dir, repository_name=options.name)
+    cherrypy.config.update({
+            'tools.encode.on': True,
+            'tools.encode.encoding': 'utf8',
+            })
+    app_config = {
+        '/static': {
+            'tools.staticdir.on': True,
+            'tools.staticdir.dir': path.abspath(options.static_dir),
+            }
+        }
+    cherrypy.quickstart(w, '/', app_config)