diff --git a/planet/__init__.py b/planet/__init__.py
index d66a958..8b9b982 100644
--- a/planet/__init__.py
+++ b/planet/__init__.py
@@ -2,6 +2,9 @@ xmlns = 'http://planet.intertwingly.net/'
logger = None
+import config
+config.__init__()
+
def getLogger(level):
""" get a logger with the specified log level """
global logger
diff --git a/planet/config.py b/planet/config.py
index 2e657af..2191d15 100644
--- a/planet/config.py
+++ b/planet/config.py
@@ -11,7 +11,7 @@ Usage:
config.load('config.ini')
# administrative / structural information
- print config.templates()
+ print config.template_files()
print config.feeds()
# planet wide configuration
@@ -37,6 +37,7 @@ def __init__():
"""define the struture of an ini file"""
import config
+ # underlying implementation
def get(section, option, default):
if section and parser.has_option(section, option):
return parser.get(section, option)
@@ -49,6 +50,10 @@ def __init__():
setattr(config, name, lambda default=default: get(None,name,default))
planet_predefined_options.append(name)
+ def define_planet_list(name):
+ setattr(config, name, lambda : get(None,name,'').split())
+ planet_predefined_options.append(name)
+
def define_tmpl(name, default):
setattr(config, name, lambda section, default=default:
get(section,name,default))
@@ -63,25 +68,57 @@ def __init__():
define_planet('cache_directory', "cache")
define_planet('log_level', "WARNING")
define_planet('feed_timeout', 20)
+ define_planet('date_format', "%B %d, %Y %I:%M %p")
+ define_planet('generator', 'Venus')
+ define_planet('generator_uri', 'http://intertwingly.net/code/venus/')
+ define_planet('owner_name', 'Anonymous Coward')
+ define_planet('owner_email', '')
+ define_planet('output_theme', '')
+ define_planet('output_dir', 'output')
+
+ define_planet_list('template_files')
+ define_planet_list('bill_of_materials')
+ define_planet_list('template_directories')
# template options
define_tmpl_int('days_per_page', 0)
define_tmpl_int('items_per_page', 60)
define_tmpl('encoding', 'utf-8')
- # prevent re-initialization
- setattr(config, '__init__', lambda: None)
-
-def load(file):
+def load(config_file):
""" initialize and load a configuration"""
- __init__()
global parser
parser = ConfigParser()
- parser.read(file)
+ parser.read(config_file)
-def template_files():
- """ list the templates defined """
- return parser.get('Planet','template_files').split(' ')
+ if parser.has_option('Planet', 'output_theme'):
+ theme = parser.get('Planet', 'output_theme')
+ for path in ("", os.path.join(sys.path[0],'themes')):
+ theme_dir = os.path.join(path,theme)
+ theme_file = os.path.join(theme_dir,'config.ini')
+ if os.path.exists(theme_file):
+ # initial search list for theme directories
+ dirs = [theme_dir]
+ if parser.has_option('Planet', 'template_directories'):
+ dirs.insert(0,parser.get('Planet', 'template_directories'))
+
+ # read in the theme
+ parser = ConfigParser()
+ parser.read(theme_file)
+
+ # complete search list for theme directories
+ if parser.has_option('Planet', 'template_directories'):
+ dirs += [os.path.join(theme_dir,dir) for dir in
+ parser.get('Planet', 'template_directories').split()]
+
+ # merge configurations, allowing current one to override theme
+ parser.read(config_file)
+ parser.set('Planet', 'template_directories', ' '.join(dirs))
+ break
+ else:
+ import config, planet
+ log = planet.getLogger(config.log_level())
+ log.error('Unable to find theme %s', theme)
def cache_sources_directory():
if parser.has_option('Planet', 'cache_sources_directory'):
diff --git a/planet/reconstitute.py b/planet/reconstitute.py
index 2f57726..cc72054 100644
--- a/planet/reconstitute.py
+++ b/planet/reconstitute.py
@@ -18,7 +18,7 @@ from xml.sax.saxutils import escape
from xml.dom import minidom
from BeautifulSoup import BeautifulSoup
from xml.parsers.expat import ExpatError
-import planet
+import planet, config
illegal_xml_chars = re.compile("[\x01-\x08\x0B\x0C\x0E-\x1F]")
@@ -29,6 +29,7 @@ def createTextElement(parent, name, value):
xelement = xdoc.createElement(name)
xelement.appendChild(xdoc.createTextNode(value))
parent.appendChild(xelement)
+ return xelement
def invalidate(c):
""" replace invalid characters """
@@ -98,7 +99,9 @@ def date(xentry, name, parsed):
""" insert a date-formated element into the entry """
if not parsed: return
formatted = time.strftime("%Y-%m-%dT%H:%M:%SZ", parsed)
- createTextElement(xentry, name, formatted)
+ xdate = createTextElement(xentry, name, formatted)
+ formatted = time.strftime(config.date_format(), parsed)
+ xdate.setAttribute('planet:format', formatted)
def author(xentry, name, detail):
""" insert an author-like element into the entry """
diff --git a/planet/splice.py b/planet/splice.py
index 55f9739..815326a 100644
--- a/planet/splice.py
+++ b/planet/splice.py
@@ -1,8 +1,8 @@
""" Splice together a planet from a cache of feed entries """
-import glob, os
+import glob, os, time, shutil
from xml.dom import minidom
import planet, config, feedparser, reconstitute
-from reconstitute import createTextElement
+from reconstitute import createTextElement, date
from spider import filename
def splice(configFile):
@@ -11,6 +11,7 @@ def splice(configFile):
config.load(configFile)
log = planet.getLogger(config.log_level())
+ log.info("Loading cached data")
cache = config.cache_directory()
dir=[(os.stat(file).st_mtime,file) for file in glob.glob(cache+"/*")
if not os.path.isdir(file)]
@@ -18,17 +19,20 @@ def splice(configFile):
dir.reverse()
items=max([config.items_per_page(templ)
- for templ in config.template_files()])
+ for templ in config.template_files() or ['Planet']])
doc = minidom.parseString('')
feed = doc.documentElement
- # insert Google/LiveJournal's noindex
- feed.setAttribute('indexing:index','no')
- feed.setAttribute('xmlns:indexing','urn:atom-extension:indexing')
-
# insert feed information
createTextElement(feed, 'title', config.name())
+ date(feed, 'updated', time.gmtime())
+ gen = createTextElement(feed, 'generator', config.generator())
+ gen.setAttribute('uri', config.generator_uri())
+ author = doc.createElement('author')
+ createTextElement(author, 'name', config.owner_name())
+ createTextElement(author, 'email', config.owner_email())
+ feed.appendChild(author)
# insert entry information
for mtime,file in dir[:items]:
@@ -47,3 +51,75 @@ def splice(configFile):
feed.appendChild(xdoc.documentElement)
return doc
+
+def apply(doc):
+ output_dir = config.output_dir()
+ if not os.path.exists(output_dir): os.makedirs(output_dir)
+ log = planet.getLogger(config.log_level())
+
+ try:
+ # if available, use the python interface to libxslt
+ import libxml2
+ import libxslt
+ dom = libxml2.parseDoc(doc)
+ docfile = None
+ except:
+ # otherwise, use the command line interface
+ dom = None
+ import warnings
+ warnings.simplefilter('ignore', RuntimeWarning)
+ docfile = os.tmpnam()
+ file = open(docfile,'w')
+ file.write(doc)
+ file.close()
+
+ # Go-go-gadget-template
+ for template_file in config.template_files():
+ for template_dir in config.template_directories():
+ template_resolved = os.path.join(template_dir, template_file)
+ if os.path.exists(template_resolved): break
+ else:
+ log.error("Unable to locate template %s", template_file)
+ continue
+
+ base,ext = os.path.splitext(os.path.basename(template_resolved))
+ if ext != '.xslt':
+ log.warning("Skipping template %s", template_resolved)
+ continue
+
+ log.info("Processing template %s", template_resolved)
+ output_file = os.path.join(output_dir, base)
+ if dom:
+ styledoc = libxml2.parseFile(template_resolved)
+ style = libxslt.parseStylesheetDoc(styledoc)
+ result = style.applyStylesheet(dom, None)
+ log.info("Writing %s", output_file)
+ style.saveResultToFilename(output_file, result, 0)
+ style.freeStylesheet()
+ result.freeDoc()
+ else:
+ log.info("Writing %s", output_file)
+ os.system('xsltproc %s %s > %s' %
+ (template_resolved, docfile, output_file))
+
+ if dom: dom.freeDoc()
+ if docfile: os.unlink(docfile)
+
+ # Process bill of materials
+ for copy_file in config.bill_of_materials():
+ dest = os.path.join(output_dir, copy_file)
+ for template_dir in config.template_directories():
+ source = os.path.join(template_dir, copy_file)
+ if os.path.exists(source): break
+ else:
+ log.error('Unable to locate %s', copy_file)
+ continue
+
+ mtime = os.stat(source).st_mtime
+ if not os.path.exists(dest) or os.stat(dest).st_mtime < mtime:
+ dest_dir = os.path.split(dest)[0]
+ if not os.path.exists(dest_dir): os.makedirs(dest_dir)
+
+ log.info("Copying %s to %s", source, dest)
+ shutil.copyfile(source, dest)
+ shutil.copystat(source, dest)
diff --git a/runtests.py b/runtests.py
index 1c25c3d..55e22ae 100755
--- a/runtests.py
+++ b/runtests.py
@@ -1,8 +1,11 @@
#!/usr/bin/env python
-import glob, trace, unittest
+import glob, trace, unittest, os, sys
+
+# start in a consistent, predictable location
+os.chdir(sys.path[0])
# find all of the planet test modules
-modules = map(trace.fullmodname, glob.glob('tests/test_*.py'))
+modules = map(trace.fullmodname, glob.glob(os.path.join('tests', 'test_*.py')))
# load all of the tests into a suite
suite = unittest.TestLoader().loadTestsFromNames(modules)
diff --git a/splice.py b/splice.py
index e5ed424..580015b 100755
--- a/splice.py
+++ b/splice.py
@@ -13,10 +13,7 @@ if __name__ == '__main__':
# at the moment, we don't have template support, so we cheat and
# simply insert a XSLT processing instruction
doc = splice.splice(sys.argv[1])
- pi = doc.createProcessingInstruction(
- 'xml-stylesheet','type="text/xsl" href="planet.xslt"')
- doc.insertBefore(pi, doc.firstChild)
- print doc.toxml('utf-8')
+ splice.apply(doc.toxml('utf-8'))
else:
print "Usage:"
print " python %s config.ini" % sys.argv[0]
diff --git a/tests/capture.py b/tests/capture.py
new file mode 100755
index 0000000..b71191d
--- /dev/null
+++ b/tests/capture.py
@@ -0,0 +1,42 @@
+#!/usr/bin/env python
+
+"""
+While unit tests are intended to be independently executable, it often
+is helpful to ensure that some downstream tasks can be run with the
+exact output produced by upstream tasks.
+
+This script captures such output. It should be run whenever there is
+a major change in the contract between stages
+"""
+
+import shutil, os, sys
+
+# move up a directory
+sys.path.insert(1, os.path.split(sys.path[0])[0])
+os.chdir(sys.path[1])
+
+# copy spider output to splice input
+from planet import spider
+spider.spiderPlanet('tests/data/spider/config.ini')
+if os.path.exists('tests/data/splice/cache'):
+ shutil.rmtree('tests/data/splice/cache')
+shutil.move('tests/work/spider/cache', 'tests/data/splice/cache')
+
+source=open('tests/data/spider/config.ini')
+dest1=open('tests/data/splice/config.ini', 'w')
+dest1.write(source.read().replace('/work/spider/', '/data/splice/'))
+dest1.close()
+
+source.seek(0)
+dest2=open('tests/data/apply/config.ini', 'w')
+dest2.write(source.read().replace('[Planet]', '''[Planet]
+output_theme = asf
+output_dir = tests/work/apply'''))
+dest2.close()
+source.close()
+
+# copy splice output to apply input
+from planet import splice
+file=open('tests/data/apply/feed.xml', 'w')
+file.write(splice.splice('tests/data/splice/config.ini').toxml('utf-8'))
+file.close()
diff --git a/tests/data/apply/config.ini b/tests/data/apply/config.ini
new file mode 100644
index 0000000..6256d2d
--- /dev/null
+++ b/tests/data/apply/config.ini
@@ -0,0 +1,17 @@
+[Planet]
+output_theme = asf
+output_dir = tests/work/apply
+name = test planet
+cache_directory = tests/work/spider/cache
+
+[tests/data/spider/testfeed0.atom]
+name = not found
+
+[tests/data/spider/testfeed1b.atom]
+name = one
+
+[tests/data/spider/testfeed2.atom]
+name = two
+
+[tests/data/spider/testfeed3.rss]
+name = three
diff --git a/tests/data/apply/feed.xml b/tests/data/apply/feed.xml
new file mode 100644
index 0000000..f4029b2
--- /dev/null
+++ b/tests/data/apply/feed.xml
@@ -0,0 +1,215 @@
+
+test planet2006-08-21T12:54:31ZVenusAnonymous Coward
+ tag:planet.intertwingly.net,2006:testfeed3/2
+
+ Venus
+ the Morning Star
+ 2006-08-21T12:54:31Z
+
+
+
+ It’s just data
+ Sam Ruby
+ three
+
+
+ http://example.com/4
+
+ Mars
+ the Red Planet
+ 2006-08-21T12:54:31Z
+
+
+
+ It’s just data
+ Sam Ruby
+ three
+
+
+ tag:planet.intertwingly.net,2006:testfeed1/2
+
+ Venus
+ the Jewel of the Sky
+ 2006-02-02T00:00:00Z
+ 2006-01-02T00:00:00Z
+
+ tag:planet.intertwingly.net,2006:testfeed1
+
+ Sam Ruby
+ rubys@intertwingly.net
+ http://www.intertwingly.net/blog/
+
+
+
+ It’s just data
+ Sam Ruby
+ 2006-06-17T00:15:18Z
+ one
+
+
+ tag:planet.intertwingly.net,2006:testfeed2/4
+
+ Mars
+ the Red Planet
+ 2006-01-04T00:00:00Z
+
+ tag:planet.intertwingly.net,2006:testfeed2
+
+ Sam Ruby
+ rubys@intertwingly.net
+ http://www.intertwingly.net/blog/
+
+
+
+ It’s just data
+ Sam Ruby
+ 2006-06-17T00:15:18Z
+ two
+
+
+ tag:planet.intertwingly.net,2006:testfeed1/4
+
+ Mars
+ the Red Planet
+ 2006-01-04T00:00:00Z
+
+ tag:planet.intertwingly.net,2006:testfeed1
+
+ Sam Ruby
+ rubys@intertwingly.net
+ http://www.intertwingly.net/blog/
+
+
+
+ It’s just data
+ Sam Ruby
+ 2006-06-17T00:15:18Z
+ one
+
+
+ tag:planet.intertwingly.net,2006:testfeed2/3
+
+ Earth
+ the Blue Planet
+ 2006-01-03T00:00:00Z
+
+ tag:planet.intertwingly.net,2006:testfeed2
+
+ Sam Ruby
+ rubys@intertwingly.net
+ http://www.intertwingly.net/blog/
+
+
+
+ It’s just data
+ Sam Ruby
+ 2006-06-17T00:15:18Z
+ two
+
+
+ tag:planet.intertwingly.net,2006:testfeed1/3
+
+ Earth
+ the Blue Planet
+ 2006-01-03T00:00:00Z
+
+ tag:planet.intertwingly.net,2006:testfeed1
+
+ Sam Ruby
+ rubys@intertwingly.net
+ http://www.intertwingly.net/blog/
+
+
+
+ It’s just data
+ Sam Ruby
+ 2006-06-17T00:15:18Z
+ one
+
+
+ http://example.com/3
+
+ Earth
+ the Blue Planet
+ 2006-01-03T00:00:00Z
+
+
+
+ It’s just data
+ Sam Ruby
+ three
+
+
+ tag:planet.intertwingly.net,2006:testfeed2/2
+
+ Venus
+ the Morning Star
+ 2006-01-02T00:00:00Z
+
+ tag:planet.intertwingly.net,2006:testfeed2
+
+ Sam Ruby
+ rubys@intertwingly.net
+ http://www.intertwingly.net/blog/
+
+
+
+ It’s just data
+ Sam Ruby
+ 2006-06-17T00:15:18Z
+ two
+
+
+ tag:planet.intertwingly.net,2006:testfeed3/1
+
+ Mercury
+ Messenger of the Roman Gods
+ 2006-01-01T00:00:00Z
+
+
+
+ It’s just data
+ Sam Ruby
+ three
+
+
+ tag:planet.intertwingly.net,2006:testfeed2/1
+
+ Mercury
+ Messenger of the Roman Gods
+ 2006-01-01T00:00:00Z
+
+ tag:planet.intertwingly.net,2006:testfeed2
+
+ Sam Ruby
+ rubys@intertwingly.net
+ http://www.intertwingly.net/blog/
+
+
+
+ It’s just data
+ Sam Ruby
+ 2006-06-17T00:15:18Z
+ two
+
+
+ tag:planet.intertwingly.net,2006:testfeed1/1
+
+ Mercury
+ Messenger of the Roman Gods
+ 2006-01-01T00:00:00Z
+
+ tag:planet.intertwingly.net,2006:testfeed1
+
+ Sam Ruby
+ rubys@intertwingly.net
+ http://www.intertwingly.net/blog/
+
+
+
+ It’s just data
+ Sam Ruby
+ 2006-06-17T00:15:18Z
+ one
+
+tag:planet.intertwingly.net,2006:testfeed2Sam Rubyrubys@intertwingly.nethttp://www.intertwingly.net/blog/It’s just dataSam Ruby2006-06-17T00:15:18ZtwoIt’s just dataSam Rubythreetag:planet.intertwingly.net,2006:testfeed1Sam Rubyrubys@intertwingly.nethttp://www.intertwingly.net/blog/It’s just dataSam Ruby2006-06-17T00:15:18Zone
\ No newline at end of file
diff --git a/tests/data/config/themed.ini b/tests/data/config/themed.ini
new file mode 100644
index 0000000..7de933e
--- /dev/null
+++ b/tests/data/config/themed.ini
@@ -0,0 +1,14 @@
+[Planet]
+name = Test Configuration
+output_theme = asf
+items_per_page = 50
+template_directories = /foo /bar
+
+[index.html.xslt]
+days_per_page = 7
+
+[feed1]
+name = one
+
+[feed2]
+name = two
diff --git a/tests/data/spider/config.ini b/tests/data/spider/config.ini
index 7b38417..7a6c5e7 100644
--- a/tests/data/spider/config.ini
+++ b/tests/data/spider/config.ini
@@ -1,6 +1,6 @@
[Planet]
+name = test planet
cache_directory = tests/work/spider/cache
-template_files =
[tests/data/spider/testfeed0.atom]
name = not found
diff --git a/tests/data/splice/cache/example.com,3 b/tests/data/splice/cache/example.com,3
index df0943b..78c8001 100644
--- a/tests/data/splice/cache/example.com,3
+++ b/tests/data/splice/cache/example.com,3
@@ -4,7 +4,7 @@
Earth
the Blue Planet
- 2006-01-03T00:00:00Z
+ 2006-01-03T00:00:00Z
diff --git a/tests/data/splice/cache/example.com,4 b/tests/data/splice/cache/example.com,4
index bc229ff..ee2f187 100644
--- a/tests/data/splice/cache/example.com,4
+++ b/tests/data/splice/cache/example.com,4
@@ -4,7 +4,7 @@
Mars
the Red Planet
- 2006-08-18T18:30:50Z
+ 2006-08-21T12:54:31Z
diff --git a/tests/data/splice/cache/planet.intertwingly.net,2006,testfeed1,1 b/tests/data/splice/cache/planet.intertwingly.net,2006,testfeed1,1
index ee44d5b..3c3c8f3 100644
--- a/tests/data/splice/cache/planet.intertwingly.net,2006,testfeed1,1
+++ b/tests/data/splice/cache/planet.intertwingly.net,2006,testfeed1,1
@@ -4,7 +4,7 @@
Mercury
Messenger of the Roman Gods
- 2006-01-01T00:00:00Z
+ 2006-01-01T00:00:00Z
tag:planet.intertwingly.net,2006:testfeed1
@@ -16,7 +16,7 @@
It’s just data
Sam Ruby
- 2006-06-17T00:15:18Z
+ 2006-06-17T00:15:18Z
one
diff --git a/tests/data/splice/cache/planet.intertwingly.net,2006,testfeed1,2 b/tests/data/splice/cache/planet.intertwingly.net,2006,testfeed1,2
index ee8ae2c..84d5fd4 100644
--- a/tests/data/splice/cache/planet.intertwingly.net,2006,testfeed1,2
+++ b/tests/data/splice/cache/planet.intertwingly.net,2006,testfeed1,2
@@ -4,8 +4,8 @@
Venus
the Jewel of the Sky
- 2006-02-02T00:00:00Z
- 2006-01-02T00:00:00Z
+ 2006-02-02T00:00:00Z
+ 2006-01-02T00:00:00Z
tag:planet.intertwingly.net,2006:testfeed1
@@ -17,7 +17,7 @@
It’s just data
Sam Ruby
- 2006-06-17T00:15:18Z
+ 2006-06-17T00:15:18Z
one
diff --git a/tests/data/splice/cache/planet.intertwingly.net,2006,testfeed1,3 b/tests/data/splice/cache/planet.intertwingly.net,2006,testfeed1,3
index e55d16a..f53e642 100644
--- a/tests/data/splice/cache/planet.intertwingly.net,2006,testfeed1,3
+++ b/tests/data/splice/cache/planet.intertwingly.net,2006,testfeed1,3
@@ -4,7 +4,7 @@
Earth
the Blue Planet
- 2006-01-03T00:00:00Z
+ 2006-01-03T00:00:00Z
tag:planet.intertwingly.net,2006:testfeed1
@@ -16,7 +16,7 @@
It’s just data
Sam Ruby
- 2006-06-17T00:15:18Z
+ 2006-06-17T00:15:18Z
one
diff --git a/tests/data/splice/cache/planet.intertwingly.net,2006,testfeed1,4 b/tests/data/splice/cache/planet.intertwingly.net,2006,testfeed1,4
index 85b6a01..bd126fa 100644
--- a/tests/data/splice/cache/planet.intertwingly.net,2006,testfeed1,4
+++ b/tests/data/splice/cache/planet.intertwingly.net,2006,testfeed1,4
@@ -4,7 +4,7 @@
Mars
the Red Planet
- 2006-01-04T00:00:00Z
+ 2006-01-04T00:00:00Z
tag:planet.intertwingly.net,2006:testfeed1
@@ -16,7 +16,7 @@
It’s just data
Sam Ruby
- 2006-06-17T00:15:18Z
+ 2006-06-17T00:15:18Z
one
diff --git a/tests/data/splice/cache/planet.intertwingly.net,2006,testfeed2,1 b/tests/data/splice/cache/planet.intertwingly.net,2006,testfeed2,1
index d143d18..4e387d0 100644
--- a/tests/data/splice/cache/planet.intertwingly.net,2006,testfeed2,1
+++ b/tests/data/splice/cache/planet.intertwingly.net,2006,testfeed2,1
@@ -4,7 +4,7 @@
Mercury
Messenger of the Roman Gods
- 2006-01-01T00:00:00Z
+ 2006-01-01T00:00:00Z
tag:planet.intertwingly.net,2006:testfeed2
@@ -16,7 +16,7 @@
It’s just data
Sam Ruby
- 2006-06-17T00:15:18Z
+ 2006-06-17T00:15:18Z
two
diff --git a/tests/data/splice/cache/planet.intertwingly.net,2006,testfeed2,2 b/tests/data/splice/cache/planet.intertwingly.net,2006,testfeed2,2
index edeaeaa..2b3f94d 100644
--- a/tests/data/splice/cache/planet.intertwingly.net,2006,testfeed2,2
+++ b/tests/data/splice/cache/planet.intertwingly.net,2006,testfeed2,2
@@ -4,7 +4,7 @@
Venus
the Morning Star
- 2006-01-02T00:00:00Z
+ 2006-01-02T00:00:00Z
tag:planet.intertwingly.net,2006:testfeed2
@@ -16,7 +16,7 @@
It’s just data
Sam Ruby
- 2006-06-17T00:15:18Z
+ 2006-06-17T00:15:18Z
two
diff --git a/tests/data/splice/cache/planet.intertwingly.net,2006,testfeed2,3 b/tests/data/splice/cache/planet.intertwingly.net,2006,testfeed2,3
index a724a82..576644e 100644
--- a/tests/data/splice/cache/planet.intertwingly.net,2006,testfeed2,3
+++ b/tests/data/splice/cache/planet.intertwingly.net,2006,testfeed2,3
@@ -4,7 +4,7 @@
Earth
the Blue Planet
- 2006-01-03T00:00:00Z
+ 2006-01-03T00:00:00Z
tag:planet.intertwingly.net,2006:testfeed2
@@ -16,7 +16,7 @@
It’s just data
Sam Ruby
- 2006-06-17T00:15:18Z
+ 2006-06-17T00:15:18Z
two
diff --git a/tests/data/splice/cache/planet.intertwingly.net,2006,testfeed2,4 b/tests/data/splice/cache/planet.intertwingly.net,2006,testfeed2,4
index f28d7b9..aeaa78c 100644
--- a/tests/data/splice/cache/planet.intertwingly.net,2006,testfeed2,4
+++ b/tests/data/splice/cache/planet.intertwingly.net,2006,testfeed2,4
@@ -4,7 +4,7 @@
Mars
the Red Planet
- 2006-01-04T00:00:00Z
+ 2006-01-04T00:00:00Z
tag:planet.intertwingly.net,2006:testfeed2
@@ -16,7 +16,7 @@
It’s just data
Sam Ruby
- 2006-06-17T00:15:18Z
+ 2006-06-17T00:15:18Z
two
diff --git a/tests/data/splice/cache/planet.intertwingly.net,2006,testfeed3,1 b/tests/data/splice/cache/planet.intertwingly.net,2006,testfeed3,1
index 5ca9f26..e19e044 100644
--- a/tests/data/splice/cache/planet.intertwingly.net,2006,testfeed3,1
+++ b/tests/data/splice/cache/planet.intertwingly.net,2006,testfeed3,1
@@ -4,7 +4,7 @@
Mercury
Messenger of the Roman Gods
- 2006-01-01T00:00:00Z
+ 2006-01-01T00:00:00Z
diff --git a/tests/data/splice/cache/planet.intertwingly.net,2006,testfeed3,2 b/tests/data/splice/cache/planet.intertwingly.net,2006,testfeed3,2
index f5acd6b..a1262b1 100644
--- a/tests/data/splice/cache/planet.intertwingly.net,2006,testfeed3,2
+++ b/tests/data/splice/cache/planet.intertwingly.net,2006,testfeed3,2
@@ -4,7 +4,7 @@
Venus
the Morning Star
- 2006-08-18T18:30:50Z
+ 2006-08-21T12:54:31Z
diff --git a/tests/data/splice/cache/sources/tests,data,spider,testfeed1b.atom b/tests/data/splice/cache/sources/tests,data,spider,testfeed1b.atom
index 8cb9e5c..d525b98 100644
--- a/tests/data/splice/cache/sources/tests,data,spider,testfeed1b.atom
+++ b/tests/data/splice/cache/sources/tests,data,spider,testfeed1b.atom
@@ -10,6 +10,6 @@
It’s just data
Sam Ruby
- 2006-06-17T00:15:18Z
+ 2006-06-17T00:15:18Z
one
diff --git a/tests/data/splice/cache/sources/tests,data,spider,testfeed2.atom b/tests/data/splice/cache/sources/tests,data,spider,testfeed2.atom
index 6aeb0ab..df9da4d 100644
--- a/tests/data/splice/cache/sources/tests,data,spider,testfeed2.atom
+++ b/tests/data/splice/cache/sources/tests,data,spider,testfeed2.atom
@@ -10,6 +10,6 @@
It’s just data
Sam Ruby
- 2006-06-17T00:15:18Z
+ 2006-06-17T00:15:18Z
two
diff --git a/tests/data/splice/config.ini b/tests/data/splice/config.ini
index 0ba74c3..9c8cf3e 100644
--- a/tests/data/splice/config.ini
+++ b/tests/data/splice/config.ini
@@ -1,7 +1,6 @@
[Planet]
name = test planet
cache_directory = tests/data/splice/cache
-template_files =
[tests/data/spider/testfeed0.atom]
name = not found
diff --git a/tests/test_apply.py b/tests/test_apply.py
new file mode 100644
index 0000000..bdd0468
--- /dev/null
+++ b/tests/test_apply.py
@@ -0,0 +1,33 @@
+#!/usr/bin/env python
+
+import unittest, os, shutil
+from planet import config, splice
+
+workdir = 'tests/work/apply'
+configfile = 'tests/data/apply/config.ini'
+testfeed = 'tests/data/apply/feed.xml'
+
+class ApplyTest(unittest.TestCase):
+ def setUp(self):
+ try:
+ os.makedirs(workdir)
+ except:
+ self.tearDown()
+ os.makedirs(workdir)
+
+ def tearDown(self):
+ shutil.rmtree(workdir)
+ os.removedirs(os.path.split(workdir)[0])
+
+ def test_apply(self):
+ testfile = open(testfeed)
+ feeddata = testfile.read()
+ testfile.close()
+
+ config.load(configfile)
+ splice.apply(feeddata)
+
+ for file in ['index.html', 'default.css', 'images/foaf.png']:
+ path = os.path.join(workdir, file)
+ self.assertTrue(os.path.exists(path))
+ self.assertTrue(os.stat(path).st_size > 0)
diff --git a/tests/test_config.py b/tests/test_config.py
index eb3c79d..8773a46 100644
--- a/tests/test_config.py
+++ b/tests/test_config.py
@@ -3,8 +3,6 @@
import unittest
from planet import config
-workdir = 'tests/work/spider/cache'
-
class ConfigTest(unittest.TestCase):
def setUp(self):
config.load('tests/data/config/basic.ini')
@@ -16,7 +14,9 @@ class ConfigTest(unittest.TestCase):
config.template_files())
def test_feeds(self):
- self.assertEqual(['feed1', 'feed2'], config.feeds())
+ feeds = config.feeds()
+ feeds.sort()
+ self.assertEqual(['feed1', 'feed2'], feeds)
# planet wide configuration
diff --git a/tests/test_spider.py b/tests/test_spider.py
index 1f945e8..067c602 100644
--- a/tests/test_spider.py
+++ b/tests/test_spider.py
@@ -1,6 +1,6 @@
#!/usr/bin/env python
-import unittest, os, glob, calendar
+import unittest, os, glob, calendar, shutil
from planet.spider import filename, spiderFeed, spiderPlanet
from planet import feedparser, config
@@ -17,13 +17,8 @@ class SpiderTest(unittest.TestCase):
os.makedirs(workdir)
def tearDown(self):
- for file in glob.glob(workdir+"/sources/*"):
- os.unlink(file)
- if os.path.exists(workdir+"/sources"):
- os.rmdir(workdir+"/sources")
- for file in glob.glob(workdir+"/*"):
- os.unlink(file)
- os.removedirs(workdir)
+ shutil.rmtree(workdir)
+ os.removedirs(os.path.split(workdir)[0])
def test_filename(self):
self.assertEqual('./example.com,index.html',
diff --git a/tests/test_themes.py b/tests/test_themes.py
new file mode 100644
index 0000000..b92eb41
--- /dev/null
+++ b/tests/test_themes.py
@@ -0,0 +1,58 @@
+#!/usr/bin/env python
+
+import unittest
+from planet import config
+from os.path import split
+
+class ConfigTest(unittest.TestCase):
+ def setUp(self):
+ config.load('tests/data/config/themed.ini')
+
+ # template directories
+
+ def test_template_directories(self):
+ self.assertEqual(['foo', 'bar', 'asf', 'common'],
+ [split(dir)[1] for dir in config.template_directories()])
+
+ # administrivia
+
+ def test_template(self):
+ self.assertTrue('index.html.xslt' in config.template_files())
+
+ def test_feeds(self):
+ feeds = config.feeds()
+ feeds.sort()
+ self.assertEqual(['feed1', 'feed2'], feeds)
+
+ # planet wide configuration
+
+ def test_name(self):
+ self.assertEqual('Test Configuration', config.name())
+
+ def test_link(self):
+ self.assertEqual('Unconfigured Planet', config.link())
+
+ # per template configuration
+
+ def test_days_per_page(self):
+ self.assertEqual(7, config.days_per_page('index.html.xslt'))
+ self.assertEqual(0, config.days_per_page('atom.xml.xslt'))
+
+ def test_items_per_page(self):
+ self.assertEqual(50, config.items_per_page('index.html.xslt'))
+ self.assertEqual(50, config.items_per_page('atom.xml.xslt'))
+
+ def test_encoding(self):
+ self.assertEqual('utf-8', config.encoding('index.html.xslt'))
+ self.assertEqual('utf-8', config.encoding('atom.xml.xslt'))
+
+ # dictionaries
+
+ def test_feed_options(self):
+ self.assertEqual('one', config.feed_options('feed1')['name'])
+ self.assertEqual('two', config.feed_options('feed2')['name'])
+
+ def test_template_options(self):
+ option = config.template_options('index.html.xslt')
+ self.assertEqual('7', option['days_per_page'])
+ self.assertEqual('50', option['items_per_page'])
diff --git a/themes/asf/config.ini b/themes/asf/config.ini
new file mode 100644
index 0000000..10f6a22
--- /dev/null
+++ b/themes/asf/config.ini
@@ -0,0 +1,19 @@
+# This template is based on the one originally developed by Stefano Mazzocci
+# for planetapache.org, and modified by Sam Ruby for planet.intertwingly.net
+
+[Planet]
+template_files:
+ atom.xml.xslt
+ foafroll.xml.xslt
+ index.html.xslt
+ opml.xml.xslt
+
+template_directories:
+ ../common
+
+bill_of_materials:
+ default.css
+ personalize.js
+ images/feed-icon-10x10.png
+ images/opml.png
+ images/foaf.png
diff --git a/themes/asf/default.css b/themes/asf/default.css
new file mode 100644
index 0000000..001c641
--- /dev/null
+++ b/themes/asf/default.css
@@ -0,0 +1,429 @@
+/*
+ * Written by Stefano Mazzocchi
+ */
+
+/* ----------------------------- Global Definitions -------------------- */
+
+body {
+ margin: 0px;
+ padding: 0px;
+ color: #222;
+ background-color: #fff;
+ quotes: "\201C" "\201E" "\2018" "\2019";
+}
+
+a:link {
+ color: #222;
+}
+
+a:visited {
+ color: #555;
+}
+
+a:hover {
+ color: #000;
+}
+
+a:active {
+}
+
+a:focus {
+}
+
+h1 {
+ font-size: x-large;
+ text-transform: uppercase;
+ letter-spacing: 0.25em;
+ padding: 10px;
+ margin: 0px 0px 0px 0px;
+ color: #000;
+ font-weight: normal;
+ background-color: #eee;
+ border-bottom: 2px solid #bbb
+}
+
+/* ----------------------------- Sidebar --------------------------- */
+
+#sidebar {
+ float: right;
+ top: 150px;
+ right: 0px;
+ width: 210px;
+ background-color: white;
+
+ padding: 0px 0px 20px 0px;
+ margin: 0px 0px 20px 20px;
+ border-left: 1px solid #ccc;
+ border-bottom: 1px solid #ccc;
+}
+
+#sidebar h2 {
+ letter-spacing: 0.15em;
+ text-transform: uppercase;
+ font-size: x-small;
+ color: #666;
+ font-weight: normal;
+ padding: 2px 0px 2px 4px;
+ margin: 15px 0px 5px 10px;
+ border-top: 1px solid #ccc;
+ border-left: 1px solid #ccc;
+ border-bottom: 1px solid #ccc;
+}
+
+#sidebar p {
+ font-size: x-small;
+ padding-left: 20px;
+ padding-right: 5px;
+}
+
+#sidebar ul {
+ font-family: sans-serif;
+ margin-left: 5px;
+ padding-left: 25px;
+}
+
+#sidebar li {
+ margin-left: 0px;
+ text-indent: -15px;
+ list-style-type: none;
+ font-size: x-small;
+}
+
+#sidebar ul li a {
+ text-decoration: none;
+}
+
+#sidebar ul li a:hover {
+ text-decoration: underline;
+}
+
+#sidebar img {
+ border: 0;
+}
+
+#sidebar dl {
+ font-size: x-small;
+ padding-left: 1.0em;
+}
+
+#sidebar dl ul {
+ padding-left: 1em;
+}
+
+#sidebar dt {
+ margin-top: 1em;
+ font-weight: bold;
+ padding-left: 1.0em;
+}
+
+#sidebar dd {
+ margin-left: 2.5em;
+}
+
+#sidebar .message {
+ cursor: help;
+ border-bottom: 1px dashed red;
+}
+
+#sidebar a.message:hover {
+ cursor: help;
+ background-color: #ffD0D0;
+ border: 1px dashed red !important;
+ text-decoration: none !important;
+}
+
+/* ----------------------------- Body ---------------------------- */
+
+#body {
+ margin-top: 10px;
+}
+
+.admin {
+ text-align: right;
+}
+
+#body h2.date {
+ text-transform: none;
+ font-size: medium;
+ color: #333;
+ font-weight: bold;
+ text-align: right;
+ border-top: 1px solid #ccc;
+ background-color: #eee;
+ border-bottom: 1px solid #ccc;
+ padding: 1px 15px 1px 5px;
+ margin: 0;
+}
+
+/* ----------------------------- News ---------------------------- */
+
+.news {
+ margin: 30px 10px 30px 10px;
+ clear: left;
+}
+
+.news > h3 {
+ text-indent: -10px;
+ margin: 12px;
+ padding: 0px;
+ font-size: medium;
+}
+
+.news > h3 > a:first-child {
+ margin-left: 10px
+}
+
+.news > h3 > a:first-child:before {
+ content: '⌘';
+ color: #D70;
+ margin-left: -18px;
+ margin-right: 2px;
+ text-decoration: none;
+}
+
+img.icon {
+ height: 16px;
+ width: 16px;
+ margin-left: -8px;
+ margin-bottom: -2px;
+ margin-right: 3px;
+}
+
+.news .content {
+ margin: 5px 5px 5px 15px;
+ padding: 0px 5px 0px 5px;
+ border-left: 1px solid #ccc;
+ line-height: 1.2em;
+ font-size: small;
+ font-family: sans-serif;
+}
+
+.news .links {
+}
+
+.news .permalink {
+ text-align: right;
+}
+
+/* ----------------------------- News Content ---------------------------- */
+
+.news .content p {
+ line-height: 1.2em;
+}
+
+.news .content img {
+ margin: 5px;
+}
+
+.news .content blockquote {
+ margin: 10px 35px 10px 35px;
+ padding: 5px;
+}
+
+.news .content pre {
+ font-family: monospace;
+ font-size: medium;
+ font-weight: bold;
+ border: 1px solid #ddd;
+ padding: 10px;
+ margin: 10px 20px 10px 20px;
+ background-color: #f8f8f8;
+ overflow: auto;
+}
+
+.news .content ul, .news .content ol {
+ margin: 5px 35px 5px 35px;
+ padding: 5px;
+ counter-reset: item;
+}
+
+.news .content ul > ul, .news .content ul > ol, .news .content ol > ul, .news .content ol > ol {
+ margin: 0px 0px 0px 35px;
+ padding: 0px;
+}
+
+.news .content li {
+ padding: 1px;
+ line-height: 1.2em;
+}
+
+.news code {
+ font-family: monospace;
+ font-size: medium;
+ font-weight: bold;
+}
+
+.news .content a {
+ text-decoration: none;
+ color: #000;
+ border-bottom: 1px dotted #777;
+ margin: 0px 2px 0px 2px;
+ padding: 1px 1px 1px 1px;
+}
+
+.news .content a:hover {
+ border: 1px dotted #000;
+ background-color: #eee;
+ padding: 1px 2px 1px 2px;
+ margin: 0px;
+}
+
+.news .content a:active {
+ background-color: #ccc !important;
+ position: relative;
+ top: 1px;
+ left: 1px;
+ padding: 1px 2px 1px 2px;
+ margin: 0px;
+}
+
+.news .content a:focus {
+ border: 1px solid #fff !important;
+ background-color: #ccc !important;
+ padding: 1px 2px 1px 2px;
+ margin: 0px;
+}
+
+/* --------------------------- Accomodations ----------------------- */
+
+/* boing boing */
+br {
+ clear: none !important;
+}
+
+/* engadget */
+h6 {
+ clear: left !important;
+}
+
+/* cadenhead */
+p.sourcecode {
+ font-family: monospace;
+ font-size: medium;
+ font-weight: bold;
+ border: 1px solid #ddd;
+ padding: 10px;
+ margin: 10px 20px 10px 20px;
+ background-color: #f8f8f8;
+ overflow: auto;
+}
+
+/* cadenhead */
+span.sourcecode {
+ font-family: monospace;
+ font-size: medium;
+ font-weight: bold;
+ font-size: large;
+ background-color: #f8f8f8;
+}
+
+/* hsivonen */
+ul p, ol p {
+ margin-top: 0.3em;
+ margin-bottom: 0.3em;
+}
+
+/* programmableweb */
+.imgRight {
+ float: right;
+}
+
+/* gizmodo */
+img.left {
+ float: left;
+}
+
+/* gizmodo */
+img.right {
+ float: right;
+}
+
+/* gizmodo */
+img.center {
+ display: block;
+ margin-left: auto;
+ margin-right: auto;
+}
+
+/* wikipedia */
+table {
+ width: auto !important;
+}
+
+/* del.icio.us */
+.delicious-tags {
+ font-size: x-small;
+ text-align: right;
+}
+
+/* musings */
+img.mathlogo, img.svglogo {
+ float: right;
+ border: 0;
+}
+
+math {
+ white-space: nowrap;
+}
+
+math[display=block] {
+ overflow: auto;
+}
+
+.eqno {
+ float: right;
+}
+
+/* sutor */
+img.post-img-right {
+ float:right;
+}
+
+/* niall */
+img.floatright {
+ float: right;
+}
+
+/* jason kolb */
+.FeaturedPost > li {
+ list-style-type: none;
+ background-color: #f8f8f8;
+}
+
+/* GigaOM */
+p img {
+ float: left;
+}
+
+/* Tantek */
+ul.tags,ul.tags li,h4.tags {
+ display:inline;
+ font-size: x-small
+}
+
+ul.tags a:link, ul.tags a:visited {
+ color:green
+}
+
+/* DiveIntoMark */
+.framed {
+ float: none;
+}
+
+/* ----------------------------- Footer ---------------------------- */
+
+#footer {
+ padding: 0px;
+ margin: 30px 0px 50px 50px;
+}
+
+#footer p {
+ padding: 2px 2px 2px 5px;
+ background-color: #ccc;
+ border-top: 1px solid #aaa;
+ border-bottom: 1px solid #aaa;
+ border-left: 1px solid #aaa;
+ letter-spacing: 0.15em;
+ text-transform: uppercase;
+ text-align: left;
+}
diff --git a/themes/asf/index.html.xslt b/themes/asf/index.html.xslt
new file mode 100644
index 0000000..5665700
--- /dev/null
+++ b/themes/asf/index.html.xslt
@@ -0,0 +1,153 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/themes/asf/personalize.js b/themes/asf/personalize.js
new file mode 100644
index 0000000..83db3a3
--- /dev/null
+++ b/themes/asf/personalize.js
@@ -0,0 +1,220 @@
+var entries = []; // list of news items
+
+var days = ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday",
+ "Friday", "Saturday"];
+var months = ["January", "February", "March", "April", "May", "June", "July",
+ "August", "September", "October", "November", "December"];
+
+// event complete: stop propagation of the event
+function stopPropagation(event) {
+ if (event.preventDefault) {
+ event.preventDefault();
+ event.stopPropagation();
+ } else {
+ event.returnValue = false;
+ }
+}
+
+// scroll back to the previous article
+function prevArticle(event) {
+ for (var i=entries.length; --i>=0;) {
+ if (entries[i].anchor.offsetTop < document.documentElement.scrollTop) {
+ window.location.hash=entries[i].anchor.id;
+ stopPropagation(event);
+ break;
+ }
+ }
+}
+
+// advance to the next article
+function nextArticle(event) {
+ for (var i=1; i document.documentElement.scrollTop) {
+ window.location.hash=entries[i].anchor.id;
+ stopPropagation(event);
+ break;
+ }
+ }
+}
+
+// process keypresses
+function navkey(event) {
+ var checkbox = document.getElementById('navkeys');
+ if (!checkbox || !checkbox.checked) return;
+
+ if (!event) event=window.event;
+ key=event.keyCode;
+
+ if (!document.documentElement) return;
+ if (!entries[0].anchor || !entries[0].anchor.offsetTop) return;
+
+ if (key == 'J'.charCodeAt(0)) nextArticle(event);
+ if (key == 'K'.charCodeAt(0)) prevArticle(event);
+}
+
+// create (or reset) a cookie
+function createCookie(name,value,days) {
+ if (days) {
+ var date = new Date();
+ date.setTime(date.getTime()+(days*24*60*60*1000));
+ var expires = "; expires="+date.toGMTString();
+ }
+ else expires = "";
+ document.cookie = name+"="+value+expires+"; path=/";
+}
+
+// read a cookie
+function readCookie(name) {
+ var nameEQ = name + "=";
+ var ca = document.cookie.split(';');
+ for(var i=0;i < ca.length;i++) {
+ var c = ca[i];
+ while (c.charAt(0)==' ') c = c.substring(1,c.length);
+ if (c.indexOf(nameEQ) == 0) return c.substring(nameEQ.length,c.length);
+ }
+ return null;
+}
+
+// each time the value of the option changes, update the cookie
+function selectOption() {
+ var checkbox = document.getElementById('navkeys');
+ if (!checkbox) return;
+ createCookie("navkeys", checkbox.checked?'true':'false', 365);
+}
+
+// add navkeys option to sidebar
+function addOption(event) {
+ if (entries.length > 1 && entries[entries.length-1].parent.offsetTop > 0) {
+ var sidebar = document.getElementById('sidebar');
+ if (!sidebar) return;
+
+ for (var i=entries.length; --i>=0;) {
+ var a = entries[i].anchor = document.createElement('a');
+ a.id = "news-" + i;
+ entries[i].parent.insertBefore(a, entries[i].parent.firstChild);
+ }
+
+ var h2 = document.createElement('h2');
+ h2.appendChild(document.createTextNode('Options'));
+ sidebar.appendChild(h2);
+
+ var form = document.createElement('form');
+ var p = document.createElement('p');
+ var input = document.createElement('input');
+ input.type = "checkbox";
+ input.id = "navkeys";
+ p.appendChild(input);
+ var a = document.createElement('a');
+ a.title = "Navigate entries";
+ a.appendChild(document.createTextNode('Enable '));
+ var code = document.createElement('code');
+ code.appendChild(document.createTextNode('J'));
+ a.appendChild(code);
+ a.appendChild(document.createTextNode(' and '));
+ code = document.createElement('code');
+ code.appendChild(document.createTextNode('K'));
+ a.appendChild(code);
+ a.appendChild(document.createTextNode(' keys'));
+ p.appendChild(a);
+ form.appendChild(p);
+ sidebar.appendChild(form);
+
+ var cookie = readCookie("navkeys");
+ if (cookie && cookie == 'true') input.checked = true;
+ input.onclick = selectOption;
+ document.onkeydown = navkey;
+ }
+}
+
+// convert date to local time
+var localere = /^(\w+) (\d+) (\w+) \d+ 0?(\d\d?:\d\d):\d\d ([AP]M) (EST|EDT|CST|CDT|MST|MDT|PST|PDT)/;
+function localizeDate(element) {
+ var date = new Date();
+ date.setTime(Date.parse(element.innerHTML + " GMT"));
+
+ var local = date.toLocaleString();
+ var match = local.match(localere);
+ if (match) {
+ element.innerHTML = match[4] + ' ' + match[5].toLowerCase();
+ element.title = match[6] + " \u2014 " +
+ match[1] + ', ' + match[3] + ' ' + match[2];
+ return days[date.getDay()] + ', ' + months[date.getMonth()] + ' ' +
+ date.getDate() + ', ' + date.getFullYear();
+ } else {
+ element.title = element.innerHTML + ' GMT';
+ element.innerHTML = local;
+ return days[date.getDay()] + ', ' + date.getDate() + ' ' +
+ months[date.getMonth()] + ' ' + date.getFullYear();
+ }
+
+}
+
+// find entries (and localizeDates)
+function findEntries() {
+
+ var span = document.getElementsByTagName('span');
+
+ for (var i=0; i
+
+
+
+
+
+
+
+ no
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/themes/common/foafroll.xml.xslt b/themes/common/foafroll.xml.xslt
new file mode 100644
index 0000000..7781eac
--- /dev/null
+++ b/themes/common/foafroll.xml.xslt
@@ -0,0 +1,39 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/themes/common/images/feed-icon-10x10.png b/themes/common/images/feed-icon-10x10.png
new file mode 100644
index 0000000..cc869bc
Binary files /dev/null and b/themes/common/images/feed-icon-10x10.png differ
diff --git a/themes/common/images/foaf.png b/themes/common/images/foaf.png
new file mode 100644
index 0000000..0fea5ba
Binary files /dev/null and b/themes/common/images/foaf.png differ
diff --git a/themes/common/images/opml.png b/themes/common/images/opml.png
new file mode 100644
index 0000000..3f18190
Binary files /dev/null and b/themes/common/images/opml.png differ
diff --git a/themes/common/images/planet.png b/themes/common/images/planet.png
new file mode 100644
index 0000000..9606a0c
Binary files /dev/null and b/themes/common/images/planet.png differ
diff --git a/themes/common/opml.xml.xslt b/themes/common/opml.xml.xslt
new file mode 100644
index 0000000..afa5d57
--- /dev/null
+++ b/themes/common/opml.xml.xslt
@@ -0,0 +1,25 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+