weblog-0.6/.hg_archival.txt0000644000000000000000000000013610761173772016101 0ustar00usergroup00000000000000repo: 00ecfb3367fecf6d8ba7a94c3f995bc789c18d1e node: 79224765beeae864cff568b346326e88c5c06a11 weblog-0.6/.hgignore0000644000000000000000000000003610761173772014615 0ustar00usergroup00000000000000syntax: glob *.pyc *~ .*.swp weblog-0.6/.hgtags0000644000000000000000000000064010761173772014271 0ustar00usergroup000000000000009bafbb9dfb928172d988390ea61932b610278ea3 WEBLOG_0_1 7053f6c08fab7af1c5b76d78a9bb6e41fe6a8b5a WEBLOG_0_2 ebe752dc0a655b451babdc2acb6027a523ac8474 WEBLOG_0_3 9cc6b91a2fb95946b5443461201c8a57ad301a53 WEBLOG_0_4 85db8e1cb11890a15f38b3d161dc59962e00b135 WEBLOG_0_5 fcd5f323c67112916c0d43d776f52a96064bef53 WEBLOG_0_5 44abff8da985b456545fa393a2f634932400476b WEBLOG_0_5 a0a1dd94b8b2371f45536e90ee03074dae314f71 WEBLOG_0_6 weblog-0.6/COPYING0000644000000000000000000000467010761173772014055 0ustar00usergroup00000000000000Copyright (c) 2007, Henry Precheur Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. --- Also the file PyRSS2Gen.py is covered by the BSD license: (This is the BSD license, based on the template at http://www.opensource.org/licenses/bsd-license.php ) Copyright (c) 2003, Dalke Scientific Software, LLC All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. * Neither the name of the Dalke Scientific Softare, LLC, Andrew Dalke, nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. weblog-0.6/README0000644000000000000000000000074110761173772013675 0ustar00usergroup00000000000000Weblog is a web log or blog publisher. It takes structured text files as input and outputs static HTML / RSS files. Weblog aims to be simple and robust. to learn how to install and use weblog please read the text file: doc/weblog.rst If you have docutils installed you can turn it into a HTML file: $ rst2html.py weblog.py > weblog.html Jinja is needed to use Weblog (http://jinja.pocoo.org) Weblog includes PyRSS2Gen (http://www.dalkescientific.com/Python/PyRSS2Gen.html) weblog-0.6/TODO0000644000000000000000000000031010761173772013475 0ustar00usergroup00000000000000--- v0.6 --- markdown better exception handling (unexpected exception -> bug) --- v1.0 --- use logging documentation tips on uploading doc on templating profile the code if too slow calendar (?) weblog-0.6/doc/weblog.rst0000644000000000000000000002027010761173772015572 0ustar00usergroup00000000000000Weblog manual ============= :Author: Henry Precheur :Reviewers: Anis Kadri, Bastien Simondi, Eric Salama Abstract -------- Weblog is a *web log* or *blog* publisher. It takes structured text files as input and outputs static HTML / RSS files. Weblog aims to be simple and robust. In the following document *Weblog* is the name of the software. The *web log* concept is referred as the more common term *blog*. According to Wikipedia_: A *blog* (a portmanteau of *web log*) is a website where entries are written in chronological order and commonly displayed in reverse chronological order. .. _Wikipedia: http://en.wikipedia.org/wiki/Blog Pre-requirements ~~~~~~~~~~~~~~~~ - Python version 2.4+ - Jinja version 1.1+. You can learn how to install Jinja at http://jinja.pocoo.org/documentation/installation. Installation ------------ Download Weblog's latest version at http://henry.precheur.org/weblog/. Extract it:: tar zxf weblog.tar.gz It can be used right away. You can also install it using the supplied ``setup.py`` script. Run ``python setup.py --help`` to learn how to use it. Quick Start ----------- In the following examples ``weblog/`` represents Weblog's installation directory. Create a new directory named ``my_blog``. The $ sign represents the shell prompt, do not type it!:: $ mkdir my_blog Copy from the Weblog installation directory the file ``weblog.ini`` into ``my_blog``:: $ cp weblog/examples/weblog.ini my_blog ``weblog.ini`` is the configuration file of the blog. Check the configuration file section for more information. Do not worry about it now, no modification is required to get the following examples working. Create a file named ``first_post.html`` in the ``my_blog`` directory:: title: First post author: Me date: 2007-08-25 Hello world! Actually all the post filenames must end with ``.html``. Go in the ``my_blog`` directory and run the ``weblog_publish.py`` script:: $ cd my_blog/ $ python weblog/weblog_publish.py Or if ``weblog_publish.py`` is in your ``PATH``:: $ weblog_publish.py It should create a directory named ``output`` which contains the generated files. You can look at the results by opening the file ``output/index.html`` in your browser. The first 3 lines of the file ``first_post.html`` define the post's parameters. These are standard :RFC:`2822` headers (the headers used in Emails). Only ``title`` and ``date`` are mandatory. ``author`` is optional. If you don't fill this field, the author will be the one specified in ``weblog.ini``. The line ``Hello world!`` is the actual content of the post. Note that a blank line is required between the headers and the content. The content is an HTML block. Use the HTML syntax to format your post content. For example create a second file named ``second_post.html``:: title: Second post author: Me (again!) date: 2007-08-26 Second test post!

© 2007 Me

Regenerate the blog files:: $ python weblog/weblog_publish.py Or:: $ weblog_publish.py Reload the page in your browser. You should see a second post with some formating. The default post file encoding is ASCII. If you want to use a different encoding you can specify it via the field ``encoding``:: title: Encoding test date: 2007-11-5 encoding: latin-1 Here you can put some ISO-8856-1 text ... If you always use the same encoding. You can specify the default encoding in ``weblog.ini``. Encoding and escaping --------------------- Weblog tries to make sure its output is always *correct*. Non-ASCII characters, are converted to HTML entities so you don't have to worry about it. The output will *never* be encoded into ISO-8856-1, UTF-8 or another non-ASCII encoding. Encoding conversions are not so simple in practice. By doing only one conversion to the simplest encoding possible, a lot of problems are solved. The content of the post is not escaped. The title and the date of the post are escaped. A title like this: ``Hello World`` will be escaped. HTML tags will appear, and no formating will be applied to ``world``. The original text "Hello World" will appear instead of "Hello *World*", You can override this by specifying ``raw`` as the encoding. Using the ``raw`` encoding nothing will be escaped or converted, but you must make sure all characters are ASCII characters:: title: Non-escaped title author: Me <me@my_weblog.org> encoding: raw When the ``raw`` encoding is used. No checking is performed to make sure there are only ASCII characters in the text. This might change in the future. How URI's are handled --------------------- A *relative* link like ```` will be rewritten in the RSS file and in some HTML files to make sure it always point to the correct URI. But an *absolute* link like ```` won't be rewritten. Since it should always be good. Note that Weblog considers ``/`` as the root directory. So ``test.html`` and ``/test.html`` point to the same file. Command line parameters ----------------------- Usage: weblog_publish.py [options] Options: -h, --help show this help message and exit -s DIR, --source-dir=DIR The source directory where the blog posts and thefile weblog.ini are located -o DIR, --output-dir=DIR The directory where all the generated files willbe written. If it does not exist it will be created. Configuration file ------------------ All configuration options are in the ``weblog`` section. Learn more about the format of the configuration file: http://docs.python.org/lib/module-ConfigParser.html. A configuration file looks like this:: [weblog] title: Blog's title url: http://example.com/ description: A sample blog. source_dir: path/to/my/posts output_dir: path/to/output/directory encoding: latin-1 author: Me Fields description ~~~~~~~~~~~~~~~~~~ title The blog's title. It appears at the top of the blog's homepage and in the page's title This field is mandatory. url The base URL of your blog. For example ``http://my-host.com/my-weblog/``. It is used to generate the absolute URL's to your blog. This field is mandatory. description A short description of your blog. Like "My favorite books reviews", or "Dr. Spock, publications about electronics". Note that it is possible to use multiple lines like this:: description: My blog about configuration files. the description will be merged to a single line like this ``My blog about configuration files.``. This field is mandatory. source_dir The directory containing the file ``weblog.ini``, the post files and possibly the ``templates`` directory. By default the current directory. output_dir The output directory. Generated files will be put there. By default ``output``. encoding The default post file encoding. Default ``ASCII``. It is overridden by the ``encoding`` field in the post file. author The default author. It is overridden by the ``author`` field in the post file. post_per_page The number of post displayed per listing page. Default is 10. rss_post_limit The maximum number of post to be included in the RSS file. The most recent posts are the ones included. Default is 10. html_head Additional information for the ```` section. Useful to add custom CSS style sheets. Can be a string or a filename. If a file with this name exists in the source directory then it is read. Else it is considered as a string. The result is processed using Jinja. You can use the variable ``top_dir`` to link to external files. It contains the path to the top directory of the blog. Examples:: html_head= html_head={{ top_dir }}my_stylesheet.css html_header Additional content located just before the blog content. Can be a string or a filename. (See html_head above) Useful to add a logo or a search box at the top. html_footer Additional content located just after the blog content. Can be a string or a filename. (See html_head above) Useful to add ... a footer! .. vim:se tw=80 sw=2 ts=2 et encoding=utf-8: weblog-0.6/examples/enconding.html0000644000000000000000000000055110761173772017464 0ustar00usergroup00000000000000title: Weblog encode le français! author: Henry Prêcheur encoding: latin-1 date: 2007-10-01

Weblog encode maintenant le texte correctement! Des caractéres tels que: È, Õ ou Ä sont maintenant bien encodés!
Français, Español & Deutsh :)

The encoding of the file is ISO-8859-1 or latin-1.

weblog-0.6/examples/first_post.html0000644000000000000000000000007410761173772017714 0ustar00usergroup00000000000000title: First post author: Me date: 2007-08-25 Hello world! weblog-0.6/examples/second_post.html0000644000000000000000000000020710761173772020036 0ustar00usergroup00000000000000title: Second post date: 2007-08-26 Second test post!

The author lastname is Prêcheur

weblog-0.6/examples/utf-8.html0000644000000000000000000000016110761173772016460 0ustar00usergroup00000000000000title: Some UTF-8, ç ä é ö ó date: 2008-1-1 encoding: UTF-8 Test post with UTF-8 inside ... ç ä é ö ó weblog-0.6/examples/weblog.ini0000644000000000000000000000027010761173772016610 0ustar00usergroup00000000000000[weblog] title=Sample blog url=http://blog.sample.org description=Brief description of this sample blog. Do multiline, this way! encoding=UTF-8 author=Me weblog-0.6/setup.py0000644000000000000000000000234310761173772014527 0ustar00usergroup00000000000000try: from setuptools import setup except: from distutils.core import setup import os version = '0.6' f = open(os.path.join(os.path.dirname(__file__), 'doc', 'weblog.rst')) long_description = f.read().strip().decode('utf-8') f.close() setup( name="weblog", version=version, packages=['weblog'], package_data={'weblog': ['templates/*.tmpl']}, scripts=['weblog_publish.py'], requires=['Jinja (>=1.1)'], data_files=[('doc', ['doc/weblog.rst'])], # unzip the egg so we can access to documentation & templates zip_safe = False, # metadata for upload to PyPI author = "Henry Precheur", author_email = "henry@precheur.org", description = "Weblog is a blog publisher. " \ "It takes structured text files as input and outputs static HTML " \ "and RSS files. Weblog aims to be simple and robust.", long_description=long_description, license = "ISC", keywords = "weblog blog journal rss", url = "http://henry.precheur.org/weblog/", classifiers=[ 'Development Status :: 4 - Beta', 'Topic :: Internet :: WWW/HTTP :: Dynamic Content :: News/Diary', 'Intended Audience :: End Users/Desktop', 'Programming Language :: Python', ]) weblog-0.6/test.py0000644000000000000000000000672510761173772014356 0ustar00usergroup00000000000000import os import shutil import tempfile import unittest import StringIO from weblog import Post, PostError from weblog import jinja_environment from weblog_publish import load_post_list, generate_rss, \ generate_index_listing class TestSimpleLoad(unittest.TestCase): def test_load_post_list(self): post_list = load_post_list('test/simple/') self.assertEqual(len(post_list), 3) sorted_list = sorted(post_list) self.assertEqual(sorted_list[0].title, 'post1') self.assertEqual(sorted_list[1].title, 'post2') self.assertEqual(sorted_list[2].title, 'post3') def test_load_post_list_encoding_failure(self): Post.ENCODING = 'ascii' self.assertRaises(PostError, load_post_list, 'test/encoding/') def test_load_post_list_encoding(self): Post.ENCODING = 'UTF-8' post_list = load_post_list('test/encoding/') self.assertEqual(len(post_list), 2) sorted_list = sorted(post_list) self.assertEqual(sorted_list[0].title, 'UTF-8 post ÖÉÈÄ ...') self.assertEqual(sorted_list[0].content, 'Öéèä\n') self.assertEqual(sorted_list[1].title, 'latin post ÖÉÈÄ ...') self.assertEqual(sorted_list[1].content, 'Öéèä\n') class TestGeneration(unittest.TestCase): env = jinja_environment(os.path.dirname(__file__)) def setUp(self): self.tempdir = tempfile.mkdtemp() def tearDown(self): shutil.rmtree(self.tempdir) def test_generate_listing_empty(self): generate_index_listing(10, self.tempdir, self.env.get_template('index.html.tmpl'), list(), dict(title='test', url='http://test.net', description='test')) def test_generate_listing(self): post1 = '''title: post1 date: 2008-02-04 post 1''' post2 = '''title: post2 date: 2008-01-18 author: test@test.com post 2''' post_list = [Post(StringIO.StringIO(post1)), Post(StringIO.StringIO(post2))] generate_index_listing(10, self.tempdir, self.env.get_template('index.html.tmpl'), post_list, dict(title='test', url='http://test.net', description='test')) def test_generate_rss(self): post1 = '''title: post1 date: 2008-02-04 post 1''' post2 = '''title: post2 date: 2008-01-18 author: test@test.com post 2
''' post_list = [Post(StringIO.StringIO(post1)), Post(StringIO.StringIO(post2))] generate_rss(post_list, os.path.join(self.tempdir, 'rss.xml'), dict(title='test', url='http://test.net', description='test')) def test_generate_rss_empty(self): generate_rss(list(), os.path.join(self.tempdir, 'rss.xml'), dict(title='test', url='http://test.net', description='test')) if __name__ == '__main__': unittest.main() weblog-0.6/test/encoding/latin-1.html0000644000000000000000000000010410761173772017706 0ustar00usergroup00000000000000title: latin post ÖÉÈÄ ... date: 2008-02-04 encoding: latin-1 Öéèä weblog-0.6/test/encoding/utf-8.html0000644000000000000000000000007210761173772017410 0ustar00usergroup00000000000000title: UTF-8 post ÖÉÈÄ ... date: 2008-02-03 Öéèä weblog-0.6/test/encoding/weblog.ini0000644000000000000000000000016310761173772017540 0ustar00usergroup00000000000000[weblog] title=Test blog url=http://blog.test.org description=Test blog author=test encoding=utf-8 weblog-0.6/test/simple/post1.html0000644000000000000000000000004510761173772017216 0ustar00usergroup00000000000000title: post1 date: 2007-01-01 post1 weblog-0.6/test/simple/post2.html0000644000000000000000000000004410761173772017216 0ustar00usergroup00000000000000title: post2 date: 2007-6-15 post2 weblog-0.6/test/simple/post3.html0000644000000000000000000000004510761173772017220 0ustar00usergroup00000000000000title: post3 date: 2007-12-31 post3 weblog-0.6/test/simple/weblog.ini0000644000000000000000000000014410761173772017242 0ustar00usergroup00000000000000[weblog] title=Test blog url=http://blog.test.org description=Test blog author=test weblog-0.6/weblog/PyRSS2Gen.py0000644000000000000000000003375410761173772016354 0ustar00usergroup00000000000000"""PyRSS2Gen - A Python library for generating RSS 2.0 feeds.""" __name__ = "PyRSS2Gen" __version__ = (1, 0, 0) __author__ = "Andrew Dalke " _generator_name = __name__ + "-" + ".".join(map(str, __version__)) import datetime # Could make this the base class; will need to add 'publish' class WriteXmlMixin: def write_xml(self, outfile, encoding = "iso-8859-1"): from xml.sax import saxutils handler = saxutils.XMLGenerator(outfile, encoding) handler.startDocument() self.publish(handler) handler.endDocument() def to_xml(self, encoding = "iso-8859-1"): try: import cStringIO as StringIO except ImportError: import StringIO f = StringIO.StringIO() self.write_xml(f, encoding) return f.getvalue() def _element(handler, name, obj, d = {}): if isinstance(obj, basestring) or obj is None: # special-case handling to make the API easier # to use for the common case. handler.startElement(name, d) if obj is not None: handler.characters(obj) handler.endElement(name) else: # It better know how to emit the correct XML. obj.publish(handler) def _opt_element(handler, name, obj): if obj is None: return _element(handler, name, obj) def _format_date(dt): """convert a datetime into an RFC 822 formatted date Input date must be in GMT. """ # Looks like: # Sat, 07 Sep 2002 00:00:01 GMT # Can't use strftime because that's locale dependent # # Isn't there a standard way to do this for Python? The # rfc822 and email.Utils modules assume a timestamp. The # following is based on the rfc822 module. return "%s, %02d %s %04d %02d:%02d:%02d GMT" % ( ["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"][dt.weekday()], dt.day, ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"][dt.month-1], dt.year, dt.hour, dt.minute, dt.second) ## # A couple simple wrapper objects for the fields which # take a simple value other than a string. class IntElement: """implements the 'publish' API for integers Takes the tag name and the integer value to publish. (Could be used for anything which uses str() to be published to text for XML.) """ element_attrs = {} def __init__(self, name, val): self.name = name self.val = val def publish(self, handler): handler.startElement(self.name, self.element_attrs) handler.characters(str(self.val)) handler.endElement(self.name) class DateElement: """implements the 'publish' API for a datetime.datetime Takes the tag name and the datetime to publish. Converts the datetime to RFC 2822 timestamp (4-digit year). """ def __init__(self, name, dt): self.name = name self.dt = dt def publish(self, handler): _element(handler, self.name, _format_date(self.dt)) #### class Category: """Publish a category element""" def __init__(self, category, domain = None): self.category = category self.domain = domain def publish(self, handler): d = {} if self.domain is not None: d["domain"] = self.domain _element(handler, "category", self.category, d) class Cloud: """Publish a cloud""" def __init__(self, domain, port, path, registerProcedure, protocol): self.domain = domain self.port = port self.path = path self.registerProcedure = registerProcedure self.protocol = protocol def publish(self, handler): _element(handler, "cloud", None, { "domain": self.domain, "port": str(self.port), "path": self.path, "registerProcedure": self.registerProcedure, "protocol": self.protocol}) class Image: """Publish a channel Image""" element_attrs = {} def __init__(self, url, title, link, width = None, height = None, description = None): self.url = url self.title = title self.link = link self.width = width self.height = height self.description = description def publish(self, handler): handler.startElement("image", self.element_attrs) _element(handler, "url", self.url) _element(handler, "title", self.title) _element(handler, "link", self.link) width = self.width if isinstance(width, int): width = IntElement("width", width) _opt_element(handler, "width", width) height = self.height if isinstance(height, int): height = IntElement("height", height) _opt_element(handler, "height", height) _opt_element(handler, "description", self.description) handler.endElement("image") class Guid: """Publish a guid Defaults to being a permalink, which is the assumption if it's omitted. Hence strings are always permalinks. """ def __init__(self, guid, isPermaLink = 1): self.guid = guid self.isPermaLink = isPermaLink def publish(self, handler): d = {} if self.isPermaLink: d["isPermaLink"] = "true" else: d["isPermaLink"] = "false" _element(handler, "guid", self.guid, d) class TextInput: """Publish a textInput Apparently this is rarely used. """ element_attrs = {} def __init__(self, title, description, name, link): self.title = title self.description = description self.name = name self.link = link def publish(self, handler): handler.startElement("textInput", self.element_attrs) _element(handler, "title", self.title) _element(handler, "description", self.description) _element(handler, "name", self.name) _element(handler, "link", self.link) handler.endElement("textInput") class Enclosure: """Publish an enclosure""" def __init__(self, url, length, type): self.url = url self.length = length self.type = type def publish(self, handler): _element(handler, "enclosure", None, {"url": self.url, "length": str(self.length), "type": self.type, }) class Source: """Publish the item's original source, used by aggregators""" def __init__(self, name, url): self.name = name self.url = url def publish(self, handler): _element(handler, "source", self.name, {"url": self.url}) class SkipHours: """Publish the skipHours This takes a list of hours, as integers. """ element_attrs = {} def __init__(self, hours): self.hours = hours def publish(self, handler): if self.hours: handler.startElement("skipHours", self.element_attrs) for hour in self.hours: _element(handler, "hour", str(hour)) handler.endElement("skipHours") class SkipDays: """Publish the skipDays This takes a list of days as strings. """ element_attrs = {} def __init__(self, days): self.days = days def publish(self, handler): if self.days: handler.startElement("skipDays", self.element_attrs) for day in self.days: _element(handler, "day", day) handler.endElement("skipDays") class RSS2(WriteXmlMixin): """The main RSS class. Stores the channel attributes, with the "category" elements under ".categories" and the RSS items under ".items". """ rss_attrs = {"version": "2.0"} element_attrs = {} def __init__(self, title, link, description, language = None, copyright = None, managingEditor = None, webMaster = None, pubDate = None, # a datetime, *in* *GMT* lastBuildDate = None, # a datetime categories = None, # list of strings or Category generator = _generator_name, docs = "http://blogs.law.harvard.edu/tech/rss", cloud = None, # a Cloud ttl = None, # integer number of minutes image = None, # an Image rating = None, # a string; I don't know how it's used textInput = None, # a TextInput skipHours = None, # a SkipHours with a list of integers skipDays = None, # a SkipDays with a list of strings items = None, # list of RSSItems ): self.title = title self.link = link self.description = description self.language = language self.copyright = copyright self.managingEditor = managingEditor self.webMaster = webMaster self.pubDate = pubDate self.lastBuildDate = lastBuildDate if categories is None: categories = [] self.categories = categories self.generator = generator self.docs = docs self.cloud = cloud self.ttl = ttl self.image = image self.rating = rating self.textInput = textInput self.skipHours = skipHours self.skipDays = skipDays if items is None: items = [] self.items = items def publish(self, handler): handler.startElement("rss", self.rss_attrs) handler.startElement("channel", self.element_attrs) _element(handler, "title", self.title) _element(handler, "link", self.link) _element(handler, "description", self.description) self.publish_extensions(handler) _opt_element(handler, "language", self.language) _opt_element(handler, "copyright", self.copyright) _opt_element(handler, "managingEditor", self.managingEditor) _opt_element(handler, "webMaster", self.webMaster) pubDate = self.pubDate if isinstance(pubDate, datetime.datetime): pubDate = DateElement("pubDate", pubDate) _opt_element(handler, "pubDate", pubDate) lastBuildDate = self.lastBuildDate if isinstance(lastBuildDate, datetime.datetime): lastBuildDate = DateElement("lastBuildDate", lastBuildDate) _opt_element(handler, "lastBuildDate", lastBuildDate) for category in self.categories: if isinstance(category, basestring): category = Category(category) category.publish(handler) _opt_element(handler, "generator", self.generator) _opt_element(handler, "docs", self.docs) if self.cloud is not None: self.cloud.publish(handler) ttl = self.ttl if isinstance(self.ttl, int): ttl = IntElement("ttl", ttl) _opt_element(handler, "tt", ttl) if self.image is not None: self.image.publish(handler) _opt_element(handler, "rating", self.rating) if self.textInput is not None: self.textInput.publish(handler) if self.skipHours is not None: self.skipHours.publish(handler) if self.skipDays is not None: self.skipDays.publish(handler) for item in self.items: item.publish(handler) handler.endElement("channel") handler.endElement("rss") def publish_extensions(self, handler): # Derived classes can hook into this to insert # output after the three required fields. pass class RSSItem(WriteXmlMixin): """Publish an RSS Item""" element_attrs = {} def __init__(self, title = None, # string link = None, # url as string description = None, # string author = None, # email address as string categories = None, # list of string or Category comments = None, # url as string enclosure = None, # an Enclosure guid = None, # a unique string pubDate = None, # a datetime source = None, # a Source ): if title is None and description is None: raise TypeError( "must define at least one of 'title' or 'description'") self.title = title self.link = link self.description = description self.author = author if categories is None: categories = [] self.categories = categories self.comments = comments self.enclosure = enclosure self.guid = guid self.pubDate = pubDate self.source = source # It sure does get tedious typing these names three times... def publish(self, handler): handler.startElement("item", self.element_attrs) _opt_element(handler, "title", self.title) _opt_element(handler, "link", self.link) self.publish_extensions(handler) _opt_element(handler, "description", self.description) _opt_element(handler, "author", self.author) for category in self.categories: if isinstance(category, basestring): category = Category(category) category.publish(handler) _opt_element(handler, "comments", self.comments) if self.enclosure is not None: self.enclosure.publish(handler) _opt_element(handler, "guid", self.guid) pubDate = self.pubDate if isinstance(pubDate, datetime.datetime): pubDate = DateElement("pubDate", pubDate) _opt_element(handler, "pubDate", pubDate) if self.source is not None: self.source.publish(handler) handler.endElement("item") def publish_extensions(self, handler): # Derived classes can hook into this to insert # output after the title and link elements pass weblog-0.6/weblog/__init__.py0000644000000000000000000000124210761173772016402 0ustar00usergroup00000000000000from utils import encode, escape_and_encode, load_configuration from post import Post, PostError from jinja_environment import jinja_environment from PyRSS2Gen import RSS2, RSSItem from html_full_uri import html_full_uri import listing __all__ = ['Post', 'PostError', 'RSS2', 'RSSItem', 'encode_text', 'escape_and_encode', 'listing', 'jinja_environment', 'load_configuration', 'html_full_uri'] if __name__ == '__main__': import doctest import utils import post import listing import html_full_uri doctest.testmod(utils) doctest.testmod(post) doctest.testmod(listing) doctest.testmod(html_full_uri) doctest.testmod() weblog-0.6/weblog/html_full_uri.py0000644000000000000000000001054510761173772017516 0ustar00usergroup00000000000000import re _scheme_regex = re.compile(r'\w+://') def external_uri(uri): ''' Returns True if ``uri`` refers to an external resource. >>> external_uri('http://www.google.ca/') True >>> external_uri('mailto://me@example.com') True >>> external_uri('/pic.jpg') False >>> external_uri('') False ''' if _scheme_regex.match(uri): return True else: return False from HTMLParser import HTMLParser from cStringIO import StringIO class FullUrlHtmlParser(HTMLParser): ''' >>> p = FullUrlHtmlParser('http://www.example.com') >>> p.feed('') >>> print p.buffer.getvalue() A more complex example:: >>> p.reset() >>> p.feed(r""" ... ... foo ... ... some random text. ... bar ... »~ ... ... ... More ..........""") >>> print p.buffer.getvalue() #doctest: +NORMALIZE_WHITESPACE foo some random text. bar »~ More .......... ''' def __init__(self, base_uri): HTMLParser.__init__(self) self.buffer = StringIO() self.base_uri = base_uri.rstrip('/') def reset(self): HTMLParser.reset(self) if hasattr(self, 'buffer'): del self.buffer self.buffer = StringIO() @staticmethod def html_attrs(attrs): return ' '.join('%s=\'%s\'' % (k, v) for k,v in attrs.iteritems()) def make_full_url(self, attr, attrs): if attr in attrs and not external_uri(attrs[attr]): attrs[attr] = '/'.join((self.base_uri, attrs[attr].lstrip('/'))) def check_and_rewrite_tag(self, tag, attrs, endtag=''): if attrs: attrs = dict(attrs) if tag == 'a': self.make_full_url('href', attrs) elif tag == 'img': self.make_full_url('src', attrs) elif tag == 'object': self.make_full_url('data', attrs) self.make_full_url('codebase', attrs) elif tag == 'script': self.make_full_url('src', attrs) self.buffer.write('<%s %s%s>' % (tag, self.html_attrs(attrs), endtag)) else: self.buffer.write('<%s%s>' % (tag, endtag)) def handle_starttag(self, tag, attrs): self.check_and_rewrite_tag(tag, attrs) def handle_startendtag(self, tag, attrs): self.check_and_rewrite_tag(tag, attrs, endtag='/') def handle_endtag(self, tag): self.buffer.write('' % tag) def handle_data(self, data): self.buffer.write(data) def handle_charref(self, name): self.buffer.write('&#%s;' % name) def handle_entityref(self, name): self.buffer.write('&%s;' % name) def handle_comment(self, comment): self.buffer.write('' % comment) def handle_decl(self, decl): self.buffer.write('' % decl) def handle_pi(self, pi): self.buffer.write('' % pi) def html_full_uri(base_url, text): ''' Appends ``base_uri`` to relative uri's in the HTML document ``text``. Example with ``base_uri=http://example.com``:: '' becomes '' '' becomes '' but ' is not changed since it is an *absolute* URI. >>> html_full_uri('http://example.com', '') "" >>> html_full_uri('http://example.com', '') "" ''' p = FullUrlHtmlParser(base_url) p.feed(text) return p.buffer.getvalue() if __name__ == '__main__': import doctest doctest.testmod() weblog-0.6/weblog/jinja_environment.py0000644000000000000000000000254110761173772020365 0ustar00usergroup00000000000000import os import sys from jinja import Environment, FileSystemLoader, PackageLoader, ChoiceLoader def jinja_environment(source_dir): """ Build the Jinja environment. Setup all template loaders. """ TEMPLATE_DIR = 'templates' fs_loader = FileSystemLoader(os.path.join(source_dir, TEMPLATE_DIR)) fs_app_loader = FileSystemLoader(os.path.join(sys.path[0], 'weblog', TEMPLATE_DIR)) # if setuptools is present use the loader else fake it. try: import pkg_resources except ImportError: pkg_loader = FileSystemLoader(os.path.join(os.path.dirname(__file__), TEMPLATE_DIR)) else: pkg_loader = PackageLoader('weblog', TEMPLATE_DIR) choice_loader = ChoiceLoader([fs_loader, fs_app_loader, pkg_loader]) env = Environment(loader=choice_loader, trim_blocks=True) # an extra filter def do_renderstring(): ''' Render the passed string. Useful to do things like: '{{ my_custom_template|renderstring }}' where my_custom_template == '{{ foo }} bar' ''' def wrapped(env, context, value): if value: return env.from_string(value).render(context.to_dict()) return wrapped env.filters['renderstring'] = do_renderstring return env weblog-0.6/weblog/listing.py0000644000000000000000000001064210761173772016320 0ustar00usergroup00000000000000from os.path import join class Page(object): ''' Page contains a `string key` named title used to compare against other `Page`s and strings. It is used for the pagination. The item can be whatever you want. >>> page_list = sorted([Page('2', 2), Page('3', None), Page('1', '1')]) >>> page_list [, , ] >>> for i in page_list: ... print i.title, i.item 1 1 2 2 3 None >>> '2' in page_list True >>> '5' in page_list False >>> page_list.index('2') 1 ''' def __init__(self, title, item): ''' >>> Page('foo', 'bar') ''' super(Page, self).__init__() self.title = title self.item = item def filename(self): return self.title + '.html' def url(self): ''' >>> Page('foo', 'bar').url() 'foo.html' ''' return self.filename() def __repr__(self): return '<%s(%r, %r)>' % \ (self.__class__.__name__, self.title, self.item) def __cmp__(self, other): ''' >>> Page('1', '') > '2' False >>> Page('foo', 'string') == 'foo' True >>> Page('2', '') > Page('4', '') False >>> Page('bar', '') == Page('bar', '') True >>> Page('1', '') > 0 # base object address comparison True ''' if isinstance(other, Page): return cmp(self.title, other.title) elif isinstance(other, self.title.__class__): return cmp(self.title, other) else: return cmp(id(self), id(object)) class PageIndex(Page): ''' special case to have a page that returns 'index' as filename ''' def filename(self): return 'index.html' def slice_list(full_list, limit): ''' return an iterable containing the given list sliced in sub-lists of size `limit`. >>> list(slice_list(range(10), 3)) [[0, 1, 2], [3, 4, 5], [6, 7, 8], [9]] >>> list(slice_list([], 10)) [] >>> list(slice_list([1, 2, 3], 5)) [[1, 2, 3]] >>> list(slice_list([1, 2, 3], 3)) [[1, 2, 3]] ''' it = iter(full_list) result = list() try: while True: for c in xrange(limit): result.append(it.next()) yield result result = list() except StopIteration: if result: yield result raise StopIteration def slice_list_groupby(full_list, func): ''' >>> slice_list_groupby(range(10), lambda x: 'even' if x % 2 == 0 else 'odd') {'even': [0, 2, 4, 6, 8], 'odd': [1, 3, 5, 7, 9]} >>> slice_list_groupby([1, 2, 3], lambda x: x) {1: [1], 2: [2], 3: [3]} >>> slice_list_groupby([dict(key='1'), dict(key='2'), dict(key='3')], ... lambda x: x['key']) {'1': [{'key': '1'}], '3': [{'key': '3'}], '2': [{'key': '2'}]} ''' d = dict() for i in full_list: key = func(i) if d.has_key(key): d[key].append(i) else: d[key] = [i] return d def generate_list(output_dir, template, page_list, template_params): for page in page_list: filename = join(output_dir, page.filename()) output_file = file(filename, 'w') output_file.write(template.render( post_list=page.item, page=page, pages=page_list, **template_params)) output_file.close() def generate_index_listing(limit, output_dir, template, post_list, template_params): ''' Generate a listing containing at most `limit` post per page. ''' # list() needed since pages is subscripted later pages = list(slice_list(post_list, limit)) if pages: pages = [PageIndex('0', pages[0])] + \ [Page(str(k + 1), v) for k, v in enumerate(pages[1:])] generate_list(output_dir, template, pages, template_params) def generate_monthly_listing(output_dir, template, post_list, template_params): pages = slice_list_groupby(post_list, lambda x: str(x.date.year) + '-' + str(x.date.month)) pages = list(Page(str(k), pages[k]) for k in (sorted(pages.keys()))) generate_list(output_dir, template, pages, template_params) if __name__ == '__main__': import doctest doctest.testmod() weblog-0.6/weblog/post.py0000644000000000000000000001532210761173772015634 0ustar00usergroup00000000000000import email import codecs from datetime import date from time import strptime from urllib import quote from cgi import escape from utils import encode, escape_and_encode class PostError(Exception): ''' Error in post file ''' def __init__(self, message, filename, line=None): super(PostError, self).__init__(self, message) self.message = message self.filename = filename self.line = line def __repr__(self): return '%s(%r, %r, %r)' % (self.__class__.__name__, self.exception, self.filename, self.line) def __str__(self): if self.line is None: return 'Error in post file %s: %s' % (self.filename, self.message) else: return 'Error in post file %s line %d: %s' % (self.filename, self.line, self.message) class Post(object): ENCODING = 'ascii' AUTHOR = 'unknown author' def __init__(self, f): ''' >>> file_content = """title: test ... date: 2008-1-1 ... author: test author ... encoding: utf-8 ... ... test.""" >>> from StringIO import StringIO >>> p = Post(StringIO(file_content)) >>> p >>> p.title == 'test' True >>> import datetime >>> p.date == datetime.date(2008, 1, 1) True >>> p.content == 'test.' True >>> p.encoding == 'utf-8' True >>> p.author == 'test author' True >>> Post(StringIO('title: no payload\\ndate: 2008-1-1')) >>> Post(StringIO('title: no date')) Traceback (most recent call last): ... PostError: Error in post file : no date defined >>> Post(StringIO("""title: bad encoding ... date: 2008-1-1 ... encoding: bad-encoding""")) Traceback (most recent call last): ... PostError: Error in post file : unknown encoding: \ bad-encoding >>> Post(StringIO("""title: bad date ... date: 200008-101-10""")) Traceback (most recent call last): ... PostError: Error in post file : unable to parse \ date '200008-101-10' (use YYYY-MM-DD format) ''' if isinstance(f, (str, unicode)): self._filename = f input_file = open(f) else: self._filename = None input_file = f e = email.message_from_file(input_file) for (key, value) in ((key.lower(), value) for (key, value) in e.items()): self.__dict__[key] = value if not hasattr(self, 'date'): raise PostError('no date defined', self.get_filename()) if not hasattr(self, 'encoding'): self.encoding = self.ENCODING if self.encoding.lower() != 'raw': try: codecs.lookup(self.encoding) except LookupError, e: raise PostError(e.message, self.get_filename()) if not hasattr(self, 'author'): self.author = self.AUTHOR try: self.date = self._parse_date(self.date) except ValueError, e: raise PostError(e.message, self.get_filename()) try: self.ascii_title = self.title if self.encoding == 'raw' else \ self.title.decode(self.encoding).\ encode('ascii', 'replace') self.title = escape_and_encode(self.title, self.encoding) self.author = escape_and_encode(self.author, self.encoding) self.content = encode(e.get_payload(), self.encoding) except UnicodeDecodeError, e: raise PostError('bad encoding in post file %s' % e, self.get_filename()) # FIXME prefix & suffix param or members of the class ? def url(self, prefix=''): ''' >>> file_content = """title: test ... date: 2008-1-1 ... ... test""" >>> from StringIO import StringIO >>> Post(StringIO(file_content)).url() '2008/1/1/test.html' >>> Post(StringIO(file_content)).url('prefix/') 'prefix/2008/1/1/test.html' >>> file_content = """title: Weird @!% filename ... date: 2008-1-1 ... ... test""" >>> Post(StringIO(file_content)).url() '2008/1/1/Weird%20%40%21%25%20filename.html' ''' return '%s%d/%d/%d/%s.html' % \ (prefix, self.date.year, self.date.month, self.date.day, quote(self.ascii_title)) @staticmethod def _parse_date(date_): """ >>> Post._parse_date('2006-1-1') datetime.date(2006, 1, 1) >>> Post._parse_date('2007-12-31') datetime.date(2007, 12, 31) >>> Post._parse_date('10000-1-1') Traceback (most recent call last): ... ValueError: unable to parse date '10000-1-1' (use YYYY-MM-DD format) >>> Post._parse_date(2007) Traceback (most recent call last): ... TypeError: expected string or buffer """ date_fmt_list = ['%Y-%m-%d', '%y-%m-%d', '%m/%d/%Y', '%m/%d/%y', '%d/%m/%y', '%d/%m/%Y'] date_tuple = None for date_fmt in date_fmt_list: try: date_tuple = strptime(date_, date_fmt) break except ValueError: continue if date_tuple is None: raise ValueError('unable to parse date %r ' '(use YYYY-MM-DD format)' % (date_)) return date(*date_tuple[0:3]) def get_filename(self): if not self._filename: return '' else: return self._filename def __cmp__(self, other): ''' >>> file1 = """title: 1 ... date: 2008-1-1 ... ... test""" >>> file2 = """title: 2 ... date: 2007-12-31""" >>> from StringIO import StringIO >>> Post(StringIO(file1)) > Post(StringIO(file2)) True >>> Post(StringIO(file1)) == Post(StringIO(file2)) False >>> Post(StringIO(file1)) == Post(StringIO(file1)) True >>> l = [Post(StringIO(file2)), Post(StringIO(file1))] >>> l.index(Post(StringIO(file1))) 1 ''' c = cmp(self.date, other.date) if c == 0: return cmp(self.title, other.title) else: return c def __hash__(self): return hash(self.date) def __repr__(self): return '<%s(%r, %r)>' % (self.__class__.__name__, self.title, self.date) if __name__ == '__main__': import doctest doctest.testmod() weblog-0.6/weblog/templates/base.html.tmpl0000644000000000000000000000111310761173772021037 0ustar00usergroup00000000000000 {% block title %}{{ title }}{% endblock %} {{ html_head|renderstring }} {% block extrahead %} {% endblock %} {{ html_header|renderstring }} {% block content %}{% endblock %} {{ html_footer|renderstring }} {# vim:set ft=htmljinja: #} weblog-0.6/weblog/templates/index.html.tmpl0000644000000000000000000000173610761173772021247 0ustar00usergroup00000000000000{% extends 'base.html.tmpl' %} {% block content %}

{{ title }}

{% if description %}

{{ description }}

{% endif %} {% for post in post_list %}

{{ post.title }}

{{ post.date }}, by {{ post.author | urlize }}

{{ post.content }}
{% endfor %} {% if pages|length > 1 %} {% endif %} {% endblock %} {# vim:set ft=htmljinja: #} weblog-0.6/weblog/templates/post.html.tmpl0000644000000000000000000000053210761173772021116 0ustar00usergroup00000000000000{% extends 'base.html.tmpl' %} {% block content %}

{{ title }}

{{ date }}, by {{ author | urlize }}

{{ content }}

back to the blog

{% endblock %} {# vim:set ft=htmljinja: #} weblog-0.6/weblog/utils.py0000644000000000000000000000716510761173772016015 0ustar00usergroup00000000000000import os import sys from cgi import escape from ConfigParser import SafeConfigParser, NoOptionError def encode(text, encoding, errors='xmlcharrefreplace'): ''' >>> encode('foo & bar', 'ascii') 'foo & bar' >>> encode('\\xdcTF-8 ?', 'raw') '\\xdcTF-8 ?' >>> encode('\\xdcTF-8 ?', 'latin-1') 'ÜTF-8 ?' >>> encode(u'\\xdcTF-8 ?', 'UTF-8') 'ÜTF-8 ?' ''' if encoding.lower() == 'raw': return text elif isinstance(text, unicode): return text.encode('ascii', errors) else: return text.decode(encoding).encode('ascii', errors) def escape_and_encode(text, encoding, errors='xmlcharrefreplace'): ''' >>> escape_and_encode('<>&', 'ascii') '<>&' >>> escape_and_encode(u'\\xdcTF-8', 'utf-8') 'ÜTF-8' >>> escape_and_encode('\\xdcTF-8', 'latin-1') 'ÜTF-8' >>> escape_and_encode('\\xdcTF-8 &<>', 'raw') '\\xdcTF-8 &<>' ''' if encoding.lower() == 'raw': return text else: return encode(escape(text), encoding, errors) def load_if_filename(source_dir, f): ''' If ``f`` is a filename. Read it and returns the content. Else return ``f``. If ``bool(f)`` is false returns ``None``. ''' if not f: return full = os.path.join(source_dir, f) if os.path.exists(full): return file(full).read() else: return f def load_configuration(config_file, source_dir=None): ''' Read the file ``config_file`` and sanitise it. Returns a dictionnary containing the parameters from the [weblog] section. >>> from StringIO import StringIO >>> config_file = StringIO(""" ... [weblog] ... title = Test title ... url = http://example.com ... description = Example blog""") >>> load_configuration(config_file) #doctest: +NORMALIZE_WHITESPACE {'url': 'http://example.com/', 'rss_post_limit': 10, 'description': 'Example blog', 'post_per_page': 10, 'title': 'Test title'} ''' config = SafeConfigParser() if isinstance(config_file, basestring): config_file = os.path.join(source_dir or '', config_file) if not os.path.exists(config_file): raise IOError('Unable to find configuration file %s' % config_file) config.read(config_file) else: config.readfp(config_file) config_dict = dict(config.items('weblog')) try: config_dict['title'] = encode(config_dict['title'], config_dict.get('encoding', 'ascii')) blog_base_url = config_dict['url'] if blog_base_url and not blog_base_url.endswith('/'): blog_base_url += '/' config_dict['url'] = blog_base_url def _load_if_filename(key): if config_dict.get(key): config_dict[key] = load_if_filename(source_dir, config_dict[key]) _load_if_filename('html_head') _load_if_filename('html_header') _load_if_filename('html_footer') def config_set_int(key, default): try: config_dict[key] = int(config_dict.get(key, default)) except ValueError, e: sys.exit('In config file \'%s\'\n' '%s is not an integer: %s' % \ (config_file, key, e)) config_set_int('post_per_page', 10) config_set_int('rss_post_limit', 10) except KeyError, e: sys.exit('Unable to find %s in configuration file \'%s\'' % \ (e, CONFIG_FILE)) else: return config_dict if __name__ == '__main__': import doctest doctest.testmod() weblog-0.6/weblog_publish.py0000644000000000000000000001325110761173772016374 0ustar00usergroup00000000000000import os import sys import datetime from shutil import copy from optparse import OptionParser from weblog import RSS2, RSSItem, Post, PostError, encode, load_configuration from weblog.listing import generate_index_listing from weblog import jinja_environment from weblog import html_full_uri def load_post_list(path): post_list = set() for post_filename in os.listdir(path): if post_filename.endswith('.html'): p = Post(os.path.join(path, post_filename)) if p in post_list: for post in post_list: if post == p: duplicated_post_filename = post.get_filename() break raise IOError('"%s" There is already a post ' 'with this title and date ("%s")' % \ (post_filename, duplicated_post_filename)) else: post_list.add(p) return post_list def generate_post_html(post_list, output_dir, post_tmpl, params): for post in post_list: def make_dir(dir): if os.path.exists(dir): if not os.path.isdir(dir): raise IOError('%s exists and is not a directory' % dir) else: os.mkdir(dir) dir = output_dir for d in (post.date.year, post.date.month, post.date.day): dir = os.path.join(dir, str(d)) make_dir(dir) filename = os.path.join(dir, post.ascii_title + '.html') if os.path.exists(filename) and not os.path.isfile(filename): raise IOError('%s exists and is not a file' % filename) output = file(filename, 'w') top_dir = '../../../' output.write(post_tmpl.render(title=post.title, date=post.date, author=post.author, content=html_full_uri(top_dir, post.content), top_dir=top_dir, **dict(((k, v) for k, v in params.iteritems() if k != 'title')))) def generate_rss(post_list, filename, params): rss_items = [] for post in post_list: rss_items.append( RSSItem( title=post.title, link=post.url(prefix=params['url']), description=html_full_uri(params['url'], post.content), guid=post.url(prefix=params['url']), pubDate=datetime.datetime(*(post.date.timetuple()[:3])))) rss = RSS2( title = params['title'], link = params['url'], description = params['description'], lastBuildDate = datetime.datetime.now(), items=rss_items) rss.write_xml(open(filename, "w")) def main(source_dir, output_dir): # hardcoded configuration file. Might be a good idea to make it customizabe. CONFIG_FILE = 'weblog.ini' try: config = load_configuration(CONFIG_FILE, source_dir or '.') except IOError, e: sys.exit(e) source_dir = source_dir or config.get('source_dir', '.') output_dir = output_dir or config.get('output_dir', 'output') author = config.get('author', None) # add the default author & encoding constant to the post class if 'encoding' in config: Post.ENCODING = config['encoding'] if author: Post.AUTHOR = author if not os.path.exists(output_dir): os.mkdir(output_dir) env = jinja_environment(source_dir) try: post_list = list(reversed(sorted(load_post_list(source_dir)))) except (IOError, PostError), e: print 'Error while loading input files ...' sys.exit(e) def generate_all(): params = dict( title=config['title'], description=config['description'], url=config['url'], html_head=config.get('html_head'), html_header=config.get('html_header'), html_footer=config.get('html_footer')) # generate the main index page index_template = env.get_template('index.html.tmpl') generate_index_listing(config.get('post_per_page'), output_dir, index_template, post_list, params) post_tmpl = env.get_template('post.html.tmpl') generate_post_html(post_list, output_dir, post_tmpl, params) generate_rss(post_list[:config.get('rss_post_limit')], os.path.join(output_dir, 'rss.xml'), params) for f in config.get('extra_files', '').split(): copy(os.path.join(source_dir, f), output_dir) if False: generate_all() else: try: generate_all() except IOError, e: print 'Error while generating files ...' sys.exit(e) else: print 'Successfully generated weblog.' if __name__ == '__main__': parser = OptionParser() parser.add_option("-s", "--source-dir", dest="source_dir", help="The source directory where the blog posts and the" "file weblog.ini are located", metavar="DIR") parser.add_option("-o", "--output-dir", dest="output_dir", help="The directory where all the generated files will" "be written. If it does not exist it will be created.", metavar="DIR") (options, args) = parser.parse_args() if args: parser.error('unknown extra arguments: %s' % ' '.join(args)) else: main(options.source_dir, options.output_dir)