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
|
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>
|
||||||
|
@ -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:
|
||||||
|
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(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')
|
||||||
|
@ -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;
|
||||||
|
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