Move dirtag.xul and *.rdf generation into CherryPy.
authorW. Trevor King <wking@drexel.edu>
Sat, 17 Jul 2010 15:34:50 +0000 (11:34 -0400)
committerW. Trevor King <wking@drexel.edu>
Sat, 17 Jul 2010 22:16:26 +0000 (18:16 -0400)
Now the intial data displayed by dirtag.xul matches the on-disk raw/tag
structure.

I made a few changes in node handling so traversals would have all the
information required (Node.root, Node.type, Node.tags, and associated
handlers).

dirtag/__init__.py
dirtag/web.py [new file with mode: 0755]
static/raw-tree.rdf [deleted file]
static/tag-tree.rdf [deleted file]
template/dirtag.xul [moved from static/dirtag.xul with 94% similarity]

index 406c2860225cd6cc8809bad24b04065bbe55aedf..cb69eeb9ef2c741f9774bfa4a9df8a21a2063031 100644 (file)
@@ -20,6 +20,24 @@ import os
 import os.path
 
 
+class Node (list):
+    def __init__(self, root, type, tags, *args, **kwargs):
+        list.__init__(self, *args, **kwargs)
+        self.root = root
+        self.type = type
+        self.tags = tags
+
+    def pre_extend(self, a):
+        """
+        >>> n = Node(None, None, None, ['a', 'b', 'c'])
+        >>> n.pre_extend([1, 2, 3])
+        >>> print n
+        [1, 2, 3, 'a', 'b', 'c']
+        """
+        for i,element in enumerate(a):
+            self.insert(i, element)
+
+
 class Tree (list):
     """
     >>> t = Tree('a')
@@ -36,25 +54,26 @@ class Tree (list):
     z|a|1
     z|a|1|A
     z|a|2
-    >>> print '\\n'.join(['|'.join(x) for x in t.traverse(depth=1)])
-    1
-    1|A
-    2
     """
-    def __init__(self, data=None, *args, **kwargs):
+    def __init__(self, data=None, root=None, *args, **kwargs):
         self.data = data
+        self.root = root
         list.__init__(self, *args, **kwargs)
 
-    def traverse(self, prefix=[], depth=0):
+    def traverse(self, prefix=[], depth=0, type='both', dirtag=None):
         p = prefix + [self.data]
-        if depth < len(p):
-            yield p[depth:]
+        if depth < len(p) and type in ['both', 'dirs']:
+            yield Node(self.root, 'dir', [], p[depth:])
         for child in self:
             if hasattr(child, 'traverse'):
-                for grandchild in child.traverse(p, depth=depth):
+                for grandchild in child.traverse(
+                    p, depth=depth, type=type, dirtag=dirtag):
                     yield grandchild
-            elif depth <= len(p):
-                yield (p + [child])[depth:]
+            elif depth <= len(p) and type in ['both', 'files']:
+                n = Node(self.root, 'file', [], (p + [child])[depth:])
+                if dirtag != None:
+                    n.tags = dirtag.node_tags(n)
+                yield n
 
 
 def dir_tree(root_dir):
@@ -70,6 +89,16 @@ def dir_tree(root_dir):
     x|y|b1.dat
     x|a2.dat
     x|b3.dat
+    >>> print '\\n'.join(['|'.join(x) for x in t.traverse(depth=1, type='files')])
+    1|a1.dat
+    1|b1.dat
+    x|y|b1.dat
+    x|a2.dat
+    x|b3.dat
+    >>> print '\\n'.join(['|'.join(x) for x in t.traverse(depth=1, type='dirs')])
+    1
+    x
+    x|y
     """
     if not os.path.exists(root_dir):
         raise ValueError(root_dir)
@@ -77,11 +106,11 @@ def dir_tree(root_dir):
     for dirpath,dirnames,filenames in os.walk(root_dir, followlinks=True):
         t = trees.get(
             dirpath,
-            Tree(os.path.basename(dirpath)))
+            Tree(os.path.basename(dirpath), root=root_dir))
         if dirpath == root_dir:
             root = t
         for d in sorted(dirnames):
-            t2 = Tree(d)
+            t2 = Tree(d, root=root_dir)
             trees[os.path.join(dirpath, d)] = t2
             t.append(t2)
         t.extend(sorted(filenames))
@@ -91,15 +120,18 @@ def dir_tree(root_dir):
 class Dirtag (object):
     """
     >>> d = Dirtag('test/raw', 'test/tag')
-    >>> print '\\n'.join(['|'.join(x) for x in d.elements()])
-    a
-    a|a1.dat
-    a|a2.dat
-    a|a3.dat
-    b
-    b|b1.dat
-    b|b2.dat
-    b|b3.dat
+    >>> print '\\n'.join(['\t'.join([x.type, x.root, '|'.join(x),
+    ...                              ','.join(['/'.join(t) for t in x.tags])])
+    ...                   for x in d.elements()]
+    ...                 )  # doctest: +NORMALIZE_WHITESPACE
+    dir       test/raw      a      
+    file      test/raw      a|a1.dat      1
+    file      test/raw      a|a2.dat      x
+    file      test/raw      a|a3.dat      
+    dir       test/raw      b      
+    file      test/raw      b|b1.dat      1,x/y
+    file      test/raw      b|b2.dat      
+    file      test/raw      b|b3.dat      x
     >>> print '\\n'.join(['|'.join(x) for x in d.elements(['x'])])
     x|y
     x|y|b1.dat
@@ -131,13 +163,14 @@ class Dirtag (object):
     def elements(self, tag=None):
         if tag == None:
             tag = []
-            nodes = dir_tree(self.raw_dir).traverse(depth=1)
+            nodes = dir_tree(self.raw_dir).traverse(depth=1, dirtag=self)
         else:
             p = [self.tag_dir]+tag
             nodes = dir_tree(os.path.join(*p)).traverse(
-                prefix=p[:-1], depth=len(p))
+                prefix=p[:-1], depth=len(p), dirtag=self)
         for node in nodes:
-            yield tag+node
+            node.pre_extend(tag)
+            yield node
 
     def tags(self, target):
         for node in dir_tree(self.tag_dir).traverse(depth=1):
@@ -170,6 +203,21 @@ class Dirtag (object):
         finally:
             os.chdir(cwd)
 
+    def raw_node(self, node):
+        """Return a node for the real file linked to by node.
+        """
+        if node.root == self.raw_dir:
+            return node
+        path = os.path.realpath(os.path.join(node.root, *node))
+        raw_path = os.path.relpath(path, self.raw_dir)
+        return raw_path.split(os.path.sep)
+
+    def node_tags(self, node):
+        """Return a list of tags for a particular node.
+        """
+        return self.tags(self.raw_node(node))
+
+
 if __name__ == '__main__':
     import doctest
     doctest.testmod()
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)
diff --git a/static/raw-tree.rdf b/static/raw-tree.rdf
deleted file mode 100644 (file)
index 4782d2e..0000000
+++ /dev/null
@@ -1,61 +0,0 @@
-<?xml version="1.0"?>
-
-<RDF:RDF xmlns:RDF="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
-         xmlns:FILES="http://dirtag.com/rdf#">
-
-  <RDF:Description RDF:about="http://dirtag.com/raw/a">
-    <FILES:name>a</FILES:name>
-  </RDF:Description>
-
-    <RDF:Description RDF:about="http://dirtag.com/raw/a/a1.dat">
-      <FILES:name>a1.dat</FILES:name>
-      <FILES:tags>1</FILES:tags>
-    </RDF:Description>
-
-    <RDF:Description RDF:about="http://dirtag.com/raw/a/a2.dat">
-      <FILES:name>a2.dat</FILES:name>
-      <FILES:tags>x</FILES:tags>
-    </RDF:Description>
-
-    <RDF:Description RDF:about="http://dirtag.com/raw/a/a3.dat">
-      <FILES:name>a3.dat</FILES:name>
-      <FILES:tags></FILES:tags>
-    </RDF:Description>
-
-  <RDF:Description RDF:about="http://dirtag.com/raw/b">
-    <FILES:name>b</FILES:name>
-  </RDF:Description>
-
-    <RDF:Description RDF:about="http://dirtag.com/raw/b/b1.dat">
-      <FILES:name>b1.dat</FILES:name>
-      <FILES:tags>1,x/y</FILES:tags>
-    </RDF:Description>
-
-    <RDF:Description RDF:about="http://dirtag.com/raw/b/b2.dat">
-      <FILES:name>b2.dat</FILES:name>
-      <FILES:tags></FILES:tags>
-    </RDF:Description>
-
-    <RDF:Description RDF:about="http://dirtag.com/raw/b/b3.dat">
-      <FILES:name>b3.dat</FILES:name>
-      <FILES:tags>x</FILES:tags>
-    </RDF:Description>
-
-  <RDF:Seq RDF:about="http://dirtag.com/raw/files">
-    <RDF:li>
-      <RDF:Seq RDF:about="http://dirtag.com/raw/a">
-        <RDF:li RDF:resource="http://dirtag.com/raw/a/a1.dat"/>
-        <RDF:li RDF:resource="http://dirtag.com/raw/a/a2.dat"/>
-        <RDF:li RDF:resource="http://dirtag.com/raw/a/a3.dat"/>
-      </RDF:Seq>
-    </RDF:li>
-    <RDF:li>
-      <RDF:Seq RDF:about="http://dirtag.com/raw/b">
-        <RDF:li RDF:resource="http://dirtag.com/raw/b/b1.dat"/>
-        <RDF:li RDF:resource="http://dirtag.com/raw/b/b2.dat"/>
-        <RDF:li RDF:resource="http://dirtag.com/raw/b/b3.dat"/>
-      </RDF:Seq>
-    </RDF:li>
-  </RDF:Seq>
-
-</RDF:RDF>
diff --git a/static/tag-tree.rdf b/static/tag-tree.rdf
deleted file mode 100644 (file)
index 50d181e..0000000
+++ /dev/null
@@ -1,38 +0,0 @@
-<?xml version="1.0"?>
-
-<RDF:RDF xmlns:RDF="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
-         xmlns:FILES="http://dirtag.com/rdf#">
-
-  <RDF:Description RDF:about="http://dirtag.com/tag/1">
-    <FILES:name>1</FILES:name>
-  </RDF:Description>
-
-  <RDF:Description RDF:about="http://dirtag.com/tag/x">
-    <FILES:name>x</FILES:name>
-  </RDF:Description>
-
-    <RDF:Description RDF:about="http://dirtag.com/tag/x/y">
-      <FILES:name>y</FILES:name>
-    </RDF:Description>
-
-  <RDF:Seq RDF:about="http://dirtag.com/tag/files">
-    <RDF:li>
-      <RDF:Seq RDF:about="http://dirtag.com/tag/1">
-        <RDF:li RDF:resource="http://dirtag.com/raw/a/a1.dat"/>
-        <RDF:li RDF:resource="http://dirtag.com/raw/b/b1.dat"/>
-      </RDF:Seq>
-    </RDF:li>
-    <RDF:li>
-      <RDF:Seq RDF:about="http://dirtag.com/tag/x">
-        <RDF:li RDF:resource="http://dirtag.com/raw/a/a2.dat"/>
-        <RDF:li RDF:resource="http://dirtag.com/raw/b/b3.dat"/>
-       <RDF:li>
-         <RDF:Seq RDF:about="http://dirtag.com/tag/x/y">
-            <RDF:li RDF:resource="http://dirtag.com/raw/b/b1.dat"/>
-         </RDF:Seq>
-       </RDF:li>
-      </RDF:Seq>
-    </RDF:li>
-  </RDF:Seq>
-
-</RDF:RDF>
similarity index 94%
rename from static/dirtag.xul
rename to template/dirtag.xul
index cf5ac73104a6ba7161e00804f5f2c5558272c0c2..a6d6920cc47143a5403bbd1036c3c11babd29061 100644 (file)
@@ -1,12 +1,12 @@
 <?xml version="1.0"?>
-<?xml-stylesheet href="dirtag.css" type="text/css"?>
+<?xml-stylesheet href="/static/dirtag.css" type="text/css"?>
 
 <!-- Firefox doesn't load external DTDs for web-served XML,
     https://developer.mozilla.org/en/XML_in_Mozilla#DTDs_and_Other_External_Entities
   so we use and internal DOCTYPE.  This does make localization a bit
   more annoying. -->
 <!DOCTYPE window [
-  <!ENTITY window.title "TODO: Title">
+  <!ENTITY window.title "{{ repository_name }}">
   <!ENTITY XUL_note.text.pre_XUL "DirTag uses ">
   <!ENTITY XUL_note.text.XUL_to_Firefox ", an XML user interface markup language developed by the Mozilla project.  You probably need to view this page using ">
   <!ENTITY XUL_note.text.post_Firefox ".">
@@ -28,7 +28,7 @@
    xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
    >
 
-  <script type="application/javascript" src="dirtag.js"/>
+  <script type="application/javascript" src="/static/dirtag.js"/>
   <groupbox id="new-tag">
     <caption label="&new_tag.caption;"/>
     <hbox>
@@ -53,7 +53,7 @@
        <tabpanels flex="1">
          <tabpanel flex="1">
            <tree id="raw-tree" rows="2" seltype="single" flex="1"
-                 datasources="raw-tree.rdf" ref="http://dirtag.com/raw/files"
+                 datasources="raw.rdf" ref="http://dirtag.com/raw/files"
                  flags="dont-build-content" onselect="raw_tree_select();">
              <treecols>
                <treecol id="tab-tree-path-column" label="&path_column.label;" primary="true"
@@ -78,7 +78,7 @@
          </tabpanel>
          <tabpanel flex="1">
            <tree id="tag-tree" rows="2" seltype="single" flex="1"
-                 datasources="tag-tree.rdf raw-tree.rdf"
+                 datasources="tag.rdf raw.rdf"
                  ref="http://dirtag.com/tag/files"
                  flags="dont-build-content" onselect="tag_tree_select();">
              <treecols>
        <caption label="&tags.caption;"/>
        <tree id="checkbox-tree" rows="2" seltype="single" selstype="primary"
              editable="true"
-             datasources="tag-tree.rdf raw-tree.rdf"
+             datasources="tag.rdf raw.rdf"
              ref="http://dirtag.com/tag/files"
              flex="1">
          <!-- checkbox callback attached in dirtag.js's initialize() -->