Mercurial > wikked
changeset 44:db0daa85cd6e
Moved page formatting code to its own module.
author | Ludovic Chabant <ludovic@chabant.com> |
---|---|
date | Sat, 12 Jan 2013 12:06:56 -0800 |
parents | 0c2e7c26f93c |
children | f63a2062fb99 |
files | wikked/formatter.py wikked/wiki.py |
diffstat | 2 files changed, 160 insertions(+), 152 deletions(-) [+] |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/wikked/formatter.py Sat Jan 12 12:06:56 2013 -0800 @@ -0,0 +1,158 @@ +import os +import os.path +import re + + +class FormatterNotFound(Exception): + pass + + +class PageFormattingContext(object): + def __init__(self, url, ext, slugify=None): + self.url = url + self.ext = ext + self.slugify = slugify + self.out_links = [] + self.included_pages = [] + self.meta = {} + + @property + def urldir(self): + return os.path.dirname(self.url) + + def getAbsoluteUrl(self, url): + if url.startswith('/'): + # Absolute page URL. + abs_url = url[1:] + else: + # Relative page URL. Let's normalize all `..` in it, + # which could also replace forward slashes by backslashes + # on Windows, so we need to convert that back. + raw_abs_url = os.path.join(self.urldir, url) + abs_url = os.path.normpath(raw_abs_url).replace('\\', '/') + if self.slugify is not None: + abs_url = self.slugify(url) + return abs_url + + +class PageFormatter(object): + def __init__(self, wiki): + self.wiki = wiki + + def formatText(self, ctx, text): + text = self._preProcessWikiSyntax(ctx, text) + formatter = self._getFormatter(ctx.ext) + text = formatter(text) + text = self._postProcessWikiSyntax(ctx, text) + return formatter(text) + + def _getFormatter(self, extension): + formatter = None + for k, v in self.wiki.formatters.iteritems(): + if extension in v: + return k + raise FormatterNotFound("No formatter mapped to file extension: " + extension) + + def _preProcessWikiSyntax(self, ctx, text): + text = self._processWikiMeta(ctx, text) + text = self._processWikiLinks(ctx, text) + return text + + def _postProcessWikiSyntax(self, ctx, text): + return text + + def _processWikiMeta(self, ctx, text): + def repl(m): + meta_name = str(m.group(1)) + meta_value = str(m.group(3)) + if meta_value is not None and len(meta_value) > 0: + if meta_name not in ctx.meta: + ctx.meta[meta_name] = meta_value + elif ctx.meta[meta_name] is list: + ctx.meta[meta_name].append(meta_value) + else: + ctx.meta[meta_name] = [ ctx.meta[meta_name], meta_value ] + else: + ctx.meta[meta_name] = True + if meta_name == 'include': + return self._processInclude(ctx, meta_value) + elif meta_name == 'query': + return self._processQuery(ctx, meta_value) + return '' + + text = re.sub(r'^\[\[((__|\+)?[a-zA-Z][a-zA-Z0-9_\-]+):\s*(.*)\]\]\s*$', repl, text, flags=re.MULTILINE) + return text + + def _processWikiLinks(self, ctx, text): + s = self + + # [[display name|Whatever/PageName]] + def repl1(m): + return s._formatWikiLink(ctx, m.group(1), m.group(2)) + text = re.sub(r'\[\[([^\|\]]+)\|([^\]]+)\]\]', repl1, text) + + # [[Namespace/PageName]] + def repl2(m): + a, b = m.group(1, 2) + url = b if a is None else (a + b) + return s._formatWikiLink(ctx, b, url) + text = re.sub(r'\[\[([^\]]+/)?([^\]]+)\]\]', repl2, text) + + return text + + def _processInclude(self, ctx, value): + # TODO: handle self-includes or cyclic includes. + abs_included_url = ctx.getAbsoluteUrl(value) + included_page = self.wiki.getPage(abs_included_url) + ctx.included_pages.append(abs_included_url) + return included_page.formatted_text + + def _processQuery(self, ctx, query): + parameters = { + 'header': "<ul>", + 'footer': "</ul>", + 'item': "<li><a class=\"wiki-link\" data-wiki-url=\"{{url}}\">{{title}}</a></li>", + 'empty': "<p>No page matches the query.</p>" + } + meta_query = {} + arg_pattern = r"(^|\|)(?P<name>[a-zA-Z][a-zA-Z0-9_\-]+)=(?P<value>[^\|]+)" + for m in re.findall(arg_pattern, query): + if m[1] not in parameters: + meta_query[m[1]] = m[2] + else: + parameters[m[1]] = m[2] + + matched_pages = [] + for p in self.wiki.getPages(): + if p.url == ctx.url: + continue + for key, value in meta_query.iteritems(): + actual = p.getUserMeta(key) + if (type(actual) is list and value in actual) or (actual == value): + matched_pages.append(p) + if len(matched_pages) == 0: + return parameters['empty'] + + text = parameters['header'] + for p in matched_pages: + item_str = parameters['item'] + tokens = { + 'url': p.url, + 'title': p.title + } + for tk, tv in tokens.iteritems(): + item_str = item_str.replace('{{%s}}' % tk, tv) + text += item_str + text += parameters['footer'] + + return text + + def _formatWikiLink(self, ctx, display, url): + abs_url = ctx.getAbsoluteUrl(url) + ctx.out_links.append(abs_url) + + css_class = 'wiki-link' + if not self.wiki.pageExists(abs_url): + css_class += ' missing' + return '<a class="%s" data-wiki-url="%s">%s</a>' % (css_class, abs_url, display) +
--- a/wikked/wiki.py Sat Jan 12 11:54:06 2013 -0800 +++ b/wikked/wiki.py Sat Jan 12 12:06:56 2013 -0800 @@ -13,151 +13,13 @@ from scm import MercurialSourceControl from indexer import WhooshWikiIndex from auth import UserManager +from formatter import PageFormatter, PageFormattingContext class InitializationError(Exception): pass -class FormatterNotFound(Exception): - pass - - -class PageFormattingContext(object): - def __init__(self, url, ext): - self.url = url - self.ext = ext - self.out_links = [] - self.included_pages = [] - self.meta = {} - - @property - def urldir(self): - return os.path.dirname(self.url) - - -class PageFormatter(object): - def __init__(self, wiki): - self.wiki = wiki - - def formatText(self, ctx, text): - text = self._preProcessWikiSyntax(ctx, text) - formatter = self._getFormatter(ctx.ext) - text = formatter(text) - text = self._postProcessWikiSyntax(ctx, text) - return formatter(text) - - def _getFormatter(self, extension): - formatter = None - for k, v in self.wiki.formatters.iteritems(): - if extension in v: - return k - raise FormatterNotFound("No formatter mapped to file extension: " + extension) - - def _preProcessWikiSyntax(self, ctx, text): - text = self._processWikiMeta(ctx, text) - text = self._processWikiLinks(ctx, text) - return text - - def _postProcessWikiSyntax(self, ctx, text): - return text - - def _processWikiMeta(self, ctx, text): - def repl(m): - meta_name = str(m.group(1)) - meta_value = str(m.group(3)) - if meta_value is not None and len(meta_value) > 0: - if meta_name not in ctx.meta: - ctx.meta[meta_name] = meta_value - elif ctx.meta[meta_name] is list: - ctx.meta[meta_name].append(meta_value) - else: - ctx.meta[meta_name] = [ ctx.meta[meta_name], meta_value ] - else: - ctx.meta[meta_name] = True - if meta_name == 'include': - return self._processInclude(ctx, meta_value) - elif meta_name == 'query': - return self._processQuery(ctx, meta_value) - return '' - - text = re.sub(r'^\[\[((__|\+)?[a-zA-Z][a-zA-Z0-9_\-]+):\s*(.*)\]\]\s*$', repl, text, flags=re.MULTILINE) - return text - - def _processWikiLinks(self, ctx, text): - s = self - - # [[display name|Whatever/PageName]] - def repl1(m): - return s._formatWikiLink(ctx, m.group(1), m.group(2)) - text = re.sub(r'\[\[([^\|\]]+)\|([^\]]+)\]\]', repl1, text) - - # [[Namespace/PageName]] - def repl2(m): - a, b = m.group(1, 2) - url = b if a is None else (a + b) - return s._formatWikiLink(ctx, b, url) - text = re.sub(r'\[\[([^\]]+/)?([^\]]+)\]\]', repl2, text) - - return text - - def _processInclude(self, ctx, value): - # TODO: handle self-includes or cyclic includes. - abs_included_url = Page.get_absolute_url(ctx.urldir, value) - abs_included_url = Page.title_to_url(abs_included_url) - included_page = self.wiki.getPage(abs_included_url) - ctx.included_pages.append(abs_included_url) - return included_page.formatted_text - - def _processQuery(self, ctx, query): - parameters = { - 'header': "<ul>", - 'footer': "</ul>", - 'item': "<li><a class=\"wiki-link\" data-wiki-url=\"{{url}}\">{{title}}</a></li>", - 'empty': "<p>No page matches the query.</p>" - } - meta_query = {} - arg_pattern = r"(^|\|)(?P<name>[a-zA-Z][a-zA-Z0-9_\-]+)=(?P<value>[^\|]+)" - for m in re.findall(arg_pattern, query): - if m[1] not in parameters: - meta_query[m[1]] = m[2] - else: - parameters[m[1]] = m[2] - - matched_pages = [] - for p in self.wiki.getPages(): - if p.url == ctx.url: - continue - for key, value in meta_query.iteritems(): - actual = p.getUserMeta(key) - if (type(actual) is list and value in actual) or (actual == value): - matched_pages.append(p) - if len(matched_pages) == 0: - return parameters['empty'] - - text = parameters['header'] - for p in matched_pages: - item_str = parameters['item'] - tokens = { - 'url': p.url, - 'title': p.title - } - for tk, tv in tokens.iteritems(): - item_str = item_str.replace('{{%s}}' % tk, tv) - text += item_str - text += parameters['footer'] - - return text - - def _formatWikiLink(self, ctx, display, url): - abs_url = Page.get_absolute_url(ctx.urldir, url) - slug = Page.title_to_url(abs_url) - ctx.out_links.append(slug) - - css_class = 'wiki-link' - if not self.wiki.pageExists(slug): - css_class += ' missing' - return '<a class="%s" data-wiki-url="%s">%s</a>' % (css_class, slug, display) class Page(object): @@ -271,7 +133,7 @@ ext = self._meta['ext'] if ext[0] == '.': ext = ext[1:] - ctx = PageFormattingContext(self.url, ext) + ctx = PageFormattingContext(self.url, ext, slugify=Page.title_to_url) f = PageFormatter(self.wiki) self._meta['formatted'] = f.formatText(ctx, self._meta['content']) self._meta['user'] = ctx.meta @@ -311,18 +173,6 @@ return m.group(0).upper() return re.sub(r'^.|\s\S', upperChar, url.lower().replace('-', ' ')) - @staticmethod - def get_absolute_url(base_url, url): - if url.startswith('/'): - # Absolute page URL. - return url[1:] - else: - # Relative page URL. Let's normalize all `..` in it, - # which could also replace forward slashes by backslashes - # on Windows, so we need to convert that back. - raw_abs_url = os.path.join(base_url, url) - return os.path.normpath(raw_abs_url).replace('\\', '/') - class Wiki(object): def __init__(self, root=None, logger=None):