class Node (list):
- def __init__(self, root, type, tags, *args, **kwargs):
+ def __init__(self, tree, root, *args, **kwargs):
list.__init__(self, *args, **kwargs)
+ self.tree = tree
self.root = root
- self.type = type
- self.tags = tags
+ if isinstance(tree, Tree):
+ self.type = 'dir'
+ else:
+ self.type = 'file'
def pre_extend(self, a):
"""
- >>> n = Node(None, None, None, ['a', 'b', 'c'])
+ >>> n = Node(None, None, ['a', 'b', 'c'])
>>> n.pre_extend([1, 2, 3])
>>> print n
[1, 2, 3, 'a', 'b', 'c']
>>> t.append(Tree('1'))
>>> t.append(Tree('2'))
>>> t[0].append(Tree('A'))
+ >>> t[0].append('x')
>>> print '\\n'.join(['|'.join(x) for x in t.traverse()])
a
a|1
a|1|A
+ a|1|x
a|2
>>> print '\\n'.join(['|'.join(x) for x in t.traverse(prefix=['z'])])
z|a
z|a|1
z|a|1|A
+ z|a|1|x
z|a|2
+ >>> print '\\n'.join([x for x in t.hook_out(
+ ... on_open_dir=lambda node, data : ' '*len(node) + 'opening %s' % '/'.join(node),
+ ... on_close_dir=lambda node, data : ' '*len(node) + 'closing %s' % '/'.join(node),
+ ... on_empty_dir=lambda node, data : ' '*len(node) + 'empty %s' % '/'.join(node),
+ ... on_file=lambda node, data : ' '*len(node) + 'file %s' % '/'.join(node),)])
+ opening a
+ opening a/1
+ empty a/1/A
+ file a/1/x
+ closing a/1
+ empty a/2
+ closing a
"""
def __init__(self, data=None, root=None, *args, **kwargs):
self.data = data
def traverse(self, prefix=[], depth=0, type='both', dirtag=None):
p = prefix + [self.data]
if depth < len(p) and type in ['both', 'dirs']:
- yield Node(self.root, 'dir', [], p[depth:])
+ n = Node(self, self.root, p[depth:])
+ n.tags = []
+ yield n
for child in self:
if hasattr(child, 'traverse'):
for grandchild in child.traverse(
p, depth=depth, type=type, dirtag=dirtag):
yield grandchild
elif depth <= len(p) and type in ['both', 'files']:
- n = Node(self.root, 'file', [], (p + [child])[depth:])
+ n = Node(None, self.root, (p + [child])[depth:])
if dirtag != None:
- n.tags = dirtag.node_tags(n)
+ n.tags = list(dirtag.node_tags(n))
yield n
+ def hook_out(self, on_open_dir=None, on_close_dir=None,
+ on_empty_dir=None, on_file=None, data=None,
+ **kwargs):
+ stack = []
+ for node in self.traverse(**kwargs):
+ while (len(stack) > 0 and
+ not self._list_startswith(stack[-1], node[:-1])):
+ if on_close_dir != None:
+ yield on_close_dir(stack[-1], data)
+ 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':
+ if on_file != None:
+ yield on_file(node, data)
+ stack.pop()
+ elif node.type == 'dir' and len(node.tree) == 0:
+ if on_empty_dir != None:
+ yield on_empty_dir(node, data)
+ stack.pop()
+ else:
+ if on_open_dir != None:
+ yield on_open_dir(node, data)
+ while len(stack) > 0:
+ if on_close_dir != None:
+ yield on_close_dir(stack[-1], data)
+ stack.pop()
+
+ def _list_startswith(self, a, b):
+ """
+ >>> t = Tree('a')
+ >>> t._list_startswith([1, 2, 3], [1, 2, 3, 4])
+ True
+ >>> t._list_startswith([1, 2, 3], [1, 2, 3])
+ True
+ >>> t._list_startswith([1, 2, 3], [1, 2])
+ False
+ >>> t._list_startswith([1, 2, 3], [2, 2, 3])
+ False
+ """
+ return len([x for x,y in zip(a,b) if x==y]) == len(a)
+
def dir_tree(root_dir):
"""Generate a directory tree.
x|y|b1.html
x|a2.html
x|b3.html
+ >>> print '\\n'.join(['|'.join(x) for x in d.tags()])
+ 1
+ x
+ x|y
>>> print '\\n'.join(['|'.join(x) for x in d.tags(['b', 'b2.html'])])
<BLANKLINE>
>>> print '\\n'.join(['|'.join(x) for x in d.tags(['b', 'b1.html'])])
1
x|y
-
>>> print d.tag_path(['a', 'a3.html'], ['x', 'y'])
test/tag/x/y/a3.html
>>> os.listdir('test/tag/x/y')
node.pre_extend(tag)
yield node
- def tags(self, target):
- for node in dir_tree(self.tag_dir).traverse(depth=1):
- p = os.path.join(*([self.tag_dir]+node))
- t = os.path.abspath(os.path.join(*([self.raw_dir] + target)))
- if (os.path.islink(p) and os.path.realpath(p) == t):
- yield os.path.join(node[:-1])
+ def tags(self, target=None):
+ if target == None:
+ for node in dir_tree(self.tag_dir).traverse(depth=1, type='dirs'):
+ yield os.path.join(node)
+ else:
+ for node in dir_tree(self.tag_dir).traverse(depth=1):
+ p = os.path.join(*([self.tag_dir]+node))
+ t = os.path.abspath(os.path.join(*([self.raw_dir] + target)))
+ if (os.path.islink(p) and os.path.realpath(p) == t):
+ yield os.path.join(node[:-1])
def tag_path(self, target, tag):
# TODO: assumes unique basenames. Check?
class WebInterface:
"""The web interface to Dirtag.
"""
- def __init__(self, dirtag, template_dir, repository_name='Dirtag'):
+ def __init__(self, dirtag, template_dir='template',
+ 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/'
+ # RESTful HTML interface
+
@cherrypy.expose
def index(self):
+ template = self.env.get_template('dirtag.html')
+ return template.render(
+ repository_name=self.repository_name,
+ raw_html=self._dir_html(self.dirtag.raw_dir),
+ tag_html=self._dir_html(self.dirtag.tag_dir),
+ )
+
+ def _dir_html(self, root_dir):
+ """
+ >>> x = WebInterface(None)
+ >>> print x._dir_html('test/tag')
+ <ul>
+ <li><a href="">1</a></li>
+ <BLANKLINE>
+ <li>x
+ <ul>
+ <li><a href="">x/y</a></li>
+ <BLANKLINE>
+ </ul>
+ </li>
+ </ul>
+ """
+ lines = ['<ul>']
+ lines.extend([x for x in dir_tree(root_dir).hook_out(
+ depth=1, type='dirs',
+ on_open_dir=self._dir_html_on_open_dir,
+ on_close_dir=self._dir_html_on_close_dir,
+ on_empty_dir=self._dir_html_on_empty_dir,
+ )])
+ lines.append('</ul>')
+ return '\n'.join(lines)
+
+ def _dir_html_on_open_dir(self, node, data):
+ if len([x for x in node.tree if isinstance(x, Tree)]) > 0:
+ return ('%s<li>%s\n%s<ul>'
+ % (' '*(4*len(node)-2),
+ self._dir_html_link(node, data),
+ ' '*(4*len(node))))
+ return self._dir_html_on_empty_dir(node, data)
+
+ def _dir_html_on_close_dir(self, node, data):
+ if len([x for x in node.tree if isinstance(x, Tree)]) > 0:
+ return ('%s</ul>\n%s</li>'
+ % (' '*(4*len(node)), ' '*(4*len(node)-2)))
+ return ''
+
+ def _dir_html_on_empty_dir(self, node, data):
+ return ('%s<li>%s</li>'
+ % (' '*(4*len(node)-2), self._dir_html_link(node, data)))
+
+ def _dir_html_link(self, node, data):
+ return ('<a href="%s">%s</a>'
+ % ('dir?%s' % urlencode({
+ 'root':node.root,
+ 'selected':'/'.join(node),
+ }),
+ '/'.join(node)))
+
+ @cherrypy.expose
+ def dir(self, root, selected):
+ s = self._selected_dir(root, selected)
+ template = self.env.get_template('dir.html')
+
+ return template.render(
+ repository_name=self.repository_name,
+ files=[(f[-1],
+ '/'.join(f),
+ self._dir_tags(f),
+ 'file?%s' % urlencode({'selected':'/'.join(
+ self.dirtag.raw_node(f))}))
+ for f in s.tree.traverse(prefix=[s.root]+s[:-1],
+ depth=1,
+ type='files',
+ dirtag=self.dirtag)
+ if len(f) == len(s)+1])
+
+ def _dir_tags(self, node):
+ if len(node.tags) > 0:
+ return ','.join(['/'.join(t) for t in node.tags])
+ else:
+ return '-'
+
+ @cherrypy.expose
+ def file(self, selected):
+ # Disable form value caching in Firefox. See
+ # http://www.mozilla.org/projects/netlib/http/http-caching-faq.html
+ cherrypy.response.headers['Cache-Control'] = 'no-store'
+ s = self._selected_file(dir_tree(self.dirtag.raw_dir), selected)
+ template = self.env.get_template('file.html')
+ return template.render(
+ repository_name=self.repository_name,
+ selected_path=selected,
+ selected_url='static/raw/'+selected,
+ tags=[('/'.join(t), t in s.tags) for t in self.dirtag.tags()],
+ )
+
+ def _selected_dir(self, root, selected):
+ if root == self.dirtag.raw_dir:
+ tree = dir_tree(self.dirtag.raw_dir)
+ else:
+ assert root == self.dirtag.tag_dir, root
+ tree = dir_tree(self.dirtag.tag_dir)
+ s = None
+ for node in tree.traverse(depth=1, type='dirs'):
+ if '/'.join(node) == selected:
+ s = node
+ break
+ assert s != None, selected
+ assert s.tree != None, selected
+ return s
+
+ def _selected_file(self, tree, selected):
+ s = None
+ for node in tree.traverse(depth=1, type='files', dirtag=self.dirtag):
+ if '/'.join(node) == selected:
+ s = node
+ break
+ assert s != None, selected
+ assert s.tree == None, selecte
+ return s
+
+ # HTML user input methods
+
+ @cherrypy.expose
+ def set_tags(self, path, tags=[], selected=None):
+ s = self._selected_file(dir_tree(self.dirtag.raw_dir), path)
+ old_tags = list(s.tags)
+ path = path.split('/')
+ selected_tags = [tag.split('/') for tag in tags]
+ for tag in self.dirtag.tags():
+ if tag in selected_tags:
+ if tag not in old_tags:
+ self.dirtag.add_tag(path, tag)
+ else:
+ if tag in old_tags:
+ self.dirtag.remove_tag(path, tag)
+ if selected != None:
+ raise cherrypy.HTTPRedirect(
+ 'file?%s' % urlencode({'selected':selected}),
+ status=302)
+ return '<p>Removed tag %s from %s</p>' % (tag, path)
+
+ # XUL interface
+
+ @cherrypy.expose
+ def dirtag_xul(self):
template = self.env.get_template('dirtag.xul')
cherrypy.response.headers['Content-Type'] = \
'application/vnd.mozilla.xul+xml'
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([x for x in tree.hook_out(
+ data=self, depth=1,
+ on_open_dir=lambda node,data: (
+ '%s<RDF:li>\n%s<RDF:Seq RDF:about="%s%s">'
+ % (' '*(4*len(node)), ' '*(4*len(node)+2),
+ data.rdf_root, '/'.join([name]+node))),
+ on_close_dir=lambda node,data: (
+ '%s</RDF:Seq>\n%s</RDF:li>'
+ % (' '*(4*len(node)+2), ' '*(4*len(node)))),
+ on_file=lambda node,data: (
+ '%s<RDF:li RDF:resource="%s%s"/>'
+ % (' '*(4*len(node)), data.rdf_root,
+ '/'.join(['raw']+data.dirtag.raw_node(node)))),
+ )])
lines.extend([
' </RDF:Seq>',
'',
])
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)
+ # XUL user input methods
@cherrypy.expose
def new_tag(self, tag):