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')
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):
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)
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))
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
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):
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()
--- /dev/null
+#!/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)
+++ /dev/null
-<?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>
+++ /dev/null
-<?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>
<?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 ".">
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>
<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"
</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() -->