c4e554f545cb

merge
[view raw] [browse files]
author Chris Eldredge <chris.eldredge@gmail.com>
date Mon, 13 Aug 2012 18:43:07 +0100
parents 737c9dda302a (diff) f605dfaddcc9 (current diff)
children 133cd05dafd1
branches/tags (none)
files __init__.py

Changes

--- 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.
--- 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')
--- 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