constance

Scripts for generating (an earlier obsolete version of) my personal web site
git clone https://code.djc.id.au/git/constance/
commit 66362b13945937f5f8dad3bbf46ee3e98656154c
parent 4d4da972e4a8a39f16fab4e10a610a5fdd622a0d
Author: Dan Callaghan <djc@djc.id.au>
Date:   Sun,  8 Jun 2008 15:12:21 +1000

Atom feed for index

committer: Dan Callaghan <djc@djc.id.au>

--HG--
extra : convert_revision : 119bf327c663992ed73d0983791b167e2f58a756

Diffstat:
Mapp.py | 39++++++++++++++++++++++++++++-----------
Mblog.py | 20++++++++++++++++++++
Mexport_readinglog_wp.py | 6+++---
Mtemplates/_commonwrapper.xml | 4++--
Mtemplates/_entry.xml | 2+-
Mtemplates/multiple.xml | 6++----
Atemplates/multiple_atom.xml | 49+++++++++++++++++++++++++++++++++++++++++++++++++
7 files changed, 105 insertions(+), 21 deletions(-)
diff --git a/app.py b/app.py
@@ -12,6 +12,7 @@ import blog
 ENTRIES_DIR = os.path.join(os.path.dirname(__file__), u'entries')
 READINGLOG_FILE = os.path.join(os.path.dirname(__file__), u'reading_log')
 BASE_URL = ''
+ENTRIES_PER_PAGE = 20
 
 template_loader = TemplateLoader(
 		os.path.join(os.path.dirname(__file__), 'templates'), 
@@ -32,13 +33,25 @@ class BlogApplication(RegexApplication):
 		self.entries = blog.Entries(ENTRIES_DIR, READINGLOG_FILE)
 
 	def index(self):
-		rendered = template_loader.load('multiple.xml').generate(
-				title=None, 
-				all_categories=self.entries.categories(), 
-				entries=self.entries, 
-				offset=int(self.request.args.get('offset', 0))
-				).render('xhtml')
-		return HttpResponse(rendered, [('Content-Type', 'text/html')], 200)
+		offset = int(self.request.args.get('offset', 0))
+		sorted_entries = sorted(self.entries, key=lambda e: e.publication_date, reverse=True)[offset:offset + ENTRIES_PER_PAGE]
+		format = self.request.args.get('format', 'html')
+		if format == 'html':
+			rendered = template_loader.load('multiple.xml').generate(
+					title=None, 
+					all_categories=self.entries.categories(), 
+					sorted_entries=sorted_entries, 
+					offset=offset,
+					).render('xhtml')
+			return HttpResponse(rendered, [('Content-Type', 'text/html')], 200)
+		elif format == 'atom':
+			rendered = template_loader.load('multiple_atom.xml').generate(
+					sorted_entries=sorted_entries, 
+					feed_updated=sorted_entries[0].modified_date
+					).render('xml')
+			return HttpResponse(rendered, [('Content-Type', 'application/atom+xml')], 200)
+		else:
+			raise PageNotFound('Unknown format %r' % format)
 	
 	def post(self, id):
 		id = id.decode(self.charset) # shouldn't Colubrid do this?
@@ -57,12 +70,14 @@ class BlogApplication(RegexApplication):
 		categories = self.entries.by_category()
 		if category not in categories:
 			raise PageNotFound()
+		offset = int(self.request.args.get('offset', 0))
 		entries = categories[category]
+		sorted_entries = sorted(entries, key=lambda e: e.publication_date, reverse=True)[offset:offset + ENTRIES_PER_PAGE]
 		rendered = template_loader.load('multiple.xml').generate(
 				title=u'%s category' % category, 
 				all_categories=self.entries.categories(), 
-				entries=entries, 
-				offset=int(self.request.args.get('offset', 0))
+				sorted_entries=sorted_entries, 
+				offset=offset
 				).render('xhtml')
 		return HttpResponse(rendered, [('Content-Type', 'text/html')], 200)
 
@@ -71,12 +86,14 @@ class BlogApplication(RegexApplication):
 		by_tag = self.entries.by_tag()
 		if tag not in by_tag:
 			raise PageNotFound()
+		offset = int(self.request.args.get('offset', 0))
 		entries = by_tag[tag]
+		sorted_entries = sorted(entries, key=lambda e: e.publication_date, reverse=True)[offset:offset + ENTRIES_PER_PAGE]
 		rendered = template_loader.load('multiple.xml').generate(
 				title=u'“%s” tag' % tag, 
 				all_categories=self.entries.categories(), 
-				entries=entries, 
-				offset=int(self.request.args.get('offset', 0))
+				sorted_entries=sorted_entries, 
+				offset=offset
 				).render('xhtml')
 		return HttpResponse(rendered, [('Content-Type', 'text/html')], 200)
 
diff --git a/blog.py b/blog.py
@@ -21,6 +21,14 @@ def cleanup_metadata(meta):
 		cleaned[k] = v
 	return cleaned
 
+IDIFY_WHITESPACE_PATT = re.compile(r'(?u)\s+')
+IDIFY_ACCEPT_PATT = re.compile(r'(?u)\w|[-_]')
+def idify(s):
+	# http://www.w3.org/TR/REC-xml/#NT-Name
+	s = s.lower()
+	s = IDIFY_WHITESPACE_PATT.sub(u'-', s)
+	return u''.join(c for c in s if IDIFY_ACCEPT_PATT.match(c))
+
 
 class EntryNotFoundError(ValueError): pass
 
@@ -112,6 +120,7 @@ class Entry(object):
 
 		self.modified_date = datetime.fromtimestamp(os.path.getmtime(os.path.join(self.dir, 'content.txt')))
 		self.publication_date = self.metadata.get('publication-date', None) or self.modified_date
+		self._guid = self.metadata.get('guid', None)
 
 	def comments(self):
 		return Comments(self.comments_dir)
@@ -124,21 +133,29 @@ class Entry(object):
 		return os.path.isdir(self.comments_dir) and \
 				os.access(self.comments_dir, os.R_OK)
 
+	def guid(self):
+		return self._guid or u'http://www.djc.id.au%s/%s' % (BASE_URL, self.id)
+
 
 class ReadingLogEntry(object):
 
 	def __init__(self, yaml_dict):
 		self.title = yaml_dict['Title']
+		self.id = idify(self.title)
 		self.author = yaml_dict['Author']
 		self.publication_date = self.modified_date = self.date = yaml_dict['Date']
 		self.url = yaml_dict.get('URL', None)
 		self.rating = yaml_dict.get('Rating', None)
 		self.categories = frozenset([u'Reading'])
 		self.tags = frozenset()
+		self._guid = yaml_dict.get('GUID', None)
 
 	def has_comments(self):
 		return False
 
+	def guid(self):
+		return self._guid or u'http://www.djc.id.au%s/#post-%s' % (BASE_URL, self.id)
+
 
 class Comments(object):
 
@@ -184,3 +201,6 @@ class Comment(object):
 
 	def author_name(self):
 		return self.author or u'Anonymous'
+
+
+from app import BASE_URL # XXX
diff --git a/export_readinglog_wp.py b/export_readinglog_wp.py
@@ -8,10 +8,10 @@ def export(f):
 	cn = MySQLdb.connect(host='cruz', user='root', passwd='ELIDED', db='wordpress', use_unicode=True)
 
 	cur = cn.cursor()
-	cur.execute('SELECT id, post_title, post_date FROM wp_posts INNER JOIN wp_term_relationships ON wp_term_relationships.object_id = wp_posts.id WHERE post_status = %s AND term_taxonomy_id = %s ORDER BY post_date ASC', ('publish', 14))
+	cur.execute('SELECT id, post_title, post_date, guid FROM wp_posts INNER JOIN wp_term_relationships ON wp_term_relationships.object_id = wp_posts.id WHERE post_status = %s AND term_taxonomy_id = %s ORDER BY post_date ASC', ('publish', 14))
 	for row in cur.fetchall():
-		id, title, date = row
-		entry = {'Title': title, 'Date': date}
+		id, title, date, guid = row
+		entry = {'Title': title, 'Date': date, 'GUID': guid}
 		subcur = cn.cursor()
 		subcur.execute('SELECT meta_key, meta_value FROM wp_postmeta WHERE post_id = %s', (id,))
 		for key, value in subcur.fetchall():
diff --git a/templates/_commonwrapper.xml b/templates/_commonwrapper.xml
@@ -16,7 +16,7 @@ from app import BASE_URL
         <meta http-equiv="content-type" content="text/html; charset=utf-8" />
 		<meta name="generator" content="Dan’s blogging engine" />
         <link rel="stylesheet" type="text/css" href="${BASE_URL}/static/css/common.css" />
-		<link rel="alternate" type="application/atom+xml" title="Atom feed" href="${BASE_URL}/feed?format=atom" />
+		<link rel="alternate" type="application/atom+xml" title="Atom feed" href="?format=atom" />
 	</head>
 </py:match>
 
@@ -32,7 +32,7 @@ from app import BASE_URL
 				<li id="feeds">
 					<h2>Feeds</h2>
 					<ul id="feed">
-						<li><a href="${BASE_URL}/feed?format=atom" title="Atom feed for posts">Atom posts</a></li>
+						<li><a rel="alternate" type="application/atom+xml" href="${BASE_URL}/?format=atom" title="Atom feed for posts">Atom posts</a></li>
 					</ul>
 				</li>
 				<li class="categories">
diff --git a/templates/_entry.xml b/templates/_entry.xml
@@ -42,7 +42,7 @@ from viewutils import mini_markdown, tag_list, category_list
 </span>
 
 <div class="entry readinglog" py:if="'Reading' in entry.categories">
-	<h3 class="entrytitle">
+	<h3 class="entrytitle" id="post-${entry.id}">
 		<a py:strip="not entry.url" href="${entry.url}">${mini_markdown(entry.title)}</a>
 		<py:if test="entry.author">
 			by ${entry.author}
diff --git a/templates/multiple.xml b/templates/multiple.xml
@@ -13,10 +13,8 @@
 
 <h2 class="archives" py:if="title">Archive for the ${title}</h2>
 
-<py:with vars="sorted_entries = sorted(entries, key=lambda e: e.publication_date, reverse=True);
-		show_prev = bool(offset);
-		show_next = len(sorted_entries) > offset + 20">
-	<py:for each="entry in sorted_entries[offset:offset + 20]">
+<py:with vars="show_prev = bool(offset); show_next = len(sorted_entries) > offset + 20">
+	<py:for each="entry in sorted_entries">
 		${show_entry(entry, show_comments=False)}
 	</py:for>
 	<p py:if="show_prev or show_next">
diff --git a/templates/multiple_atom.xml b/templates/multiple_atom.xml
@@ -0,0 +1,49 @@
+<feed xmlns="http://www.w3.org/2005/Atom"
+      xmlns:xhtml="http://www.w3.org/1999/xhtml"
+      xmlns:py="http://genshi.edgewall.org/"
+	  xmlns:xi="http://www.w3.org/2001/XInclude">
+
+<?python
+from app import BASE_URL
+from viewutils import tag_list
+ATOM_TIME_FORMAT = str('%Y-%m-%dT%H:%M:%S+10:00')
+?>
+
+<id>http://www.djc.id.au${BASE_URL}/?format=atom</id>
+<title type="text">djc</title>
+<link rel="self" type="application/atom+xml" href="http://www.djc.id.au${BASE_URL}/?format=atom" />
+<link rel="alternate" href="http://www.djc.id.au${BASE_URL}/" />
+<generator>Dan’s blogging engine</generator>
+<updated>${feed_updated.strftime(ATOM_TIME_FORMAT)}</updated>
+
+<entry py:for="entry in sorted_entries">
+	<id>${entry.guid()}</id>
+	<published>${entry.publication_date.strftime(ATOM_TIME_FORMAT)}</published>
+	<updated>${entry.modified_date.strftime(ATOM_TIME_FORMAT)}</updated>
+	<author>
+		<name>djc</name>
+	</author>
+	<category py:for="category in entry.categories" scheme="http://www.djc.id.au${BASE_URL}/+categories/" term="${category}" />
+	<category py:for="tag in entry.tags" scheme="http://www.djc.id.au${BASE_URL}/+tags/" term="${tag}" />
+	<py:if test="'Reading' not in entry.categories">
+		<link rel="alternate" href="http://www.djc.id.au${BASE_URL}/${entry.id}" />
+		<title type="text">${entry.title}</title>
+		<content type="xhtml" xml:base="http://www.djc.id.au${BASE_URL}/${entry.id}"><xhtml:div>
+			${entry.body}
+			<p py:if="entry.tags">
+				<img src="${BASE_URL}/static/images/tag_blue.png" alt="Tags:" />
+				${tag_list(entry.tags)}
+			</p>
+		</xhtml:div></content>
+	</py:if>
+	<py:if test="'Reading' in entry.categories">
+		<title type="text">${entry.title} by ${entry.author}</title>
+		<summary type="text">${entry.rating} stars</summary>
+		<content type="xhtml"><xhtml:div>
+			<p><a href="${entry.url}">${entry.title}</a> by ${entry.author}</p>
+			<p>${entry.rating} stars</p>
+		</xhtml:div></content>
+	</py:if>
+</entry>
+
+</feed>