# HG changeset patch # User Chris Eldredge # Date 1344879787 -3600 # Node ID c4e554f545cb62a753cf0653ea65c6bc81800244 # Parent 737c9dda302a667383cf0dec0e22a917878fd3c9# Parent f605dfaddcc9d9a9487de21ab27e8e889894f72e merge diff -r f605dfaddcc9 -r c4e554f545cb ReadMe.markdown --- a/ReadMe.markdown Fri Aug 10 22:42:27 2012 +0100 +++ b/ReadMe.markdown Mon Aug 13 18:43:07 2012 +0100 @@ -2,12 +2,14 @@ ============================ Similar to GitHub, this module looks for a file named ReadMe.md or -ReadMe.markdown (case insensitive) in the "default" revision. +ReadMe.markdown (case insensitive). A custom theme is provided that displays the formatted contents on the summary (index) view. -In addition to showing formatted information on the summary view, this module enables any -other markdown content to be rendered using the url pattern `/repo/markdown/changeid/WikiPage.md`. +In addition to showing formatted information on the summary view, this module replaces +the default `file` view for `.md` or `.markdown` files, enabling you to use the +[WikiLinks](http://packages.python.org/Markdown/extensions/wikilinks.html) extension +to support documentation split across several pages. ##Install## @@ -20,6 +22,7 @@ [web] templates = /example/hgext.markdown style = markdown + markdown.changeid = tip #optional; 'tip' is default value. [extensions] hgext.markdown=/example/hgext.markdown @@ -33,6 +36,10 @@ ##Preview## -You can preview changes before committing them by browsing to `/repo/markdown/_preview/ReadMe.markdown`. -The `_preview` keyword makes this extension look for content in the working copy. +You can preview changes in your working copy before committing them by browsing to e.g. `http://localhost:8000/preview/ReadMe.markdown`. + +##Security## +This extension enables users with commit/push access to hgweb to create arbitrary html content that may be browsed by other users. +At this time no attempt has been made to detect or prevent attacks such as cross-site scripting (xss) and other types of attacks. +In general this extension should only be used if you trust all users with push access. diff -r f605dfaddcc9 -r c4e554f545cb __init__.py --- a/__init__.py Fri Aug 10 22:42:27 2012 +0100 +++ b/__init__.py Mon Aug 13 18:43:07 2012 +0100 @@ -1,97 +1,159 @@ -import os -import sys - -# If using TortoiseHg, obtain Python-Markdown and tell Python where to find it: -#sys.path.append("c:/python27/Lib/site-packages/markdown-2.2.0-py2.7.egg") - -import markdown -from mercurial.hgweb import webcommands, webutil, common -from mercurial import extensions, encoding, util -import logging -logging.basicConfig() - -def filerevision_markdown(web, req, tmpl): - path = webutil.cleanpath(web.repo, req.form.get('file', [''])[0]) - parts = os.path.splitext(path) - - if not parts[1] == '.markdown' and not parts[1] == '.md': - return webcommands.file(web, req, tmpl) - - if not path: - return webcommands.file(web, req, tmpl) - - previewMode = 'node' in req.form and req.form['node'][0] == '_preview' - - if previewMode: - text = file(web.repo.root + "/" + path).read() - else: - fctx = webutil.filectx(web.repo, req) - text = fctx.data() - - if util.binary(text): - # todo: handle preview mode - return rawfile(web, req, tmpl) - - md = markdown.Markdown(extensions=['wikilinks(base_url={0},end_url={1})'.format('', parts[1])]) - html = md.convert(text) - - args = {'file':path, - 'readmefilename':parts[0].split('/')[-1], - 'path':webutil.up(path), - 'readme':html} - - if previewMode: - args.update({'rev':'PREVIEW', 'node':'PREVIEW'}) - else: - args.update({'rev':fctx.rev(), - 'node':fctx.hex(), - 'author':fctx.user(), - 'date':fctx.date(), - 'desc':fctx.description(), - 'branch':webutil.nodebranchnodefault(fctx), - 'parent':webutil.parents(fctx), - 'child':webutil.children(fctx)}) - - return tmpl("markdown", **args) - -def summary_markdown(orig, web, req, tmpl): - changeid = 'default' # todo: add hgrc config setting - previewMode = False - text = None - - cctx = web.repo[changeid] - changeid = cctx.hex()[0:12] - for filename in cctx: - if filename.lower() == 'readme.md' or filename.lower() == 'readme.markdown': - fctx = cctx.filectx(filename) - text = fctx.data() - readmefile = filename - break - - if text: - ext = os.path.splitext(readmefile)[1] - base_url = tmpl.defaults['url'] + 'markdown/' + changeid + "/" - md = markdown.Markdown( - extensions=['urlrebase', 'wikilinks'], - extension_configs={ - 'urlrebase' : [('base_url', base_url)], - 'wikilinks' : [('base_url', base_url), ('end_url', ext)]}) - readme = md.convert(text) - else: - readmefile = "ReadMe" - readme = "Add ReadMe.md or ReadMe.markdown to this repository to display it here." - - tmpl.defaults['readmefilename'] = readmefile - tmpl.defaults['readme'] = readme - - return orig(web, req, tmpl) - -def find_working_copy_readme(repo): - for filename in os.listdir(repo.root): - if filename.lower() == 'readme.md' or filename.lower() == 'readme.markdown': - return filename - -def extsetup(): - extensions.wrapfunction(webcommands, 'summary', summary_markdown) - webcommands.markdown = filerevision_markdown - webcommands.__all__.append('markdown') +import os +import sys + +# If using TortoiseHg, obtain Python-Markdown and tell Python where to find it: +#sys.path.append("c:/python27/Lib/site-packages/markdown-2.2.0-py2.7.egg") + +import logging, markdown, mimetypes +from mercurial import extensions, encoding, util +from mercurial.hgweb import webcommands, webutil, common +from mercurial.hgweb.common import ErrorResponse, HTTP_OK, HTTP_FORBIDDEN, HTTP_NOT_FOUND +import mdx_urlrebase + +logging.basicConfig() + +def preview_markdown(web, req, tmpl): + f = req.form.get('node', [''])[0] + path = req.form.get('file', [''])[0] + if path: + f = f + '/' + path + + if not f: + f = find_working_copy_readme(web.repo.root) + + parts = os.path.splitext(f) + + try: + text = file(web.repo.root + "/" + f, "rb").read() + except IOError: + raise ErrorResponse(HTTP_NOT_FOUND, 'path not found: ' + f) + + if not parts[1] == '.markdown' and not parts[1] == '.md': + return preview_sendraw(web, req, f, text) + + md = markdown.Markdown(extensions=['wikilinks(base_url={0},end_url={1})'.format('', parts[1])]) + html = md.convert(text) + + args = {'file':f, + 'readmefilename':parts[0].split('/')[-1], + 'path':webutil.up(f), + 'readme':html, + 'rev':'PREVIEW', + 'node':'PREVIEW'} + + return tmpl("markdown", **args) + +def preview_sendraw(web, req, path, data): + guessmime = web.configbool('web', 'guessmime', False) + + if util.binary(data): + mt = 'application/binary' + else: + mt = 'text/plain' + + if guessmime: + mt = mimetypes.guess_type(path)[0] + if mt is None: + mt = binary(text) and 'application/binary' or 'text/plain' + if mt.startswith('text/'): + mt += '; charset="%s"' % encoding.encoding + + req.respond(HTTP_OK, mt, path, len(data)) + return [data] + +def file_markdown(orig, web, req, tmpl): + f = req.form.get('file', [''])[0] + parts = os.path.splitext(f) + + try: + fctx = webutil.filectx(web.repo, req) + text = fctx.data() + except LookupError, inst: + try: + return webcommands.manifest(web, req, tmpl) + except ErrorResponse: + raise inst + + if util.binary(text): + return webcommands.rawfile(web, req, tmpl) + + if not parts[1] == '.markdown' and not parts[1] == '.md': + return orig(web, req, tmpl) + + md = markdown.Markdown(extensions=['wikilinks(base_url={0},end_url={1})'.format('', parts[1])]) + html = md.convert(text) + + args = {'file':f, + 'readmefilename':parts[0].split('/')[-1], + 'path':webutil.up(f), + 'readme':html} + + args.update({'rev':fctx.rev(), + 'node':fctx.hex(), + 'author':fctx.user(), + 'date':fctx.date(), + 'desc':fctx.description(), + 'branch':webutil.nodebranchnodefault(fctx), + 'parent':webutil.parents(fctx), + 'child':webutil.children(fctx)}) + + return tmpl("markdown", **args) + +def summary_markdown(orig, web, req, tmpl): + """ + Decorates the default summary view by adding 'readme' and 'readmefile' content + to the template. + """ + + changeid = web.config('web', 'markdown.changeid', 'tip') + previewMode = False + text = None + + cctx = web.repo[changeid] + changeid = cctx.hex()[0:12] + for filename in cctx: + if filename.lower() == 'readme.md' or filename.lower() == 'readme.markdown': + fctx = cctx.filectx(filename) + text = fctx.data() + readmefile = filename + break + + if text: + ext = os.path.splitext(readmefile)[1] + base_url = tmpl.defaults['url'] + 'file/' + changeid + "/" + base_raw_url = tmpl.defaults['url'] + 'rawfile/' + changeid + "/" + + def rebase(proc, e, attr): + uri = e.get(attr, '') + if '://' in uri or uri.startswith('/'): + return + base = base_url + if attr == 'src': + base = base_raw_url + e.set(attr, proc.rebase(base, uri)) + + ext = mdx_urlrebase.UrlRebaseExtension(configs=[('rebase', rebase)]) + md = markdown.Markdown( + extensions=[ext, 'wikilinks'], + extension_configs={ + 'wikilinks' : [('base_url', base_url), ('end_url', ext)]}) + readme = md.convert(text) + else: + readmefile = "ReadMe" + readme = "Add ReadMe.md or ReadMe.markdown to this repository to display it here." + + tmpl.defaults['readmefilename'] = readmefile + tmpl.defaults['readme'] = readme + + return orig(web, req, tmpl) + +def find_working_copy_readme(dir): + for filename in os.listdir(dir): + if filename.lower() == 'readme.md' or filename.lower() == 'readme.markdown': + return filename + +def extsetup(): + extensions.wrapfunction(webcommands, 'file', file_markdown) + extensions.wrapfunction(webcommands, 'summary', summary_markdown) + webcommands.preview = preview_markdown + webcommands.__all__.append('preview') diff -r f605dfaddcc9 -r c4e554f545cb mdx_urlrebase.py --- a/mdx_urlrebase.py Fri Aug 10 22:42:27 2012 +0100 +++ b/mdx_urlrebase.py Mon Aug 13 18:43:07 2012 +0100 @@ -4,6 +4,7 @@ def __init__ (self, configs): # set extension defaults self.config = { + 'rebase' : [None, 'Callable to rebase elementes with custom logic.'], 'base_url' : ['/', 'String to append to beginning or URL.'], } @@ -21,15 +22,23 @@ self.config = config def run(self, root): + rebase = self.rebase_element + if "rebase" in self.config: + rebase = self.config["rebase"] + for a in root.findall(".//a"): - uri = a.get('href', '') - if '://' in uri or uri.startswith('/'): - continue - a.set('href', self.rebase(uri)) + rebase(self, a, 'href') + for i in root.findall(".//img"): + rebase(self, i, 'src') return root - def rebase(self, uri): - base = self.config['base_url'] + def rebase_element(self, also_self, e, attr): + uri = e.get(attr, '') + if '://' in uri or uri.startswith('/'): + return + e.set(attr, self.rebase(uri)) + + def rebase(self, base, uri): if base[0] and not base.endswith('/'): base = base + '/' return base + uri