Genshi Themes
This commit is contained in:
parent
9d97a03d33
commit
1e3ca39733
@ -167,5 +167,18 @@ a <code>planet:format</code> attribute containing the referenced date
|
||||
formatted according to the <code>[planet] date_format</code> specified
|
||||
in the configuration</li>
|
||||
</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>
|
||||
</html>
|
||||
|
@ -1,8 +1,51 @@
|
||||
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
|
||||
|
||||
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={}):
|
||||
""" process an Genshi template """
|
||||
|
||||
@ -12,7 +55,68 @@ def run(script, doc, output_file=None, options={}):
|
||||
tmpl = MarkupTemplate(tmpl_fileobj, script)
|
||||
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')
|
||||
|
||||
if output_file:
|
||||
|
21
tests/data/apply/config-genshi.ini
Normal file
21
tests/data/apply/config-genshi.ini
Normal 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
|
@ -47,8 +47,15 @@ class ApplyTest(unittest.TestCase):
|
||||
self.assertEqual(12, content)
|
||||
self.assertEqual(3, lang)
|
||||
|
||||
def test_apply_fancy(self):
|
||||
def test_apply_classic_fancy(self):
|
||||
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)
|
||||
|
||||
# verify that selected files are there
|
||||
@ -93,3 +100,9 @@ except ImportError:
|
||||
logger.warn("xsltproc is not available => can't test XSLT templates")
|
||||
for method in dir(ApplyTest):
|
||||
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')
|
||||
|
@ -208,7 +208,7 @@ body > h1 {
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
#body h2 {
|
||||
#body > h2 {
|
||||
text-transform: none;
|
||||
font-size: medium;
|
||||
color: #333;
|
||||
|
20
themes/genshi_fancy/config.ini
Normal file
20
themes/genshi_fancy/config.ini
Normal 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
|
95
themes/genshi_fancy/index.html.genshi
Normal file
95
themes/genshi_fancy/index.html.genshi
Normal 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>
|
150
themes/genshi_fancy/planet.css
Normal file
150
themes/genshi_fancy/planet.css
Normal 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;
|
||||
}
|
Loading…
Reference in New Issue
Block a user