Mercurial > genshi > mirror
view 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 source
# -*- 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')