Genshi Themes

This commit is contained in:
Sam Ruby 2007-04-24 16:13:15 -04:00
parent 9d97a03d33
commit 1e3ca39733
8 changed files with 420 additions and 4 deletions

View File

@ -167,5 +167,18 @@ a <code>planet:format</code> attribute containing the referenced date
formatted according to the <code>[planet] date_format</code> specified formatted according to the <code>[planet] date_format</code> specified
in the configuration</li> in the configuration</li>
</ul> </ul>
<h3>genshi</h3>
<p>Genshi approaches the power of XSLT, but with a syntax that many Python
programmers find more natural, succinct and expressive. Genshi templates
have access to the full range of <a href="http://feedparser.org/docs/reference.html">feedparser</a> values, with the following additions:</p>
<ul>
<li>In addition to a <code>feed</code> element which describes the feed
for your planet, there is also a <code>feeds</code> element which contains
the description for each subscription.</li>
<li>All <code>feed</code>, <code>feeds</code>, and <code>source</code> elements have a child <code>config</code> element which contains the config.ini entries associated with that feed.</li>
<li>All text construct detail elements (<code>subtitle</code>, <code>rights</code>, <code>title</code>, <code>summary</code>, <code>content</code>) also contain a <code>stream</code> element which contains the value as a Genshi stream.</li>
<li>Each of the <code>entries</code> has a <code>new_date</code> and <code>new_feed</code> value which indicates if this entry's date or feed differs from the preceeding entry.</li>
</ul>
</body> </body>
</html> </html>

View File

@ -1,8 +1,51 @@
from StringIO import StringIO from StringIO import StringIO
from xml.sax.saxutils import escape
from genshi.input import XMLParser from genshi.input import HTMLParser, XMLParser
from genshi.template import Context, MarkupTemplate from genshi.template import Context, MarkupTemplate
subscriptions = []
feed_types = [
'application/atom+xml',
'application/rss+xml',
'application/rdf+xml'
]
def norm(value):
""" Convert to Unicode """
if hasattr(value,'items'):
return dict([(norm(n),norm(v)) for n,v in value.items()])
try:
return value.decode('utf-8')
except:
return value.decode('iso-8859-1')
def find_config(config, feed):
# match based on self link
for link in feed.links:
if link.has_key('rel') and link.rel=='self':
if link.has_key('type') and link.type in feed_types:
if link.has_key('href') and link.href in subscriptions:
return norm(dict(config.parser.items(link.href)))
# match based on name
for sub in subscriptions:
if config.parser.has_option(sub, 'name') and \
norm(config.parser.get(sub, 'name')) == feed.planet_name:
return norm(dict(config.parser.items(sub)))
return {}
def streamify(text,bozo):
""" add a .stream to a _detail textConstruct """
if text.type == 'text/plain':
text.stream = HTMLParser(StringIO(escape(text.value)))
elif text.type == 'text/html' or bozo != 'false':
text.stream = HTMLParser(StringIO(text.value))
else:
text.stream = XMLParser(StringIO("<div>%s</div>" % text.value))
def run(script, doc, output_file=None, options={}): def run(script, doc, output_file=None, options={}):
""" process an Genshi template """ """ process an Genshi template """
@ -12,7 +55,68 @@ def run(script, doc, output_file=None, options={}):
tmpl = MarkupTemplate(tmpl_fileobj, script) tmpl = MarkupTemplate(tmpl_fileobj, script)
tmpl_fileobj.close() tmpl_fileobj.close()
context.push({'input':XMLParser(StringIO(doc))}) if not output_file:
# filter
context.push({'input':XMLParser(StringIO(doc))})
else:
# template
import time
from planet import config,feedparser
from planet.spider import filename
# gather a list of subscriptions, feeds
global subscriptions
feeds = []
sources = config.cache_sources_directory()
for sub in config.subscriptions():
data=feedparser.parse(filename(sources,sub))
data.feed.config = norm(dict(config.parser.items(sub)))
if data.feed.has_key('link'):
feeds.append((data.feed.config.get('name',''),data.feed))
subscriptions.append(norm(sub))
feeds.sort()
# annotate each entry
new_date_format = config.new_date_format()
vars = feedparser.parse(StringIO(doc))
vars.feeds = [value for name,value in feeds]
last_feed = None
last_date = None
for entry in vars.entries:
entry.source.config = find_config(config, entry.source)
# add new_feed and new_date fields
entry.new_feed = entry.source.id
entry.new_date = date = None
if entry.has_key('published_parsed'): date=entry.published_parsed
if entry.has_key('updated_parsed'): date=entry.updated_parsed
if date: entry.new_date = time.strftime(new_date_format, date)
# remove new_feed and new_date fields if not "new"
if entry.new_date == last_date:
entry.new_date = None
if entry.new_feed == last_feed:
entry.new_feed = None
else:
last_feed = entry.new_feed
elif entry.new_date:
last_date = entry.new_date
last_feed = None
# add streams for all text constructs
for key in entry.keys():
if key.endswith("_detail") and entry[key].has_key('type') and \
entry[key].has_key('value'):
streamify(entry[key],entry.source.planet_bozo)
if entry.has_key('content'):
for content in entry.content:
streamify(content,entry.source.planet_bozo)
# add cumulative feed information to the Genshi context
vars.feed.config = dict(config.parser.items('Planet',True))
context.push(vars)
# apply template
output=tmpl.generate(context).render('xml') output=tmpl.generate(context).render('xml')
if output_file: if output_file:

View File

@ -0,0 +1,21 @@
[Planet]
output_theme = genshi_fancy
output_dir = tests/work/apply
name = test planet
cache_directory = tests/work/spider/cache
bill_of_materials:
images/#{face}
[tests/data/spider/testfeed0.atom]
name = not found
[tests/data/spider/testfeed1b.atom]
name = one
face = jdub.png
[tests/data/spider/testfeed2.atom]
name = two
[tests/data/spider/testfeed3.rss]
name = three

View File

@ -47,8 +47,15 @@ class ApplyTest(unittest.TestCase):
self.assertEqual(12, content) self.assertEqual(12, content)
self.assertEqual(3, lang) self.assertEqual(3, lang)
def test_apply_fancy(self): def test_apply_classic_fancy(self):
config.load(configfile % 'fancy') config.load(configfile % 'fancy')
self.apply_fancy()
def test_apply_genshi_fancy(self):
config.load(configfile % 'genshi')
self.apply_fancy()
def apply_fancy(self):
splice.apply(self.feeddata) splice.apply(self.feeddata)
# verify that selected files are there # verify that selected files are there
@ -93,3 +100,9 @@ except ImportError:
logger.warn("xsltproc is not available => can't test XSLT templates") logger.warn("xsltproc is not available => can't test XSLT templates")
for method in dir(ApplyTest): for method in dir(ApplyTest):
if method.startswith('test_'): delattr(ApplyTest,method) if method.startswith('test_'): delattr(ApplyTest,method)
import test_filter_genshi
for method in dir(test_filter_genshi.GenshiFilterTests):
if method.startswith('test_'): break
else:
delattr(ApplyTest,'test_apply_genshi_fancy')

View File

@ -208,7 +208,7 @@ body > h1 {
text-align: right; text-align: right;
} }
#body h2 { #body > h2 {
text-transform: none; text-transform: none;
font-size: medium; font-size: medium;
color: #333; color: #333;

View File

@ -0,0 +1,20 @@
# This theme reimplements the classic "fancy" htmltmpl using genshi
[Planet]
template_files:
atom.xml.xslt
foafroll.xml.xslt
index.html.genshi
opml.xml.xslt
rss10.xml.tmpl
rss20.xml.tmpl
template_directories:
../common
../classic_fancy
bill_of_materials:
planet.css
images/feed-icon-10x10.png
images/logo.png
images/venus.png

View File

@ -0,0 +1,95 @@
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:py="http://genshi.edgewall.org/">
<!--!
### Fancy Planet HTML template, converted to Genshi.
###
### When combined with the stylesheet and images in the output/ directory
### of the Planet source, this gives you a much prettier result than the
### default examples template and demonstrates how to use the config file
### to support things like faces
###
### For documentation on the more boring template elements, see
### http://www.intertwingly.net/code/venus/docs/templates.html
-->
<head>
<title>$feed.config.name</title>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
<meta name="generator" content="$feed.generator"/>
<link rel="stylesheet" href="planet.css" type="text/css"/>
<link py:for="link in feed.links"
py:if="link.type in ['application/atom+xml','application/rss+xml']"
href="$link.href" rel="alternate" title="$link.title" type="$link.type"/>
</head>
<body>
<h1>$feed.config.name</h1>
<py:for each="entry in entries">
<div class="channelgroup" py:strip="not entry.new_date">
<h2 py:if="entry.new_date">$entry.new_date</h2>
<div class="entrygroup" py:strip="not entry.new_feed">
<h3 py:if="entry.new_feed"><a href="$entry.link" title="$entry.source.title">$entry.source.config.name</a></h3>
<img py:if="entry.new_feed and entry.source.config.face" class="face" src="images/$entry.source.config.face" width="$entry.source.config.facewidth" height="$entry.source.config.faceheight" alt=""/>
<h4 py:if="entry.title" lang="$entry.title_detail.language"><a href="$entry.link">$entry.title_detail.stream</a></h4>
<div class="entry">
<div class="content" py:choose="">
<py:when test="entry.content">${entry.content[0].stream}</py:when>
<py:when test="entry.summary_detail">${entry.summary_detail.stream}</py:when>
</div>
<p class="date"><py:if test="entry.author_detail and entry.author_detail.name">by $entry.author_detail.name at </py:if>$entry.updated</p>
</div>
</div>
</div>
</py:for>
<div class="sidebar">
<img src="images/logo.png" width="136" height="136" alt=""/>
<h2>Subscriptions</h2>
<ul>
<li py:for="feed in feeds">
<a py:for="link in feed.links" py:if="link.rel == 'self' and
link.type in ['application/atom+xml','application/rss+xml']"
href="$link.href" title="subscribe"><img src="images/feed-icon-10x10.png" alt="(feed)"/></a>
<py:choose>
<a py:when="feed.planet_message" href="$feed.link" class="message" title="$feed.planet_message">$feed.config.name</a>
<a py:otherwise="1" href="$feed.link" title="$feed.title">$feed.config.name</a>
</py:choose>
</li>
</ul>
<p>
<strong>Last updated:</strong><br/>
$feed.updated<br/>
<em>All times are UTC.</em><br/>
<br/>
Powered by:<br/>
<a href="http://intertwingly.net/code/venus/"><img src="images/venus.png" width="80" height="15" alt="Planet Venus" border="0"/></a>
</p>
<p>
<h2>Planetarium:</h2>
<ul>
<li><a href="http://www.planetapache.org/">Planet Apache</a></li>
<li><a href="http://planet.debian.net/">Planet Debian</a></li>
<li><a href="http://planet.freedesktop.org/">Planet freedesktop.org</a></li>
<li><a href="http://planet.gnome.org/">Planet GNOME</a></li>
<li><a href="http://planetsun.org/">Planet Sun</a></li>
<li><a href="http://fedora.linux.duke.edu/fedorapeople/">Fedora People</a></li>
<li><a href="http://www.planetplanet.org/">more...</a></li>
</ul>
</p>
</div>
</body>
</html>

View File

@ -0,0 +1,150 @@
body {
border-right: 1px solid black;
margin-right: 200px;
padding-left: 20px;
padding-right: 20px;
}
h1 {
margin-top: 0px;
padding-top: 20px;
font-family: "Bitstream Vera Sans", sans-serif;
font-weight: normal;
letter-spacing: -2px;
text-transform: lowercase;
text-align: right;
color: grey;
}
.admin {
text-align: right;
}
h2 {
font-family: "Bitstream Vera Sans", sans-serif;
font-weight: normal;
color: #200080;
margin-left: -20px;
}
h3 {
font-family: "Bitstream Vera Sans", sans-serif;
font-weight: normal;
background-color: #a0c0ff;
border: 1px solid #5080b0;
padding: 4px;
}
h3 a {
text-decoration: none;
color: inherit;
}
h4 {
font-family: "Bitstream Vera Sans", sans-serif;
font-weight: bold;
}
h4 a {
text-decoration: none;
color: inherit;
}
img.face {
float: right;
margin-top: -3em;
}
.entry {
margin-bottom: 2em;
}
.entry .date {
font-family: "Bitstream Vera Sans", sans-serif;
color: grey;
}
.entry .date a {
text-decoration: none;
color: inherit;
}
.sidebar {
position: absolute;
top: 0px;
right: 0px;
width: 200px;
margin-left: 0px;
margin-right: 0px;
padding-right: 0px;
padding-top: 20px;
padding-left: 0px;
font-family: "Bitstream Vera Sans", sans-serif;
font-size: 85%;
}
.sidebar h2 {
font-size: 110%;
font-weight: bold;
color: black;
padding-left: 5px;
margin-left: 0px;
}
.sidebar ul {
padding-left: 1em;
margin-left: 0px;
list-style-type: none;
}
.sidebar ul li:hover {
color: grey;
}
.sidebar ul li a {
text-decoration: none;
}
.sidebar ul li a:hover {
text-decoration: underline;
}
.sidebar ul li a img {
border: 0;
}
.sidebar p {
border-top: 1px solid grey;
margin-top: 30px;
padding-top: 10px;
padding-left: 5px;
}
.sidebar .message {
cursor: help;
border-bottom: 1px dashed red;
}
.sidebar a.message:hover {
cursor: help;
background-color: #ff0000;
color: #ffffff !important;
text-decoration: none !important;
}
a:hover {
text-decoration: underline !important;
color: blue !important;
}