Commits

David Chambers committed bad5458

Added `post_urls`, a tremendously useful function which returns a post's short and canonical URLs. Simplified the code responsible for mapping posts to URLs.

  • Participants
  • Parent commits 3782e9e

Comments (0)

Files changed (7)

 
 def atom(request):
 	content_type = 'application/atom+xml; charset=utf-8'
-	cache_key = '/'.join([BASE_URL.rstrip('/'), request.META['PATH_INFO']])
+	cache_key = feed_url = BASE_URL.rstrip('/') + request.META['PATH_INFO']
 	xml = cache.get(cache_key)
 	if xml:
 		return HttpResponse(xml, content_type=content_type)
 		'author_name':  lambda: unicode(PRIMARY_AUTHOR_NAME, 'utf-8'),
 		'author_email': lambda: unicode(PRIMARY_AUTHOR_EMAIL, 'utf-8'),
 		'author_link':  lambda: unicode(PRIMARY_AUTHOR_URL, 'utf-8'),
-		'feed_url':     lambda: u'%sfeed/' % BASE_URL,
+		'feed_url':     lambda: feed_url,
 		'feed_guid':    lambda: unicode(BASE_URL, 'utf-8'),
 	}
 
 			if todo or all:
 				required = {
 					'title':       lambda: post['meta']['title'],
-					'link':        lambda: u'%s%s/' % (BASE_URL, post['meta']['slug']),
+					'link':        lambda: post['meta']['url'],
 					'description': lambda: post['html'],
 				}
 				optional = {
 					'author_name': lambda: post['meta']['author'],
 					'pubdate':     lambda: post['meta']['datetime'],
-					'unique_id':   lambda: u'%s%s/' % (BASE_URL, post['meta']['slug']),
+					'unique_id':   lambda: post['meta']['url'],
 				}
 				kwargs = all_kwargs(required, optional, post)
 				if kwargs:

templates/archives.dhtml

 						<h2>{{ month|month }} {{ year }}</h2>
 						<ol>
 					{% for post in these_posts %}
-							<li><a href="{{ post.meta.path }}{{ post.meta.slug }}/"><time datetime="{{ post.meta.datetime|datetime }}">{{ post.meta.datetime|display_date }}</time> {{ post.meta.title }}</a></li>
+							<li><a href="{{ post.meta.url }}"><time datetime="{{ post.meta.datetime|datetime }}">{{ post.meta.datetime|display_date }}</time> {{ post.meta.title }}</a></li>
 					{% endfor %}
 						</ol>
 					</li>

templates/index.dhtml

 	{% for post in posts %}{% if forloop.counter <= 5 %}
 			<article>
 				<header>
-					<h2><a href="{{ post.meta.path }}{{ post.meta.slug }}/">{{ post.meta.title|safe }}</a></h2>
+					<h2><a href="{{ post.meta.url }}">{{ post.meta.title|safe }}</a></h2>
 					<time datetime="{{ post.meta.datetime|datetime }}">{{ post.meta.datetime|display_date }}</time>
 				</header>
 				{{ post.excerpt|safe }}

templates/post.dhtml

 							<dt>Short URL</dt>
 							<dd><a href="{{ short_url }}">{{ short_url }}</a></dd>
 							<dt>Markdown</dt>
-							<dd><a href="{{ markdown_url }}">{{ markdown_url }}</a></dd>
+							<dd><a href="{{ short_url }}m/">{{ short_url }}m/</a></dd>
 						</dl>
 					{% endifequal %}
 					</header>
 	(r'^archive/$', lambda request: HttpResponsePermanentRedirect('/archives/')),
 	(r'^archives/$', archives),
 	(r'^(.+?)/redirect/$', redirect),
-	url(r'^(.+?)/comment/$', post, name='post_comment'),
-	url(r'^(.+?)/m/$', post, name='post_markdown', kwargs={'view_source': True}),
-	url(r'^(.+?)/$', post, name='post')
+	(r'^(.+?)/comment/$', post),
+	(r'^(.+?)/(m/)?$', post),
 )
 import markdown
 import pytz
 
-from django.conf import settings
+from django.conf import settings as project_settings
 from django.core.cache import cache
 from django.template import Context, loader
+from mango import settings
 from mango.settings import *
 
 block = [r'(?m)^(', r'(?=[ \n\r])[^\n\r]*(\r?\n|$))+']
 	# {{ filesize }} following internal links
 	'ref-style':     re.compile(r'(\[(?P<id>[^\]]+)\]\s*){{\s*filesize\s*}}'),
 	'inline':        re.compile(r'(\[[^\]]+\]\(/(?P<path>\S+?)\)\s*){{\s*filesize\s*}}'),
+
+	'alias=>canon':  re.compile(r'^(0*(?P<alias>.*?)=>)?(?P<canon>.+)$'),
 }
 
 def id_to_path(identifier, text):
 					meta[key] = value[0]
 
 	if meta.has_key('date') and meta.has_key('time'):
-		tz = pytz.timezone(settings.TIME_ZONE)
+		tz = pytz.timezone(project_settings.TIME_ZONE)
 		dt_format = ' '.join([MARKDOWN_DATE_FORMAT, MARKDOWN_TIME_FORMAT])
 		try:
 			meta['datetime'] = tz.localize(datetime.datetime.strptime(' '.join([meta['date'], meta['time']]), dt_format)).astimezone(pytz.utc)
 	f.close()
 	return u
 
-def split_filename(filename):
+def post_urls(filepath):
 	"""
-	Infers an alias and slug from a given filename
+	Returns a post's short and canonical URLs
 	
-	>>> split_filename('01=>my-first-post.text')
-	('1', 'my-first-post')
-	>>> split_filename('my-first-post.text')
-	('', 'my-first-post')
+	>>> path_to_posts = os.path.join(os.sep, *PATH_TO_POSTS.split('/'))
+	>>> setattr(settings, 'SHORT_URL_BASE', 'http://✪df.ws/')
+	>>> post_urls(os.path.join(path_to_posts, '01=>my-first-post.text'))
+	(u'http://\u272adf.ws/1/', u'/my-first-post/')
+	>>> setattr(settings, 'SHORT_URL_BASE', '')
+	>>> post_urls(os.path.join(path_to_posts, '01=>my-first-post.text'))
+	(u'/1/', u'/my-first-post/')
+	>>> post_urls(os.path.join(path_to_posts,
+	...         'js=>javascript', 'libs=>libraries', 'prototype.js', '$.text'))
+	(u'/js/libs/prototype.js/$/', u'/javascript/libraries/prototype.js/$/')
 	"""
-	filename = os.path.splitext(filename)[0] # strip extension
-	alias = ''
-	slug = filename
-	match = re.match(r'^0*(?P<alias>.*?)=>(?P<slug>.*)$', filename)
+	canon_fragments = ['', '']
+	alias_fragments = ['', '']
+
+	head, tail = os.path.split(filepath)
+	match = re.match(RE['alias=>canon'], tail)
 	if match:
-		alias = match.group('alias')
-		slug = match.group('slug')
-	return (alias, slug)
+		canon = os.path.splitext(match.group('canon'))[0] # strip extension
+		canon_fragments.insert(1, canon)
+		alias_fragments.insert(1, match.group('alias') or canon)
 
-def url_path(dirpath):
-	"""
-	Changes the directory path given to the url path
-	
-	>>> url_path(PATH_TO_POSTS + '/test/location')
-	u'/test/location/'
-	"""
-	path_to_posts = os.path.abspath(PATH_TO_POSTS)
-	return unicode(os.path.abspath(dirpath).replace(path_to_posts, '', 1) + '/')
+	while tail:
+		head, tail = os.path.split(head)
+		match = re.match(RE['alias=>canon'], tail)
+		if match:
+			canon = match.group('canon')
+			canon_fragments.insert(1, canon)
+			alias_fragments.insert(1, match.group('alias') or canon)
+
+	short_url_base = settings.SHORT_URL_BASE.decode('utf-8') or '/'
+	short_url = u'/'.join(alias_fragments).replace(PATH_TO_POSTS, short_url_base, 1)
+	canon_url = u'/'.join(canon_fragments).replace(PATH_TO_POSTS, '/', 1)
+
+	return (short_url, canon_url)
 
 def posts(path_to_posts=PATH_TO_POSTS):
 	"""
 				this = cache.get(absolute_path)
 				if not this:
 					this = parse_markdown(get_contents(absolute_path))
-					this['meta']['path'] = url_path(dirpath)
-					this['meta']['slug'] = split_filename(filename)[1]
+					this['meta']['url'] = post_urls(absolute_path)[1]
 					cache.set(absolute_path, this, CACHE_SECONDS)
 				posts.append(this)
 
 from mango.forms import CommentForm, ContactForm
 from mango.settings import *
 from mango.templatetags.mango_extras import slugify
+from mango.utils import RE
 
 def redirect(request, path):
 	return HttpResponseRedirect('/%s/' % path)
 
 #TODO - this name should be changed as it now is also an index page, should this be combined with the index post?
 def post(request, path, view_source=False):
-	def render_post(filename, canon_url, short_url):
-		short_url = (SHORT_URL_BASE or BASE_URL).rstrip('/') + short_url
-		context = utils.parse_markdown(utils.get_contents(filename))
-		context['short_url'] = short_url
-		context['markdown_url'] = short_url + 'm/'
-
+	def render_post(filepath):
+		context = utils.parse_markdown(utils.get_contents(filepath))
 		form = CommentForm()
 		thread = None
 		num_total_posts = 0
 				except AttributeError:
 					pass 
 
-		context['thread'] = thread
-		context['num_visible_posts'] = num_visible_posts
-		context['type'] = 'post' if 'datetime' in context['meta'] else 'page'
-		context['form'] = form
+		return render_to_string('post.dhtml', dict(context, **dict({
+					'form': form,
+					'num_visible_posts': num_visible_posts,
+					'short_url': short_url,
+					'thread': thread,
+					'type': 'post' if 'datetime' in context['meta'] else 'page',
+				}, **context_defaults(request))))
 
-		return render_to_string('post.dhtml', dict(context, **context_defaults(request)))
+	filepath = os.path.join(os.sep, *PATH_TO_POSTS.split('/'))
+	is_short = False
+	fragments = path.split('/')
+	last_index = len(fragments) - 1
+	for index, fragment in enumerate(fragments):
+		found = False
+		for entry in os.listdir(filepath):
+			name = entry
+			if index == last_index:
+				name, ext = os.path.splitext(entry)
+			match = re.match(RE['alias=>canon'], name)
+			if fragment in match.groupdict().values():
+				found = True
+				if fragment == match.group('alias'):
+					is_short = True
+				filepath = os.path.join(filepath, entry)
+				break
+		if not found:
+			raise Http404
 
-	filepath = PATH_TO_POSTS
-	is_short = False
-	short_url = '' # the short URL, generated from the folder and file names, using aliases if provided
-	canon_url = '' # the canonical URL
-	for p in path.split('/'):
-		match = False
-		# filename here can either be a file or a directory
-		for filename in os.listdir(filepath):
-			alias, slug = utils.split_filename(filename)
-			if p in (alias, slug):
-				match = True
-				if p == alias:
-					is_short = True
-				filepath = os.path.join(filepath, filename)
-				canon_url = os.path.join(canon_url, slug)
-				short_url = os.path.join(short_url, alias or slug)
-				break
-		if not match:
-			raise Http404
+	short_url, canon_url = utils.post_urls(filepath)
 
 	if is_short:
 		if view_source:
-			return HttpResponseRedirect(reverse('post_markdown', args=[canon_url]))
-		else:
-			return HttpResponseRedirect(reverse('post', args=[canon_url]))
+			canon_url += 'm/'
+		return HttpResponseRedirect(canon_url)
 
 	if os.path.isdir(filepath):
-		#TODO - if view source is true what do we do here?
+		#TODO - if view_source is true what do we do here?
 		#TODO - caching
 		html = render_to_response('index.dhtml', dict({
 			'posts': [post for year, month, posts in utils.posts(filepath) for post in posts],
 		return HttpResponse(text, content_type='text/plain; charset=utf-8')
 
 	if os.path.islink(filepath): # symbolic link
-		dirpath, filename = os.path.split(os.path.realpath(filepath))
-		return HttpResponseRedirect(u'%s%s/' % (utils.url_path(dirpath), utils.split_filename(filename)[1]))
+		short_url, canon_url = utils.post_urls(os.path.realpath(filepath))
+		return HttpResponseRedirect(canon_url)
 
 	# Parse the Markdown and return it through the Django template.
-	value = render_post(filepath, reverse('post', args=[canon_url]), reverse('post', args=[short_url]))
+	value = render_post(filepath)
 	if isinstance(value, HttpResponseRedirect):
 		return value