Mercurial > genshi > mirror
diff examples/trac/trac/wiki/api.py @ 39:93b4dcbafd7b trunk
Copy Trac to main branch.
author | cmlenz |
---|---|
date | Mon, 03 Jul 2006 18:53:27 +0000 |
parents | |
children |
line wrap: on
line diff
new file mode 100644 --- /dev/null +++ b/examples/trac/trac/wiki/api.py @@ -0,0 +1,264 @@ +# -*- coding: utf-8 -*- +# +# Copyright (C) 2003-2005 Edgewall Software +# Copyright (C) 2003-2005 Jonas Borgström <jonas@edgewall.com> +# Copyright (C) 2004-2005 Christopher Lenz <cmlenz@gmx.de> +# All rights reserved. +# +# This software is licensed as described in the file COPYING, which +# you should have received as part of this distribution. The terms +# are also available at http://trac.edgewall.com/license.html. +# +# This software consists of voluntary contributions made by many +# individuals. For the exact contribution history, see the revision +# history and logs, available at http://projects.edgewall.com/trac/. +# +# Author: Jonas Borgström <jonas@edgewall.com> +# Christopher Lenz <cmlenz@gmx.de> + +try: + import threading +except ImportError: + import dummy_threading as threading +import time +import urllib +import re + +from trac.config import BoolOption +from trac.core import * +from trac.util.markup import html + + +class IWikiChangeListener(Interface): + """Extension point interface for components that should get notified about + the creation, deletion and modification of wiki pages. + """ + + def wiki_page_added(page): + """Called whenever a new Wiki page is added.""" + + def wiki_page_changed(page, version, t, comment, author, ipnr): + """Called when a page has been modified.""" + + def wiki_page_deleted(page): + """Called when a page has been deleted.""" + + def wiki_page_version_deleted(page): + """Called when a version of a page has been deleted.""" + + +class IWikiPageManipulator(Interface): + """Extension point interface for components that need to to specific + pre and post processing of wiki page changes. + + Unlike change listeners, a manipulator can reject changes being committed + to the database. + """ + + def prepare_wiki_page(req, page, fields): + """Not currently called, but should be provided for future + compatibility.""" + + def validate_wiki_page(req, page): + """Validate a wiki page after it's been populated from user input. + + Must return a list of `(field, message)` tuples, one for each problem + detected. `field` can be `None` to indicate an overall problem with the + page. Therefore, a return value of `[]` means everything is OK.""" + + +class IWikiMacroProvider(Interface): + """Extension point interface for components that provide Wiki macros.""" + + def get_macros(): + """Return an iterable that provides the names of the provided macros.""" + + def get_macro_description(name): + """Return a plain text description of the macro with the specified name. + """ + + def render_macro(req, name, content): + """Return the HTML output of the macro.""" + + +class IWikiSyntaxProvider(Interface): + + def get_wiki_syntax(): + """Return an iterable that provides additional wiki syntax. + + Additional wiki syntax correspond to a pair of (regexp, cb), + the `regexp` for the additional syntax and the callback `cb` + which will be called if there's a match. + That function is of the form cb(formatter, ns, match). + """ + + def get_link_resolvers(): + """Return an iterable over (namespace, formatter) tuples. + + Each formatter should be a function of the form + fmt(formatter, ns, target, label), and should + return some HTML fragment. + The `label` is already HTML escaped, whereas the `target` is not. + """ + + +class WikiSystem(Component): + """Represents the wiki system.""" + + implements(IWikiChangeListener, IWikiSyntaxProvider) + + change_listeners = ExtensionPoint(IWikiChangeListener) + macro_providers = ExtensionPoint(IWikiMacroProvider) + syntax_providers = ExtensionPoint(IWikiSyntaxProvider) + + INDEX_UPDATE_INTERVAL = 5 # seconds + + ignore_missing_pages = BoolOption('wiki', 'ignore_missing_pages', 'false', + """Enable/disable highlighting CamelCase links to missing pages + (''since 0.9'').""") + + split_page_names = BoolOption('wiki', 'split_page_names', 'false', + """Enable/disable splitting the WikiPageNames with space characters + (''since 0.10'').""") + + def __init__(self): + self._index = None + self._last_index_update = 0 + self._index_lock = threading.RLock() + self._compiled_rules = None + self._link_resolvers = None + self._helper_patterns = None + self._external_handlers = None + + def _update_index(self): + self._index_lock.acquire() + try: + now = time.time() + if now > self._last_index_update + WikiSystem.INDEX_UPDATE_INTERVAL: + self.log.debug('Updating wiki page index') + db = self.env.get_db_cnx() + cursor = db.cursor() + cursor.execute("SELECT DISTINCT name FROM wiki") + self._index = {} + for (name,) in cursor: + self._index[name] = True + self._last_index_update = now + finally: + self._index_lock.release() + + # Public API + + def get_pages(self, prefix=None): + """Iterate over the names of existing Wiki pages. + + If the `prefix` parameter is given, only names that start with that + prefix are included. + """ + self._update_index() + for page in self._index.keys(): + if not prefix or page.startswith(prefix): + yield page + + def has_page(self, pagename): + """Whether a page with the specified name exists.""" + self._update_index() + return self._index.has_key(pagename.rstrip('/')) + + def _get_rules(self): + self._prepare_rules() + return self._compiled_rules + rules = property(_get_rules) + + def _get_helper_patterns(self): + self._prepare_rules() + return self._helper_patterns + helper_patterns = property(_get_helper_patterns) + + def _get_external_handlers(self): + self._prepare_rules() + return self._external_handlers + external_handlers = property(_get_external_handlers) + + def _prepare_rules(self): + from trac.wiki.formatter import Formatter + if not self._compiled_rules: + helpers = [] + handlers = {} + syntax = Formatter._pre_rules[:] + i = 0 + for resolver in self.syntax_providers: + for regexp, handler in resolver.get_wiki_syntax(): + handlers['i' + str(i)] = handler + syntax.append('(?P<i%d>%s)' % (i, regexp)) + i += 1 + syntax += Formatter._post_rules[:] + helper_re = re.compile(r'\?P<([a-z\d_]+)>') + for rule in syntax: + helpers += helper_re.findall(rule)[1:] + rules = re.compile('(?:' + '|'.join(syntax) + ')') + self._external_handlers = handlers + self._helper_patterns = helpers + self._compiled_rules = rules + + def _get_link_resolvers(self): + if not self._link_resolvers: + resolvers = {} + for resolver in self.syntax_providers: + for namespace, handler in resolver.get_link_resolvers(): + resolvers[namespace] = handler + self._link_resolvers = resolvers + return self._link_resolvers + link_resolvers = property(_get_link_resolvers) + + # IWikiChangeListener methods + + def wiki_page_added(self, page): + if not self.has_page(page.name): + self.log.debug('Adding page %s to index' % page.name) + self._index[page.name] = True + + def wiki_page_changed(self, page, version, t, comment, author, ipnr): + pass + + def wiki_page_deleted(self, page): + if self.has_page(page.name): + self.log.debug('Removing page %s from index' % page.name) + del self._index[page.name] + + def wiki_page_version_deleted(self, page): + pass + + # IWikiSyntaxProvider methods + + def format_page_name(self, page): + if self.split_page_names: + return re.sub(r"([a-z])([A-Z][a-z])", r"\1 \2", page) + return page + + def get_wiki_syntax(self): + def wikipagenames_link(formatter, match, fullmatch): + return self._format_link(formatter, 'wiki', match, + self.format_page_name(match), + self.ignore_missing_pages) + + yield (r"!?(?<!/)\b" # start at a word boundary but not after '/' + r"[A-Z][a-z]+(?:[A-Z][a-z]*[a-z/])+" # wiki words + r"(?:#[A-Za-z0-9]+)?" # optional fragment identifier + r"(?=:?\Z|:?\s|[.,;!?\)}\]])", # what should follow it + wikipagenames_link) + + def get_link_resolvers(self): + def link_resolver(formatter, ns, target, label): + return self._format_link(formatter, ns, target, label, False) + yield ('wiki', link_resolver) + + def _format_link(self, formatter, ns, page, label, ignore_missing): + page, query, fragment = formatter.split_link(page) + href = formatter.href.wiki(page) + fragment + if not self.has_page(page): + if ignore_missing: + return label + return html.A(label+'?', href=href, class_='missing wiki', + rel='nofollow') + else: + return html.A(label, href=href, class_='wiki')