├── planet_sage ├── log.txt ├── opml.xml.tmpl ├── rss20.xml.tmpl ├── foafroll.xml.tmpl ├── rss10.xml.tmpl ├── atom.xml.tmpl ├── index.html.tmpl └── config.ini ├── planet-2.0 ├── planet │ ├── tests │ │ ├── __init__.py │ │ ├── data │ │ │ ├── simple.tmpl │ │ │ └── simple2.tmpl │ │ ├── test_channel.py │ │ ├── test_main.py │ │ ├── test_sub.py │ │ └── test_sanitize.py │ ├── atomstyler.py │ ├── cache.py │ ├── compat_logging │ │ ├── config.py │ │ └── handlers.py │ ├── timeoutsocket.py │ ├── sanitize.py │ └── __init__.py ├── NEWS ├── .gitignore ├── AUTHORS ├── examples │ ├── output │ │ ├── images │ │ │ ├── edd.png │ │ │ ├── jdub.png │ │ │ ├── logo.png │ │ │ ├── opml.png │ │ │ ├── thom.png │ │ │ ├── keybuk.png │ │ │ ├── planet.png │ │ │ ├── evolution.png │ │ │ └── feed-icon-10x10.png │ │ └── planet.css │ ├── opml.xml.tmpl │ ├── rss20.xml.tmpl │ ├── foafroll.xml.tmpl │ ├── rss10.xml.tmpl │ ├── atom.xml.tmpl │ ├── basic │ │ ├── index.html.tmpl │ │ └── config.ini │ └── fancy │ │ ├── config.ini │ │ └── index.html.tmpl ├── PKG-INFO ├── runtests.py ├── THANKS ├── README ├── setup.py ├── TODO ├── planet.py ├── INSTALL ├── planet-cache.py └── LICENCE ├── README.md ├── .gitignore └── update /planet_sage/log.txt: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /planet-2.0/planet/tests/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /planet-2.0/NEWS: -------------------------------------------------------------------------------- 1 | Planet 1.0 2 | ---------- 3 | 4 | * First release! 5 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Planet SageMath Blog 2 | 3 | http://planet.sagemath.org 4 | 5 | -------------------------------------------------------------------------------- /planet-2.0/.gitignore: -------------------------------------------------------------------------------- 1 | examples/*.tmplc 2 | planet/tests/*.tmplc 3 | cache/ 4 | -------------------------------------------------------------------------------- /planet-2.0/AUTHORS: -------------------------------------------------------------------------------- 1 | Scott James Remnant 2 | Jeff Waugh 3 | -------------------------------------------------------------------------------- /planet-2.0/planet/tests/data/simple.tmpl: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /planet-2.0/planet/tests/data/simple2.tmpl: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /planet-2.0/examples/output/images/edd.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sagemath/planet/master/planet-2.0/examples/output/images/edd.png -------------------------------------------------------------------------------- /planet-2.0/examples/output/images/jdub.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sagemath/planet/master/planet-2.0/examples/output/images/jdub.png -------------------------------------------------------------------------------- /planet-2.0/examples/output/images/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sagemath/planet/master/planet-2.0/examples/output/images/logo.png -------------------------------------------------------------------------------- /planet-2.0/examples/output/images/opml.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sagemath/planet/master/planet-2.0/examples/output/images/opml.png -------------------------------------------------------------------------------- /planet-2.0/examples/output/images/thom.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sagemath/planet/master/planet-2.0/examples/output/images/thom.png -------------------------------------------------------------------------------- /planet-2.0/examples/output/images/keybuk.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sagemath/planet/master/planet-2.0/examples/output/images/keybuk.png -------------------------------------------------------------------------------- /planet-2.0/examples/output/images/planet.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sagemath/planet/master/planet-2.0/examples/output/images/planet.png -------------------------------------------------------------------------------- /planet-2.0/examples/output/images/evolution.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sagemath/planet/master/planet-2.0/examples/output/images/evolution.png -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | output/ 2 | .hg/ 3 | log 4 | planet_sage/cache 5 | planet_sage/cache.bak 6 | planet_sage/output 7 | *.tmplc 8 | *.pyc 9 | *.pyo 10 | -------------------------------------------------------------------------------- /planet-2.0/examples/output/images/feed-icon-10x10.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sagemath/planet/master/planet-2.0/examples/output/images/feed-icon-10x10.png -------------------------------------------------------------------------------- /planet-2.0/PKG-INFO: -------------------------------------------------------------------------------- 1 | Metadata-Version: 1.0 2 | Name: planet 3 | Version: 2.0 4 | Summary: The Planet Feed Aggregator 5 | Home-page: http://www.planetplanet.org/ 6 | Author: Planet Developers 7 | Author-email: devel@lists.planetplanet.org 8 | License: Python 9 | Description: UNKNOWN 10 | Platform: UNKNOWN 11 | -------------------------------------------------------------------------------- /planet-2.0/runtests.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | import glob, trace, unittest 3 | 4 | # find all of the planet test modules 5 | modules = map(trace.fullmodname, glob.glob('planet/tests/test_*.py')) 6 | 7 | # load all of the tests into a suite 8 | suite = unittest.TestLoader().loadTestsFromNames(modules) 9 | 10 | # run test suite 11 | unittest.TextTestRunner().run(suite) 12 | -------------------------------------------------------------------------------- /planet-2.0/THANKS: -------------------------------------------------------------------------------- 1 | Patches and Bug Fixes 2 | --------------------- 3 | 4 | Chris Dolan - fixes, exclude filtering, duplicate culling 5 | David Edmondson - filtering 6 | Lucas Nussbaum - locale configuration 7 | David Pashley - cache code profiling and recursion fixing 8 | Gediminas Paulauskas - days per page 9 | 10 | 11 | Spycyroll Maintainers 12 | --------------------- 13 | 14 | Vattekkat Satheesh Babu 15 | Richard Jones 16 | Garth Kidd 17 | Eliot Landrum 18 | Bryan Richard 19 | -------------------------------------------------------------------------------- /planet-2.0/README: -------------------------------------------------------------------------------- 1 | Planet 2 | ------ 3 | 4 | Planet is a flexible feed aggregator. It downloads news feeds published by 5 | web sites and aggregates their content together into a single combined feed, 6 | latest news first. 7 | 8 | It uses Mark Pilgrim's Universal Feed Parser to read from RDF, RSS and Atom 9 | feeds; and Tomas Styblo's templating engine to output static files in any 10 | format you can dream up. 11 | 12 | Keywords: feed, blog, aggregator, RSS, RDF, Atom, OPML, Python 13 | -------------------------------------------------------------------------------- /update: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # change to script's directory 3 | cd `dirname "$0"` 4 | 5 | export GIT_AUTHOR_NAME="SMC Management Task" 6 | export GIT_AUTHOR_EMAIL="`whoami`@`hostname`" 7 | 8 | # now into the target dir 9 | cd planet_sage 10 | 11 | # this calls the update script (via crontab!) and files end up in "output" 12 | python ../planet-2.0/planet.py config.ini 1> ../log/update.out 2> ../log/update.error 13 | 14 | cd ../output 15 | 16 | git fetch origin 17 | git checkout -B gh-pages 18 | git reset --soft origin/gh-pages 19 | git add -A -- . 20 | git commit -m "auto publish `date -u --rfc-3339=seconds`" 21 | git push 22 | -------------------------------------------------------------------------------- /planet_sage/opml.xml.tmpl: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | <TMPL_VAR name ESCAPE="HTML"> 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | " xmlUrl="" title="" htmlUrl="" /> 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /planet-2.0/examples/opml.xml.tmpl: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | <TMPL_VAR name ESCAPE="HTML"> 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | " xmlUrl="" title="" htmlUrl="" /> 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /planet-2.0/setup.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """The Planet Feed Aggregator""" 3 | 4 | import os 5 | from distutils.core import setup 6 | 7 | from planet import __version__ as VERSION 8 | from planet import __license__ as LICENSE 9 | 10 | if 'PLANET_VERSION' in os.environ.keys(): 11 | VERSION = os.environ['PLANET_VERSION'] 12 | 13 | setup(name="planet", 14 | version=VERSION, 15 | description="The Planet Feed Aggregator", 16 | author="Planet Developers", 17 | author_email="devel@lists.planetplanet.org", 18 | url="http://www.planetplanet.org/", 19 | license=LICENSE, 20 | packages=["planet", "planet.compat_logging", "planet.tests"], 21 | scripts=["planet.py", "planet-cache.py", "runtests.py"], 22 | ) 23 | -------------------------------------------------------------------------------- /planet-2.0/TODO: -------------------------------------------------------------------------------- 1 | TODO 2 | ==== 3 | 4 | * Expire feed history 5 | 6 | The feed cache doesn't currently expire old entries, so could get 7 | large quite rapidly. We should probably have a config setting for 8 | the cache expiry, the trouble is some channels might need a longer 9 | or shorter one than others. 10 | 11 | * Allow display normalisation to specified timezone 12 | 13 | Some Planet admins would like their feed to be displayed in the local 14 | timezone, instead of UTC. 15 | 16 | * Support OPML and foaf subscriptions 17 | 18 | This might be a bit invasive, but I want to be able to subscribe to OPML 19 | and FOAF files, and see each feed as if it were subscribed individually. 20 | Perhaps we can do this with a two-pass configuration scheme, first to pull 21 | the static configs, second to go fetch and generate the dynamic configs. 22 | The more I think about it, the less invasive it sounds. Hmm. 23 | -------------------------------------------------------------------------------- /planet_sage/rss20.xml.tmpl: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | <TMPL_VAR name> 6 | 7 | en 8 | - 9 | 10 | 11 | 12 | <TMPL_VAR channel_name ESCAPE="HTML"><TMPL_IF title>: <TMPL_VAR title_plain ESCAPE="HTML"></TMPL_IF> 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | () 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /planet-2.0/examples/rss20.xml.tmpl: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | <TMPL_VAR name> 6 | 7 | en 8 | - 9 | 10 | 11 | 12 | <TMPL_VAR channel_name ESCAPE="HTML"><TMPL_IF title>: <TMPL_VAR title_plain ESCAPE="HTML"></TMPL_IF> 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | () 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /planet_sage/foafroll.xml.tmpl: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 | 11 | 12 | " /> 13 | 14 | 15 | 16 | 17 | 18 | 19 | "> 20 | 21 | 22 | " /> 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /planet-2.0/examples/foafroll.xml.tmpl: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 | 11 | 12 | " /> 13 | 14 | 15 | 16 | 17 | 18 | 19 | "> 20 | 21 | 22 | " /> 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /planet-2.0/planet/tests/test_channel.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | import unittest 4 | import planet 5 | import tempfile 6 | import ConfigParser 7 | 8 | class FakePlanet: 9 | """ 10 | A dummy Planet object that's enough to fool the 11 | Channel.__init__ method 12 | """ 13 | 14 | def __init__(self): 15 | self.cache_directory = tempfile.gettempdir() 16 | self.config = ConfigParser.ConfigParser() 17 | 18 | class FeedInformationTest(unittest.TestCase): 19 | """ 20 | Test the Channel.feed_information method 21 | """ 22 | 23 | def setUp(self): 24 | self.url = 'URL' 25 | self.changed_url = 'Changed URL' 26 | self.channel = planet.Channel(FakePlanet(), self.url) 27 | 28 | def test_unchangedurl(self): 29 | self.assertEqual(self.channel.feed_information(), '<%s>' % self.url) 30 | 31 | def test_changedurl(self): 32 | # change the URL directly 33 | self.channel.url = self.changed_url 34 | self.assertEqual(self.channel.feed_information(), 35 | "<%s> (formerly <%s>)" % (self.changed_url, self.url)) 36 | 37 | if __name__ == '__main__': 38 | unittest.main() 39 | -------------------------------------------------------------------------------- /planet_sage/rss10.xml.tmpl: -------------------------------------------------------------------------------- 1 | 2 | 9 | "> 10 | <TMPL_VAR name ESCAPE="HTML"> 11 | 12 | - 13 | 14 | 15 | 16 | 17 | " /> 18 | 19 | 20 | 21 | 22 | 23 | 24 | "> 25 | <TMPL_VAR channel_name ESCAPE="HTML"><TMPL_IF title>: <TMPL_VAR title_plain ESCAPE="HTML"></TMPL_IF> 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /planet-2.0/examples/rss10.xml.tmpl: -------------------------------------------------------------------------------- 1 | 2 | 9 | "> 10 | <TMPL_VAR name ESCAPE="HTML"> 11 | 12 | - 13 | 14 | 15 | 16 | 17 | " /> 18 | 19 | 20 | 21 | 22 | 23 | 24 | "> 25 | <TMPL_VAR channel_name ESCAPE="HTML"><TMPL_IF title>: <TMPL_VAR title_plain ESCAPE="HTML"></TMPL_IF> 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /planet_sage/atom.xml.tmpl: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | <TMPL_VAR name> 5 | "/> 6 | "/> 7 | 8 | 9 | 10 | 11 | 12 | xml:lang=""> 13 | xml:lang="<TMPL_VAR title_language>"</TMPL_IF>><TMPL_VAR title ESCAPE="HTML"> 14 | "/> 15 | 16 | 17 | xml:lang=""> 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | <TMPL_VAR channel_title ESCAPE="HTML"> 39 | 40 | <TMPL_VAR channel_name ESCAPE="HTML"> 41 | 42 | 43 | 44 | 45 | "/> 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | -------------------------------------------------------------------------------- /planet-2.0/examples/atom.xml.tmpl: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | <TMPL_VAR name> 5 | "/> 6 | "/> 7 | 8 | 9 | 10 | 11 | 12 | xml:lang=""> 13 | xml:lang="<TMPL_VAR title_language>"</TMPL_IF>><TMPL_VAR title ESCAPE="HTML"> 14 | "/> 15 | 16 | 17 | xml:lang=""> 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | <TMPL_VAR channel_title ESCAPE="HTML"> 39 | 40 | <TMPL_VAR channel_name ESCAPE="HTML"> 41 | 42 | 43 | 44 | 45 | "/> 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | -------------------------------------------------------------------------------- /planet-2.0/examples/output/planet.css: -------------------------------------------------------------------------------- 1 | body { 2 | border-right: 1px solid black; 3 | margin-right: 200px; 4 | 5 | padding-left: 20px; 6 | padding-right: 20px; 7 | } 8 | 9 | h1 { 10 | margin-top: 0px; 11 | padding-top: 20px; 12 | 13 | font-family: "Bitstream Vera Sans", sans-serif; 14 | font-weight: normal; 15 | letter-spacing: -2px; 16 | text-transform: lowercase; 17 | text-align: right; 18 | 19 | color: grey; 20 | } 21 | 22 | h2 { 23 | font-family: "Bitstream Vera Sans", sans-serif; 24 | font-weight: normal; 25 | color: #200080; 26 | 27 | margin-left: -20px; 28 | } 29 | 30 | h3 { 31 | font-family: "Bitstream Vera Sans", sans-serif; 32 | font-weight: normal; 33 | 34 | background-color: #a0c0ff; 35 | border: 1px solid #5080b0; 36 | 37 | padding: 4px; 38 | } 39 | 40 | h3 a { 41 | text-decoration: none; 42 | color: inherit; 43 | } 44 | 45 | h4 { 46 | font-family: "Bitstream Vera Sans", sans-serif; 47 | font-weight: bold; 48 | } 49 | 50 | h4 a { 51 | text-decoration: none; 52 | color: inherit; 53 | } 54 | 55 | img.face { 56 | float: right; 57 | margin-top: -3em; 58 | } 59 | 60 | .entry { 61 | margin-bottom: 2em; 62 | } 63 | 64 | .entry .date { 65 | font-family: "Bitstream Vera Sans", sans-serif; 66 | color: grey; 67 | } 68 | 69 | .entry .date a { 70 | text-decoration: none; 71 | color: inherit; 72 | } 73 | 74 | .sidebar { 75 | position: absolute; 76 | top: 0px; 77 | right: 0px; 78 | width: 200px; 79 | 80 | margin-left: 0px; 81 | margin-right: 0px; 82 | padding-right: 0px; 83 | 84 | padding-top: 20px; 85 | padding-left: 0px; 86 | 87 | font-family: "Bitstream Vera Sans", sans-serif; 88 | font-size: 85%; 89 | } 90 | 91 | .sidebar h2 { 92 | font-size: 110%; 93 | font-weight: bold; 94 | color: black; 95 | 96 | padding-left: 5px; 97 | margin-left: 0px; 98 | } 99 | 100 | .sidebar ul { 101 | padding-left: 1em; 102 | margin-left: 0px; 103 | 104 | list-style-type: none; 105 | } 106 | 107 | .sidebar ul li:hover { 108 | color: grey; 109 | } 110 | 111 | .sidebar ul li a { 112 | text-decoration: none; 113 | } 114 | 115 | .sidebar ul li a:hover { 116 | text-decoration: underline; 117 | } 118 | 119 | .sidebar ul li a img { 120 | border: 0; 121 | } 122 | 123 | .sidebar p { 124 | border-top: 1px solid grey; 125 | margin-top: 30px; 126 | padding-top: 10px; 127 | 128 | padding-left: 5px; 129 | } 130 | 131 | .sidebar .message { 132 | cursor: help; 133 | border-bottom: 1px dashed red; 134 | } 135 | 136 | .sidebar a.message:hover { 137 | cursor: help; 138 | background-color: #ff0000; 139 | color: #ffffff !important; 140 | text-decoration: none !important; 141 | } 142 | 143 | a:hover { 144 | text-decoration: underline !important; 145 | color: blue !important; 146 | } 147 | -------------------------------------------------------------------------------- /planet-2.0/planet/tests/test_main.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | import os, sys, shutil, errno, unittest 3 | from ConfigParser import ConfigParser 4 | from StringIO import StringIO 5 | import planet 6 | 7 | class MainTest(unittest.TestCase): 8 | 9 | def test_minimal(self): 10 | configp = ConfigParser() 11 | my_planet = planet.Planet(configp) 12 | my_planet.run("Planet Name", "http://example.com", []) 13 | 14 | def test_onefeed(self): 15 | configp = ConfigParser() 16 | configp.readfp(StringIO("""[http://www.example.com/] 17 | name = Mary 18 | """)) 19 | my_planet = planet.Planet(configp) 20 | my_planet.run("Planet Name", "http://example.com", [], True) 21 | 22 | 23 | def test_generateall(self): 24 | configp = ConfigParser() 25 | configp.readfp(StringIO("""[http://www.example.com/] 26 | name = Mary 27 | """)) 28 | my_planet = planet.Planet(configp) 29 | my_planet.run("Planet Name", "http://example.com", [], True) 30 | basedir = os.path.join(os.path.dirname(os.path.abspath(sys.modules[__name__].__file__)), 'data') 31 | os.mkdir(self.output_dir) 32 | t_file_names = ['simple', 'simple2'] 33 | self._remove_cached_templates(basedir, t_file_names) 34 | t_files = [os.path.join(basedir, t_file) + '.tmpl' for t_file in t_file_names] 35 | my_planet.generate_all_files(t_files, "Planet Name", 36 | 'http://example.com/', 'http://example.com/feed/', 'Mary', 'mary@example.com') 37 | for file_name in t_file_names: 38 | name = os.path.join(self.output_dir, file_name) 39 | content = file(name).read() 40 | self.assertEqual(content, 'Mary\n') 41 | 42 | def _remove_cached_templates(self, basedir, template_files): 43 | """ 44 | Remove the .tmplc files and force them to be rebuilt. 45 | 46 | This is required mainly so that the tests don't fail in mysterious ways in 47 | directories that have been moved, eg 'branches/my-branch' to 48 | 'branches/mysterious-branch' -- the .tmplc files seem to remember their full 49 | path 50 | """ 51 | for file in template_files: 52 | path = os.path.join(basedir, file + '.tmplc') 53 | try: 54 | os.remove(path) 55 | except OSError, e: 56 | # we don't care about the file not being there, we care about 57 | # everything else 58 | if e.errno != errno.ENOENT: 59 | raise 60 | 61 | def setUp(self): 62 | super(MainTest, self).setUp() 63 | self.output_dir = 'output' 64 | 65 | def tearDown(self): 66 | super(MainTest, self).tearDown() 67 | shutil.rmtree(self.output_dir, ignore_errors = True) 68 | shutil.rmtree('cache', ignore_errors = True) 69 | 70 | if __name__ == '__main__': 71 | unittest.main() 72 | -------------------------------------------------------------------------------- /planet-2.0/examples/basic/index.html.tmpl: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | ### Planet HTML template. 5 | ### 6 | ### This is intended to demonstrate and document Planet's templating 7 | ### facilities, and at the same time provide a good base for you to 8 | ### modify into your own design. 9 | ### 10 | ### The output's a bit boring though, if you're after less documentation 11 | ### and more instant gratification, there's an example with a much 12 | ### prettier output in the fancy-examples/ directory of the Planet source. 13 | 14 | ### Lines like this are comments, and are automatically removed by the 15 | ### templating engine before processing. 16 | 17 | 18 | ### Planet makes a large number of variables available for your templates. 19 | ### See INSTALL for the complete list. The raw value can be placed in your 20 | ### output file using . We'll put the name of our 21 | ### Planet in the page title and again in an h1. 22 | 23 | 24 | <TMPL_VAR name> 25 | 26 | "> 27 | 28 | 29 | 30 |

31 | 32 | ### One of the two loops available is the Channels loop. This allows you 33 | ### to easily create a list of subscriptions, which is exactly what we'll do 34 | ### here. 35 | 36 | ### Note that we can also expand variables inside HTML tags, but we need 37 | ### to be cautious and HTML-escape any illegal characters using the form 38 | ### 39 | 40 |
41 |

Subscriptions

42 | 47 |
48 | 49 | ### The other loop is the Items loop, which will get iterated for each 50 | ### news item. 51 | 52 | 53 | 54 | ### Visually distinguish articles from different days by checking for 55 | ### the new_date flag. This demonstrates the ... 56 | ### check. 57 | 58 | 59 |

60 |
61 | 62 | ### Group consecutive articles by the same author together by checking 63 | ### for the new_channel flag. 64 | 65 | 66 |

" title="">

67 |
68 | 69 | 70 | 71 |

">

72 |
73 |

74 | 75 |

76 |

77 | ">by at 78 |

79 |
80 | 81 |
82 |

83 | Powered by Planet!
84 | Last updated: 85 |

86 | 87 | 88 | 89 | -------------------------------------------------------------------------------- /planet-2.0/examples/basic/config.ini: -------------------------------------------------------------------------------- 1 | # Planet configuration file 2 | 3 | # Every planet needs a [Planet] section 4 | [Planet] 5 | # name: Your planet's name 6 | # link: Link to the main page 7 | # owner_name: Your name 8 | # owner_email: Your e-mail address 9 | name = Planet Zog 10 | link = http://www.planet.zog/ 11 | owner_name = Zig The Alien 12 | owner_email = zig@planet.zog 13 | 14 | # cache_directory: Where cached feeds are stored 15 | # new_feed_items: Number of items to take from new feeds 16 | # log_level: One of DEBUG, INFO, WARNING, ERROR or CRITICAL 17 | cache_directory = examples/cache 18 | new_feed_items = 2 19 | log_level = DEBUG 20 | 21 | # template_files: Space-separated list of output template files 22 | template_files = examples/basic/index.html.tmpl examples/atom.xml.tmpl examples/rss20.xml.tmpl examples/rss10.xml.tmpl examples/opml.xml.tmpl examples/foafroll.xml.tmpl 23 | 24 | # The following provide defaults for each template: 25 | # output_dir: Directory to place output files 26 | # items_per_page: How many items to put on each page 27 | # days_per_page: How many complete days of posts to put on each page 28 | # This is the absolute, hard limit (over the item limit) 29 | # date_format: strftime format for the default 'date' template variable 30 | # new_date_format: strftime format for the 'new_date' template variable 31 | # encoding: output encoding for the file, Python 2.3+ users can use the 32 | # special "xml" value to output ASCII with XML character references 33 | # locale: locale to use for (e.g.) strings in dates, default is taken from your 34 | # system. You can specify more locales separated by ':', planet will 35 | # use the first available one 36 | output_dir = examples/output 37 | items_per_page = 60 38 | days_per_page = 0 39 | date_format = %B %d, %Y %I:%M %p 40 | new_date_format = %B %d, %Y 41 | encoding = utf-8 42 | # locale = C 43 | 44 | 45 | # To define a different value for a particular template you may create 46 | # a section with the same name as the template file's filename (as given 47 | # in template_files). 48 | # 49 | # [examples/rss10.xml.tmpl] 50 | # items_per_page = 30 51 | # encoding = xml 52 | 53 | 54 | # Any other section defines a feed to subscribe to. The section title 55 | # (in the []s) is the URI of the feed itself. A section can also be 56 | # have any of the following options: 57 | # 58 | # name: Name of the feed (defaults to the title found in the feed) 59 | # 60 | # Additionally any other option placed here will be available in 61 | # the template (prefixed with channel_ for the Items loop). You can 62 | # define defaults for these in a [DEFAULT] section, for example 63 | # Planet Debian uses the following to define faces: 64 | # 65 | # [DEFAULT] 66 | # facewidth = 64 67 | # faceheight = 64 68 | # 69 | # [http://www.blog.com/rss] 70 | # face = foo.png 71 | # faceheight = 32 72 | # 73 | # The facewidth of the defined blog defaults to 64. 74 | 75 | [http://www.netsplit.com/blog/index.rss] 76 | name = Scott James Remnant 77 | 78 | [http://www.gnome.org/~jdub/blog/?flav=rss] 79 | name = Jeff Waugh 80 | 81 | [http://usefulinc.com/edd/blog/rss91] 82 | name = Edd Dumbill 83 | 84 | [http://blog.clearairturbulence.org/?flav=rss] 85 | name = Thom May 86 | 87 | [http://www.hadess.net/diary.rss] 88 | name = Bastien Nocera 89 | -------------------------------------------------------------------------------- /planet-2.0/planet/tests/test_sub.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | import os, glob, unittest 3 | from ConfigParser import ConfigParser 4 | from StringIO import StringIO 5 | import planet 6 | 7 | class SubTest(unittest.TestCase): 8 | 9 | def setUp(self): 10 | planet.logging.basicConfig() 11 | self.config = ConfigParser() 12 | self.config.add_section('Planet') 13 | self.config.set('Planet', 'cache_directory', 'planet/tests/data/cache') 14 | self.my_planet = planet.Planet(self.config) 15 | 16 | def tearDown(self): 17 | for file in glob.glob('planet/tests/data/cache/*'): 18 | os.unlink(file) 19 | os.removedirs('planet/tests/data/cache') 20 | 21 | def test_fetch(self): 22 | self.config.readfp(StringIO("""[planet/tests/data/before.atom] 23 | name = Test Feed 24 | """)) 25 | self.my_planet.run("test", "http://example.com", [], 0) 26 | channels, channels_list = self.my_planet.gather_channel_info() 27 | self.assertEqual(len(channels_list), 1) 28 | self.assertEqual(channels_list[0]['configured_url'], 29 | 'planet/tests/data/before.atom') 30 | 31 | items_list = self.my_planet.gather_items_info(channels) 32 | self.assertEqual(len(items_list), 1) 33 | self.assertEqual(items_list[0]['summary'],'Some text.') 34 | self.assertEqual(items_list[0]['date_iso'],'2003-12-13T18:30:02+00:00') 35 | 36 | # this test is actually per the Atom spec definition of 'updated' 37 | def test_update_with_new_date(self): 38 | self.config.readfp(StringIO("""[planet/tests/data/before.atom] 39 | name = Test Feed 40 | """)) 41 | self.my_planet.run("test", "http://example.com", [], 0) 42 | channels, channels_list = self.my_planet.gather_channel_info() 43 | 44 | channel = channels.keys()[0] 45 | channel.url = 'planet/tests/data/after.atom' 46 | os.link('planet/tests/data/cache/planet,tests,data,before.atom', 47 | 'planet/tests/data/cache/planet,tests,data,after.atom') 48 | channel.update() 49 | 50 | items_list = self.my_planet.gather_items_info(channels) 51 | self.assertEqual(len(items_list), 1) 52 | self.assertEqual(items_list[0]['summary'],'Updated text.') 53 | self.assertEqual(items_list[0]['date_iso'],'2006-05-21T18:54:02+00:00') 54 | 55 | def test_update_with_no_date(self): 56 | self.config.readfp(StringIO("""[planet/tests/data/before.rss] 57 | name = Test Feed 58 | """)) 59 | self.my_planet.run("test", "http://example.com", [], 0) 60 | 61 | channels, channels_list = self.my_planet.gather_channel_info() 62 | channel = channels.keys()[0] 63 | item=channel._items.values()[0] 64 | item.set_as_date('date',(2003, 12, 13, 18, 30, 2, 5, 347, 0)) 65 | 66 | channel.url = 'planet/tests/data/after.rss' 67 | os.link('planet/tests/data/cache/planet,tests,data,before.rss', 68 | 'planet/tests/data/cache/planet,tests,data,after.rss') 69 | items_list = self.my_planet.gather_items_info(channels) 70 | self.assertEqual(items_list[0]['date_iso'],'2003-12-13T18:30:02+00:00') 71 | channel.update() 72 | 73 | items_list = self.my_planet.gather_items_info(channels) 74 | self.assertEqual(len(items_list), 1) 75 | self.assertEqual(items_list[0]['summary'],'Updated text.') 76 | self.assertEqual(items_list[0]['date_iso'],'2003-12-13T18:30:02+00:00') 77 | 78 | if __name__ == '__main__': 79 | unittest.main() 80 | -------------------------------------------------------------------------------- /planet-2.0/examples/fancy/config.ini: -------------------------------------------------------------------------------- 1 | # Planet configuration file 2 | # 3 | # This illustrates some of Planet's fancier features with example. 4 | 5 | # Every planet needs a [Planet] section 6 | [Planet] 7 | # name: Your planet's name 8 | # link: Link to the main page 9 | # owner_name: Your name 10 | # owner_email: Your e-mail address 11 | name = Planet Schmanet 12 | link = http://planet.schmanet.janet/ 13 | owner_name = Janet Weiss 14 | owner_email = janet@slut.sex 15 | 16 | # cache_directory: Where cached feeds are stored 17 | # new_feed_items: Number of items to take from new feeds 18 | # log_level: One of DEBUG, INFO, WARNING, ERROR or CRITICAL 19 | # feed_timeout: number of seconds to wait for any given feed 20 | cache_directory = examples/cache 21 | new_feed_items = 2 22 | log_level = DEBUG 23 | feed_timeout = 20 24 | 25 | # template_files: Space-separated list of output template files 26 | template_files = examples/fancy/index.html.tmpl examples/atom.xml.tmpl examples/rss20.xml.tmpl examples/rss10.xml.tmpl examples/opml.xml.tmpl examples/foafroll.xml.tmpl 27 | 28 | # The following provide defaults for each template: 29 | # output_dir: Directory to place output files 30 | # items_per_page: How many items to put on each page 31 | # days_per_page: How many complete days of posts to put on each page 32 | # This is the absolute, hard limit (over the item limit) 33 | # date_format: strftime format for the default 'date' template variable 34 | # new_date_format: strftime format for the 'new_date' template variable 35 | # encoding: output encoding for the file, Python 2.3+ users can use the 36 | # special "xml" value to output ASCII with XML character references 37 | # locale: locale to use for (e.g.) strings in dates, default is taken from your 38 | # system. You can specify more locales separated by ':', planet will 39 | # use the first available one 40 | output_dir = examples/output 41 | items_per_page = 60 42 | days_per_page = 0 43 | date_format = %B %d, %Y %I:%M %p 44 | new_date_format = %B %d, %Y 45 | encoding = utf-8 46 | # locale = C 47 | 48 | 49 | # To define a different value for a particular template you may create 50 | # a section with the same name as the template file's filename (as given 51 | # in template_files). 52 | 53 | # Provide no more than 7 days articles on the front page 54 | [examples/fancy/index.html.tmpl] 55 | days_per_page = 7 56 | 57 | # If non-zero, all feeds which have not been updated in the indicated 58 | # number of days will be marked as inactive 59 | activity_threshold = 0 60 | 61 | 62 | # Options placed in the [DEFAULT] section provide defaults for the feed 63 | # sections. Placing a default here means you only need to override the 64 | # special cases later. 65 | [DEFAULT] 66 | # Hackergotchi default size. 67 | # If we want to put a face alongside a feed, and it's this size, we 68 | # can omit these variables. 69 | facewidth = 65 70 | faceheight = 85 71 | 72 | 73 | # Any other section defines a feed to subscribe to. The section title 74 | # (in the []s) is the URI of the feed itself. A section can also be 75 | # have any of the following options: 76 | # 77 | # name: Name of the feed (defaults to the title found in the feed) 78 | # 79 | # Additionally any other option placed here will be available in 80 | # the template (prefixed with channel_ for the Items loop). We use 81 | # this trick to make the faces work -- this isn't something Planet 82 | # "natively" knows about. Look at fancy-examples/index.html.tmpl 83 | # for the flip-side of this. 84 | 85 | [http://www.netsplit.com/blog/index.rss] 86 | name = Scott James Remnant 87 | face = keybuk.png 88 | # pick up the default facewidth and faceheight 89 | 90 | [http://www.gnome.org/~jdub/blog/?flav=rss] 91 | name = Jeff Waugh 92 | face = jdub.png 93 | facewidth = 70 94 | faceheight = 74 95 | 96 | [http://usefulinc.com/edd/blog/rss91] 97 | name = Edd Dumbill 98 | face = edd.png 99 | facewidth = 62 100 | faceheight = 80 101 | 102 | [http://blog.clearairturbulence.org/?flav=rss] 103 | name = Thom May 104 | face = thom.png 105 | # pick up the default faceheight only 106 | facewidth = 59 107 | -------------------------------------------------------------------------------- /planet-2.0/planet/atomstyler.py: -------------------------------------------------------------------------------- 1 | from xml.dom import minidom, Node 2 | from urlparse import urlparse, urlunparse 3 | from xml.parsers.expat import ExpatError 4 | from htmlentitydefs import name2codepoint 5 | import re 6 | 7 | # select and apply an xml:base for this entry 8 | class relativize: 9 | def __init__(self, parent): 10 | self.score = {} 11 | self.links = [] 12 | self.collect_and_tally(parent) 13 | self.base = self.select_optimal_base() 14 | if self.base: 15 | if not parent.hasAttribute('xml:base'): 16 | self.rebase(parent) 17 | parent.setAttribute('xml:base', self.base) 18 | 19 | # collect and tally cite, href and src attributes 20 | def collect_and_tally(self,parent): 21 | uri = None 22 | if parent.hasAttribute('cite'): uri=parent.getAttribute('cite') 23 | if parent.hasAttribute('href'): uri=parent.getAttribute('href') 24 | if parent.hasAttribute('src'): uri=parent.getAttribute('src') 25 | 26 | if uri: 27 | parts=urlparse(uri) 28 | if parts[0].lower() == 'http': 29 | parts = (parts[1]+parts[2]).split('/') 30 | base = None 31 | for i in range(1,len(parts)): 32 | base = tuple(parts[0:i]) 33 | self.score[base] = self.score.get(base,0) + len(base) 34 | if base and base not in self.links: self.links.append(base) 35 | 36 | for node in parent.childNodes: 37 | if node.nodeType == Node.ELEMENT_NODE: 38 | self.collect_and_tally(node) 39 | 40 | # select the xml:base with the highest score 41 | def select_optimal_base(self): 42 | if not self.score: return None 43 | for link in self.links: 44 | self.score[link] = 0 45 | winner = max(self.score.values()) 46 | if not winner: return None 47 | for key in self.score.keys(): 48 | if self.score[key] == winner: 49 | if winner == len(key): return None 50 | return urlunparse(('http', key[0], '/'.join(key[1:]), '', '', '')) + '/' 51 | 52 | # rewrite cite, href and src attributes using this base 53 | def rebase(self,parent): 54 | uri = None 55 | if parent.hasAttribute('cite'): uri=parent.getAttribute('cite') 56 | if parent.hasAttribute('href'): uri=parent.getAttribute('href') 57 | if parent.hasAttribute('src'): uri=parent.getAttribute('src') 58 | if uri and uri.startswith(self.base): 59 | uri = uri[len(self.base):] or '.' 60 | if parent.hasAttribute('href'): uri=parent.setAttribute('href', uri) 61 | if parent.hasAttribute('src'): uri=parent.setAttribute('src', uri) 62 | 63 | for node in parent.childNodes: 64 | if node.nodeType == Node.ELEMENT_NODE: 65 | self.rebase(node) 66 | 67 | # convert type="html" to type="plain" or type="xhtml" as appropriate 68 | def retype(parent): 69 | for node in parent.childNodes: 70 | if node.nodeType == Node.ELEMENT_NODE: 71 | 72 | if node.hasAttribute('type') and node.getAttribute('type') == 'html': 73 | if len(node.childNodes)==0: 74 | node.removeAttribute('type') 75 | elif len(node.childNodes)==1: 76 | 77 | # replace html entity defs with utf-8 78 | chunks=re.split('&(\w+);', node.childNodes[0].nodeValue) 79 | for i in range(1,len(chunks),2): 80 | if chunks[i] in ['amp', 'lt', 'gt', 'apos', 'quot']: 81 | chunks[i] ='&' + chunks[i] +';' 82 | elif chunks[i] in name2codepoint: 83 | chunks[i]=unichr(name2codepoint[chunks[i]]) 84 | else: 85 | chunks[i]='&' + chunks[i] + ';' 86 | text = u"".join(chunks) 87 | 88 | try: 89 | # see if the resulting text is a well-formed XML fragment 90 | div = '
%s
' 91 | data = minidom.parseString((div % text.encode('utf-8'))) 92 | 93 | if text.find('<') < 0: 94 | # plain text 95 | node.removeAttribute('type') 96 | text = data.documentElement.childNodes[0].nodeValue 97 | node.childNodes[0].replaceWholeText(text) 98 | 99 | elif len(text) > 80: 100 | # xhtml 101 | node.setAttribute('type', 'xhtml') 102 | node.removeChild(node.childNodes[0]) 103 | node.appendChild(data.documentElement) 104 | 105 | except ExpatError: 106 | # leave as html 107 | pass 108 | 109 | else: 110 | # recurse 111 | retype(node) 112 | 113 | if parent.nodeName == 'entry': 114 | relativize(parent) 115 | 116 | if __name__ == '__main__': 117 | 118 | # run styler on each file mention on the command line 119 | import sys 120 | for feed in sys.argv[1:]: 121 | doc = minidom.parse(feed) 122 | doc.normalize() 123 | retype(doc.documentElement) 124 | open(feed,'w').write(doc.toxml('utf-8')) 125 | -------------------------------------------------------------------------------- /planet-2.0/examples/fancy/index.html.tmpl: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | ### Fancy Planet HTML template. 5 | ### 6 | ### When combined with the stylesheet and images in the output/ directory 7 | ### of the Planet source, this gives you a much prettier result than the 8 | ### default examples template and demonstrates how to use the config file 9 | ### to support things like faces 10 | ### 11 | ### For documentation on the more boring template elements, see 12 | ### examples/config.ini and examples/index.html.tmpl in the Planet source. 13 | 14 | 15 | <TMPL_VAR name> 16 | 17 | "> 18 | 19 | 20 | " title="" type="application/+xml"> 21 | 22 | 23 | 24 | 25 |

26 | 27 | 28 | 29 | 30 | ### End
31 |
32 | ### End
33 |
34 |
35 |
36 |

37 | 38 | 39 | 40 | 41 | ### End
42 |
43 |
44 |
45 | 46 | ### Planet provides template variables for *all* configuration options for 47 | ### the channel (and defaults), even if it doesn't know about them. We 48 | ### exploit this here to add hackergotchi faces to our channels. Planet 49 | ### doesn't know about the "face", "facewidth" and "faceheight" configuration 50 | ### variables, but makes them available to us anyway. 51 | 52 |

" title="">

53 | 54 | " width="" height="" alt=""> 55 | 56 | 57 | 58 | 59 |
lang=""> 60 | 61 | lang="">"> 62 | 63 |
64 |
lang=""> 65 | 66 |
67 | 68 | ### Planet also makes available all of the information from the feed 69 | ### that it can. Use the 'planet-cache' tool on the cache file for 70 | ### a particular feed to find out what additional keys it supports. 71 | ### Comment extra fields are 'author' and 'category' which we 72 | ### demonstrate below. 73 | 74 |

75 | ">by at under 76 |

77 |
78 |
79 | 80 | 81 | ### End
82 |
83 | ### End
84 |
85 |
86 | 87 | 88 | 89 | 123 | 124 | 125 | 126 | -------------------------------------------------------------------------------- /planet-2.0/planet.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """The Planet aggregator. 3 | 4 | A flexible and easy-to-use aggregator for generating websites. 5 | 6 | Visit http://www.planetplanet.org/ for more information and to download 7 | the latest version. 8 | 9 | Requires Python 2.1, recommends 2.3. 10 | """ 11 | 12 | __authors__ = [ "Scott James Remnant ", 13 | "Jeff Waugh " ] 14 | __license__ = "Python" 15 | 16 | 17 | import os 18 | import sys 19 | import time 20 | import locale 21 | import urlparse 22 | 23 | import planet 24 | 25 | from ConfigParser import ConfigParser 26 | 27 | # Default configuration file path 28 | CONFIG_FILE = "config.ini" 29 | 30 | # Defaults for the [Planet] config section 31 | PLANET_NAME = "Unconfigured Planet" 32 | PLANET_LINK = "Unconfigured Planet" 33 | PLANET_FEED = None 34 | OWNER_NAME = "Anonymous Coward" 35 | OWNER_EMAIL = "" 36 | LOG_LEVEL = "WARNING" 37 | FEED_TIMEOUT = 20 # seconds 38 | 39 | # Default template file list 40 | TEMPLATE_FILES = "examples/basic/planet.html.tmpl" 41 | 42 | 43 | 44 | def config_get(config, section, option, default=None, raw=0, vars=None): 45 | """Get a value from the configuration, with a default.""" 46 | if config.has_option(section, option): 47 | return config.get(section, option, raw=raw, vars=None) 48 | else: 49 | return default 50 | 51 | def main(): 52 | config_file = CONFIG_FILE 53 | offline = 0 54 | verbose = 0 55 | 56 | for arg in sys.argv[1:]: 57 | if arg == "-h" or arg == "--help": 58 | print "Usage: planet [options] [CONFIGFILE]" 59 | print 60 | print "Options:" 61 | print " -v, --verbose DEBUG level logging during update" 62 | print " -o, --offline Update the Planet from the cache only" 63 | print " -h, --help Display this help message and exit" 64 | print 65 | sys.exit(0) 66 | elif arg == "-v" or arg == "--verbose": 67 | verbose = 1 68 | elif arg == "-o" or arg == "--offline": 69 | offline = 1 70 | elif arg.startswith("-"): 71 | print >>sys.stderr, "Unknown option:", arg 72 | sys.exit(1) 73 | else: 74 | config_file = arg 75 | 76 | # Read the configuration file 77 | config = ConfigParser() 78 | config.read(config_file) 79 | if not config.has_section("Planet"): 80 | print >>sys.stderr, "Configuration missing [Planet] section." 81 | sys.exit(1) 82 | 83 | # Read the [Planet] config section 84 | planet_name = config_get(config, "Planet", "name", PLANET_NAME) 85 | planet_link = config_get(config, "Planet", "link", PLANET_LINK) 86 | planet_feed = config_get(config, "Planet", "feed", PLANET_FEED) 87 | owner_name = config_get(config, "Planet", "owner_name", OWNER_NAME) 88 | owner_email = config_get(config, "Planet", "owner_email", OWNER_EMAIL) 89 | if verbose: 90 | log_level = "DEBUG" 91 | else: 92 | log_level = config_get(config, "Planet", "log_level", LOG_LEVEL) 93 | feed_timeout = config_get(config, "Planet", "feed_timeout", FEED_TIMEOUT) 94 | template_files = config_get(config, "Planet", "template_files", 95 | TEMPLATE_FILES).split(" ") 96 | 97 | # Default feed to the first feed for which there is a template 98 | if not planet_feed: 99 | for template_file in template_files: 100 | name = os.path.splitext(os.path.basename(template_file))[0] 101 | if name.find('atom')>=0 or name.find('rss')>=0: 102 | planet_feed = urlparse.urljoin(planet_link, name) 103 | break 104 | 105 | # Define locale 106 | if config.has_option("Planet", "locale"): 107 | # The user can specify more than one locale (separated by ":") as 108 | # fallbacks. 109 | locale_ok = False 110 | for user_locale in config.get("Planet", "locale").split(':'): 111 | user_locale = user_locale.strip() 112 | try: 113 | locale.setlocale(locale.LC_ALL, user_locale) 114 | except locale.Error: 115 | pass 116 | else: 117 | locale_ok = True 118 | break 119 | if not locale_ok: 120 | print >>sys.stderr, "Unsupported locale setting." 121 | sys.exit(1) 122 | 123 | # Activate logging 124 | planet.logging.basicConfig() 125 | planet.logging.getLogger().setLevel(planet.logging.getLevelName(log_level)) 126 | log = planet.logging.getLogger("planet.runner") 127 | try: 128 | log.warning 129 | except: 130 | log.warning = log.warn 131 | 132 | # timeoutsocket allows feedparser to time out rather than hang forever on 133 | # ultra-slow servers. Python 2.3 now has this functionality available in 134 | # the standard socket library, so under 2.3 you don't need to install 135 | # anything. But you probably should anyway, because the socket module is 136 | # buggy and timeoutsocket is better. 137 | if feed_timeout: 138 | try: 139 | feed_timeout = float(feed_timeout) 140 | except: 141 | log.warning("Feed timeout set to invalid value '%s', skipping", feed_timeout) 142 | feed_timeout = None 143 | 144 | if feed_timeout and not offline: 145 | try: 146 | from planet import timeoutsocket 147 | timeoutsocket.setDefaultSocketTimeout(feed_timeout) 148 | log.debug("Socket timeout set to %d seconds", feed_timeout) 149 | except ImportError: 150 | import socket 151 | if hasattr(socket, 'setdefaulttimeout'): 152 | log.debug("timeoutsocket not found, using python function") 153 | socket.setdefaulttimeout(feed_timeout) 154 | log.debug("Socket timeout set to %d seconds", feed_timeout) 155 | else: 156 | log.error("Unable to set timeout to %d seconds", feed_timeout) 157 | 158 | # run the planet 159 | my_planet = planet.Planet(config) 160 | my_planet.run(planet_name, planet_link, template_files, offline) 161 | 162 | my_planet.generate_all_files(template_files, planet_name, 163 | planet_link, planet_feed, owner_name, owner_email) 164 | 165 | 166 | if __name__ == "__main__": 167 | main() 168 | 169 | -------------------------------------------------------------------------------- /planet-2.0/INSTALL: -------------------------------------------------------------------------------- 1 | Installing Planet 2 | ----------------- 3 | 4 | You'll need at least Python 2.1 installed on your system, we recommend 5 | Python 2.3 though as there may be bugs with the earlier libraries. 6 | 7 | Everything Pythonesque Planet needs should be included in the 8 | distribution. 9 | 10 | i. 11 | First you'll need to extract the files into a folder somewhere. 12 | I expect you've already done this, after all, you're reading this 13 | file. You can place this wherever you like, ~/planet is a good 14 | choice, but so's anywhere else you prefer. 15 | 16 | ii. 17 | Make a copy of the files in the 'examples' subdirectory, and either 18 | the 'basic' or 'fancy' subdirectory of it and put them wherever 19 | you like; I like to use the Planet's name (so ~/planet/debian), but 20 | it's really up to you. 21 | 22 | The 'basic' index.html and associated config.ini are pretty plain 23 | and boring, if you're after less documentation and more instant 24 | gratification you may wish to use the 'fancy' ones instead. You'll 25 | want the stylesheet and images from the 'output' directory if you 26 | use it. 27 | 28 | iii. 29 | Edit the config.ini file in this directory to taste, it's pretty 30 | well documented so you shouldn't have any problems here. Pay 31 | particular attention to the 'output_dir' option, which should be 32 | readable by your web server and especially the 'template_files' 33 | option where you'll want to change "examples" to wherever you just 34 | placed your copies. 35 | 36 | iv. 37 | Edit the various template (*.tmpl) files to taste, a complete list 38 | of available variables is at the bottom of this file. 39 | 40 | v. 41 | Run it: planet.py pathto/config.ini 42 | 43 | You'll want to add this to cron, make sure you run it from the 44 | right directory. 45 | 46 | vi. 47 | Tell us about it! We'd love to link to you on planetplanet.org :-) 48 | 49 | 50 | Template files 51 | -------------- 52 | 53 | The template files used are given as a space separated list in the 54 | 'template_files' option in config.ini. They are named ending in '.tmpl' 55 | which is removed to form the name of the file placed in the output 56 | directory. 57 | 58 | Reading through the example templates is recommended, they're designed to 59 | pretty much drop straight into your site with little modification 60 | anyway. 61 | 62 | Inside these template files, is replaced with the content 63 | of the 'xxx' variable. The variables available are: 64 | 65 | name .... } the value of the equivalent options 66 | link .... } from the [Planet] section of your 67 | owner_name . } Planet's config.ini file 68 | owner_email } 69 | 70 | url .... link with the output filename appended 71 | generator .. version of planet being used 72 | 73 | date .... { your date format 74 | date_iso ... current date and time in { ISO date format 75 | date_822 ... { RFC822 date format 76 | 77 | 78 | There are also two loops, 'Items' and 'Channels'. All of the lines of 79 | the template and variable substitutions are available for each item or 80 | channel. Loops are created using ... 81 | and may be used as many times as you wish. 82 | 83 | The 'Channels' loop iterates all of the channels (feeds) defined in the 84 | configuration file, within it the following variables are available: 85 | 86 | name .... value of the 'name' option in config.ini, or title 87 | title .... title retreived from the channel's feed 88 | tagline .... description retreived from the channel's feed 89 | link .... link for the human-readable content (from the feed) 90 | url .... url of the channel's feed itself 91 | 92 | Additionally the value of any other option specified in config.ini 93 | for the feed, or in the [DEFAULT] section, is available as a 94 | variable of the same name. 95 | 96 | Depending on the feed, there may be a huge variety of other 97 | variables may be available; the best way to find out what you 98 | have is using the 'planet-cache' tool to examine your cache files. 99 | 100 | The 'Items' loop iterates all of the blog entries from all of the channels, 101 | you do not place it inside a 'Channels' loop. Within it, the following 102 | variables are available: 103 | 104 | id .... unique id for this entry (sometimes just the link) 105 | link .... link to a human-readable version at the origin site 106 | 107 | title .... title of the entry 108 | summary .... a short "first page" summary 109 | content .... the full content of the entry 110 | 111 | date .... { your date format 112 | date_iso ... date and time of the entry in { ISO date format 113 | date_822 ... { RFC822 date format 114 | 115 | If the entry takes place on a date that has no prior entry has 116 | taken place on, the 'new_date' variable is set to that date. 117 | This allows you to break up the page by day. 118 | 119 | If the entry is from a different channel to the previous entry, 120 | or is the first entry from this channel on this day 121 | the 'new_channel' variable is set to the same value as the 122 | 'channel_url' variable. This allows you to collate multiple 123 | entries from the same person under the same banner. 124 | 125 | Additionally the value of any variable that would be defined 126 | for the channel is available, with 'channel_' prepended to the 127 | name (e.g. 'channel_name' and 'channel_link'). 128 | 129 | Depending on the feed, there may be a huge variety of other 130 | variables may be available; the best way to find out what you 131 | have is using the 'planet-cache' tool to examine your cache files. 132 | 133 | 134 | There are also a couple of other special things you can do in a template. 135 | 136 | - If you want HTML escaping applied to the value of a variable, use the 137 | form. 138 | 139 | - If you want URI escaping applied to the value of a variable, use the 140 | form. 141 | 142 | - To only include a section of the template if the variable has a 143 | non-empty value, you can use ..... e.g. 144 | 145 | 146 |

147 |
148 | 149 | You may place a within this block to specify an 150 | alternative, or may use ... to 151 | perform the opposite. 152 | -------------------------------------------------------------------------------- /planet_sage/index.html.tmpl: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | ### Fancy Planet HTML template. 5 | ### 6 | ### When combined with the stylesheet and images in the output/ directory 7 | ### of the Planet source, this gives you a much prettier result than the 8 | ### default examples template and demonstrates how to use the config file 9 | ### to support things like faces 10 | ### 11 | ### For documentation on the more boring template elements, see 12 | ### examples/config.ini and examples/index.html.tmpl in the Planet source. 13 | 14 | 15 | <TMPL_VAR name> 16 | 17 | "> 18 | 19 | 20 | 23 | 27 | 28 | " title="" type="application/+xml"> 29 | 30 | 31 | 32 | 33 | 34 |
35 |
36 |
37 |
38 | 39 | 50 | 51 |
52 |
53 | 55 | 56 |
57 | 58 | 59 | 60 | 61 | ### End
62 |
63 | ### End
64 |
65 |
66 |
67 |

68 | 69 | 70 | 71 | 72 | ### End
73 |
74 |
75 |
76 | 77 | ### Planet provides template variables for *all* configuration options for 78 | ### the channel (and defaults), even if it doesn't know about them. We 79 | ### exploit this here to add hackergotchi faces to our channels. Planet 80 | ### doesn't know about the "face", "facewidth" and "faceheight" configuration 81 | ### variables, but makes them available to us anyway. 82 | 83 |

" title="">

84 | 85 | " width="" height="" alt=""> 86 | 87 | 88 | 89 | 90 |
lang=""> 91 | 92 | lang="">"> 93 | 94 |
95 |
lang=""> 96 | 97 |
98 | 99 | ### Planet also makes available all of the information from the feed 100 | ### that it can. Use the 'planet-cache' tool on the cache file for 101 | ### a particular feed to find out what additional keys it supports. 102 | ### Comment extra fields are 'author' and 'category' which we 103 | ### demonstrate below. 104 | 105 |

106 | ">by at under 107 |

108 |
109 |
110 | 111 | 112 | ### End
113 |
114 | ### End
115 |
116 |
117 | 118 | 119 |
120 |
121 |
122 |
123 |
124 | 125 | 126 | 127 | 128 | 179 | 180 | 181 | 182 | 183 | -------------------------------------------------------------------------------- /planet-2.0/planet-cache.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: UTF-8 -*- 3 | """Planet cache tool. 4 | 5 | """ 6 | 7 | __authors__ = [ "Scott James Remnant ", 8 | "Jeff Waugh " ] 9 | __license__ = "Python" 10 | 11 | 12 | import os 13 | import sys 14 | import time 15 | import dbhash 16 | import ConfigParser 17 | 18 | import planet 19 | 20 | 21 | def usage(): 22 | print "Usage: planet-cache [options] CACHEFILE [ITEMID]..." 23 | print 24 | print "Examine and modify information in the Planet cache." 25 | print 26 | print "Channel Commands:" 27 | print " -C, --channel Display known information on the channel" 28 | print " -L, --list List items in the channel" 29 | print " -K, --keys List all keys found in channel items" 30 | print 31 | print "Item Commands (need ITEMID):" 32 | print " -I, --item Display known information about the item(s)" 33 | print " -H, --hide Mark the item(s) as hidden" 34 | print " -U, --unhide Mark the item(s) as not hidden" 35 | print 36 | print "Other Options:" 37 | print " -h, --help Display this help message and exit" 38 | sys.exit(0) 39 | 40 | def usage_error(msg, *args): 41 | print >>sys.stderr, msg, " ".join(args) 42 | print >>sys.stderr, "Perhaps you need --help ?" 43 | sys.exit(1) 44 | 45 | def print_keys(item, title): 46 | keys = item.keys() 47 | keys.sort() 48 | key_len = max([ len(k) for k in keys ]) 49 | 50 | print title + ":" 51 | for key in keys: 52 | if item.key_type(key) == item.DATE: 53 | value = time.strftime(planet.TIMEFMT_ISO, item[key]) 54 | else: 55 | value = str(item[key]) 56 | print " %-*s %s" % (key_len, key, fit_str(value, 74 - key_len)) 57 | 58 | def fit_str(string, length): 59 | if len(string) <= length: 60 | return string 61 | else: 62 | return string[:length-4] + " ..." 63 | 64 | 65 | if __name__ == "__main__": 66 | cache_file = None 67 | want_ids = 0 68 | ids = [] 69 | 70 | command = None 71 | 72 | for arg in sys.argv[1:]: 73 | if arg == "-h" or arg == "--help": 74 | usage() 75 | elif arg == "-C" or arg == "--channel": 76 | if command is not None: 77 | usage_error("Only one command option may be supplied") 78 | command = "channel" 79 | elif arg == "-L" or arg == "--list": 80 | if command is not None: 81 | usage_error("Only one command option may be supplied") 82 | command = "list" 83 | elif arg == "-K" or arg == "--keys": 84 | if command is not None: 85 | usage_error("Only one command option may be supplied") 86 | command = "keys" 87 | elif arg == "-I" or arg == "--item": 88 | if command is not None: 89 | usage_error("Only one command option may be supplied") 90 | command = "item" 91 | want_ids = 1 92 | elif arg == "-H" or arg == "--hide": 93 | if command is not None: 94 | usage_error("Only one command option may be supplied") 95 | command = "hide" 96 | want_ids = 1 97 | elif arg == "-U" or arg == "--unhide": 98 | if command is not None: 99 | usage_error("Only one command option may be supplied") 100 | command = "unhide" 101 | want_ids = 1 102 | elif arg.startswith("-"): 103 | usage_error("Unknown option:", arg) 104 | else: 105 | if cache_file is None: 106 | cache_file = arg 107 | elif want_ids: 108 | ids.append(arg) 109 | else: 110 | usage_error("Unexpected extra argument:", arg) 111 | 112 | if cache_file is None: 113 | usage_error("Missing expected cache filename") 114 | elif want_ids and not len(ids): 115 | usage_error("Missing expected entry ids") 116 | 117 | # Open the cache file directly to get the URL it represents 118 | try: 119 | db = dbhash.open(cache_file) 120 | url = db["url"] 121 | db.close() 122 | except dbhash.bsddb._db.DBError, e: 123 | print >>sys.stderr, cache_file + ":", e.args[1] 124 | sys.exit(1) 125 | except KeyError: 126 | print >>sys.stderr, cache_file + ": Probably not a cache file" 127 | sys.exit(1) 128 | 129 | # Now do it the right way :-) 130 | my_planet = planet.Planet(ConfigParser.ConfigParser()) 131 | my_planet.cache_directory = os.path.dirname(cache_file) 132 | channel = planet.Channel(my_planet, url) 133 | 134 | for item_id in ids: 135 | if not channel.has_item(item_id): 136 | print >>sys.stderr, item_id + ": Not in channel" 137 | sys.exit(1) 138 | 139 | # Do the user's bidding 140 | if command == "channel": 141 | print_keys(channel, "Channel Keys") 142 | 143 | elif command == "item": 144 | for item_id in ids: 145 | item = channel.get_item(item_id) 146 | print_keys(item, "Item Keys for %s" % item_id) 147 | 148 | elif command == "list": 149 | print "Items in Channel:" 150 | for item in channel.items(hidden=1, sorted=1): 151 | print " " + item.id 152 | print " " + time.strftime(planet.TIMEFMT_ISO, item.date) 153 | if hasattr(item, "title"): 154 | print " " + fit_str(item.title, 70) 155 | if hasattr(item, "hidden"): 156 | print " (hidden)" 157 | 158 | elif command == "keys": 159 | keys = {} 160 | for item in channel.items(): 161 | for key in item.keys(): 162 | keys[key] = 1 163 | 164 | keys = keys.keys() 165 | keys.sort() 166 | 167 | print "Keys used in Channel:" 168 | for key in keys: 169 | print " " + key 170 | print 171 | 172 | print "Use --item to output values of particular items." 173 | 174 | elif command == "hide": 175 | for item_id in ids: 176 | item = channel.get_item(item_id) 177 | if hasattr(item, "hidden"): 178 | print item_id + ": Already hidden." 179 | else: 180 | item.hidden = "yes" 181 | 182 | channel.cache_write() 183 | print "Done." 184 | 185 | elif command == "unhide": 186 | for item_id in ids: 187 | item = channel.get_item(item_id) 188 | if hasattr(item, "hidden"): 189 | del(item.hidden) 190 | else: 191 | print item_id + ": Not hidden." 192 | 193 | channel.cache_write() 194 | print "Done." 195 | -------------------------------------------------------------------------------- /planet-2.0/planet/tests/test_sanitize.py: -------------------------------------------------------------------------------- 1 | # adapted from http://www.iamcal.com/publish/articles/php/processing_html_part_2/ 2 | # and from http://feedparser.org/tests/wellformed/sanitize/ 3 | # by Aaron Swartz, 2006, public domain 4 | 5 | import unittest, new 6 | from planet import sanitize 7 | 8 | class SanitizeTest(unittest.TestCase): pass 9 | 10 | # each call to HTML adds a test case to SanitizeTest 11 | testcases = 0 12 | def HTML(a, b): 13 | global testcases 14 | testcases += 1 15 | func = lambda self: self.assertEqual(sanitize.HTML(a), b) 16 | method = new.instancemethod(func, None, SanitizeTest) 17 | setattr(SanitizeTest, "test_%d" % testcases, method) 18 | 19 | ## basics 20 | HTML("","") 21 | HTML("hello","hello") 22 | 23 | ## balancing tags 24 | HTML("hello","hello") 25 | HTML("hello","hello") 26 | HTML("hello","hello") 27 | HTML("hello","hello") 28 | HTML("hello","hello") 29 | HTML("","") 30 | 31 | ## trailing slashes 32 | HTML('','') 33 | HTML('','') 34 | HTML('','') 35 | 36 | ## balancing angle brakets 37 | HTML('','b>') 39 | HTML('','>') 41 | HTML('foofoo','b>foo') 43 | HTML('>') 44 | HTML('b><','b>') 45 | HTML('>','>') 46 | 47 | ## attributes 48 | HTML('','') 49 | HTML('','') 50 | HTML('','') 51 | 52 | ## dangerous tags (a small sample) 53 | sHTML = lambda x: HTML(x, 'safe description') 54 | sHTML('safe description') 55 | sHTML('safe description') 56 | sHTML('safe description') 57 | sHTML('safe description') 58 | sHTML('safe description') 59 | sHTML('safe') 60 | sHTML('safe description') 61 | sHTML('safe description') 62 | sHTML('safe description') 63 | sHTML('safe description') 64 | 65 | for x in ['onabort', 'onblur', 'onchange', 'onclick', 'ondblclick', 'onerror', 'onfocus', 'onkeydown', 'onkeypress', 'onkeyup', 'onload', 'onmousedown', 'onmouseout', 'onmouseover', 'onmouseup', 'onreset', 'resize', 'onsubmit', 'onunload']: 66 | HTML('' % x, 67 | '') 68 | 69 | HTML('never trust your upstream platypus', 'never trust your upstream platypus') 70 | 71 | ## ignorables 72 | HTML('foo', 'foo') 74 | 75 | ## non-allowed tags 76 | HTML('','') 80 | HTML('\r\n\r\n\r\n\r\n\r\nfunction executeMe()\r\n{\r\n\r\n\r\n\r\n\r\n/*