diff --git a/wiki/README.txt b/wiki/README.txt index ac382d6..51eaced 100644 --- a/wiki/README.txt +++ b/wiki/README.txt @@ -4,6 +4,9 @@ Yet Another WikiWiki Framework ($Id$) + >>> from zope import component + >>> from zope.publisher.browser import TestRequest + An Example for an Elementary Wiki Structure =========================================== @@ -26,9 +29,12 @@ We format the content of the start page using the restructured text format. The parser for restructured text and a corresponding HTML writer are the default plugins used, so we can already render the page as HTML. - >>> print startPage.render() + >>> print startPage.render(TestRequest())
Welcome to the Demo Wiki
+Links to existing pages +----------------------- + We now create another page that contains a link to the start page. >>> aboutPage = wiki.createPage('about') @@ -40,11 +46,36 @@ We now create another page that contains a link to the start page. ... `Back to the Start PageInformation about the Demo Wiki
This is the cybertools demo wiki.
- + + +Let's now have a look at the link manager - it should have recorded the link +from the page content. + + >>> from cybertools.wiki.interfaces import ILinkManager + >>> linkManager = manager.getPlugin(ILinkManager, 'basic') + >>> links = linkManager.links + >>> len(links) + 1 + >>> link = links.values()[0] + >>> link.source, link.target, link.name, link.refuri + (0, 1, u'start_page', 'http://127.0.0.1/demo_wiki/start_page') + +Links to not yet existing pages +------------------------------- + + >>> aboutPage.text += ''' + ... `More...Information about the Demo Wiki
+This is the cybertools demo wiki.
+ + + diff --git a/wiki/base/config.py b/wiki/base/config.py index 97ac548..2adf598 100644 --- a/wiki/base/config.py +++ b/wiki/base/config.py @@ -40,7 +40,7 @@ class BaseConfiguration(object): def getConfig(self, functionality): c = self.get(functionality) if c is None: - parent = self.getParent() + parent = self.getConfigParent() if parent is not None: return parent.getConfig(functionality) return c @@ -48,7 +48,7 @@ class BaseConfiguration(object): def get(self, key, default=None): return getattr(self, key, None) - def getParent(self): + def getConfigParent(self): return self.parent diff --git a/wiki/base/link.py b/wiki/base/link.py index e2b3d82..5da713d 100644 --- a/wiki/base/link.py +++ b/wiki/base/link.py @@ -22,9 +22,11 @@ Basic (sample) implementations for links and link management $Id$ """ +from docutils.nodes import Text from zope.interface import implements +from zope.traversing.browser import absoluteURL -from cybertools.wiki.interfaces import ILink, ILinkManager +from cybertools.wiki.interfaces import ILink, ILinkManager, ILinkProcessor class LinkManager(object): @@ -41,15 +43,31 @@ class LinkManager(object): link = Link(name, source, target, **kw) link.manager = self id = self.generateLinkIdentifier(link) - self.linksBySource[source] = self.links[id] = link + self.links[id] = link + self.linksBySource.setdefault(source, []).append(link) + return link def removeLink(self, link): if link.identifier in self.links: link.manager = None del self.links[link.identifier] + def query(self, source=None, target=None, name=None, **kw): + if source is None: + result = self.links.values() + else: + result = self.linksBySource.get(source, []) + kw.update(dict(target=target, name=name)) + for k, v in kw.items(): + if v is None: + continue + if not isinstance(v, (list, tuple)): + v = [v] + result = [r for r in result if getattr(r, k) in v] + return result + def generateLinkIdentifier(self, link): - identifier = 'l%07i' % (max(self.links.keys() or [0]) + 1) + identifier = '%07i' % (max([int(k) for k in self.links.keys()] or [0]) + 1) link.identifier = identifier return identifier @@ -76,5 +94,51 @@ class Link(object): def __getattr__(self, attr): if attr not in ILink: raise AttributeError(attr) - return getattr(self, attr, None) + return self.__dict__.get(attr) + + +class LinkProcessor(object): + + implements(ILinkProcessor) + + parent = None # parent (tree) processor + + def __init__(self, context): + self.node = self.context = context + + def getProperties(self): + raise ValueError("Method 'getProperties()' must be implemented by subclass.") + + def process(self): + #print 'processing reference:', self.node + props = self.getProperties() + source = self.parent.context + wiki = source.getWiki() + manager = wiki.getManager() + sourceUid = manager.getUid(source) + name = props['targetName'] + lmName = source.getConfig('linkManager') + lm = wiki.getManager().getPlugin(ILinkManager, lmName) + existing = lm.query(source=sourceUid, name=name) + if existing: + link = existing[0] + target = manager.getObject(link.target) + else: + target = wiki.getPage(name) + targetUid = manager.getUid(target) + link = lm.createLink(name, sourceUid, targetUid) + if link.refuri is None: + request = self.parent.request + if request is not None: + if target is None: + link.refuri = '%s/create.html?linkid=%s' % ( + absoluteURL(wiki, request), link.identifier) + else: + link.refuri = absoluteURL(target, request) + self.setProperty('refuri', link.refuri) + if target is None: + # change CSS class, link text + # needs overriding of HTMLTranslator.visit_reference() + self.setProperty('class', 'create') # no direct effect + self.node.insert(0, Text('?')) diff --git a/wiki/base/process.py b/wiki/base/process.py index 0a1438c..284ec38 100644 --- a/wiki/base/process.py +++ b/wiki/base/process.py @@ -38,7 +38,7 @@ class TreeProcessor(object): implements(ITreeProcessor) adapts(IWikiPage) - tree = None + tree = request = None visitor = None def __init__(self, context): @@ -54,20 +54,17 @@ class Visitor(object): def __init__(self, context): self.context = context # the tree processor self.document = context.tree # needed internally - self.processors = {} # cache self.processorNames = self.context.context.getConfig('nodeProcessors') def dispatch_visit(self, node): #print 'visiting', node.tagname tag = node.tagname - procs = self.processors.get(tag) - if procs is None: - procs = self.processors[tag] = [] - procNames = self.processorNames.get(tag, []) - for n in procNames: - proc = component.queryAdapter(node, INodeProcessor, name=n) - if proc is not None: - proc.parent = self.context - procs.append(proc) + procs = [] + procNames = self.processorNames.get(tag, []) + for n in procNames: + proc = component.queryAdapter(node, INodeProcessor, name=n) + if proc is not None: + proc.parent = self.context + procs.append(proc) for p in procs: p.process() diff --git a/wiki/base/wiki.py b/wiki/base/wiki.py index bdf0c36..4ff51d1 100644 --- a/wiki/base/wiki.py +++ b/wiki/base/wiki.py @@ -24,6 +24,7 @@ $Id$ from zope import component from zope.interface import implements +from zope.app.intid.interfaces import IIntIds from cybertools.wiki.interfaces import IWikiConfiguration from cybertools.wiki.interfaces import IWikiManager, IWiki, IWikiPage @@ -57,9 +58,15 @@ class WikiManager(BaseConfiguration): def getPlugin(self, type, name): return component.getUtility(type, name=name) + def getUid(self, obj): + return component.getUtility(IIntIds).getId(obj) + + def getObject(self, uid): + return component.getUtility(IIntIds).getObject(uid) + # configuration - def getParent(self): + def getConfigParent(self): return component.getUtility(IWikiConfiguration) @@ -96,7 +103,7 @@ class Wiki(BaseConfiguration): # configuration - def getParent(self): + def getConfigParent(self): return self.getManager() @@ -114,10 +121,10 @@ class WikiPage(BaseConfiguration): def getWiki(self): return self.wiki - def render(self): + def render(self, request=None): source = self.preprocess(self.text) tree = self.parse(source) - self.process(tree) + self.process(tree, request) result = self.write(tree) return self.postprocess(result) @@ -134,10 +141,11 @@ class WikiPage(BaseConfiguration): def preprocess(self, source): return source - def process(self, tree): + def process(self, tree, request=None): processor = component.getAdapter(self, ITreeProcessor, name=self.getConfig('processor')) processor.tree = tree + processor.request = request processor.process() def postprocess(self, result): @@ -145,6 +153,6 @@ class WikiPage(BaseConfiguration): # configuration - def getParent(self): + def getConfigParent(self): return self.getWiki() diff --git a/wiki/dcu/process.py b/wiki/dcu/process.py index 62ada95..07e02b2 100644 --- a/wiki/dcu/process.py +++ b/wiki/dcu/process.py @@ -26,26 +26,15 @@ from docutils.nodes import reference from zope.interface import implements from zope.component import adapts -from cybertools.wiki.interfaces import INodeProcessor, ILinkManager +from cybertools.wiki.base.link import LinkProcessor -class Reference(object): +class Reference(LinkProcessor): - implements(INodeProcessor) adapts(reference) - parent = None # parent (tree) processor - - def __init__(self, context): - self.node = self.context = context - - def process(self): - print 'processing reference:', self.node - source = self.parent.context - wiki = source.getWiki() - sourceName = ':'.join((wiki.name, source.name)) - targetName = self.node['refuri'] - lmName = source.getConfig('linkManager') - lm = wiki.getManager().getPlugin(ILinkManager, lmName) - target = wiki.getPage(targetName) + def getProperties(self): + return dict(targetName=self.node['refuri']) + def setProperty(self, name, value): + self.node[name] = value diff --git a/wiki/interfaces.py b/wiki/interfaces.py index 3073125..02f245d 100644 --- a/wiki/interfaces.py +++ b/wiki/interfaces.py @@ -35,7 +35,7 @@ class IWikiConfiguration(Interface): parser = Attribute('Plug-in component converting from text input ' 'format to internal tree format.') - def getParent(): + def getConfigParent(): """ Return the parent object in case this configuration does not provide configuration information for a certain functionality. """ @@ -66,6 +66,13 @@ class IWikiManager(Interface): """ Return the plugin of the type given with the name given. """ + def getUid(obj): + """ Return the unique id of the object given. + """ + + def getObject(uid): + """ Return the object referenced by the unique id given. + """ class IWiki(Interface): """ A collection of wiki pages, or - more generally - wiki components. @@ -111,7 +118,7 @@ class IWikiPage(Interface): """ The wiki this page belongs to.' """ - def render(): + def render(request=None): """ Convert the source text of the page to presentation format. """ @@ -127,7 +134,7 @@ class IWikiPage(Interface): """ Modify the source text of the page before parsing it and return it. """ - def process(tree): + def process(tree, request=None): """ Scan the tree, changing it if necessary and collecting interesting information about the nodes, e.g. about links. """ @@ -162,6 +169,8 @@ class ITreeProcessor(Interface): context = Attribute('The wiki page from which the tree was generated.') tree = Attribute('The tree to be processed.') + request = Attribute('An optional request object, needed e.g. for ' + 'rendering absolute URLs.') def process(): """ Do what is necessary. @@ -179,6 +188,11 @@ class INodeProcessor(Interface): """ Do what is necessary. """ + def getProperties(): + """ Return a dictionary of properties provided by the context + node that may be needed for processing. + """ + # wiki elements @@ -187,7 +201,7 @@ class ILinkManager(Interface): """ def createLink(name, source, target, **kw): - """ Create and register a link record. + """ Create, register, and return a link record. Optional attributes are given as keyword arguments. """ @@ -231,6 +245,7 @@ class ILink(Interface): 'for external links - the target URI.') targetFragment = Attribute('Optional: an address part leading to a ' 'text anchor or the part of an image.') + refuri = Attribute('The URI linking to the target object.') user = Attribute('Optional: a string denoting the creator of the record.') run = Attribute('Optional: May be used to group the links from a certain ' 'source at different times.') @@ -238,3 +253,9 @@ class ILink(Interface): def getManager(): """ Return the link manager this link is managed by. """ + + +class ILinkProcessor(INodeProcessor): + """ A node processor specialized on links (references). + """ + diff --git a/wiki/tests.py b/wiki/tests.py index 2c524a0..594c21f 100755 --- a/wiki/tests.py +++ b/wiki/tests.py @@ -9,13 +9,38 @@ $Id$ import unittest, doctest from zope.testing.doctestunit import DocFileSuite from zope import component +from zope.interface import implements +from zope.app.intid.interfaces import IIntIds +from zope.publisher.interfaces.browser import IBrowserRequest +from zope.traversing.browser.interfaces import IAbsoluteURL +from cybertools.relation.tests import IntIdsStub from cybertools.wiki.base.config import WikiConfiguration from cybertools.wiki.base.process import TreeProcessor from cybertools.wiki.base.link import LinkManager from cybertools.wiki.dcu.html import Writer as DocutilsHTMLWriter from cybertools.wiki.dcu.rstx import Parser as DocutilsRstxParser from cybertools.wiki.dcu import process +from cybertools.wiki.interfaces import IWiki, IWikiPage + + +class WikiURL(object): + + implements(IAbsoluteURL) + + def __init__(self, context, request): + self.context = context + self.request = request + + def __call__(self): + return '%s/%s' % (self.request.URL, self.context.name) + + +class PageURL(WikiURL): + + def __call__(self): + return '%s/%s' % (WikiURL(self.context.getWiki(), self.request)(), + self.context.name) class Test(unittest.TestCase): @@ -26,6 +51,9 @@ class Test(unittest.TestCase): def setUp(testCase): + component.provideAdapter(WikiURL, (IWiki, IBrowserRequest), IAbsoluteURL) + component.provideAdapter(PageURL, (IWikiPage, IBrowserRequest), IAbsoluteURL) + component.provideUtility(IntIdsStub()) component.provideUtility(WikiConfiguration()) component.provideUtility(DocutilsHTMLWriter(), name='docutils.html') component.provideUtility(DocutilsRstxParser(), name='docutils.rstx')