./weblog+jinja-0.8/0000755000175000017500000000000011006524413013114 5ustar henryhenry./weblog+jinja-0.8/test/0000755000175000017500000000000011006524411014071 5ustar henryhenry./weblog+jinja-0.8/test/full_uri/0000755000175000017500000000000011006524411015712 5ustar henryhenry./weblog+jinja-0.8/test/full_uri/weblog.ini0000644000175000017500000000016311006524411017672 0ustar henryhenry[weblog] title=Test blog url=http://blog.test.org description=Test blog author=test encoding=utf-8 ./weblog+jinja-0.8/test/full_uri/utf-8.html0000644000175000017500000000025011006524411017540 0ustar henryhenrytitle: UTF-8 post ÖÉÈÄ ... date: 2008-02-03 Öéèä äyÔÀ Weblog ./weblog+jinja-0.8/test/encoding/0000755000175000017500000000000011006524411015657 5ustar henryhenry./weblog+jinja-0.8/test/encoding/weblog.ini0000644000175000017500000000016311006524411017637 0ustar henryhenry[weblog] title=Test blog url=http://blog.test.org description=Test blog author=test encoding=utf-8 ./weblog+jinja-0.8/test/encoding/latin-1.html0000644000175000017500000000010411006524411020005 0ustar henryhenrytitle: latin post ÖÉÈÄ ... date: 2008-02-04 encoding: latin-1 Öéèä ./weblog+jinja-0.8/test/encoding/utf-8.html0000644000175000017500000000007211006524411017507 0ustar henryhenrytitle: UTF-8 post ÖÉÈÄ ... date: 2008-02-03 Öéèä ./weblog+jinja-0.8/test/empty/0000755000175000017500000000000011006524411015227 5ustar henryhenry./weblog+jinja-0.8/test/empty/weblog.ini0000644000175000017500000000014411006524411017206 0ustar henryhenry[weblog] title=Test blog url=http://blog.test.org description=Test blog author=test ./weblog+jinja-0.8/test/simple/0000755000175000017500000000000011006524411015362 5ustar henryhenry./weblog+jinja-0.8/test/simple/post2.html0000644000175000017500000000004411006524411017315 0ustar henryhenrytitle: post2 date: 2007-6-15 post2 ./weblog+jinja-0.8/test/simple/weblog.ini0000644000175000017500000000014411006524411017341 0ustar henryhenry[weblog] title=Test blog url=http://blog.test.org description=Test blog author=test ./weblog+jinja-0.8/test/simple/post3.html0000644000175000017500000000004511006524411017317 0ustar henryhenrytitle: post3 date: 2007-12-31 post3 ./weblog+jinja-0.8/test/simple/post1.html0000644000175000017500000000004511006524411017315 0ustar henryhenrytitle: post1 date: 2007-01-01 post1 ./weblog+jinja-0.8/setup.py0000644000175000017500000000261111006524411014624 0ustar henryhenrytry: from setuptools import setup except: from distutils.core import setup import os version = '0.8' f = open(os.path.join(os.path.dirname(__file__), 'doc', 'weblog.rst')) # The long description has to be ascii encoded ... long_description = f.read().strip().decode('utf-8').encode('ascii', 'replace') f.close() setup(name="weblog", version=version, packages=['weblog'], package_data={'weblog': ['templates/*.tmpl']}, scripts=['bin/weblog'], requires=['Jinja (>=1.1)'], install_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 = ('Simple blog publisher. It reads structured text ' 'files and generates static HTML / RSS files. Weblog aims ' 'to be simple and robust.'), long_description=long_description, license = "ISC", keywords = "weblog blog journal diary 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+jinja-0.8/weblog_run.py0000755000175000017500000000037511006524411015637 0ustar henryhenry#!/usr/bin/env python import imp from os import path filename = path.join(path.dirname(__file__), 'bin', 'weblog') module = imp.load_module('weblog_executable', file(filename), filename, ('', 'r', imp.PY_SOURCE)) module.main() ./weblog+jinja-0.8/bin/0000755000175000017500000000000011006524411013662 5ustar henryhenry./weblog+jinja-0.8/bin/weblog0000755000175000017500000000424511006524411015074 0ustar henryhenry#!/usr/bin/env python # vim:set filetype=python: import os import sys import datetime import logging from shutil import copy from optparse import OptionParser, SUPPRESS_HELP from weblog import command_publish, command_date _COMMANDS = ('publish', 'date') def 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 are " "written. If it does not exist it is created.", metavar="DIR") parser.add_option('-q', '--quiet', dest='quiet', default=False, action='store_true', help='Do not output anything except critical error ' 'messages') parser.add_option('--debug', dest='debug', default=False, action='store_true', help=SUPPRESS_HELP) parser.set_usage('%%prog [option] command\n\nCommands:\n %s' % \ '\n '.join(_COMMANDS)) (options, args) = parser.parse_args() if options.debug: logging.basicConfig(level=logging.DEBUG, format='%(levelname)s %(message)s') elif options.quiet: logging.basicConfig(level=logging.CRITICAL, format='%(messages)s') else: logging.basicConfig(level=logging.INFO, format='%(message)s') if not args: logging.warning('Warning: No command specified, assuming \'publish\'.\n' ' To remove this warning, type: %s publish' % sys.argv[0]) command = 'publish' else: command = args.pop(0) if command not in _COMMANDS: parser.error('invalid command \'%s\'' % command) elif command == 'publish': command_publish(args, options) elif command == 'date': command_date(args, options) if __name__ == '__main__': main() ./weblog+jinja-0.8/doc/0000755000175000017500000000000011006524411013657 5ustar henryhenry./weblog+jinja-0.8/doc/weblog.rst0000644000175000017500000002345511006524411015701 0ustar henryhenryWeblog manual ============= :Author: Henry Prêcheur :Reviewers: Anis Kadri, Bastien Simondi, Eric Salama Abstract -------- Simple blog publisher. It reads structured text files and generates static HTML / RSS files. Weblog aims to be simple and robust. In this 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.5+ - Jinja version 1.1+. 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 using the helper script ``weblog_run.py``. Or install it using the supplied ``setup.py`` script. Run ``python setup.py --help`` to learn how to use it. Alternatively if easy_install is present, simply type:: easy_install weblog It fetches the latest version of Weblog and installs it. Quick Start ----------- In the following examples ``weblog/`` represents Weblog's installation directory. If you downloaded the source tarball without installing Weblog; Use the helper script ``weblog_run.py`` instead of the ``weblog`` command:: $ python /path/to/weblog/weblog_run.py --help 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 using the publish command:: $ cd my_blog/ $ weblog publish It should create a directory named ``output`` containing the generated files. Look at the results by opening the file ``output/index.html`` in your web-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`` is mandatory. ``date`` and ``author`` are optional. If you don't fill these fields, the author is the one specified in ``weblog.ini``, and the post's date is the post file's last modification date. 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:: $ weblog publish Reload the page in your browser. You should see a second post with some formating. The default post file encoding is ASCII. To use a different encoding 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 ... Specify the default encoding in ``weblog.ini``, to avoid setting the encoding field for every file. While writing your blog post, don't bother about the ``date`` field immediately. Weblog automatically sets the date to the filename's last modification time. A good practice though is to set the date when the post gets published. By doing so the date won't get changed if the file gets copied. To set the date of a post, use the command ``date``:: $ date Mon Apr 14 00:10:44 PDT 2008 $ cat my_blog_post.html title: My blog post This is a blog post without any date. $ weblog date my_blog_post.html Setting date to 2008-04-14 00:12:22 in file my_blog_post.html $ cat my_blog_post.html title: My blog post date: 2008-04-14 00:12:22 This is a blog post without any date. $ weblog date my_blog_post 2008-5-15 Setting date to 2008-05-15 in file my_blog_post.html $ cat my_blog_post.html title: My blog post date: 2008-05-15 This is a blog post without any date. The ``date`` command accepts 3 formats as argument: - YEAR-MONTH-DAY (2008-01-31) - YEAR-MONTH-DAY HOUR:MINUTE (2008-01-31 16:45) - YEAR-MONTH-DAY HOUR:MINUTE:SECONDS (2008-01-31 16:45:14) For conciseness the ``date`` command uses aliases to specify commonly used date: - now - today (like now but only set the date, not the time) - tomorrow (now + 24 hours) - next_day (like tomorrow but only sets the date, not the time) 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 is *never* 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. The title ``Hello World`` is escaped. HTML tags appear, and no formating is applied to ``world``. The original text "Hello World" appears instead of "Hello *World*", It is possible to override this by specifying ``raw`` as the encoding. Using the ``raw`` encoding nothing is 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 If the ``raw`` encoding is used, all the characters must be ASCII characters. Otherwise an error is reported. How URI's are handled --------------------- Relative links (````) are rewritten in the RSS file and in some HTML files. In the RSS file ``base_url`` is prepended to the link to make sure it always points to the correct URI. Absolute links (````) are not rewritten. It should always point to the correct location regardless of the context. Note that Weblog considers ``/`` as the root directory. If ``base_url`` is ``http://example.com/``; ``test.html`` and ``/test.html`` are both rewritten to ``http://example.com/test.html``. Command line parameters ----------------------- Usage: weblog [options] Options: -h, --help show this help message and exit -s DIR, --source-dir=DIR The source directory where the blog posts and the file weblog.ini are located -o DIR, --output-dir=DIR The directory where all the generated files are written. If it does not exist it is created. -q, --quiet Do not output anything except critical error messages 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 sample configuration file:: [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 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:: description: My blog about configuration files. The description is merged to a single line; ``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 are 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. 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+jinja-0.8/test.py0000644000175000017500000001264311006524411014451 0ustar henryhenryimport os import shutil import tempfile import unittest import StringIO import email import datetime from optparse import Values from weblog import Post, PostError, jinja_environment from weblog.publish import load_post_list, generate_rss, generate_index_listing from weblog.date import command_date from weblog.publish import command_publish 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.DEFAULT_ENCODING = 'ascii' self.assertRaises(PostError, load_post_list, 'test/encoding/') def test_load_post_list_encoding(self): Post.DEFAULT_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')) def test_date(self): filename = os.path.join(self.tempdir, 'set_date.html') # First test a message without any date defined def file_without_date(): file(filename, 'w').write('title: Some title\n\nSome content') file_without_date() command_date([filename, '2008-1-1'], None) message = email.message_from_file(file(filename)) self.assert_('date' in message) self.assertEqual(message['date'], str(datetime.date(2008, 1, 1))) # Then test a file which has already a date def file_with_date(): file(filename, 'w').write('title: Some title\ndate: 2008-12-31\n' '\nSome content') file_with_date() command_date([filename, '2008-1-1'], None) message = email.message_from_file(file(filename)) self.assert_('date' in message) self.assertEqual(message['date'], str(datetime.date(2008, 1, 1))) # Test aliases for alias in ('now', 'today', 'tomorrow', 'next_day'): file_without_date() command_date([filename, alias], None) message = email.message_from_file(file(filename)) self.assert_('date' in message) def _test_publish(self, dirname): options = Values(dict(source_dir=os.path.join(os.path.dirname(__file__), 'test', dirname), output_dir=self.tempdir, debug=False)) command_publish(None, options) def test_publish_empty(self): self._test_publish('empty') def test_publish_encoding(self): self._test_publish('encoding') def test_publish_full_uri(self): self._test_publish('full_uri') def test_publish_simple(self): self._test_publish('simple') if __name__ == '__main__': unittest.main() ./weblog+jinja-0.8/weblog/0000755000175000017500000000000011006524411014371 5ustar henryhenry./weblog+jinja-0.8/weblog/utils.py0000644000175000017500000001053511006524411016107 0ustar henryhenryimport 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') Traceback (most recent call last): ... UnicodeDecodeError: 'ascii' codec can't decode byte 0xdc in position 0: \ ordinal not in range(128) >>> encode('\\xdcTF-8 ?', 'latin-1') 'ÜTF-8 ?' >>> encode(u'\\xdcTF-8 ?', 'UTF-8') 'ÜTF-8 ?' ''' if encoding.lower() == 'raw': return text.encode('ascii') 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'): ''' Escapes '&', '<' and '>' to HTML-safe sequences and encode the text to the specified encoding. >>> 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') Traceback (most recent call last): ... UnicodeDecodeError: 'ascii' codec can't decode byte 0xdc in position 0: \ ordinal not in range(128) >>> escape_and_encode('UTF-8 &<>', 'raw') 'UTF-8 &<>' ''' if encoding.lower() == 'raw': return text.encode('ascii') 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``. # Assumes that there is no file named 'This is not a file' in the current # directory ;-) >>> load_if_filename('.', 'This is not a file') 'This is not a file' >>> load_if_filename('.', '') >>> load_if_filename('.', list()) >>> load_if_filename('.', 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 key in config_dict: 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+jinja-0.8/weblog/__init__.py0000644000175000017500000000130111006524411016475 0ustar henryhenryfrom utils import load_configuration from post import Post, PostError from jinja_environment import jinja_environment from html_full_uri import html_full_uri from publish import command_publish from date import command_date import listing __all__ = ('Post', 'PostError', 'listing', 'jinja_environment', 'load_configuration', 'html_full_uri', 'command_publish', 'command_date') if __name__ == '__main__': import doctest import utils import post import listing import html_full_uri import date doctest.testmod(utils) doctest.testmod(post) doctest.testmod(listing) doctest.testmod(html_full_uri) doctest.testmod(date) doctest.testmod() ./weblog+jinja-0.8/weblog/post.py0000644000175000017500000002075611006524411015742 0ustar henryhenryimport email import codecs import logging from os import stat from datetime import datetime, date 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): DEFAULT_ENCODING = 'ascii' DEFAULT_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 [[HH:MM]:SS] format) ''' if isinstance(f, (str, basestring)): self._filename = f input_file = open(f) else: self._filename = None input_file = f post_file = email.message_from_file(input_file) self.__dict__.update((key.lower(), value) for (key, value) in post_file.items()) if not hasattr(self, 'encoding'): self.encoding = self.DEFAULT_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.DEFAULT_AUTHOR # Handle the date. If no date was specified use the file's modification # time. if not hasattr(self, 'date'): # Get the date from file's mtime and issue a warning if not self._filename: raise PostError('No date defined', self.get_filename()) else: self.date = datetime.\ fromtimestamp(stat(self._filename).st_mtime) logging.warning('No date defined in \'%s\', using the ' \ 'file\'s last modification time instead.' % \ self._filename) else: try: self.date = self.parse_date(self.date) except ValueError, e: raise PostError(e.message, self.get_filename()) try: self.ascii_title = self.title.decode('ascii' if self.encoding == 'raw' else self.encoding).\ encode('ascii', 'replace') self.title = escape_and_encode(self.title, self.encoding) except UnicodeDecodeError, e: raise PostError('Bad encoding in title', self.get_filename()) try: self.author = escape_and_encode(self.author, self.encoding) except UnicodeDecodeError, e: raise PostError('Bad encoding in author', self.get_filename()) try: self.content = encode(post_file.get_payload(), self.encoding) except UnicodeDecodeError, e: # find error line number for line_number, line in enumerate(post_file.as_string().\ splitlines()): try: line.decode('ascii' if self.encoding == 'raw' else self.encoding) except UnicodeDecodeError, e: break # line_number starts at 0, real line number == line_number + 1 raise PostError('Bad encoding in content line %d, %s' % \ (line_number + 1, 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)) _DATE_FORMAT_LIST = ('%Y-%m-%d', '%y-%m-%d') _DATETIME_FORMAT_LIST = \ tuple('%s %%H:%%M' % f for f in _DATE_FORMAT_LIST) + \ tuple('%s %%H:%%M:%%S' % f for f in _DATE_FORMAT_LIST) @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('2008-4-05 12:35') datetime.datetime(2008, 4, 5, 12, 35) >>> Post.parse_date('10000-1-1') Traceback (most recent call last): ... ValueError: Unable to parse date '10000-1-1' (Use YYYY-MM-DD [[HH:MM]:SS] format) >>> Post.parse_date(2007) Traceback (most recent call last): ... TypeError: strptime() argument 1 must be string, not int """ for date_format in Post._DATE_FORMAT_LIST: try: return datetime.strptime(date_, date_format).date() except ValueError: continue for date_format in Post._DATETIME_FORMAT_LIST: try: return datetime.strptime(date_, date_format) except ValueError: continue raise ValueError('Unable to parse date %r\n' '(Use YYYY-MM-DD [[HH:MM]:SS] format)' % (date_)) 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 ''' return cmp(str(self.date) + str(self.title), str(other.date) + str(self.title)) def __hash__(self): return hash(str(self.date) + self.title) def __repr__(self): return '<%s(%r, %r)>' % (self.__class__.__name__, self.title, self.date) if __name__ == '__main__': import doctest doctest.testmod() ./weblog+jinja-0.8/weblog/publish.py0000644000175000017500000001315311006524411016414 0ustar henryhenryimport os import datetime import logging from sys import exit from shutil import copy import jinja_environment from PyRSS2Gen import RSS2, RSSItem from html_full_uri import html_full_uri from post import Post, PostError from utils import load_configuration from listing import generate_index_listing def load_post_list(path): ''' List and load all the files ending with '.html' in the passed directory. Returns a list containing ``Post`` objects created using the loaded files. ''' post_list = set() for filename in os.listdir(path): if filename.endswith('.html'): logging.debug('Loading \'%s\'', filename) p = Post(os.path.join(path, filename)) if p in post_list: logging.debug('%r is duplicated', p) for duplicated_post in post_list: if duplicated_post == p: break raise IOError('"%s", there is already a post ' 'with this title and date ("%s")' % \ (filename, duplicated_post.get_filename())) else: post_list.add(p) else: logging.debug('Ignoring \'%s\'', filename) return post_list def generate_post_html(post_list, output_dir, post_tmpl, params): for post in post_list: logging.debug('Generating HTML file for %r', post) dir = os.path.join(output_dir, str(post.date.year), str(post.date.month), str(post.date.day)) if not os.path.exists(dir): logging.debug('Creating \'%s\'', dir) os.makedirs(dir) elif not os.path.isdir(dir): raise IOError('\'%s\' already exists and is not a directory' % dir) filename = os.path.join(dir, post.ascii_title + '.html') 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): def make_rss_item(post): return 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=post.date) rss = RSS2(title = params['title'], link = params['url'], description = params['description'], lastBuildDate = datetime.datetime.now(), items=(make_rss_item(post) for post in post_list)) rss.write_xml(open(filename, "w")) def command_publish(args, options): source_dir = options.source_dir output_dir = options.output_dir # hard-coded configuration file. Might be a good idea to make it # customizable. CONFIG_FILE = 'weblog.ini' try: config = load_configuration(CONFIG_FILE, source_dir or '.') except IOError, e: logging.error('Error while loading configuration file \'%s\'' % \ CONFIG_FILE) exit(e) source_dir = source_dir or config.get('source_dir', '.') output_dir = output_dir or config.get('output_dir', 'output') # add the default author & encoding constant to the post class if 'encoding' in config: Post.DEFAULT_ENCODING = config['encoding'] author = config.get('author', None) if author: Post.DEFAULT_AUTHOR = author if not os.path.exists(output_dir): os.mkdir(output_dir) env = jinja_environment.jinja_environment(source_dir) try: post_list = list(reversed(sorted(load_post_list(source_dir)))) except (IOError, PostError), e: logging.error('Error while loading post files.') 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 logging.debug('Generating HTML listings') index_template = env.get_template('index.html.tmpl') generate_index_listing(config['post_per_page'], output_dir, index_template, post_list, params) logging.debug('Generating HTML posts files') post_tmpl = env.get_template('post.html.tmpl') generate_post_html(post_list, output_dir, post_tmpl, params) generate_rss(post_list[:config['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 options.debug: generate_all() else: try: generate_all() except IOError, e: logging.error('Error while generating files ...') exit(e) else: logging.info('Successfully generated weblog.') ./weblog+jinja-0.8/weblog/html_full_uri.py0000644000175000017500000001072211006524411017612 0ustar henryhenryimport 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): ''' Parse an HTML document and transform relative URI to absolute URI. Prepending ``base_uri`` to them. >>> 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+jinja-0.8/weblog/PyRSS2Gen.py0000644000175000017500000003432411006524411016445 0ustar henryhenry"""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. if isinstance(dt, datetime.datetime): hour = dt.hour minute = dt.minute second = dt.second elif isinstance(dt, datetime.date): hour = minute = second = 0 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, hour, minute, 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.date 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, datetime.date)): pubDate = DateElement("pubDate", pubDate) _opt_element(handler, "pubDate", pubDate) lastBuildDate = self.lastBuildDate if isinstance(lastBuildDate, (datetime.datetime, datetime.date)): 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, datetime.date)): 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+jinja-0.8/weblog/templates/0000755000175000017500000000000011006524411016367 5ustar henryhenry./weblog+jinja-0.8/weblog/templates/post.html.tmpl0000644000175000017500000000053711006524411021222 0ustar henryhenry{% extends 'base.html.tmpl' %} {% block content %}

{{ title }}

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

{{ content }}

back to the blog

{% endblock %} {# vim:set ft=htmljinja: #} ./weblog+jinja-0.8/weblog/templates/index.html.tmpl0000644000175000017500000000173611006524411021346 0ustar henryhenry{% 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+jinja-0.8/weblog/templates/base.html.tmpl0000644000175000017500000000114211006524411021140 0ustar henryhenry {% block title %}{{ title }}{% endblock %} {{ html_head|renderstring }} {% block extrahead %} {% endblock %} {{ html_header|renderstring }}
{% block content %}{% endblock %}
{{ html_footer|renderstring }} {# vim:set ft=htmljinja: #} ./weblog+jinja-0.8/weblog/date.py0000644000175000017500000000403211006524411015657 0ustar henryhenryimport sys import logging import datetime from post import Post def command_date(args, options): ''' Execute the 'date' command, which set the date to the specified filename. The command need at least one parameter. The remaining parameters are the date to be set in the file. >>> command_date(None, None) # doctest: +ELLIPSIS Traceback (most recent call last): ... SystemExit: No file specified: ... >>> command_date(['/dev/null', '2008-1000-10'], None) Traceback (most recent call last): ... SystemExit: Unable to parse date '2008-1000-10' (Use YYYY-MM-DD [[HH:MM]:SS] format) ''' if not args: sys.exit(StandardError('No file specified:\n %s date filename [date]' % sys.argv[0])) filename = args.pop(0) if args: if len(args) == 1 and args[0] == 'today': date = datetime.date.today() elif len(args) == 1 and args[0] == 'next_day': date = datetime.date.today() + datetime.timedelta(days=1) elif len(args) == 1 and args[0] == 'tomorrow': date = datetime.datetime.now() + datetime.timedelta(days=1) elif len(args) == 1 and args[0] == 'now': date = datetime.datetime.now() else: try: date = Post.parse_date(' '.join(args)) except ValueError, e: sys.exit(e) else: date = datetime.datetime.now() def _date_string(date): if isinstance(date, datetime.datetime): return date.strftime('%Y-%m-%d %H:%M:%S') else: return str(date) logging.info('Setting date to %s in file %s', _date_string(date), filename) import email try: post_file = email.message_from_file(file(filename)) if 'date' in post_file: post_file.replace_header('date', _date_string(date)) else: post_file.add_header('date', _date_string(date)) file(filename, 'w').write(post_file.as_string()) except IOError, e: sys.exit(e) ./weblog+jinja-0.8/weblog/listing.py0000644000175000017500000001122611006524411016416 0ustar henryhenryfrom 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.html' as filename. Used for the first page of the listing. ''' 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 key in d: 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:])] else: # If there is no page generates an 'empty' page pages = [PageIndex('0', None)] 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+jinja-0.8/weblog/jinja_environment.py0000644000175000017500000000331411006524411020463 0ustar henryhenryimport os import sys try: from jinja import Environment, FileSystemLoader, PackageLoader, ChoiceLoader except ImportError: sys.exit('Please install Jinja (http://jinja.pocoo.org/) to use Weblog') 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) def do_renderstring(): def wrapped(env, context, value): ''' Render the passed string. It is similar to the tag rendertemplate, except it uses the passed string as the template. Example: The template 'Hello {{ string_template|renderstring }}!'; Called with the following context: dict(string_template='{{ foo }} world', foo='crazy') Renders to: 'Hello crazy world!' ''' if value: return env.from_string(value).render(context.to_dict()) return wrapped env.filters['renderstring'] = do_renderstring return env ./weblog+jinja-0.8/TODO0000644000175000017500000000026411006524411013604 0ustar henryhenry-- v0.9 --- Add link checker smart error reporting documentation tips on uploading doc on templating & styling --- v1.0 --- Stable release: - more unit tests - more docs! ./weblog+jinja-0.8/COPYING0000644000175000017500000000467011006524411014154 0ustar henryhenryCopyright (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+jinja-0.8/jinja/0000755000175000017500000000000011006524413014207 5ustar henryhenry./weblog+jinja-0.8/jinja/utils.py0000644000175000017500000004555011006524413015732 0ustar henryhenry# -*- coding: utf-8 -*- """ jinja.utils ~~~~~~~~~~~ Utility functions. **license information**: some of the regular expressions and the ``urlize`` function were taken from the django framework. :copyright: 2007 by Armin Ronacher, Lawrence Journal-World. :license: BSD, see LICENSE for more details. """ import re import sys import string from types import MethodType, FunctionType from jinja import nodes from jinja.exceptions import SecurityException, TemplateNotFound from jinja.datastructure import TemplateData # the python2.4 version of deque is missing the remove method # because a for loop with a lookup for the missing value written # in python is slower we just use deque if we have python2.5 or higher try: from collections import deque deque.remove except (ImportError, AttributeError): class deque(list): """ Minimal subclass of list that provides the deque interface used by the native `BaseContext` and the `CacheDict` """ def appendleft(self, item): list.insert(self, 0, item) def popleft(self): return list.pop(self, 0) def clear(self): del self[:] # support for a working reversed() in 2.3 try: reversed = reversed except NameError: def reversed(iterable): if hasattr(iterable, '__reversed__'): return iterable.__reversed__() try: return iter(iterable[::-1]) except TypeError: return iter(tuple(iterable)[::-1]) # set support for python 2.3 try: set = set except NameError: from sets import Set as set # sorted support (just a simplified version) try: sorted = sorted except NameError: _cmp = cmp def sorted(seq, cmp=None, key=None, reverse=False): rv = list(seq) if key is not None: cmp = lambda a, b: _cmp(key(a), key(b)) rv.sort(cmp) if reverse: rv.reverse() return rv # group by support try: from itertools import groupby except ImportError: class groupby(object): def __init__(self, iterable, key=lambda x: x): self.keyfunc = key self.it = iter(iterable) self.tgtkey = self.currkey = self.currvalue = xrange(0) def __iter__(self): return self def next(self): while self.currkey == self.tgtkey: self.currvalue = self.it.next() self.currkey = self.keyfunc(self.currvalue) self.tgtkey = self.currkey return (self.currkey, self._grouper(self.tgtkey)) def _grouper(self, tgtkey): while self.currkey == tgtkey: yield self.currvalue self.currvalue = self.it.next() self.currkey = self.keyfunc(self.currvalue) #: function types callable_types = (FunctionType, MethodType) #: number of maximal range items MAX_RANGE = 1000000 _word_split_re = re.compile(r'(\s+)') _punctuation_re = re.compile( '^(?P(?:%s)*)(?P.*?)(?P(?:%s)*)$' % ( '|'.join([re.escape(p) for p in ('(', '<', '<')]), '|'.join([re.escape(p) for p in ('.', ',', ')', '>', '\n', '>')]) ) ) _simple_email_re = re.compile(r'^\S+@[a-zA-Z0-9._-]+\.[a-zA-Z0-9._-]+$') #: used by from_string as cache _from_string_env = None def escape(s, quote=None): """ SGML/XML escape an unicode object. """ s = s.replace("&", "&").replace("<", "<").replace(">", ">") if not quote: return s return s.replace('"', """) def urlize(text, trim_url_limit=None, nofollow=False): """ Converts any URLs in text into clickable links. Works on http://, https:// and www. links. Links can have trailing punctuation (periods, commas, close-parens) and leading punctuation (opening parens) and it'll still do the right thing. If trim_url_limit is not None, the URLs in link text will be limited to trim_url_limit characters. If nofollow is True, the URLs in link text will get a rel="nofollow" attribute. """ trim_url = lambda x, limit=trim_url_limit: limit is not None \ and (x[:limit] + (len(x) >=limit and '...' or '')) or x words = _word_split_re.split(text) nofollow_attr = nofollow and ' rel="nofollow"' or '' for i, word in enumerate(words): match = _punctuation_re.match(word) if match: lead, middle, trail = match.groups() if middle.startswith('www.') or ( '@' not in middle and not middle.startswith('http://') and len(middle) > 0 and middle[0] in string.letters + string.digits and ( middle.endswith('.org') or middle.endswith('.net') or middle.endswith('.com') )): middle = '%s' % (middle, nofollow_attr, trim_url(middle)) if middle.startswith('http://') or \ middle.startswith('https://'): middle = '%s' % (middle, nofollow_attr, trim_url(middle)) if '@' in middle and not middle.startswith('www.') and \ not ':' in middle and _simple_email_re.match(middle): middle = '%s' % (middle, middle) if lead + middle + trail != word: words[i] = lead + middle + trail return u''.join(words) def from_string(source): """ Create a template from the template source. """ global _from_string_env if _from_string_env is None: from jinja.environment import Environment _from_string_env = Environment() return _from_string_env.from_string(source) #: minor speedup _getattr = getattr def get_attribute(obj, name): """ Return the attribute from name. Raise either `AttributeError` or `SecurityException` if something goes wrong. """ if not isinstance(name, basestring): raise AttributeError(name) if name[:2] == name[-2:] == '__': raise SecurityException('not allowed to access internal attributes') if getattr(obj, '__class__', None) in callable_types and \ name.startswith('func_') or name.startswith('im_'): raise SecurityException('not allowed to access function attributes') r = _getattr(obj, 'jinja_allowed_attributes', None) if r is not None and name not in r: raise SecurityException('disallowed attribute accessed') # attribute lookups convert unicode strings to ascii bytestrings. # this process could raise an UnicodeEncodeError. try: return _getattr(obj, name) except UnicodeError: raise AttributeError(name) def safe_range(start, stop=None, step=None): """ "Safe" form of range that does not generate too large lists. """ if step is None: step = 1 if stop is None: r = xrange(0, start, step) else: r = xrange(start, stop, step) if len(r) > MAX_RANGE: def limit(): i = 0 for item in r: i += 1 yield item if i >= MAX_RANGE: break return list(limit()) return list(r) def generate_lorem_ipsum(n=5, html=True, min=20, max=100): """ Generate some lorem impsum for the template. """ from jinja.constants import LOREM_IPSUM_WORDS from random import choice, random, randrange words = LOREM_IPSUM_WORDS.split() result = [] for _ in xrange(n): next_capitalized = True last_comma = last_fullstop = 0 word = None last = None p = [] # each paragraph contains out of 20 to 100 words. for idx, _ in enumerate(xrange(randrange(min, max))): while True: word = choice(words) if word != last: last = word break if next_capitalized: word = word.capitalize() next_capitalized = False # add commas if idx - randrange(3, 8) > last_comma: last_comma = idx last_fullstop += 2 word += ',' # add end of sentences if idx - randrange(10, 20) > last_fullstop: last_comma = last_fullstop = idx word += '.' next_capitalized = True p.append(word) # ensure that the paragraph ends with a dot. p = u' '.join(p) if p.endswith(','): p = p[:-1] + '.' elif not p.endswith('.'): p += '.' result.append(p) if not html: return u'\n\n'.join(result) return u'\n'.join([u'

%s

' % escape(x) for x in result]) def watch_changes(env, context, iterable, *attributes): """ Wise replacement for ``{% ifchanged %}``. """ # find the attributes to watch if attributes: tests = [] tmp = [] for attribute in attributes: if isinstance(attribute, (str, unicode, int, long, bool)): tmp.append(attribute) else: tests.append(tuple(attribute)) if tmp: tests.append(tuple(attribute)) last = tuple([object() for x in tests]) # or no attributes if we watch the object itself else: tests = None last = object() # iterate trough it and keep check the attributes or values for item in iterable: if tests is None: cur = item else: cur = tuple([env.get_attributes(item, x) for x in tests]) if cur != last: changed = True last = cur else: changed = False yield changed, item watch_changes.jinja_context_callable = True def render_included(env, context, template_name): """ Works like djangos {% include %} tag. It doesn't include the template but load it independently and renders it to a string. """ #XXX: ignores parent completely! tmpl = env.get_template(template_name) return tmpl.render(context.to_dict()) render_included.jinja_context_callable = True # python2.4 and lower has a bug regarding joining of broken generators. # because of the runtime debugging system we have to keep track of the # number of frames to skip. that's what RUNTIME_EXCEPTION_OFFSET is for. try: _test_singleton = object() def _test_gen_bug(): raise TypeError(_test_singleton) yield None ''.join(_test_gen_bug()) except TypeError, e: if e.args and e.args[0] is _test_singleton: capture_generator = u''.join RUNTIME_EXCEPTION_OFFSET = 1 else: capture_generator = lambda gen: u''.join(tuple(gen)) RUNTIME_EXCEPTION_OFFSET = 2 del _test_singleton, _test_gen_bug def pformat(obj, verbose=False): """ Prettyprint an object. Either use the `pretty` library or the builtin `pprint`. """ try: from pretty import pretty return pretty(obj, verbose=verbose) except ImportError: from pprint import pformat return pformat(obj) def buffereater(f, template_data=False): """ Used by the python translator to capture output of substreams. (macros, filter sections etc) """ def wrapped(*a, **kw): __traceback_hide__ = True rv = capture_generator(f(*a, **kw)) if template_data: rv = TemplateData(rv) return rv return wrapped def empty_block(context): """ An empty callable that just returns an empty decorator. Used to represent empty blocks. """ if 0: yield None def collect_translations(ast): """ Collect all translatable strings for the given ast. The return value is a list of tuples in the form ``(lineno, singular, plural)``. If a translation doesn't require a plural form the third item is `None`. """ todo = [ast] result = [] while todo: node = todo.pop() if node.__class__ is nodes.Trans: result.append((node.lineno, node.singular, node.plural)) elif node.__class__ is nodes.CallExpression and \ node.node.__class__ is nodes.NameExpression and \ node.node.name == '_': if len(node.args) == 1 and not node.kwargs and not node.dyn_args \ and not node.dyn_kwargs and \ node.args[0].__class__ is nodes.ConstantExpression: result.append((node.lineno, node.args[0].value, None)) todo.extend(node.get_child_nodes()) result.sort(lambda a, b: cmp(a[0], b[0])) return result class DebugHelper(object): """ Debugging Helper. Available in the template as "debug". """ jinja_context_callable = True jinja_allowed_attributes = ['filters'] def __init__(self): raise TypeError('cannot create %r instances' % self.__class__.__name__) def __call__(self, env, context): """Print a nice representation of the context.""" return pformat(context.to_dict(), verbose=True) def filters(self, env, context, builtins=True): """List the filters.""" from inspect import getdoc strip = set() if not builtins: from jinja.defaults import DEFAULT_FILTERS strip = set(DEFAULT_FILTERS.values()) filters = env.filters.items() filters.sort(lambda a, b: cmp(a[0].lower(), b[0].lower())) result = [] for name, f in filters: if f in strip: continue doc = '\n'.join([' ' + x for x in (getdoc(f) or '').splitlines()]) result.append('`%s`\n\n%s' % (name, doc)) return '\n\n'.join(result) filters.jinja_context_callable = True def tests(self, env, context, builtins=True): """List the tests.""" from inspect import getdoc strip = set() if not builtins: from jinja.defaults import DEFAULT_TESTS strip = set(DEFAULT_TESTS.values()) tests = env.tests.items() tests.sort(lambda a, b: cmp(a[0].lower(), b[0].lower())) result = [] for name, f in tests: if f in strip: continue doc = '\n'.join([' ' + x for x in (getdoc(f) or '').splitlines()]) result.append('`%s`\n\n%s' % (name, doc)) return '\n\n'.join(result) tests.jinja_context_callable = True def __str__(self): print 'use debug() for debugging the context' #: the singleton instance of `DebugHelper` debug_helper = object.__new__(DebugHelper) class CacheDict(object): """ A dict like object that stores a limited number of items and forgets about the least recently used items:: >>> cache = CacheDict(3) >>> cache['A'] = 0 >>> cache['B'] = 1 >>> cache['C'] = 2 >>> len(cache) 3 If we now access 'A' again it has a higher priority than B:: >>> cache['A'] 0 If we add a new item 'D' now 'B' will disappear:: >>> cache['D'] = 3 >>> len(cache) 3 >>> 'B' in cache False If you iterate over the object the most recently used item will be yielded First:: >>> for item in cache: ... print item D A C If you want to iterate the other way round use ``reverse(cache)``. Implementation note: This is not a nice way to solve that problem but for smaller capacities it's faster than a linked list. Perfect for template environments where you don't expect too many different keys. """ def __init__(self, capacity): self.capacity = capacity self._mapping = {} self._queue = deque() # alias all queue methods for faster lookup self._popleft = self._queue.popleft self._pop = self._queue.pop self._remove = self._queue.remove self._append = self._queue.append def copy(self): """ Return an shallow copy of the instance. """ rv = CacheDict(self.capacity) rv._mapping.update(self._mapping) rv._queue = self._queue[:] return rv def get(self, key, default=None): """ Return an item from the cache dict or `default` """ if key in self: return self[key] return default def setdefault(self, key, default=None): """ Set `default` if the key is not in the cache otherwise leave unchanged. Return the value of this key. """ if key in self: return self[key] self[key] = default return default def clear(self): """ Clear the cache dict. """ self._mapping.clear() self._queue.clear() def __contains__(self, key): """ Check if a key exists in this cache dict. """ return key in self._mapping def __len__(self): """ Return the current size of the cache dict. """ return len(self._mapping) def __repr__(self): return '<%s %r>' % ( self.__class__.__name__, self._mapping ) def __getitem__(self, key): """ Get an item from the cache dict. Moves the item up so that it has the highest priority then. Raise an `KeyError` if it does not exist. """ rv = self._mapping[key] if self._queue[-1] != key: self._remove(key) self._append(key) return rv def __setitem__(self, key, value): """ Sets the value for an item. Moves the item up so that it has the highest priority then. """ if key in self._mapping: self._remove(key) elif len(self._mapping) == self.capacity: del self._mapping[self._popleft()] self._append(key) self._mapping[key] = value def __delitem__(self, key): """ Remove an item from the cache dict. Raise an `KeyError` if it does not exist. """ del self._mapping[key] self._remove(key) def __iter__(self): """ Iterate over all values in the cache dict, ordered by the most recent usage. """ return reversed(self._queue) def __reversed__(self): """ Iterate over the values in the cache dict, oldest items coming first. """ return iter(self._queue) __copy__ = copy def __deepcopy__(self): """ Return a deep copy of the cache dict. """ from copy import deepcopy rv = CacheDict(self.capacity) rv._mapping = deepcopy(self._mapping) rv._queue = deepcopy(self._queue) return rv NAMESPACE = { 'range': safe_range, 'debug': debug_helper, 'lipsum': generate_lorem_ipsum, 'watchchanges': watch_changes, 'rendertemplate': render_included } ./weblog+jinja-0.8/jinja/__init__.py0000644000175000017500000000456111006524413016326 0ustar henryhenry# -*- coding: utf-8 -*- """ jinja ~~~~~ Jinja is a `sandboxed`_ template engine written in pure Python. It provides a `Django`_ like non-XML syntax and compiles templates into executable python code. It's basically a combination of Django templates and python code. Nutshell -------- Here a small example of a Jinja template:: {% extends 'base.html' %} {% block title %}Memberlist{% endblock %} {% block content %} {% endblock %} Philosophy ---------- Application logic is for the controller but don't try to make the life for the template designer too hard by giving him too few functionality. For more informations visit the new `jinja webpage`_ and `documentation`_. Note ---- This is the Jinja 1.0 release which is completely incompatible with the old "pre 1.0" branch. The old branch will still receive security updates and bugfixes but the 1.0 branch will be the only version that receives support. If you have an application that uses Jinja 0.9 and won't be updated in the near future the best idea is to ship a Jinja 0.9 checkout together with the application. The `Jinja tip`_ is installable via `easy_install` with ``easy_install Jinja==dev``. .. _sandboxed: http://en.wikipedia.org/wiki/Sandbox_(computer_security) .. _Django: http://www.djangoproject.com/ .. _jinja webpage: http://jinja.pocoo.org/ .. _documentation: http://jinja.pocoo.org/documentation/index.html .. _Jinja tip: http://dev.pocoo.org/hg/jinja-main/archive/tip.tar.gz#egg=Jinja-dev :copyright: 2007 by Armin Ronacher. :license: BSD, see LICENSE for more details. """ from jinja.environment import Environment from jinja.datastructure import Markup from jinja.plugin import jinja_plugin_factory as template_plugin_factory from jinja.loaders import FileSystemLoader, PackageLoader, DictLoader, \ ChoiceLoader, FunctionLoader, MemcachedFileSystemLoader from jinja.utils import from_string __all__ = ['Environment', 'Markup', 'FileSystemLoader', 'PackageLoader', 'DictLoader', 'ChoiceLoader', 'FunctionLoader', 'MemcachedFileSystemLoader', 'from_string'] __version__ = '1.2' ./weblog+jinja-0.8/jinja/exceptions.py0000644000175000017500000000355111006524413016746 0ustar henryhenry# -*- coding: utf-8 -*- """ jinja.exceptions ~~~~~~~~~~~~~~~~ Jinja exceptions. :copyright: 2007 by Armin Ronacher. :license: BSD, see LICENSE for more details. """ class TemplateError(RuntimeError): pass class SecurityException(TemplateError): """ Raise if the template designer tried to do something dangerous. """ class FilterNotFound(KeyError, TemplateError): """ Raised if a filter does not exist. """ def __init__(self, message): KeyError.__init__(self, message) class FilterArgumentError(TypeError, TemplateError): """ An argument passed to the filter was invalid. """ def __init__(self, message): TypeError.__init__(self, message) class TestNotFound(KeyError, TemplateError): """ Raised if a test does not exist. """ def __init__(self, message): KeyError.__init__(self, message) class TestArgumentError(TypeError, TemplateError): """ An argument passed to a test function was invalid. """ def __init__(self, message): TypeError.__init__(self, message) class TemplateNotFound(IOError, TemplateError): """ Raised if a template does not exist. """ def __init__(self, name): IOError.__init__(self, name) self.name = name class TemplateSyntaxError(SyntaxError, TemplateError): """ Raised to tell the user that there is a problem with the template. """ def __init__(self, message, lineno, filename): SyntaxError.__init__(self, message) self.lineno = lineno self.filename = filename class TemplateRuntimeError(TemplateError): """ Raised by the template engine if a tag encountered an error when rendering. """ class TemplateIncludeError(TemplateError): """ Raised by the `ControlledLoader` if recursive includes where detected. """ ./weblog+jinja-0.8/jinja/_debugger.c0000644000175000017500000000304011006524413016273 0ustar henryhenry/** * Jinja Extended Debugger * ~~~~~~~~~~~~~~~~~~~~~~~ * * this module allows the jinja debugger to set the tb_next flag * on traceback objects. This is required to inject a traceback into * another one. * * For better windows support (not everybody has a visual studio 2003 * at home) it would be a good thing to have a ctypes implementation, but * because the struct is not exported there is currently no sane way. * * :copyright: 2007 by Armin Ronacher. * :license: BSD, see LICENSE for more details. */ #include /** * set the tb_next attribute of a traceback object */ static PyObject * tb_set_next(PyObject *self, PyObject *args) { PyTracebackObject *tb, *old; PyObject *next; if (!PyArg_ParseTuple(args, "O!O:tb_set_next", &PyTraceBack_Type, &tb, &next)) return NULL; if (next == Py_None) next = NULL; else if (!PyTraceBack_Check(next)) { PyErr_SetString(PyExc_TypeError, "tb_set_next arg 2 must be traceback or None"); return NULL; } else Py_INCREF(next); old = tb->tb_next; tb->tb_next = (PyTracebackObject*)next; Py_XDECREF(old); Py_INCREF(Py_None); return Py_None; } static PyMethodDef module_methods[] = { {"tb_set_next", (PyCFunction)tb_set_next, METH_VARARGS, "Set the tb_next member of a traceback object."}, {NULL, NULL, 0, NULL} /* Sentinel */ }; #ifndef PyMODINIT_FUNC /* declarations for DLL import/export */ #define PyMODINIT_FUNC void #endif PyMODINIT_FUNC init_debugger(void) { PyObject *module = Py_InitModule3("jinja._debugger", module_methods, ""); if (!module) return; } ./weblog+jinja-0.8/jinja/lexer.py0000644000175000017500000004677011006524413015716 0ustar henryhenry# -*- coding: utf-8 -*- """ jinja.lexer ~~~~~~~~~~~ This module implements a Jinja / Python combination lexer. The `Lexer` class provided by this module is used to do some preprocessing for Jinja. On the one hand it filters out invalid operators like the bitshift operators we don't allow in templates. On the other hand it separates template code and python code in expressions. Because of some limitations in the compiler package which are just natural but annoying for Jinja, the lexer also "escapes" non names that are not keywords. The Jinja parser then removes those escaping marks again. This is required in order to make "class" and some other python keywords we don't use valid identifiers. :copyright: 2007 by Armin Ronacher. :license: BSD, see LICENSE for more details. """ import re import unicodedata from jinja.datastructure import TokenStream, Token from jinja.exceptions import TemplateSyntaxError from jinja.utils import set, sorted from weakref import WeakValueDictionary __all__ = ['Lexer', 'Failure', 'keywords'] # cache for the lexers. Exists in order to be able to have multiple # environments with the same lexer _lexer_cache = WeakValueDictionary() # static regular expressions whitespace_re = re.compile(r'\s+(?um)') name_re = re.compile(r'[a-zA-Z_][a-zA-Z0-9_]*') string_re = re.compile(r"('([^'\\]*(?:\\.[^'\\]*)*)'" r'|"([^"\\]*(?:\\.[^"\\]*)*)")(?ms)') integer_re = re.compile(r'\d+') float_re = re.compile(r'\d+\.\d+') regex_re = re.compile(r'@/([^/\\]*(?:\\.[^/\\]*)*)*/[a-z]*(?ms)') # set of used keywords keywords = set(['and', 'block', 'cycle', 'elif', 'else', 'endblock', 'endfilter', 'endfor', 'endif', 'endmacro', 'endraw', 'endtrans', 'extends', 'filter', 'for', 'if', 'in', 'include', 'is', 'macro', 'not', 'or', 'pluralize', 'raw', 'recursive', 'set', 'trans', 'print', 'call', 'endcall']) # bind operators to token types operators = { '+': 'add', '-': 'sub', '/': 'div', '//': 'floordiv', '*': 'mul', '%': 'mod', '**': 'pow', '~': 'tilde', '!': 'bang', '@': 'at', '[': 'lbracket', ']': 'rbracket', '(': 'lparen', ')': 'rparen', '{': 'lbrace', '}': 'rbrace', '==': 'eq', '!=': 'ne', '>': 'gt', '>=': 'gteq', '<': 'lt', '<=': 'lteq', '=': 'assign', '.': 'dot', ':': 'colon', '|': 'pipe', ',': 'comma' } reverse_operators = dict([(v, k) for k, v in operators.iteritems()]) assert len(operators) == len(reverse_operators), 'operators dropped' operator_re = re.compile('(%s)' % '|'.join([re.escape(x) for x in sorted(operators, key=lambda x: -len(x))])) def unescape_string(lineno, filename, s): r""" Unescape a string. Supported escapes: \a, \n, \r\, \f, \v, \\, \", \', \0 \x00, \u0000, \U00000000, \N{...} Not supported are \101 because imho redundant. """ result = [] write = result.append simple_escapes = { 'a': '\a', 'n': '\n', 'r': '\r', 'f': '\f', 't': '\t', 'v': '\v', '\\': '\\', '"': '"', "'": "'", '0': '\x00' } unicode_escapes = { 'x': 2, 'u': 4, 'U': 8 } chariter = iter(s) next_char = chariter.next try: for char in chariter: if char == '\\': char = next_char() if char in simple_escapes: write(simple_escapes[char]) elif char in unicode_escapes: seq = [next_char() for x in xrange(unicode_escapes[char])] try: write(unichr(int(''.join(seq), 16))) except ValueError: raise TemplateSyntaxError('invalid unicode codepoint', lineno, filename) elif char == 'N': if next_char() != '{': raise TemplateSyntaxError('no name for codepoint', lineno, filename) seq = [] while True: char = next_char() if char == '}': break seq.append(char) try: write(unicodedata.lookup(u''.join(seq))) except KeyError: raise TemplateSyntaxError('unknown character name', lineno, filename) else: write('\\' + char) else: write(char) except StopIteration: raise TemplateSyntaxError('invalid string escape', lineno, filename) return u''.join(result) def unescape_regex(s): """ Unescape rules for regular expressions. """ buffer = [] write = buffer.append in_escape = False for char in s: if in_escape: in_escape = False if char not in safe_chars: write('\\' + char) continue write(char) return u''.join(buffer) class Failure(object): """ Class that raises a `TemplateSyntaxError` if called. Used by the `Lexer` to specify known errors. """ def __init__(self, message, cls=TemplateSyntaxError): self.message = message self.error_class = cls def __call__(self, lineno, filename): raise self.error_class(self.message, lineno, filename) class LexerMeta(type): """ Metaclass for the lexer that caches instances for the same configuration in a weak value dictionary. """ def __call__(cls, environment): key = hash((environment.block_start_string, environment.block_end_string, environment.variable_start_string, environment.variable_end_string, environment.comment_start_string, environment.comment_end_string, environment.trim_blocks)) # use the cached lexer if possible if key in _lexer_cache: return _lexer_cache[key] # create a new lexer and cache it lexer = type.__call__(cls, environment) _lexer_cache[key] = lexer return lexer class Lexer(object): """ Class that implements a lexer for a given environment. Automatically created by the environment class, usually you don't have to do that. Note that the lexer is not automatically bound to an environment. Multiple environments can share the same lexer. """ __metaclass__ = LexerMeta def __init__(self, environment): # shortcuts c = lambda x: re.compile(x, re.M | re.S) e = re.escape # lexing rules for tags tag_rules = [ (whitespace_re, None, None), (float_re, 'float', None), (integer_re, 'integer', None), (name_re, 'name', None), (string_re, 'string', None), (regex_re, 'regex', None), (operator_re, 'operator', None) ] #: if variables and blocks have the same delimiters we won't #: receive any variable blocks in the parser. This variable is `True` #: if we need that. self.no_variable_block = ( (environment.variable_start_string is environment.variable_end_string is None) or (environment.variable_start_string == environment.block_start_string and environment.variable_end_string == environment.block_end_string) ) # assamble the root lexing rule. because "|" is ungreedy # we have to sort by length so that the lexer continues working # as expected when we have parsing rules like <% for block and # <%= for variables. (if someone wants asp like syntax) # variables are just part of the rules if variable processing # is required. root_tag_rules = [ ('comment', environment.comment_start_string), ('block', environment.block_start_string) ] if not self.no_variable_block: root_tag_rules.append(('variable', environment.variable_start_string)) root_tag_rules.sort(lambda a, b: cmp(len(b[1]), len(a[1]))) # block suffix if trimming is enabled block_suffix_re = environment.trim_blocks and '\\n?' or '' # global lexing rules self.rules = { 'root': [ # directives (c('(.*?)(?:%s)' % '|'.join( ['(?P(?:\s*%s\-|%s)\s*raw\s*%s)' % ( e(environment.block_start_string), e(environment.block_start_string), e(environment.block_end_string) )] + [ '(?P<%s_begin>\s*%s\-|%s)' % (n, e(r), e(r)) for n, r in root_tag_rules ])), ('data', '#bygroup'), '#bygroup'), # data (c('.+'), 'data', None) ], # comments 'comment_begin': [ (c(r'(.*?)((?:\-%s\s*|%s)%s)' % ( e(environment.comment_end_string), e(environment.comment_end_string), block_suffix_re )), ('comment', 'comment_end'), '#pop'), (c('(.)'), (Failure('Missing end of comment tag'),), None) ], # blocks 'block_begin': [ (c('(?:\-%s\s*|%s)%s' % ( e(environment.block_end_string), e(environment.block_end_string), block_suffix_re )), 'block_end', '#pop'), ] + tag_rules, # raw block 'raw_begin': [ (c('(.*?)((?:\s*%s\-|%s)\s*endraw\s*(?:\-%s\s*|%s%s))' % ( e(environment.block_start_string), e(environment.block_start_string), e(environment.block_end_string), e(environment.block_end_string), block_suffix_re )), ('data', 'raw_end'), '#pop'), (c('(.)'), (Failure('Missing end of raw directive'),), None) ] } # only add the variable rules to the list if we process variables # the variable_end_string variable could be None and break things. if not self.no_variable_block: self.rules['variable_begin'] = [ (c('\-%s\s*|%s' % ( e(environment.variable_end_string), e(environment.variable_end_string) )), 'variable_end', '#pop') ] + tag_rules def tokenize(self, source, filename=None): """ Works like `tokeniter` but returns a tokenstream of tokens and not a generator or token tuples. Additionally all token values are already converted into types and postprocessed. For example keywords are already keyword tokens, not named tokens, comments are removed, integers and floats converted, strings unescaped etc. """ def generate(): for lineno, token, value in self.tokeniter(source, filename): if token in ('comment_begin', 'comment', 'comment_end'): continue elif token == 'data': try: value = str(value) except UnicodeError: pass elif token == 'name': value = str(value) if value in keywords: token = value value = '' elif token == 'string': value = unescape_string(lineno, filename, value[1:-1]) try: value = str(value) except UnicodeError: pass elif token == 'regex': args = value[value.rfind('/') + 1:] value = unescape_regex(value[2:-(len(args) + 1)]) if args: value = '(?%s)%s' % (args, value) elif token == 'integer': value = int(value) elif token == 'float': value = float(value) elif token == 'operator': token = operators[value] value = '' yield Token(lineno, token, value) return TokenStream(generate(), filename) def tokeniter(self, source, filename=None): """ This method tokenizes the text and returns the tokens in a generator. Use this method if you just want to tokenize a template. The output you get is not compatible with the input the jinja parser wants. The parser uses the `tokenize` function with returns a `TokenStream` and keywords instead of just names. """ source = '\n'.join(source.splitlines()) pos = 0 lineno = 1 stack = ['root'] statetokens = self.rules['root'] source_length = len(source) balancing_stack = [] while True: # tokenizer loop for regex, tokens, new_state in statetokens: m = regex.match(source, pos) # if no match we try again with the next rule if not m: continue # we only match blocks and variables if brances / parentheses # are balanced. continue parsing with the lower rule which # is the operator rule. do this only if the end tags look # like operators if balancing_stack and \ tokens in ('variable_end', 'block_end'): continue # tuples support more options if isinstance(tokens, tuple): for idx, token in enumerate(tokens): # hidden group if token is None: g = m.group(idx) if g: lineno += g.count('\n') continue # failure group elif token.__class__ is Failure: raise token(lineno, filename) # bygroup is a bit more complex, in that case we # yield for the current token the first named # group that matched elif token == '#bygroup': for key, value in m.groupdict().iteritems(): if value is not None: yield lineno, key, value lineno += value.count('\n') break else: raise RuntimeError('%r wanted to resolve ' 'the token dynamically' ' but no group matched' % regex) # normal group else: data = m.group(idx + 1) if data: yield lineno, token, data lineno += data.count('\n') # strings as token just are yielded as it, but just # if the data is not empty else: data = m.group() # update brace/parentheses balance if tokens == 'operator': if data == '{': balancing_stack.append('}') elif data == '(': balancing_stack.append(')') elif data == '[': balancing_stack.append(']') elif data in ('}', ')', ']'): if not balancing_stack: raise TemplateSyntaxError('unexpected "%s"' % data, lineno, filename) expected_op = balancing_stack.pop() if expected_op != data: raise TemplateSyntaxError('unexpected "%s", ' 'expected "%s"' % (data, expected_op), lineno, filename) # yield items if tokens is not None: if data: yield lineno, tokens, data lineno += data.count('\n') # fetch new position into new variable so that we can check # if there is a internal parsing error which would result # in an infinite loop pos2 = m.end() # handle state changes if new_state is not None: # remove the uppermost state if new_state == '#pop': stack.pop() # resolve the new state by group checking elif new_state == '#bygroup': for key, value in m.groupdict().iteritems(): if value is not None: stack.append(key) break else: raise RuntimeError('%r wanted to resolve the ' 'new state dynamically but' ' no group matched' % regex) # direct state name given else: stack.append(new_state) statetokens = self.rules[stack[-1]] # we are still at the same position and no stack change. # this means a loop without break condition, avoid that and # raise error elif pos2 == pos: raise RuntimeError('%r yielded empty string without ' 'stack change' % regex) # publish new function and start again pos = pos2 break # if loop terminated without break we havn't found a single match # either we are at the end of the file or we have a problem else: # end of text if pos >= source_length: return # something went wrong raise TemplateSyntaxError('unexpected char %r at %d' % (source[pos], pos), lineno, filename) ./weblog+jinja-0.8/jinja/datastructure.py0000644000175000017500000004762111006524413017465 0ustar henryhenry# -*- coding: utf-8 -*- """ jinja.datastructure ~~~~~~~~~~~~~~~~~~~ Module that helds several data types used in the template engine. :copyright: 2007 by Armin Ronacher. :license: BSD, see LICENSE for more details. """ from jinja.exceptions import TemplateSyntaxError, TemplateRuntimeError _missing = object() def contextcallable(f): """ Mark a function context callable. """ f.jinja_context_callable = True return f def unsafe(f): """ Mark function as unsafe. """ f.jinja_unsafe_call = True return f def make_undefined(implementation): """ Creates an undefined singleton based on a given implementation. It performs some tests that make sure the undefined type implements everything it should. """ self = object.__new__(implementation) self.__reduce__() return self class AbstractUndefinedType(object): """ Base class for any undefined type. """ __slots__ = () def __init__(self): raise TypeError('cannot create %r instances' % self.__class__.__name__) def __setattr__(self, name, value): raise AttributeError('%r object has no attribute %r' % ( self.__class__.__name__, name )) def __eq__(self, other): return self is other def __ne__(self, other): return self is not other def __copy__(self): return self __deepcopy__ = __copy__ def __repr__(self): return 'Undefined' def __reduce__(self): raise TypeError('undefined objects have to provide a __reduce__') class SilentUndefinedType(AbstractUndefinedType): """ An object that does not exist. """ __slots__ = () def __add__(self, other): """Any operator returns the operand.""" return other __sub__ = __mul__ = __div__ = __rsub__ = __rmul__ = __div__ = __mod__ =\ __radd__ = __rmod__ = __add__ def __getitem__(self, arg): """Getting any item returns `Undefined`""" return self def __iter__(self): """Iterating over `Undefined` returns an empty iterator.""" if False: yield None def __getattr__(self, arg): """Getting any attribute returns `Undefined`""" return self def __nonzero__(self): """`Undefined` is considered boolean `False`""" return False def __len__(self): """`Undefined` is an empty sequence""" return 0 def __str__(self): """The string representation is empty.""" return '' def __unicode__(self): """The unicode representation is empty.""" return u'' def __int__(self): """Converting `Undefined` to an integer ends up in ``0``""" return 0 def __float__(self): """Converting `Undefined` to an float ends up in ``0.0``""" return 0.0 def __call__(self, *args, **kwargs): """Calling `Undefined` returns `Undefined`""" return self def __reduce__(self): """Helper for pickle.""" return 'SilentUndefined' class ComplainingUndefinedType(AbstractUndefinedType): """ An object that does not exist. """ __slots__ = () def __len__(self): """Getting the length raises error.""" raise TemplateRuntimeError('Operated on undefined object') def __iter__(self): """Iterating over `Undefined` raises an error.""" raise TemplateRuntimeError('Iterated over undefined object') def __nonzero__(self): """`Undefined` is considered boolean `False`""" return False def __str__(self): """The string representation raises an error.""" raise TemplateRuntimeError('Undefined object rendered') def __unicode__(self): """The unicode representation raises an error.""" self.__str__() def __call__(self, *args, **kwargs): """Calling `Undefined` returns `Undefined`""" raise TemplateRuntimeError('Undefined object called') def __reduce__(self): """Helper for pickle.""" return 'ComplainingUndefined' #: the singleton instances for the undefined objects SilentUndefined = make_undefined(SilentUndefinedType) ComplainingUndefined = make_undefined(ComplainingUndefinedType) #: jinja 1.0 compatibility Undefined = SilentUndefined UndefinedType = SilentUndefinedType class FakeTranslator(object): """ Default null translator. """ def gettext(self, s): """ Translate a singular string. """ return s def ngettext(self, s, p, n): """ Translate a plural string. """ if n == 1: return s return p class Deferred(object): """ Object marking an deferred value. Deferred objects are objects that are called first access in the context. """ def __init__(self, factory): self.factory = factory def __call__(self, context, name): return self.factory(context.environment, context, name) class Markup(unicode): """ Compatibility for Pylons and probably some other frameworks. It's only used in Jinja environments with `auto_escape` set to true. """ def __html__(self): return unicode(self) class TemplateData(Markup): """ Subclass of unicode to mark objects that are coming from the template. The autoescape filter can use that. """ # import these here because those modules import Deferred and Undefined # from this module. try: # try to use the c implementation of the base context if available from jinja._speedups import BaseContext except ImportError: # if there is no c implementation we go with a native python one from jinja._native import BaseContext class Context(BaseContext): """ Dict like object containing the variables for the template. """ def __init__(self, *args, **kwargs): environment = args[0] if not kwargs and len(args) == 2 and isinstance(args[1], dict): base = args[1] else: base = dict(*args[1:], **kwargs) super(Context, self).__init__(environment.undefined_singleton, environment.globals, base) self._translate_func = None self.cache = {} self.environment = environment def to_dict(self): """ Convert the context into a dict. This skips the globals. """ result = {} for layer in self.stack[1:]: for key, value in layer.iteritems(): if key.startswith('::'): continue result[key] = value return result def set_nonlocal(self, name, value): """ Set a value in an outer scope. """ for layer in self.stack[:0:-1]: if name in layer: layer[name] = value return self.initial[name] = value def translate_func(self): """ The translation function for this context. It takes 4 parameters. The singular string, the optional plural one, The name of the variable in the replacements dict and the replacements dict. This is only used by the i18n system internally the simplified version (just one argument) is available in the template for the user too. """ if self._translate_func is not None: return self._translate_func translator = self.environment.get_translator(self) gettext = translator.gettext ngettext = translator.ngettext def translate(s, p=None, n=None, r=None): if p is None: s = gettext(s) else: s = ngettext(s, p, r[n]) # apply replacement substitution only if replacements # are given. This is the case for {% trans %}...{% endtrans %} # but for the "_()" syntax and a trans tag without a body. if r is not None: return s % r return s translate.__doc__ = Context.translate_func.__doc__ self._translate_func = translate return translate translate_func = property(translate_func, doc=translate_func.__doc__) def __repr__(self): """ String representation of the context. """ return 'Context(%r)' % self.to_dict() def __pretty__(self, p, cycle): if cycle: return p.text('Context({...})') p.begin_group(9, 'Context({') for idx, (key, value) in enumerate(self.to_dict().iteritems()): if idx: p.text(',') p.breakable() p.pretty(key) p.text(': ') p.pretty(value) p.end_group(9, '})') class LoopContext(object): """ Simple class that provides special loop variables. Used by `Environment.iterate`. """ jinja_allowed_attributes = ['index', 'index0', 'length', 'parent', 'even', 'odd', 'revindex0', 'revindex', 'first', 'last'] def __init__(self, seq, parent, loop_function): self.loop_function = loop_function self.parent = parent self._stack = [] if loop_function is None: self.push(seq) def push(self, seq): """ Push a sequence to the loop stack. This is used by the recursive for loop. """ # iteration over None is catched, but we don't catch iteration # over undefined because that behavior is handled in the # undefined singleton if seq is None: seq = () length = 0 else: try: length = len(seq) except (AttributeError, TypeError): seq = list(seq) length = len(seq) self._stack.append({ 'index': -1, 'seq': seq, 'length': length }) return self def pop(self): """Remove the last layer from the loop stack.""" return self._stack.pop() iterated = property(lambda s: s._stack[-1]['index'] > -1) index0 = property(lambda s: s._stack[-1]['index']) index = property(lambda s: s._stack[-1]['index'] + 1) revindex0 = property(lambda s: s._stack[-1]['length'] - s._stack[-1]['index'] - 1) revindex = property(lambda s: s._stack[-1]['length'] - s._stack[-1]['index']) length = property(lambda s: s._stack[-1]['length']) even = property(lambda s: s._stack[-1]['index'] % 2 == 1) odd = property(lambda s: s._stack[-1]['index'] % 2 == 0) first = property(lambda s: s._stack[-1]['index'] == 0) last = property(lambda s: s._stack[-1]['index'] == s._stack[-1]['length'] - 1) def __iter__(self): s = self._stack[-1] for idx, item in enumerate(s['seq']): s['index'] = idx yield item def __len__(self): return self._stack[-1]['length'] def __call__(self, seq): if self.loop_function is not None: return self.loop_function(seq) raise TemplateRuntimeError('In order to make loops callable you have ' 'to define them with the "recursive" ' 'modifier.') def __repr__(self): if self._stack: return '' % ( self.index, self.length, self.loop_function is not None and ' recursive' or '' ) return '' class CycleContext(object): """ Helper class used for cycling. """ def __init__(self, seq=None): self.pos = -1 # bind the correct helper function based on the constructor signature if seq is not None: self.seq = seq self.length = len(seq) self.cycle = self.cycle_static else: self.cycle = self.cycle_dynamic def cycle_static(self): """Helper function for static cycling.""" self.pos = (self.pos + 1) % self.length return self.seq[self.pos] def cycle_dynamic(self, seq): """Helper function for dynamic cycling.""" self.pos = pos = (self.pos + 1) % len(seq) return seq[pos] class SuperBlock(object): """ Helper class for ``{{ super() }}``. """ jinja_allowed_attributes = ['name'] def __init__(self, name, blocks, level, context): self.name = name self.context = context if name in blocks: self.stack = blocks[name] self.level = level else: self.stack = None def __call__(self, offset=1): if self.stack is not None: level = self.level + (offset - 1) if level < len(self.stack): return self.stack[level](self.context) raise TemplateRuntimeError('no super block for %r' % self.name) def __repr__(self): return '' % self.name class StateTest(object): """ Wrapper class for basic lambdas in order to simplify debugging in the parser. It also provides static helper functions that replace some lambda expressions """ def __init__(self, func, msg): self.func = func self.msg = msg def __call__(self, token): return self.func(token) def expect_token(*types, **kw): """Scans until one of the given tokens is found.""" msg = kw.pop('msg', None) if kw: raise TypeError('unexpected keyword argument %r' % iter(kw).next()) if len(types) == 1: if msg is None: msg = "expected '%s'" % types[0] return StateTest(lambda t: t.type == types[0], msg) if msg is None: msg = 'expected one of %s' % ', '.join(["'%s'" % type for type in types]) return StateTest(lambda t: t.type in types, msg) expect_token = staticmethod(expect_token) class Token(object): """ Token class. """ __slots__ = ('lineno', 'type', 'value') def __init__(self, lineno, type, value): self.lineno = lineno self.type = intern(str(type)) self.value = value def __str__(self): from jinja.lexer import keywords, reverse_operators if self.type in keywords: return self.type elif self.type in reverse_operators: return reverse_operators[self.type] return self.value def __repr__(self): return 'Token(%r, %r, %r)' % ( self.lineno, self.type, self.value ) class TokenStreamIterator(object): """ The iterator for tokenstreams. Iterate over the stream until the eof token is reached. """ def __init__(self, stream): self._stream = stream def __iter__(self): return self def next(self): token = self._stream.current if token.type == 'eof': self._stream.close() raise StopIteration() self._stream.next() return token class TokenStream(object): """ A token stream wraps a generator and supports pushing tokens back. It also provides some functions to expect tokens and similar stuff. Important note: Do never push more than one token back to the stream. Although the stream object won't stop you from doing so, the behavior is undefined. Multiple pushed tokens are only used internally! """ def __init__(self, generator, filename): self._next = generator.next self._pushed = [] self.current = Token(1, 'initial', '') self.filename = filename self.next() def __iter__(self): return TokenStreamIterator(self) def bound(self): """Return True if the token stream is bound to a parser.""" return self.parser is not None bound = property(bound, doc=bound.__doc__) def lineno(self): """The current line number.""" return self.current.lineno lineno = property(lineno, doc=lineno.__doc__) def __nonzero__(self): """Are we at the end of the tokenstream?""" return bool(self._pushed) or self.current.type != 'eof' eos = property(lambda x: not x.__nonzero__(), doc=__nonzero__.__doc__) def look(self): """See what's the next token.""" if self._pushed: return self._pushed[-1] old_token = self.current self.next() new_token = self.current self.current = old_token self.push(new_token) return new_token def push(self, token): """Push a token back to the stream.""" self._pushed.append(token) def skip(self, n): """Got n tokens ahead.""" for x in xrange(n): self.next() def shift(self, token): """ Push one token into the stream. """ old_current = self.current self.next() self.push(self.current) self.push(old_current) self.push(token) self.next() def next(self): """Go one token ahead.""" if self._pushed: self.current = self._pushed.pop() elif self.current.type != 'eof': try: self.current = self._next() except StopIteration: self.close() def read_whitespace(self): """Read all the whitespace, up to the next tag.""" lineno = self.current.lineno buf = [] while self.current.type == 'data' and not \ self.current.value.strip(): buf.append(self.current.value) self.next() if buf: return Token(lineno, 'data', u''.join(buf)) def close(self): """Close the stream.""" self.current = Token(self.current.lineno, 'eof', '') self._next = None def expect(self, token_type, token_value=_missing): """Expect a given token type and return it""" if self.current.type != token_type: raise TemplateSyntaxError("expected token %r, got %r" % (token_type, self.current.type), self.current.lineno, self.filename) elif token_value is not _missing and \ self.current.value != token_value: raise TemplateSyntaxError("expected %r, got %r" % (token_value, self.current.value), self.current.lineno, self.filename) try: return self.current finally: self.next() class TemplateStream(object): """ Wraps a genererator for outputing template streams. """ def __init__(self, gen): self._gen = gen self._next = gen.next self.buffered = False def disable_buffering(self): """ Disable the output buffering. """ self._next = self._gen.next self.buffered = False def enable_buffering(self, size=5): """ Enable buffering. Buffer `size` items before yielding them. """ if size <= 1: raise ValueError('buffer size too small') self.buffered = True def buffering_next(): buf = [] c_size = 0 push = buf.append next = self._gen.next try: while True: item = next() if item: push(item) c_size += 1 if c_size >= size: raise StopIteration() except StopIteration: if not c_size: raise return u''.join(buf) self._next = buffering_next def __iter__(self): return self def next(self): return self._next() ./weblog+jinja-0.8/jinja/_native.py0000644000175000017500000000507111006524413016211 0ustar henryhenry# -*- coding: utf-8 -*- """ jinja._native ~~~~~~~~~~~~~ This module implements the native base classes in case of not having a jinja with the _speedups module compiled. Note that if you change semantics here you have to edit the _speedups.c file to in order to support those changes for jinja setups with enabled speedup module. :copyright: 2007 by Armin Ronacher. :license: BSD, see LICENSE for more details. """ from jinja.datastructure import Deferred from jinja.utils import deque class BaseContext(object): def __init__(self, undefined_singleton, globals, initial): self._undefined_singleton = undefined_singleton self.current = current = {} self._stack = deque([current, initial, globals]) self.globals = globals self.initial = initial self._push = self._stack.appendleft self._pop = self._stack.popleft def stack(self): return list(self._stack)[::-1] stack = property(stack) def pop(self): """Pop the last layer from the stack and return it.""" rv = self._pop() self.current = self._stack[0] return rv def push(self, data=None): """ Push one layer to the stack and return it. Layer must be a dict or omitted. """ data = data or {} self._push(data) self.current = self._stack[0] return data def __getitem__(self, name): """ Resolve one item. Restrict the access to internal variables such as ``'::cycle1'``. Resolve deferreds. """ if not name.startswith('::'): for d in self._stack: if name in d: rv = d[name] if rv.__class__ is Deferred: rv = rv(self, name) # never touch the globals! if d is self.globals: self.initial[name] = rv else: d[name] = rv return rv return self._undefined_singleton def __setitem__(self, name, value): """Set a variable in the outermost layer.""" self.current[name] = value def __delitem__(self, name): """Delete a variable in the outermost layer.""" if name in self.current: del self.current[name] def __contains__(self, name): """ Check if the context contains a given variable.""" for layer in self._stack: if name in layer: return True return False ./weblog+jinja-0.8/jinja/tests.py0000644000175000017500000000651311006524413015730 0ustar henryhenry# -*- coding: utf-8 -*- """ jinja.tests ~~~~~~~~~~~ Jinja test functions. Used with the "is" operator. :copyright: 2007 by Armin Ronacher. :license: BSD, see LICENSE for more details. """ import re number_re = re.compile(r'^-?\d+(\.\d+)?$') regex_type = type(number_re) def test_odd(): """ Return true if the variable is odd. """ return lambda e, c, v: v % 2 == 1 def test_even(): """ Return true of the variable is even. """ return lambda e, c, v: v % 2 == 0 def test_defined(): """ Return true if the variable is defined: .. sourcecode:: jinja {% if variable is defined %} value of variable: {{ variable }} {% else %} variable is not defined {% endif %} See also the ``default`` filter. """ return lambda e, c, v: v is not e.undefined_singleton def test_lower(): """ Return true if the variable is lowercase. """ return lambda e, c, v: isinstance(v, basestring) and v.islower() def test_upper(): """ Return true if the variable is uppercase. """ return lambda e, c, v: isinstance(v, basestring) and v.isupper() def test_numeric(): """ Return true if the variable is numeric. """ return lambda e, c, v: isinstance(v, (int, long, float)) or ( isinstance(v, basestring) and number_re.match(v) is not None) def test_sequence(): """ Return true if the variable is a sequence. Sequences are variables that are iterable. """ def wrapped(environment, context, value): try: len(value) value.__getitem__ except: return False return True return wrapped def test_matching(regex): r""" Test if the variable matches the regular expression given. Note that you have to escape special chars using *two* backslashes, these are *not* raw strings. .. sourcecode:: jinja {% if var is matching @/^\d+$/ %} var looks like a number {% else %} var doesn't really look like a number {% endif %} """ def wrapped(environment, context, value): if type(regex) is regex_type: regex_ = regex else: if environment.disable_regexps: raise RuntimeError('regular expressions disabled.') if isinstance(regex, unicode): regex_ = re.compile(regex, re.U) elif isinstance(regex, str): regex_ = re.compile(regex) else: return False return regex_.search(value) is not None return wrapped def test_sameas(other): """ Check if an object points to the same memory address than another object: .. sourcecode:: jinja {% if foo.attribute is sameas(false) %} the foo attribute really is the `False` singleton {% endif %} *New in Jinja 1.2* """ return lambda e, c, v: v is other TESTS = { 'odd': test_odd, 'even': test_even, 'defined': test_defined, 'lower': test_lower, 'upper': test_upper, 'numeric': test_numeric, 'sequence': test_sequence, 'matching': test_matching, 'sameas': test_sameas } ./weblog+jinja-0.8/jinja/filters.py0000644000175000017500000006650311006524413016243 0ustar henryhenry# -*- coding: utf-8 -*- """ jinja.filters ~~~~~~~~~~~~~ Bundled jinja filters. :copyright: 2007 by Armin Ronacher. :license: BSD, see LICENSE for more details. """ import re from random import choice from operator import itemgetter from urllib import urlencode, quote from jinja.utils import urlize, escape, reversed, sorted, groupby, \ get_attribute, pformat from jinja.datastructure import TemplateData from jinja.exceptions import FilterArgumentError, SecurityException _striptags_re = re.compile(r'(|<[^>]*>)') def stringfilter(f): """ Decorator for filters that just work on unicode objects. """ def decorator(*args): def wrapped(env, context, value): nargs = list(args) for idx, var in enumerate(nargs): if isinstance(var, str): nargs[idx] = env.to_unicode(var) return f(env.to_unicode(value), *nargs) return wrapped try: decorator.__doc__ = f.__doc__ decorator.__name__ = f.__name__ except: pass return decorator def simplefilter(f): """ Decorator for simplifying filters. Filter arguments are passed to the decorated function without environment and context. The source value is the first argument. (like stringfilter but without unicode conversion) """ def decorator(*args): def wrapped(env, context, value): return f(value, *args) return wrapped try: decorator.__doc__ = f.__doc__ decorator.__name__ = f.__name__ except: pass return decorator def do_replace(s, old, new, count=None): """ Return a copy of the value with all occurrences of a substring replaced with a new one. The first argument is the substring that should be replaced, the second is the replacement string. If the optional third argument ``count`` is given, only the first ``count`` occurrences are replaced: .. sourcecode:: jinja {{ "Hello World"|replace("Hello", "Goodbye") }} -> Goodbye World {{ "aaaaargh"|replace("a", "d'oh, ", 2) }} -> d'oh, d'oh, aaargh """ if not isinstance(old, basestring) or \ not isinstance(new, basestring): raise FilterArgumentError('the replace filter requires ' 'string replacement arguments') if count is None: return s.replace(old, new) if not isinstance(count, (int, long)): raise FilterArgumentError('the count parameter of the ' 'replace filter requires ' 'an integer') return s.replace(old, new, count) do_replace = stringfilter(do_replace) def do_upper(s): """ Convert a value to uppercase. """ return s.upper() do_upper = stringfilter(do_upper) def do_lower(s): """ Convert a value to lowercase. """ return s.lower() do_lower = stringfilter(do_lower) def do_escape(attribute=False): """ XML escape ``&``, ``<``, and ``>`` in a string of data. If the optional parameter is `true` this filter will also convert ``"`` to ``"``. This filter is just used if the environment was configured with disabled `auto_escape`. This method will have no effect it the value is already escaped. """ #: because filters are cached we can make a local alias to #: speed things up a bit e = escape def wrapped(env, context, s): if isinstance(s, TemplateData): return s elif hasattr(s, '__html__'): return s.__html__() #: small speedup, do not convert to unicode if we already #: have an unicode object. if s.__class__ is not unicode: s = env.to_unicode(s) return e(s, attribute) return wrapped def do_xmlattr(autospace=False): """ Create an SGML/XML attribute string based on the items in a dict. All values that are neither `none` nor `undefined` are automatically escaped: .. sourcecode:: html+jinja ... Results in something like this: .. sourcecode:: html
    ...
As you can see it automatically prepends a space in front of the item if the filter returned something. You can disable this by passing `false` as only argument to the filter. *New in Jinja 1.1* """ e = escape def wrapped(env, context, d): if not hasattr(d, 'iteritems'): raise TypeError('a dict is required') result = [] for key, value in d.iteritems(): if value not in (None, env.undefined_singleton): result.append(u'%s="%s"' % ( e(env.to_unicode(key)), e(env.to_unicode(value), True) )) rv = u' '.join(result) if autospace: rv = ' ' + rv return rv return wrapped def do_capitalize(s): """ Capitalize a value. The first character will be uppercase, all others lowercase. """ return s.capitalize() do_capitalize = stringfilter(do_capitalize) def do_title(s): """ Return a titlecased version of the value. I.e. words will start with uppercase letters, all remaining characters are lowercase. """ return s.title() do_title = stringfilter(do_title) def do_dictsort(case_sensitive=False, by='key'): """ Sort a dict and yield (key, value) pairs. Because python dicts are unsorted you may want to use this function to order them by either key or value: .. sourcecode:: jinja {% for item in mydict|dictsort %} sort the dict by key, case insensitive {% for item in mydict|dicsort(true) %} sort the dict by key, case sensitive {% for item in mydict|dictsort(false, 'value') %} sort the dict by key, case insensitive, sorted normally and ordered by value. """ if by == 'key': pos = 0 elif by == 'value': pos = 1 else: raise FilterArgumentError('You can only sort by either ' '"key" or "value"') def sort_func(value, env): if isinstance(value, basestring): value = env.to_unicode(value) if not case_sensitive: value = value.lower() return value def wrapped(env, context, value): items = value.items() items.sort(lambda a, b: cmp(sort_func(a[pos], env), sort_func(b[pos], env))) return items return wrapped def do_default(default_value=u'', boolean=False): """ If the value is undefined it will return the passed default value, otherwise the value of the variable: .. sourcecode:: jinja {{ my_variable|default('my_variable is not defined') }} This will output the value of ``my_variable`` if the variable was defined, otherwise ``'my_variable is not defined'``. If you want to use default with variables that evaluate to false you have to set the second parameter to `true`: .. sourcecode:: jinja {{ ''|default('the string was empty', true) }} """ def wrapped(env, context, value): if (boolean and not value) or value in (env.undefined_singleton, None): return default_value return value return wrapped def do_join(d=u''): """ Return a string which is the concatenation of the strings in the sequence. The separator between elements is an empty string per default, you can define ith with the optional parameter: .. sourcecode:: jinja {{ [1, 2, 3]|join('|') }} -> 1|2|3 {{ [1, 2, 3]|join }} -> 123 """ def wrapped(env, context, value): return env.to_unicode(d).join([env.to_unicode(x) for x in value]) return wrapped def do_count(): """ Return the length of the value. In case if getting an integer or float it will convert it into a string an return the length of the new string. If the object has no length it will of corse return 0. """ def wrapped(env, context, value): try: if type(value) in (int, float, long): return len(str(value)) return len(value) except TypeError: return 0 return wrapped def do_reverse(): """ Return a reversed list of the sequence filtered. You can use this for example for reverse iteration: .. sourcecode:: jinja {% for item in seq|reverse %} {{ item|e }} {% endfor %} """ def wrapped(env, context, value): try: return value[::-1] except: l = list(value) l.reverse() return l return wrapped def do_center(value, width=80): """ Centers the value in a field of a given width. """ return value.center(width) do_center = stringfilter(do_center) def do_first(): """ Return the frist item of a sequence. """ def wrapped(env, context, seq): try: return iter(seq).next() except StopIteration: return env.undefined_singleton return wrapped def do_last(): """ Return the last item of a sequence. """ def wrapped(env, context, seq): try: return iter(reversed(seq)).next() except StopIteration: return env.undefined_singleton return wrapped def do_random(): """ Return a random item from the sequence. """ def wrapped(env, context, seq): try: return choice(seq) except IndexError: return env.undefined_singleton return wrapped def do_urlencode(): """ urlencode a string or directory. .. sourcecode:: jinja {{ {'foo': 'bar', 'blub': 'blah'}|urlencode }} -> foo=bar&blub=blah {{ 'Hello World' }} -> Hello%20World """ def wrapped(env, context, value): if isinstance(value, dict): tmp = {} for key, value in value.iteritems(): key = env.to_unicode(key).encode(env.charset) value = env.to_unicode(value).encode(env.charset) tmp[key] = value return urlencode(tmp) else: return quote(env.to_unicode(value).encode(env.charset)) return wrapped def do_jsonencode(): """ JSON dump a variable. just works if simplejson is installed. .. sourcecode:: jinja {{ 'Hello World'|jsonencode }} -> "Hello World" """ global simplejson try: simplejson except NameError: import simplejson return lambda e, c, v: simplejson.dumps(v) def do_filesizeformat(): """ Format the value like a 'human-readable' file size (i.e. 13 KB, 4.1 MB, 102 bytes, etc). """ def wrapped(env, context, value): # fail silently try: bytes = float(value) except TypeError: bytes = 0 if bytes < 1024: return "%d Byte%s" % (bytes, bytes != 1 and 's' or '') elif bytes < 1024 * 1024: return "%.1f KB" % (bytes / 1024) elif bytes < 1024 * 1024 * 1024: return "%.1f MB" % (bytes / (1024 * 1024)) return "%.1f GB" % (bytes / (1024 * 1024 * 1024)) return wrapped def do_pprint(verbose=False): """ Pretty print a variable. Useful for debugging. With Jinja 1.2 onwards you can pass it a parameter. If this parameter is truthy the output will be more verbose (this requires `pretty`) """ def wrapped(env, context, value): return pformat(value, verbose=verbose) return wrapped def do_urlize(value, trim_url_limit=None, nofollow=False): """ Converts URLs in plain text into clickable links. If you pass the filter an additional integer it will shorten the urls to that number. Also a third argument exists that makes the urls "nofollow": .. sourcecode:: jinja {{ mytext|urlize(40, True) }} links are shortened to 40 chars and defined with rel="nofollow" """ return urlize(value, trim_url_limit, nofollow) do_urlize = stringfilter(do_urlize) def do_indent(s, width=4, indentfirst=False): """ {{ s|indent[ width[ indentfirst[ usetab]]] }} Return a copy of the passed string, each line indented by 4 spaces. The first line is not indented. If you want to change the number of spaces or indent the first line too you can pass additional parameters to the filter: .. sourcecode:: jinja {{ mytext|indent(2, True) }} indent by two spaces and indent the first line too. """ indention = ' ' * width if indentfirst: return u'\n'.join([indention + line for line in s.splitlines()]) return s.replace('\n', '\n' + indention) do_indent = stringfilter(do_indent) def do_truncate(s, length=255, killwords=False, end='...'): """ Return a truncated copy of the string. The length is specified with the first parameter which defaults to ``255``. If the second parameter is ``true`` the filter will cut the text at length. Otherwise it will try to save the last word. If the text was in fact truncated it will append an ellipsis sign (``"..."``). If you want a different ellipsis sign than ``"..."`` you can specify it using the third parameter. .. sourcecode jinja:: {{ mytext|truncate(300, false, '»') }} truncate mytext to 300 chars, don't split up words, use a right pointing double arrow as ellipsis sign. """ if len(s) <= length: return s elif killwords: return s[:length] + end words = s.split(' ') result = [] m = 0 for word in words: m += len(word) + 1 if m > length: break result.append(word) result.append(end) return u' '.join(result) do_truncate = stringfilter(do_truncate) def do_wordwrap(s, pos=79, hard=False): """ Return a copy of the string passed to the filter wrapped after ``79`` characters. You can override this default using the first parameter. If you set the second parameter to `true` Jinja will also split words apart (usually a bad idea because it makes reading hard). """ if len(s) < pos: return s if hard: return u'\n'.join([s[idx:idx + pos] for idx in xrange(0, len(s), pos)]) # code from http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/148061 return reduce(lambda line, word, pos=pos: u'%s%s%s' % (line, u' \n'[(len(line)-line.rfind('\n') - 1 + len(word.split('\n', 1)[0]) >= pos)], word), s.split(' ')) do_wordwrap = stringfilter(do_wordwrap) def do_wordcount(s): """ Count the words in that string. """ return len([x for x in s.split() if x]) do_wordcount = stringfilter(do_wordcount) def do_textile(s): """ Prase the string using textile. requires the `PyTextile`_ library. .. _PyTextile: http://dealmeida.net/projects/textile/ """ from textile import textile return textile(s.encode('utf-8')).decode('utf-8') do_textile = stringfilter(do_textile) def do_markdown(s): """ Parse the string using markdown. requires the `Python-markdown`_ library. .. _Python-markdown: http://www.freewisdom.org/projects/python-markdown/ """ from markdown import markdown return markdown(s.encode('utf-8')).decode('utf-8') do_markdown = stringfilter(do_markdown) def do_rst(s): """ Parse the string using the reStructuredText parser from the docutils package. requires `docutils`_. .. _docutils: http://docutils.sourceforge.net/ """ from docutils.core import publish_parts parts = publish_parts(source=s, writer_name='html4css1') return parts['fragment'] do_rst = stringfilter(do_rst) def do_int(default=0): """ Convert the value into an integer. If the conversion doesn't work it will return ``0``. You can override this default using the first parameter. """ def wrapped(env, context, value): try: return int(value) except (TypeError, ValueError): try: return int(float(value)) except (TypeError, ValueError): return default return wrapped def do_float(default=0.0): """ Convert the value into a floating point number. If the conversion doesn't work it will return ``0.0``. You can override this default using the first parameter. """ def wrapped(env, context, value): try: return float(value) except (TypeError, ValueError): return default return wrapped def do_string(): """ Convert the value into an string. """ return lambda e, c, v: e.to_unicode(v) def do_format(*args): """ Apply python string formatting on an object: .. sourcecode:: jinja {{ "%s - %s"|format("Hello?", "Foo!") }} -> Hello? - Foo! Note that you cannot use the mapping syntax (``%(name)s``) like in python. Use `|dformat` for that. """ def wrapped(env, context, value): return env.to_unicode(value) % args return wrapped def do_dformat(d): """ Apply python mapping string formatting on an object: .. sourcecode:: jinja {{ "Hello %(username)s!"|dformat({'username': 'John Doe'}) }} -> Hello John Doe! This is useful when adding variables to translateable string expressions. *New in Jinja 1.1* """ if not isinstance(d, dict): raise FilterArgumentError('dict required') def wrapped(env, context, value): return env.to_unicode(value) % d return wrapped def do_trim(value): """ Strip leading and trailing whitespace. """ return value.strip() do_trim = stringfilter(do_trim) def do_capture(name='captured', clean=False): """ Store the value in a variable called ``captured`` or a variable with the name provided. Useful for filter blocks: .. sourcecode:: jinja {% filter capture('foo') %} ... {% endfilter %} {{ foo }} This will output "..." two times. One time from the filter block and one time from the variable. If you don't want the filter to output something you can use it in `clean` mode: .. sourcecode:: jinja {% filter capture('foo', True) %} ... {% endfilter %} {{ foo }} """ if not isinstance(name, basestring): raise FilterArgumentError('You can only capture into variables') def wrapped(env, context, value): context[name] = value if clean: return TemplateData() return value return wrapped def do_striptags(value): """ Strip SGML/XML tags and replace adjacent whitespace by one space. *new in Jinja 1.1* """ return ' '.join(_striptags_re.sub('', value).split()) do_striptags = stringfilter(do_striptags) def do_slice(slices, fill_with=None): """ Slice an iterator and return a list of lists containing those items. Useful if you want to create a div containing three div tags that represent columns: .. sourcecode:: html+jinja
{%- for column in items|slice(3) %}
    {%- for item in column %}
  • {{ item }}
  • {%- endfor %}
{%- endfor %}
If you pass it a second argument it's used to fill missing values on the last iteration. *new in Jinja 1.1* """ def wrapped(env, context, value): result = [] seq = list(value) length = len(seq) items_per_slice = length // slices slices_with_extra = length % slices offset = 0 for slice_number in xrange(slices): start = offset + slice_number * items_per_slice if slice_number < slices_with_extra: offset += 1 end = offset + (slice_number + 1) * items_per_slice tmp = seq[start:end] if fill_with is not None and slice_number >= slices_with_extra: tmp.append(fill_with) result.append(tmp) return result return wrapped def do_batch(linecount, fill_with=None): """ A filter that batches items. It works pretty much like `slice` just the other way round. It returns a list of lists with the given number of items. If you provide a second parameter this is used to fill missing items. See this example: .. sourcecode:: html+jinja {%- for row in items|batch(3, ' ') %} {%- for column in row %} {{ column }} {%- endfor %} {%- endfor %}
*new in Jinja 1.1* """ def wrapped(env, context, value): result = [] tmp = [] for item in value: if len(tmp) == linecount: result.append(tmp) tmp = [] tmp.append(item) if tmp: if fill_with is not None and len(tmp) < linecount: tmp += [fill_with] * (linecount - len(tmp)) result.append(tmp) return result return wrapped def do_sum(): """ Sum up the given sequence of numbers. *new in Jinja 1.1* """ def wrapped(env, context, value): return sum(value) return wrapped def do_abs(): """ Return the absolute value of a number. *new in Jinja 1.1* """ def wrapped(env, context, value): return abs(value) return wrapped def do_round(precision=0, method='common'): """ Round the number to a given precision. The first parameter specifies the precision (default is ``0``), the second the rounding method: - ``'common'`` rounds either up or down - ``'ceil'`` always rounds up - ``'floor'`` always rounds down If you don't specify a method ``'common'`` is used. .. sourcecode:: jinja {{ 42.55|round }} -> 43 {{ 42.55|round(1, 'floor') }} -> 42.5 *new in Jinja 1.1* """ if not method in ('common', 'ceil', 'floor'): raise FilterArgumentError('method must be common, ceil or floor') if precision < 0: raise FilterArgumentError('precision must be a postive integer ' 'or zero.') def wrapped(env, context, value): if method == 'common': return round(value, precision) import math func = getattr(math, method) if precision: return func(value * 10 * precision) / (10 * precision) else: return func(value) return wrapped def do_sort(reverse=False): """ Sort a sequence. Per default it sorts ascending, if you pass it `True` as first argument it will reverse the sorting. *new in Jinja 1.1* """ def wrapped(env, context, value): return sorted(value, reverse=reverse) return wrapped def do_groupby(attribute): """ Group a sequence of objects by a common attribute. If you for example have a list of dicts or objects that represent persons with `gender`, `first_name` and `last_name` attributes and you want to group all users by genders you can do something like the following snippet: .. sourcecode:: html+jinja
    {% for group in persons|groupby('gender') %}
  • {{ group.grouper }}
      {% for person in group.list %}
    • {{ person.first_name }} {{ person.last_name }}
    • {% endfor %}
  • {% endfor %}
As you can see the item we're grouping by is stored in the `grouper` attribute and the `list` contains all the objects that have this grouper in common. *New in Jinja 1.2* """ def wrapped(env, context, value): expr = lambda x: env.get_attribute(x, attribute) return sorted([{ 'grouper': a, 'list': list(b) } for a, b in groupby(sorted(value, key=expr), expr)], key=itemgetter('grouper')) return wrapped def do_getattribute(attribute): """ Get one attribute from an object. Normally you don't have to use this filter because the attribute and subscript expressions try to either get an attribute of an object or an item. In some situations it could be that there is an item *and* an attribute with the same name. In that situation only the item is returned, never the attribute. .. sourcecode:: jinja {{ foo.bar }} -> {{ foo|getattribute('bar') }} *New in Jinja 1.2* """ def wrapped(env, context, value): try: return get_attribute(value, attribute) except (SecurityException, AttributeError): return env.undefined_singleton return wrapped def do_getitem(key): """ This filter basically works like the normal subscript expression but it doesn't fall back to attribute lookup. If an item does not exist for an object undefined is returned. .. sourcecode:: jinja {{ foo.bar }} -> {{ foo|getitem('bar') }} *New in Jinja 1.2* """ def wrapped(env, context, value): try: return value[key] except (TypeError, KeyError, IndexError, AttributeError): return env.undefined_singleton return wrapped FILTERS = { 'replace': do_replace, 'upper': do_upper, 'lower': do_lower, 'escape': do_escape, 'e': do_escape, 'xmlattr': do_xmlattr, 'capitalize': do_capitalize, 'title': do_title, 'default': do_default, 'join': do_join, 'count': do_count, 'dictsort': do_dictsort, 'length': do_count, 'reverse': do_reverse, 'center': do_center, 'title': do_title, 'capitalize': do_capitalize, 'first': do_first, 'last': do_last, 'random': do_random, 'urlencode': do_urlencode, 'jsonencode': do_jsonencode, 'filesizeformat': do_filesizeformat, 'pprint': do_pprint, 'indent': do_indent, 'truncate': do_truncate, 'wordwrap': do_wordwrap, 'wordcount': do_wordcount, 'textile': do_textile, 'markdown': do_markdown, 'rst': do_rst, 'int': do_int, 'float': do_float, 'string': do_string, 'urlize': do_urlize, 'format': do_format, 'dformat': do_dformat, 'capture': do_capture, 'trim': do_trim, 'striptags': do_striptags, 'slice': do_slice, 'batch': do_batch, 'sum': do_sum, 'abs': do_abs, 'round': do_round, 'sort': do_sort, 'groupby': do_groupby, 'getattribute': do_getattribute, 'getitem': do_getitem } ./weblog+jinja-0.8/jinja/parser.py0000644000175000017500000012621611006524413016065 0ustar henryhenry# -*- coding: utf-8 -*- """ jinja.parser ~~~~~~~~~~~~ Implements the template parser. The Jinja template parser is not a real parser but a combination of the python compiler package and some postprocessing. The tokens yielded by the lexer are used to separate template data and expressions. The expression tokens are then converted into strings again and processed by the python parser. :copyright: 2007 by Armin Ronacher. :license: BSD, see LICENSE for more details. """ from jinja import nodes from jinja.datastructure import StateTest from jinja.exceptions import TemplateSyntaxError from jinja.utils import set __all__ = ['Parser'] # general callback functions for the parser end_of_block = StateTest.expect_token('block_end', msg='expected end of block tag') end_of_variable = StateTest.expect_token('variable_end', msg='expected end of variable') end_of_comment = StateTest.expect_token('comment_end', msg='expected end of comment') # internal tag callbacks switch_for = StateTest.expect_token('else', 'endfor') end_of_for = StateTest.expect_token('endfor') switch_if = StateTest.expect_token('else', 'elif', 'endif') end_of_if = StateTest.expect_token('endif') end_of_filter = StateTest.expect_token('endfilter') end_of_macro = StateTest.expect_token('endmacro') end_of_call = StateTest.expect_token('endcall') end_of_block_tag = StateTest.expect_token('endblock') end_of_trans = StateTest.expect_token('endtrans') # this ends a tuple tuple_edge_tokens = set(['rparen', 'block_end', 'variable_end', 'in', 'recursive']) class Parser(object): """ The template parser class. Transforms sourcecode into an abstract syntax tree. """ def __init__(self, environment, source, filename=None): self.environment = environment if isinstance(source, str): source = source.decode(environment.template_charset, 'ignore') if isinstance(filename, unicode): filename = filename.encode('utf-8') self.source = source self.filename = filename self.closed = False #: set for blocks in order to keep them unique self.blocks = set() #: mapping of directives that require special treatment self.directives = { # "fake" directives that just trigger errors 'raw': self.parse_raw_directive, 'extends': self.parse_extends_directive, # real directives 'for': self.parse_for_loop, 'if': self.parse_if_condition, 'cycle': self.parse_cycle_directive, 'call': self.parse_call_directive, 'set': self.parse_set_directive, 'filter': self.parse_filter_directive, 'print': self.parse_print_directive, 'macro': self.parse_macro_directive, 'block': self.parse_block_directive, 'include': self.parse_include_directive, 'trans': self.parse_trans_directive } #: set of directives that are only available in a certain #: context. self.context_directives = set([ 'elif', 'else', 'endblock', 'endfilter', 'endfor', 'endif', 'endmacro', 'endraw', 'endtrans', 'pluralize' ]) #: get the `no_variable_block` flag self.no_variable_block = self.environment.lexer.no_variable_block self.stream = environment.lexer.tokenize(source, filename) def parse_raw_directive(self): """ Handle fake raw directive. (real raw directives are handled by the lexer. But if there are arguments to raw or the end tag is missing the parser tries to resolve this directive. In that case present the user a useful error message. """ if self.stream: raise TemplateSyntaxError('raw directive does not support ' 'any arguments.', self.stream.lineno, self.filename) raise TemplateSyntaxError('missing end tag for raw directive.', self.stream.lineno, self.filename) def parse_extends_directive(self): """ Handle the extends directive used for inheritance. """ raise TemplateSyntaxError('mispositioned extends tag. extends must ' 'be the first tag of a template.', self.stream.lineno, self.filename) def parse_for_loop(self): """ Handle a for directive and return a ForLoop node """ token = self.stream.expect('for') item = self.parse_tuple_expression(simplified=True) if not item.allows_assignments(): raise TemplateSyntaxError('cannot assign to expression', token.lineno, self.filename) self.stream.expect('in') seq = self.parse_tuple_expression() if self.stream.current.type == 'recursive': self.stream.next() recursive = True else: recursive = False self.stream.expect('block_end') body = self.subparse(switch_for) # do we have an else section? if self.stream.current.type == 'else': self.stream.next() self.stream.expect('block_end') else_ = self.subparse(end_of_for, True) else: self.stream.next() else_ = None self.stream.expect('block_end') return nodes.ForLoop(item, seq, body, else_, recursive, token.lineno, self.filename) def parse_if_condition(self): """ Handle if/else blocks. """ token = self.stream.expect('if') expr = self.parse_expression() self.stream.expect('block_end') tests = [(expr, self.subparse(switch_if))] else_ = None # do we have an else section? while True: if self.stream.current.type == 'else': self.stream.next() self.stream.expect('block_end') else_ = self.subparse(end_of_if, True) elif self.stream.current.type == 'elif': self.stream.next() expr = self.parse_expression() self.stream.expect('block_end') tests.append((expr, self.subparse(switch_if))) continue else: self.stream.next() break self.stream.expect('block_end') return nodes.IfCondition(tests, else_, token.lineno, self.filename) def parse_cycle_directive(self): """ Handle {% cycle foo, bar, baz %}. """ token = self.stream.expect('cycle') expr = self.parse_tuple_expression() self.stream.expect('block_end') return nodes.Cycle(expr, token.lineno, self.filename) def parse_set_directive(self): """ Handle {% set foo = 'value of foo' %}. """ token = self.stream.expect('set') name = self.stream.expect('name') self.test_name(name.value) self.stream.expect('assign') value = self.parse_expression() if self.stream.current.type == 'bang': self.stream.next() scope_local = False else: scope_local = True self.stream.expect('block_end') return nodes.Set(name.value, value, scope_local, token.lineno, self.filename) def parse_filter_directive(self): """ Handle {% filter foo|bar %} directives. """ token = self.stream.expect('filter') filters = [] while self.stream.current.type != 'block_end': if filters: self.stream.expect('pipe') token = self.stream.expect('name') args = [] if self.stream.current.type == 'lparen': self.stream.next() while self.stream.current.type != 'rparen': if args: self.stream.expect('comma') args.append(self.parse_expression()) self.stream.expect('rparen') filters.append((token.value, args)) self.stream.expect('block_end') body = self.subparse(end_of_filter, True) self.stream.expect('block_end') return nodes.Filter(body, filters, token.lineno, self.filename) def parse_print_directive(self): """ Handle {% print foo %}. """ token = self.stream.expect('print') expr = self.parse_tuple_expression() node = nodes.Print(expr, token.lineno, self.filename) self.stream.expect('block_end') return node def parse_macro_directive(self): """ Handle {% macro foo bar, baz %} as well as {% macro foo(bar, baz) %}. """ token = self.stream.expect('macro') macro_name = self.stream.expect('name') self.test_name(macro_name.value) if self.stream.current.type == 'lparen': self.stream.next() needle_token = 'rparen' else: needle_token = 'block_end' args = [] while self.stream.current.type != needle_token: if args: self.stream.expect('comma') name = self.stream.expect('name').value self.test_name(name) if self.stream.current.type == 'assign': self.stream.next() default = self.parse_expression() else: default = None args.append((name, default)) self.stream.next() if needle_token == 'rparen': self.stream.expect('block_end') body = self.subparse(end_of_macro, True) self.stream.expect('block_end') return nodes.Macro(macro_name.value, args, body, token.lineno, self.filename) def parse_call_directive(self): """ Handle {% call foo() %}...{% endcall %} """ token = self.stream.expect('call') expr = self.parse_call_expression() self.stream.expect('block_end') body = self.subparse(end_of_call, True) self.stream.expect('block_end') return nodes.Call(expr, body, token.lineno, self.filename) def parse_block_directive(self): """ Handle block directives used for inheritance. """ token = self.stream.expect('block') name = self.stream.expect('name').value # check if this block does not exist by now. if name in self.blocks: raise TemplateSyntaxError('block %r defined twice' % name, token.lineno, self.filename) self.blocks.add(name) if self.stream.current.type != 'block_end': lineno = self.stream.lineno expr = self.parse_tuple_expression() node = nodes.Print(expr, lineno, self.filename) body = nodes.NodeList([node], lineno, self.filename) self.stream.expect('block_end') else: # otherwise parse the body and attach it to the block self.stream.expect('block_end') body = self.subparse(end_of_block_tag, True) self.stream.expect('block_end') return nodes.Block(name, body, token.lineno, self.filename) def parse_include_directive(self): """ Handle the include directive used for template inclusion. """ token = self.stream.expect('include') template = self.stream.expect('string').value self.stream.expect('block_end') return nodes.Include(template, token.lineno, self.filename) def parse_trans_directive(self): """ Handle translatable sections. """ trans_token = self.stream.expect('trans') # string based translations {% trans "foo" %} if self.stream.current.type == 'string': text = self.stream.expect('string') self.stream.expect('block_end') return nodes.Trans(text.value, None, None, None, trans_token.lineno, self.filename) # block based translations replacements = {} plural_var = None while self.stream.current.type != 'block_end': if replacements: self.stream.expect('comma') name = self.stream.expect('name') if self.stream.current.type == 'assign': self.stream.next() value = self.parse_expression() else: value = nodes.NameExpression(name.value, name.lineno, self.filename) if name.value in replacements: raise TemplateSyntaxError('translation variable %r ' 'is defined twice' % name.value, name.lineno, self.filename) replacements[name.value] = value if plural_var is None: plural_var = name.value self.stream.expect('block_end') def process_variable(): var_name = self.stream.expect('name') if var_name.value not in replacements: raise TemplateSyntaxError('unregistered translation variable' " '%s'." % var_name.value, var_name.lineno, self.filename) buf.append('%%(%s)s' % var_name.value) buf = singular = [] plural = None while True: token = self.stream.current if token.type == 'data': buf.append(token.value.replace('%', '%%')) self.stream.next() elif token.type == 'variable_begin': self.stream.next() process_variable() self.stream.expect('variable_end') elif token.type == 'block_begin': self.stream.next() if plural is None and self.stream.current.type == 'pluralize': self.stream.next() if self.stream.current.type == 'name': plural_var = self.stream.expect('name').value plural = buf = [] elif self.stream.current.type == 'endtrans': self.stream.next() self.stream.expect('block_end') break else: if self.no_variable_block: process_variable() else: raise TemplateSyntaxError('blocks are not allowed ' 'in trans tags', self.stream.lineno, self.filename) self.stream.expect('block_end') else: assert False, 'something very strange happened' singular = u''.join(singular) if plural is not None: plural = u''.join(plural) return nodes.Trans(singular, plural, plural_var, replacements, trans_token.lineno, self.filename) def parse_expression(self): """ Parse one expression from the stream. """ return self.parse_conditional_expression() def parse_subscribed_expression(self): """ Like parse_expression but parses slices too. Because this parsing function requires a border the two tokens rbracket and comma mark the end of the expression in some situations. """ lineno = self.stream.lineno if self.stream.current.type == 'colon': self.stream.next() args = [None] else: node = self.parse_expression() if self.stream.current.type != 'colon': return node self.stream.next() args = [node] if self.stream.current.type == 'colon': args.append(None) elif self.stream.current.type not in ('rbracket', 'comma'): args.append(self.parse_expression()) else: args.append(None) if self.stream.current.type == 'colon': self.stream.next() if self.stream.current.type not in ('rbracket', 'comma'): args.append(self.parse_expression()) else: args.append(None) else: args.append(None) return nodes.SliceExpression(*(args + [lineno, self.filename])) def parse_conditional_expression(self): """ Parse a conditional expression (foo if bar else baz) """ lineno = self.stream.lineno expr1 = self.parse_or_expression() while self.stream.current.type == 'if': self.stream.next() expr2 = self.parse_or_expression() self.stream.expect('else') expr3 = self.parse_conditional_expression() expr1 = nodes.ConditionalExpression(expr2, expr1, expr3, lineno, self.filename) lineno = self.stream.lineno return expr1 def parse_or_expression(self): """ Parse something like {{ foo or bar }}. """ lineno = self.stream.lineno left = self.parse_and_expression() while self.stream.current.type == 'or': self.stream.next() right = self.parse_and_expression() left = nodes.OrExpression(left, right, lineno, self.filename) lineno = self.stream.lineno return left def parse_and_expression(self): """ Parse something like {{ foo and bar }}. """ lineno = self.stream.lineno left = self.parse_compare_expression() while self.stream.current.type == 'and': self.stream.next() right = self.parse_compare_expression() left = nodes.AndExpression(left, right, lineno, self.filename) lineno = self.stream.lineno return left def parse_compare_expression(self): """ Parse something like {{ foo == bar }}. """ known_operators = set(['eq', 'ne', 'lt', 'lteq', 'gt', 'gteq', 'in']) lineno = self.stream.lineno expr = self.parse_add_expression() ops = [] while True: if self.stream.current.type in known_operators: op = self.stream.current.type self.stream.next() ops.append([op, self.parse_add_expression()]) elif self.stream.current.type == 'not' and \ self.stream.look().type == 'in': self.stream.skip(2) ops.append(['not in', self.parse_add_expression()]) else: break if not ops: return expr return nodes.CompareExpression(expr, ops, lineno, self.filename) def parse_add_expression(self): """ Parse something like {{ foo + bar }}. """ lineno = self.stream.lineno left = self.parse_sub_expression() while self.stream.current.type == 'add': self.stream.next() right = self.parse_sub_expression() left = nodes.AddExpression(left, right, lineno, self.filename) lineno = self.stream.lineno return left def parse_sub_expression(self): """ Parse something like {{ foo - bar }}. """ lineno = self.stream.lineno left = self.parse_concat_expression() while self.stream.current.type == 'sub': self.stream.next() right = self.parse_concat_expression() left = nodes.SubExpression(left, right, lineno, self.filename) lineno = self.stream.lineno return left def parse_concat_expression(self): """ Parse something like {{ foo ~ bar }}. """ lineno = self.stream.lineno args = [self.parse_mul_expression()] while self.stream.current.type == 'tilde': self.stream.next() args.append(self.parse_mul_expression()) if len(args) == 1: return args[0] return nodes.ConcatExpression(args, lineno, self.filename) def parse_mul_expression(self): """ Parse something like {{ foo * bar }}. """ lineno = self.stream.lineno left = self.parse_div_expression() while self.stream.current.type == 'mul': self.stream.next() right = self.parse_div_expression() left = nodes.MulExpression(left, right, lineno, self.filename) lineno = self.stream.lineno return left def parse_div_expression(self): """ Parse something like {{ foo / bar }}. """ lineno = self.stream.lineno left = self.parse_floor_div_expression() while self.stream.current.type == 'div': self.stream.next() right = self.parse_floor_div_expression() left = nodes.DivExpression(left, right, lineno, self.filename) lineno = self.stream.lineno return left def parse_floor_div_expression(self): """ Parse something like {{ foo // bar }}. """ lineno = self.stream.lineno left = self.parse_mod_expression() while self.stream.current.type == 'floordiv': self.stream.next() right = self.parse_mod_expression() left = nodes.FloorDivExpression(left, right, lineno, self.filename) lineno = self.stream.lineno return left def parse_mod_expression(self): """ Parse something like {{ foo % bar }}. """ lineno = self.stream.lineno left = self.parse_pow_expression() while self.stream.current.type == 'mod': self.stream.next() right = self.parse_pow_expression() left = nodes.ModExpression(left, right, lineno, self.filename) lineno = self.stream.lineno return left def parse_pow_expression(self): """ Parse something like {{ foo ** bar }}. """ lineno = self.stream.lineno left = self.parse_unary_expression() while self.stream.current.type == 'pow': self.stream.next() right = self.parse_unary_expression() left = nodes.PowExpression(left, right, lineno, self.filename) lineno = self.stream.lineno return left def parse_unary_expression(self): """ Parse all kinds of unary expressions. """ if self.stream.current.type == 'not': return self.parse_not_expression() elif self.stream.current.type == 'sub': return self.parse_neg_expression() elif self.stream.current.type == 'add': return self.parse_pos_expression() return self.parse_primary_expression() def parse_not_expression(self): """ Parse something like {{ not foo }}. """ token = self.stream.expect('not') node = self.parse_unary_expression() return nodes.NotExpression(node, token.lineno, self.filename) def parse_neg_expression(self): """ Parse something like {{ -foo }}. """ token = self.stream.expect('sub') node = self.parse_unary_expression() return nodes.NegExpression(node, token.lineno, self.filename) def parse_pos_expression(self): """ Parse something like {{ +foo }}. """ token = self.stream.expect('add') node = self.parse_unary_expression() return nodes.PosExpression(node, token.lineno, self.filename) def parse_primary_expression(self, parse_postfix=True): """ Parse a primary expression such as a name or literal. """ current = self.stream.current if current.type == 'name': if current.value in ('true', 'false'): node = self.parse_bool_expression() elif current.value == 'none': node = self.parse_none_expression() elif current.value == 'undefined': node = self.parse_undefined_expression() elif current.value == '_': node = self.parse_gettext_call() else: node = self.parse_name_expression() elif current.type in ('integer', 'float'): node = self.parse_number_expression() elif current.type == 'string': node = self.parse_string_expression() elif current.type == 'regex': node = self.parse_regex_expression() elif current.type == 'lparen': node = self.parse_paren_expression() elif current.type == 'lbracket': node = self.parse_list_expression() elif current.type == 'lbrace': node = self.parse_dict_expression() elif current.type == 'at': node = self.parse_set_expression() else: raise TemplateSyntaxError("unexpected token '%s'" % self.stream.current, self.stream.current.lineno, self.filename) if parse_postfix: node = self.parse_postfix_expression(node) return node def parse_tuple_expression(self, enforce=False, simplified=False): """ Parse multiple expressions into a tuple. This can also return just one expression which is not a tuple. If you want to enforce a tuple, pass it enforce=True. """ lineno = self.stream.lineno if simplified: parse = self.parse_primary_expression else: parse = self.parse_expression args = [] is_tuple = False while True: if args: self.stream.expect('comma') if self.stream.current.type in tuple_edge_tokens: break args.append(parse()) if self.stream.current.type == 'comma': is_tuple = True else: break if not is_tuple and args: if enforce: raise TemplateSyntaxError('tuple expected', lineno, self.filename) return args[0] return nodes.TupleExpression(args, lineno, self.filename) def parse_bool_expression(self): """ Parse a boolean literal. """ token = self.stream.expect('name') if token.value == 'true': value = True elif token.value == 'false': value = False else: raise TemplateSyntaxError("expected boolean literal", token.lineno, self.filename) return nodes.ConstantExpression(value, token.lineno, self.filename) def parse_none_expression(self): """ Parse a none literal. """ token = self.stream.expect('name', 'none') return nodes.ConstantExpression(None, token.lineno, self.filename) def parse_undefined_expression(self): """ Parse an undefined literal. """ token = self.stream.expect('name', 'undefined') return nodes.UndefinedExpression(token.lineno, self.filename) def parse_gettext_call(self): """ parse {{ _('foo') }}. """ # XXX: check if only one argument was passed and if # it is a string literal. Maybe that should become a special # expression anyway. token = self.stream.expect('name', '_') node = nodes.NameExpression(token.value, token.lineno, self.filename) return self.parse_call_expression(node) def parse_name_expression(self): """ Parse any name. """ token = self.stream.expect('name') self.test_name(token.value) return nodes.NameExpression(token.value, token.lineno, self.filename) def parse_number_expression(self): """ Parse a number literal. """ token = self.stream.current if token.type not in ('integer', 'float'): raise TemplateSyntaxError('integer or float literal expected', token.lineno, self.filename) self.stream.next() return nodes.ConstantExpression(token.value, token.lineno, self.filename) def parse_string_expression(self): """ Parse a string literal. """ token = self.stream.expect('string') return nodes.ConstantExpression(token.value, token.lineno, self.filename) def parse_regex_expression(self): """ Parse a regex literal. """ token = self.stream.expect('regex') return nodes.RegexExpression(token.value, token.lineno, self.filename) def parse_paren_expression(self): """ Parse a parenthized expression. """ self.stream.expect('lparen') try: return self.parse_tuple_expression() finally: self.stream.expect('rparen') def parse_list_expression(self): """ Parse something like {{ [1, 2, "three"] }} """ token = self.stream.expect('lbracket') items = [] while self.stream.current.type != 'rbracket': if items: self.stream.expect('comma') if self.stream.current.type == 'rbracket': break items.append(self.parse_expression()) self.stream.expect('rbracket') return nodes.ListExpression(items, token.lineno, self.filename) def parse_dict_expression(self): """ Parse something like {{ {1: 2, 3: 4} }} """ token = self.stream.expect('lbrace') items = [] while self.stream.current.type != 'rbrace': if items: self.stream.expect('comma') if self.stream.current.type == 'rbrace': break key = self.parse_expression() self.stream.expect('colon') value = self.parse_expression() items.append((key, value)) self.stream.expect('rbrace') return nodes.DictExpression(items, token.lineno, self.filename) def parse_set_expression(self): """ Parse something like {{ @(1, 2, 3) }}. """ token = self.stream.expect('at') self.stream.expect('lparen') items = [] while self.stream.current.type != 'rparen': if items: self.stream.expect('comma') if self.stream.current.type == 'rparen': break items.append(self.parse_expression()) self.stream.expect('rparen') return nodes.SetExpression(items, token.lineno, self.filename) def parse_postfix_expression(self, node): """ Parse a postfix expression such as a filter statement or a function call. """ while True: current = self.stream.current.type if current == 'dot' or current == 'lbracket': node = self.parse_subscript_expression(node) elif current == 'lparen': node = self.parse_call_expression(node) elif current == 'pipe': node = self.parse_filter_expression(node) elif current == 'is': node = self.parse_test_expression(node) else: break return node def parse_subscript_expression(self, node): """ Parse a subscript statement. Gets attributes and items from an object. """ lineno = self.stream.lineno if self.stream.current.type == 'dot': self.stream.next() token = self.stream.current if token.type in ('name', 'integer'): arg = nodes.ConstantExpression(token.value, token.lineno, self.filename) else: raise TemplateSyntaxError('expected name or number', token.lineno, self.filename) self.stream.next() elif self.stream.current.type == 'lbracket': self.stream.next() args = [] while self.stream.current.type != 'rbracket': if args: self.stream.expect('comma') args.append(self.parse_subscribed_expression()) self.stream.expect('rbracket') if len(args) == 1: arg = args[0] else: arg = nodes.TupleExpression(args, lineno, self.filename) else: raise TemplateSyntaxError('expected subscript expression', self.lineno, self.filename) return nodes.SubscriptExpression(node, arg, lineno, self.filename) def parse_call_expression(self, node=None): """ Parse a call. """ if node is None: node = self.parse_primary_expression(parse_postfix=False) token = self.stream.expect('lparen') args = [] kwargs = [] dyn_args = None dyn_kwargs = None require_comma = False def ensure(expr): if not expr: raise TemplateSyntaxError('invalid syntax for function ' 'call expression', token.lineno, self.filename) while self.stream.current.type != 'rparen': if require_comma: self.stream.expect('comma') # support for trailing comma if self.stream.current.type == 'rparen': break if self.stream.current.type == 'mul': ensure(dyn_args is None and dyn_kwargs is None) self.stream.next() dyn_args = self.parse_expression() elif self.stream.current.type == 'pow': ensure(dyn_kwargs is None) self.stream.next() dyn_kwargs = self.parse_expression() else: ensure(dyn_args is None and dyn_kwargs is None) if self.stream.current.type == 'name' and \ self.stream.look().type == 'assign': key = self.stream.current.value self.stream.skip(2) kwargs.append((key, self.parse_expression())) else: ensure(not kwargs) args.append(self.parse_expression()) require_comma = True self.stream.expect('rparen') return nodes.CallExpression(node, args, kwargs, dyn_args, dyn_kwargs, token.lineno, self.filename) def parse_filter_expression(self, node): """ Parse filter calls. """ lineno = self.stream.lineno filters = [] while self.stream.current.type == 'pipe': self.stream.next() token = self.stream.expect('name') args = [] if self.stream.current.type == 'lparen': self.stream.next() while self.stream.current.type != 'rparen': if args: self.stream.expect('comma') args.append(self.parse_expression()) self.stream.expect('rparen') filters.append((token.value, args)) return nodes.FilterExpression(node, filters, lineno, self.filename) def parse_test_expression(self, node): """ Parse test calls. """ token = self.stream.expect('is') if self.stream.current.type == 'not': self.stream.next() negated = True else: negated = False name = self.stream.expect('name').value args = [] if self.stream.current.type == 'lparen': self.stream.next() while self.stream.current.type != 'rparen': if args: self.stream.expect('comma') args.append(self.parse_expression()) self.stream.expect('rparen') elif self.stream.current.type in ('name', 'string', 'integer', 'float', 'lparen', 'lbracket', 'lbrace', 'regex'): args.append(self.parse_expression()) node = nodes.TestExpression(node, name, args, token.lineno, self.filename) if negated: node = nodes.NotExpression(node, token.lineno, self.filename) return node def test_name(self, name): """ Test if a name is not a special constant """ if name in ('true', 'false', 'none', 'undefined', '_'): raise TemplateSyntaxError('expected name not special constant', self.stream.lineno, self.filename) def subparse(self, test, drop_needle=False): """ Helper function used to parse the sourcecode until the test function which is passed a tuple in the form (lineno, token, data) returns True. In that case the current token is pushed back to the stream and the generator ends. The test function is only called for the first token after a block tag. Variable tags are *not* aliases for {% print %} in that case. If drop_needle is True the needle_token is removed from the stream. """ if self.closed: raise RuntimeError('parser is closed') result = [] buffer = [] next = self.stream.next lineno = self.stream.lineno def assemble_list(): push_buffer() return nodes.NodeList(result, lineno, self.filename) def push_variable(): buffer.append((True, self.parse_tuple_expression())) def push_data(): buffer.append((False, self.stream.expect('data'))) def push_buffer(): if not buffer: return template = [] variables = [] for is_var, data in buffer: if is_var: template.append('%s') variables.append(data) else: template.append(data.value.replace('%', '%%')) result.append(nodes.Text(u''.join(template), variables, buffer[0][1].lineno, self.filename)) del buffer[:] def push_node(node): push_buffer() result.append(node) while self.stream: token_type = self.stream.current.type if token_type == 'variable_begin': next() push_variable() self.stream.expect('variable_end') elif token_type == 'raw_begin': next() push_data() self.stream.expect('raw_end') elif token_type == 'block_begin': next() if test is not None and test(self.stream.current): if drop_needle: next() return assemble_list() handler = self.directives.get(self.stream.current.type) if handler is None: if self.no_variable_block: push_variable() self.stream.expect('block_end') elif self.stream.current.type in self.context_directives: raise TemplateSyntaxError('unexpected directive %r.' % self.stream.current.type, lineno, self.filename) else: name = self.stream.current.value raise TemplateSyntaxError('unknown directive %r.' % name, lineno, self.filename) else: node = handler() if node is not None: push_node(node) elif token_type == 'data': push_data() # this should be unreachable code else: assert False, "unexpected token %r" % self.stream.current if test is not None: msg = isinstance(test, StateTest) and ': ' + test.msg or '' raise TemplateSyntaxError('unexpected end of stream' + msg, self.stream.lineno, self.filename) return assemble_list() def sanitize_tree(self, body, extends): self._sanitize_tree([body], [body], extends, body) return body def _sanitize_tree(self, nodelist, stack, extends, body): """ This is not a closure because python leaks memory if it is. It's used by `parse()` to make sure blocks do not trigger unexpected behavior. """ for node in nodelist: if extends is not None and \ node.__class__ is nodes.Block and \ stack[-1] is not body: for n in stack: if n.__class__ is nodes.Block: break else: raise TemplateSyntaxError('misplaced block %r, ' 'blocks in child ' 'templates must be ' 'either top level or ' 'located in a block ' 'tag.' % node.name, node.lineno, self.filename) stack.append(node) self._sanitize_tree(node.get_child_nodes(), stack, extends, body) stack.pop() def parse(self): """ Parse the template and return a Template node. This also does some post processing sanitizing and parses for an extends tag. """ if self.closed: raise RuntimeError('parser is closed') try: # get the leading whitespace, if we are not in a child # template we push that back to the stream later. leading_whitespace = self.stream.read_whitespace() # parse an optional extends which *must* be the first node # of a template. if self.stream.current.type == 'block_begin' and \ self.stream.look().type == 'extends': self.stream.skip(2) extends = self.stream.expect('string').value self.stream.expect('block_end') else: extends = None if leading_whitespace: self.stream.shift(leading_whitespace) body = self.sanitize_tree(self.subparse(None), extends) return nodes.Template(extends, body, 1, self.filename) finally: self.close() def close(self): """Clean up soon.""" self.closed = True self.stream = self.directives = self.stream = self.blocks = \ self.environment = None ./weblog+jinja-0.8/jinja/environment.py0000644000175000017500000003711111006524413017130 0ustar henryhenry# -*- coding: utf-8 -*- """ jinja.environment ~~~~~~~~~~~~~~~~~ Provides a class that holds runtime and parsing time options. :copyright: 2007 by Armin Ronacher. :license: BSD, see LICENSE for more details. """ from jinja.lexer import Lexer from jinja.parser import Parser from jinja.loaders import LoaderWrapper from jinja.datastructure import SilentUndefined, Markup, Context, FakeTranslator from jinja.utils import collect_translations, get_attribute from jinja.exceptions import FilterNotFound, TestNotFound, \ SecurityException, TemplateSyntaxError from jinja.defaults import DEFAULT_FILTERS, DEFAULT_TESTS, DEFAULT_NAMESPACE __all__ = ['Environment'] #: minor speedup _getattr = getattr class Environment(object): """ The Jinja environment. The core component of Jinja is the `Environment`. It contains important shared variables like configuration, filters, tests, globals and others. """ def __init__(self, block_start_string='{%', block_end_string='%}', variable_start_string='{{', variable_end_string='}}', comment_start_string='{#', comment_end_string='#}', trim_blocks=False, auto_escape=False, default_filters=None, template_charset='utf-8', charset='utf-8', namespace=None, loader=None, filters=None, tests=None, context_class=Context, undefined_singleton=SilentUndefined, disable_regexps=False, friendly_traceback=True, translator_factory=None): """ Here the possible initialization parameters: ========================= ============================================ `block_start_string` * the string marking the begin of a block. this defaults to ``'{%'``. `block_end_string` * the string marking the end of a block. defaults to ``'%}'``. `variable_start_string` * the string marking the begin of a print statement. defaults to ``'{{'``. `comment_start_string` * the string marking the begin of a comment. defaults to ``'{#'``. `comment_end_string` * the string marking the end of a comment. defaults to ``'#}'``. `trim_blocks` * If this is set to ``True`` the first newline after a block is removed (block, not variable tag!). Defaults to ``False``. `auto_escape` If this is set to ``True`` Jinja will automatically escape all variables using xml escaping methods. If you don't want to escape a string you have to wrap it in a ``Markup`` object from the ``jinja.datastructure`` module. If `auto_escape` is ``True`` there will be also a ``Markup`` object in the template namespace to define partial html fragments. Note that we do not recommend this feature. `default_filters` list of tuples in the form (``filter_name``, ``arguments``) where ``filter_name`` is the name of a registered filter and ``arguments`` a tuple with the filter arguments. The filters specified here will always be applied when printing data to the template. *new in Jinja 1.1* `template_charset` The charset of the templates. Defaults to ``'utf-8'``. `charset` Charset of all string input data. Defaults to ``'utf-8'``. `namespace` Global namespace for all templates. `loader` Specify a template loader. `filters` dict of filters or the default filters if not defined. `tests` dict of tests of the default tests if not defined. `context_class` the context class this template should use. See the `Context` documentation for more details. `undefined_singleton` The singleton value that is used for missing variables. *new in Jinja 1.1* `disable_regexps` Disable support for regular expresssions. `friendly_traceback` Set this to `False` to disable the developer friendly traceback rewriting. Whenever an runtime or syntax error occours jinja will try to make a developer friendly traceback that shows the error in the template line. This however can be annoying when debugging broken functions that are called from the template. *new in Jinja 1.1* `translator_factory` A callback function that is called with the context as first argument to get the translator for the current instance. *new in Jinja 1.2* ========================= ============================================ All of these variables except those marked with a star (*) are modifiable after environment initialization. """ # lexer / parser information self.block_start_string = block_start_string self.block_end_string = block_end_string self.variable_start_string = variable_start_string self.variable_end_string = variable_end_string self.comment_start_string = comment_start_string self.comment_end_string = comment_end_string self.trim_blocks = trim_blocks # other stuff self.template_charset = template_charset self.charset = charset self.loader = loader if filters is None: filters = DEFAULT_FILTERS.copy() self.filters = filters if tests is None: tests = DEFAULT_TESTS.copy() self.tests = tests self.default_filters = default_filters or [] self.context_class = context_class self.undefined_singleton = undefined_singleton self.disable_regexps = disable_regexps self.friendly_traceback = friendly_traceback # global namespace if namespace is None: namespace = DEFAULT_NAMESPACE.copy() self.globals = namespace # jinja 1.0 compatibility if auto_escape: self.default_filters.append(('escape', (True,))) self.globals['Markup'] = Markup # and here the translator factory self.translator_factory = translator_factory # create lexer self.lexer = Lexer(self) def loader(self, value): """ Get or set the template loader. """ self._loader = LoaderWrapper(self, value) loader = property(lambda s: s._loader, loader, doc=loader.__doc__) def parse(self, source, filename=None): """ Parse the sourcecode and return the abstract syntax tree. This tree of nodes is used by the `translators`_ to convert the template into executable source- or bytecode. .. _translators: translators.txt """ parser = Parser(self, source, filename) return parser.parse() def lex(self, source, filename=None): """ Lex the given sourcecode and return a generator that yields tokens. The stream returned is not usable for Jinja but can be used if Jinja templates should be processed by other tools (for example syntax highlighting etc) The tuples are returned in the form ``(lineno, token, value)``. """ return self.lexer.tokeniter(source, filename) def from_string(self, source): """ Load and parse a template source and translate it into eval-able Python code. This code is wrapped within a `Template` class that allows you to render it. """ from jinja.translators.python import PythonTranslator try: rv = PythonTranslator.process(self, Parser(self, source).parse(), source) except TemplateSyntaxError, e: # on syntax errors rewrite the traceback if wanted if not self.friendly_traceback: raise from jinja.debugger import raise_syntax_error if __debug__: __traceback_hide__ = True raise_syntax_error(e, self, source) else: return rv def get_template(self, filename): """ Load a template from a loader. If the template does not exist, you will get a `TemplateNotFound` exception. """ return self._loader.load(filename) def to_unicode(self, value): """ Convert a value to unicode with the rules defined on the environment. """ # undefined and None expand to "" if value in (None, self.undefined_singleton): return u'' # things that are already unicode can pass. As long as nobody # does ugly things with the class it works for jinja too elif isinstance(value, unicode): return value # otherwise try to use __unicode__ or decode __str__ try: return unicode(value) except UnicodeError: return str(value).decode(self.charset, 'ignore') def get_translator(self, context): """ Return the translator for i18n. A translator is an object that provides the two functions ``gettext(string)`` and ``ngettext(singular, plural, n)``. Note that both of them have to return unicode! """ if self.translator_factory is not None: return self.translator_factory(context) return FakeTranslator() def get_translations(self, name): """ Load template `name` and return all translatable strings (note that that it really just returns the strings form this template, not from the parent or any included templates!) """ return collect_translations(self.loader.parse(name)) def get_translations_for_string(self, string): """ Like `get_translations`, but the translations are loaded from a normal string that represents the template. """ return collect_translations(self.parse(string)) def apply_filters(self, value, context, filters): """ Apply a list of filters on the variable. """ # some traceback systems allow to skip frames. but allow # disabling that via -O to not make things slow if __debug__: __traceback_hide__ = True cache = context.cache for key in filters: if key in cache: func = cache[key] else: filtername, args = key if filtername not in self.filters: raise FilterNotFound(filtername) cache[key] = func = self.filters[filtername](*args) value = func(self, context, value) return value def perform_test(self, context, testname, args, value): """ Perform a test on a variable. """ # some traceback systems allow to skip frames. but allow # disabling that via -O to not make things slow if __debug__: __traceback_hide__ = True key = (testname, args) if key in context.cache: func = context.cache[key] else: if testname not in self.tests: raise TestNotFound(testname) context.cache[key] = func = self.tests[testname](*args) return not not func(self, context, value) def get_attribute(self, obj, name): """ Get one attribute from an object. """ # some traceback systems allow to skip frames. but allow # disabling that via -O to not make things slow if __debug__: __traceback_hide__ = True try: return obj[name] except (TypeError, KeyError, IndexError, AttributeError): try: return get_attribute(obj, name) except (AttributeError, SecurityException): pass if obj is self.undefined_singleton: return _getattr(obj, name) return self.undefined_singleton def get_attributes(self, obj, attributes): """ Get some attributes from an object. If attributes is an empty sequence the object is returned as it. """ get = self.get_attribute for name in attributes: obj = get(obj, name) return obj def call_function(self, f, context, args, kwargs, dyn_args, dyn_kwargs): """ Function call helper. Called for all functions that are passed any arguments. """ # some traceback systems allow to skip frames. but allow # disabling that via -O to not make things slow if __debug__: __traceback_hide__ = True if dyn_args is not None: args += tuple(dyn_args) if dyn_kwargs is not None: kwargs.update(dyn_kwargs) if _getattr(f, 'jinja_unsafe_call', False) or \ _getattr(f, 'alters_data', False): return self.undefined_singleton if _getattr(f, 'jinja_context_callable', False): args = (self, context) + args return f(*args, **kwargs) def call_function_simple(self, f, context): """ Function call without arguments. Because of the smaller signature and fewer logic here we have a bit of redundant code. """ # some traceback systems allow to skip frames. but allow # disabling that via -O to not make things slow if __debug__: __traceback_hide__ = True if _getattr(f, 'jinja_unsafe_call', False) or \ _getattr(f, 'alters_data', False): return self.undefined_singleton if _getattr(f, 'jinja_context_callable', False): return f(self, context) return f() def finish_var(self, value, ctx): """ As long as no write_var function is passed to the template evaluator the source generated by the python translator will call this function for all variables. """ # some traceback systems allow to skip frames. but allow # disabling that via -O to not make things slow if __debug__: __traceback_hide__ = True if value is None: return u'' elif value is self.undefined_singleton: return unicode(value) elif _getattr(value, 'jinja_no_finalization', False): return value val = self.to_unicode(value) if self.default_filters: val = self.apply_filters(val, ctx, self.default_filters) return val ./weblog+jinja-0.8/jinja/_speedups.c0000644000175000017500000002763211006524413016354 0ustar henryhenry/** * jinja._speedups * ~~~~~~~~~~~~~~~ * * This module implements the BaseContext, a c implementation of the * Context baseclass. If this extension is not compiled the datastructure * module implements a class in python. * * Note that if you change semantics here you have to edit the _native.py * to in order to support those changes for jinja setups without the * speedup module too. * * :copyright: 2007 by Armin Ronacher. * :license: BSD, see LICENSE for more details. */ #include #include /* Set by init_constants to real values */ static PyObject *Deferred; /** * Internal struct used by BaseContext to store the * stacked namespaces. */ struct StackLayer { PyObject *dict; /* current value, a dict */ struct StackLayer *prev; /* lower struct layer or NULL */ }; /** * BaseContext python class. */ typedef struct { PyObject_HEAD struct StackLayer *globals; /* the dict for the globals */ struct StackLayer *initial; /* initial values */ struct StackLayer *current; /* current values */ long stacksize; /* current size of the stack */ PyObject *undefined_singleton; /* the singleton returned on missing values */ } BaseContext; /** * Called by init_speedups in order to retrieve references * to some exceptions and classes defined in jinja python modules */ static int init_constants(void) { PyObject *datastructure = PyImport_ImportModule("jinja.datastructure"); if (!datastructure) return 0; Deferred = PyObject_GetAttrString(datastructure, "Deferred"); Py_DECREF(datastructure); return 1; } /** * GC Helper */ static int BaseContext_clear(BaseContext *self) { struct StackLayer *current = self->current, *tmp; while (current) { tmp = current; Py_XDECREF(current->dict); current->dict = NULL; current = tmp->prev; PyMem_Free(tmp); } self->current = NULL; return 0; } /** * Deallocator for BaseContext. * * Frees the memory for the stack layers before freeing the object. */ static void BaseContext_dealloc(BaseContext *self) { BaseContext_clear(self); self->ob_type->tp_free((PyObject*)self); } /** * GC Helper */ static int BaseContext_traverse(BaseContext *self, visitproc visit, void *args) { int vret; struct StackLayer *layer = self->current; while (layer) { vret = visit(layer->dict, args); if (vret != 0) return vret; layer = layer->prev; } return 0; } /** * Initializes the BaseContext. * * Like the native python class it takes a reference to the undefined * singleton which will be used for undefined values. * The other two arguments are the global namespace and the initial * namespace which usually contains the values passed to the render * function of the template. Both must be dicts. */ static int BaseContext_init(BaseContext *self, PyObject *args, PyObject *kwds) { PyObject *undefined = NULL, *globals = NULL, *initial = NULL; if (!PyArg_ParseTuple(args, "OOO", &undefined, &globals, &initial)) return -1; if (!PyDict_Check(globals) || !PyDict_Check(initial)) { PyErr_SetString(PyExc_TypeError, "stack layers must be dicts."); return -1; } self->current = PyMem_Malloc(sizeof(struct StackLayer)); self->current->prev = NULL; self->current->dict = PyDict_New(); if (!self->current->dict) return -1; self->initial = PyMem_Malloc(sizeof(struct StackLayer)); self->initial->prev = NULL; self->initial->dict = initial; Py_INCREF(initial); self->current->prev = self->initial; self->globals = PyMem_Malloc(sizeof(struct StackLayer)); self->globals->prev = NULL; self->globals->dict = globals; Py_INCREF(globals); self->initial->prev = self->globals; self->undefined_singleton = undefined; Py_INCREF(undefined); self->stacksize = 3; return 0; } /** * Pop the highest layer from the stack and return it */ static PyObject* BaseContext_pop(BaseContext *self) { PyObject *result; struct StackLayer *tmp = self->current; if (self->stacksize <= 3) { PyErr_SetString(PyExc_IndexError, "stack too small."); return NULL; } result = self->current->dict; assert(result); self->current = tmp->prev; PyMem_Free(tmp); self->stacksize--; /* Took the reference to result from the struct. */ return result; } /** * Push a new layer to the stack and return it. If no parameter * is provided an empty dict is created. Otherwise the dict passed * to it is used as new layer. */ static PyObject* BaseContext_push(BaseContext *self, PyObject *args) { PyObject *value = NULL; struct StackLayer *new; if (!PyArg_ParseTuple(args, "|O:push", &value)) return NULL; if (!value) { value = PyDict_New(); if (!value) return NULL; } else if (!PyDict_Check(value)) { PyErr_SetString(PyExc_TypeError, "dict required."); return NULL; } else Py_INCREF(value); new = PyMem_Malloc(sizeof(struct StackLayer)); if (!new) { Py_DECREF(value); return NULL; } new->dict = value; new->prev = self->current; self->current = new; self->stacksize++; Py_INCREF(value); return value; } /** * Getter that creates a list representation of the internal * stack. Used for compatibility with the native python implementation. */ static PyObject* BaseContext_getstack(BaseContext *self, void *closure) { int idx = 0; struct StackLayer *current = self->current; PyObject *result = PyList_New(self->stacksize); if (!result) return NULL; while (current) { Py_INCREF(current->dict); PyList_SET_ITEM(result, idx++, current->dict); current = current->prev; } PyList_Reverse(result); return result; } /** * Getter that returns a reference to the current layer in the context. */ static PyObject* BaseContext_getcurrent(BaseContext *self, void *closure) { Py_INCREF(self->current->dict); return self->current->dict; } /** * Getter that returns a reference to the initial layer in the context. */ static PyObject* BaseContext_getinitial(BaseContext *self, void *closure) { Py_INCREF(self->initial->dict); return self->initial->dict; } /** * Getter that returns a reference to the global layer in the context. */ static PyObject* BaseContext_getglobals(BaseContext *self, void *closure) { Py_INCREF(self->globals->dict); return self->globals->dict; } /** * Implements the context lookup. * * This works exactly like the native implementation but a lot * faster. It disallows access to internal names (names that start * with "::") and resolves Deferred values. */ static PyObject* BaseContext_getitem(BaseContext *self, PyObject *item) { PyObject *result; char *name = NULL; int isdeferred; struct StackLayer *current = self->current; /* allow unicode keys as long as they are ascii keys */ if (PyUnicode_CheckExact(item)) { item = PyUnicode_AsASCIIString(item); if (!item) goto missing; } else if (!PyString_Check(item)) goto missing; /* disallow access to internal jinja values */ name = PyString_AS_STRING(item); if (name[0] == ':' && name[1] == ':') goto missing; while (current) { /* GetItemString just builds a new string from "name" again... */ result = PyDict_GetItem(current->dict, item); if (!result) { current = current->prev; continue; } isdeferred = PyObject_IsInstance(result, Deferred); if (isdeferred == -1) return NULL; else if (isdeferred) { PyObject *namespace; PyObject *resolved = PyObject_CallFunctionObjArgs( result, self, item, NULL); if (!resolved) return NULL; /* never touch the globals */ if (current == self->globals) namespace = self->initial->dict; else namespace = current->dict; if (PyDict_SetItem(namespace, item, resolved) < 0) return NULL; Py_INCREF(resolved); return resolved; } Py_INCREF(result); return result; } missing: Py_INCREF(self->undefined_singleton); return self->undefined_singleton; } /** * Check if the context contains a given value. */ static int BaseContext_contains(BaseContext *self, PyObject *item) { char *name; struct StackLayer *current = self->current; /* allow unicode objects as keys as long as they are ASCII */ if (PyUnicode_CheckExact(item)) { item = PyUnicode_AsASCIIString(item); if (!item) return 0; } else if (!PyString_Check(item)) return 0; name = PyString_AS_STRING(item); if (name[0] == ':' && name[1] == ':') return 0; while (current) { /* XXX: for 2.4 and newer, use PyDict_Contains */ if (!PyMapping_HasKey(current->dict, item)) { current = current->prev; continue; } return 1; } return 0; } /** * Set an value in the highest layer or delete one. */ static int BaseContext_setitem(BaseContext *self, PyObject *item, PyObject *value) { /* allow unicode objects as keys as long as they are ASCII */ if (PyUnicode_CheckExact(item)) { item = PyUnicode_AsASCIIString(item); if (!item) { PyErr_Clear(); goto error; } } else if (!PyString_Check(item)) goto error; if (!value) return PyDict_DelItem(self->current->dict, item); return PyDict_SetItem(self->current->dict, item, value); error: PyErr_SetString(PyExc_TypeError, "expected string argument"); return -1; } static PyGetSetDef BaseContext_getsetters[] = { {"stack", (getter)BaseContext_getstack, NULL, "a read only copy of the internal stack", NULL}, {"current", (getter)BaseContext_getcurrent, NULL, "reference to the current layer on the stack", NULL}, {"initial", (getter)BaseContext_getinitial, NULL, "reference to the initial layer on the stack", NULL}, {"globals", (getter)BaseContext_getglobals, NULL, "reference to the global layer on the stack", NULL}, {NULL} /* Sentinel */ }; static PyMethodDef BaseContext_methods[] = { {"pop", (PyCFunction)BaseContext_pop, METH_NOARGS, "ctx.pop() -> dict\n\n" "Pop the last layer from the stack and return it."}, {"push", (PyCFunction)BaseContext_push, METH_VARARGS, "ctx.push([layer]) -> layer\n\n" "Push one layer to the stack. Layer must be a dict " "or omitted."}, {NULL} /* Sentinel */ }; static PySequenceMethods BaseContext_as_sequence = { 0, /* sq_length */ 0, /* sq_concat */ 0, /* sq_repeat */ 0, /* sq_item */ 0, /* sq_slice */ 0, /* sq_ass_item */ 0, /* sq_ass_slice */ (objobjproc)BaseContext_contains,/* sq_contains */ 0, /* sq_inplace_concat */ 0 /* sq_inplace_repeat */ }; static PyMappingMethods BaseContext_as_mapping = { NULL, (binaryfunc)BaseContext_getitem, (objobjargproc)BaseContext_setitem }; static PyTypeObject BaseContextType = { PyObject_HEAD_INIT(NULL) 0, /* ob_size */ "jinja._speedups.BaseContext", /* tp_name */ sizeof(BaseContext), /* tp_basicsize */ 0, /* tp_itemsize */ (destructor)BaseContext_dealloc,/* tp_dealloc */ 0, /* tp_print */ 0, /* tp_getattr */ 0, /* tp_setattr */ 0, /* tp_compare */ 0, /* tp_repr */ 0, /* tp_as_number */ &BaseContext_as_sequence, /* tp_as_sequence */ &BaseContext_as_mapping, /* tp_as_mapping */ 0, /* tp_hash */ 0, /* tp_call */ 0, /* tp_str */ 0, /* tp_getattro */ 0, /* tp_setattro */ 0, /* tp_as_buffer */ Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE | Py_TPFLAGS_HAVE_GC, /*tp_flags*/ "", /* tp_doc */ (traverseproc)BaseContext_traverse, /* tp_traverse */ (inquiry)BaseContext_clear, /* tp_clear */ 0, /* tp_richcompare */ 0, /* tp_weaklistoffset */ 0, /* tp_iter */ 0, /* tp_iternext */ BaseContext_methods, /* tp_methods */ 0, /* tp_members */ BaseContext_getsetters, /* tp_getset */ 0, /* tp_base */ 0, /* tp_dict */ 0, /* tp_descr_get */ 0, /* tp_descr_set */ 0, /* tp_dictoffset */ (initproc)BaseContext_init, /* tp_init */ 0, /* tp_alloc */ 0 /* tp_new */ }; static PyMethodDef module_methods[] = { {NULL, NULL, 0, NULL} /* Sentinel */ }; #ifndef PyMODINIT_FUNC /* declarations for DLL import/export */ #define PyMODINIT_FUNC void #endif PyMODINIT_FUNC init_speedups(void) { PyObject *module; BaseContextType.tp_new = (newfunc)PyType_GenericNew; if (PyType_Ready(&BaseContextType) < 0) return; if (!init_constants()) return; module = Py_InitModule3("_speedups", module_methods, ""); if (!module) return; Py_INCREF(&BaseContextType); PyModule_AddObject(module, "BaseContext", (PyObject*)&BaseContextType); } ./weblog+jinja-0.8/jinja/LICENSE0000644000175000017500000000301411006524413015212 0ustar henryhenryCopyright (c) 2006 by the respective authors (see AUTHORS file). 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. * The names of the contributors may not 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+jinja-0.8/jinja/nodes.py0000644000175000017500000004224611006524413015701 0ustar henryhenry# -*- coding: utf-8 -*- """ jinja.nodes ~~~~~~~~~~~ This module implements additional nodes derived from the ast base node. It also provides some node tree helper functions like `in_lineno` and `get_nodes` used by the parser and translator in order to normalize python and jinja nodes. :copyright: 2007 by Armin Ronacher. :license: BSD, see LICENSE for more details. """ from itertools import chain from copy import copy def get_nodes(nodetype, tree, exclude_root=True): """ Get all nodes from nodetype in the tree excluding the node passed if `exclude_root` is `True` (default). """ if exclude_root: todo = tree.get_child_nodes() else: todo = [tree] while todo: node = todo.pop() if node.__class__ is nodetype: yield node todo.extend(node.get_child_nodes()) class NotPossible(NotImplementedError): """ If a given node cannot do something. """ class Node(object): """ Jinja node. """ def __init__(self, lineno=None, filename=None): self.lineno = lineno self.filename = filename def get_items(self): return [] def get_child_nodes(self): return [x for x in self.get_items() if isinstance(x, Node)] def allows_assignments(self): return False def __repr__(self): return 'Node()' class Text(Node): """ Node that represents normal text. """ def __init__(self, text, variables, lineno=None, filename=None): Node.__init__(self, lineno, filename) self.text = text self.variables = variables def get_items(self): return [self.text] + list(self.variables) def __repr__(self): return 'Text(%r, %r)' % ( self.text, self.variables ) class NodeList(list, Node): """ A node that stores multiple childnodes. """ def __init__(self, data, lineno=None, filename=None): Node.__init__(self, lineno, filename) list.__init__(self, data) def get_items(self): return list(self) def __repr__(self): return 'NodeList(%s)' % list.__repr__(self) class Template(Node): """ Node that represents a template. """ def __init__(self, extends, body, lineno=None, filename=None): Node.__init__(self, lineno, filename) self.extends = extends self.body = body def get_items(self): return [self.extends, self.body] def __repr__(self): return 'Template(%r, %r)' % ( self.extends, self.body ) class ForLoop(Node): """ A node that represents a for loop """ def __init__(self, item, seq, body, else_, recursive, lineno=None, filename=None): Node.__init__(self, lineno, filename) self.item = item self.seq = seq self.body = body self.else_ = else_ self.recursive = recursive def get_items(self): return [self.item, self.seq, self.body, self.else_, self.recursive] def __repr__(self): return 'ForLoop(%r, %r, %r, %r, %r)' % ( self.item, self.seq, self.body, self.else_, self.recursive ) class IfCondition(Node): """ A node that represents an if condition. """ def __init__(self, tests, else_, lineno=None, filename=None): Node.__init__(self, lineno, filename) self.tests = tests self.else_ = else_ def get_items(self): result = [] for test in self.tests: result.extend(test) result.append(self.else_) return result def __repr__(self): return 'IfCondition(%r, %r)' % ( self.tests, self.else_ ) class Cycle(Node): """ A node that represents the cycle statement. """ def __init__(self, seq, lineno=None, filename=None): Node.__init__(self, lineno, filename) self.seq = seq def get_items(self): return [self.seq] def __repr__(self): return 'Cycle(%r)' % (self.seq,) class Print(Node): """ A node that represents variable tags and print calls. """ def __init__(self, expr, lineno=None, filename=None): Node.__init__(self, lineno, filename) self.expr = expr def get_items(self): return [self.expr] def __repr__(self): return 'Print(%r)' % (self.expr,) class Macro(Node): """ A node that represents a macro. """ def __init__(self, name, arguments, body, lineno=None, filename=None): Node.__init__(self, lineno, filename) self.name = name self.arguments = arguments self.body = body def get_items(self): return [self.name] + list(chain(*self.arguments)) + [self.body] def __repr__(self): return 'Macro(%r, %r, %r)' % ( self.name, self.arguments, self.body ) class Call(Node): """ A node that represents am extended macro call. """ def __init__(self, expr, body, lineno=None, filename=None): Node.__init__(self, lineno, filename) self.expr = expr self.body = body def get_items(self): return [self.expr, self.body] def __repr__(self): return 'Call(%r, %r)' % ( self.expr, self.body ) class Set(Node): """ Allows defining own variables. """ def __init__(self, name, expr, scope_local, lineno=None, filename=None): Node.__init__(self, lineno, filename) self.name = name self.expr = expr self.scope_local = scope_local def get_items(self): return [self.name, self.expr, self.scope_local] def __repr__(self): return 'Set(%r, %r, %r)' % ( self.name, self.expr, self.scope_local ) class Filter(Node): """ Node for filter sections. """ def __init__(self, body, filters, lineno=None, filename=None): Node.__init__(self, lineno, filename) self.body = body self.filters = filters def get_items(self): return [self.body] + list(self.filters) def __repr__(self): return 'Filter(%r, %r)' % ( self.body, self.filters ) class Block(Node): """ A node that represents a block. """ def __init__(self, name, body, lineno=None, filename=None): Node.__init__(self, lineno, filename) self.name = name self.body = body def replace(self, node): """ Replace the current data with the copied data of another block node. """ assert node.__class__ is Block self.lineno = node.lineno self.filename = node.filename self.name = node.name self.body = copy(node.body) def clone(self): """ Create an independent clone of this node. """ return copy(self) def get_items(self): return [self.name, self.body] def __repr__(self): return 'Block(%r, %r)' % ( self.name, self.body ) class Include(Node): """ A node that represents the include tag. """ def __init__(self, template, lineno=None, filename=None): Node.__init__(self, lineno, filename) self.template = template def get_items(self): return [self.template] def __repr__(self): return 'Include(%r)' % ( self.template ) class Trans(Node): """ A node for translatable sections. """ def __init__(self, singular, plural, indicator, replacements, lineno=None, filename=None): Node.__init__(self, lineno, filename) self.singular = singular self.plural = plural self.indicator = indicator self.replacements = replacements def get_items(self): rv = [self.singular, self.plural, self.indicator] if self.replacements: rv.extend(self.replacements.values()) rv.extend(self.replacements.keys()) return rv def __repr__(self): return 'Trans(%r, %r, %r, %r)' % ( self.singular, self.plural, self.indicator, self.replacements ) class Expression(Node): """ Baseclass for all expressions. """ class BinaryExpression(Expression): """ Baseclass for all binary expressions. """ def __init__(self, left, right, lineno=None, filename=None): Expression.__init__(self, lineno, filename) self.left = left self.right = right def get_items(self): return [self.left, self.right] def __repr__(self): return '%s(%r, %r)' % ( self.__class__.__name__, self.left, self.right ) class UnaryExpression(Expression): """ Baseclass for all unary expressions. """ def __init__(self, node, lineno=None, filename=None): Expression.__init__(self, lineno, filename) self.node = node def get_items(self): return [self.node] def __repr__(self): return '%s(%r)' % ( self.__class__.__name__, self.node ) class ConstantExpression(Expression): """ any constat such as {{ "foo" }} """ def __init__(self, value, lineno=None, filename=None): Expression.__init__(self, lineno, filename) self.value = value def get_items(self): return [self.value] def __repr__(self): return 'ConstantExpression(%r)' % (self.value,) class UndefinedExpression(Expression): """ represents the special 'undefined' value. """ def __repr__(self): return 'UndefinedExpression()' class RegexExpression(Expression): """ represents the regular expression literal. """ def __init__(self, value, lineno=None, filename=None): Expression.__init__(self, lineno, filename) self.value = value def get_items(self): return [self.value] def __repr__(self): return 'RegexExpression(%r)' % (self.value,) class NameExpression(Expression): """ any name such as {{ foo }} """ def __init__(self, name, lineno=None, filename=None): Expression.__init__(self, lineno, filename) self.name = name def get_items(self): return [self.name] def allows_assignments(self): return self.name != '_' def __repr__(self): return 'NameExpression(%r)' % self.name class ListExpression(Expression): """ any list literal such as {{ [1, 2, 3] }} """ def __init__(self, items, lineno=None, filename=None): Expression.__init__(self, lineno, filename) self.items = items def get_items(self): return list(self.items) def __repr__(self): return 'ListExpression(%r)' % (self.items,) class DictExpression(Expression): """ any dict literal such as {{ {1: 2, 3: 4} }} """ def __init__(self, items, lineno=None, filename=None): Expression.__init__(self, lineno, filename) self.items = items def get_items(self): return list(chain(*self.items)) def __repr__(self): return 'DictExpression(%r)' % (self.items,) class SetExpression(Expression): """ any set literal such as {{ @(1, 2, 3) }} """ def __init__(self, items, lineno=None, filename=None): Expression.__init__(self, lineno, filename) self.items = items def get_items(self): return self.items[:] def __repr__(self): return 'SetExpression(%r)' % (self.items,) class ConditionalExpression(Expression): """ {{ foo if bar else baz }} """ def __init__(self, test, expr1, expr2, lineno=None, filename=None): Expression.__init__(self, lineno, filename) self.test = test self.expr1 = expr1 self.expr2 = expr2 def get_items(self): return [self.test, self.expr1, self.expr2] def __repr__(self): return 'ConstantExpression(%r, %r, %r)' % ( self.test, self.expr1, self.expr2 ) class FilterExpression(Expression): """ {{ foo|bar|baz }} """ def __init__(self, node, filters, lineno=None, filename=None): Expression.__init__(self, lineno, filename) self.node = node self.filters = filters def get_items(self): result = [self.node] for filter, args in self.filters: result.append(filter) result.extend(args) return result def __repr__(self): return 'FilterExpression(%r, %r)' % ( self.node, self.filters ) class TestExpression(Expression): """ {{ foo is lower }} """ def __init__(self, node, name, args, lineno=None, filename=None): Expression.__init__(self, lineno, filename) self.node = node self.name = name self.args = args def get_items(self): return [self.node, self.name] + list(self.args) def __repr__(self): return 'TestExpression(%r, %r, %r)' % ( self.node, self.name, self.args ) class CallExpression(Expression): """ {{ foo(bar) }} """ def __init__(self, node, args, kwargs, dyn_args, dyn_kwargs, lineno=None, filename=None): Expression.__init__(self, lineno, filename) self.node = node self.args = args self.kwargs = kwargs self.dyn_args = dyn_args self.dyn_kwargs = dyn_kwargs def get_items(self): return [self.node, self.args, self.kwargs, self.dyn_args, self.dyn_kwargs] def __repr__(self): return 'CallExpression(%r, %r, %r, %r, %r)' % ( self.node, self.args, self.kwargs, self.dyn_args, self.dyn_kwargs ) class SubscriptExpression(Expression): """ {{ foo.bar }} and {{ foo['bar'] }} etc. """ def __init__(self, node, arg, lineno=None, filename=None): Expression.__init__(self, lineno, filename) self.node = node self.arg = arg def get_items(self): return [self.node, self.arg] def __repr__(self): return 'SubscriptExpression(%r, %r)' % ( self.node, self.arg ) class SliceExpression(Expression): """ 1:2:3 etc. """ def __init__(self, start, stop, step, lineno=None, filename=None): Expression.__init__(self, lineno, filename) self.start = start self.stop = stop self.step = step def get_items(self): return [self.start, self.stop, self.step] def __repr__(self): return 'SliceExpression(%r, %r, %r)' % ( self.start, self.stop, self.step ) class TupleExpression(Expression): """ For loop unpacking and some other things like multiple arguments for subscripts. """ def __init__(self, items, lineno=None, filename=None): Expression.__init__(self, lineno, filename) self.items = items def get_items(self): return list(self.items) def allows_assignments(self): for item in self.items: if not item.allows_assignments(): return False return True def __repr__(self): return 'TupleExpression(%r)' % (self.items,) class ConcatExpression(Expression): """ For {{ foo ~ bar }}. Because of various reasons (especially because unicode conversion takes place for the left and right expression and is better optimized that way) """ def __init__(self, args, lineno=None, filename=None): Expression.__init__(self, lineno, filename) self.args = args def get_items(self): return list(self.args) def __repr__(self): return 'ConcatExpression(%r)' % (self.items,) class CompareExpression(Expression): """ {{ foo == bar }}, {{ foo >= bar }} etc. """ def __init__(self, expr, ops, lineno=None, filename=None): Expression.__init__(self, lineno, filename) self.expr = expr self.ops = ops def get_items(self): return [self.expr] + list(chain(*self.ops)) def __repr__(self): return 'CompareExpression(%r, %r)' % ( self.expr, self.ops ) class MulExpression(BinaryExpression): """ {{ foo * bar }} """ class DivExpression(BinaryExpression): """ {{ foo / bar }} """ class FloorDivExpression(BinaryExpression): """ {{ foo // bar }} """ class AddExpression(BinaryExpression): """ {{ foo + bar }} """ class SubExpression(BinaryExpression): """ {{ foo - bar }} """ class ModExpression(BinaryExpression): """ {{ foo % bar }} """ class PowExpression(BinaryExpression): """ {{ foo ** bar }} """ class AndExpression(BinaryExpression): """ {{ foo and bar }} """ class OrExpression(BinaryExpression): """ {{ foo or bar }} """ class NotExpression(UnaryExpression): """ {{ not foo }} """ class NegExpression(UnaryExpression): """ {{ -foo }} """ class PosExpression(UnaryExpression): """ {{ +foo }} """ ./weblog+jinja-0.8/jinja/AUTHORS0000644000175000017500000000036411006524413015262 0ustar henryhenryJinja is written and maintained by Armin Ronacher . Other contributors (as mentionend in :copyright:s) are: - Armin Ronacher - Georg Brandl - Lawrence Journal-World. - Bryan McLemore ./weblog+jinja-0.8/jinja/plugin.py0000644000175000017500000001732311006524413016065 0ustar henryhenry# -*- coding: utf-8 -*- """ jinja.plugin ~~~~~~~~~~~~ Support for the `GeneralTemplateInterface`__ and the Buffet interface. Do not use this module on your own. We don't recommend those interfaces! If you are able to, you should really use Jinja without those abstraction layers. __ http://trac.pocoo.org/wiki/GeneralTemplateInterface :copyright: 2007 by Armin Ronacher. :license: BSD, see LICENSE for more details. """ from warnings import warn from jinja.environment import Environment from jinja.loaders import FunctionLoader, FileSystemLoader, PackageLoader from jinja.exceptions import TemplateNotFound class BuffetPlugin(object): """ Implements the Jinja buffet plugin. Well. It works for pylons and should work for TurboGears too if their plugin system would work. """ def __init__(self, extra_vars_func=None, options=None): if 'jinja.environment' in options: self.env = options['jinja.environment'] else: opt = {} for key, value in options.iteritems(): if key.startswith('jinja.') and key != 'jinja.extension': opt[key[6:]] = value loader_func = opt.pop('loader_func', None) getmtime_func = opt.pop('getmtime_func', None) use_memcache = opt.pop('use_memcache', False) memcache_size = opt.pop('memcache_size', 40) cache_folder = opt.pop('cache_folder', None) auto_reload = opt.pop('auto_reload', True) if 'searchpath' in opt: opt['loader'] = FileSystemLoader(opt.pop('searchpath'), use_memcache, memcache_size, cache_folder, auto_reload) elif 'package' in opt: opt['loader'] = PackageLoader(opt.pop('package'), opt.pop('package_path', ''), use_memcache, memcache_size, cache_folder, auto_reload) elif loader_func is not None: opt['loader'] = FunctionLoader(loader_func, getmtime_func, use_memcache, memcache_size, cache_folder, auto_reload) self.env = Environment(**opt) self.extra_vars_func = extra_vars_func self.extension = options.pop('jinja.extension', 'html') def load_template(self, templatename, template_string=None): if template_string is not None: return self.env.from_string(template_string) if templatename.startswith('!'): jinja_name = templatename[1:] else: jinja_name = templatename.replace('.', '/') + '.' + self.extension return self.env.get_template(jinja_name) def render(self, info, format='html', fragment=False, template=None): if isinstance(template, basestring): template = self.load_template(template) if self.extra_vars_func: info.update(self.extra_vars_func()) return template.render(info) def jinja_plugin_factory(options): """ Basic implementation of the `GeneralTemplateInterface`. Supports ``loader_func`` and ``getmtime_func``, as well as string and file loading but ignores ``mode`` since it's a text based template engine. All options passed to this function are forwarded to the jinja environment. Exceptions are the following keys: =================== ================================================= ``environment`` If this is provided it must be the only configuration value and it's used as jinja environment. ``searchpath`` If provided a new file system loader with this search path is instanciated. ``package`` Name of the python package containing the templates. If this and ``package_path`` is defined a `PackageLoader` is used. ``package_path`` Path to the templates inside of a package. ``loader_func`` Function that takes the name of the template to load. If it returns a string or unicode object it's used to load a template. If the return value is None it's considered missing. ``getmtime_func`` Function used to check if templates requires reloading. Has to return the UNIX timestamp of the last template change or 0 if this template does not exist or requires updates at any cost. ``use_memcache`` Set this to ``True`` to enable memory caching. This is usually a good idea in production mode, but disable it during development since it won't reload template changes automatically. This only works in persistent environments like FastCGI. ``memcache_size`` Number of template instance you want to cache. Defaults to ``40``. ``cache_folder`` Set this to an existing directory to enable caching of templates on the file system. Note that this only affects templates transformed into python code. Default is ``None`` which means that caching is disabled. ``auto_reload`` Set this to `False` for a slightly better performance. In that case of `getmtime_func` not being provided this won't have an effect. =================== ================================================= """ warn(DeprecationWarning('general plugin interface implementation ' 'deprecated because not an accepted ' 'standard.')) if 'environment' in options: env = options['environment'] if not len(options) == 1: raise TypeError('if environment provided no other ' 'arguments are allowed') else: loader_func = options.pop('loader_func', None) getmtime_func = options.pop('getmtime_func', None) use_memcache = options.pop('use_memcache', False) memcache_size = options.pop('memcache_size', 40) cache_folder = options.pop('cache_folder', None) auto_reload = options.pop('auto_reload', True) if 'searchpath' in options: options['loader'] = FileSystemLoader(options.pop('searchpath'), use_memcache, memcache_size, cache_folder, auto_reload) elif 'package' in options: options['loader'] = PackageLoader(options.pop('package'), options.pop('package_path', ''), use_memcache, memcache_size, cache_folder, auto_reload) elif loader_func is not None: options['loader'] = FunctionLoader(loader_func, getmtime_func, use_memcache, memcache_size, cache_folder, auto_reload) env = Environment(**options) def render_function(template, values, options): if options.get('is_string'): tmpl = env.from_string(template) else: try: tmpl = env.get_template(template) except TemplateNotFound: return return tmpl.render(**values) return render_function ./weblog+jinja-0.8/jinja/constants.py0000644000175000017500000000312611006524413016577 0ustar henryhenry# -*- coding: utf-8 -*- """ jinja.constants ~~~~~~~~~~~~~~~ Various constants. :copyright: 2007 by Armin Ronacher. :license: BSD, see LICENSE for more details. """ #: list of lorem ipsum words used by the lipsum() helper function LOREM_IPSUM_WORDS = u'''\ a ac accumsan ad adipiscing aenean aliquam aliquet amet ante aptent arcu at auctor augue bibendum blandit class commodo condimentum congue consectetuer consequat conubia convallis cras cubilia cum curabitur curae cursus dapibus diam dictum dictumst dignissim dis dolor donec dui duis egestas eget eleifend elementum elit enim erat eros est et etiam eu euismod facilisi facilisis fames faucibus felis fermentum feugiat fringilla fusce gravida habitant habitasse hac hendrerit hymenaeos iaculis id imperdiet in inceptos integer interdum ipsum justo lacinia lacus laoreet lectus leo libero ligula litora lobortis lorem luctus maecenas magna magnis malesuada massa mattis mauris metus mi molestie mollis montes morbi mus nam nascetur natoque nec neque netus nibh nisi nisl non nonummy nostra nulla nullam nunc odio orci ornare parturient pede pellentesque penatibus per pharetra phasellus placerat platea porta porttitor posuere potenti praesent pretium primis proin pulvinar purus quam quis quisque rhoncus ridiculus risus rutrum sagittis sapien scelerisque sed sem semper senectus sit sociis sociosqu sodales sollicitudin suscipit suspendisse taciti tellus tempor tempus tincidunt torquent tortor tristique turpis ullamcorper ultrices ultricies urna ut varius vehicula vel velit venenatis vestibulum vitae vivamus viverra volutpat vulputate''' ./weblog+jinja-0.8/jinja/defaults.py0000644000175000017500000000064611006524413016376 0ustar henryhenry# -*- coding: utf-8 -*- """ jinja.defaults ~~~~~~~~~~~~~~ Jinja default filters and tags. :copyright: 2007 by Armin Ronacher. :license: BSD, see LICENSE for more details. """ from jinja.filters import FILTERS as DEFAULT_FILTERS from jinja.tests import TESTS as DEFAULT_TESTS from jinja.utils import NAMESPACE as DEFAULT_NAMESPACE __all__ = ['DEFAULT_FILTERS', 'DEFAULT_TESTS', 'DEFAULT_NAMESPACE'] ./weblog+jinja-0.8/jinja/loaders.py0000644000175000017500000007474411006524413016232 0ustar henryhenry# -*- coding: utf-8 -*- """ jinja.loaders ~~~~~~~~~~~~~ Jinja loader classes. :copyright: 2007 by Armin Ronacher, Bryan McLemore. :license: BSD, see LICENSE for more details. """ import codecs try: from hashlib import sha1 except ImportError: from sha import new as sha1 import time from os import path from threading import Lock from jinja.parser import Parser from jinja.translators.python import PythonTranslator, Template from jinja.exceptions import TemplateNotFound, TemplateSyntaxError, \ TemplateIncludeError from jinja.utils import CacheDict #: when updating this, update the listing in the jinja package too __all__ = ['FileSystemLoader', 'PackageLoader', 'DictLoader', 'ChoiceLoader', 'FunctionLoader', 'MemcachedFileSystemLoader'] def get_template_filename(searchpath, name): """ Return the filesystem filename wanted. """ return path.join(searchpath, *[p for p in name.split('/') if p and p[0] != '.']) def get_cachename(cachepath, name, salt=None): """ Return the filename for a cached file. """ return path.join(cachepath, 'jinja_%s.cache' % sha1('jinja(%s|%s)tmpl' % (name, salt or '')).hexdigest()) def _loader_missing(*args, **kwargs): """Helper function for `LoaderWrapper`.""" raise RuntimeError('no loader defined') class LoaderWrapper(object): """ Wraps a loader so that it's bound to an environment. Also handles template syntax errors. """ def __init__(self, environment, loader): self.environment = environment self.loader = loader if self.loader is None: self.get_source = self.parse = self.load = _loader_missing self.available = False else: self.available = True def __getattr__(self, name): """ Not found attributes are redirected to the loader """ return getattr(self.loader, name) def get_source(self, name, parent=None): """Retrieve the sourcecode of a template.""" # just ascii chars are allowed as template names name = str(name) return self.loader.get_source(self.environment, name, parent) def parse(self, name, parent=None): """Retreive a template and parse it.""" # just ascii chars are allowed as template names name = str(name) return self.loader.parse(self.environment, name, parent) def load(self, name, translator=PythonTranslator): """ Translate a template and return it. This must not necesarily be a template class. The javascript translator for example will just output a string with the translated code. """ # just ascii chars are allowed as template names name = str(name) try: return self.loader.load(self.environment, name, translator) except TemplateSyntaxError, e: if not self.environment.friendly_traceback: raise __traceback_hide__ = True from jinja.debugger import raise_syntax_error raise_syntax_error(e, self.environment) def get_controlled_loader(self): """ Return a loader that runs in a controlled environment. (Keeps track of templates that it loads and is not thread safe). """ return ControlledLoader(self.environment, self.loader) def _loader_missing(self, *args, **kwargs): """Helper method that overrides all other methods if no loader is defined.""" raise RuntimeError('no loader defined') def __nonzero__(self): return self.available class ControlledLoader(LoaderWrapper): """ Used for template extending and including. """ def __init__(self, environment, loader): LoaderWrapper.__init__(self, environment, loader) self._stack = [] def get_controlled_loader(self): raise TypeError('Cannot get new controlled loader from an already ' 'controlled loader.') def mark_as_processed(self): """Mark the last parsed/sourced/included template as processed.""" if not self._stack: raise RuntimeError('No template for marking found') self._stack.pop() def _controlled(method): def new_method(self, name, *args, **kw): if name in self._stack: raise TemplateIncludeError('Circular imports/extends ' 'detected. %r appeared twice.' % name) self._stack.append(name) return method(self, name, *args, **kw) try: new_method.__name__ = method.__name__ new_method.__doc__ = method.__doc__ except AttributeError: pass return new_method get_source = _controlled(LoaderWrapper.get_source) parse = _controlled(LoaderWrapper.parse) load = _controlled(LoaderWrapper.load) del _controlled class BaseLoader(object): """ Use this class to implement loaders. Just inherit from this class and implement a method called `get_source` with the signature (`environment`, `name`, `parent`) that returns sourcecode for the template. For more complex loaders you probably want to override `load` to or not use the `BaseLoader` at all. """ def parse(self, environment, name, parent): """ Load and parse a template """ source = self.get_source(environment, name, parent) return Parser(environment, source, name).parse() def load(self, environment, name, translator): """ Load and translate a template """ ast = self.parse(environment, name, None) return translator.process(environment, ast) def get_source(self, environment, name, parent): """ Override this method to get the source for a template. """ raise TemplateNotFound(name) class CachedLoaderMixin(object): """ Mixin this class to implement simple memory and disk caching. The memcaching just uses a dict in the loader so if you have a global environment or at least a global loader this can speed things up. If the memcaching is enabled you can use (with Jinja 1.1 onwards) the `clear_memcache` function to clear the cache. For memcached support check the `MemcachedLoaderMixin`. """ def __init__(self, use_memcache, cache_size, cache_folder, auto_reload, cache_salt=None): if use_memcache: self.__memcache = CacheDict(cache_size) else: self.__memcache = None self.__cache_folder = cache_folder if not hasattr(self, 'check_source_changed'): self.__auto_reload = False else: self.__auto_reload = auto_reload self.__salt = cache_salt self.__times = {} self.__lock = Lock() def clear_memcache(self): """ Clears the memcache. """ if self.__memcache is not None: self.__memcache.clear() def load(self, environment, name, translator): """ Load and translate a template. First we check if there is a cached version of this template in the memory cache. If this is not the cache check for a compiled template in the disk cache folder. And if none of this is the case we translate the temlate, cache and return it. """ self.__lock.acquire() try: # caching is only possible for the python translator. skip # all other translators if translator is not PythonTranslator: return super(CachedLoaderMixin, self).load( environment, name, translator) tmpl = None save_to_disk = False push_to_memory = False # auto reload enabled? check for the last change of # the template if self.__auto_reload: last_change = self.check_source_changed(environment, name) else: last_change = None # check if we have something in the memory cache and the # memory cache is enabled. if self.__memcache is not None: if name in self.__memcache: tmpl = self.__memcache[name] # if auto reload is enabled check if the template changed if last_change and last_change > self.__times[name]: tmpl = None push_to_memory = True else: push_to_memory = True # mem cache disabled or not cached by now # try to load if from the disk cache if tmpl is None and self.__cache_folder is not None: cache_fn = get_cachename(self.__cache_folder, name, self.__salt) if last_change is not None: try: cache_time = path.getmtime(cache_fn) except OSError: cache_time = 0 if last_change is None or (cache_time and last_change <= cache_time): try: f = file(cache_fn, 'rb') except IOError: tmpl = None save_to_disk = True else: try: tmpl = Template.load(environment, f) finally: f.close() else: save_to_disk = True # if we still have no template we load, parse and translate it. if tmpl is None: tmpl = super(CachedLoaderMixin, self).load( environment, name, translator) # save the compiled template on the disk if enabled if save_to_disk: f = file(cache_fn, 'wb') try: tmpl.dump(f) finally: f.close() # if memcaching is enabled and the template not loaded # we add that there. if push_to_memory: self.__times[name] = time.time() self.__memcache[name] = tmpl return tmpl finally: self.__lock.release() class MemcachedLoaderMixin(object): """ Uses a memcached server to cache the templates. Requires the memcache library from `tummy`_ or the cmemcache library from `Gijsbert de Haan`_. With Jinja 1.2 onwards you can also provide a `client` keyword argument that takes an already instanciated memcache client or memcache client like object. .. _tummy: http://www.tummy.com/Community/software/python-memcached/ .. _Gisjsbert de Haan: http://gijsbert.org/cmemcache/ """ def __init__(self, use_memcache, memcache_time=60 * 60 * 24 * 7, memcache_host=None, item_prefix='template/', client=None): if memcache_host is None: memcache_host = ['127.0.0.1:11211'] if use_memcache: if client is None: try: try: from cmemcache import Client except ImportError: from memcache import Client except ImportError: raise RuntimeError('the %r loader requires an installed ' 'memcache module' % self.__class__.__name__) client = Client(list(memcache_host)) self.__memcache = client self.__memcache_time = memcache_time else: self.__memcache = None self.__item_prefix = item_prefix self.__lock = Lock() def load(self, environment, name, translator): """ Load and translate a template. First we check if there is a cached version of this template in the memory cache. If this is not the cache check for a compiled template in the disk cache folder. And if none of this is the case we translate the template, cache and return it. """ self.__lock.acquire() try: # caching is only possible for the python translator. skip # all other translators if translator is not PythonTranslator: return super(MemcachedLoaderMixin, self).load( environment, name, translator) tmpl = None push_to_memory = False # check if we have something in the memory cache and the # memory cache is enabled. if self.__memcache is not None: bytecode = self.__memcache.get(self.__item_prefix + name) if bytecode: tmpl = Template.load(environment, bytecode) else: push_to_memory = True # if we still have no template we load, parse and translate it. if tmpl is None: tmpl = super(MemcachedLoaderMixin, self).load( environment, name, translator) # if memcaching is enabled and the template not loaded # we add that there. if push_to_memory: self.__memcache.set(self.__item_prefix + name, tmpl.dump(), self.__memcache_time) return tmpl finally: self.__lock.release() class BaseFileSystemLoader(BaseLoader): """ Baseclass for the file system loader that does not do any caching. It exists to avoid redundant code, just don't use it without subclassing. How subclassing can work: .. sourcecode:: python from jinja.loaders import BaseFileSystemLoader class MyFileSystemLoader(BaseFileSystemLoader): def __init__(self): BaseFileSystemLoader.__init__(self, '/path/to/templates') The base file system loader only takes one parameter beside self which is the path to the templates. """ def __init__(self, searchpath): self.searchpath = path.abspath(searchpath) def get_source(self, environment, name, parent): filename = get_template_filename(self.searchpath, name) if path.exists(filename): f = codecs.open(filename, 'r', environment.template_charset) try: return f.read() finally: f.close() else: raise TemplateNotFound(name) class FileSystemLoader(CachedLoaderMixin, BaseFileSystemLoader): """ Loads templates from the filesystem: .. sourcecode:: python from jinja import Environment, FileSystemLoader e = Environment(loader=FileSystemLoader('templates/')) You can pass the following keyword arguments to the loader on initialization: =================== ================================================= ``searchpath`` String with the path to the templates on the filesystem. ``use_memcache`` Set this to ``True`` to enable memory caching. This is usually a good idea in production mode, but disable it during development since it won't reload template changes automatically. This only works in persistent environments like FastCGI. ``memcache_size`` Number of template instance you want to cache. Defaults to ``40``. ``cache_folder`` Set this to an existing directory to enable caching of templates on the file system. Note that this only affects templates transformed into python code. Default is ``None`` which means that caching is disabled. ``auto_reload`` Set this to `False` for a slightly better performance. In that case Jinja won't check for template changes on the filesystem. ``cache_salt`` Optional unique number to not confuse the caching system when caching more than one template loader in the same folder. Defaults to the searchpath. *New in Jinja 1.1* =================== ================================================= """ def __init__(self, searchpath, use_memcache=False, memcache_size=40, cache_folder=None, auto_reload=True, cache_salt=None): BaseFileSystemLoader.__init__(self, searchpath) if cache_salt is None: cache_salt = self.searchpath CachedLoaderMixin.__init__(self, use_memcache, memcache_size, cache_folder, auto_reload, cache_salt) def check_source_changed(self, environment, name): filename = get_template_filename(self.searchpath, name) if path.exists(filename): return path.getmtime(filename) return -1 class MemcachedFileSystemLoader(MemcachedLoaderMixin, BaseFileSystemLoader): """ Loads templates from the filesystem and caches them on a memcached server. .. sourcecode:: python from jinja import Environment, MemcachedFileSystemLoader e = Environment(loader=MemcachedFileSystemLoader('templates/', memcache_host=['192.168.2.250:11211'] )) You can pass the following keyword arguments to the loader on initialization: =================== ================================================= ``searchpath`` String with the path to the templates on the filesystem. ``use_memcache`` Set this to ``True`` to enable memcached caching. In that case it behaves like a normal `FileSystemLoader` with disabled caching. ``memcache_time`` The expire time of a template in the cache. ``memcache_host`` a list of memcached servers. ``item_prefix`` The prefix for the items on the server. Defaults to ``'template/'``. =================== ================================================= """ def __init__(self, searchpath, use_memcache=True, memcache_time=60 * 60 * 24 * 7, memcache_host=None, item_prefix='template/'): BaseFileSystemLoader.__init__(self, searchpath) MemcachedLoaderMixin.__init__(self, use_memcache, memcache_time, memcache_host, item_prefix) class BasePackageLoader(BaseLoader): """ Baseclass for the package loader that does not do any caching. It accepts two parameters: The name of the package and the path relative to the package: .. sourcecode:: python from jinja.loaders import BasePackageLoader class MyPackageLoader(BasePackageLoader): def __init__(self): BasePackageLoader.__init__(self, 'my_package', 'shared/templates') The relative path must use slashes as path delimiters, even on Mac OS and Microsoft Windows. It uses the `pkg_resources` libraries distributed with setuptools for retrieving the data from the packages. This works for eggs too so you don't have to mark your egg as non zip safe. If pkg_resources is not available it just falls back to path joining relative to the package. """ def __init__(self, package_name, package_path, force_native=False): try: import pkg_resources except ImportError: raise RuntimeError('setuptools not installed') self.package_name = package_name self.package_path = package_path self.force_native = force_native def _get_load_func(self): if hasattr(self, '_load_func'): return self._load_func try: from pkg_resources import resource_exists, resource_string if self.force_native: raise ImportError() except ImportError: basepath = path.dirname(__import__(self.package_name, None, None, ['__file__']).__file__) def load_func(name): filename = path.join(basepath, *( self.package_path.split('/') + [p for p in name.split('/') if p != '..']) ) if path.exists(filename): f = file(filename) try: return f.read() finally: f.close() else: def load_func(name): path = '/'.join([self.package_path] + [p for p in name.split('/') if p != '..']) if resource_exists(self.package_name, path): return resource_string(self.package_name, path) self._load_func = load_func return load_func def get_source(self, environment, name, parent): load_func = self._get_load_func() contents = load_func(name) if contents is None: raise TemplateNotFound(name) return contents.decode(environment.template_charset) class PackageLoader(CachedLoaderMixin, BasePackageLoader): """ Loads templates from python packages using setuptools. .. sourcecode:: python from jinja import Environment, PackageLoader e = Environment(loader=PackageLoader('yourapp', 'template/path')) You can pass the following keyword arguments to the loader on initialization: =================== ================================================= ``package_name`` Name of the package containing the templates. ``package_path`` Path of the templates inside the package. ``use_memcache`` Set this to ``True`` to enable memory caching. This is usually a good idea in production mode, but disable it during development since it won't reload template changes automatically. This only works in persistent environments like FastCGI. ``memcache_size`` Number of template instance you want to cache. Defaults to ``40``. ``cache_folder`` Set this to an existing directory to enable caching of templates on the file system. Note that this only affects templates transformed into python code. Default is ``None`` which means that caching is disabled. ``auto_reload`` Set this to `False` for a slightly better performance. In that case Jinja won't check for template changes on the filesystem. If the templates are inside of an egg file this won't have an effect. ``cache_salt`` Optional unique number to not confuse the caching system when caching more than one template loader in the same folder. Defaults to ``package_name + '/' + package_path``. *New in Jinja 1.1* =================== ================================================= Important note: If you're using an application that is inside of an egg never set `auto_reload` to `True`. The egg resource manager will automatically export files to the file system and touch them so that you not only end up with additional temporary files but also an automatic reload each time you load a template. """ def __init__(self, package_name, package_path, use_memcache=False, memcache_size=40, cache_folder=None, auto_reload=True, cache_salt=None): BasePackageLoader.__init__(self, package_name, package_path) if cache_salt is None: cache_salt = package_name + '/' + package_path CachedLoaderMixin.__init__(self, use_memcache, memcache_size, cache_folder, auto_reload, cache_salt) def check_source_changed(self, environment, name): from pkg_resources import resource_exists, resource_filename fn = resource_filename(self.package_name, '/'.join([self.package_path] + [p for p in name.split('/') if p and p[0] != '.'])) if resource_exists(self.package_name, fn): return path.getmtime(fn) return -1 class BaseFunctionLoader(BaseLoader): """ Baseclass for the function loader that doesn't do any caching. It just accepts one parameter which is the function which is called with the name of the requested template. If the return value is `None` the loader will raise a `TemplateNotFound` error. .. sourcecode:: python from jinja.loaders import BaseFunctionLoader templates = {...} class MyFunctionLoader(BaseFunctionLoader): def __init__(self): BaseFunctionLoader(templates.get) """ def __init__(self, loader_func): self.loader_func = loader_func def get_source(self, environment, name, parent): rv = self.loader_func(name) if rv is None: raise TemplateNotFound(name) if isinstance(rv, str): return rv.decode(environment.template_charset) return rv class FunctionLoader(CachedLoaderMixin, BaseFunctionLoader): """ Loads templates by calling a function which has to return a string or `None` if an error occoured. .. sourcecode:: python from jinja import Environment, FunctionLoader def my_load_func(template_name): if template_name == 'foo': return '...' e = Environment(loader=FunctionLoader(my_load_func)) Because the interface is limited there is no way to cache such templates. Usually you should try to use a loader with a more solid backend. You can pass the following keyword arguments to the loader on initialization: =================== ================================================= ``loader_func`` Function that takes the name of the template to load. If it returns a string or unicode object it's used to load a template. If the return value is None it's considered missing. ``getmtime_func`` Function used to check if templates requires reloading. Has to return the UNIX timestamp of the last template change or ``-1`` if this template does not exist or requires updates at any cost. ``use_memcache`` Set this to ``True`` to enable memory caching. This is usually a good idea in production mode, but disable it during development since it won't reload template changes automatically. This only works in persistent environments like FastCGI. ``memcache_size`` Number of template instance you want to cache. Defaults to ``40``. ``cache_folder`` Set this to an existing directory to enable caching of templates on the file system. Note that this only affects templates transformed into python code. Default is ``None`` which means that caching is disabled. ``auto_reload`` Set this to `False` for a slightly better performance. In that case of `getmtime_func` not being provided this won't have an effect. ``cache_salt`` Optional unique number to not confuse the caching system when caching more than one template loader in the same folder. =================== ================================================= """ def __init__(self, loader_func, getmtime_func=None, use_memcache=False, memcache_size=40, cache_folder=None, auto_reload=True, cache_salt=None): BaseFunctionLoader.__init__(self, loader_func) # when changing the signature also check the jinja.plugin function # loader instantiation. self.getmtime_func = getmtime_func if auto_reload and getmtime_func is None: auto_reload = False CachedLoaderMixin.__init__(self, use_memcache, memcache_size, cache_folder, auto_reload, cache_salt) def check_source_changed(self, environment, name): return self.getmtime_func(name) class DictLoader(BaseLoader): """ Load templates from a given dict: .. sourcecode:: python from jinja import Environment, DictLoader e = Environment(loader=DictLoader(dict( layout='...', index='{% extends 'layout' %}...' ))) This loader does not have any caching capabilities. """ def __init__(self, templates): self.templates = templates def get_source(self, environment, name, parent): if name in self.templates: return self.templates[name] raise TemplateNotFound(name) class ChoiceLoader(object): """ A loader that tries multiple loaders in the order they are given to the `ChoiceLoader`: .. sourcecode:: python from jinja import ChoiceLoader, FileSystemLoader loader1 = FileSystemLoader("templates1") loader2 = FileSystemLoader("templates2") loader = ChoiceLoader([loader1, loader2]) """ def __init__(self, loaders): self.loaders = list(loaders) def get_source(self, environment, name, parent): for loader in self.loaders: try: return loader.get_source(environment, name, parent) except TemplateNotFound, e: if e.name != name: raise continue raise TemplateNotFound(name) def parse(self, environment, name, parent): for loader in self.loaders: try: return loader.parse(environment, name, parent) except TemplateNotFound, e: if e.name != name: raise continue raise TemplateNotFound(name) def load(self, environment, name, translator): for loader in self.loaders: try: return loader.load(environment, name, translator) except TemplateNotFound, e: if e.name != name: raise continue raise TemplateNotFound(name) ./weblog+jinja-0.8/jinja/translators/0000755000175000017500000000000011006524413016563 5ustar henryhenry./weblog+jinja-0.8/jinja/translators/__init__.py0000644000175000017500000000140511006524413020674 0ustar henryhenry# -*- coding: utf-8 -*- """ jinja.translators ~~~~~~~~~~~~~~~~~ The submodules of this module provide translators for the jinja ast. :copyright: 2007 by Armin Ronacher. :license: BSD, see LICENSE for more details. """ class Translator(object): """ Base class of all translators. """ def process(environment, tree, source=None): """ Process the given ast with the rules defined in environment and return a translated version of it. The translated object can be anything. The python translator for example outputs Template instances, a javascript translator would probably output strings. This is a static function. """ pass process = staticmethod(process) ./weblog+jinja-0.8/jinja/translators/python.py0000644000175000017500000011767711006524413020501 0ustar henryhenry# -*- coding: utf-8 -*- """ jinja.translators.python ~~~~~~~~~~~~~~~~~~~~~~~~ This module translates a jinja ast into python code. This translator tries hard to keep Jinja sandboxed. All security relevant calls are wrapped by methods defined in the environment. This affects: - method calls - attribute access - name resolution It also adds debug symbols used by the traceback toolkit implemented in `jinja.utils`. Implementation Details ====================== It might sound strange but the translator tries to keep the generated code readable as much as possible. This simplifies debugging the Jinja core a lot. The additional processing overhead is just relevant for the translation process, the additional comments and whitespace won't appear in the saved bytecode. :copyright: 2007 by Armin Ronacher. :license: BSD, see LICENSE for more details. """ import re import sys from jinja import nodes from jinja.nodes import get_nodes from jinja.parser import Parser from jinja.exceptions import TemplateSyntaxError from jinja.translators import Translator from jinja.datastructure import TemplateStream from jinja.utils import set, capture_generator #: regular expression for the debug symbols _debug_re = re.compile(r'^\s*\# DEBUG\(filename=(?P.*?), ' r'lineno=(?P\d+)\)$') # For Python versions without generator exit exceptions try: GeneratorExit = GeneratorExit except NameError: class GeneratorExit(Exception): pass # For Pythons without conditional expressions try: exec '0 if 0 else 0' have_conditional_expr = True except SyntaxError: have_conditional_expr = False class Template(object): """ Represents a finished template. """ def __init__(self, environment, code): self.environment = environment self.code = code self.generate_func = None def dump(self, stream=None): """Dump the template into python bytecode.""" if stream is not None: from marshal import dump dump(self.code, stream) else: from marshal import dumps return dumps(self.code) def load(environment, data): """Load the template from python bytecode.""" if isinstance(data, basestring): from marshal import loads code = loads(data) else: from marshal import load code = load(data) return Template(environment, code) load = staticmethod(load) def render(self, *args, **kwargs): """Render a template.""" __traceback_hide__ = True ctx = self._prepare(*args, **kwargs) try: return capture_generator(self.generate_func(ctx)) except: self._debug(ctx, *sys.exc_info()) def stream(self, *args, **kwargs): """Render a template as stream.""" def proxy(ctx): try: for item in self.generate_func(ctx): yield item except GeneratorExit: return except: self._debug(ctx, *sys.exc_info()) return TemplateStream(proxy(self._prepare(*args, **kwargs))) def _prepare(self, *args, **kwargs): """Prepare the template execution.""" # if there is no generation function we execute the code # in a new namespace and save the generation function and # debug information. env = self.environment if self.generate_func is None: ns = {'environment': env} exec self.code in ns self.generate_func = ns['generate'] return env.context_class(env, *args, **kwargs) def _debug(self, ctx, exc_type, exc_value, traceback): """Debugging Helper""" # just modify traceback if we have that feature enabled from traceback import print_exception print_exception(exc_type, exc_value, traceback) if self.environment.friendly_traceback: # hook the debugger in from jinja.debugger import translate_exception exc_type, exc_value, traceback = translate_exception( self, ctx, exc_type, exc_value, traceback) print_exception(exc_type, exc_value, traceback) raise exc_type, exc_value, traceback class PythonTranslator(Translator): """ Pass this translator a ast tree to get valid python code. """ def __init__(self, environment, node, source): self.environment = environment self.loader = environment.loader.get_controlled_loader() self.node = node self.source = source self.closed = False #: current level of indention self.indention = 0 #: each {% cycle %} tag has a unique ID which increments #: automatically for each tag. self.last_cycle_id = 0 #: set of used shortcuts jinja has to make local automatically self.used_shortcuts = set(['undefined_singleton']) #: set of used datastructures jinja has to import self.used_data_structures = set() #: set of used utils jinja has to import self.used_utils = set() #: flags for runtime error self.require_runtime_error = False #: do wee need a "set" object? self.need_set_import = False #: flag for regular expressions self.compiled_regular_expressions = {} #: bind the nodes to the callback functions. There are #: some missing! A few are specified in the `unhandled` #: mapping in order to disallow their usage, some of them #: will not appear in the jinja parser output because #: they are filtered out. self.handlers = { # block nodes nodes.Template: self.handle_template, nodes.Text: self.handle_template_text, nodes.NodeList: self.handle_node_list, nodes.ForLoop: self.handle_for_loop, nodes.IfCondition: self.handle_if_condition, nodes.Cycle: self.handle_cycle, nodes.Print: self.handle_print, nodes.Macro: self.handle_macro, nodes.Call: self.handle_call, nodes.Set: self.handle_set, nodes.Filter: self.handle_filter, nodes.Block: self.handle_block, nodes.Include: self.handle_include, nodes.Trans: self.handle_trans, # expression nodes nodes.NameExpression: self.handle_name, nodes.CompareExpression: self.handle_compare, nodes.TestExpression: self.handle_test, nodes.ConstantExpression: self.handle_const, nodes.RegexExpression: self.handle_regex, nodes.SubscriptExpression: self.handle_subscript, nodes.FilterExpression: self.handle_filter_expr, nodes.CallExpression: self.handle_call_expr, nodes.AddExpression: self.handle_add, nodes.SubExpression: self.handle_sub, nodes.ConcatExpression: self.handle_concat, nodes.DivExpression: self.handle_div, nodes.FloorDivExpression: self.handle_floor_div, nodes.MulExpression: self.handle_mul, nodes.ModExpression: self.handle_mod, nodes.PosExpression: self.handle_pos, nodes.NegExpression: self.handle_neg, nodes.PowExpression: self.handle_pow, nodes.DictExpression: self.handle_dict, nodes.SetExpression: self.handle_set_expr, nodes.ListExpression: self.handle_list, nodes.TupleExpression: self.handle_tuple, nodes.UndefinedExpression: self.handle_undefined, nodes.AndExpression: self.handle_and, nodes.OrExpression: self.handle_or, nodes.NotExpression: self.handle_not, nodes.SliceExpression: self.handle_slice, nodes.ConditionalExpression: self.handle_conditional_expr } # -- public methods def process(environment, node, source=None): """ The only public method. Creates a translator instance, translates the code and returns it in form of an `Template` instance. """ translator = PythonTranslator(environment, node, source) filename = node.filename or '