./weblog+jinja-1.1004075500017500000000000000000001106302214500130335ustar00henrywheel./weblog+jinja-1.1/bin004075500017500000000000000000001106302214500136035ustar00henrywheel./weblog+jinja-1.1/bin/weblog010075500017500000000000000052471106302214500150740ustar00henrywheel#!/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", default='.', help='The source directory where the blog posts are ' 'located. [default: \'%default\']', metavar="DIR") parser.add_option("-o", "--output_dir", dest="output_dir", default='output', help='The directory where all the generated files are ' 'written. If it does not exist it is created.' '[default: \'%default\']', metavar="DIR") parser.add_option('-c', '--conf', dest='configuration_file', help='The configuration file to use. If the file is not ' 'present in the current directory, the source directory ' 'is searched.' ' [default: \'%default\']', metavar='FILE', default='weblog.ini') 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.ERROR, format='%(message)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-1.1/doc004075500017500000000000000000001106302214500136005ustar00henrywheel./weblog+jinja-1.1/doc/style.rst010064400017500000000000000105161106302214500155510ustar00henrywheelCustomizing Weblog's appearance =============================== By default Weblog does not have a style-sheet thus looks raw. It is possible to make it more appealing by adding a style-sheet. This document details the possibilities of customizing Weblog's visual appearance. Note to the user ~~~~~~~~~~~~~~~~ On Internet content is more important than appearance. Even with the best graphics and the fanciest website possible, if you don't have content your site will be worthless and nobody will look at it. An interesting post will drive people to your Blog. Your choice of color or a custom logo will not. Don't overspent time on design! Getting started --------------- External CSS ~~~~~~~~~~~~ The recommended way of customizing Weblog visual appearance. Is via an external CSS style-sheet. Add the following line to ``weblog.ini``:: html_head: extra_files: style.css Create a file named ``style.css`` in the source directory and generate a temporary blog to tweak CSS file:: $ cd source/directory $ touch style.css $ weblog -s . -o temporary_blog Open ``temporary_blog/index.html`` in your browser and change the visual appearance by editing ``temporary_blog/style.css``. Inline CSS ~~~~~~~~~~ This method is also valid, but it makes HTML files bigger. The "External CSS" method is prefered over this one. To have the CSS stylesheet embedded into the pages, create a file named ``style.css`` containing:: Pages structure --------------- Most of Weblog HTML tags are associated with an `id` or a `class`. The following tables show the different tags and class associated with it. Base structure ~~~~~~~~~~~~~~ The structure common to all pages. `header` and `footer` are user-defined. +--------------+ | Body | | | | +----------+ | | | header | | | +----------+ | | | div#main | | | +----------+ | | | footer | | | +----------+ | +--------------+ Listing structure ~~~~~~~~~~~~~~~~~ The structure of a listing page contained in the `main div`. +----------------------+ | h1#title | +----------------------+ | p#description | +----------------------+ | List of posts | | | | +------------------+ | | | h2.post-title | | | +------------------+ | | | p.post-header | | | | | | | | +-------------+ | | | | | span.date | | | | | +-------------+ | | | | | span.author | | | | | +-------------+ | | | +------------------+ | | | div.post-content | | | +------------------+ | | | +----------------------+ | hr.footer-ruler | +----------------------+ | div.paginator | | | | +------------------+ | | | a or span + | | | .paginator-link + | | +------------------+ | +----------------------+ Post structure ~~~~~~~~~~~~~~ +------------------+ | h1.post-title | +------------------+ | p.post-header | | | | +-------------+ | | | span.date | | | +-------------+ | | | span.author | | | +-------------+ | +------------------+ | div.post-content | +------------------+ Custom header & footer ---------------------- The custom header and footer make it possible to add a menu bar or logo. To add a custom logo at the top of the blog, create a directory ``html`` in the source directory, and create a file named ``header.html`` in this new directory:: Then edit ``weblog.ini`` and add the following lines:: html_header = html/header.html extra_files = my_fancy_logo.png This insert the content of the file ``html/header.html`` before the blog's title, and copy the file ``my_fancy_logo.png``. CSS resources ------------- Learning and developing with CSS is hard. The CSS syntax tend to be confusing for beginners. The numerous browser incompatibilities makes the designer's work even more complicated. Here is a list of useful resources regarding this subject: * SitePoint_ CSS Reference is helpful if you are a beginner with CSS. It lists all CSS properties and document how well they are supported by the different browsers. * HtmlHelp_ contains a complete HTML 4 reference. .. _HtmlHelp: http://htmlhelp.com/reference/html40/ .. _SitePoint: http://reference.sitepoint.com/css .. vim:se tw=80 sw=2 ts=2 et encoding=utf-8: ./weblog+jinja-1.1/doc/weblog.rst010064400017500000000000000263151106302214500156740ustar00henrywheelWeblog 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+ or Jinja 2.0+. Learn how to install Jinja at http://jinja.pocoo.org/2/documentation/intro#installation or 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. Attaching a file to a post -------------------------- To attach files like images to a blog post, use the field ``files``:: title: Attach a file files: picture.png directory/file a picture a file It will copy ``picture.png`` and ``directory/file``. If ``directory`` does not exist, it will be created. 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. feed_limit The maximum number of post to be included in the Feed file. The most recent posts are the ones included. Default is 10. Note: rss_limit has been renamed to feed_limit. 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! extra_files Additional files to be copied. Typically used to copy CSS style sheets and/or pictures for the blog graphic design. Files are copied into `output_dir`. The path is not preserved: The file `style/weblog.css` gets copied into `output_dir/weblog.css` not into `output_dir/style/weblog.css`. This behavior is likely to change in the future. Tips on Uploading ----------------- rsync_ is a useful tool to upload files generated by Weblog. To make sure rsync does not change the last modification time of the files that did not change, use the following:: rsync --compress --checksum --recursive path/to/blog remote_host:public/dir/ Accurate last modification time makes efficient caching possible. .. _rsync: http://samba.anu.edu.au/rsync/ Need more help? --------------- Don't hesitate to ask questions about Weblog: http://groups.google.com/group/weblog-users or weblog-users@googlegroups.com .. vim:se tw=80 sw=2 ts=2 et encoding=utf-8: ./weblog+jinja-1.1/COPYING010064400017500000000000000013701106302214500141430ustar00henrywheelCopyright (c) 2007, 2008, 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. ./weblog+jinja-1.1/README010064400017500000000000000006201106302214500137650ustar00henrywheelWeblog is a web log or blog publisher. It takes structured text files as input and outputs static HTML / RSS files. Weblog aims to be simple and robust. to learn how to install and use weblog please read the text file: doc/weblog.rst If you have docutils installed you can turn it into a HTML file: $ rst2html.py weblog.rst > weblog.html Jinja is needed to use Weblog (http://jinja.pocoo.org) ./weblog+jinja-1.1/TODO010064400017500000000000000001121106302214500135710ustar00henrywheel1.2 --- Document Markdown syntax Add full tutorial More and more docs :) ./weblog+jinja-1.1/examples004075500017500000000000000000001106302214500146515ustar00henrywheel./weblog+jinja-1.1/examples/second_post.html010064400017500000000000000002071106302214500201320ustar00henrywheeltitle: Second post date: 2007-08-26 Second test post!

The author lastname is Prêcheur

./weblog+jinja-1.1/examples/enconding.html010064400017500000000000000005511106302214500175600ustar00henrywheeltitle: Weblog encode le français! author: Henry Prêcheur encoding: latin-1 date: 2007-10-01

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

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

./weblog+jinja-1.1/examples/first_post.html010064400017500000000000000000741106302214500200100ustar00henrywheeltitle: First post author: Me date: 2007-08-25 Hello world! ./weblog+jinja-1.1/examples/w3_steely_style.css010064400017500000000000000003601106302214500205740ustar00henrywheelbody { text-align: center; /* for IE 4+ */ } div#main { margin: 0 auto; text-align: left; /* counter the body center */ width: 42em; max-width: 90%; } p.weblog-ad { margin: 0 auto; text-align: left !important; } ./weblog+jinja-1.1/examples/utf-8.html010064400017500000000000000001611106302214500165540ustar00henrywheeltitle: Some UTF-8, ç ä é ö ó date: 2008-1-1 encoding: UTF-8 Test post with UTF-8 inside ... ç ä é ö ó ./weblog+jinja-1.1/examples/weblog.ini010064400017500000000000000002701106302214500167040ustar00henrywheel[weblog] title=Sample blog url=http://blog.sample.org description=Brief description of this sample blog. Do multiline, this way! encoding=UTF-8 author=Me ./weblog+jinja-1.1/examples/weblog_w3_steely_css.ini010064400017500000000000000005161106302214500215550ustar00henrywheel[weblog] title=Sample blog url=http://blog.sample.org description=Brief description of this sample blog. author=Me html_head= extra_files=w3_steely_style.css ./weblog+jinja-1.1/test004075500017500000000000000000001106300367500140215ustar00henrywheel./weblog+jinja-1.1/test/empty004075500017500000000000000000001106302214500151505ustar00henrywheel./weblog+jinja-1.1/test/empty/weblog.ini010064400017500000000000000001441106302214500172030ustar00henrywheel[weblog] title=Test blog url=http://blog.test.org description=Test blog author=test ./weblog+jinja-1.1/test/encoding004075500017500000000000000000001106302214500156005ustar00henrywheel./weblog+jinja-1.1/test/encoding/latin-1.html010064400017500000000000000001041106302214500200020ustar00henrywheeltitle: latin post ÖÉÈÄ ... date: 2008-02-04 encoding: latin-1 Öéèä ./weblog+jinja-1.1/test/encoding/utf-8.html010064400017500000000000000000721106302214500175040ustar00henrywheeltitle: UTF-8 post ÖÉÈÄ ... date: 2008-02-03 Öéèä ./weblog+jinja-1.1/test/encoding/weblog.ini010064400017500000000000000001631106302214500176340ustar00henrywheel[weblog] title=Test blog url=http://blog.test.org description=Test blog author=test encoding=utf-8 ./weblog+jinja-1.1/test/full_url004075500017500000000000000000001106302214500156365ustar00henrywheel./weblog+jinja-1.1/test/full_url/utf-8.html010064400017500000000000000002501106302214500175400ustar00henrywheeltitle: UTF-8 post ÖÉÈÄ ... date: 2008-02-03 Öéèä
äyÔÀ Weblog ./weblog+jinja-1.1/test/full_url/weblog.ini010064400017500000000000000001631106302214500176720ustar00henrywheel[weblog] title=Test blog url=http://blog.test.org description=Test blog author=test encoding=utf-8 ./weblog+jinja-1.1/test/simple004075500017500000000000000000001106302214500153035ustar00henrywheel./weblog+jinja-1.1/test/simple/post1.html010064400017500000000000000000451106302214500173120ustar00henrywheeltitle: post1 date: 2007-01-01 post1 ./weblog+jinja-1.1/test/simple/post2.html010064400017500000000000000000441106302214500173120ustar00henrywheeltitle: post2 date: 2007-6-15 post2 ./weblog+jinja-1.1/test/simple/post3.html010064400017500000000000000000451106302214500173140ustar00henrywheeltitle: post3 date: 2007-12-31 post3 ./weblog+jinja-1.1/test/simple/weblog.ini010064400017500000000000000001441106302214500173360ustar00henrywheel[weblog] title=Test blog url=http://blog.test.org description=Test blog author=test ./weblog+jinja-1.1/setup.cfg010064400017500000000000000000471106302214500147310ustar00henrywheel[nosetests] verbosity=3 with-doctest=1 ./weblog+jinja-1.1/setup.py010064400017500000000000000026561106302214500146320ustar00henrywheeltry: from setuptools import setup except: from distutils.core import setup import os import weblog 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=weblog.__version__, packages=['weblog'], package_data={'weblog': ['templates/*.tmpl']}, scripts=['bin/weblog'], requires=['Jinja2 (>=2.0)'], install_requires=['Jinja2'], data_files=[('doc', ['doc/weblog.rst', 'doc/style.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 = "ISCL", keywords = "weblog blog journal diary atom", url = "http://henry.precheur.org/weblog/", classifiers=[ 'Development Status :: 5 - Production/Stable', 'Topic :: Internet :: WWW/HTTP :: Dynamic Content :: News/Diary', 'Intended Audience :: End Users/Desktop', 'Programming Language :: Python', ]) ./weblog+jinja-1.1/test.py010064400017500000000000000320201106302214500144350ustar00henrywheelimport os import shutil import tempfile import unittest import email import datetime from textwrap import dedent # make sample configuration files more readable. from StringIO import StringIO from optparse import Values from weblog import Post, PostError, jinja_environment from weblog.publish import load_post_list, generate_index_listing from weblog.date import command_date from weblog.publish import command_publish from weblog.load import load_configuration, ConfigurationError def _file(string): return StringIO(dedent(string)) 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, u'UTF-8 post \xd6\xc9\xc8\xc4 ...') self.assertEqual(sorted_list[0].content, u'\xd6\xe9\xe8\xe4\n') self.assertEqual(sorted_list[1].title, u'latin post \xd6\xc9\xc8\xc4 ...') self.assertEqual(sorted_list[1].content, u'\xd6\xe9\xe8\xe4\n') class TestPost(unittest.TestCase): def test_simple(self): sample_post = '''\ title: test date: 2008-1-1 author: test author encoding: ascii test.''' post = Post(_file(sample_post)) self.assertEqual(post.title, u'test') self.assertEqual(post.date, datetime.date(2008, 1, 1)) self.assertEqual(post.author, u'test author') self.assertEqual(post.encoding, 'ascii') self.assertEqual(post.content, u'test.') def test_encoding(self): sample_post = u'''\ title: Test UTF-8 \xdcTF-8 ? author: Henry Pr\xeacheur encoding: utf8 blah \xdcTF-8.'''.encode('utf8') # convert to str post = Post(_file(sample_post)) self.assertEqual(post.title, u'Test UTF-8 \xdcTF-8 ?') self.assertEqual(post.author, u'Henry Pr\xeacheur ') self.assertEqual(post.encoding, u'utf8') self.assertEqual(post.content, u'blah \xdcTF-8.') def test_no_payload(self): sample_post = 'title: no payload\ndate: 2008-1-1' try: Post(_file(sample_post)) except PostError, e: self.assertEqual(e.args, (': does not have content',)) else: self.failUnless(False) # Should not be there def test_bad_encoding(self): sample_post = ('title: bad encoding\ndate: 2008-1-1\n' 'encoding: bad-encoding\n\ntest') try: Post(_file(sample_post)) except PostError, e: self.assertEqual(e.args, (': unknown encoding: ' 'bad-encoding',)) else: self.failUnless(False) # Should not be there self.assertRaises(PostError, Post, _file(sample_post)) def test_bad_date(self): sample_post = 'title: bad encoding\ndate: 20 bad date 08-1-1\n\ntest' try: Post(_file(sample_post)) except PostError, e: self.assertEqual(e.args, (": Unable to parse date " "'20 bad date 08-1-1'\n" "(Use YYYY-MM-DD [[HH:MM]:SS] format)",)) else: self.failUnless(False) # Should not be there def test_markdown(self): sample_post = ('title: markdown\ndate: 2008-9-12\n\n' '*boo*\n\n----\nblah') post = Post(_file(sample_post), markup='markdown') self.assertEqual(post.get_html(), (u'

boo

\n\n' '
\n\n

blah

\n')) self.assertEqual(post.get_xhtml(), (u'

boo

\n\n' '
\n\n

blah

\n')) def test_html(self): sample_post = 'title: markdown\ndate: 2008-9-12\n\n

boo
blah

' post = Post(_file(sample_post), markup='html') self.assertEqual(post.get_html(), u'

boo
blah

') self.assertEqual(post.get_xhtml(), u'

boo
blah

') class TestJinja(unittest.TestCase): env = jinja_environment(os.path.dirname(__file__)) def test_renderstring(self): template = self.env.\ from_string('Hello {{ string_template|renderstring }}!') self.assertEqual(template.render(dict(string_template=('{{ foo }} ' 'world'), foo='crazy')), u'Hello crazy world!') def test_renderstring_empty(self): template = self.env.\ from_string('Hello {{ string_template|renderstring }}!') self.assertEqual(template.render(dict(string_template='', foo='crazy')), u'Hello !') def test_format_date_(self): template = self.env.from_string('{{ d|format_date }}') self.assertEqual(template.render(dict(d=datetime.date(2008, 7, 21))), '2008-07-21') self.assertEqual(template.render(dict(d=datetime.datetime(2008, 7, 21, 21, 42, 12, 123))), '2008-07-21 21:42:12') self.assertRaises(TypeError, template.render, dict(d=12)) 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\ndate: 2008-02-04\n\npost 1' post2 = ('title: post2\ndate: 2008-01-18\nauthor: test@test.com\n\n' 'post 2') post_list = [Post(StringIO(post1)), Post(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_date(self): filename = os.path.join(self.tempdir, 'set_date.html') # First test a message without any date defined def file_without_date(): open(filename, 'w').write('title: Some title\n\nSome content') file_without_date() command_date([filename, '2008-1-1'], None) message = email.message_from_file(open(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(): open(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(open(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(open(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, configuration_file='weblog.ini', 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_url(self): self._test_publish('full_url') def test_publish_simple(self): self._test_publish('simple') class TestConfiguration(unittest.TestCase): def test_empty(self): self.assertRaises(ConfigurationError, load_configuration, _file('')) def test_bad_encoding(self): conf = '''\ [weblog] title=test author=Henry Pr\xc3\xaacheur url=http://example.com/''' # Default encoding is ascii self.assertRaises(ConfigurationError, load_configuration, _file(conf)) def test_encoding(self): conf = u'''\ [weblog] title=test author=Henry Pr\xeacheur encoding=utf8 url=http://example.com/'''.encode('utf8') d = load_configuration(_file(conf)) self.assertEqual(d['author'], u'Henry Pr\xeacheur ') def test_load_simple(self): sample_configuration = '''\ [weblog] title = Test title url = http://example.com description = Example blog''' self.assertEqual(load_configuration(_file(sample_configuration)), dict(title=u'Test title', url=u'http://example.com/', feed_limit=10, description=u'Example blog', post_per_page=10)) def test_load_no_title(self): sample_configuration = '''\ [weblog] url = http://example.com description = Example blog''' self.assertRaises(ConfigurationError, load_configuration, _file(sample_configuration)) def test_load_no_title(self): sample_configuration = '''\ [weblog] title = dummy title description = Example blog''' self.assertRaises(ConfigurationError, load_configuration, _file(sample_configuration)) def test_load_feed_limit(self): sample_configuration = '''\ [weblog] title = Test title url = http://example.com description = Example blog''' c = load_configuration(_file(sample_configuration)) self.assertEqual(c['feed_limit'], 10) sample_configuration = '''\ [weblog] title = Test title url = http://example.com description = Example blog feed_limit = 42''' c = load_configuration(_file(sample_configuration)) self.assertEqual(c['feed_limit'], 42) sample_configuration = '''\ [weblog] title = Test title url = http://example.com description = Example blog feed_limit = not_a_number''' self.assertRaises(ConfigurationError, load_configuration, _file(sample_configuration)) def test_load_post_per_page(self): sample_configuration = '''\ [weblog] title = Test title url = http://example.com description = Example blog''' c = load_configuration(_file(sample_configuration)) self.assertEqual(c['post_per_page'], 10) sample_configuration = '''\ [weblog] title = Test title url = http://example.com description = Example blog post_per_page = 42''' c = load_configuration(_file(sample_configuration)) self.assertEqual(c['post_per_page'], 42) sample_configuration = '''\ [weblog] title = Test title url = http://example.com description = Example blog post_per_page = not_a_number''' self.assertRaises(ConfigurationError, load_configuration, _file(sample_configuration)) if __name__ == '__main__': import nose nose.main() ./weblog+jinja-1.1/weblog004075500017500000000000000000001106302214500143125ustar00henrywheel./weblog+jinja-1.1/weblog/html_full_url.py010064400017500000000000000117121106302214500176120ustar00henrywheelimport re from utf8_html_parser import UTF8HTMLParser # Ignore http:// ftp:// mailto: javascript: ... _scheme_regex = re.compile(r'\w+:') def internal_url(url): ''' Returns True if ``url`` refers to an external resource. >>> internal_url('http://www.google.ca/') False >>> internal_url('mailto:me@example.com') False >>> internal_url('javascript:return false;') False >>> internal_url('/pic.jpg') True >>> internal_url('') True ''' if _scheme_regex.match(url): return False else: return True class FullUrlHtmlParser(UTF8HTMLParser): ''' Parse an HTML document and transform relative URI to absolute URI. Prepending ``base_url`` to them:: >>> p = FullUrlHtmlParser('http://www.example.com') >>> p.feed(u'') >>> p.get_value() u"" Non-external resource are ignored:: >>> p = FullUrlHtmlParser('http://www.example.com') >>> p.feed('') >>> p.get_value() u"" A more complex example:: >>> p.reset() >>> p.feed(r""" ... ... foo ... ... some random text. ... bar ... »~ ... ... ... More ..........""") >>> print p.get_value() #doctest: +NORMALIZE_WHITESPACE foo some random text. bar »~ More .......... ''' def __init__(self, base_url): UTF8HTMLParser.__init__(self) self.base_url = base_url.rstrip('/') @staticmethod def html_attrs(attrs): ''' >>> FullUrlHtmlParser.html_attrs((('src', 'pic.jpg'), ('alt', 'pic'))) u"src='pic.jpg' alt='pic'" >>> FullUrlHtmlParser.html_attrs(list()) u'' ''' return u' '.join(u'%s=\'%s\'' % (k, v) for k, v in attrs) def make_full_url(self, attr, attrs): ''' Change ``attrs[attr]`` from a relative URI to an absolute URI. >>> p = FullUrlHtmlParser('http://www.example.com') >>> tuple(p.make_full_url('src', (('src', 'page'), ('foo', 'bar')))) (('src', 'http://www.example.com/page'), ('foo', 'bar')) >>> tuple(p.make_full_url('src', tuple())) () ''' for key, value in attrs: if key == attr and internal_url(value): yield (key, self.base_url + '/' + value) else: yield (key, value) def rewrite_tag(self, tag, attrs, endtag=u''): ''' >>> p = FullUrlHtmlParser('http://www.example.com') >>> p.rewrite_tag('a', (('href', 'foo'),)) u"" >>> p.rewrite_tag('img', (('src', 'pic.png'), ('width', '100'))) u"" ''' if attrs: if tag == u'a': attrs = self.make_full_url(u'href', attrs) elif tag == u'img': attrs = self.make_full_url(u'src', attrs) elif tag == u'object': attrs = self.make_full_url(u'data', attrs) attrs = self.make_full_url(u'codebase', attrs) elif tag == u'script': attrs = self.make_full_url(u'src', attrs) return u'<%s %s%s>' % (tag, self.html_attrs(attrs), endtag) else: return u'<%s%s>' % (tag, endtag) def handle_starttag(self, tag, attrs): self.output.append(self.rewrite_tag(tag, attrs)) def handle_startendtag(self, tag, attrs): self.output.append(self.rewrite_tag(tag, attrs, endtag=u'/')) def html_full_url(base_url, text): ''' Appends ``base_url`` to relative uri's in the HTML document ``text``. Example with ``base_url=http://example.com``:: '' becomes '' '' becomes '' but ' is not changed since it is an *absolute* URI. >>> html_full_url('http://example.com', '') u"" >>> html_full_url('http://example.com', '') u"" ''' p = FullUrlHtmlParser(base_url) p.feed(text) return p.get_value() if __name__ == '__main__': import doctest doctest.testmod() ./weblog+jinja-1.1/weblog/__init__.py010064400017500000000000000015301106302214500164760ustar00henrywheelfrom load import load_configuration, load_post_list from post import Post, PostError from _jinja_environment import jinja_environment from html_full_url import html_full_url from publish import command_publish from date import command_date import listing __author__ = 'Henry Precheur ' __version__ = '1.1' __license__ = 'ISCL' __all__ = ('Post', 'PostError', 'listing', 'jinja_environment', 'load_configuration', 'load_post_list', 'html_full_url', 'command_publish', 'command_date') def main(): import doctest import utils import post import listing import html_full_url import date doctest.testmod(utils) doctest.testmod(post) doctest.testmod(listing) doctest.testmod(html_full_url) doctest.testmod(date) doctest.testmod() if __name__ == '__main__': main() ./weblog+jinja-1.1/weblog/date.py010064400017500000000000000036271106302214500156650ustar00henrywheelimport sys import logging import datetime import email from utils import format_date 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: raise SystemExit('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, error: raise SystemExit(error) else: date = datetime.datetime.now() logging.info('Setting date to %s in file %s', format_date(date), filename) try: post_file = email.message_from_file(file(filename)) if 'date' in post_file: post_file.replace_header('date', format_date(date)) else: post_file.add_header('date', format_date(date)) file(filename, 'w').write(post_file.as_string()) except IOError, error: raise SystemExit(error) ./weblog+jinja-1.1/weblog/listing.py010064400017500000000000000112241106302214500164110ustar00henrywheelfrom 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', '') == None # base object comparison False ''' 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(other)) 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', list())] 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-1.1/weblog/_jinja_environment.py010064400017500000000000000045401106302214500206210ustar00henrywheelimport os import sys import datetime from utils import format_date import rfc3339 try: from jinja2 import Environment, FileSystemLoader, ChoiceLoader from jinja2 import environmentfilter, contextfilter, Markup @contextfilter def renderstring(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: env = context.environment result = env.from_string(value).render(context.get_all()) if env.autoescape: result = Markup(result) return result else: return '' def format_date_(value): return format_date(value) def rfc3339_(value): return rfc3339.rfc3339(value) def decode(value): if value: return value.encode('ascii', 'xmlcharrefreplace') else: return '' except ImportError: raise SystemExit('Please install Jinja 2 (http://jinja.pocoo.org/2/)' ' 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 from jinja import PackageLoader 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) env.filters['renderstring'] = renderstring env.filters['format_date'] = format_date_ env.filters['rfc3339'] = rfc3339_ env.filters['decode'] = decode return env ./weblog+jinja-1.1/weblog/templates004075500017500000000000000000001106302214500163105ustar00henrywheel./weblog+jinja-1.1/weblog/templates/base.html.tmpl010064400017500000000000000016331106302214500211420ustar00henrywheel {% block title %}{{ title|decode|escape }}{% endblock %} {% block feed %} {% endblock %} {{ html_head|decode|renderstring }} {% block extrahead %} {% endblock %} {% block header %} {{ html_header|decode|renderstring }} {% endblock %}
{% block content %}{% endblock %}
{% block footer %} {% if html_footer %} {{ html_footer|decode|renderstring }} {% else %}

Published using Weblog

{% endif %} {% endblock %} {# vim:set ft=htmljinja: #} ./weblog+jinja-1.1/weblog/templates/feed.atom.tmpl010064400017500000000000000021201106302214500211170ustar00henrywheel {{ url|escape }} {{ title|escape }} {{ description|escape }} {{ feed_updated|rfc3339 }} {% if author %} {{ author.name()|escape }} {{ author.email() }} {{ url|escape }} {% endif %} Weblog {% for post in posts %} {{ post.url(url) }} {{ post.title|escape }} {{ post.date|rfc3339 }} {{ post.author.name()|escape }} {{ post.author.email()|escape }} {{ url|escape }}
{{ post.get_xhtml() }}
{% endfor %}
{# vim: set filetype=jinja ts=4 sw=4 et: #} ./weblog+jinja-1.1/weblog/templates/index.html.tmpl010064400017500000000000000022531106302214500213360ustar00henrywheel{% extends 'base.html.tmpl' %} {% block content %}

{{ title|decode|escape }}

{% if description %}

{{ description|decode|escape }}

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

{{ post.title|decode|escape }}

{{ post.date|format_date }}, by {{ post.author.name()|decode }} <{{ post.author.email()|urlize }}>

{{ post.get_html()|decode }}
{% endfor %} {% if pages|length > 1 %}
{% if pages.index(page) > 0 %} « prev {% endif %} {% for p in pages %} {% if p == page %} {{ p.title }} {% else %} {{ p.title }} {% endif %} {% endfor %} {% if pages.index(page) + 1 < pages|length %} next » {% endif %}
{% endif %} {% endblock %} {# vim:set ft=htmljinja: #} ./weblog+jinja-1.1/weblog/templates/post.html.tmpl010064400017500000000000000007471106302214500212220ustar00henrywheel{% extends 'base.html.tmpl' %} {% block content %}

{{ title|decode|escape }}

{{ date|format_date }}, by {{ author.name()|decode }} <{{ author.email()|urlize }}>

{{ content|decode }}
{% endblock %} {# vim:set ft=htmljinja: #} ./weblog+jinja-1.1/weblog/html_to_xhtml.py010064400017500000000000000033371106302214500176300ustar00henrywheelimport logging from htmlentitydefs import name2codepoint, entitydefs from utf8_html_parser import UTF8HTMLParser class _Parser(UTF8HTMLParser): ''' Parse an HTML document and convert it to valid xhtml. ''' _EMPTY_HTML_TAGS = ('area', 'base', 'basefont', 'br', 'col', 'frame', 'hr', 'img', 'input', 'isindex', 'link', 'meta', 'param') _XML_ENTITIES = ('amp', 'gt', 'lt', 'quot') def handle_starttag(self, tag, attrs): if tag in self._EMPTY_HTML_TAGS: self.handle_startendtag(tag, attrs) elif attrs: self.output.append(u'<%s %s>' % (tag, self.html_attrs(attrs))) else: self.output.append(u'<%s>' % tag) def handle_startendtag(self, tag, attrs): if attrs: self.output.append(u'<%s %s />' % (tag, self.html_attrs(attrs))) else: self.output.append('<%s />' % tag) def handle_entityref(self, name): if name in self._XML_ENTITIES: self.output.append(u'&%s;' % name) elif name in name2codepoint: self.output.append(u'&#%d;' % name2codepoint[name]) else: logging.warning('Unknown XHTML entiry: &%s;' % name); def html_to_xhtml(html): ''' Convert html to xhtml >>> html_to_xhtml('

Hello
World

') u'

Hello
World

' >>> html_to_xhtml('Test & —') u'Test & —' >>> html_to_xhtml("test") u"test" >>> html_to_xhtml("— > & &unknown;") u'— > & ' ''' p = _Parser() p.feed(html) return p.get_value() if __name__ == '__main__': import doctest doctest.testmod() ./weblog+jinja-1.1/weblog/load.py010064400017500000000000000105271106302214500156640ustar00henrywheelimport os import logging from utils import load_if_filename import ConfigParser from post import Post class ConfigurationError(Exception): def __init__(self, filename, error, *args): Exception.__init__(self, "Error in '%s': %s" % (filename, error)) _CONFIGURATION_KEYS = ('title', 'url', 'description', 'source_dir', 'encoding', 'output_dir', 'author', 'post_per_page', 'feed_limit', 'html_head', 'html_header', 'html_footer', 'extra_files') def load_configuration(configuration_file, source_dir=None): ''' Read the file ``config_file`` and sanitise it. Returns a dictionnary containing the parameters from the [weblog] section. All strings are converted to `unicode`. ''' if isinstance(configuration_file, basestring): try: f = open(configuration_file) filename = configuration_file except IOError: # The file was not found try to load it from the source directory if # it is just a filename. if os.path.basename(configuration_file) == configuration_file: filename = os.path.join(source_dir, configuration_file) f = open(filename) else: raise else: f = configuration_file filename = 'unknown filename' try: config_parser = ConfigParser.SafeConfigParser() config_parser.readfp(f) config_dict = dict(config_parser.items('weblog')) except ConfigParser.Error, e: raise ConfigurationError(filename, e) encoding = config_dict.get('encoding') or 'ascii' try: for key, value in config_dict.iteritems(): if key in _CONFIGURATION_KEYS: config_dict[key] = unicode(value, encoding) else: if key == 'rss_limit': logging.warning('rss_limit is obsolete, use feed_limit ' 'instead.') else: logging.warning("unknown key '%s' in %s" % (key, filename)) except UnicodeDecodeError, e: raise ConfigurationError(filename, "for key '%s', %s" % (key, e)) try: # Check that at least 'title' and 'url' are presents config_dict['title'] if not config_dict['url'].endswith('/'): config_dict['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: raise ConfigurationError(filename, "Error in configuration file '%s' " "'%s': %s" % (configuration_file, key, e)) config_set_int('post_per_page', 10) config_set_int('feed_limit', 10) except KeyError, e: raise ConfigurationError(filename, "Unable to find %s in configuration file " "'%s'" % (e, configuration_file)) else: return config_dict 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') or filename.endswith('.txt'): 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 ./weblog+jinja-1.1/weblog/markdown2.py010075500017500000000000002176241106302214500166630ustar00henrywheel#!/usr/bin/env python # Copyright (c) 2007-2008 ActiveState Corp. # License: MIT (http://www.opensource.org/licenses/mit-license.php) r"""A fast and complete Python implementation of Markdown. [from http://daringfireball.net/projects/markdown/] > Markdown is a text-to-HTML filter; it translates an easy-to-read / > easy-to-write structured text format into HTML. Markdown's text > format is most similar to that of plain text email, and supports > features such as headers, *emphasis*, code blocks, blockquotes, and > links. > > Markdown's syntax is designed not as a generic markup language, but > specifically to serve as a front-end to (X)HTML. You can use span-level > HTML tags anywhere in a Markdown document, and you can use block level > HTML tags (like
and as well). Module usage: >>> import markdown2 >>> markdown2.markdown("*boo!*") # or use `html = markdown_path(PATH)` u'

boo!

\n' >>> markdowner = Markdown() >>> markdowner.convert("*boo!*") u'

boo!

\n' >>> markdowner.convert("**boom!**") u'

boom!

\n' This implementation of Markdown implements the full "core" syntax plus a number of extras (e.g., code syntax coloring, footnotes) as described on . """ cmdln_desc = """A fast and complete Python implementation of Markdown, a text-to-HTML conversion tool for web writers. """ # Dev Notes: # - There is already a Python markdown processor # (http://www.freewisdom.org/projects/python-markdown/). # - Python's regex syntax doesn't have '\z', so I'm using '\Z'. I'm # not yet sure if there implications with this. Compare 'pydoc sre' # and 'perldoc perlre'. __version_info__ = (1, 0, 1, 9, '+') # first three nums match Markdown.pl __version__ = '.'.join(map(str, __version_info__)) __author__ = "Trent Mick" import os import sys from pprint import pprint import re import logging try: from hashlib import md5 except ImportError: from md5 import md5 import optparse from random import random import codecs #---- Python version compat if sys.version_info[:2] < (2,4): from sets import Set as set def reversed(sequence): for i in sequence[::-1]: yield i def _unicode_decode(s, encoding, errors='xmlcharrefreplace'): return unicode(s, encoding, errors) else: def _unicode_decode(s, encoding, errors='strict'): return s.decode(encoding, errors) #---- globals DEBUG = False log = logging.getLogger("markdown") DEFAULT_TAB_WIDTH = 4 # Table of hash values for escaped characters: def _escape_hash(s): # Lame attempt to avoid possible collision with someone actually # using the MD5 hexdigest of one of these chars in there text. # Other ideas: random.random(), uuid.uuid() #return md5(s).hexdigest() # Markdown.pl effectively does this. return 'md5:'+md5(s).hexdigest() g_escape_table = dict([(ch, _escape_hash(ch)) for ch in '\\`*_{}[]()>#+-.!']) #---- exceptions class MarkdownError(Exception): pass #---- public api def markdown_path(path, encoding="utf-8", html4tags=False, tab_width=DEFAULT_TAB_WIDTH, safe_mode=None, extras=None, link_patterns=None, use_file_vars=False): text = codecs.open(path, 'r', encoding).read() return Markdown(html4tags=html4tags, tab_width=tab_width, safe_mode=safe_mode, extras=extras, link_patterns=link_patterns, use_file_vars=use_file_vars).convert(text) def markdown(text, html4tags=False, tab_width=DEFAULT_TAB_WIDTH, safe_mode=None, extras=None, link_patterns=None, use_file_vars=False): return Markdown(html4tags=html4tags, tab_width=tab_width, safe_mode=safe_mode, extras=extras, link_patterns=link_patterns, use_file_vars=use_file_vars).convert(text) class Markdown(object): # The dict of "extras" to enable in processing -- a mapping of # extra name to argument for the extra. Most extras do not have an # argument, in which case the value is None. # # This can be set via (a) subclassing and (b) the constructor # "extras" argument. extras = None urls = None titles = None html_blocks = None html_spans = None html_removed_text = "[HTML_REMOVED]" # for compat with markdown.py # Used to track when we're inside an ordered or unordered list # (see _ProcessListItems() for details): list_level = 0 _ws_only_line_re = re.compile(r"^[ \t]+$", re.M) def __init__(self, html4tags=False, tab_width=4, safe_mode=None, extras=None, link_patterns=None, use_file_vars=False): if html4tags: self.empty_element_suffix = ">" else: self.empty_element_suffix = " />" self.tab_width = tab_width # For compatibility with earlier markdown2.py and with # markdown.py's safe_mode being a boolean, # safe_mode == True -> "replace" if safe_mode is True: self.safe_mode = "replace" else: self.safe_mode = safe_mode if self.extras is None: self.extras = {} elif not isinstance(self.extras, dict): self.extras = dict([(e, None) for e in self.extras]) if extras: if not isinstance(extras, dict): extras = dict([(e, None) for e in extras]) self.extras.update(extras) assert isinstance(self.extras, dict) self._instance_extras = self.extras.copy() self.link_patterns = link_patterns self.use_file_vars = use_file_vars self._outdent_re = re.compile(r'^(\t|[ ]{1,%d})' % tab_width, re.M) def reset(self): self.urls = {} self.titles = {} self.html_blocks = {} self.html_spans = {} self.list_level = 0 self.extras = self._instance_extras.copy() if "footnotes" in self.extras: self.footnotes = {} self.footnote_ids = [] def convert(self, text): """Convert the given text.""" # Main function. The order in which other subs are called here is # essential. Link and image substitutions need to happen before # _EscapeSpecialChars(), so that any *'s or _'s in the # and tags get encoded. # Clear the global hashes. If we don't clear these, you get conflicts # from other articles when generating a page which contains more than # one article (e.g. an index page that shows the N most recent # articles): self.reset() if not isinstance(text, unicode): #TODO: perhaps shouldn't presume UTF-8 for string input? text = unicode(text, 'utf-8') if self.use_file_vars: # Look for emacs-style file variable hints. emacs_vars = self._get_emacs_vars(text) if "markdown-extras" in emacs_vars: splitter = re.compile("[ ,]+") for e in splitter.split(emacs_vars["markdown-extras"]): if '=' in e: ename, earg = e.split('=', 1) try: earg = int(earg) except ValueError: pass else: ename, earg = e, None self.extras[ename] = earg # Standardize line endings: text = re.sub("\r\n|\r", "\n", text) # Make sure $text ends with a couple of newlines: text += "\n\n" # Convert all tabs to spaces. text = self._detab(text) # Strip any lines consisting only of spaces and tabs. # This makes subsequent regexen easier to write, because we can # match consecutive blank lines with /\n+/ instead of something # contorted like /[ \t]*\n+/ . text = self._ws_only_line_re.sub("", text) if self.safe_mode: text = self._hash_html_spans(text) # Turn block-level HTML blocks into hash entries text = self._hash_html_blocks(text, raw=True) # Strip link definitions, store in hashes. if "footnotes" in self.extras: # Must do footnotes first because an unlucky footnote defn # looks like a link defn: # [^4]: this "looks like a link defn" text = self._strip_footnote_definitions(text) text = self._strip_link_definitions(text) text = self._run_block_gamut(text) text = self._unescape_special_chars(text) if "footnotes" in self.extras: text = self._add_footnotes(text) if self.safe_mode: text = self._unhash_html_spans(text) text += "\n" return text _emacs_oneliner_vars_pat = re.compile(r"-\*-\s*([^\r\n]*?)\s*-\*-", re.UNICODE) # This regular expression is intended to match blocks like this: # PREFIX Local Variables: SUFFIX # PREFIX mode: Tcl SUFFIX # PREFIX End: SUFFIX # Some notes: # - "[ \t]" is used instead of "\s" to specifically exclude newlines # - "(\r\n|\n|\r)" is used instead of "$" because the sre engine does # not like anything other than Unix-style line terminators. _emacs_local_vars_pat = re.compile(r"""^ (?P(?:[^\r\n|\n|\r])*?) [\ \t]*Local\ Variables:[\ \t]* (?P.*?)(?:\r\n|\n|\r) (?P.*?\1End:) """, re.IGNORECASE | re.MULTILINE | re.DOTALL | re.VERBOSE) def _get_emacs_vars(self, text): """Return a dictionary of emacs-style local variables. Parsing is done loosely according to this spec (and according to some in-practice deviations from this): http://www.gnu.org/software/emacs/manual/html_node/emacs/Specifying-File-Variables.html#Specifying-File-Variables """ emacs_vars = {} SIZE = pow(2, 13) # 8kB # Search near the start for a '-*-'-style one-liner of variables. head = text[:SIZE] if "-*-" in head: match = self._emacs_oneliner_vars_pat.search(head) if match: emacs_vars_str = match.group(1) assert '\n' not in emacs_vars_str emacs_var_strs = [s.strip() for s in emacs_vars_str.split(';') if s.strip()] if len(emacs_var_strs) == 1 and ':' not in emacs_var_strs[0]: # While not in the spec, this form is allowed by emacs: # -*- Tcl -*- # where the implied "variable" is "mode". This form # is only allowed if there are no other variables. emacs_vars["mode"] = emacs_var_strs[0].strip() else: for emacs_var_str in emacs_var_strs: try: variable, value = emacs_var_str.strip().split(':', 1) except ValueError: log.debug("emacs variables error: malformed -*- " "line: %r", emacs_var_str) continue # Lowercase the variable name because Emacs allows "Mode" # or "mode" or "MoDe", etc. emacs_vars[variable.lower()] = value.strip() tail = text[-SIZE:] if "Local Variables" in tail: match = self._emacs_local_vars_pat.search(tail) if match: prefix = match.group("prefix") suffix = match.group("suffix") lines = match.group("content").splitlines(0) #print "prefix=%r, suffix=%r, content=%r, lines: %s"\ # % (prefix, suffix, match.group("content"), lines) # Validate the Local Variables block: proper prefix and suffix # usage. for i, line in enumerate(lines): if not line.startswith(prefix): log.debug("emacs variables error: line '%s' " "does not use proper prefix '%s'" % (line, prefix)) return {} # Don't validate suffix on last line. Emacs doesn't care, # neither should we. if i != len(lines)-1 and not line.endswith(suffix): log.debug("emacs variables error: line '%s' " "does not use proper suffix '%s'" % (line, suffix)) return {} # Parse out one emacs var per line. continued_for = None for line in lines[:-1]: # no var on the last line ("PREFIX End:") if prefix: line = line[len(prefix):] # strip prefix if suffix: line = line[:-len(suffix)] # strip suffix line = line.strip() if continued_for: variable = continued_for if line.endswith('\\'): line = line[:-1].rstrip() else: continued_for = None emacs_vars[variable] += ' ' + line else: try: variable, value = line.split(':', 1) except ValueError: log.debug("local variables error: missing colon " "in local variables entry: '%s'" % line) continue # Do NOT lowercase the variable name, because Emacs only # allows "mode" (and not "Mode", "MoDe", etc.) in this block. value = value.strip() if value.endswith('\\'): value = value[:-1].rstrip() continued_for = variable else: continued_for = None emacs_vars[variable] = value # Unquote values. for var, val in emacs_vars.items(): if len(val) > 1 and (val.startswith('"') and val.endswith('"') or val.startswith('"') and val.endswith('"')): emacs_vars[var] = val[1:-1] return emacs_vars # Cribbed from a post by Bart Lateur: # _detab_re = re.compile(r'(.*?)\t', re.M) def _detab_sub(self, match): g1 = match.group(1) return g1 + (' ' * (self.tab_width - len(g1) % self.tab_width)) def _detab(self, text): r"""Remove (leading?) tabs from a file. >>> m = Markdown() >>> m._detab("\tfoo") ' foo' >>> m._detab(" \tfoo") ' foo' >>> m._detab("\t foo") ' foo' >>> m._detab(" foo") ' foo' >>> m._detab(" foo\n\tbar\tblam") ' foo\n bar blam' """ if '\t' not in text: return text return self._detab_re.subn(self._detab_sub, text)[0] _block_tags_a = 'p|div|h[1-6]|blockquote|pre|table|dl|ol|ul|script|noscript|form|fieldset|iframe|math|ins|del' _strict_tag_block_re = re.compile(r""" ( # save in \1 ^ # start of line (with re.M) <(%s) # start tag = \2 \b # word break (.*\n)*? # any number of lines, minimally matching # the matching end tag [ \t]* # trailing spaces/tabs (?=\n+|\Z) # followed by a newline or end of document ) """ % _block_tags_a, re.X | re.M) _block_tags_b = 'p|div|h[1-6]|blockquote|pre|table|dl|ol|ul|script|noscript|form|fieldset|iframe|math' _liberal_tag_block_re = re.compile(r""" ( # save in \1 ^ # start of line (with re.M) <(%s) # start tag = \2 \b # word break (.*\n)*? # any number of lines, minimally matching .* # the matching end tag [ \t]* # trailing spaces/tabs (?=\n+|\Z) # followed by a newline or end of document ) """ % _block_tags_b, re.X | re.M) # Save for usage in coming 'xml' extra. XXX_liberal_tag_block_re = re.compile(r""" ( # save in \1 ^ # start of line (with re.M) (?: <(%s|\w+:\w+) # start tag = \2 \b # word break (?:.*\n)*? # any number of lines, minimally matching .* # the matching end tag | <(\w+:)?\w+ # single tag-start \b # word break .*? # any content on one line, minimally matching /> # end of tag | <\?\w+ # start of processing instruction \b # word break .*? # any content on one line, minimally matching \?> # the PI end tag ) [ \t]* # trailing spaces/tabs (?=\n+|\Z) # followed by a newline or end of document ) """ % _block_tags_b, re.X | re.M) def _hash_html_block_sub(self, match, raw=False): html = match.group(1) if raw and self.safe_mode: html = self._sanitize_html(html) key = _hash_text(html) self.html_blocks[key] = html return "\n\n" + key + "\n\n" def _hash_html_blocks(self, text, raw=False): """Hashify HTML blocks We only want to do this for block-level HTML tags, such as headers, lists, and tables. That's because we still want to wrap

s around "paragraphs" that are wrapped in non-block-level tags, such as anchors, phrase emphasis, and spans. The list of tags we're looking for is hard-coded. @param raw {boolean} indicates if these are raw HTML blocks in the original source. It makes a difference in "safe" mode. """ if '<' not in text: return text # Pass `raw` value into our calls to self._hash_html_block_sub. hash_html_block_sub = _curry(self._hash_html_block_sub, raw=raw) # First, look for nested blocks, e.g.: #

#
# tags for inner block must be indented. #
#
# # The outermost tags must start at the left margin for this to match, and # the inner nested divs must be indented. # We need to do this before the next, more liberal match, because the next # match will start at the first `
` and stop at the first `
`. text = self._strict_tag_block_re.sub(hash_html_block_sub, text) # Now match more liberally, simply from `\n` to `\n` text = self._liberal_tag_block_re.sub(hash_html_block_sub, text) # Special case just for
. It was easier to make a special # case than to make the other regex more complicated. if "", start_idx) + 3 except ValueError, ex: break # Start position for next comment block search. start = end_idx # Validate whitespace before comment. if start_idx: # - Up to `tab_width - 1` spaces before start_idx. for i in range(self.tab_width - 1): if text[start_idx - 1] != ' ': break start_idx -= 1 if start_idx == 0: break # - Must be preceded by 2 newlines or hit the start of # the document. if start_idx == 0: pass elif start_idx == 1 and text[0] == '\n': start_idx = 0 # to match minute detail of Markdown.pl regex elif text[start_idx-2:start_idx] == '\n\n': pass else: break # Validate whitespace after comment. # - Any number of spaces and tabs. while end_idx < len(text): if text[end_idx] not in ' \t': break end_idx += 1 # - Must be following by 2 newlines or hit end of text. if text[end_idx:end_idx+2] not in ('', '\n', '\n\n'): continue # Escape and hash (must match `_hash_html_block_sub`). html = text[start_idx:end_idx] if raw and self.safe_mode: html = self._sanitize_html(html) key = _hash_text(html) self.html_blocks[key] = html text = text[:start_idx] + "\n\n" + key + "\n\n" + text[end_idx:] return text def _strip_link_definitions(self, text): # Strips link definitions from text, stores the URLs and titles in # hash references. less_than_tab = self.tab_width - 1 # Link defs are in the form: # [id]: url "optional title" _link_def_re = re.compile(r""" ^[ ]{0,%d}\[(.+)\]: # id = \1 [ \t]* \n? # maybe *one* newline [ \t]* ? # url = \2 [ \t]* (?: \n? # maybe one newline [ \t]* (?<=\s) # lookbehind for whitespace ['"(] ([^\n]*) # title = \3 ['")] [ \t]* )? # title is optional (?:\n+|\Z) """ % less_than_tab, re.X | re.M | re.U) return _link_def_re.sub(self._extract_link_def_sub, text) def _extract_link_def_sub(self, match): id, url, title = match.groups() key = id.lower() # Link IDs are case-insensitive self.urls[key] = self._encode_amps_and_angles(url) if title: self.titles[key] = title.replace('"', '"') return "" def _extract_footnote_def_sub(self, match): id, text = match.groups() text = _dedent(text, skip_first_line=not text.startswith('\n')).strip() normed_id = re.sub(r'\W', '-', id) # Ensure footnote text ends with a couple newlines (for some # block gamut matches). self.footnotes[normed_id] = text + "\n\n" return "" def _strip_footnote_definitions(self, text): """A footnote definition looks like this: [^note-id]: Text of the note. May include one or more indented paragraphs. Where, - The 'note-id' can be pretty much anything, though typically it is the number of the footnote. - The first paragraph may start on the next line, like so: [^note-id]: Text of the note. """ less_than_tab = self.tab_width - 1 footnote_def_re = re.compile(r''' ^[ ]{0,%d}\[\^(.+)\]: # id = \1 [ \t]* ( # footnote text = \2 # First line need not start with the spaces. (?:\s*.*\n+) (?: (?:[ ]{%d} | \t) # Subsequent lines must be indented. .*\n+ )* ) # Lookahead for non-space at line-start, or end of doc. (?:(?=^[ ]{0,%d}\S)|\Z) ''' % (less_than_tab, self.tab_width, self.tab_width), re.X | re.M) return footnote_def_re.sub(self._extract_footnote_def_sub, text) _hr_res = [ re.compile(r"^[ ]{0,2}([ ]?\*[ ]?){3,}[ \t]*$", re.M), re.compile(r"^[ ]{0,2}([ ]?\-[ ]?){3,}[ \t]*$", re.M), re.compile(r"^[ ]{0,2}([ ]?\_[ ]?){3,}[ \t]*$", re.M), ] def _run_block_gamut(self, text): # These are all the transformations that form block-level # tags like paragraphs, headers, and list items. text = self._do_headers(text) # Do Horizontal Rules: hr = "\n tags around block-level tags. text = self._hash_html_blocks(text) text = self._form_paragraphs(text) return text def _pyshell_block_sub(self, match): lines = match.group(0).splitlines(0) _dedentlines(lines) indent = ' ' * self.tab_width s = ('\n' # separate from possible cuddled paragraph + indent + ('\n'+indent).join(lines) + '\n\n') return s def _prepare_pyshell_blocks(self, text): """Ensure that Python interactive shell sessions are put in code blocks -- even if not properly indented. """ if ">>>" not in text: return text less_than_tab = self.tab_width - 1 _pyshell_block_re = re.compile(r""" ^([ ]{0,%d})>>>[ ].*\n # first line ^(\1.*\S+.*\n)* # any number of subsequent lines ^\n # ends with a blank line """ % less_than_tab, re.M | re.X) return _pyshell_block_re.sub(self._pyshell_block_sub, text) def _run_span_gamut(self, text): # These are all the transformations that occur *within* block-level # tags like paragraphs, headers, and list items. text = self._do_code_spans(text) text = self._escape_special_chars(text) # Process anchor and image tags. text = self._do_links(text) # Make links out of things like `` # Must come after _do_links(), because you can use < and > # delimiters in inline links like [this](). text = self._do_auto_links(text) if "link-patterns" in self.extras: text = self._do_link_patterns(text) text = self._encode_amps_and_angles(text) text = self._do_italics_and_bold(text) # Do hard breaks: text = re.sub(r" {2,}\n", " | # auto-link (e.g., ) <\w+[^>]*> | # comment | <\?.*?\?> # processing instruction ) """, re.X) def _escape_special_chars(self, text): # Python markdown note: the HTML tokenization here differs from # that in Markdown.pl, hence the behaviour for subtle cases can # differ (I believe the tokenizer here does a better job because # it isn't susceptible to unmatched '<' and '>' in HTML tags). # Note, however, that '>' is not allowed in an auto-link URL # here. escaped = [] is_html_markup = False for token in self._sorta_html_tokenize_re.split(text): if is_html_markup: # Within tags/HTML-comments/auto-links, encode * and _ # so they don't conflict with their use in Markdown for # italics and strong. We're replacing each such # character with its corresponding MD5 checksum value; # this is likely overkill, but it should prevent us from # colliding with the escape values by accident. escaped.append(token.replace('*', g_escape_table['*']) .replace('_', g_escape_table['_'])) else: escaped.append(self._encode_backslash_escapes(token)) is_html_markup = not is_html_markup return ''.join(escaped) def _hash_html_spans(self, text): # Used for safe_mode. def _is_auto_link(s): if ':' in s and self._auto_link_re.match(s): return True elif '@' in s and self._auto_email_link_re.match(s): return True return False tokens = [] is_html_markup = False for token in self._sorta_html_tokenize_re.split(text): if is_html_markup and not _is_auto_link(token): sanitized = self._sanitize_html(token) key = _hash_text(sanitized) self.html_spans[key] = sanitized tokens.append(key) else: tokens.append(token) is_html_markup = not is_html_markup return ''.join(tokens) def _unhash_html_spans(self, text): for key, sanitized in self.html_spans.items(): text = text.replace(key, sanitized) return text def _sanitize_html(self, s): if self.safe_mode == "replace": return self.html_removed_text elif self.safe_mode == "escape": replacements = [ ('&', '&'), ('<', '<'), ('>', '>'), ] for before, after in replacements: s = s.replace(before, after) return s else: raise MarkdownError("invalid value for 'safe_mode': %r (must be " "'escape' or 'replace')" % self.safe_mode) _tail_of_inline_link_re = re.compile(r''' # Match tail of: [text](/url/) or [text](/url/ "title") \( # literal paren [ \t]* (?P # \1 <.*?> | .*? ) [ \t]* ( # \2 (['"]) # quote char = \3 (?P.*?) \3 # matching quote )? # title is optional \) ''', re.X | re.S) _tail_of_reference_link_re = re.compile(r''' # Match tail of: [text][id] [ ]? # one optional space (?:\n[ ]*)? # one optional newline followed by spaces \[ (?P<id>.*?) \] ''', re.X | re.S) def _do_links(self, text): """Turn Markdown link shortcuts into XHTML <a> and <img> tags. This is a combination of Markdown.pl's _DoAnchors() and _DoImages(). They are done together because that simplified the approach. It was necessary to use a different approach than Markdown.pl because of the lack of atomic matching support in Python's regex engine used in $g_nested_brackets. """ MAX_LINK_TEXT_SENTINEL = 300 # `anchor_allowed_pos` is used to support img links inside # anchors, but not anchors inside anchors. An anchor's start # pos must be `>= anchor_allowed_pos`. anchor_allowed_pos = 0 curr_pos = 0 while True: # Handle the next link. # The next '[' is the start of: # - an inline anchor: [text](url "title") # - a reference anchor: [text][id] # - an inline img: ![text](url "title") # - a reference img: ![text][id] # - a footnote ref: [^id] # (Only if 'footnotes' extra enabled) # - a footnote defn: [^id]: ... # (Only if 'footnotes' extra enabled) These have already # been stripped in _strip_footnote_definitions() so no # need to watch for them. # - a link definition: [id]: url "title" # These have already been stripped in # _strip_link_definitions() so no need to watch for them. # - not markup: [...anything else... try: start_idx = text.index('[', curr_pos) except ValueError: break text_length = len(text) # Find the matching closing ']'. # Markdown.pl allows *matching* brackets in link text so we # will here too. Markdown.pl *doesn't* currently allow # matching brackets in img alt text -- we'll differ in that # regard. bracket_depth = 0 for p in range(start_idx+1, min(start_idx+MAX_LINK_TEXT_SENTINEL, text_length)): ch = text[p] if ch == ']': bracket_depth -= 1 if bracket_depth < 0: break elif ch == '[': bracket_depth += 1 else: # Closing bracket not found within sentinel length. # This isn't markup. curr_pos = start_idx + 1 continue link_text = text[start_idx+1:p] # Possibly a footnote ref? if "footnotes" in self.extras and link_text.startswith("^"): normed_id = re.sub(r'\W', '-', link_text[1:]) if normed_id in self.footnotes: self.footnote_ids.append(normed_id) result = '<sup class="footnote-ref" id="fnref-%s">' \ '<a href="#fn-%s">%s</a></sup>' \ % (normed_id, normed_id, len(self.footnote_ids)) text = text[:start_idx] + result + text[p+1:] else: # This id isn't defined, leave the markup alone. curr_pos = p+1 continue # Now determine what this is by the remainder. p += 1 if p == text_length: return text # Inline anchor or img? if text[p] == '(': # attempt at perf improvement match = self._tail_of_inline_link_re.match(text, p) if match: # Handle an inline anchor or img. is_img = start_idx > 0 and text[start_idx-1] == "!" if is_img: start_idx -= 1 url, title = match.group("url"), match.group("title") if url and url[0] == '<': url = url[1:-1] # '<url>' -> 'url' # We've got to encode these to avoid conflicting # with italics/bold. url = url.replace('*', g_escape_table['*']) \ .replace('_', g_escape_table['_']) if title: title_str = ' title="%s"' \ % title.replace('*', g_escape_table['*']) \ .replace('_', g_escape_table['_']) \ .replace('"', '"') else: title_str = '' if is_img: result = '<img src="%s" alt="%s"%s%s' \ % (url, link_text.replace('"', '"'), title_str, self.empty_element_suffix) curr_pos = start_idx + len(result) text = text[:start_idx] + result + text[match.end():] elif start_idx >= anchor_allowed_pos: result_head = '<a href="%s"%s>' % (url, title_str) result = '%s%s</a>' % (result_head, link_text) # <img> allowed from curr_pos on, <a> from # anchor_allowed_pos on. curr_pos = start_idx + len(result_head) anchor_allowed_pos = start_idx + len(result) text = text[:start_idx] + result + text[match.end():] else: # Anchor not allowed here. curr_pos = start_idx + 1 continue # Reference anchor or img? else: match = self._tail_of_reference_link_re.match(text, p) if match: # Handle a reference-style anchor or img. is_img = start_idx > 0 and text[start_idx-1] == "!" if is_img: start_idx -= 1 link_id = match.group("id").lower() if not link_id: link_id = link_text.lower() # for links like [this][] if link_id in self.urls: url = self.urls[link_id] # We've got to encode these to avoid conflicting # with italics/bold. url = url.replace('*', g_escape_table['*']) \ .replace('_', g_escape_table['_']) title = self.titles.get(link_id) if title: title = title.replace('*', g_escape_table['*']) \ .replace('_', g_escape_table['_']) title_str = ' title="%s"' % title else: title_str = '' if is_img: result = '<img src="%s" alt="%s"%s%s' \ % (url, link_text.replace('"', '"'), title_str, self.empty_element_suffix) curr_pos = start_idx + len(result) text = text[:start_idx] + result + text[match.end():] elif start_idx >= anchor_allowed_pos: result = '<a href="%s"%s>%s</a>' \ % (url, title_str, link_text) result_head = '<a href="%s"%s>' % (url, title_str) result = '%s%s</a>' % (result_head, link_text) # <img> allowed from curr_pos on, <a> from # anchor_allowed_pos on. curr_pos = start_idx + len(result_head) anchor_allowed_pos = start_idx + len(result) text = text[:start_idx] + result + text[match.end():] else: # Anchor not allowed here. curr_pos = start_idx + 1 else: # This id isn't defined, leave the markup alone. curr_pos = match.end() continue # Otherwise, it isn't markup. curr_pos = start_idx + 1 return text _setext_h_re = re.compile(r'^(.+)[ \t]*\n(=+|-+)[ \t]*\n+', re.M) def _setext_h_sub(self, match): n = {"=": 1, "-": 2}[match.group(2)[0]] demote_headers = self.extras.get("demote-headers") if demote_headers: n = min(n + demote_headers, 6) return "<h%d>%s</h%d>\n\n" \ % (n, self._run_span_gamut(match.group(1)), n) _atx_h_re = re.compile(r''' ^(\#{1,6}) # \1 = string of #'s [ \t]* (.+?) # \2 = Header text [ \t]* (?<!\\) # ensure not an escaped trailing '#' \#* # optional closing #'s (not counted) \n+ ''', re.X | re.M) def _atx_h_sub(self, match): n = len(match.group(1)) demote_headers = self.extras.get("demote-headers") if demote_headers: n = min(n + demote_headers, 6) return "<h%d>%s</h%d>\n\n" \ % (n, self._run_span_gamut(match.group(2)), n) def _do_headers(self, text): # Setext-style headers: # Header 1 # ======== # # Header 2 # -------- text = self._setext_h_re.sub(self._setext_h_sub, text) # atx-style headers: # # Header 1 # ## Header 2 # ## Header 2 with closing hashes ## # ... # ###### Header 6 text = self._atx_h_re.sub(self._atx_h_sub, text) return text _marker_ul_chars = '*+-' _marker_any = r'(?:[%s]|\d+\.)' % _marker_ul_chars _marker_ul = '(?:[%s])' % _marker_ul_chars _marker_ol = r'(?:\d+\.)' def _list_sub(self, match): lst = match.group(1) lst_type = match.group(3) in self._marker_ul_chars and "ul" or "ol" result = self._process_list_items(lst) if self.list_level: return "<%s>\n%s</%s>\n" % (lst_type, result, lst_type) else: return "<%s>\n%s</%s>\n\n" % (lst_type, result, lst_type) def _do_lists(self, text): # Form HTML ordered (numbered) and unordered (bulleted) lists. for marker_pat in (self._marker_ul, self._marker_ol): # Re-usable pattern to match any entire ul or ol list: less_than_tab = self.tab_width - 1 whole_list = r''' ( # \1 = whole list ( # \2 [ ]{0,%d} (%s) # \3 = first list item marker [ \t]+ ) (?:.+?) ( # \4 \Z | \n{2,} (?=\S) (?! # Negative lookahead for another list item marker [ \t]* %s[ \t]+ ) ) ) ''' % (less_than_tab, marker_pat, marker_pat) # We use a different prefix before nested lists than top-level lists. # See extended comment in _process_list_items(). # # Note: There's a bit of duplication here. My original implementation # created a scalar regex pattern as the conditional result of the test on # $g_list_level, and then only ran the $text =~ s{...}{...}egmx # substitution once, using the scalar as the pattern. This worked, # everywhere except when running under MT on my hosting account at Pair # Networks. There, this caused all rebuilds to be killed by the reaper (or # perhaps they crashed, but that seems incredibly unlikely given that the # same script on the same server ran fine *except* under MT. I've spent # more time trying to figure out why this is happening than I'd like to # admit. My only guess, backed up by the fact that this workaround works, # is that Perl optimizes the substition when it can figure out that the # pattern will never change, and when this optimization isn't on, we run # afoul of the reaper. Thus, the slightly redundant code to that uses two # static s/// patterns rather than one conditional pattern. if self.list_level: sub_list_re = re.compile("^"+whole_list, re.X | re.M | re.S) text = sub_list_re.sub(self._list_sub, text) else: list_re = re.compile(r"(?:(?<=\n\n)|\A\n?)"+whole_list, re.X | re.M | re.S) text = list_re.sub(self._list_sub, text) return text _list_item_re = re.compile(r''' (\n)? # leading line = \1 (^[ \t]*) # leading whitespace = \2 (%s) [ \t]+ # list marker = \3 ((?:.+?) # list item text = \4 (\n{1,2})) # eols = \5 (?= \n* (\Z | \2 (%s) [ \t]+)) ''' % (_marker_any, _marker_any), re.M | re.X | re.S) _last_li_endswith_two_eols = False def _list_item_sub(self, match): item = match.group(4) leading_line = match.group(1) leading_space = match.group(2) if leading_line or "\n\n" in item or self._last_li_endswith_two_eols: item = self._run_block_gamut(self._outdent(item)) else: # Recursion for sub-lists: item = self._do_lists(self._outdent(item)) if item.endswith('\n'): item = item[:-1] item = self._run_span_gamut(item) self._last_li_endswith_two_eols = (len(match.group(5)) == 2) return "<li>%s</li>\n" % item def _process_list_items(self, list_str): # Process the contents of a single ordered or unordered list, # splitting it into individual list items. # The $g_list_level global keeps track of when we're inside a list. # Each time we enter a list, we increment it; when we leave a list, # we decrement. If it's zero, we're not in a list anymore. # # We do this because when we're not inside a list, we want to treat # something like this: # # I recommend upgrading to version # 8. Oops, now this line is treated # as a sub-list. # # As a single paragraph, despite the fact that the second line starts # with a digit-period-space sequence. # # Whereas when we're inside a list (or sub-list), that line will be # treated as the start of a sub-list. What a kludge, huh? This is # an aspect of Markdown's syntax that's hard to parse perfectly # without resorting to mind-reading. Perhaps the solution is to # change the syntax rules such that sub-lists must start with a # starting cardinal number; e.g. "1." or "a.". self.list_level += 1 self._last_li_endswith_two_eols = False list_str = list_str.rstrip('\n') + '\n' list_str = self._list_item_re.sub(self._list_item_sub, list_str) self.list_level -= 1 return list_str def _get_pygments_lexer(self, lexer_name): try: from pygments import lexers, util except ImportError: return None try: return lexers.get_lexer_by_name(lexer_name) except util.ClassNotFound: return None def _color_with_pygments(self, codeblock, lexer): import pygments import pygments.formatters class HtmlCodeFormatter(pygments.formatters.HtmlFormatter): def _wrap_code(self, inner): """A function for use in a Pygments Formatter which wraps in <code> tags. """ yield 0, "<code>" for tup in inner: yield tup yield 0, "</code>" def wrap(self, source, outfile): """Return the source with a code, pre, and div.""" return self._wrap_div(self._wrap_pre(self._wrap_code(source))) formatter = HtmlCodeFormatter(cssclass="codehilite") return pygments.highlight(codeblock, lexer, formatter) def _code_block_sub(self, match): codeblock = match.group(1) codeblock = self._outdent(codeblock) codeblock = self._detab(codeblock) codeblock = codeblock.lstrip('\n') # trim leading newlines codeblock = codeblock.rstrip() # trim trailing whitespace if "code-color" in self.extras and codeblock.startswith(":::"): lexer_name, rest = codeblock.split('\n', 1) lexer_name = lexer_name[3:].strip() lexer = self._get_pygments_lexer(lexer_name) codeblock = rest.lstrip("\n") # Remove lexer declaration line. if lexer: colored = self._color_with_pygments(codeblock, lexer) return "\n\n%s\n\n" % colored codeblock = self._encode_code(codeblock) return "\n\n<pre><code>%s\n</code></pre>\n\n" % codeblock def _do_code_blocks(self, text): """Process Markdown `<pre><code>` blocks.""" code_block_re = re.compile(r''' (?:\n\n|\A) ( # $1 = the code block -- one or more lines, starting with a space/tab (?: (?:[ ]{%d} | \t) # Lines must start with a tab or a tab-width of spaces .*\n+ )+ ) ((?=^[ ]{0,%d}\S)|\Z) # Lookahead for non-space at line-start, or end of doc ''' % (self.tab_width, self.tab_width), re.M | re.X) return code_block_re.sub(self._code_block_sub, text) # Rules for a code span: # - backslash escapes are not interpreted in a code span # - to include one or or a run of more backticks the delimiters must # be a longer run of backticks # - cannot start or end a code span with a backtick; pad with a # space and that space will be removed in the emitted HTML # See `test/tm-cases/escapes.text` for a number of edge-case # examples. _code_span_re = re.compile(r''' (?<!\\) (`+) # \1 = Opening run of ` (?!`) # See Note A test/tm-cases/escapes.text (.+?) # \2 = The code block (?<!`) \1 # Matching closer (?!`) ''', re.X | re.S) def _code_span_sub(self, match): c = match.group(2).strip(" \t") c = self._encode_code(c) return "<code>%s</code>" % c def _do_code_spans(self, text): # * Backtick quotes are used for <code></code> spans. # # * You can use multiple backticks as the delimiters if you want to # include literal backticks in the code span. So, this input: # # Just type ``foo `bar` baz`` at the prompt. # # Will translate to: # # <p>Just type <code>foo `bar` baz</code> at the prompt.</p> # # There's no arbitrary limit to the number of backticks you # can use as delimters. If you need three consecutive backticks # in your code, use four for delimiters, etc. # # * You can use spaces to get literal backticks at the edges: # # ... type `` `bar` `` ... # # Turns to: # # ... type <code>`bar`</code> ... return self._code_span_re.sub(self._code_span_sub, text) def _encode_code(self, text): """Encode/escape certain characters inside Markdown code runs. The point is that in code, these characters are literals, and lose their special Markdown meanings. """ replacements = [ # Encode all ampersands; HTML entities are not # entities within a Markdown code span. ('&', '&'), # Do the angle bracket song and dance: ('<', '<'), ('>', '>'), # Now, escape characters that are magic in Markdown: ('*', g_escape_table['*']), ('_', g_escape_table['_']), ('{', g_escape_table['{']), ('}', g_escape_table['}']), ('[', g_escape_table['[']), (']', g_escape_table[']']), ('\\', g_escape_table['\\']), ] for before, after in replacements: text = text.replace(before, after) return text _strong_re = re.compile(r"(\*\*|__)(?=\S)(.+?[*_]*)(?<=\S)\1", re.S) _em_re = re.compile(r"(\*|_)(?=\S)(.+?)(?<=\S)\1", re.S) _code_friendly_strong_re = re.compile(r"\*\*(?=\S)(.+?[*_]*)(?<=\S)\*\*", re.S) _code_friendly_em_re = re.compile(r"\*(?=\S)(.+?)(?<=\S)\*", re.S) def _do_italics_and_bold(self, text): # <strong> must go first: if "code-friendly" in self.extras: text = self._code_friendly_strong_re.sub(r"<strong>\1</strong>", text) text = self._code_friendly_em_re.sub(r"<em>\1</em>", text) else: text = self._strong_re.sub(r"<strong>\2</strong>", text) text = self._em_re.sub(r"<em>\2</em>", text) return text _block_quote_re = re.compile(r''' ( # Wrap whole match in \1 ( ^[ \t]*>[ \t]? # '>' at the start of a line .+\n # rest of the first line (.+\n)* # subsequent consecutive lines \n* # blanks )+ ) ''', re.M | re.X) _bq_one_level_re = re.compile('^[ \t]*>[ \t]?', re.M); _html_pre_block_re = re.compile(r'(\s*<pre>.+?</pre>)', re.S) def _dedent_two_spaces_sub(self, match): return re.sub(r'(?m)^ ', '', match.group(1)) def _block_quote_sub(self, match): bq = match.group(1) bq = self._bq_one_level_re.sub('', bq) # trim one level of quoting bq = self._ws_only_line_re.sub('', bq) # trim whitespace-only lines bq = self._run_block_gamut(bq) # recurse bq = re.sub('(?m)^', ' ', bq) # These leading spaces screw with <pre> content, so we need to fix that: bq = self._html_pre_block_re.sub(self._dedent_two_spaces_sub, bq) return "<blockquote>\n%s\n</blockquote>\n\n" % bq def _do_block_quotes(self, text): if '>' not in text: return text return self._block_quote_re.sub(self._block_quote_sub, text) def _form_paragraphs(self, text): # Strip leading and trailing lines: text = text.strip('\n') # Wrap <p> tags. grafs = re.split(r"\n{2,}", text) for i, graf in enumerate(grafs): if graf in self.html_blocks: # Unhashify HTML blocks grafs[i] = self.html_blocks[graf] else: # Wrap <p> tags. graf = self._run_span_gamut(graf) grafs[i] = "<p>" + graf.lstrip(" \t") + "</p>" return "\n\n".join(grafs) def _add_footnotes(self, text): if self.footnotes: footer = [ '<div class="footnotes">', '<hr' + self.empty_element_suffix, '<ol>', ] for i, id in enumerate(self.footnote_ids): if i != 0: footer.append('') footer.append('<li id="fn-%s">' % id) footer.append(self._run_block_gamut(self.footnotes[id])) backlink = ('<a href="#fnref-%s" ' 'class="footnoteBackLink" ' 'title="Jump back to footnote %d in the text.">' '↩</a>' % (id, i+1)) if footer[-1].endswith("</p>"): footer[-1] = footer[-1][:-len("</p>")] \ + ' ' + backlink + "</p>" else: footer.append("\n<p>%s</p>" % backlink) footer.append('</li>') footer.append('</ol>') footer.append('</div>') return text + '\n\n' + '\n'.join(footer) else: return text # Ampersand-encoding based entirely on Nat Irons's Amputator MT plugin: # http://bumppo.net/projects/amputator/ _ampersand_re = re.compile(r'&(?!#?[xX]?(?:[0-9a-fA-F]+|\w+);)') _naked_lt_re = re.compile(r'<(?![a-z/?\$!])', re.I) def _encode_amps_and_angles(self, text): # Smart processing for ampersands and angle brackets that need # to be encoded. text = self._ampersand_re.sub('&', text) # Encode naked <'s text = self._naked_lt_re.sub('<', text) return text def _encode_backslash_escapes(self, text): for ch, escape in g_escape_table.items(): text = text.replace("\\"+ch, escape) return text _auto_link_re = re.compile(r'<((https?|ftp):[^\'">\s]+)>', re.I) def _auto_link_sub(self, match): g1 = match.group(1) return '<a href="%s">%s</a>' % (g1, g1) _auto_email_link_re = re.compile(r""" < (?:mailto:)? ( [-.\w]+ \@ [-\w]+(\.[-\w]+)*\.[a-zA-Z]+ ) > """, re.I | re.X | re.U) def _auto_email_link_sub(self, match): return self._encode_email_address( self._unescape_special_chars(match.group(1))) def _do_auto_links(self, text): text = self._auto_link_re.sub(self._auto_link_sub, text) text = self._auto_email_link_re.sub(self._auto_email_link_sub, text) return text def _encode_email_address(self, addr): # Input: an email address, e.g. "foo@example.com" # # Output: the email address as a mailto link, with each character # of the address encoded as either a decimal or hex entity, in # the hopes of foiling most address harvesting spam bots. E.g.: # # <a href="mailto:foo@e # xample.com">foo # @example.com</a> # # Based on a filter by Matthew Wickline, posted to the BBEdit-Talk # mailing list: <http://tinyurl.com/yu7ue> chars = [_xml_encode_email_char_at_random(ch) for ch in "mailto:" + addr] # Strip the mailto: from the visible part. addr = '<a href="%s">%s</a>' \ % (''.join(chars), ''.join(chars[7:])) return addr def _do_link_patterns(self, text): """Caveat emptor: there isn't much guarding against link patterns being formed inside other standard Markdown links, e.g. inside a [link def][like this]. Dev Notes: *Could* consider prefixing regexes with a negative lookbehind assertion to attempt to guard against this. """ link_from_hash = {} for regex, href in self.link_patterns: replacements = [] for match in regex.finditer(text): replacements.append((match.span(), match.expand(href))) for (start, end), href in reversed(replacements): escaped_href = ( href.replace('"', '"') # b/c of attr quote # To avoid markdown <em> and <strong>: .replace('*', g_escape_table['*']) .replace('_', g_escape_table['_'])) link = '<a href="%s">%s</a>' % (escaped_href, text[start:end]) hash = md5(link).hexdigest() link_from_hash[hash] = link text = text[:start] + hash + text[end:] for hash, link in link_from_hash.items(): text = text.replace(hash, link) return text def _unescape_special_chars(self, text): # Swap back in all the special characters we've hidden. for ch, hash in g_escape_table.items(): text = text.replace(hash, ch) return text def _outdent(self, text): # Remove one level of line-leading tabs or spaces return self._outdent_re.sub('', text) class MarkdownWithExtras(Markdown): """A markdowner class that enables most extras: - footnotes - code-color (only has effect if 'pygments' Python module on path) These are not included: - pyshell (specific to Python-related documenting) - code-friendly (because it *disables* part of the syntax) - link-patterns (because you need to specify some actual link-patterns anyway) """ extras = ["footnotes", "code-color"] #---- internal support functions # From http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/52549 def _curry(*args, **kwargs): function, args = args[0], args[1:] def result(*rest, **kwrest): combined = kwargs.copy() combined.update(kwrest) return function(*args + rest, **combined) return result # Recipe: regex_from_encoded_pattern (1.0) def _regex_from_encoded_pattern(s): """'foo' -> re.compile(re.escape('foo')) '/foo/' -> re.compile('foo') '/foo/i' -> re.compile('foo', re.I) """ if s.startswith('/') and s.rfind('/') != 0: # Parse it: /PATTERN/FLAGS idx = s.rfind('/') pattern, flags_str = s[1:idx], s[idx+1:] flag_from_char = { "i": re.IGNORECASE, "l": re.LOCALE, "s": re.DOTALL, "m": re.MULTILINE, "u": re.UNICODE, } flags = 0 for char in flags_str: try: flags |= flag_from_char[char] except KeyError: raise ValueError("unsupported regex flag: '%s' in '%s' " "(must be one of '%s')" % (char, s, ''.join(flag_from_char.keys()))) return re.compile(s[1:idx], flags) else: # not an encoded regex return re.compile(re.escape(s)) # Recipe: dedent (0.1.2) def _dedentlines(lines, tabsize=8, skip_first_line=False): """_dedentlines(lines, tabsize=8, skip_first_line=False) -> dedented lines "lines" is a list of lines to dedent. "tabsize" is the tab width to use for indent width calculations. "skip_first_line" is a boolean indicating if the first line should be skipped for calculating the indent width and for dedenting. This is sometimes useful for docstrings and similar. Same as dedent() except operates on a sequence of lines. Note: the lines list is modified **in-place**. """ DEBUG = False if DEBUG: print "dedent: dedent(..., tabsize=%d, skip_first_line=%r)"\ % (tabsize, skip_first_line) indents = [] margin = None for i, line in enumerate(lines): if i == 0 and skip_first_line: continue indent = 0 for ch in line: if ch == ' ': indent += 1 elif ch == '\t': indent += tabsize - (indent % tabsize) elif ch in '\r\n': continue # skip all-whitespace lines else: break else: continue # skip all-whitespace lines if DEBUG: print "dedent: indent=%d: %r" % (indent, line) if margin is None: margin = indent else: margin = min(margin, indent) if DEBUG: print "dedent: margin=%r" % margin if margin is not None and margin > 0: for i, line in enumerate(lines): if i == 0 and skip_first_line: continue removed = 0 for j, ch in enumerate(line): if ch == ' ': removed += 1 elif ch == '\t': removed += tabsize - (removed % tabsize) elif ch in '\r\n': if DEBUG: print "dedent: %r: EOL -> strip up to EOL" % line lines[i] = lines[i][j:] break else: raise ValueError("unexpected non-whitespace char %r in " "line %r while removing %d-space margin" % (ch, line, margin)) if DEBUG: print "dedent: %r: %r -> removed %d/%d"\ % (line, ch, removed, margin) if removed == margin: lines[i] = lines[i][j+1:] break elif removed > margin: lines[i] = ' '*(removed-margin) + lines[i][j+1:] break else: if removed: lines[i] = lines[i][removed:] return lines def _dedent(text, tabsize=8, skip_first_line=False): """_dedent(text, tabsize=8, skip_first_line=False) -> dedented text "text" is the text to dedent. "tabsize" is the tab width to use for indent width calculations. "skip_first_line" is a boolean indicating if the first line should be skipped for calculating the indent width and for dedenting. This is sometimes useful for docstrings and similar. textwrap.dedent(s), but don't expand tabs to spaces """ lines = text.splitlines(1) _dedentlines(lines, tabsize=tabsize, skip_first_line=skip_first_line) return ''.join(lines) class _memoized(object): """Decorator that caches a function's return value each time it is called. If called later with the same arguments, the cached value is returned, and not re-evaluated. http://wiki.python.org/moin/PythonDecoratorLibrary """ def __init__(self, func): self.func = func self.cache = {} def __call__(self, *args): try: return self.cache[args] except KeyError: self.cache[args] = value = self.func(*args) return value except TypeError: # uncachable -- for instance, passing a list as an argument. # Better to not cache than to blow up entirely. return self.func(*args) def __repr__(self): """Return the function's docstring.""" return self.func.__doc__ def _hr_tag_re_from_tab_width(tab_width): return re.compile(r""" (?: (?<=\n\n) # Starting after a blank line | # or \A\n? # the beginning of the doc ) ( # save in \1 [ ]{0,%d} <(hr) # start tag = \2 \b # word break ([^<>])*? # /?> # the matching end tag [ \t]* (?=\n{2,}|\Z) # followed by a blank line or end of document ) """ % (tab_width - 1), re.X) _hr_tag_re_from_tab_width = _memoized(_hr_tag_re_from_tab_width) def _xml_encode_email_char_at_random(ch): r = random() # Roughly 10% raw, 45% hex, 45% dec. # '@' *must* be encoded. I [John Gruber] insist. if r > 0.9 and ch != "@": return ch elif r < 0.45: # The [1:] is to drop leading '0': 0x63 -> x63 return '&#%s;' % hex(ord(ch))[1:] else: return '&#%s;' % ord(ch) def _hash_text(text): return 'md5:'+md5(text.encode("utf-8")).hexdigest() #---- mainline class _NoReflowFormatter(optparse.IndentedHelpFormatter): """An optparse formatter that does NOT reflow the description.""" def format_description(self, description): return description or "" def _test(): import doctest doctest.testmod() def main(argv=sys.argv): if not logging.root.handlers: logging.basicConfig() usage = "usage: %prog [PATHS...]" version = "%prog "+__version__ parser = optparse.OptionParser(prog="markdown2", usage=usage, version=version, description=cmdln_desc, formatter=_NoReflowFormatter()) parser.add_option("-v", "--verbose", dest="log_level", action="store_const", const=logging.DEBUG, help="more verbose output") parser.add_option("--encoding", help="specify encoding of text content") parser.add_option("--html4tags", action="store_true", default=False, help="use HTML 4 style for empty element tags") parser.add_option("-s", "--safe", metavar="MODE", dest="safe_mode", help="sanitize literal HTML: 'escape' escapes " "HTML meta chars, 'replace' replaces with an " "[HTML_REMOVED] note") parser.add_option("-x", "--extras", action="append", help="Turn on specific extra features (not part of " "the core Markdown spec). Supported values: " "'code-friendly' disables _/__ for emphasis; " "'code-color' adds code-block syntax coloring; " "'link-patterns' adds auto-linking based on patterns; " "'footnotes' adds the footnotes syntax;" "'pyshell' to put unindented Python interactive shell sessions in a <code> block.") parser.add_option("--use-file-vars", help="Look for and use Emacs-style 'markdown-extras' " "file var to turn on extras. See " "<http://code.google.com/p/python-markdown2/wiki/Extras>.") parser.add_option("--link-patterns-file", help="path to a link pattern file") parser.add_option("--self-test", action="store_true", help="run internal self-tests (some doctests)") parser.add_option("--compare", action="store_true", help="run against Markdown.pl as well (for testing)") parser.set_defaults(log_level=logging.INFO, compare=False, encoding="utf-8", safe_mode=None, use_file_vars=False) opts, paths = parser.parse_args() log.setLevel(opts.log_level) if opts.self_test: return _test() if opts.extras: extras = {} for s in opts.extras: splitter = re.compile("[,;: ]+") for e in splitter.split(s): if '=' in e: ename, earg = e.split('=', 1) try: earg = int(earg) except ValueError: pass else: ename, earg = e, None extras[ename] = earg else: extras = None if opts.link_patterns_file: link_patterns = [] f = open(opts.link_patterns_file) try: for i, line in enumerate(f.readlines()): if not line.strip(): continue if line.lstrip().startswith("#"): continue try: pat, href = line.rstrip().rsplit(None, 1) except ValueError: raise MarkdownError("%s:%d: invalid link pattern line: %r" % (opts.link_patterns_file, i+1, line)) link_patterns.append( (_regex_from_encoded_pattern(pat), href)) finally: f.close() else: link_patterns = None from os.path import join, dirname, abspath markdown_pl = join(dirname(dirname(abspath(__file__))), "test", "Markdown.pl") for path in paths: if opts.compare: print "==== Markdown.pl ====" perl_cmd = 'perl %s "%s"' % (markdown_pl, path) o = os.popen(perl_cmd) perl_html = o.read() o.close() sys.stdout.write(perl_html) print "==== markdown2.py ====" html = markdown_path(path, encoding=opts.encoding, html4tags=opts.html4tags, safe_mode=opts.safe_mode, extras=extras, link_patterns=link_patterns, use_file_vars=opts.use_file_vars) sys.stdout.write( html.encode(sys.stdout.encoding or "utf-8", 'xmlcharrefreplace')) if opts.compare: print "==== match? %r ====" % (perl_html == html) if __name__ == "__main__": sys.exit( main(sys.argv) ) ������������������������������������������������������������������������������������������������������������./weblog+jinja-1.1/weblog/post.py�������������������������������������������������������������������0100644�0001750�0000000�00000021054�11063022145�0015727�0����������������������������������������������������������������������������������������������������ustar�00henry���������������������������wheel������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������import re import email import codecs import logging from os import stat import datetime from urllib import quote try: from markdown2 import markdown except ImportError: def markdown(text, *args): logging.warning('Markdown syntax not available.' 'Please install markdown2.') # Ugly but better than nothing :) return '<pre>%s</pre>' % text from html_to_xhtml import html_to_xhtml class PostError(Exception): ''' Error in post file ''' def __init__(self, filename, message): Exception.__init__(self, '%s: %s' % (filename, message)) class Author(unicode): _AUTHOR_REGEX = re.compile(u'(?P<name>.+[^\s])\s*<(?P<email>[^@]+@.+)>', re.UNICODE) def name(self): r = self._AUTHOR_REGEX.match(self) if r: return r.group('name') else: return self def email(self): r = self._AUTHOR_REGEX.match(self) if r: return r.group('email') else: return '' class Post(object): DEFAULT_ENCODING = u'ascii' DEFAULT_AUTHOR = u'unknown author' def __init__(self, f, markup=None): if isinstance(f, basestring): self._filename = f input_file = open(f) else: self._filename = None input_file = f post_file = email.message_from_file(input_file) # First get the file's encoding self.encoding = unicode(post_file.get('encoding') or self.DEFAULT_ENCODING) try: codecs.lookup(self.encoding) except LookupError, e: raise PostError(self.get_filename(), str(e)) # Copy all field "into the object" and convert string to unicode. try: for key, value in post_file.items(): self.__dict__[key.encode('ascii').lower()] = \ unicode(value, self.encoding) except UnicodeDecodeError, e: raise PostError(self.get_filename(), "for key '%s': %s" % (key, e)) if not hasattr(self, 'author'): self.author = Author(self.DEFAULT_AUTHOR) else: self.author = Author(self.author) # Handle the date. If no date was specified use the file's modification # time. if not hasattr(self, 'date'): if not self._filename: self.date = None else: # Get the date from file's mtime and issue a warning mtime = stat(self._filename).st_mtime self.date = datetime.datetime.fromtimestamp(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(self.get_filename(), str(e)) try: self.ascii_title = self.title.encode('ascii', 'replace') except UnicodeDecodeError, e: raise PostError(self.get_filename(), 'Bad encoding in title') if not post_file.get_payload(): raise PostError(self.get_filename(), 'does not have content') try: self.content = unicode(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(self.get_filename(), 'Bad encoding in content line %d, %s' % \ (line_number + 1, e)) if not markup: # Determine type via file extension if self._filename and self._filename.endswith('.txt'): self._markup = 'markdown' elif self._filename and self._filename.endswith('.html'): self._markup = 'html' elif not self._filename: self._markup = 'html' else: logging.warning("Unable to determine '%s' type, falling " "back to HTML" % self._filename) self._markup = 'html' else: assert markup in ('markdown', 'html') self._markup = markup # Transform the 'files' field into a list of string if hasattr(self, 'files'): self.files = self.files.split() else: self.files = list() # 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.datetime.strptime(date_, date_format).date() except ValueError: continue for date_format in Post._DATETIME_FORMAT_LIST: try: return datetime.datetime.strptime(date_, date_format) except ValueError: continue raise ValueError('Unable to parse date \'%s\'\n' '(Use YYYY-MM-DD [[HH:MM]:SS] format)' % (date_)) def get_filename(self): if not self._filename: return '<unknown filename>' else: return self._filename def get_html(self): if self._markup == 'markdown': return markdown(self.content, html4tags=True) elif self._markup == 'html': return self.content else: assert False, "Unknown type for %r" % self def get_xhtml(self): if self._markup == 'markdown': return markdown(self.content, html4tags=False) elif self._markup == 'html': return html_to_xhtml(self.content) else: assert False, "Unknown type for %r" % self def __cmp__(self, other): ''' >>> file1 = "title: 1\\ndate: 2008-1-1\\n\\ntest" >>> file2 = "title: 2\\ndate: 2007-12-31\\n\\ntest" >>> 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(unicode(self.date) + self.title, unicode(other.date) + other.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-1.1/weblog/publish.py����������������������������������������������������������������0100644�0001750�0000000�00000012100�11063022145�0016400�0����������������������������������������������������������������������������������������������������ustar�00henry���������������������������wheel������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������import os import datetime import logging import codecs from shutil import copy import weblog from _jinja_environment import jinja_environment from html_full_url import html_full_url from post import Post, PostError from load import load_post_list, load_configuration from listing import generate_index_listing 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) top_dir = '../../../' r = post_tmpl.render(title=post.title, date=post.date, author=post.author, content=html_full_url(top_dir, post.get_html()), top_dir=top_dir, **dict(((k, v) for k, v in params.iteritems() if k != 'title' and k != 'content'))) open(os.path.join(dir, post.ascii_title + '.html'), 'w').write(r) def command_publish(args, options): source_dir = options.source_dir output_dir = options.output_dir try: config = load_configuration(options.configuration_file, source_dir or '.') except (KeyError, ValueError, IOError), error: logging.error('Error while loading configuration file \'%s\'' % options.configuration_file) raise SystemExit(error) 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(source_dir) try: post_list = list(reversed(sorted(load_post_list(source_dir)))) except (IOError, PostError), e: logging.error('Error while loading post files.') raise SystemExit(e) def generate_all(): params = dict(title=config['title'], description=config.get('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) # Copy all 'attached' files for post in post_list: for filename in post.files: destination = os.path.join(output_dir, filename) # Create the destination directory if it does not exist destination_dir = os.path.dirname(destination) # isdir returns False if the passed file does not exist if not os.path.isdir(destination_dir): os.makedirs(destination_dir) copy(os.path.join(source_dir, filename), destination) # Generate Atom feed template = env.get_template('feed.atom.tmpl') # Last time the feed was updated posts = post_list[:config['feed_limit']] if posts: feed_updated = max(p.date for p in posts) else: feed_updated = datetime.datetime.utcnow() atom_file = codecs.open(os.path.join(output_dir, 'feed.atom'), 'w', encoding='utf8') atom_file.write(template.render(posts=posts, feed_updated=feed_updated, weblog_version=weblog.__version__, **params)) atom_file.close() 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 ...') raise SystemExit(e) else: logging.info('Successfully generated weblog.') ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������./weblog+jinja-1.1/weblog/rfc3339.py����������������������������������������������������������������0100644�0001750�0000000�00000016007�11063022145�0016040�0����������������������������������������������������������������������������������������������������ustar�00henry���������������������������wheel������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������#!/usr/bin/env python ''' The function `rfc3339` formats dates according to the :RFC:`3339`. `rfc3339` tries to have as much as possible sensible defaults. ''' __author__ = 'Henry Precheur <henry@precheur.org>' __license__ = 'Public Domain' __version__ = '1' __all__ = ('rfc3339', ) import datetime import time import unittest def _timezone(utcoffset): ''' Return a string representing the timezone offset. >>> _timezone(0) '+00:00' >>> _timezone(3600) '+01:00' >>> _timezone(-28800) '-08:00' ''' # Python's division uses floor(), not round() like in other languages. # >>> -1 / 2 # -1 hours = int(float(utcoffset)) // 3600 minutes = abs(utcoffset) % 3600 // 60 return '%+03d:%02d' % (hours, minutes) def _timedelta_to_seconds(timedelta): ''' >>> _timedelta_to_seconds(datetime.timedelta(hours=3)) 10800 >>> _timedelta_to_seconds(datetime.timedelta(hours=3, minutes=15)) 11700 ''' return (timedelta.days * 86400 + timedelta.seconds + timedelta.microseconds // 1000) def _utc_offset(date, use_system_timezone): ''' Return the UTC offset of `date`. If `date` does not have any `tzinfo`, use the timezone informations stored locally on the system. >>> if time.daylight: ... system_timezone = -time.altzone ... else: ... system_timezone = -time.timezone >>> _utc_offset(datetime.datetime.now(), True) == system_timezone True >>> _utc_offset(datetime.datetime.now(), False) 0 ''' if isinstance(date, datetime.datetime) and date.tzinfo is not None: return _timedelta_to_seconds(date.dst() or date.utcoffset()) elif use_system_timezone: if time.daylight: return -time.altzone else: return -time.timezone else: return 0 def _utc_string(d): return d.strftime('%Y-%m-%dT%H:%M:%SZ') def rfc3339(date, utc=False, use_system_timezone=True): ''' Return a string formatted according to the :RFC:`3339`. If called with `utc=True`, it normalizes `date` to the UTC date. If `date` does not have any timezone information, uses the local timezone:: >>> date = datetime.datetime(2008, 4, 2, 20) >>> rfc3339(date, utc=True, use_system_timezone=False) '2008-04-02T20:00:00Z' >>> rfc3339(date) # doctest: +ELLIPSIS '2008-04-02T20:00:00...' If called with `user_system_time=False` don't use the local timezone if `date` does not have timezone informations and consider the offset to UTC to be zero:: >>> rfc3339(date, use_system_timezone=False) '2008-04-02T20:00:00+00:00' `date` must be a `datetime.datetime`, `datetime.date` or a timestamp as returned by `time.time()`:: >>> rfc3339(0, utc=True, use_system_timezone=False) '1970-01-01T00:00:00Z' >>> rfc3339(datetime.date(2008, 9, 6), utc=True, ... use_system_timezone=False) '2008-09-06T00:00:00Z' >>> rfc3339(datetime.date(2008, 9, 6), ... use_system_timezone=False) '2008-09-06T00:00:00+00:00' >>> rfc3339('foo bar') Traceback (most recent call last): ... TypeError: excepted datetime, got str instead ''' # Check if `date` is a timestamp. try: if utc: return _utc_string(datetime.datetime.utcfromtimestamp(date)) else: date = datetime.datetime.fromtimestamp(date) except TypeError: pass if isinstance(date, datetime.date): utcoffset = _utc_offset(date, use_system_timezone) if utc: if not isinstance(date, datetime.datetime): date = datetime.datetime(*date.timetuple()[:3]) return _utc_string(date + datetime.timedelta(seconds=utcoffset)) else: return date.strftime('%Y-%m-%dT%H:%M:%S') + _timezone(utcoffset) else: raise TypeError('excepted %s, got %s instead' % (datetime.datetime.__name__, date.__class__.__name__)) class LocalTimeTestCase(unittest.TestCase): ''' Test the use of the timezone saved locally. Since it is hard to test using doctest. ''' def setUp(self): local_utcoffset = _utc_offset(datetime.datetime.now(), True) self.local_utcoffset = datetime.timedelta(seconds=local_utcoffset) self.local_timezone = _timezone(local_utcoffset) def test_datetime(self): d = datetime.datetime.now() self.assertEqual(rfc3339(d), d.strftime('%Y-%m-%dT%H:%M:%S') + self.local_timezone) def test_datetime_timezone(self): class FixedNoDst(datetime.tzinfo): 'A timezone info with fixed offset, not DST' def utcoffset(self, dt): return datetime.timedelta(hours=2, minutes=30) def dst(self, dt): return None fixed_no_dst = FixedNoDst() class Fixed(FixedNoDst): 'A timezone info with DST' def dst(self, dt): return datetime.timedelta(hours=3, minutes=15) fixed = Fixed() d = datetime.datetime.now().replace(tzinfo=fixed_no_dst) timezone = _timezone(_timedelta_to_seconds(fixed_no_dst.\ utcoffset(None))) self.assertEqual(rfc3339(d), d.strftime('%Y-%m-%dT%H:%M:%S') + timezone) d = datetime.datetime.now().replace(tzinfo=fixed) timezone = _timezone(_timedelta_to_seconds(fixed.dst(None))) self.assertEqual(rfc3339(d), d.strftime('%Y-%m-%dT%H:%M:%S') + timezone) def test_datetime_utc(self): d = datetime.datetime.now() d_utc = d + self.local_utcoffset self.assertEqual(rfc3339(d, utc=True), d_utc.strftime('%Y-%m-%dT%H:%M:%SZ')) def test_date(self): d = datetime.date.today() self.assertEqual(rfc3339(d), d.strftime('%Y-%m-%dT%H:%M:%S') + self.local_timezone) def test_date_utc(self): d = datetime.date.today() # Convert `date` to `datetime`, since `date` ignores seconds and hours # in timedeltas: # >>> datetime.date(2008, 9, 7) + datetime.timedelta(hours=23) # datetime.date(2008, 9, 7) d_utc = datetime.datetime(*d.timetuple()[:3]) + self.local_utcoffset self.assertEqual(rfc3339(d, utc=True), d_utc.strftime('%Y-%m-%dT%H:%M:%SZ')) def test_timestamp(self): d = time.time() self.assertEqual(rfc3339(d), datetime.datetime.fromtimestamp(d).\ strftime('%Y-%m-%dT%H:%M:%S') + self.local_timezone) def test_timestamp_utc(self): d = time.time() d_utc = datetime.datetime.utcfromtimestamp(d) + self.local_utcoffset self.assertEqual(rfc3339(d), (d_utc.strftime('%Y-%m-%dT%H:%M:%S') + self.local_timezone)) if __name__ == '__main__': import doctest doctest.testmod() unittest.main() �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������./weblog+jinja-1.1/weblog/utf8_html_parser.py�������������������������������������������������������0100644�0001750�0000000�00000004370�11063022145�0020232�0����������������������������������������������������������������������������������������������������ustar�00henry���������������������������wheel������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������from cgi import escape from HTMLParser import HTMLParser class UTF8HTMLParser(HTMLParser): ''' Parse a HTML document and convert all nodes to UTF-8:: >>> parser = UTF8HTMLParser() >>> parser.feed("<p>Hello <a href='crazy'>world</a></p>") >>> parser.get_value() u"<p>Hello <a href='crazy'>world</a></p>" >>> parser.feed('<p>Another sentence.</p>') >>> parser.get_value() u"<p>Hello <a href='crazy'>world</a></p><p>Another sentence.</p>" `reset()` resets the parser:: >>> parser.reset() >>> parser.get_value() u'' ''' def __init__(self): HTMLParser.__init__(self) self.output = list() def reset(self): HTMLParser.reset(self) self.output = list() def get_value(self): return u''.join(self.output) @staticmethod def html_attrs(attrs): ''' >>> UTF8HTMLParser.html_attrs((('src', 'pic.jpg'), ('alt', 'pic'))) u"src='pic.jpg' alt='pic'" >>> UTF8HTMLParser.html_attrs(list()) u'' >>> UTF8HTMLParser.html_attrs((('href', 'sample?foo=1&bar=2'),)) u"href='sample?foo=1&bar=2'" ''' # HTMLParser unescape attributes values, we don't want that. return u' '.join(u'%s=\'%s\'' % (k, escape(v)) for k, v in attrs) def handle_starttag(self, tag, attrs): if attrs: self.output.append(u'<%s %s>' % (tag, self.html_attrs(attrs))) else: self.output.append(u'<%s>' % tag) def handle_startendtag(self, tag, attrs): self.handle_starttag(tag, attrs) def handle_endtag(self, tag): self.output.append(u'</%s>' % tag) def handle_data(self, data): self.output.append(data) def handle_charref(self, name): self.output.append(u'&#%s;' % name) def handle_entityref(self, name): self.output.append(u'&%s;' % name) def handle_comment(self, comment): self.output.append(u'<!-- %s -->' % comment) def handle_decl(self, decl): self.output.append(u'<!%s>' % decl) def handle_pi(self, pi): self.output.append(u'<?%s>' % pi) if __name__ == '__main__': import doctest doctest.testmod() ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������./weblog+jinja-1.1/weblog/utils.py������������������������������������������������������������������0100644�0001750�0000000�00000003075�11063022145�0016105�0����������������������������������������������������������������������������������������������������ustar�00henry���������������������������wheel������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������import os import logging import datetime from cgi import escape 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 format_date(date): ''' Return a string representing a ``date`` or a ``datetime``. >>> format_date(datetime.datetime(2008, 1, 1, 20, 40, 23, 345)) '2008-01-01 20:40:23' >>> format_date(datetime.datetime(2008, 1, 1)) '2008-01-01 00:00:00' >>> format_date(datetime.date(2008, 1, 1)) '2008-01-01' >>> format_date(datetime.time()) Traceback (most recent call last): ... TypeError: expected date or datetime, got time instead ''' if isinstance(date, datetime.datetime): return date.strftime('%Y-%m-%d %H:%M:%S') elif isinstance(date, datetime.date): return str(date) else: raise TypeError('expected %s or %s, got %s instead' % (datetime.date.__name__, datetime.datetime.__name__, date.__class__.__name__)) if __name__ == '__main__': import doctest doctest.testmod() �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������./weblog+jinja-1.1/jinja2���������������������������������������������������������������������������0040755�0001750�0000000�00000000000�11063003705�0014211�5����������������������������������������������������������������������������������������������������ustar�00henry���������������������������wheel������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������./weblog+jinja-1.1/jinja2/visitor.py����������������������������������������������������������������0100644�0001750�0000000�00000006372�11063022145�0016345�0����������������������������������������������������������������������������������������������������ustar�00henry���������������������������wheel������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������# -*- coding: utf-8 -*- """ jinja2.visitor ~~~~~~~~~~~~~~ This module implements a visitor for the nodes. :copyright: Copyright 2008 by Armin Ronacher. :license: BSD. """ from jinja2.nodes import Node class NodeVisitor(object): """Walks the abstract syntax tree and call visitor functions for every node found. The visitor functions may return values which will be forwarded by the `visit` method. Per default the visitor functions for the nodes are ``'visit_'`` + class name of the node. So a `TryFinally` node visit function would be `visit_TryFinally`. This behavior can be changed by overriding the `get_visitor` function. If no visitor function exists for a node (return value `None`) the `generic_visit` visitor is used instead. """ def get_visitor(self, node): """Return the visitor function for this node or `None` if no visitor exists for this node. In that case the generic visit function is used instead. """ method = 'visit_' + node.__class__.__name__ return getattr(self, method, None) def visit(self, node, *args, **kwargs): """Visit a node.""" f = self.get_visitor(node) if f is not None: return f(node, *args, **kwargs) return self.generic_visit(node, *args, **kwargs) def generic_visit(self, node, *args, **kwargs): """Called if no explicit visitor function exists for a node.""" for node in node.iter_child_nodes(): self.visit(node, *args, **kwargs) class NodeTransformer(NodeVisitor): """Walks the abstract syntax tree and allows modifications of nodes. The `NodeTransformer` will walk the AST and use the return value of the visitor functions to replace or remove the old node. If the return value of the visitor function is `None` the node will be removed from the previous location otherwise it's replaced with the return value. The return value may be the original node in which case no replacement takes place. """ def generic_visit(self, node, *args, **kwargs): for field, old_value in node.iter_fields(): if isinstance(old_value, list): new_values = [] for value in old_value: if isinstance(value, Node): value = self.visit(value, *args, **kwargs) if value is None: continue elif not isinstance(value, Node): new_values.extend(value) continue new_values.append(value) old_value[:] = new_values elif isinstance(old_value, Node): new_node = self.visit(old_value, *args, **kwargs) if new_node is None: delattr(node, field) else: setattr(node, field, new_node) return node def visit_list(self, node, *args, **kwargs): """As transformers may return lists in some places this method can be used to enforce a list as return value. """ rv = self.visit(node, *args, **kwargs) if not isinstance(rv, list): rv = [rv] return rv ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������./weblog+jinja-1.1/jinja2/_speedups.c���������������������������������������������������������������0100644�0001750�0000000�00000013261�11063022145�0016422�0����������������������������������������������������������������������������������������������������ustar�00henry���������������������������wheel������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������/** * jinja2._speedups * ~~~~~~~~~~~~~~~~ * * This module implements functions for automatic escaping in C for better * performance. Additionally it defines a `tb_set_next` function to patch the * debug traceback. If the speedups module is not compiled a ctypes * implementation of `tb_set_next` and Python implementations of the other * functions are used. * * :copyright: 2008 by Armin Ronacher, Mickaël Guérin. * :license: BSD. */ #include <Python.h> #define ESCAPED_CHARS_TABLE_SIZE 63 #define UNICHR(x) (((PyUnicodeObject*)PyUnicode_DecodeASCII(x, strlen(x), NULL))->str); #if PY_VERSION_HEX < 0x02050000 && !defined(PY_SSIZE_T_MIN) typedef int Py_ssize_t; #define PY_SSIZE_T_MAX INT_MAX #define PY_SSIZE_T_MIN INT_MIN #endif static PyObject* markup; static Py_ssize_t escaped_chars_delta_len[ESCAPED_CHARS_TABLE_SIZE]; static Py_UNICODE *escaped_chars_repl[ESCAPED_CHARS_TABLE_SIZE]; static int init_constants(void) { PyObject *module; /* happing of characters to replace */ escaped_chars_repl['"'] = UNICHR("""); escaped_chars_repl['\''] = UNICHR("'"); escaped_chars_repl['&'] = UNICHR("&"); escaped_chars_repl['<'] = UNICHR("<"); escaped_chars_repl['>'] = UNICHR(">"); /* lengths of those characters when replaced - 1 */ memset(escaped_chars_delta_len, 0, sizeof (escaped_chars_delta_len)); escaped_chars_delta_len['"'] = escaped_chars_delta_len['\''] = \ escaped_chars_delta_len['&'] = 4; escaped_chars_delta_len['<'] = escaped_chars_delta_len['>'] = 3; /* import markup type so that we can mark the return value */ module = PyImport_ImportModule("jinja2.utils"); if (!module) return 0; markup = PyObject_GetAttrString(module, "Markup"); Py_DECREF(module); return 1; } static PyObject* escape_unicode(PyUnicodeObject *in) { PyUnicodeObject *out; Py_UNICODE *inp = in->str; const Py_UNICODE *inp_end = in->str + in->length; Py_UNICODE *next_escp; Py_UNICODE *outp; Py_ssize_t delta=0, erepl=0, delta_len=0; /* First we need to figure out how long the escaped string will be */ while (*(inp) || inp < inp_end) { if (*inp < ESCAPED_CHARS_TABLE_SIZE && escaped_chars_delta_len[*inp]) { delta += escaped_chars_delta_len[*inp]; ++erepl; } ++inp; } /* Do we need to escape anything at all? */ if (!erepl) { Py_INCREF(in); return (PyObject*)in; } out = (PyUnicodeObject*)PyUnicode_FromUnicode(NULL, in->length + delta); if (!out) return NULL; outp = out->str; inp = in->str; while (erepl-- > 0) { /* look for the next substitution */ next_escp = inp; while (next_escp < inp_end) { if (*next_escp < ESCAPED_CHARS_TABLE_SIZE && (delta_len = escaped_chars_delta_len[*next_escp])) { ++delta_len; break; } ++next_escp; } if (next_escp > inp) { /* copy unescaped chars between inp and next_escp */ Py_UNICODE_COPY(outp, inp, next_escp-inp); outp += next_escp - inp; } /* escape 'next_escp' */ Py_UNICODE_COPY(outp, escaped_chars_repl[*next_escp], delta_len); outp += delta_len; inp = next_escp + 1; } if (inp < inp_end) Py_UNICODE_COPY(outp, inp, in->length - (inp - in->str)); return (PyObject*)out; } static PyObject* escape(PyObject *self, PyObject *text) { PyObject *s = NULL, *rv = NULL, *html; /* we don't have to escape integers, bools or floats */ if (PyInt_CheckExact(text) || PyLong_CheckExact(text) || PyFloat_CheckExact(text) || PyBool_Check(text) || text == Py_None) return PyObject_CallFunctionObjArgs(markup, text, NULL); /* if the object has an __html__ method that performs the escaping */ html = PyObject_GetAttrString(text, "__html__"); if (html) { rv = PyObject_CallObject(html, NULL); Py_DECREF(html); return rv; } /* otherwise make the object unicode if it isn't, then escape */ PyErr_Clear(); if (!PyUnicode_Check(text)) { PyObject *unicode = PyObject_Unicode(text); if (!unicode) return NULL; s = escape_unicode((PyUnicodeObject*)unicode); Py_DECREF(unicode); } else s = escape_unicode((PyUnicodeObject*)text); /* convert the unicode string into a markup object. */ rv = PyObject_CallFunctionObjArgs(markup, (PyObject*)s, NULL); Py_DECREF(s); return rv; } static PyObject* soft_unicode(PyObject *self, PyObject *s) { if (!PyUnicode_Check(s)) return PyObject_Unicode(s); Py_INCREF(s); return s; } 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[] = { {"escape", (PyCFunction)escape, METH_O, "escape(s) -> markup\n\n" "Convert the characters &, <, >, ', and \" in string s to HTML-safe\n" "sequences. Use this if you need to display text that might contain\n" "such characters in HTML. Marks return value as markup string."}, {"soft_unicode", (PyCFunction)soft_unicode, METH_O, "soft_unicode(object) -> string\n\n" "Make a string unicode if it isn't already. That way a markup\n" "string is not converted back to unicode."}, {"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_speedups(void) { if (!init_constants()) return; Py_InitModule3("jinja2._speedups", module_methods, ""); } �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������./weblog+jinja-1.1/jinja2/compiler.py���������������������������������������������������������������0100644�0001750�0000000�00000145462�11063022145�0016464�0����������������������������������������������������������������������������������������������������ustar�00henry���������������������������wheel������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������# -*- coding: utf-8 -*- """ jinja2.compiler ~~~~~~~~~~~~~~~ Compiles nodes into python code. :copyright: Copyright 2008 by Armin Ronacher. :license: BSD. """ from copy import copy from keyword import iskeyword from cStringIO import StringIO from itertools import chain from jinja2 import nodes from jinja2.visitor import NodeVisitor, NodeTransformer from jinja2.exceptions import TemplateAssertionError from jinja2.utils import Markup, concat, escape operators = { 'eq': '==', 'ne': '!=', 'gt': '>', 'gteq': '>=', 'lt': '<', 'lteq': '<=', 'in': 'in', 'notin': 'not in' } try: exec '(0 if 0 else 0)' except SyntaxError: have_condexpr = False else: have_condexpr = True def generate(node, environment, name, filename, stream=None): """Generate the python source for a node tree.""" if not isinstance(node, nodes.Template): raise TypeError('Can\'t compile non template nodes') generator = CodeGenerator(environment, name, filename, stream) generator.visit(node) if stream is None: return generator.stream.getvalue() def has_safe_repr(value): """Does the node have a safe representation?""" if value is None or value is NotImplemented or value is Ellipsis: return True if isinstance(value, (bool, int, long, float, complex, basestring, xrange, Markup)): return True if isinstance(value, (tuple, list, set, frozenset)): for item in value: if not has_safe_repr(item): return False return True elif isinstance(value, dict): for key, value in value.iteritems(): if not has_safe_repr(key): return False if not has_safe_repr(value): return False return True return False def find_undeclared(nodes, names): """Check if the names passed are accessed undeclared. The return value is a set of all the undeclared names from the sequence of names found. """ visitor = UndeclaredNameVisitor(names) try: for node in nodes: visitor.visit(node) except VisitorExit: pass return visitor.undeclared class Identifiers(object): """Tracks the status of identifiers in frames.""" def __init__(self): # variables that are known to be declared (probably from outer # frames or because they are special for the frame) self.declared = set() # undeclared variables from outer scopes self.outer_undeclared = set() # names that are accessed without being explicitly declared by # this one or any of the outer scopes. Names can appear both in # declared and undeclared. self.undeclared = set() # names that are declared locally self.declared_locally = set() # names that are declared by parameters self.declared_parameter = set() def add_special(self, name): """Register a special name like `loop`.""" self.undeclared.discard(name) self.declared.add(name) def is_declared(self, name, local_only=False): """Check if a name is declared in this or an outer scope.""" if name in self.declared_locally or name in self.declared_parameter: return True if local_only: return False return name in self.declared def find_shadowed(self): """Find all the shadowed names.""" return (self.declared | self.outer_undeclared) & \ (self.declared_locally | self.declared_parameter) class Frame(object): """Holds compile time information for us.""" def __init__(self, parent=None): self.identifiers = Identifiers() # a toplevel frame is the root + soft frames such as if conditions. self.toplevel = False # the root frame is basically just the outermost frame, so no if # conditions. This information is used to optimize inheritance # situations. self.rootlevel = False # inside some tags we are using a buffer rather than yield statements. # this for example affects {% filter %} or {% macro %}. If a frame # is buffered this variable points to the name of the list used as # buffer. self.buffer = None # the name of the block we're in, otherwise None. self.block = parent and parent.block or None # the parent of this frame self.parent = parent if parent is not None: self.identifiers.declared.update( parent.identifiers.declared | parent.identifiers.declared_locally | parent.identifiers.declared_parameter | parent.identifiers.undeclared ) self.identifiers.outer_undeclared.update( parent.identifiers.undeclared - self.identifiers.declared ) self.buffer = parent.buffer def copy(self): """Create a copy of the current one.""" rv = copy(self) rv.identifiers = copy(self.identifiers) return rv def inspect(self, nodes, hard_scope=False): """Walk the node and check for identifiers. If the scope is hard (eg: enforce on a python level) overrides from outer scopes are tracked differently. """ visitor = FrameIdentifierVisitor(self.identifiers, hard_scope) for node in nodes: visitor.visit(node) def inner(self): """Return an inner frame.""" return Frame(self) def soft(self): """Return a soft frame. A soft frame may not be modified as standalone thing as it shares the resources with the frame it was created of, but it's not a rootlevel frame any longer. """ rv = copy(self) rv.rootlevel = False return rv class VisitorExit(RuntimeError): """Exception used by the `UndeclaredNameVisitor` to signal a stop.""" class DependencyFinderVisitor(NodeVisitor): """A visitor that collects filter and test calls.""" def __init__(self): self.filters = set() self.tests = set() def visit_Filter(self, node): self.generic_visit(node) self.filters.add(node.name) def visit_Test(self, node): self.generic_visit(node) self.tests.add(node.name) def visit_Block(self, node): """Stop visiting at blocks.""" class UndeclaredNameVisitor(NodeVisitor): """A visitor that checks if a name is accessed without being declared. This is different from the frame visitor as it will not stop at closure frames. """ def __init__(self, names): self.names = set(names) self.undeclared = set() def visit_Name(self, node): if node.ctx == 'load' and node.name in self.names: self.undeclared.add(node.name) if self.undeclared == self.names: raise VisitorExit() else: self.names.discard(node.name) def visit_Block(self, node): """Stop visiting a blocks.""" class FrameIdentifierVisitor(NodeVisitor): """A visitor for `Frame.inspect`.""" def __init__(self, identifiers, hard_scope): self.identifiers = identifiers self.hard_scope = hard_scope def visit_Name(self, node): """All assignments to names go through this function.""" if node.ctx == 'store': self.identifiers.declared_locally.add(node.name) elif node.ctx == 'param': self.identifiers.declared_parameter.add(node.name) elif node.ctx == 'load' and not \ self.identifiers.is_declared(node.name, self.hard_scope): self.identifiers.undeclared.add(node.name) def visit_Macro(self, node): self.identifiers.declared_locally.add(node.name) def visit_Import(self, node): self.generic_visit(node) self.identifiers.declared_locally.add(node.target) def visit_FromImport(self, node): self.generic_visit(node) for name in node.names: if isinstance(name, tuple): self.identifiers.declared_locally.add(name[1]) else: self.identifiers.declared_locally.add(name) def visit_Assign(self, node): """Visit assignments in the correct order.""" self.visit(node.node) self.visit(node.target) def visit_For(self, node): """Visiting stops at for blocks. However the block sequence is visited as part of the outer scope. """ self.visit(node.iter) def visit_CallBlock(self, node): for child in node.iter_child_nodes(exclude=('body',)): self.visit(child) def visit_FilterBlock(self, node): self.visit(node.filter) def visit_Block(self, node): """Stop visiting at blocks.""" class CompilerExit(Exception): """Raised if the compiler encountered a situation where it just doesn't make sense to further process the code. Any block that raises such an exception is not further processed. """ class CodeGenerator(NodeVisitor): def __init__(self, environment, name, filename, stream=None): if stream is None: stream = StringIO() self.environment = environment self.name = name self.filename = filename self.stream = stream # aliases for imports self.import_aliases = {} # a registry for all blocks. Because blocks are moved out # into the global python scope they are registered here self.blocks = {} # the number of extends statements so far self.extends_so_far = 0 # some templates have a rootlevel extends. In this case we # can safely assume that we're a child template and do some # more optimizations. self.has_known_extends = False # the current line number self.code_lineno = 1 # registry of all filters and tests (global, not block local) self.tests = {} self.filters = {} # the debug information self.debug_info = [] self._write_debug_info = None # the number of new lines before the next write() self._new_lines = 0 # the line number of the last written statement self._last_line = 0 # true if nothing was written so far. self._first_write = True # used by the `temporary_identifier` method to get new # unique, temporary identifier self._last_identifier = 0 # the current indentation self._indentation = 0 # -- Various compilation helpers def fail(self, msg, lineno): """Fail with a `TemplateAssertionError`.""" raise TemplateAssertionError(msg, lineno, self.name, self.filename) def temporary_identifier(self): """Get a new unique identifier.""" self._last_identifier += 1 return 't_%d' % self._last_identifier def buffer(self, frame): """Enable buffering for the frame from that point onwards.""" frame.buffer = self.temporary_identifier() self.writeline('%s = []' % frame.buffer) def return_buffer_contents(self, frame): """Return the buffer contents of the frame.""" if self.environment.autoescape: self.writeline('return Markup(concat(%s))' % frame.buffer) else: self.writeline('return concat(%s)' % frame.buffer) def indent(self): """Indent by one.""" self._indentation += 1 def outdent(self, step=1): """Outdent by step.""" self._indentation -= step def start_write(self, frame, node=None): """Yield or write into the frame buffer.""" if frame.buffer is None: self.writeline('yield ', node) else: self.writeline('%s.append(' % frame.buffer, node) def end_write(self, frame): """End the writing process started by `start_write`.""" if frame.buffer is not None: self.write(')') def simple_write(self, s, frame, node=None): """Simple shortcut for start_write + write + end_write.""" self.start_write(frame, node) self.write(s) self.end_write(frame) def blockvisit(self, nodes, frame, force_generator=True): """Visit a list of nodes as block in a frame. If the current frame is no buffer a dummy ``if 0: yield None`` is written automatically unless the force_generator parameter is set to False. """ if frame.buffer is None and force_generator: self.writeline('if 0: yield None') try: for node in nodes: self.visit(node, frame) except CompilerExit: pass def write(self, x): """Write a string into the output stream.""" if self._new_lines: if not self._first_write: self.stream.write('\n' * self._new_lines) self.code_lineno += self._new_lines if self._write_debug_info is not None: self.debug_info.append((self._write_debug_info, self.code_lineno)) self._write_debug_info = None self._first_write = False self.stream.write(' ' * self._indentation) self._new_lines = 0 self.stream.write(x) def writeline(self, x, node=None, extra=0): """Combination of newline and write.""" self.newline(node, extra) self.write(x) def newline(self, node=None, extra=0): """Add one or more newlines before the next write.""" self._new_lines = max(self._new_lines, 1 + extra) if node is not None and node.lineno != self._last_line: self._write_debug_info = node.lineno self._last_line = node.lineno def signature(self, node, frame, extra_kwargs=None): """Writes a function call to the stream for the current node. A leading comma is added automatically. The extra keyword arguments may not include python keywords otherwise a syntax error could occour. The extra keyword arguments should be given as python dict. """ # if any of the given keyword arguments is a python keyword # we have to make sure that no invalid call is created. kwarg_workaround = False for kwarg in chain((x.key for x in node.kwargs), extra_kwargs or ()): if iskeyword(kwarg): kwarg_workaround = True break for arg in node.args: self.write(', ') self.visit(arg, frame) if not kwarg_workaround: for kwarg in node.kwargs: self.write(', ') self.visit(kwarg, frame) if extra_kwargs is not None: for key, value in extra_kwargs.iteritems(): self.write(', %s=%s' % (key, value)) if node.dyn_args: self.write(', *') self.visit(node.dyn_args, frame) if kwarg_workaround: if node.dyn_kwargs is not None: self.write(', **dict({') else: self.write(', **{') for kwarg in node.kwargs: self.write('%r: ' % kwarg.key) self.visit(kwarg.value, frame) self.write(', ') if extra_kwargs is not None: for key, value in extra_kwargs.iteritems(): self.write('%r: %s, ' % (key, value)) if node.dyn_kwargs is not None: self.write('}, **') self.visit(node.dyn_kwargs, frame) self.write(')') else: self.write('}') elif node.dyn_kwargs is not None: self.write(', **') self.visit(node.dyn_kwargs, frame) def pull_locals(self, frame): """Pull all the references identifiers into the local scope.""" for name in frame.identifiers.undeclared: self.writeline('l_%s = context.resolve(%r)' % (name, name)) def pull_dependencies(self, nodes): """Pull all the dependencies.""" visitor = DependencyFinderVisitor() for node in nodes: visitor.visit(node) for dependency in 'filters', 'tests': mapping = getattr(self, dependency) for name in getattr(visitor, dependency): if name not in mapping: mapping[name] = self.temporary_identifier() self.writeline('%s = environment.%s[%r]' % (mapping[name], dependency, name)) def collect_shadowed(self, frame): """This function returns all the shadowed variables in a dict in the form name: alias and will write the required assignments into the current scope. No indentation takes place. """ aliases = {} for name in frame.identifiers.find_shadowed(): aliases[name] = ident = self.temporary_identifier() self.writeline('%s = l_%s' % (ident, name)) return aliases def restore_shadowed(self, aliases): """Restore all aliases.""" for name, alias in aliases.iteritems(): self.writeline('l_%s = %s' % (name, alias)) def function_scoping(self, node, frame, children=None, find_special=True): """In Jinja a few statements require the help of anonymous functions. Those are currently macros and call blocks and in the future also recursive loops. As there is currently technical limitation that doesn't allow reading and writing a variable in a scope where the initial value is coming from an outer scope, this function tries to fall back with a common error message. Additionally the frame passed is modified so that the argumetns are collected and callers are looked up. This will return the modified frame. """ # we have to iterate twice over it, make sure that works if children is None: children = node.iter_child_nodes() children = list(children) func_frame = frame.inner() func_frame.inspect(children, hard_scope=True) # variables that are undeclared (accessed before declaration) and # declared locally *and* part of an outside scope raise a template # assertion error. Reason: we can't generate reasonable code from # it without aliasing all the variables. XXX: alias them ^^ overriden_closure_vars = ( func_frame.identifiers.undeclared & func_frame.identifiers.declared & (func_frame.identifiers.declared_locally | func_frame.identifiers.declared_parameter) ) if overriden_closure_vars: self.fail('It\'s not possible to set and access variables ' 'derived from an outer scope! (affects: %s' % ', '.join(sorted(overriden_closure_vars)), node.lineno) # remove variables from a closure from the frame's undeclared # identifiers. func_frame.identifiers.undeclared -= ( func_frame.identifiers.undeclared & func_frame.identifiers.declared ) # no special variables for this scope, abort early if not find_special: return func_frame func_frame.accesses_kwargs = False func_frame.accesses_varargs = False func_frame.accesses_caller = False func_frame.arguments = args = ['l_' + x.name for x in node.args] undeclared = find_undeclared(children, ('caller', 'kwargs', 'varargs')) if 'caller' in undeclared: func_frame.accesses_caller = True func_frame.identifiers.add_special('caller') args.append('l_caller') if 'kwargs' in undeclared: func_frame.accesses_kwargs = True func_frame.identifiers.add_special('kwargs') args.append('l_kwargs') if 'varargs' in undeclared: func_frame.accesses_varargs = True func_frame.identifiers.add_special('varargs') args.append('l_varargs') return func_frame def macro_body(self, node, frame, children=None): """Dump the function def of a macro or call block.""" frame = self.function_scoping(node, frame, children) args = frame.arguments self.writeline('def macro(%s):' % ', '.join(args), node) self.indent() self.buffer(frame) self.pull_locals(frame) self.blockvisit(node.body, frame) self.return_buffer_contents(frame) self.outdent() return frame def macro_def(self, node, frame): """Dump the macro definition for the def created by macro_body.""" arg_tuple = ', '.join(repr(x.name) for x in node.args) name = getattr(node, 'name', None) if len(node.args) == 1: arg_tuple += ',' self.write('Macro(environment, macro, %r, (%s), (' % (name, arg_tuple)) for arg in node.defaults: self.visit(arg, frame) self.write(', ') self.write('), %r, %r, %r)' % ( bool(frame.accesses_kwargs), bool(frame.accesses_varargs), bool(frame.accesses_caller) )) def position(self, node): """Return a human readable position for the node.""" rv = 'line %d' % node.lineno if self.name is not None: rv += ' in' + repr(self.name) return rv # -- Statement Visitors def visit_Template(self, node, frame=None): assert frame is None, 'no root frame allowed' from jinja2.runtime import __all__ as exported self.writeline('from __future__ import division') self.writeline('from jinja2.runtime import ' + ', '.join(exported)) # do we have an extends tag at all? If not, we can save some # overhead by just not processing any inheritance code. have_extends = node.find(nodes.Extends) is not None # find all blocks for block in node.find_all(nodes.Block): if block.name in self.blocks: self.fail('block %r defined twice' % block.name, block.lineno) self.blocks[block.name] = block # find all imports and import them for import_ in node.find_all(nodes.ImportedName): if import_.importname not in self.import_aliases: imp = import_.importname self.import_aliases[imp] = alias = self.temporary_identifier() if '.' in imp: module, obj = imp.rsplit('.', 1) self.writeline('from %s import %s as %s' % (module, obj, alias)) else: self.writeline('import %s as %s' % (imp, alias)) # add the load name self.writeline('name = %r' % self.name) # generate the root render function. self.writeline('def root(context, environment=environment):', extra=1) # process the root frame = Frame() frame.inspect(node.body) frame.toplevel = frame.rootlevel = True self.indent() if have_extends: self.writeline('parent_template = None') if 'self' in find_undeclared(node.body, ('self',)): frame.identifiers.add_special('self') self.writeline('l_self = TemplateReference(context)') self.pull_locals(frame) self.pull_dependencies(node.body) self.blockvisit(node.body, frame) self.outdent() # make sure that the parent root is called. if have_extends: if not self.has_known_extends: self.indent() self.writeline('if parent_template is not None:') self.indent() self.writeline('for event in parent_template.' 'root_render_func(context):') self.indent() self.writeline('yield event') self.outdent(2 + (not self.has_known_extends)) # at this point we now have the blocks collected and can visit them too. for name, block in self.blocks.iteritems(): block_frame = Frame() block_frame.inspect(block.body) block_frame.block = name self.writeline('def block_%s(context, environment=environment):' % name, block, 1) self.indent() undeclared = find_undeclared(block.body, ('self', 'super')) if 'self' in undeclared: block_frame.identifiers.add_special('self') self.writeline('l_self = TemplateReference(context)') if 'super' in undeclared: block_frame.identifiers.add_special('super') self.writeline('l_super = context.super(%r, ' 'block_%s)' % (name, name)) self.pull_locals(block_frame) self.pull_dependencies(block.body) self.blockvisit(block.body, block_frame) self.outdent() self.writeline('blocks = {%s}' % ', '.join('%r: block_%s' % (x, x) for x in self.blocks), extra=1) # add a function that returns the debug info self.writeline('debug_info = %r' % '&'.join('%s=%s' % x for x in self.debug_info)) def visit_Block(self, node, frame): """Call a block and register it for the template.""" level = 1 if frame.toplevel: # if we know that we are a child template, there is no need to # check if we are one if self.has_known_extends: return if self.extends_so_far > 0: self.writeline('if parent_template is None:') self.indent() level += 1 self.writeline('for event in context.blocks[%r][0](context):' % node.name, node) self.indent() self.simple_write('event', frame) self.outdent(level) def visit_Extends(self, node, frame): """Calls the extender.""" if not frame.toplevel: self.fail('cannot use extend from a non top-level scope', node.lineno) # if the number of extends statements in general is zero so # far, we don't have to add a check if something extended # the template before this one. if self.extends_so_far > 0: # if we have a known extends we just add a template runtime # error into the generated code. We could catch that at compile # time too, but i welcome it not to confuse users by throwing the # same error at different times just "because we can". if not self.has_known_extends: self.writeline('if parent_template is not None:') self.indent() self.writeline('raise TemplateRuntimeError(%r)' % 'extended multiple times') # if we have a known extends already we don't need that code here # as we know that the template execution will end here. if self.has_known_extends: raise CompilerExit() self.outdent() self.writeline('parent_template = environment.get_template(', node) self.visit(node.template, frame) self.write(', %r)' % self.name) self.writeline('for name, parent_block in parent_template.' 'blocks.iteritems():') self.indent() self.writeline('context.blocks.setdefault(name, []).' 'append(parent_block)') self.outdent() # if this extends statement was in the root level we can take # advantage of that information and simplify the generated code # in the top level from this point onwards if frame.rootlevel: self.has_known_extends = True # and now we have one more self.extends_so_far += 1 def visit_Include(self, node, frame): """Handles includes.""" if node.with_context: self.writeline('template = environment.get_template(', node) self.visit(node.template, frame) self.write(', %r)' % self.name) self.writeline('for event in template.root_render_func(' 'template.new_context(context.parent, True)):') else: self.writeline('for event in environment.get_template(', node) self.visit(node.template, frame) self.write(', %r).module._body_stream:' % self.name) self.indent() self.simple_write('event', frame) self.outdent() def visit_Import(self, node, frame): """Visit regular imports.""" self.writeline('l_%s = ' % node.target, node) if frame.toplevel: self.write('context.vars[%r] = ' % node.target) self.write('environment.get_template(') self.visit(node.template, frame) self.write(', %r).' % self.name) if node.with_context: self.write('make_module(context.parent, True)') else: self.write('module') if frame.toplevel and not node.target.startswith('_'): self.writeline('context.exported_vars.discard(%r)' % node.target) def visit_FromImport(self, node, frame): """Visit named imports.""" self.newline(node) self.write('included_template = environment.get_template(') self.visit(node.template, frame) self.write(', %r).' % self.name) if node.with_context: self.write('make_module(context.parent, True)') else: self.write('module') var_names = [] discarded_names = [] for name in node.names: if isinstance(name, tuple): name, alias = name else: alias = name self.writeline('l_%s = getattr(included_template, ' '%r, missing)' % (alias, name)) self.writeline('if l_%s is missing:' % alias) self.indent() self.writeline('l_%s = environment.undefined(%r %% ' 'included_template.__name__, ' 'name=%r)' % (alias, 'the template %%r (imported on %s) does ' 'not export the requested name %s' % ( self.position(node), repr(name) ), name)) self.outdent() if frame.toplevel: var_names.append(alias) if not alias.startswith('_'): discarded_names.append(alias) if var_names: if len(var_names) == 1: name = var_names[0] self.writeline('context.vars[%r] = l_%s' % (name, name)) else: self.writeline('context.vars.update({%s})' % ', '.join( '%r: l_%s' % (name, name) for name in var_names )) if discarded_names: if len(discarded_names) == 1: self.writeline('context.exported_vars.discard(%r)' % discarded_names[0]) else: self.writeline('context.exported_vars.difference_' 'update((%s))' % ', '.join(map(repr, discarded_names))) def visit_For(self, node, frame): # when calculating the nodes for the inner frame we have to exclude # the iterator contents from it children = node.iter_child_nodes(exclude=('iter',)) if node.recursive: loop_frame = self.function_scoping(node, frame, children, find_special=False) else: loop_frame = frame.inner() loop_frame.inspect(children) # try to figure out if we have an extended loop. An extended loop # is necessary if the loop is in recursive mode if the special loop # variable is accessed in the body. extended_loop = node.recursive or 'loop' in \ find_undeclared(node.iter_child_nodes( only=('body',)), ('loop',)) # make sure the loop variable is a special one and raise a template # assertion error if a loop tries to write to loop loop_frame.identifiers.add_special('loop') for name in node.find_all(nodes.Name): if name.ctx == 'store' and name.name == 'loop': self.fail('Can\'t assign to special loop variable ' 'in for-loop target', name.lineno) # if we don't have an recursive loop we have to find the shadowed # variables at that point if not node.recursive: aliases = self.collect_shadowed(loop_frame) # otherwise we set up a buffer and add a function def else: self.writeline('def loop(reciter, loop_render_func):', node) self.indent() self.buffer(loop_frame) aliases = {} self.pull_locals(loop_frame) if node.else_: iteration_indicator = self.temporary_identifier() self.writeline('%s = 1' % iteration_indicator) # Create a fake parent loop if the else or test section of a # loop is accessing the special loop variable and no parent loop # exists. if 'loop' not in aliases and 'loop' in find_undeclared( node.iter_child_nodes(only=('else_', 'test')), ('loop',)): self.writeline("l_loop = environment.undefined(%r, name='loop')" % ("'loop' is undefined. the filter section of a loop as well " "as the else block doesn't have access to the special 'loop'" " variable of the current loop. Because there is no parent " "loop it's undefined. Happened in loop on %s" % self.position(node))) self.writeline('for ', node) self.visit(node.target, loop_frame) self.write(extended_loop and ', l_loop in LoopContext(' or ' in ') # if we have an extened loop and a node test, we filter in the # "outer frame". if extended_loop and node.test is not None: self.write('(') self.visit(node.target, loop_frame) self.write(' for ') self.visit(node.target, loop_frame) self.write(' in ') if node.recursive: self.write('reciter') else: self.visit(node.iter, loop_frame) self.write(' if (') test_frame = loop_frame.copy() self.visit(node.test, test_frame) self.write('))') elif node.recursive: self.write('reciter') else: self.visit(node.iter, loop_frame) if node.recursive: self.write(', recurse=loop_render_func):') else: self.write(extended_loop and '):' or ':') # tests in not extended loops become a continue if not extended_loop and node.test is not None: self.indent() self.writeline('if not ') self.visit(node.test, loop_frame) self.write(':') self.indent() self.writeline('continue') self.outdent(2) self.indent() self.blockvisit(node.body, loop_frame, force_generator=True) if node.else_: self.writeline('%s = 0' % iteration_indicator) self.outdent() if node.else_: self.writeline('if %s:' % iteration_indicator) self.indent() self.blockvisit(node.else_, loop_frame, force_generator=False) self.outdent() # reset the aliases if there are any. self.restore_shadowed(aliases) # if the node was recursive we have to return the buffer contents # and start the iteration code if node.recursive: self.return_buffer_contents(loop_frame) self.outdent() self.start_write(frame, node) self.write('loop(') self.visit(node.iter, frame) self.write(', loop)') self.end_write(frame) def visit_If(self, node, frame): if_frame = frame.soft() self.writeline('if ', node) self.visit(node.test, if_frame) self.write(':') self.indent() self.blockvisit(node.body, if_frame) self.outdent() if node.else_: self.writeline('else:') self.indent() self.blockvisit(node.else_, if_frame) self.outdent() def visit_Macro(self, node, frame): macro_frame = self.macro_body(node, frame) self.newline() if frame.toplevel: if not node.name.startswith('_'): self.write('context.exported_vars.add(%r)' % node.name) self.writeline('context.vars[%r] = ' % node.name) self.write('l_%s = ' % node.name) self.macro_def(node, macro_frame) def visit_CallBlock(self, node, frame): children = node.iter_child_nodes(exclude=('call',)) call_frame = self.macro_body(node, frame, children) self.writeline('caller = ') self.macro_def(node, call_frame) self.start_write(frame, node) self.visit_Call(node.call, call_frame, forward_caller=True) self.end_write(frame) def visit_FilterBlock(self, node, frame): filter_frame = frame.inner() filter_frame.inspect(node.iter_child_nodes()) aliases = self.collect_shadowed(filter_frame) self.pull_locals(filter_frame) self.buffer(filter_frame) self.blockvisit(node.body, filter_frame, force_generator=False) self.start_write(frame, node) self.visit_Filter(node.filter, filter_frame) self.end_write(frame) self.restore_shadowed(aliases) def visit_ExprStmt(self, node, frame): self.newline(node) self.visit(node.node, frame) def visit_Output(self, node, frame): # if we have a known extends statement, we don't output anything if self.has_known_extends and frame.toplevel: return if self.environment.finalize: finalize = lambda x: unicode(self.environment.finalize(x)) else: finalize = unicode self.newline(node) # if we are in the toplevel scope and there was already an extends # statement we have to add a check that disables our yield(s) here # so that they don't appear in the output. outdent_later = False if frame.toplevel and self.extends_so_far != 0: self.writeline('if parent_template is None:') self.indent() outdent_later = True # try to evaluate as many chunks as possible into a static # string at compile time. body = [] for child in node.nodes: try: const = child.as_const() except nodes.Impossible: body.append(child) continue try: if self.environment.autoescape: if hasattr(const, '__html__'): const = const.__html__() else: const = escape(const) const = finalize(const) except: # if something goes wrong here we evaluate the node # at runtime for easier debugging body.append(child) continue if body and isinstance(body[-1], list): body[-1].append(const) else: body.append([const]) # if we have less than 3 nodes or a buffer we yield or extend/append if len(body) < 3 or frame.buffer is not None: if frame.buffer is not None: # for one item we append, for more we extend if len(body) == 1: self.writeline('%s.append(' % frame.buffer) else: self.writeline('%s.extend((' % frame.buffer) self.indent() for item in body: if isinstance(item, list): val = repr(concat(item)) if frame.buffer is None: self.writeline('yield ' + val) else: self.writeline(val + ', ') else: if frame.buffer is None: self.writeline('yield ', item) else: self.newline(item) close = 1 if self.environment.autoescape: self.write('escape(') else: self.write('unicode(') if self.environment.finalize is not None: self.write('environment.finalize(') close += 1 self.visit(item, frame) self.write(')' * close) if frame.buffer is not None: self.write(', ') if frame.buffer is not None: # close the open parentheses self.outdent() self.writeline(len(body) == 1 and ')' or '))') # otherwise we create a format string as this is faster in that case else: format = [] arguments = [] for item in body: if isinstance(item, list): format.append(concat(item).replace('%', '%%')) else: format.append('%s') arguments.append(item) self.writeline('yield ') self.write(repr(concat(format)) + ' % (') idx = -1 self.indent() for argument in arguments: self.newline(argument) close = 0 if self.environment.autoescape: self.write('escape(') close += 1 if self.environment.finalize is not None: self.write('environment.finalize(') close += 1 self.visit(argument, frame) self.write(')' * close + ', ') self.outdent() self.writeline(')') if outdent_later: self.outdent() def visit_Assign(self, node, frame): self.newline(node) # toplevel assignments however go into the local namespace and # the current template's context. We create a copy of the frame # here and add a set so that the Name visitor can add the assigned # names here. if frame.toplevel: assignment_frame = frame.copy() assignment_frame.assigned_names = set() else: assignment_frame = frame self.visit(node.target, assignment_frame) self.write(' = ') self.visit(node.node, frame) # make sure toplevel assignments are added to the context. if frame.toplevel: public_names = [x for x in assignment_frame.assigned_names if not x.startswith('_')] if len(assignment_frame.assigned_names) == 1: name = iter(assignment_frame.assigned_names).next() self.writeline('context.vars[%r] = l_%s' % (name, name)) else: self.writeline('context.vars.update({') for idx, name in enumerate(assignment_frame.assigned_names): if idx: self.write(', ') self.write('%r: l_%s' % (name, name)) self.write('})') if public_names: if len(public_names) == 1: self.writeline('context.exported_vars.add(%r)' % public_names[0]) else: self.writeline('context.exported_vars.update((%s))' % ', '.join(map(repr, public_names))) # -- Expression Visitors def visit_Name(self, node, frame): if node.ctx == 'store' and frame.toplevel: frame.assigned_names.add(node.name) self.write('l_' + node.name) def visit_Const(self, node, frame): val = node.value if isinstance(val, float): self.write(str(val)) else: self.write(repr(val)) def visit_TemplateData(self, node, frame): self.write(repr(node.as_const())) def visit_Tuple(self, node, frame): self.write('(') idx = -1 for idx, item in enumerate(node.items): if idx: self.write(', ') self.visit(item, frame) self.write(idx == 0 and ',)' or ')') def visit_List(self, node, frame): self.write('[') for idx, item in enumerate(node.items): if idx: self.write(', ') self.visit(item, frame) self.write(']') def visit_Dict(self, node, frame): self.write('{') for idx, item in enumerate(node.items): if idx: self.write(', ') self.visit(item.key, frame) self.write(': ') self.visit(item.value, frame) self.write('}') def binop(operator): def visitor(self, node, frame): self.write('(') self.visit(node.left, frame) self.write(' %s ' % operator) self.visit(node.right, frame) self.write(')') return visitor def uaop(operator): def visitor(self, node, frame): self.write('(' + operator) self.visit(node.node, frame) self.write(')') return visitor visit_Add = binop('+') visit_Sub = binop('-') visit_Mul = binop('*') visit_Div = binop('/') visit_FloorDiv = binop('//') visit_Pow = binop('**') visit_Mod = binop('%') visit_And = binop('and') visit_Or = binop('or') visit_Pos = uaop('+') visit_Neg = uaop('-') visit_Not = uaop('not ') del binop, uaop def visit_Concat(self, node, frame): self.write('%s((' % (self.environment.autoescape and 'markup_join' or 'unicode_join')) for arg in node.nodes: self.visit(arg, frame) self.write(', ') self.write('))') def visit_Compare(self, node, frame): self.visit(node.expr, frame) for op in node.ops: self.visit(op, frame) def visit_Operand(self, node, frame): self.write(' %s ' % operators[node.op]) self.visit(node.expr, frame) def visit_Getattr(self, node, frame): self.write('environment.getattr(') self.visit(node.node, frame) self.write(', %r)' % node.attr) def visit_Getitem(self, node, frame): # slices or integer subscriptions bypass the getitem # method if we can determine that at compile time. if isinstance(node.arg, nodes.Slice) or \ (isinstance(node.arg, nodes.Const) and isinstance(node.arg.value, (int, long))): self.visit(node.node, frame) self.write('[') self.visit(node.arg, frame) self.write(']') else: self.write('environment.getitem(') self.visit(node.node, frame) self.write(', ') self.visit(node.arg, frame) self.write(')') def visit_Slice(self, node, frame): if node.start is not None: self.visit(node.start, frame) self.write(':') if node.stop is not None: self.visit(node.stop, frame) if node.step is not None: self.write(':') self.visit(node.step, frame) def visit_Filter(self, node, frame): self.write(self.filters[node.name] + '(') func = self.environment.filters.get(node.name) if func is None: self.fail('no filter named %r' % node.name, node.lineno) if getattr(func, 'contextfilter', False): self.write('context, ') elif getattr(func, 'environmentfilter', False): self.write('environment, ') # if the filter node is None we are inside a filter block # and want to write to the current buffer if node.node is not None: self.visit(node.node, frame) elif self.environment.autoescape: self.write('Markup(concat(%s))' % frame.buffer) else: self.write('concat(%s)' % frame.buffer) self.signature(node, frame) self.write(')') def visit_Test(self, node, frame): self.write(self.tests[node.name] + '(') if node.name not in self.environment.tests: self.fail('no test named %r' % node.name, node.lineno) self.visit(node.node, frame) self.signature(node, frame) self.write(')') def visit_CondExpr(self, node, frame): def write_expr2(): if node.expr2 is not None: return self.visit(node.expr2, frame) self.write('environment.undefined(%r)' % ('the inline if-' 'expression on %s evaluated to false and ' 'no else section was defined.' % self.position(node))) if not have_condexpr: self.write('((') self.visit(node.test, frame) self.write(') and (') self.visit(node.expr1, frame) self.write(',) or (') write_expr2() self.write(',))[0]') else: self.write('(') self.visit(node.expr1, frame) self.write(' if ') self.visit(node.test, frame) self.write(' else ') write_expr2() self.write(')') def visit_Call(self, node, frame, forward_caller=False): if self.environment.sandboxed: self.write('environment.call(context, ') else: self.write('context.call(') self.visit(node.node, frame) extra_kwargs = forward_caller and {'caller': 'caller'} or None self.signature(node, frame, extra_kwargs) self.write(')') def visit_Keyword(self, node, frame): self.write(node.key + '=') self.visit(node.value, frame) # -- Unused nodes for extensions def visit_MarkSafe(self, node, frame): self.write('Markup(') self.visit(node.expr, frame) self.write(')') def visit_EnvironmentAttribute(self, node, frame): self.write('environment.' + node.name) def visit_ExtensionAttribute(self, node, frame): self.write('environment.extensions[%r].%s' % (node.identifier, node.name)) def visit_ImportedName(self, node, frame): self.write(self.import_aliases[node.importname]) def visit_InternalName(self, node, frame): self.write(node.name) def visit_ContextReference(self, node, frame): self.write('context') def visit_Continue(self, node, frame): self.writeline('continue', node) def visit_Break(self, node, frame): self.writeline('break', node) ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������./weblog+jinja-1.1/jinja2/constants.py��������������������������������������������������������������0100644�0001750�0000000�00000003126�11063022145�0016654�0����������������������������������������������������������������������������������������������������ustar�00henry���������������������������wheel������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������# -*- 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-1.1/jinja2/debug.py������������������������������������������������������������������0100644�0001750�0000000�00000013707�11063022145�0015734�0����������������������������������������������������������������������������������������������������ustar�00henry���������������������������wheel������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������# -*- coding: utf-8 -*- """ jinja2.debug ~~~~~~~~~~~~ Implements the debug interface for Jinja. This module does some pretty ugly stuff with the Python traceback system in order to achieve tracebacks with correct line numbers, locals and contents. :copyright: Copyright 2008 by Armin Ronacher. :license: BSD. """ import sys from types import CodeType def translate_exception(exc_info): """If passed an exc_info it will automatically rewrite the exceptions all the way down to the correct line numbers and frames. """ result_tb = prev_tb = None initial_tb = tb = exc_info[2].tb_next while tb is not None: template = tb.tb_frame.f_globals.get('__jinja_template__') if template is not None: lineno = template.get_corresponding_lineno(tb.tb_lineno) tb = fake_exc_info(exc_info[:2] + (tb,), template.filename, lineno, prev_tb)[2] if result_tb is None: result_tb = tb prev_tb = tb tb = tb.tb_next return exc_info[:2] + (result_tb or initial_tb,) def translate_syntax_error(error): """When passed a syntax error it will generate a new traceback with more debugging information. """ filename = error.filename if filename is None: filename = '<template>' elif isinstance(filename, unicode): filename = filename.encode('utf-8') code = compile('\n' * (error.lineno - 1) + 'raise __jinja_exception__', filename, 'exec') try: exec code in {'__jinja_exception__': error} except: exc_info = sys.exc_info() return exc_info[:2] + (exc_info[2].tb_next,) def fake_exc_info(exc_info, filename, lineno, tb_back=None): """Helper for `translate_exception`.""" exc_type, exc_value, tb = exc_info # figure the real context out real_locals = tb.tb_frame.f_locals.copy() ctx = real_locals.get('context') if ctx: locals = ctx.get_all() else: locals = {} for name, value in real_locals.iteritems(): if name.startswith('l_'): locals[name[2:]] = value # if there is a local called __jinja_exception__, we get # rid of it to not break the debug functionality. locals.pop('__jinja_exception__', None) # assamble fake globals we need globals = { '__name__': filename, '__file__': filename, '__jinja_exception__': exc_info[:2] } # and fake the exception code = compile('\n' * (lineno - 1) + 'raise __jinja_exception__[0], ' + '__jinja_exception__[1]', filename, 'exec') # if it's possible, change the name of the code. This won't work # on some python environments such as google appengine try: function = tb.tb_frame.f_code.co_name if function == 'root': location = 'top-level template code' elif function.startswith('block_'): location = 'block "%s"' % function[6:] else: location = 'template' code = CodeType(0, code.co_nlocals, code.co_stacksize, code.co_flags, code.co_code, code.co_consts, code.co_names, code.co_varnames, filename, location, code.co_firstlineno, code.co_lnotab, (), ()) except: pass # execute the code and catch the new traceback try: exec code in globals, locals except: exc_info = sys.exc_info() new_tb = exc_info[2].tb_next # now we can patch the exc info accordingly if tb_set_next is not None: if tb_back is not None: tb_set_next(tb_back, new_tb) if tb is not None: tb_set_next(new_tb, tb.tb_next) # return without this frame return exc_info[:2] + (new_tb,) def _init_ugly_crap(): """This function implements a few ugly things so that we can patch the traceback objects. The function returned allows resetting `tb_next` on any python traceback object. """ import ctypes from types import TracebackType # figure out side of _Py_ssize_t if hasattr(ctypes.pythonapi, 'Py_InitModule4_64'): _Py_ssize_t = ctypes.c_int64 else: _Py_ssize_t = ctypes.c_int # regular python class _PyObject(ctypes.Structure): pass _PyObject._fields_ = [ ('ob_refcnt', _Py_ssize_t), ('ob_type', ctypes.POINTER(_PyObject)) ] # python with trace if object.__basicsize__ != ctypes.sizeof(_PyObject): class _PyObject(ctypes.Structure): pass _PyObject._fields_ = [ ('_ob_next', ctypes.POINTER(_PyObject)), ('_ob_prev', ctypes.POINTER(_PyObject)), ('ob_refcnt', _Py_ssize_t), ('ob_type', ctypes.POINTER(_PyObject)) ] class _Traceback(_PyObject): pass _Traceback._fields_ = [ ('tb_next', ctypes.POINTER(_Traceback)), ('tb_frame', ctypes.POINTER(_PyObject)), ('tb_lasti', ctypes.c_int), ('tb_lineno', ctypes.c_int) ] def tb_set_next(tb, next): """Set the tb_next attribute of a traceback object.""" if not (isinstance(tb, TracebackType) and (next is None or isinstance(next, TracebackType))): raise TypeError('tb_set_next arguments must be traceback objects') obj = _Traceback.from_address(id(tb)) if tb.tb_next is not None: old = _Traceback.from_address(id(tb.tb_next)) old.ob_refcnt -= 1 if next is None: obj.tb_next = ctypes.POINTER(_Traceback)() else: next = _Traceback.from_address(id(next)) next.ob_refcnt += 1 obj.tb_next = ctypes.pointer(next) return tb_set_next # try to get a tb_set_next implementation try: from jinja2._speedups import tb_set_next except ImportError: try: tb_set_next = _init_ugly_crap() except: tb_set_next = None del _init_ugly_crap ���������������������������������������������������������./weblog+jinja-1.1/jinja2/defaults.py���������������������������������������������������������������0100644�0001750�0000000�00000001545�11063022145�0016452�0����������������������������������������������������������������������������������������������������ustar�00henry���������������������������wheel������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������# -*- coding: utf-8 -*- """ jinja2.defaults ~~~~~~~~~~~~~~~ Jinja default filters and tags. :copyright: 2007-2008 by Armin Ronacher. :license: BSD, see LICENSE for more details. """ from jinja2.utils import generate_lorem_ipsum # defaults for the parser / lexer BLOCK_START_STRING = '{%' BLOCK_END_STRING = '%}' VARIABLE_START_STRING = '{{' VARIABLE_END_STRING = '}}' COMMENT_START_STRING = '{#' COMMENT_END_STRING = '#}' LINE_STATEMENT_PREFIX = None TRIM_BLOCKS = False NEWLINE_SEQUENCE = '\n' # default filters, tests and namespace from jinja2.filters import FILTERS as DEFAULT_FILTERS from jinja2.tests import TESTS as DEFAULT_TESTS DEFAULT_NAMESPACE = { 'range': xrange, 'dict': lambda **kw: kw, 'lipsum': generate_lorem_ipsum } # export all constants __all__ = tuple(x for x in locals() if x.isupper()) �����������������������������������������������������������������������������������������������������������������������������������������������������������./weblog+jinja-1.1/jinja2/environment.py������������������������������������������������������������0100644�0001750�0000000�00000071106�11063022145�0017207�0����������������������������������������������������������������������������������������������������ustar�00henry���������������������������wheel������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������# -*- coding: utf-8 -*- """ jinja2.environment ~~~~~~~~~~~~~~~~~~ Provides a class that holds runtime and parsing time options. :copyright: 2008 by Armin Ronacher. :license: BSD, see LICENSE for more details. """ import sys from jinja2.defaults import * from jinja2.lexer import Lexer, TokenStream from jinja2.parser import Parser from jinja2.optimizer import optimize from jinja2.compiler import generate from jinja2.runtime import Undefined, Context from jinja2.exceptions import TemplateSyntaxError from jinja2.utils import import_string, LRUCache, Markup, missing, concat # for direct template usage we have up to ten living environments _spontaneous_environments = LRUCache(10) def get_spontaneous_environment(*args): """Return a new spontaneus environment. A spontaneus environment is an unnamed and unaccessable (in theory) environment that is used for template generated from a string and not from the file system. """ try: env = _spontaneous_environments.get(args) except TypeError: return Environment(*args) if env is not None: return env _spontaneous_environments[args] = env = Environment(*args) env.shared = True return env def create_cache(size): """Return the cache class for the given size.""" if size == 0: return None if size < 0: return {} return LRUCache(size) def load_extensions(environment, extensions): """Load the extensions from the list and bind it to the environment. Returns a dict of instanciated environments. """ result = {} for extension in extensions: if isinstance(extension, basestring): extension = import_string(extension) result[extension.identifier] = extension(environment) return result def _environment_sanity_check(environment): """Perform a sanity check on the environment.""" assert issubclass(environment.undefined, Undefined), 'undefined must ' \ 'be a subclass of undefined because filters depend on it.' assert environment.block_start_string != \ environment.variable_start_string != \ environment.comment_start_string, 'block, variable and comment ' \ 'start strings must be different' assert environment.newline_sequence in ('\r', '\r\n', '\n'), \ 'newline_sequence set to unknown line ending string.' return environment class Environment(object): r"""The core component of Jinja is the `Environment`. It contains important shared variables like configuration, filters, tests, globals and others. Instances of this class may be modified if they are not shared and if no template was loaded so far. Modifications on environments after the first template was loaded will lead to surprising effects and undefined behavior. Here the possible initialization parameters: `block_start_string` The string marking the begin of a block. 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 ``'{{'``. `variable_end_string` The string marking the end 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 ``'#}'``. `line_statement_prefix` If given and a string, this will be used as prefix for line based statements. See also :ref:`line-statements`. `trim_blocks` If this is set to ``True`` the first newline after a block is removed (block, not variable tag!). Defaults to `False`. `newline_sequence` The sequence that starts a newline. Must be one of ``'\r'``, ``'\n'`` or ``'\r\n'``. The default is ``'\n'`` which is a useful default for Linux and OS X systems as well as web applications. `extensions` List of Jinja extensions to use. This can either be import paths as strings or extension classes. For more information have a look at :ref:`the extensions documentation <jinja-extensions>`. `optimized` should the optimizer be enabled? Default is `True`. `undefined` :class:`Undefined` or a subclass of it that is used to represent undefined values in the template. `finalize` A callable that finalizes the variable. Per default no finalizing is applied. `autoescape` If set to true the XML/HTML autoescaping feature is enabled. `loader` The template loader for this environment. `cache_size` The size of the cache. Per default this is ``50`` which means that if more than 50 templates are loaded the loader will clean out the least recently used template. If the cache size is set to ``0`` templates are recompiled all the time, if the cache size is ``-1`` the cache will not be cleaned. `auto_reload` Some loaders load templates from locations where the template sources may change (ie: file system or database). If `auto_reload` is set to `True` (default) every time a template is requested the loader checks if the source changed and if yes, it will reload the template. For higher performance it's possible to disable that. """ #: if this environment is sandboxed. Modifying this variable won't make #: the environment sandboxed though. For a real sandboxed environment #: have a look at jinja2.sandbox sandboxed = False #: True if the environment is just an overlay overlay = False #: the environment this environment is linked to if it is an overlay linked_to = None #: shared environments have this set to `True`. A shared environment #: must not be modified shared = False def __init__(self, block_start_string=BLOCK_START_STRING, block_end_string=BLOCK_END_STRING, variable_start_string=VARIABLE_START_STRING, variable_end_string=VARIABLE_END_STRING, comment_start_string=COMMENT_START_STRING, comment_end_string=COMMENT_END_STRING, line_statement_prefix=LINE_STATEMENT_PREFIX, trim_blocks=TRIM_BLOCKS, newline_sequence=NEWLINE_SEQUENCE, extensions=(), optimized=True, undefined=Undefined, finalize=None, autoescape=False, loader=None, cache_size=50, auto_reload=True): # !!Important notice!! # The constructor accepts quite a few arguments that should be # passed by keyword rather than position. However it's important to # not change the order of arguments because it's used at least # internally in those cases: # - spontaneus environments (i18n extension and Template) # - unittests # If parameter changes are required only add parameters at the end # and don't change the arguments (or the defaults!) of the arguments # existing already. # 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.line_statement_prefix = line_statement_prefix self.trim_blocks = trim_blocks self.newline_sequence = newline_sequence # runtime information self.undefined = undefined self.optimized = optimized self.finalize = finalize self.autoescape = autoescape # defaults self.filters = DEFAULT_FILTERS.copy() self.tests = DEFAULT_TESTS.copy() self.globals = DEFAULT_NAMESPACE.copy() # set the loader provided self.loader = loader self.cache = create_cache(cache_size) self.auto_reload = auto_reload # load extensions self.extensions = load_extensions(self, extensions) _environment_sanity_check(self) def extend(self, **attributes): """Add the items to the instance of the environment if they do not exist yet. This is used by :ref:`extensions <writing-extensions>` to register callbacks and configuration values without breaking inheritance. """ for key, value in attributes.iteritems(): if not hasattr(self, key): setattr(self, key, value) def overlay(self, block_start_string=missing, block_end_string=missing, variable_start_string=missing, variable_end_string=missing, comment_start_string=missing, comment_end_string=missing, line_statement_prefix=missing, trim_blocks=missing, extensions=missing, optimized=missing, undefined=missing, finalize=missing, autoescape=missing, loader=missing, cache_size=missing, auto_reload=missing): """Create a new overlay environment that shares all the data with the current environment except of cache and the overriden attributes. Extensions cannot be removed for a overlayed environment. A overlayed environment automatically gets all the extensions of the environment it is linked to plus optional extra extensions. Creating overlays should happen after the initial environment was set up completely. Not all attributes are truly linked, some are just copied over so modifications on the original environment may not shine through. """ args = dict(locals()) del args['self'], args['cache_size'], args['extensions'] rv = object.__new__(self.__class__) rv.__dict__.update(self.__dict__) rv.overlay = True rv.linked_to = self for key, value in args.iteritems(): if value is not missing: setattr(rv, key, value) if cache_size is not missing: rv.cache = create_cache(cache_size) rv.extensions = {} for key, value in self.extensions.iteritems(): rv.extensions[key] = value.bind(rv) if extensions is not missing: rv.extensions.update(load_extensions(extensions)) return _environment_sanity_check(rv) @property def lexer(self): """Return a fresh lexer for the environment.""" return Lexer(self) def getitem(self, obj, argument): """Get an item or attribute of an object but prefer the item.""" try: return obj[argument] except (TypeError, LookupError): if isinstance(argument, basestring): try: attr = str(argument) except: pass else: try: return getattr(obj, attr) except AttributeError: pass return self.undefined(obj=obj, name=argument) def getattr(self, obj, attribute): """Get an item or attribute of an object but prefer the attribute. Unlike :meth:`getitem` the attribute *must* be a bytestring. """ try: return getattr(obj, attribute) except AttributeError: pass try: return obj[attribute] except (TypeError, LookupError, AttributeError): return self.undefined(obj=obj, name=attribute) def parse(self, source, name=None, filename=None): """Parse the sourcecode and return the abstract syntax tree. This tree of nodes is used by the compiler to convert the template into executable source- or bytecode. This is useful for debugging or to extract information from templates. If you are :ref:`developing Jinja2 extensions <writing-extensions>` this gives you a good overview of the node tree generated. """ if isinstance(filename, unicode): filename = filename.encode('utf-8') try: return Parser(self, source, name, filename).parse() except TemplateSyntaxError, e: from jinja2.debug import translate_syntax_error exc_type, exc_value, tb = translate_syntax_error(e) raise exc_type, exc_value, tb def lex(self, source, name=None, filename=None): """Lex the given sourcecode and return a generator that yields tokens as tuples in the form ``(lineno, token_type, value)``. This can be useful for :ref:`extension development <writing-extensions>` and debugging templates. This does not perform preprocessing. If you want the preprocessing of the extensions to be applied you have to filter source through the :meth:`preprocess` method. """ return self.lexer.tokeniter(unicode(source), name, filename) def preprocess(self, source, name=None, filename=None): """Preprocesses the source with all extensions. This is automatically called for all parsing and compiling methods but *not* for :meth:`lex` because there you usually only want the actual source tokenized. """ return reduce(lambda s, e: e.preprocess(s, name, filename), self.extensions.itervalues(), unicode(source)) def _tokenize(self, source, name, filename=None): """Called by the parser to do the preprocessing and filtering for all the extensions. Returns a :class:`~jinja2.lexer.TokenStream`. """ source = self.preprocess(source, name, filename) stream = self.lexer.tokenize(source, name, filename) for ext in self.extensions.itervalues(): stream = ext.filter_stream(stream) if not isinstance(stream, TokenStream): stream = TokenStream(stream, name, filename) return stream def compile(self, source, name=None, filename=None, raw=False): """Compile a node or template source code. The `name` parameter is the load name of the template after it was joined using :meth:`join_path` if necessary, not the filename on the file system. the `filename` parameter is the estimated filename of the template on the file system. If the template came from a database or memory this can be omitted. The return value of this method is a python code object. If the `raw` parameter is `True` the return value will be a string with python code equivalent to the bytecode returned otherwise. This method is mainly used internally. """ if isinstance(source, basestring): source = self.parse(source, name, filename) if self.optimized: node = optimize(source, self) source = generate(node, self, name, filename) if raw: return source if filename is None: filename = '<template>' elif isinstance(filename, unicode): filename = filename.encode('utf-8') return compile(source, filename, 'exec') def join_path(self, template, parent): """Join a template with the parent. By default all the lookups are relative to the loader root so this method returns the `template` parameter unchanged, but if the paths should be relative to the parent template, this function can be used to calculate the real template name. Subclasses may override this method and implement template path joining here. """ return template def get_template(self, name, parent=None, globals=None): """Load a template from the loader. If a loader is configured this method ask the loader for the template and returns a :class:`Template`. If the `parent` parameter is not `None`, :meth:`join_path` is called to get the real template name before loading. The `globals` parameter can be used to provide temlate wide globals. These variables are available in the context at render time. If the template does not exist a :exc:`TemplateNotFound` exception is raised. """ if self.loader is None: raise TypeError('no loader for this environment specified') if parent is not None: name = self.join_path(name, parent) if self.cache is not None: template = self.cache.get(name) if template is not None and (not self.auto_reload or \ template.is_up_to_date): return template template = self.loader.load(self, name, self.make_globals(globals)) if self.cache is not None: self.cache[name] = template return template def from_string(self, source, globals=None, template_class=None): """Load a template from a string. This parses the source given and returns a :class:`Template` object. """ globals = self.make_globals(globals) cls = template_class or self.template_class return cls.from_code(self, self.compile(source), globals, None) def make_globals(self, d): """Return a dict for the globals.""" if not d: return self.globals return dict(self.globals, **d) class Template(object): """The central template object. This class represents a compiled template and is used to evaluate it. Normally the template object is generated from an :class:`Environment` but it also has a constructor that makes it possible to create a template instance directly using the constructor. It takes the same arguments as the environment constructor but it's not possible to specify a loader. Every template object has a few methods and members that are guaranteed to exist. However it's important that a template object should be considered immutable. Modifications on the object are not supported. Template objects created from the constructor rather than an environment do have an `environment` attribute that points to a temporary environment that is probably shared with other templates created with the constructor and compatible settings. >>> template = Template('Hello {{ name }}!') >>> template.render(name='John Doe') u'Hello John Doe!' >>> stream = template.stream(name='John Doe') >>> stream.next() u'Hello John Doe!' >>> stream.next() Traceback (most recent call last): ... StopIteration """ def __new__(cls, source, block_start_string=BLOCK_START_STRING, block_end_string=BLOCK_END_STRING, variable_start_string=VARIABLE_START_STRING, variable_end_string=VARIABLE_END_STRING, comment_start_string=COMMENT_START_STRING, comment_end_string=COMMENT_END_STRING, line_statement_prefix=LINE_STATEMENT_PREFIX, trim_blocks=TRIM_BLOCKS, newline_sequence=NEWLINE_SEQUENCE, extensions=(), optimized=True, undefined=Undefined, finalize=None, autoescape=False): env = get_spontaneous_environment( block_start_string, block_end_string, variable_start_string, variable_end_string, comment_start_string, comment_end_string, line_statement_prefix, trim_blocks, newline_sequence, frozenset(extensions), optimized, undefined, finalize, autoescape, None, 0, False) return env.from_string(source, template_class=cls) @classmethod def from_code(cls, environment, code, globals, uptodate=None): """Creates a template object from compiled code and the globals. This is used by the loaders and environment to create a template object. """ t = object.__new__(cls) namespace = { 'environment': environment, '__jinja_template__': t } exec code in namespace t.environment = environment t.globals = globals t.name = namespace['name'] t.filename = code.co_filename t.blocks = namespace['blocks'] # render function and module t.root_render_func = namespace['root'] t._module = None # debug and loader helpers t._debug_info = namespace['debug_info'] t._uptodate = uptodate return t def render(self, *args, **kwargs): """This method accepts the same arguments as the `dict` constructor: A dict, a dict subclass or some keyword arguments. If no arguments are given the context will be empty. These two calls do the same:: template.render(knights='that say nih') template.render({'knights': 'that say nih'}) This will return the rendered template as unicode string. """ vars = dict(*args, **kwargs) try: return concat(self.root_render_func(self.new_context(vars))) except: from jinja2.debug import translate_exception exc_type, exc_value, tb = translate_exception(sys.exc_info()) raise exc_type, exc_value, tb def stream(self, *args, **kwargs): """Works exactly like :meth:`generate` but returns a :class:`TemplateStream`. """ return TemplateStream(self.generate(*args, **kwargs)) def generate(self, *args, **kwargs): """For very large templates it can be useful to not render the whole template at once but evaluate each statement after another and yield piece for piece. This method basically does exactly that and returns a generator that yields one item after another as unicode strings. It accepts the same arguments as :meth:`render`. """ vars = dict(*args, **kwargs) try: for event in self.root_render_func(self.new_context(vars)): yield event except: from jinja2.debug import translate_exception exc_type, exc_value, tb = translate_exception(sys.exc_info()) raise exc_type, exc_value, tb def new_context(self, vars=None, shared=False): """Create a new :class:`Context` for this template. The vars provided will be passed to the template. Per default the globals are added to the context, if shared is set to `True` the data provided is used as parent namespace. This is used to share the same globals in multiple contexts without consuming more memory. (This works because the context does not modify the parent dict) """ if vars is None: vars = {} if shared: parent = vars else: parent = dict(self.globals, **vars) return Context(self.environment, parent, self.name, self.blocks) def make_module(self, vars=None, shared=False): """This method works like the :attr:`module` attribute when called without arguments but it will evaluate the template every call rather then caching the template. It's also possible to provide a dict which is then used as context. The arguments are the same as for the :meth:`new_context` method. """ return TemplateModule(self, self.new_context(vars, shared)) @property def module(self): """The template as module. This is used for imports in the template runtime but is also useful if one wants to access exported template variables from the Python layer: >>> t = Template('{% macro foo() %}42{% endmacro %}23') >>> unicode(t.module) u'23' >>> t.module.foo() u'42' """ if self._module is not None: return self._module self._module = rv = self.make_module() return rv def get_corresponding_lineno(self, lineno): """Return the source line number of a line number in the generated bytecode as they are not in sync. """ for template_line, code_line in reversed(self.debug_info): if code_line <= lineno: return template_line return 1 @property def is_up_to_date(self): """If this variable is `False` there is a newer version available.""" if self._uptodate is None: return True return self._uptodate() @property def debug_info(self): """The debug info mapping.""" return [tuple(map(int, x.split('='))) for x in self._debug_info.split('&')] def __repr__(self): if self.name is None: name = 'memory:%x' % id(self) else: name = repr(self.name) return '<%s %s>' % (self.__class__.__name__, name) class TemplateModule(object): """Represents an imported template. All the exported names of the template are available as attributes on this object. Additionally converting it into an unicode- or bytestrings renders the contents. """ def __init__(self, template, context): self._body_stream = list(template.root_render_func(context)) self.__dict__.update(context.get_exported()) self.__name__ = template.name __unicode__ = lambda x: concat(x._body_stream) __html__ = lambda x: Markup(concat(x._body_stream)) def __str__(self): return unicode(self).encode('utf-8') def __repr__(self): if self.__name__ is None: name = 'memory:%x' % id(self) else: name = repr(self.__name__) return '<%s %s>' % (self.__class__.__name__, name) class TemplateStream(object): """A template stream works pretty much like an ordinary python generator but it can buffer multiple items to reduce the number of total iterations. Per default the output is unbuffered which means that for every unbuffered instruction in the template one unicode string is yielded. If buffering is enabled with a buffer size of 5, five items are combined into a new unicode string. This is mainly useful if you are streaming big templates to a client via WSGI which flushes after each iteration. """ def __init__(self, gen): self._gen = gen self.disable_buffering() def dump(self, fp, encoding=None, errors='strict'): """Dump the complete stream into a file or file-like object. Per default unicode strings are written, if you want to encode before writing specifiy an `encoding`. Example usage:: Template('Hello {{ name }}!').stream(name='foo').dump('hello.html') """ close = False if isinstance(fp, basestring): fp = file(fp, 'w') close = True try: if encoding is not None: iterable = (x.encode(encoding, errors) for x in self) else: iterable = self if hasattr(fp, 'writelines'): fp.writelines(iterable) else: for item in iterable: fp.write(item) finally: if close: fp.close() 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') def generator(next): buf = [] c_size = 0 push = buf.append while 1: try: while c_size < size: c = next() push(c) if c: c_size += 1 except StopIteration: if not c_size: return yield concat(buf) del buf[:] c_size = 0 self.buffered = True self._next = generator(self._gen.next).next def __iter__(self): return self def next(self): return self._next() # hook in default template class. if anyone reads this comment: ignore that # it's possible to use custom templates ;-) Environment.template_class = Template ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������./weblog+jinja-1.1/jinja2/exceptions.py�������������������������������������������������������������0100644�0001750�0000000�00000004262�11063022145�0017023�0����������������������������������������������������������������������������������������������������ustar�00henry���������������������������wheel������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������# -*- coding: utf-8 -*- """ jinja2.exceptions ~~~~~~~~~~~~~~~~~ Jinja exceptions. :copyright: 2008 by Armin Ronacher. :license: BSD, see LICENSE for more details. """ class TemplateError(Exception): """Baseclass for all template errors.""" class TemplateNotFound(IOError, LookupError, TemplateError): """Raised if a template does not exist.""" def __init__(self, name): IOError.__init__(self, name) self.name = name class TemplateSyntaxError(TemplateError): """Raised to tell the user that there is a problem with the template.""" def __init__(self, message, lineno, name=None, filename=None): if name is not None: extra = '%s, line %d' % (name.encode('utf-8'), lineno) else: extra = 'line %d' % lineno # if the message was provided as unicode we have to encode it # to utf-8 explicitly if isinstance(message, unicode): message = message.encode('utf-8') # otherwise make sure it's a in fact valid utf-8 else: message = message.decode('utf-8', 'ignore').encode('utf-8') TemplateError.__init__(self, '%s (%s)' % (message, extra)) self.message = message self.lineno = lineno self.name = name self.filename = filename class TemplateAssertionError(TemplateSyntaxError): """Like a template syntax error, but covers cases where something in the template caused an error at compile time that wasn't necessarily caused by a syntax error. However it's a direct subclass of :exc:`TemplateSyntaxError` and has the same attributes. """ class TemplateRuntimeError(TemplateError): """A generic runtime error in the template engine. Under some situations Jinja may raise this exception. """ class UndefinedError(TemplateRuntimeError): """Raised if a template tries to operate on :class:`Undefined`.""" class SecurityError(TemplateRuntimeError): """Raised if a template tries to do something insecure if the sandbox is enabled. """ class FilterArgumentError(TemplateRuntimeError): """This error is raised if a filter was called with inappropriate arguments """ ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������./weblog+jinja-1.1/jinja2/ext.py��������������������������������������������������������������������0100644�0001750�0000000�00000041424�11063022145�0015443�0����������������������������������������������������������������������������������������������������ustar�00henry���������������������������wheel������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������# -*- coding: utf-8 -*- """ jinja2.ext ~~~~~~~~~~ Jinja extensions allow to add custom tags similar to the way django custom tags work. By default two example extensions exist: an i18n and a cache extension. :copyright: Copyright 2008 by Armin Ronacher. :license: BSD. """ from collections import deque from jinja2 import nodes from jinja2.defaults import * from jinja2.environment import get_spontaneous_environment from jinja2.runtime import Undefined, concat from jinja2.exceptions import TemplateAssertionError, TemplateSyntaxError from jinja2.utils import contextfunction, import_string, Markup # the only real useful gettext functions for a Jinja template. Note # that ugettext must be assigned to gettext as Jinja doesn't support # non unicode strings. GETTEXT_FUNCTIONS = ('_', 'gettext', 'ngettext') class ExtensionRegistry(type): """Gives the extension an unique identifier.""" def __new__(cls, name, bases, d): rv = type.__new__(cls, name, bases, d) rv.identifier = rv.__module__ + '.' + rv.__name__ return rv class Extension(object): """Extensions can be used to add extra functionality to the Jinja template system at the parser level. Custom extensions are bound to an environment but may not store environment specific data on `self`. The reason for this is that an extension can be bound to another environment (for overlays) by creating a copy and reassigning the `environment` attribute. As extensions are created by the environment they cannot accept any arguments for configuration. One may want to work around that by using a factory function, but that is not possible as extensions are identified by their import name. The correct way to configure the extension is storing the configuration values on the environment. Because this way the environment ends up acting as central configuration storage the attributes may clash which is why extensions have to ensure that the names they choose for configuration are not too generic. ``prefix`` for example is a terrible name, ``fragment_cache_prefix`` on the other hand is a good name as includes the name of the extension (fragment cache). """ __metaclass__ = ExtensionRegistry #: if this extension parses this is the list of tags it's listening to. tags = set() def __init__(self, environment): self.environment = environment def bind(self, environment): """Create a copy of this extension bound to another environment.""" rv = object.__new__(self.__class__) rv.__dict__.update(self.__dict__) rv.environment = environment return rv def preprocess(self, source, name, filename=None): """This method is called before the actual lexing and can be used to preprocess the source. The `filename` is optional. The return value must be the preprocessed source. """ return source def filter_stream(self, stream): """It's passed a :class:`~jinja2.lexer.TokenStream` that can be used to filter tokens returned. This method has to return an iterable of :class:`~jinja2.lexer.Token`\s, but it doesn't have to return a :class:`~jinja2.lexer.TokenStream`. In the `ext` folder of the Jinja2 source distribution there is a file called `inlinegettext.py` which implements a filter that utilizes this method. """ return stream def parse(self, parser): """If any of the :attr:`tags` matched this method is called with the parser as first argument. The token the parser stream is pointing at is the name token that matched. This method has to return one or a list of multiple nodes. """ raise NotImplementedError() def attr(self, name, lineno=None): """Return an attribute node for the current extension. This is useful to pass constants on extensions to generated template code:: self.attr('_my_attribute', lineno=lineno) """ return nodes.ExtensionAttribute(self.identifier, name, lineno=lineno) def call_method(self, name, args=None, kwargs=None, dyn_args=None, dyn_kwargs=None, lineno=None): """Call a method of the extension. This is a shortcut for :meth:`attr` + :class:`jinja2.nodes.Call`. """ if args is None: args = [] if kwargs is None: kwargs = [] return nodes.Call(self.attr(name, lineno=lineno), args, kwargs, dyn_args, dyn_kwargs, lineno=lineno) @contextfunction def _gettext_alias(context, string): return context.resolve('gettext')(string) class InternationalizationExtension(Extension): """This extension adds gettext support to Jinja2.""" tags = set(['trans']) def __init__(self, environment): Extension.__init__(self, environment) environment.globals['_'] = _gettext_alias environment.extend( install_gettext_translations=self._install, install_null_translations=self._install_null, uninstall_gettext_translations=self._uninstall, extract_translations=self._extract ) def _install(self, translations): self.environment.globals.update( gettext=translations.ugettext, ngettext=translations.ungettext ) def _install_null(self): self.environment.globals.update( gettext=lambda x: x, ngettext=lambda s, p, n: (n != 1 and (p,) or (s,))[0] ) def _uninstall(self, translations): for key in 'gettext', 'ngettext': self.environment.globals.pop(key, None) def _extract(self, source, gettext_functions=GETTEXT_FUNCTIONS): if isinstance(source, basestring): source = self.environment.parse(source) return extract_from_ast(source, gettext_functions) def parse(self, parser): """Parse a translatable tag.""" lineno = parser.stream.next().lineno # find all the variables referenced. Additionally a variable can be # defined in the body of the trans block too, but this is checked at # a later state. plural_expr = None variables = {} while parser.stream.current.type is not 'block_end': if variables: parser.stream.expect('comma') # skip colon for python compatibility if parser.stream.skip_if('colon'): break name = parser.stream.expect('name') if name.value in variables: parser.fail('translatable variable %r defined twice.' % name.value, name.lineno, exc=TemplateAssertionError) # expressions if parser.stream.current.type is 'assign': parser.stream.next() variables[name.value] = var = parser.parse_expression() else: variables[name.value] = var = nodes.Name(name.value, 'load') if plural_expr is None: plural_expr = var parser.stream.expect('block_end') plural = plural_names = None have_plural = False referenced = set() # now parse until endtrans or pluralize singular_names, singular = self._parse_block(parser, True) if singular_names: referenced.update(singular_names) if plural_expr is None: plural_expr = nodes.Name(singular_names[0], 'load') # if we have a pluralize block, we parse that too if parser.stream.current.test('name:pluralize'): have_plural = True parser.stream.next() if parser.stream.current.type is not 'block_end': plural_expr = parser.parse_expression() parser.stream.expect('block_end') plural_names, plural = self._parse_block(parser, False) parser.stream.next() referenced.update(plural_names) else: parser.stream.next() # register free names as simple name expressions for var in referenced: if var not in variables: variables[var] = nodes.Name(var, 'load') # no variables referenced? no need to escape if not referenced: singular = singular.replace('%%', '%') if plural: plural = plural.replace('%%', '%') if not have_plural: plural_expr = None elif plural_expr is None: parser.fail('pluralize without variables', lineno) if variables: variables = nodes.Dict([nodes.Pair(nodes.Const(x, lineno=lineno), y) for x, y in variables.items()]) else: variables = None node = self._make_node(singular, plural, variables, plural_expr) node.set_lineno(lineno) return node def _parse_block(self, parser, allow_pluralize): """Parse until the next block tag with a given name.""" referenced = [] buf = [] while 1: if parser.stream.current.type is 'data': buf.append(parser.stream.current.value.replace('%', '%%')) parser.stream.next() elif parser.stream.current.type is 'variable_begin': parser.stream.next() name = parser.stream.expect('name').value referenced.append(name) buf.append('%%(%s)s' % name) parser.stream.expect('variable_end') elif parser.stream.current.type is 'block_begin': parser.stream.next() if parser.stream.current.test('name:endtrans'): break elif parser.stream.current.test('name:pluralize'): if allow_pluralize: break parser.fail('a translatable section can have only one ' 'pluralize section') parser.fail('control structures in translatable sections are ' 'not allowed') elif parser.stream.eos: parser.fail('unclosed translation block') else: assert False, 'internal parser error' return referenced, concat(buf) def _make_node(self, singular, plural, variables, plural_expr): """Generates a useful node from the data provided.""" # singular only: if plural_expr is None: gettext = nodes.Name('gettext', 'load') node = nodes.Call(gettext, [nodes.Const(singular)], [], None, None) # singular and plural else: ngettext = nodes.Name('ngettext', 'load') node = nodes.Call(ngettext, [ nodes.Const(singular), nodes.Const(plural), plural_expr ], [], None, None) # mark the return value as safe if we are in an # environment with autoescaping turned on if self.environment.autoescape: node = nodes.MarkSafe(node) if variables: node = nodes.Mod(node, variables) return nodes.Output([node]) class ExprStmtExtension(Extension): """Adds a `do` tag to Jinja2 that works like the print statement just that it doesn't print the return value. """ tags = set(['do']) def parse(self, parser): node = nodes.ExprStmt(lineno=parser.stream.next().lineno) node.node = parser.parse_tuple() return node class LoopControlExtension(Extension): """Adds break and continue to the template engine.""" tags = set(['break', 'continue']) def parse(self, parser): token = parser.stream.next() if token.value == 'break': return nodes.Break(lineno=token.lineno) return nodes.Continue(lineno=token.lineno) def extract_from_ast(node, gettext_functions=GETTEXT_FUNCTIONS, babel_style=True): """Extract localizable strings from the given template node. Per default this function returns matches in babel style that means non string parameters as well as keyword arguments are returned as `None`. This allows Babel to figure out what you really meant if you are using gettext functions that allow keyword arguments for placeholder expansion. If you don't want that behavior set the `babel_style` parameter to `False` which causes only strings to be returned and parameters are always stored in tuples. As a consequence invalid gettext calls (calls without a single string parameter or string parameters after non-string parameters) are skipped. This example explains the behavior: >>> from jinja2 import Environment >>> env = Environment() >>> node = env.parse('{{ (_("foo"), _(), ngettext("foo", "bar", 42)) }}') >>> list(extract_from_ast(node)) [(1, '_', 'foo'), (1, '_', ()), (1, 'ngettext', ('foo', 'bar', None))] >>> list(extract_from_ast(node, babel_style=False)) [(1, '_', ('foo',)), (1, 'ngettext', ('foo', 'bar'))] For every string found this function yields a ``(lineno, function, message)`` tuple, where: * ``lineno`` is the number of the line on which the string was found, * ``function`` is the name of the ``gettext`` function used (if the string was extracted from embedded Python code), and * ``message`` is the string itself (a ``unicode`` object, or a tuple of ``unicode`` objects for functions with multiple string arguments). """ for node in node.find_all(nodes.Call): if not isinstance(node.node, nodes.Name) or \ node.node.name not in gettext_functions: continue strings = [] for arg in node.args: if isinstance(arg, nodes.Const) and \ isinstance(arg.value, basestring): strings.append(arg.value) else: strings.append(None) for arg in node.kwargs: strings.append(None) if node.dyn_args is not None: strings.append(None) if node.dyn_kwargs is not None: strings.append(None) if not babel_style: strings = tuple(x for x in strings if x is not None) if not strings: continue else: if len(strings) == 1: strings = strings[0] else: strings = tuple(strings) yield node.lineno, node.node.name, strings def babel_extract(fileobj, keywords, comment_tags, options): """Babel extraction method for Jinja templates. :param fileobj: the file-like object the messages should be extracted from :param keywords: a list of keywords (i.e. function names) that should be recognized as translation functions :param comment_tags: a list of translator tags to search for and include in the results. (Unused) :param options: a dictionary of additional options (optional) :return: an iterator over ``(lineno, funcname, message, comments)`` tuples. (comments will be empty currently) """ extensions = set() for extension in options.get('extensions', '').split(','): extension = extension.strip() if not extension: continue extensions.add(import_string(extension)) if InternationalizationExtension not in extensions: extensions.add(InternationalizationExtension) environment = get_spontaneous_environment( options.get('block_start_string', BLOCK_START_STRING), options.get('block_end_string', BLOCK_END_STRING), options.get('variable_start_string', VARIABLE_START_STRING), options.get('variable_end_string', VARIABLE_END_STRING), options.get('comment_start_string', COMMENT_START_STRING), options.get('comment_end_string', COMMENT_END_STRING), options.get('line_statement_prefix') or LINE_STATEMENT_PREFIX, str(options.get('trim_blocks', TRIM_BLOCKS)).lower() in \ ('1', 'on', 'yes', 'true'), NEWLINE_SEQUENCE, frozenset(extensions), # fill with defaults so that environments are shared # with other spontaneus environments. The rest of the # arguments are optimizer, undefined, finalize, autoescape, # loader, cache size and auto reloading setting True, Undefined, None, False, None, 0, False ) source = fileobj.read().decode(options.get('encoding', 'utf-8')) try: node = environment.parse(source) except TemplateSyntaxError, e: # skip templates with syntax errors return for lineno, func, message in extract_from_ast(node, keywords): yield lineno, func, message, [] #: nicer import names i18n = InternationalizationExtension do = ExprStmtExtension loopcontrols = LoopControlExtension ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������./weblog+jinja-1.1/jinja2/filters.py����������������������������������������������������������������0100644�0001750�0000000�00000051211�11063022145�0016306�0����������������������������������������������������������������������������������������������������ustar�00henry���������������������������wheel������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������# -*- coding: utf-8 -*- """ jinja2.filters ~~~~~~~~~~~~~~ Bundled jinja filters. :copyright: 2008 by Armin Ronacher, Christoph Hack. :license: BSD, see LICENSE for more details. """ import re import math import textwrap from random import choice from operator import itemgetter from itertools import imap, groupby from jinja2.utils import Markup, escape, pformat, urlize, soft_unicode from jinja2.runtime import Undefined from jinja2.exceptions import FilterArgumentError, SecurityError _word_re = re.compile(r'\w+') def contextfilter(f): """Decorator for marking context dependent filters. The current :class:`Context` will be passed as first argument. """ if getattr(f, 'environmentfilter', False): raise TypeError('filter already marked as environment filter') f.contextfilter = True return f def environmentfilter(f): """Decorator for marking evironment dependent filters. The current :class:`Environment` is passed to the filter as first argument. """ if getattr(f, 'contextfilter', False): raise TypeError('filter already marked as context filter') f.environmentfilter = True return f def do_forceescape(value): """Enforce HTML escaping. This will probably double escape variables.""" if hasattr(value, '__html__'): value = value.__html__() return escape(unicode(value)) @environmentfilter def do_replace(environment, 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 count is None: count = -1 if not environment.autoescape: return unicode(s).replace(unicode(old), unicode(new), count) if hasattr(old, '__html__') or hasattr(new, '__html__') and \ not hasattr(s, '__html__'): s = escape(s) else: s = soft_unicode(s) return s.replace(soft_unicode(old), soft_unicode(new), count) def do_upper(s): """Convert a value to uppercase.""" return soft_unicode(s).upper() def do_lower(s): """Convert a value to lowercase.""" return soft_unicode(s).lower() @environmentfilter def do_xmlattr(_environment, d, autospace=True): """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 <ul{{ {'class': 'my_list', 'missing': none, 'id': 'list-%d'|format(variable)}|xmlattr }}> ... </ul> Results in something like this: .. sourcecode:: html <ul class="my_list" id="list-42"> ... </ul> As you can see it automatically prepends a space in front of the item if the filter returned something unless the second parameter is false. """ rv = u' '.join( u'%s="%s"' % (escape(key), escape(value)) for key, value in d.iteritems() if value is not None and not isinstance(value, Undefined) ) if autospace and rv: rv = u' ' + rv if _environment.autoescape: rv = Markup(rv) return rv def do_capitalize(s): """Capitalize a value. The first character will be uppercase, all others lowercase. """ return soft_unicode(s).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 soft_unicode(s).title() def do_dictsort(value, 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(item): value = item[pos] if isinstance(value, basestring): value = unicode(value) if not case_sensitive: value = value.lower() return value return sorted(value.items(), key=sort_func) def do_default(value, 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) }} """ if (boolean and not value) or isinstance(value, Undefined): return default_value return value @environmentfilter def do_join(environment, value, 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 it with the optional parameter: .. sourcecode:: jinja {{ [1, 2, 3]|join('|') }} -> 1|2|3 {{ [1, 2, 3]|join }} -> 123 """ # no automatic escaping? joining is a lot eaiser then if not environment.autoescape: return unicode(d).join(imap(unicode, value)) # if the delimiter doesn't have an html representation we check # if any of the items has. If yes we do a coercion to Markup if not hasattr(d, '__html__'): value = list(value) do_escape = False for idx, item in enumerate(value): if hasattr(item, '__html__'): do_escape = True else: value[idx] = unicode(item) if do_escape: d = escape(d) else: d = unicode(d) return d.join(value) # no html involved, to normal joining return soft_unicode(d).join(imap(soft_unicode, value)) def do_center(value, width=80): """Centers the value in a field of a given width.""" return unicode(value).center(width) @environmentfilter def do_first(environment, seq): """Return the first item of a sequence.""" try: return iter(seq).next() except StopIteration: return environment.undefined('No first item, sequence was empty.') @environmentfilter def do_last(environment, seq): """Return the last item of a sequence.""" try: return iter(reversed(seq)).next() except StopIteration: return environment.undefined('No last item, sequence was empty.') @environmentfilter def do_random(environment, seq): """Return a random item from the sequence.""" try: return choice(seq) except IndexError: return environment.undefined('No random item, sequence was empty.') def do_filesizeformat(value, binary=False): """Format the value like a 'human-readable' file size (i.e. 13 KB, 4.1 MB, 102 bytes, etc). Per default decimal prefixes are used (mega, giga etc.), if the second parameter is set to `True` the binary prefixes are (mebi, gibi). """ bytes = float(value) base = binary and 1024 or 1000 middle = binary and 'i' or '' if bytes < base: return "%d Byte%s" % (bytes, bytes != 1 and 's' or '') elif bytes < base * base: return "%.1f K%sB" % (bytes / base, middle) elif bytes < base * base * base: return "%.1f M%sB" % (bytes / (base * base), middle) return "%.1f G%sB" % (bytes / (base * base * base), middle) def do_pprint(value, 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`) """ return pformat(value, verbose=verbose) @environmentfilter def do_urlize(environment, 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" """ rv = urlize(soft_unicode(value), trim_url_limit, nofollow) if environment.autoescape: rv = Markup(rv) return rv def do_indent(s, width=4, indentfirst=False): """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) 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) def do_wordwrap(s, width=79, break_long_words=True): """ 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 `false` Jinja will not split words apart if they are longer than `width`. """ return u'\n'.join(textwrap.wrap(s, width=width, expand_tabs=False, replace_whitespace=False, break_long_words=break_long_words)) def do_wordcount(s): """Count the words in that string.""" return len(_word_re.findall(s)) def do_int(value, 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. """ try: return int(value) except (TypeError, ValueError): # this quirk is necessary so that "42.23"|int gives 42. try: return int(float(value)) except (TypeError, ValueError): return default def do_float(value, 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. """ try: return float(value) except (TypeError, ValueError): return default def do_format(value, *args, **kwargs): """ Apply python string formatting on an object: .. sourcecode:: jinja {{ "%s - %s"|format("Hello?", "Foo!") }} -> Hello? - Foo! """ if args and kwargs: raise FilterArgumentError('can\'t handle positional and keyword ' 'arguments at the same time') return soft_unicode(value) % (kwargs or args) def do_trim(value): """Strip leading and trailing whitespace.""" return soft_unicode(value).strip() def do_striptags(value): """Strip SGML/XML tags and replace adjacent whitespace by one space. """ if hasattr(value, '__html__'): value = value.__html__() return Markup(unicode(value)).striptags() def do_slice(value, 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 <div class="columwrapper"> {%- for column in items|slice(3) %} <ul class="column-{{ loop.index }}"> {%- for item in column %} <li>{{ item }}</li> {%- endfor %} </ul> {%- endfor %} </div> If you pass it a second argument it's used to fill missing values on the last iteration. """ 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) yield tmp def do_batch(value, 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 <table> {%- for row in items|batch(3, ' ') %} <tr> {%- for column in row %} <tr>{{ column }}</td> {%- endfor %} </tr> {%- endfor %} </table> """ result = [] tmp = [] for item in value: if len(tmp) == linecount: yield tmp tmp = [] tmp.append(item) if tmp: if fill_with is not None and len(tmp) < linecount: tmp += [fill_with] * (linecount - len(tmp)) yield tmp def do_round(value, 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 """ 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.') if method == 'common': return round(value, precision) func = getattr(math, method) if precision: return func(value * 10 * precision) / (10 * precision) else: return func(value) def do_sort(value, reverse=False): """Sort a sequence. Per default it sorts ascending, if you pass it true as first argument it will reverse the sorting. """ return sorted(value, reverse=reverse) @environmentfilter def do_groupby(environment, value, 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 <ul> {% for group in persons|groupby('gender') %} <li>{{ group.grouper }}<ul> {% for person in group.list %} <li>{{ person.first_name }} {{ person.last_name }}</li> {% endfor %}</ul></li> {% endfor %} </ul> Additionally it's possible to use tuple unpacking for the grouper and list: .. sourcecode:: html+jinja <ul> {% for grouper, list in persons|groupby('gender') %} ... {% endfor %} </ul> 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. """ expr = lambda x: environment.getitem(x, attribute) return sorted(map(_GroupTuple, groupby(sorted(value, key=expr), expr))) class _GroupTuple(tuple): __slots__ = () grouper = property(itemgetter(0)) list = property(itemgetter(1)) def __new__(cls, (key, value)): return tuple.__new__(cls, (key, list(value))) def do_list(value): """Convert the value into a list. If it was a string the returned list will be a list of characters. """ return list(value) def do_mark_safe(value): """Mark the value as safe which means that in an environment with automatic escaping enabled this variable will not be escaped. """ return Markup(value) def do_mark_unsafe(value): """Mark a value as unsafe. This is the reverse operation for :func:`safe`.""" return unicode(value) def do_reverse(value): """Reverse the object or return an iterator the iterates over it the other way round. """ if isinstance(value, basestring): return value[::-1] try: return reversed(value) except TypeError: try: rv = list(value) rv.reverse() return rv except TypeError: raise FilterArgumentError('argument must be iterable') @environmentfilter def do_attr(environment, obj, name): """Get an attribute of an object. ``foo|attr("bar")`` works like ``foo["bar"]`` just that always an attribute is returned and items are not looked up. See :ref:`Notes on subscriptions <notes-on-subscriptions>` for more details. """ try: value = getattr(obj, name) except AttributeError: return environment.undefined(obj=obj, name=name) if environment.sandboxed and not \ environment.is_safe_attribute(obj, name, value): return environment.unsafe_undefined(obj, name) return value FILTERS = { 'attr': do_attr, 'replace': do_replace, 'upper': do_upper, 'lower': do_lower, 'escape': escape, 'e': escape, 'forceescape': do_forceescape, 'capitalize': do_capitalize, 'title': do_title, 'default': do_default, 'd': do_default, 'join': do_join, 'count': len, 'dictsort': do_dictsort, 'length': len, 'reverse': do_reverse, 'center': do_center, 'indent': do_indent, 'title': do_title, 'capitalize': do_capitalize, 'first': do_first, 'last': do_last, 'random': do_random, 'filesizeformat': do_filesizeformat, 'pprint': do_pprint, 'truncate': do_truncate, 'wordwrap': do_wordwrap, 'wordcount': do_wordcount, 'int': do_int, 'float': do_float, 'string': soft_unicode, 'list': do_list, 'urlize': do_urlize, 'format': do_format, 'trim': do_trim, 'striptags': do_striptags, 'slice': do_slice, 'batch': do_batch, 'sum': sum, 'abs': abs, 'round': do_round, 'sort': do_sort, 'groupby': do_groupby, 'safe': do_mark_safe, 'xmlattr': do_xmlattr } ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������./weblog+jinja-1.1/jinja2/lexer.py������������������������������������������������������������������0100644�0001750�0000000�00000051757�11063022145�0015774�0����������������������������������������������������������������������������������������������������ustar�00henry���������������������������wheel������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������# -*- coding: utf-8 -*- """ jinja2.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. :copyright: 2007-2008 by Armin Ronacher. :license: BSD, see LICENSE for more details. """ import re import unicodedata from operator import itemgetter from collections import deque from jinja2.exceptions import TemplateSyntaxError from jinja2.utils import LRUCache # cache for the lexers. Exists in order to be able to have multiple # environments with the same lexer _lexer_cache = LRUCache(50) # static regular expressions whitespace_re = re.compile(r'\s+(?um)') string_re = re.compile(r"('([^'\\]*(?:\\.[^'\\]*)*)'" r'|"([^"\\]*(?:\\.[^"\\]*)*)")(?ms)') integer_re = re.compile(r'\d+') name_re = re.compile(r'\b[a-zA-Z_][a-zA-Z0-9_]*\b') float_re = re.compile(r'\d+\.\d+') newline_re = re.compile(r'(\r\n|\r|\n)') # bind operators to token types operators = { '+': 'add', '-': 'sub', '/': 'div', '//': 'floordiv', '*': 'mul', '%': 'mod', '**': 'pow', '~': 'tilde', '[': 'lbracket', ']': 'rbracket', '(': 'lparen', ')': 'rparen', '{': 'lbrace', '}': 'rbrace', '==': 'eq', '!=': 'ne', '>': 'gt', '>=': 'gteq', '<': 'lt', '<=': 'lteq', '=': 'assign', '.': 'dot', ':': 'colon', '|': 'pipe', ',': 'comma', ';': 'semicolon' } 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 count_newlines(value): """Count the number of newline characters in the string. This is useful for extensions that filter a stream. """ return len(newline_re.findall(value)) 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 Token(tuple): """Token class.""" __slots__ = () lineno, type, value = (property(itemgetter(x)) for x in range(3)) def __new__(cls, lineno, type, value): return tuple.__new__(cls, (lineno, intern(str(type)), value)) def __str__(self): if self.type in reverse_operators: return reverse_operators[self.type] elif self.type is 'name': return self.value return self.type def test(self, expr): """Test a token against a token expression. This can either be a token type or ``'token_type:token_value'``. This can only test against string values and types. """ # here we do a regular string equality check as test_any is usually # passed an iterable of not interned strings. if self.type == expr: return True elif ':' in expr: return expr.split(':', 1) == [self.type, self.value] return False def test_any(self, *iterable): """Test against multiple token expressions.""" for expr in iterable: if self.test(expr): return True return False 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 is an iterable that yields :class:`Token`\s. The parser however does not iterate over it but calls :meth:`next` to go one token ahead. The current active token is stored as :attr:`current`. """ def __init__(self, generator, name, filename): self._next = iter(generator).next self._pushed = deque() self.name = name self.filename = filename self.closed = False self.current = Token(1, 'initial', '') self.next() def __iter__(self): return TokenStreamIterator(self) def __nonzero__(self): """Are we at the end of the stream?""" return bool(self._pushed) or self.current.type != 'eof' eos = property(lambda x: not x.__nonzero__(), doc=__nonzero__.__doc__) def push(self, token): """Push a token back to the stream.""" self._pushed.append(token) def look(self): """Look at the next token.""" old_token = self.next() result = self.current self.push(result) self.current = old_token return result def skip(self, n=1): """Got n tokens ahead.""" for x in xrange(n): self.next() def next_if(self, expr): """Perform the token test and return the token if it matched. Otherwise the return value is `None`. """ if self.current.test(expr): return self.next() def skip_if(self, expr): """Like :meth:`next_if` but only returns `True` or `False`.""" return self.next_if(expr) is not None def next(self): """Go one token ahead and return the old one""" rv = self.current if self._pushed: self.current = self._pushed.popleft() elif self.current.type is not 'eof': try: self.current = self._next() except StopIteration: self.close() return rv def close(self): """Close the stream.""" self.current = Token(self.current.lineno, 'eof', '') self._next = None self.closed = True def expect(self, expr): """Expect a given token type and return it. This accepts the same argument as :meth:`jinja2.lexer.Token.test`. """ if not self.current.test(expr): if ':' in expr: expr = expr.split(':')[1] if self.current.type is 'eof': raise TemplateSyntaxError('unexpected end of template, ' 'expected %r.' % expr, self.current.lineno, self.name, self.filename) raise TemplateSyntaxError("expected token %r, got %r" % (expr, str(self.current)), self.current.lineno, self.name, self.filename) try: return self.current finally: self.next() class LexerMeta(type): """Metaclass for the lexer that caches instances for the same configuration in a weak value dictionary. """ def __call__(cls, environment): key = (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.line_statement_prefix, environment.trim_blocks, environment.newline_sequence) lexer = _lexer_cache.get(key) if lexer is None: 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, 'whitespace', None), (float_re, 'float', None), (integer_re, 'integer', None), (name_re, 'name', None), (string_re, 'string', None), (operator_re, 'operator', None) ] # 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), ('variable', environment.variable_start_string) ] root_tag_rules.sort(key=lambda x: -len(x[1])) # now escape the rules. This is done here so that the escape # signs don't count for the lengths of the tags. root_tag_rules = [(a, e(b)) for a, b in root_tag_rules] # if we have a line statement prefix we need an extra rule for # that. We add this rule *after* all the others. if environment.line_statement_prefix is not None: prefix = e(environment.line_statement_prefix) root_tag_rules.insert(0, ('linestatement', '^\s*' + prefix)) # block suffix if trimming is enabled block_suffix_re = environment.trim_blocks and '\\n?' or '' self.newline_sequence = environment.newline_sequence # global lexing rules self.rules = { 'root': [ # directives (c('(.*?)(?:%s)' % '|'.join( ['(?P<raw_begin>(?:\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, r, 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, # variables 'variable_begin': [ (c('\-%s\s*|%s' % ( e(environment.variable_end_string), e(environment.variable_end_string) )), 'variable_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) ], # line statements 'linestatement_begin': [ (c(r'\s*(\n|$)'), 'linestatement_end', '#pop') ] + tag_rules } def _normalize_newlines(self, value): """Called for strings and template data to normlize it to unicode.""" return newline_re.sub(self.newline_sequence, value) def tokenize(self, source, name=None, filename=None): """Calls tokeniter + tokenize and wraps it in a token stream. """ stream = self.tokeniter(source, name, filename) return TokenStream(self.wrap(stream, name, filename), name, filename) def wrap(self, stream, name=None, filename=None): """This is called with the stream as returned by `tokenize` and wraps every token in a :class:`Token` and converts the value. """ for lineno, token, value in stream: if token in ('comment_begin', 'comment', 'comment_end', 'whitespace'): continue elif token == 'linestatement_begin': token = 'block_begin' elif token == 'linestatement_end': token = 'block_end' # we are not interested in those tokens in the parser elif token in ('raw_begin', 'raw_end'): continue elif token == 'data': value = self._normalize_newlines(value) elif token == 'keyword': token = value elif token == 'name': value = str(value) elif token == 'string': # try to unescape string try: value = self._normalize_newlines(value[1:-1]) \ .encode('ascii', 'backslashreplace') \ .decode('unicode-escape') except Exception, e: msg = str(e).split(':')[-1].strip() raise TemplateSyntaxError(msg, lineno, name, filename) # if we can express it as bytestring (ascii only) # we do that for support of semi broken APIs # as datetime.datetime.strftime try: value = str(value) except UnicodeError: pass elif token == 'integer': value = int(value) elif token == 'float': value = float(value) elif token == 'operator': token = operators[value] yield Token(lineno, token, value) def tokeniter(self, source, name, 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. """ source = '\n'.join(unicode(source).splitlines()) pos = 0 lineno = 1 stack = ['root'] statetokens = self.rules['root'] source_length = len(source) balancing_stack = [] while 1: # 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 m is None: 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', 'linestatement_end'): continue # tuples support more options if isinstance(tokens, tuple): for idx, token in enumerate(tokens): # failure group if 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. 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, name, filename) expected_op = balancing_stack.pop() if expected_op != data: raise TemplateSyntaxError('unexpected "%s", ' 'expected "%s"' % (data, expected_op), lineno, name, filename) # yield items 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, name, filename) �����������������./weblog+jinja-1.1/jinja2/loaders.py����������������������������������������������������������������0100644�0001750�0000000�00000024116�11063022145�0016273�0����������������������������������������������������������������������������������������������������ustar�00henry���������������������������wheel������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������# -*- coding: utf-8 -*- """ jinja2.loaders ~~~~~~~~~~~~~~ Jinja loader classes. :copyright: 2008 by Armin Ronacher. :license: BSD, see LICENSE for more details. """ from os import path try: from hashlib import sha1 except ImportError: from sha import new as sha1 from jinja2.exceptions import TemplateNotFound from jinja2.utils import LRUCache def split_template_path(template): """Split a path into segments and perform a sanity check. If it detects '..' in the path it will raise a `TemplateNotFound` error. """ pieces = [] for piece in template.split('/'): if path.sep in piece \ or (path.altsep and path.altsep in piece) or \ piece == path.pardir: raise TemplateNotFound(template) elif piece and piece != '.': pieces.append(piece) return pieces class BaseLoader(object): """Baseclass for all loaders. Subclass this and override `get_source` to implement a custom loading mechanism. The environment provides a `get_template` method that calls the loader's `load` method to get the :class:`Template` object. A very basic example for a loader that looks up templates on the file system could look like this:: from jinja2 import BaseLoader, TemplateNotFound from os.path import join, exists, getmtime class MyLoader(BaseLoader): def __init__(self, path): self.path = path def get_source(self, environment, template): path = join(self.path, template) if not exists(path): raise TemplateNotFound(template) mtime = getmtime(path) with file(path) as f: source = f.read().decode('utf-8') return source, path, lambda: mtime == getmtime(path) """ def get_source(self, environment, template): """Get the template source, filename and reload helper for a template. It's passed the environment and template name and has to return a tuple in the form ``(source, filename, uptodate)`` or raise a `TemplateNotFound` error if it can't locate the template. The source part of the returned tuple must be the source of the template as unicode string or a ASCII bytestring. The filename should be the name of the file on the filesystem if it was loaded from there, otherwise `None`. The filename is used by python for the tracebacks if no loader extension is used. The last item in the tuple is the `uptodate` function. If auto reloading is enabled it's always called to check if the template changed. No arguments are passed so the function must store the old state somewhere (for example in a closure). If it returns `False` the template will be reloaded. """ raise TemplateNotFound(template) def load(self, environment, name, globals=None): """Loads a template. This method looks up the template in the cache or loads one by calling :meth:`get_source`. Subclasses should not override this method as loaders working on collections of other loaders (such as :class:`PrefixLoader` or :class:`ChoiceLoader`) will not call this method but `get_source` directly. """ if globals is None: globals = {} source, filename, uptodate = self.get_source(environment, name) code = environment.compile(source, name, filename) return environment.template_class.from_code(environment, code, globals, uptodate) class FileSystemLoader(BaseLoader): """Loads templates from the file system. This loader can find templates in folders on the file system and is the preferred way to load them. The loader takes the path to the templates as string, or if multiple locations are wanted a list of them which is then looked up in the given order: >>> loader = FileSystemLoader('/path/to/templates') >>> loader = FileSystemLoader(['/path/to/templates', '/other/path']) Per default the template encoding is ``'utf-8'`` which can be changed by setting the `encoding` parameter to something else. """ def __init__(self, searchpath, encoding='utf-8'): if isinstance(searchpath, basestring): searchpath = [searchpath] self.searchpath = list(searchpath) self.encoding = encoding def get_source(self, environment, template): pieces = split_template_path(template) for searchpath in self.searchpath: filename = path.join(searchpath, *pieces) if not path.isfile(filename): continue f = file(filename) try: contents = f.read().decode(self.encoding) finally: f.close() old = path.getmtime(filename) return contents, filename, lambda: path.getmtime(filename) == old raise TemplateNotFound(template) class PackageLoader(BaseLoader): """Load templates from python eggs or packages. It is constructed with the name of the python package and the path to the templates in that package: >>> loader = PackageLoader('mypackage', 'views') If the package path is not given, ``'templates'`` is assumed. Per default the template encoding is ``'utf-8'`` which can be changed by setting the `encoding` parameter to something else. Due to the nature of eggs it's only possible to reload templates if the package was loaded from the file system and not a zip file. """ def __init__(self, package_name, package_path='templates', encoding='utf-8'): from pkg_resources import DefaultProvider, ResourceManager, get_provider provider = get_provider(package_name) self.encoding = encoding self.manager = ResourceManager() self.filesystem_bound = isinstance(provider, DefaultProvider) self.provider = provider self.package_path = package_path def get_source(self, environment, template): pieces = split_template_path(template) p = '/'.join((self.package_path,) + tuple(pieces)) if not self.provider.has_resource(p): raise TemplateNotFound(template) filename = uptodate = None if self.filesystem_bound: filename = self.provider.get_resource_filename(self.manager, p) mtime = path.getmtime(filename) def uptodate(): return path.getmtime(filename) == mtime source = self.provider.get_resource_string(self.manager, p) return source.decode(self.encoding), filename, uptodate class DictLoader(BaseLoader): """Loads a template from a python dict. It's passed a dict of unicode strings bound to template names. This loader is useful for unittesting: >>> loader = DictLoader({'index.html': 'source here'}) Because auto reloading is rarely useful this is disabled per default. """ def __init__(self, mapping): self.mapping = mapping def get_source(self, environment, template): if template in self.mapping: source = self.mapping[template] return source, None, lambda: source != self.mapping[template] raise TemplateNotFound(template) class FunctionLoader(BaseLoader): """A loader that is passed a function which does the loading. The function becomes the name of the template passed and has to return either an unicode string with the template source, a tuple in the form ``(source, filename, uptodatefunc)`` or `None` if the template does not exist. >>> def load_template(name): ... if name == 'index.html' ... return '...' ... >>> loader = FunctionLoader(load_template) The `uptodatefunc` is a function that is called if autoreload is enabled and has to return `True` if the template is still up to date. For more details have a look at :meth:`BaseLoader.get_source` which has the same return value. """ def __init__(self, load_func): self.load_func = load_func def get_source(self, environment, template): rv = self.load_func(template) if rv is None: raise TemplateNotFound(template) elif isinstance(rv, basestring): return rv, None, None return rv class PrefixLoader(BaseLoader): """A loader that is passed a dict of loaders where each loader is bound to a prefix. The prefix is delimited from the template by a slash per default, which can be changed by setting the `delimiter` argument to something else. >>> loader = PrefixLoader({ ... 'app1': PackageLoader('mypackage.app1'), ... 'app2': PackageLoader('mypackage.app2') ... }) By loading ``'app1/index.html'`` the file from the app1 package is loaded, by loading ``'app2/index.html'`` the file from the second. """ def __init__(self, mapping, delimiter='/'): self.mapping = mapping self.delimiter = delimiter def get_source(self, environment, template): try: prefix, template = template.split(self.delimiter, 1) loader = self.mapping[prefix] except (ValueError, KeyError): raise TemplateNotFound(template) return loader.get_source(environment, template) class ChoiceLoader(BaseLoader): """This loader works like the `PrefixLoader` just that no prefix is specified. If a template could not be found by one loader the next one is tried. >>> loader = ChoiceLoader([ ... FileSystemLoader('/path/to/user/templates'), ... PackageLoader('mypackage') ... ]) This is useful if you want to allow users to override builtin templates from a different location. """ def __init__(self, loaders): self.loaders = loaders def get_source(self, environment, template): for loader in self.loaders: try: return loader.get_source(environment, template) except TemplateNotFound: pass raise TemplateNotFound(template) ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������./weblog+jinja-1.1/jinja2/nodes.py������������������������������������������������������������������0100644�0001750�0000000�00000057130�11063022145�0015754�0����������������������������������������������������������������������������������������������������ustar�00henry���������������������������wheel������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������# -*- coding: utf-8 -*- """ jinja2.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: 2008 by Armin Ronacher. :license: BSD, see LICENSE for more details. """ import operator from copy import copy from itertools import chain, izip from collections import deque from jinja2.utils import Markup _binop_to_func = { '*': operator.mul, '/': operator.truediv, '//': operator.floordiv, '**': operator.pow, '%': operator.mod, '+': operator.add, '-': operator.sub } _uaop_to_func = { 'not': operator.not_, '+': operator.pos, '-': operator.neg } _cmpop_to_func = { 'eq': operator.eq, 'ne': operator.ne, 'gt': operator.gt, 'gteq': operator.ge, 'lt': operator.lt, 'lteq': operator.le, 'in': lambda a, b: a in b, 'notin': lambda a, b: a not in b } class Impossible(Exception): """Raised if the node could not perform a requested action.""" class NodeType(type): """A metaclass for nodes that handles the field and attribute inheritance. fields and attributes from the parent class are automatically forwarded to the child.""" def __new__(cls, name, bases, d): for attr in 'fields', 'attributes': storage = [] storage.extend(getattr(bases[0], attr, ())) storage.extend(d.get(attr, ())) assert len(bases) == 1, 'multiple inheritance not allowed' assert len(storage) == len(set(storage)), 'layout conflict' d[attr] = tuple(storage) d.setdefault('abstract', False) return type.__new__(cls, name, bases, d) class Node(object): """Baseclass for all Jinja2 nodes. There are a number of nodes available of different types. There are three major types: - :class:`Stmt`: statements - :class:`Expr`: expressions - :class:`Helper`: helper nodes - :class:`Template`: the outermost wrapper node All nodes have fields and attributes. Fields may be other nodes, lists, or arbitrary values. Fields are passed to the constructor as regular positional arguments, attributes as keyword arguments. Each node has two attributes: `lineno` (the line number of the node) and `environment`. The `environment` attribute is set at the end of the parsing process for all nodes automatically. """ __metaclass__ = NodeType fields = () attributes = ('lineno', 'environment') abstract = True def __init__(self, *fields, **attributes): if self.abstract: raise TypeError('abstract nodes are not instanciable') if fields: if len(fields) != len(self.fields): if not self.fields: raise TypeError('%r takes 0 arguments' % self.__class__.__name__) raise TypeError('%r takes 0 or %d argument%s' % ( self.__class__.__name__, len(self.fields), len(self.fields) != 1 and 's' or '' )) for name, arg in izip(self.fields, fields): setattr(self, name, arg) for attr in self.attributes: setattr(self, attr, attributes.pop(attr, None)) if attributes: raise TypeError('unknown attribute %r' % iter(attributes).next()) def iter_fields(self, exclude=None, only=None): """This method iterates over all fields that are defined and yields ``(key, value)`` tuples. Per default all fields are returned, but it's possible to limit that to some fields by providing the `only` parameter or to exclude some using the `exclude` parameter. Both should be sets or tuples of field names. """ for name in self.fields: if (exclude is only is None) or \ (exclude is not None and name not in exclude) or \ (only is not None and name in only): try: yield name, getattr(self, name) except AttributeError: pass def iter_child_nodes(self, exclude=None, only=None): """Iterates over all direct child nodes of the node. This iterates over all fields and yields the values of they are nodes. If the value of a field is a list all the nodes in that list are returned. """ for field, item in self.iter_fields(exclude, only): if isinstance(item, list): for n in item: if isinstance(n, Node): yield n elif isinstance(item, Node): yield item def find(self, node_type): """Find the first node of a given type. If no such node exists the return value is `None`. """ for result in self.find_all(node_type): return result def find_all(self, node_type): """Find all the nodes of a given type.""" for child in self.iter_child_nodes(): if isinstance(child, node_type): yield child for result in child.find_all(node_type): yield result def copy(self): """Return a deep copy of the node.""" result = object.__new__(self.__class__) for field, value in self.iter_fields(): if isinstance(value, Node): new_value = value.copy() elif isinstance(value, list): new_value = [] for item in value: if isinstance(item, Node): item = item.copy() else: item = copy(item) new_value.append(item) else: new_value = copy(value) setattr(result, field, new_value) for attr in self.attributes: try: setattr(result, attr, getattr(self, attr)) except AttributeError: pass return result def set_ctx(self, ctx): """Reset the context of a node and all child nodes. Per default the parser will all generate nodes that have a 'load' context as it's the most common one. This method is used in the parser to set assignment targets and other nodes to a store context. """ todo = deque([self]) while todo: node = todo.popleft() if 'ctx' in node.fields: node.ctx = ctx todo.extend(node.iter_child_nodes()) return self def set_lineno(self, lineno, override=False): """Set the line numbers of the node and children.""" todo = deque([self]) while todo: node = todo.popleft() if 'lineno' in node.attributes: if node.lineno is None or override: node.lineno = lineno todo.extend(node.iter_child_nodes()) return self def set_environment(self, environment): """Set the environment for all nodes.""" todo = deque([self]) while todo: node = todo.popleft() node.environment = environment todo.extend(node.iter_child_nodes()) return self def __eq__(self, other): return type(self) is type(other) and \ tuple(self.iter_fields()) == tuple(other.iter_fields()) def __ne__(self, other): return not self.__eq__(other) def __repr__(self): return '%s(%s)' % ( self.__class__.__name__, ', '.join('%s=%r' % (arg, getattr(self, arg, None)) for arg in self.fields) ) class Stmt(Node): """Base node for all statements.""" abstract = True class Helper(Node): """Nodes that exist in a specific context only.""" abstract = True class Template(Node): """Node that represents a template. This must be the outermost node that is passed to the compiler. """ fields = ('body',) class Output(Stmt): """A node that holds multiple expressions which are then printed out. This is used both for the `print` statement and the regular template data. """ fields = ('nodes',) class Extends(Stmt): """Represents an extends statement.""" fields = ('template',) class For(Stmt): """The for loop. `target` is the target for the iteration (usually a :class:`Name` or :class:`Tuple`), `iter` the iterable. `body` is a list of nodes that are used as loop-body, and `else_` a list of nodes for the `else` block. If no else node exists it has to be an empty list. For filtered nodes an expression can be stored as `test`, otherwise `None`. """ fields = ('target', 'iter', 'body', 'else_', 'test', 'recursive') class If(Stmt): """If `test` is true, `body` is rendered, else `else_`.""" fields = ('test', 'body', 'else_') class Macro(Stmt): """A macro definition. `name` is the name of the macro, `args` a list of arguments and `defaults` a list of defaults if there are any. `body` is a list of nodes for the macro body. """ fields = ('name', 'args', 'defaults', 'body') class CallBlock(Stmt): """Like a macro without a name but a call instead. `call` is called with the unnamed macro as `caller` argument this node holds. """ fields = ('call', 'args', 'defaults', 'body') class Set(Stmt): """Allows defining own variables.""" fields = ('name', 'expr') class FilterBlock(Stmt): """Node for filter sections.""" fields = ('body', 'filter') class Block(Stmt): """A node that represents a block.""" fields = ('name', 'body') class Include(Stmt): """A node that represents the include tag.""" fields = ('template', 'with_context') class Import(Stmt): """A node that represents the import tag.""" fields = ('template', 'target', 'with_context') class FromImport(Stmt): """A node that represents the from import tag. It's important to not pass unsafe names to the name attribute. The compiler translates the attribute lookups directly into getattr calls and does *not* use the subscript callback of the interface. As exported variables may not start with double underscores (which the parser asserts) this is not a problem for regular Jinja code, but if this node is used in an extension extra care must be taken. The list of names may contain tuples if aliases are wanted. """ fields = ('template', 'names', 'with_context') class ExprStmt(Stmt): """A statement that evaluates an expression and discards the result.""" fields = ('node',) class Assign(Stmt): """Assigns an expression to a target.""" fields = ('target', 'node') class Expr(Node): """Baseclass for all expressions.""" abstract = True def as_const(self): """Return the value of the expression as constant or raise :exc:`Impossible` if this was not possible: >>> Add(Const(23), Const(42)).as_const() 65 >>> Add(Const(23), Name('var', 'load')).as_const() Traceback (most recent call last): ... Impossible This requires the `environment` attribute of all nodes to be set to the environment that created the nodes. """ raise Impossible() def can_assign(self): """Check if it's possible to assign something to this node.""" return False class BinExpr(Expr): """Baseclass for all binary expressions.""" fields = ('left', 'right') operator = None abstract = True def as_const(self): f = _binop_to_func[self.operator] try: return f(self.left.as_const(), self.right.as_const()) except: raise Impossible() class UnaryExpr(Expr): """Baseclass for all unary expressions.""" fields = ('node',) operator = None abstract = True def as_const(self): f = _uaop_to_func[self.operator] try: return f(self.node.as_const()) except: raise Impossible() class Name(Expr): """Looks up a name or stores a value in a name. The `ctx` of the node can be one of the following values: - `store`: store a value in the name - `load`: load that name - `param`: like `store` but if the name was defined as function parameter. """ fields = ('name', 'ctx') def can_assign(self): return self.name not in ('true', 'false', 'none', 'True', 'False', 'None') class Literal(Expr): """Baseclass for literals.""" abstract = True class Const(Literal): """All constant values. The parser will return this node for simple constants such as ``42`` or ``"foo"`` but it can be used to store more complex values such as lists too. Only constants with a safe representation (objects where ``eval(repr(x)) == x`` is true). """ fields = ('value',) def as_const(self): return self.value @classmethod def from_untrusted(cls, value, lineno=None, environment=None): """Return a const object if the value is representable as constant value in the generated code, otherwise it will raise an `Impossible` exception. """ from compiler import has_safe_repr if not has_safe_repr(value): raise Impossible() return cls(value, lineno=lineno, environment=environment) class TemplateData(Literal): """A constant template string.""" fields = ('data',) def as_const(self): if self.environment.autoescape: return Markup(self.data) return self.data class Tuple(Literal): """For loop unpacking and some other things like multiple arguments for subscripts. Like for :class:`Name` `ctx` specifies if the tuple is used for loading the names or storing. """ fields = ('items', 'ctx') def as_const(self): return tuple(x.as_const() for x in self.items) def can_assign(self): for item in self.items: if not item.can_assign(): return False return True class List(Literal): """Any list literal such as ``[1, 2, 3]``""" fields = ('items',) def as_const(self): return [x.as_const() for x in self.items] class Dict(Literal): """Any dict literal such as ``{1: 2, 3: 4}``. The items must be a list of :class:`Pair` nodes. """ fields = ('items',) def as_const(self): return dict(x.as_const() for x in self.items) class Pair(Helper): """A key, value pair for dicts.""" fields = ('key', 'value') def as_const(self): return self.key.as_const(), self.value.as_const() class Keyword(Helper): """A key, value pair for keyword arguments where key is a string.""" fields = ('key', 'value') class CondExpr(Expr): """A conditional expression (inline if expression). (``{{ foo if bar else baz }}``) """ fields = ('test', 'expr1', 'expr2') def as_const(self): if self.test.as_const(): return self.expr1.as_const() # if we evaluate to an undefined object, we better do that at runtime if self.expr2 is None: raise Impossible() return self.expr2.as_const() class Filter(Expr): """This node applies a filter on an expression. `name` is the name of the filter, the rest of the fields are the same as for :class:`Call`. If the `node` of a filter is `None` the contents of the last buffer are filtered. Buffers are created by macros and filter blocks. """ fields = ('node', 'name', 'args', 'kwargs', 'dyn_args', 'dyn_kwargs') def as_const(self, obj=None): if self.node is obj is None: raise Impossible() filter = self.environment.filters.get(self.name) if filter is None or getattr(filter, 'contextfilter', False): raise Impossible() if obj is None: obj = self.node.as_const() args = [x.as_const() for x in self.args] if getattr(filter, 'environmentfilter', False): args.insert(0, self.environment) kwargs = dict(x.as_const() for x in self.kwargs) if self.dyn_args is not None: try: args.extend(self.dyn_args.as_const()) except: raise Impossible() if self.dyn_kwargs is not None: try: kwargs.update(self.dyn_kwargs.as_const()) except: raise Impossible() try: return filter(obj, *args, **kwargs) except: raise Impossible() class Test(Expr): """Applies a test on an expression. `name` is the name of the test, the rest of the fields are the same as for :class:`Call`. """ fields = ('node', 'name', 'args', 'kwargs', 'dyn_args', 'dyn_kwargs') class Call(Expr): """Calls an expression. `args` is a list of arguments, `kwargs` a list of keyword arguments (list of :class:`Keyword` nodes), and `dyn_args` and `dyn_kwargs` has to be either `None` or a node that is used as node for dynamic positional (``*args``) or keyword (``**kwargs``) arguments. """ fields = ('node', 'args', 'kwargs', 'dyn_args', 'dyn_kwargs') def as_const(self): obj = self.node.as_const() # don't evaluate context functions args = [x.as_const() for x in self.args] if getattr(obj, 'contextfunction', False): raise Impossible() elif getattr(obj, 'environmentfunction', False): args.insert(0, self.environment) kwargs = dict(x.as_const() for x in self.kwargs) if self.dyn_args is not None: try: args.extend(self.dyn_args.as_const()) except: raise Impossible() if self.dyn_kwargs is not None: try: kwargs.update(self.dyn_kwargs.as_const()) except: raise Impossible() try: return obj(*args, **kwargs) except: raise Impossible() class Getitem(Expr): """Get an attribute or item from an expression and prefer the item.""" fields = ('node', 'arg', 'ctx') def as_const(self): if self.ctx != 'load': raise Impossible() try: return self.environment.getitem(self.node.as_const(), self.arg.as_const()) except: raise Impossible() def can_assign(self): return False class Getattr(Expr): """Get an attribute or item from an expression that is a ascii-only bytestring and prefer the attribute. """ fields = ('node', 'attr', 'ctx') def as_const(self): if self.ctx != 'load': raise Impossible() try: return self.environment.getattr(self.node.as_const(), arg) except: raise Impossible() def can_assign(self): return False class Slice(Expr): """Represents a slice object. This must only be used as argument for :class:`Subscript`. """ fields = ('start', 'stop', 'step') def as_const(self): def const(obj): if obj is None: return obj return obj.as_const() return slice(const(self.start), const(self.stop), const(self.step)) class Concat(Expr): """Concatenates the list of expressions provided after converting them to unicode. """ fields = ('nodes',) def as_const(self): return ''.join(unicode(x.as_const()) for x in self.nodes) class Compare(Expr): """Compares an expression with some other expressions. `ops` must be a list of :class:`Operand`\s. """ fields = ('expr', 'ops') def as_const(self): result = value = self.expr.as_const() try: for op in self.ops: new_value = op.expr.as_const() result = _cmpop_to_func[op.op](value, new_value) value = new_value except: raise Impossible() return result class Operand(Helper): """Holds an operator and an expression.""" fields = ('op', 'expr') if __debug__: Operand.__doc__ += '\nThe following operators are available: ' + \ ', '.join(sorted('``%s``' % x for x in set(_binop_to_func) | set(_uaop_to_func) | set(_cmpop_to_func))) class Mul(BinExpr): """Multiplies the left with the right node.""" operator = '*' class Div(BinExpr): """Divides the left by the right node.""" operator = '/' class FloorDiv(BinExpr): """Divides the left by the right node and truncates conver the result into an integer by truncating. """ operator = '//' class Add(BinExpr): """Add the left to the right node.""" operator = '+' class Sub(BinExpr): """Substract the right from the left node.""" operator = '-' class Mod(BinExpr): """Left modulo right.""" operator = '%' class Pow(BinExpr): """Left to the power of right.""" operator = '**' class And(BinExpr): """Short circuited AND.""" operator = 'and' def as_const(self): return self.left.as_const() and self.right.as_const() class Or(BinExpr): """Short circuited OR.""" operator = 'or' def as_const(self): return self.left.as_const() or self.right.as_const() class Not(UnaryExpr): """Negate the expression.""" operator = 'not' class Neg(UnaryExpr): """Make the expression negative.""" operator = '-' class Pos(UnaryExpr): """Make the expression positive (noop for most expressions)""" operator = '+' # Helpers for extensions class EnvironmentAttribute(Expr): """Loads an attribute from the environment object. This is useful for extensions that want to call a callback stored on the environment. """ fields = ('name',) class ExtensionAttribute(Expr): """Returns the attribute of an extension bound to the environment. The identifier is the identifier of the :class:`Extension`. This node is usually constructed by calling the :meth:`~jinja2.ext.Extension.attr` method on an extension. """ fields = ('identifier', 'name') class ImportedName(Expr): """If created with an import name the import name is returned on node access. For example ``ImportedName('cgi.escape')`` returns the `escape` function from the cgi module on evaluation. Imports are optimized by the compiler so there is no need to assign them to local variables. """ fields = ('importname',) class InternalName(Expr): """An internal name in the compiler. You cannot create these nodes yourself but the parser provides a :meth:`~jinja2.parser.Parser.free_identifier` method that creates a new identifier for you. This identifier is not available from the template and is not threated specially by the compiler. """ fields = ('name',) def __init__(self): raise TypeError('Can\'t create internal names. Use the ' '`free_identifier` method on a parser.') class MarkSafe(Expr): """Mark the wrapped expression as safe (wrap it as `Markup`).""" fields = ('expr',) def as_const(self): return Markup(self.expr.as_const()) class ContextReference(Expr): """Returns the current template context.""" class Continue(Stmt): """Continue a loop.""" class Break(Stmt): """Break a loop.""" # make sure nobody creates custom nodes def _failing_new(*args, **kwargs): raise TypeError('can\'t create custom node types') NodeType.__new__ = staticmethod(_failing_new); del _failing_new ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������./weblog+jinja-1.1/jinja2/optimizer.py��������������������������������������������������������������0100644�0001750�0000000�00000004424�11063022145�0016664�0����������������������������������������������������������������������������������������������������ustar�00henry���������������������������wheel������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������# -*- coding: utf-8 -*- """ jinja2.optimizer ~~~~~~~~~~~~~~~~ The jinja optimizer is currently trying to constant fold a few expressions and modify the AST in place so that it should be easier to evaluate it. Because the AST does not contain all the scoping information and the compiler has to find that out, we cannot do all the optimizations we want. For example loop unrolling doesn't work because unrolled loops would have a different scoping. The solution would be a second syntax tree that has the scoping rules stored. :copyright: Copyright 2008 by Christoph Hack, Armin Ronacher. :license: BSD. """ from jinja2 import nodes from jinja2.visitor import NodeTransformer def optimize(node, environment): """The context hint can be used to perform an static optimization based on the context given.""" optimizer = Optimizer(environment) return optimizer.visit(node) class Optimizer(NodeTransformer): def __init__(self, environment): self.environment = environment def visit_If(self, node): """Eliminate dead code.""" # do not optimize ifs that have a block inside so that it doesn't # break super(). if node.find(nodes.Block) is not None: return self.generic_visit(node) try: val = self.visit(node.test).as_const() except nodes.Impossible: return self.generic_visit(node) if val: body = node.body else: body = node.else_ result = [] for node in body: result.extend(self.visit_list(node)) return result def fold(self, node): """Do constant folding.""" node = self.generic_visit(node) try: return nodes.Const.from_untrusted(node.as_const(), lineno=node.lineno, environment=self.environment) except nodes.Impossible: return node visit_Add = visit_Sub = visit_Mul = visit_Div = visit_FloorDiv = \ visit_Pow = visit_Mod = visit_And = visit_Or = visit_Pos = visit_Neg = \ visit_Not = visit_Compare = visit_Getitem = visit_Getattr = visit_Call = \ visit_Filter = visit_Test = visit_CondExpr = fold del fold ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������./weblog+jinja-1.1/jinja2/parser.py�����������������������������������������������������������������0100644�0001750�0000000�00000073254�11063022145�0016145�0����������������������������������������������������������������������������������������������������ustar�00henry���������������������������wheel������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������# -*- coding: utf-8 -*- """ jinja2.parser ~~~~~~~~~~~~~ Implements the template parser. :copyright: 2008 by Armin Ronacher. :license: BSD, see LICENSE for more details. """ from jinja2 import nodes from jinja2.exceptions import TemplateSyntaxError, TemplateAssertionError _statement_keywords = frozenset(['for', 'if', 'block', 'extends', 'print', 'macro', 'include', 'from', 'import', 'set']) _compare_operators = frozenset(['eq', 'ne', 'lt', 'lteq', 'gt', 'gteq']) class Parser(object): """This is the central parsing class Jinja2 uses. It's passed to extensions and can be used to parse expressions or statements. """ def __init__(self, environment, source, name=None, filename=None): self.environment = environment self.stream = environment._tokenize(source, name, filename) self.name = name self.filename = filename self.closed = False self.extensions = {} for extension in environment.extensions.itervalues(): for tag in extension.tags: self.extensions[tag] = extension.parse self._last_identifier = 0 def fail(self, msg, lineno=None, exc=TemplateSyntaxError): """Convenience method that raises `exc` with the message, passed line number or last line number as well as the current name and filename. """ if lineno is None: lineno = self.stream.current.lineno raise exc(msg, lineno, self.name, self.filename) def is_tuple_end(self, extra_end_rules=None): """Are we at the end of a tuple?""" if self.stream.current.type in ('variable_end', 'block_end', 'rparen'): return True elif extra_end_rules is not None: return self.stream.current.test_any(extra_end_rules) return False def free_identifier(self, lineno=None): """Return a new free identifier as :class:`~jinja2.nodes.InternalName`.""" self._last_identifier += 1 rv = object.__new__(nodes.InternalName) nodes.Node.__init__(rv, 'fi%d' % self._last_identifier, lineno=lineno) return rv def parse_statement(self): """Parse a single statement.""" token = self.stream.current if token.type is not 'name': self.fail('tag name expected', token.lineno) if token.value in _statement_keywords: return getattr(self, 'parse_' + self.stream.current.value)() if token.value == 'call': return self.parse_call_block() if token.value == 'filter': return self.parse_filter_block() ext = self.extensions.get(token.value) if ext is not None: return ext(self) self.fail('unknown tag %r' % token.value, token.lineno) def parse_statements(self, end_tokens, drop_needle=False): """Parse multiple statements into a list until one of the end tokens is reached. This is used to parse the body of statements as it also parses template data if appropriate. The parser checks first if the current token is a colon and skips it if there is one. Then it checks for the block end and parses until if one of the `end_tokens` is reached. Per default the active token in the stream at the end of the call is the matched end token. If this is not wanted `drop_needle` can be set to `True` and the end token is removed. """ # the first token may be a colon for python compatibility self.stream.skip_if('colon') # in the future it would be possible to add whole code sections # by adding some sort of end of statement token and parsing those here. self.stream.expect('block_end') result = self.subparse(end_tokens) if drop_needle: self.stream.next() return result def parse_set(self): """Parse an assign statement.""" lineno = self.stream.next().lineno target = self.parse_assign_target() self.stream.expect('assign') expr = self.parse_tuple() return nodes.Assign(target, expr, lineno=lineno) def parse_for(self): """Parse a for loop.""" lineno = self.stream.expect('name:for').lineno target = self.parse_assign_target(extra_end_rules=('name:in',)) self.stream.expect('name:in') iter = self.parse_tuple(with_condexpr=False, extra_end_rules=('name:recursive',)) test = None if self.stream.skip_if('name:if'): test = self.parse_expression() recursive = self.stream.skip_if('name:recursive') body = self.parse_statements(('name:endfor', 'name:else')) if self.stream.next().value == 'endfor': else_ = [] else: else_ = self.parse_statements(('name:endfor',), drop_needle=True) return nodes.For(target, iter, body, else_, test, recursive, lineno=lineno) def parse_if(self): """Parse an if construct.""" node = result = nodes.If(lineno=self.stream.expect('name:if').lineno) while 1: node.test = self.parse_tuple(with_condexpr=False) node.body = self.parse_statements(('name:elif', 'name:else', 'name:endif')) token = self.stream.next() if token.test('name:elif'): new_node = nodes.If(lineno=self.stream.current.lineno) node.else_ = [new_node] node = new_node continue elif token.test('name:else'): node.else_ = self.parse_statements(('name:endif',), drop_needle=True) else: node.else_ = [] break return result def parse_block(self): node = nodes.Block(lineno=self.stream.next().lineno) node.name = self.stream.expect('name').value node.body = self.parse_statements(('name:endblock',), drop_needle=True) self.stream.skip_if('name:' + node.name) return node def parse_extends(self): node = nodes.Extends(lineno=self.stream.next().lineno) node.template = self.parse_expression() return node def parse_import_context(self, node, default): if self.stream.current.test_any('name:with', 'name:without') and \ self.stream.look().test('name:context'): node.with_context = self.stream.next().value == 'with' self.stream.skip() else: node.with_context = default return node def parse_include(self): node = nodes.Include(lineno=self.stream.next().lineno) node.template = self.parse_expression() return self.parse_import_context(node, True) def parse_import(self): node = nodes.Import(lineno=self.stream.next().lineno) node.template = self.parse_expression() self.stream.expect('name:as') node.target = self.parse_assign_target(name_only=True).name return self.parse_import_context(node, False) def parse_from(self): node = nodes.FromImport(lineno=self.stream.next().lineno) node.template = self.parse_expression() self.stream.expect('name:import') node.names = [] def parse_context(): if self.stream.current.value in ('with', 'without') and \ self.stream.look().test('name:context'): node.with_context = self.stream.next().value == 'with' self.stream.skip() return True return False while 1: if node.names: self.stream.expect('comma') if self.stream.current.type is 'name': if parse_context(): break target = self.parse_assign_target(name_only=True) if target.name.startswith('_'): self.fail('names starting with an underline can not ' 'be imported', target.lineno, exc=TemplateAssertionError) if self.stream.skip_if('name:as'): alias = self.parse_assign_target(name_only=True) node.names.append((target.name, alias.name)) else: node.names.append(target.name) if parse_context() or self.stream.current.type is not 'comma': break else: break if not hasattr(node, 'with_context'): node.with_context = False self.stream.skip_if('comma') return node def parse_signature(self, node): node.args = args = [] node.defaults = defaults = [] self.stream.expect('lparen') while self.stream.current.type is not 'rparen': if args: self.stream.expect('comma') arg = self.parse_assign_target(name_only=True) arg.set_ctx('param') if self.stream.skip_if('assign'): defaults.append(self.parse_expression()) args.append(arg) self.stream.expect('rparen') def parse_call_block(self): node = nodes.CallBlock(lineno=self.stream.next().lineno) if self.stream.current.type is 'lparen': self.parse_signature(node) else: node.args = [] node.defaults = [] node.call = self.parse_expression() if not isinstance(node.call, nodes.Call): self.fail('expected call', node.lineno) node.body = self.parse_statements(('name:endcall',), drop_needle=True) return node def parse_filter_block(self): node = nodes.FilterBlock(lineno=self.stream.next().lineno) node.filter = self.parse_filter(None, start_inline=True) node.body = self.parse_statements(('name:endfilter',), drop_needle=True) return node def parse_macro(self): node = nodes.Macro(lineno=self.stream.next().lineno) node.name = self.parse_assign_target(name_only=True).name self.parse_signature(node) node.body = self.parse_statements(('name:endmacro',), drop_needle=True) return node def parse_print(self): node = nodes.Output(lineno=self.stream.next().lineno) node.nodes = [] while self.stream.current.type is not 'block_end': if node.nodes: self.stream.expect('comma') node.nodes.append(self.parse_expression()) return node def parse_assign_target(self, with_tuple=True, name_only=False, extra_end_rules=None): """Parse an assignment target. As Jinja2 allows assignments to tuples, this function can parse all allowed assignment targets. Per default assignments to tuples are parsed, that can be disable however by setting `with_tuple` to `False`. If only assignments to names are wanted `name_only` can be set to `True`. The `extra_end_rules` parameter is forwarded to the tuple parsing function. """ if name_only: token = self.stream.expect('name') target = nodes.Name(token.value, 'store', lineno=token.lineno) else: if with_tuple: target = self.parse_tuple(simplified=True, extra_end_rules=extra_end_rules) else: target = self.parse_primary(with_postfix=False) target.set_ctx('store') if not target.can_assign(): self.fail('can\'t assign to %r' % target.__class__. __name__.lower(), target.lineno) return target def parse_expression(self, with_condexpr=True): """Parse an expression. Per default all expressions are parsed, if the optional `with_condexpr` parameter is set to `False` conditional expressions are not parsed. """ if with_condexpr: return self.parse_condexpr() return self.parse_or() def parse_condexpr(self): lineno = self.stream.current.lineno expr1 = self.parse_or() while self.stream.skip_if('name:if'): expr2 = self.parse_or() if self.stream.skip_if('name:else'): expr3 = self.parse_condexpr() else: expr3 = None expr1 = nodes.CondExpr(expr2, expr1, expr3, lineno=lineno) lineno = self.stream.current.lineno return expr1 def parse_or(self): lineno = self.stream.current.lineno left = self.parse_and() while self.stream.skip_if('name:or'): right = self.parse_and() left = nodes.Or(left, right, lineno=lineno) lineno = self.stream.current.lineno return left def parse_and(self): lineno = self.stream.current.lineno left = self.parse_compare() while self.stream.skip_if('name:and'): right = self.parse_compare() left = nodes.And(left, right, lineno=lineno) lineno = self.stream.current.lineno return left def parse_compare(self): lineno = self.stream.current.lineno expr = self.parse_add() ops = [] while 1: token_type = self.stream.current.type if token_type in _compare_operators: self.stream.next() ops.append(nodes.Operand(token_type, self.parse_add())) elif self.stream.skip_if('name:in'): ops.append(nodes.Operand('in', self.parse_add())) elif self.stream.current.test('name:not') and \ self.stream.look().test('name:in'): self.stream.skip(2) ops.append(nodes.Operand('notin', self.parse_add())) else: break lineno = self.stream.current.lineno if not ops: return expr return nodes.Compare(expr, ops, lineno=lineno) def parse_add(self): lineno = self.stream.current.lineno left = self.parse_sub() while self.stream.current.type is 'add': self.stream.next() right = self.parse_sub() left = nodes.Add(left, right, lineno=lineno) lineno = self.stream.current.lineno return left def parse_sub(self): lineno = self.stream.current.lineno left = self.parse_concat() while self.stream.current.type is 'sub': self.stream.next() right = self.parse_concat() left = nodes.Sub(left, right, lineno=lineno) lineno = self.stream.current.lineno return left def parse_concat(self): lineno = self.stream.current.lineno args = [self.parse_mul()] while self.stream.current.type is 'tilde': self.stream.next() args.append(self.parse_mul()) if len(args) == 1: return args[0] return nodes.Concat(args, lineno=lineno) def parse_mul(self): lineno = self.stream.current.lineno left = self.parse_div() while self.stream.current.type is 'mul': self.stream.next() right = self.parse_div() left = nodes.Mul(left, right, lineno=lineno) lineno = self.stream.current.lineno return left def parse_div(self): lineno = self.stream.current.lineno left = self.parse_floordiv() while self.stream.current.type is 'div': self.stream.next() right = self.parse_floordiv() left = nodes.Div(left, right, lineno=lineno) lineno = self.stream.current.lineno return left def parse_floordiv(self): lineno = self.stream.current.lineno left = self.parse_mod() while self.stream.current.type is 'floordiv': self.stream.next() right = self.parse_mod() left = nodes.FloorDiv(left, right, lineno=lineno) lineno = self.stream.current.lineno return left def parse_mod(self): lineno = self.stream.current.lineno left = self.parse_pow() while self.stream.current.type is 'mod': self.stream.next() right = self.parse_pow() left = nodes.Mod(left, right, lineno=lineno) lineno = self.stream.current.lineno return left def parse_pow(self): lineno = self.stream.current.lineno left = self.parse_unary() while self.stream.current.type is 'pow': self.stream.next() right = self.parse_unary() left = nodes.Pow(left, right, lineno=lineno) lineno = self.stream.current.lineno return left def parse_unary(self): token_type = self.stream.current.type lineno = self.stream.current.lineno if token_type is 'name' and self.stream.current.value == 'not': self.stream.next() node = self.parse_unary() return nodes.Not(node, lineno=lineno) if token_type is 'sub': self.stream.next() node = self.parse_unary() return nodes.Neg(node, lineno=lineno) if token_type is 'add': self.stream.next() node = self.parse_unary() return nodes.Pos(node, lineno=lineno) return self.parse_primary() def parse_primary(self, with_postfix=True): token = self.stream.current if token.type is 'name': if token.value in ('true', 'false', 'True', 'False'): node = nodes.Const(token.value in ('true', 'True'), lineno=token.lineno) elif token.value in ('none', 'None'): node = nodes.Const(None, lineno=token.lineno) else: node = nodes.Name(token.value, 'load', lineno=token.lineno) self.stream.next() elif token.type is 'string': self.stream.next() buf = [token.value] lineno = token.lineno while self.stream.current.type is 'string': buf.append(self.stream.current.value) self.stream.next() node = nodes.Const(''.join(buf), lineno=lineno) elif token.type in ('integer', 'float'): self.stream.next() node = nodes.Const(token.value, lineno=token.lineno) elif token.type is 'lparen': self.stream.next() node = self.parse_tuple() self.stream.expect('rparen') elif token.type is 'lbracket': node = self.parse_list() elif token.type is 'lbrace': node = self.parse_dict() else: self.fail("unexpected token '%s'" % (token,), token.lineno) if with_postfix: node = self.parse_postfix(node) return node def parse_tuple(self, simplified=False, with_condexpr=True, extra_end_rules=None): """Works like `parse_expression` but if multiple expressions are delimited by a comma a :class:`~jinja2.nodes.Tuple` node is created. This method could also return a regular expression instead of a tuple if no commas where found. The default parsing mode is a full tuple. If `simplified` is `True` only names and literals are parsed. The `no_condexpr` parameter is forwarded to :meth:`parse_expression`. Because tuples do not require delimiters and may end in a bogus comma an extra hint is needed that marks the end of a tuple. For example for loops support tuples between `for` and `in`. In that case the `extra_end_rules` is set to ``['name:in']``. """ lineno = self.stream.current.lineno if simplified: parse = lambda: self.parse_primary(with_postfix=False) elif with_condexpr: parse = self.parse_expression else: parse = lambda: self.parse_expression(with_condexpr=False) args = [] is_tuple = False while 1: if args: self.stream.expect('comma') if self.is_tuple_end(extra_end_rules): break args.append(parse()) if self.stream.current.type is 'comma': is_tuple = True else: break lineno = self.stream.current.lineno if not is_tuple and args: return args[0] return nodes.Tuple(args, 'load', lineno=lineno) def parse_list(self): token = self.stream.expect('lbracket') items = [] while self.stream.current.type is not '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.List(items, lineno=token.lineno) def parse_dict(self): token = self.stream.expect('lbrace') items = [] while self.stream.current.type is not '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(nodes.Pair(key, value, lineno=key.lineno)) self.stream.expect('rbrace') return nodes.Dict(items, lineno=token.lineno) def parse_postfix(self, node): while 1: token_type = self.stream.current.type if token_type is 'dot' or token_type is 'lbracket': node = self.parse_subscript(node) elif token_type is 'lparen': node = self.parse_call(node) elif token_type is 'pipe': node = self.parse_filter(node) elif token_type is 'name' and self.stream.current.value == 'is': node = self.parse_test(node) else: break return node def parse_subscript(self, node): token = self.stream.next() if token.type is 'dot': attr_token = self.stream.current self.stream.next() if attr_token.type is 'name': return nodes.Getattr(node, attr_token.value, 'load', lineno=token.lineno) elif attr_token.type is not 'integer': self.fail('expected name or number', attr_token.lineno) arg = nodes.Const(attr_token.value, lineno=attr_token.lineno) return nodes.Getitem(node, arg, 'load', lineno=token.lineno) if token.type is 'lbracket': priority_on_attribute = False args = [] while self.stream.current.type is not 'rbracket': if args: self.stream.expect('comma') args.append(self.parse_subscribed()) self.stream.expect('rbracket') if len(args) == 1: arg = args[0] else: arg = nodes.Tuple(args, self.lineno, self.filename) return nodes.Getitem(node, arg, 'load', lineno=token.lineno) self.fail('expected subscript expression', self.lineno) def parse_subscribed(self): lineno = self.stream.current.lineno if self.stream.current.type is 'colon': self.stream.next() args = [None] else: node = self.parse_expression() if self.stream.current.type is not 'colon': return node self.stream.next() args = [node] if self.stream.current.type is '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 is '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.Slice(lineno=lineno, *args) def parse_call(self, node): token = self.stream.expect('lparen') args = [] kwargs = [] dyn_args = dyn_kwargs = None require_comma = False def ensure(expr): if not expr: self.fail('invalid syntax for function call expression', token.lineno) while self.stream.current.type is not 'rparen': if require_comma: self.stream.expect('comma') # support for trailing comma if self.stream.current.type is 'rparen': break if self.stream.current.type is 'mul': ensure(dyn_args is None and dyn_kwargs is None) self.stream.next() dyn_args = self.parse_expression() elif self.stream.current.type is '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 is 'name' and \ self.stream.look().type is 'assign': key = self.stream.current.value self.stream.skip(2) value = self.parse_expression() kwargs.append(nodes.Keyword(key, value, lineno=value.lineno)) else: ensure(not kwargs) args.append(self.parse_expression()) require_comma = True self.stream.expect('rparen') if node is None: return args, kwargs, dyn_args, dyn_kwargs return nodes.Call(node, args, kwargs, dyn_args, dyn_kwargs, lineno=token.lineno) def parse_filter(self, node, start_inline=False): while self.stream.current.type == 'pipe' or start_inline: if not start_inline: self.stream.next() token = self.stream.expect('name') name = token.value while self.stream.current.type is 'dot': self.stream.next() name += '.' + self.stream.expect('name').value if self.stream.current.type is 'lparen': args, kwargs, dyn_args, dyn_kwargs = self.parse_call(None) else: args = [] kwargs = [] dyn_args = dyn_kwargs = None node = nodes.Filter(node, name, args, kwargs, dyn_args, dyn_kwargs, lineno=token.lineno) start_inline = False return node def parse_test(self, node): token = self.stream.next() if self.stream.current.test('name:not'): self.stream.next() negated = True else: negated = False name = self.stream.expect('name').value while self.stream.current.type is 'dot': self.stream.next() name += '.' + self.stream.expect('name').value dyn_args = dyn_kwargs = None kwargs = [] if self.stream.current.type is 'lparen': args, kwargs, dyn_args, dyn_kwargs = self.parse_call(None) elif self.stream.current.type in ('name', 'string', 'integer', 'float', 'lparen', 'lbracket', 'lbrace') and not \ self.stream.current.test_any('name:else', 'name:or', 'name:and'): if self.stream.current.test('name:is'): self.fail('You cannot chain multiple tests with is') args = [self.parse_expression()] else: args = [] node = nodes.Test(node, name, args, kwargs, dyn_args, dyn_kwargs, lineno=token.lineno) if negated: node = nodes.Not(node, lineno=token.lineno) return node def subparse(self, end_tokens=None): body = [] data_buffer = [] add_data = data_buffer.append def flush_data(): if data_buffer: lineno = data_buffer[0].lineno body.append(nodes.Output(data_buffer[:], lineno=lineno)) del data_buffer[:] while self.stream: token = self.stream.current if token.type is 'data': if token.value: add_data(nodes.TemplateData(token.value, lineno=token.lineno)) self.stream.next() elif token.type is 'variable_begin': self.stream.next() add_data(self.parse_tuple(with_condexpr=True)) self.stream.expect('variable_end') elif token.type is 'block_begin': flush_data() self.stream.next() if end_tokens is not None and \ self.stream.current.test_any(*end_tokens): return body rv = self.parse_statement() if isinstance(rv, list): body.extend(rv) else: body.append(rv) self.stream.expect('block_end') else: raise AssertionError('internal parsing error') flush_data() return body def parse(self): """Parse the whole template into a `Template` node.""" result = nodes.Template(self.subparse(), lineno=1) result.set_environment(self.environment) return result ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������./weblog+jinja-1.1/jinja2/runtime.py����������������������������������������������������������������0100644�0001750�0000000�00000036131�11063022145�0016325�0����������������������������������������������������������������������������������������������������ustar�00henry���������������������������wheel������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������# -*- coding: utf-8 -*- """ jinja2.runtime ~~~~~~~~~~~~~~ Runtime helpers. :copyright: Copyright 2008 by Armin Ronacher. :license: BSD. """ import sys from types import FunctionType, MethodType from itertools import chain, imap from jinja2.utils import Markup, partial, soft_unicode, escape, missing, concat from jinja2.exceptions import UndefinedError, TemplateRuntimeError # these variables are exported to the template runtime __all__ = ['LoopContext', 'Context', 'TemplateReference', 'Macro', 'Markup', 'TemplateRuntimeError', 'missing', 'concat', 'escape', 'markup_join', 'unicode_join'] _context_function_types = (FunctionType, MethodType) def markup_join(seq): """Concatenation that escapes if necessary and converts to unicode.""" buf = [] iterator = imap(soft_unicode, seq) for arg in iterator: buf.append(arg) if hasattr(arg, '__html__'): return Markup(u'').join(chain(buf, iterator)) return concat(buf) def unicode_join(seq): """Simple args to unicode conversion and concatenation.""" return concat(imap(unicode, seq)) class Context(object): """The template context holds the variables of a template. It stores the values passed to the template and also the names the template exports. Creating instances is neither supported nor useful as it's created automatically at various stages of the template evaluation and should not be created by hand. The context is immutable. Modifications on :attr:`parent` **must not** happen and modifications on :attr:`vars` are allowed from generated template code only. Template filters and global functions marked as :func:`contextfunction`\s get the active context passed as first argument and are allowed to access the context read-only. The template context supports read only dict operations (`get`, `keys`, `values`, `items`, `iterkeys`, `itervalues`, `iteritems`, `__getitem__`, `__contains__`). Additionally there is a :meth:`resolve` method that doesn't fail with a `KeyError` but returns an :class:`Undefined` object for missing variables. """ __slots__ = ('parent', 'vars', 'environment', 'exported_vars', 'name', 'blocks') def __init__(self, environment, parent, name, blocks): self.parent = parent self.vars = vars = {} self.environment = environment self.exported_vars = set() self.name = name # create the initial mapping of blocks. Whenever template inheritance # takes place the runtime will update this mapping with the new blocks # from the template. self.blocks = dict((k, [v]) for k, v in blocks.iteritems()) def super(self, name, current): """Render a parent block.""" try: blocks = self.blocks[name] block = blocks[blocks.index(current) + 1] except LookupError: return self.environment.undefined('there is no parent block ' 'called %r.' % name, name='super') wrap = self.environment.autoescape and Markup or (lambda x: x) render = lambda: wrap(concat(block(self))) render.__name__ = render.name = name return render def get(self, key, default=None): """Returns an item from the template context, if it doesn't exist `default` is returned. """ try: return self[key] except KeyError: return default def resolve(self, key): """Looks up a variable like `__getitem__` or `get` but returns an :class:`Undefined` object with the name of the name looked up. """ if key in self.vars: return self.vars[key] if key in self.parent: return self.parent[key] return self.environment.undefined(name=key) def get_exported(self): """Get a new dict with the exported variables.""" return dict((k, self.vars[k]) for k in self.exported_vars) def get_all(self): """Return a copy of the complete context as dict including the exported variables. """ return dict(self.parent, **self.vars) def call(__self, __obj, *args, **kwargs): """Call the callable with the arguments and keyword arguments provided but inject the active context or environment as first argument if the callable is a :func:`contextfunction` or :func:`environmentfunction`. """ if __debug__: __traceback_hide__ = True if isinstance(__obj, _context_function_types): if getattr(__obj, 'contextfunction', 0): args = (__self,) + args elif getattr(__obj, 'environmentfunction', 0): args = (__self.environment,) + args return __obj(*args, **kwargs) def _all(meth): proxy = lambda self: getattr(self.get_all(), meth)() proxy.__doc__ = getattr(dict, meth).__doc__ proxy.__name__ = meth return proxy keys = _all('keys') values = _all('values') items = _all('items') iterkeys = _all('iterkeys') itervalues = _all('itervalues') iteritems = _all('iteritems') del _all def __contains__(self, name): return name in self.vars or name in self.parent def __getitem__(self, key): """Lookup a variable or raise `KeyError` if the variable is undefined. """ item = self.resolve(key) if isinstance(item, Undefined): raise KeyError(key) return item def __repr__(self): return '<%s %s of %r>' % ( self.__class__.__name__, repr(self.get_all()), self.name ) # register the context as mutable mapping if possible try: from collections import MutableMapping MutableMapping.register(Context) except ImportError: pass class TemplateReference(object): """The `self` in templates.""" def __init__(self, context): self.__context = context def __getitem__(self, name): func = self.__context.blocks[name][0] wrap = self.__context.environment.autoescape and \ Markup or (lambda x: x) render = lambda: wrap(concat(func(self.__context))) render.__name__ = render.name = name return render def __repr__(self): return '<%s %r>' % ( self.__class__.__name__, self._context.name ) class LoopContext(object): """A loop context for dynamic iteration.""" def __init__(self, iterable, recurse=None): self._iterator = iter(iterable) self._recurse = recurse self.index0 = -1 # try to get the length of the iterable early. This must be done # here because there are some broken iterators around where there # __len__ is the number of iterations left (i'm looking at your # listreverseiterator!). try: self._length = len(iterable) except (TypeError, AttributeError): self._length = None def cycle(self, *args): """Cycles among the arguments with the current loop index.""" if not args: raise TypeError('no items for cycling given') return args[self.index0 % len(args)] first = property(lambda x: x.index0 == 0) last = property(lambda x: x.index0 + 1 == x.length) index = property(lambda x: x.index0 + 1) revindex = property(lambda x: x.length - x.index0) revindex0 = property(lambda x: x.length - x.index) def __len__(self): return self.length def __iter__(self): return LoopContextIterator(self) def loop(self, iterable): if self._recurse is None: raise TypeError('Tried to call non recursive loop. Maybe you ' "forgot the 'recursive' modifier.") return self._recurse(iterable, self._recurse) # a nifty trick to enhance the error message if someone tried to call # the the loop without or with too many arguments. __call__ = loop; del loop @property def length(self): if self._length is None: # if was not possible to get the length of the iterator when # the loop context was created (ie: iterating over a generator) # we have to convert the iterable into a sequence and use the # length of that. iterable = tuple(self._iterator) self._iterator = iter(iterable) self._length = len(iterable) + self.index0 + 1 return self._length def __repr__(self): return '<%s %r/%r>' % ( self.__class__.__name__, self.index, self.length ) class LoopContextIterator(object): """The iterator for a loop context.""" __slots__ = ('context',) def __init__(self, context): self.context = context def __iter__(self): return self def next(self): ctx = self.context ctx.index0 += 1 return ctx._iterator.next(), ctx class Macro(object): """Wraps a macro.""" def __init__(self, environment, func, name, arguments, defaults, catch_kwargs, catch_varargs, caller): self._environment = environment self._func = func self._argument_count = len(arguments) self.name = name self.arguments = arguments self.defaults = defaults self.catch_kwargs = catch_kwargs self.catch_varargs = catch_varargs self.caller = caller def __call__(self, *args, **kwargs): arguments = [] for idx, name in enumerate(self.arguments): try: value = args[idx] except: try: value = kwargs.pop(name) except: try: value = self.defaults[idx - self._argument_count] except: value = self._environment.undefined( 'parameter %r was not provided' % name, name=name) arguments.append(value) # it's important that the order of these arguments does not change # if not also changed in the compiler's `function_scoping` method. # the order is caller, keyword arguments, positional arguments! if self.caller: caller = kwargs.pop('caller', None) if caller is None: caller = self._environment.undefined('No caller defined', name='caller') arguments.append(caller) if self.catch_kwargs: arguments.append(kwargs) elif kwargs: raise TypeError('macro %r takes no keyword argument %r' % (self.name, iter(kwargs).next())) if self.catch_varargs: arguments.append(args[self._argument_count:]) elif len(args) > self._argument_count: raise TypeError('macro %r takes not more than %d argument(s)' % (self.name, len(self.arguments))) return self._func(*arguments) def __repr__(self): return '<%s %s>' % ( self.__class__.__name__, self.name is None and 'anonymous' or repr(self.name) ) class Undefined(object): """The default undefined type. This undefined type can be printed and iterated over, but every other access will raise an :exc:`UndefinedError`: >>> foo = Undefined(name='foo') >>> str(foo) '' >>> not foo True >>> foo + 42 Traceback (most recent call last): ... UndefinedError: 'foo' is undefined """ __slots__ = ('_undefined_hint', '_undefined_obj', '_undefined_name', '_undefined_exception') def __init__(self, hint=None, obj=None, name=None, exc=UndefinedError): self._undefined_hint = hint self._undefined_obj = obj self._undefined_name = name self._undefined_exception = exc def _fail_with_undefined_error(self, *args, **kwargs): """Regular callback function for undefined objects that raises an `UndefinedError` on call. """ if self._undefined_hint is None: if self._undefined_obj is None: hint = '%r is undefined' % self._undefined_name elif not isinstance(self._undefined_name, basestring): hint = '%r object has no element %r' % ( self._undefined_obj.__class__.__name__, self._undefined_name ) else: hint = '%r object has no attribute %r' % ( self._undefined_obj.__class__.__name__, self._undefined_name ) else: hint = self._undefined_hint raise self._undefined_exception(hint) __add__ = __radd__ = __mul__ = __rmul__ = __div__ = __rdiv__ = \ __realdiv__ = __rrealdiv__ = __floordiv__ = __rfloordiv__ = \ __mod__ = __rmod__ = __pos__ = __neg__ = __call__ = \ __getattr__ = __getitem__ = __lt__ = __le__ = __gt__ = __ge__ = \ __int__ = __float__ = __complex__ = _fail_with_undefined_error def __str__(self): return self.__unicode__().encode('utf-8') def __unicode__(self): return u'' def __len__(self): return 0 def __iter__(self): if 0: yield None def __nonzero__(self): return False def __repr__(self): return 'Undefined' class DebugUndefined(Undefined): """An undefined that returns the debug info when printed. >>> foo = DebugUndefined(name='foo') >>> str(foo) '{{ foo }}' >>> not foo True >>> foo + 42 Traceback (most recent call last): ... UndefinedError: 'foo' is undefined """ __slots__ = () def __unicode__(self): if self._undefined_hint is None: if self._undefined_obj is None: return u'{{ %s }}' % self._undefined_name return '{{ no such element: %s[%r] }}' % ( self._undefined_obj.__class__.__name__, self._undefined_name ) return u'{{ undefined value printed: %s }}' % self._undefined_hint class StrictUndefined(Undefined): """An undefined that barks on print and iteration as well as boolean tests and all kinds of comparisons. In other words: you can do nothing with it except checking if it's defined using the `defined` test. >>> foo = StrictUndefined(name='foo') >>> str(foo) Traceback (most recent call last): ... UndefinedError: 'foo' is undefined >>> not foo Traceback (most recent call last): ... UndefinedError: 'foo' is undefined >>> foo + 42 Traceback (most recent call last): ... UndefinedError: 'foo' is undefined """ __slots__ = () __iter__ = __unicode__ = __len__ = __nonzero__ = __eq__ = __ne__ = \ Undefined._fail_with_undefined_error # remove remaining slots attributes, after the metaclass did the magic they # are unneeded and irritating as they contain wrong data for the subclasses. del Undefined.__slots__, DebugUndefined.__slots__, StrictUndefined.__slots__ ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������./weblog+jinja-1.1/jinja2/sandbox.py����������������������������������������������������������������0100644�0001750�0000000�00000021342�11063022145�0016276�0����������������������������������������������������������������������������������������������������ustar�00henry���������������������������wheel������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������# -*- coding: utf-8 -*- """ jinja2.sandbox ~~~~~~~~~~~~~~ Adds a sandbox layer to Jinja as it was the default behavior in the old Jinja 1 releases. This sandbox is slightly different from Jinja 1 as the default behavior is easier to use. The behavior can be changed by subclassing the environment. :copyright: Copyright 2008 by Armin Ronacher. :license: BSD. """ import operator from types import FunctionType, MethodType, TracebackType, CodeType, \ FrameType, GeneratorType from jinja2.runtime import Undefined from jinja2.environment import Environment from jinja2.exceptions import SecurityError #: maximum number of items a range may produce MAX_RANGE = 100000 #: attributes of function objects that are considered unsafe. UNSAFE_FUNCTION_ATTRIBUTES = set(['func_closure', 'func_code', 'func_dict', 'func_defaults', 'func_globals']) #: unsafe method attributes. function attributes are unsafe for methods too UNSAFE_METHOD_ATTRIBUTES = set(['im_class', 'im_func', 'im_self']) from collections import deque from sets import Set, ImmutableSet from UserDict import UserDict, DictMixin from UserList import UserList _mutable_set_types = (ImmutableSet, Set, set) _mutable_mapping_types = (UserDict, DictMixin, dict) _mutable_sequence_types = (UserList, list) #: register Python 2.6 abstract base classes try: from collections import MutableSet, MutableMapping, MutableSequence _mutable_set_types += (MutableSet,) _mutable_mapping_types += (MutableMapping,) _mutable_sequence_types += (MutableSequence,) except ImportError: pass _mutable_spec = ( (_mutable_set_types, frozenset([ 'add', 'clear', 'difference_update', 'discard', 'pop', 'remove', 'symmetric_difference_update', 'update' ])), (_mutable_mapping_types, frozenset([ 'clear', 'pop', 'popitem', 'setdefault', 'update' ])), (_mutable_sequence_types, frozenset([ 'append', 'reverse', 'insert', 'sort', 'extend', 'remove' ])), (deque, frozenset([ 'append', 'appendleft', 'clear', 'extend', 'extendleft', 'pop', 'popleft', 'remove', 'rotate' ])) ) def safe_range(*args): """A range that can't generate ranges with a length of more than MAX_RANGE items. """ rng = xrange(*args) if len(rng) > MAX_RANGE: raise OverflowError('range too big, maximum size for range is %d' % MAX_RANGE) return rng def unsafe(f): """ Mark a function or method as unsafe:: @unsafe def delete(self): pass """ f.unsafe_callable = True return f def is_internal_attribute(obj, attr): """Test if the attribute given is an internal python attribute. For example this function returns `True` for the `func_code` attribute of python objects. This is useful if the environment method :meth:`~SandboxedEnvironment.is_safe_attribute` is overriden. >>> from jinja2.sandbox import is_internal_attribute >>> is_internal_attribute(lambda: None, "func_code") True >>> is_internal_attribute((lambda x:x).func_code, 'co_code') True >>> is_internal_attribute(str, "upper") False """ if isinstance(obj, FunctionType): if attr in UNSAFE_FUNCTION_ATTRIBUTES: return True elif isinstance(obj, MethodType): if attr in UNSAFE_FUNCTION_ATTRIBUTES or \ attr in UNSAFE_METHOD_ATTRIBUTES: return True elif isinstance(obj, type): if attr == 'mro': return True elif isinstance(obj, (CodeType, TracebackType, FrameType)): return True elif isinstance(obj, GeneratorType): if attr == 'gi_frame': return True return attr.startswith('__') def modifies_known_mutable(obj, attr): """This function checks if an attribute on a builtin mutable object (list, dict, set or deque) would modify it if called. It also supports the "user"-versions of the objects (`sets.Set`, `UserDict.*` etc.) and with Python 2.6 onwards the abstract base classes `MutableSet`, `MutableMapping`, and `MutableSequence`. >>> modifies_known_mutable({}, "clear") True >>> modifies_known_mutable({}, "keys") False >>> modifies_known_mutable([], "append") True >>> modifies_known_mutable([], "index") False If called with an unsupported object (such as unicode) `False` is returned. >>> modifies_known_mutable("foo", "upper") False """ for typespec, unsafe in _mutable_spec: if isinstance(obj, typespec): return attr in unsafe return False class SandboxedEnvironment(Environment): """The sandboxed environment. It works like the regular environment but tells the compiler to generate sandboxed code. Additionally subclasses of this environment may override the methods that tell the runtime what attributes or functions are safe to access. If the template tries to access insecure code a :exc:`SecurityError` is raised. However also other exceptions may occour during the rendering so the caller has to ensure that all exceptions are catched. """ sandboxed = True def __init__(self, *args, **kwargs): Environment.__init__(self, *args, **kwargs) self.globals['range'] = safe_range def is_safe_attribute(self, obj, attr, value): """The sandboxed environment will call this method to check if the attribute of an object is safe to access. Per default all attributes starting with an underscore are considered private as well as the special attributes of internal python objects as returned by the :func:`is_internal_attribute` function. """ return not (attr.startswith('_') or is_internal_attribute(obj, attr)) def is_safe_callable(self, obj): """Check if an object is safely callable. Per default a function is considered safe unless the `unsafe_callable` attribute exists and is True. Override this method to alter the behavior, but this won't affect the `unsafe` decorator from this module. """ return not (getattr(obj, 'unsafe_callable', False) or \ getattr(obj, 'alters_data', False)) def getitem(self, obj, argument): """Subscribe an object from sandboxed code.""" try: return obj[argument] except (TypeError, LookupError): if isinstance(argument, basestring): try: attr = str(argument) except: pass else: try: value = getattr(obj, attr) except AttributeError: pass else: if self.is_safe_attribute(obj, argument, value): return value return self.unsafe_undefined(obj, argument) return self.undefined(obj=obj, name=argument) def getattr(self, obj, attribute): """Subscribe an object from sandboxed code and prefer the attribute. The attribute passed *must* be a bytestring. """ try: value = getattr(obj, attribute) except AttributeError: try: return obj[argument] except (TypeError, LookupError): pass else: if self.is_safe_attribute(obj, attribute, value): return value return self.unsafe_undefined(obj, attribute) return self.undefined(obj=obj, name=argument) def unsafe_undefined(self, obj, attribute): """Return an undefined object for unsafe attributes.""" return self.undefined('access to attribute %r of %r ' 'object is unsafe.' % ( attribute, obj.__class__.__name__ ), name=attribute, obj=obj, exc=SecurityError) def call(__self, __context, __obj, *args, **kwargs): """Call an object from sandboxed code.""" # the double prefixes are to avoid double keyword argument # errors when proxying the call. if not __self.is_safe_callable(__obj): raise SecurityError('%r is not safely callable' % (__obj,)) return __context.call(__obj, *args, **kwargs) class ImmutableSandboxedEnvironment(SandboxedEnvironment): """Works exactly like the regular `SandboxedEnvironment` but does not permit modifications on the builtin mutable objects `list`, `set`, and `dict` by using the :func:`modifies_known_mutable` function. """ def is_safe_attribute(self, obj, attr, value): if not SandboxedEnvironment.is_safe_attribute(self, obj, attr, value): return False return not modifies_known_mutable(obj, attr) ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������./weblog+jinja-1.1/jinja2/tests.py������������������������������������������������������������������0100644�0001750�0000000�00000006103�11063022145�0016000�0����������������������������������������������������������������������������������������������������ustar�00henry���������������������������wheel������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������# -*- coding: utf-8 -*- """ jinja2.tests ~~~~~~~~~~~~ Jinja test functions. Used with the "is" operator. :copyright: 2007 by Armin Ronacher. :license: BSD, see LICENSE for more details. """ import re from jinja2.runtime import Undefined number_re = re.compile(r'^-?\d+(\.\d+)?$') regex_type = type(number_re) def test_odd(value): """Return true if the variable is odd.""" return value % 2 == 1 def test_even(value): """Return true if the variable is even.""" return value % 2 == 0 def test_divisibleby(value, num): """Check if a variable is divisible by a number.""" return value % num == 0 def test_defined(value): """Return true if the variable is defined: .. sourcecode:: jinja {% if variable is defined %} value of variable: {{ variable }} {% else %} variable is not defined {% endif %} See the :func:`default` filter for a simple way to set undefined variables. """ return not isinstance(value, Undefined) def test_undefined(value): """Like :func:`defined` but the other way round.""" return isinstance(value, Undefined) def test_none(value): """Return true if the variable is none.""" return value is None def test_lower(value): """Return true if the variable is lowercased.""" return unicode(value).islower() def test_upper(value): """Return true if the variable is uppercased.""" return unicode(value).isupper() def test_string(value): """Return true if the object is a string.""" return isinstance(value, basestring) def test_number(value): """Return true if the variable is a number.""" return isinstance(value, (int, long, float, complex)) def test_sequence(value): """Return true if the variable is a sequence. Sequences are variables that are iterable. """ try: len(value) value.__getitem__ except: return False return True def test_sameas(value, 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 %} """ return value is other def test_iterable(value): """Check if it's possible to iterate over an object.""" try: iter(value) except TypeError: return False return True def test_escaped(value): """Check if the value is escaped.""" return hasattr(value, '__html__') TESTS = { 'odd': test_odd, 'even': test_even, 'divisibleby': test_divisibleby, 'defined': test_defined, 'undefined': test_undefined, 'none': test_none, 'lower': test_lower, 'upper': test_upper, 'string': test_string, 'number': test_number, 'sequence': test_sequence, 'iterable': test_iterable, 'callable': callable, 'sameas': test_sameas, 'escaped': test_escaped } �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������./weblog+jinja-1.1/jinja2/utils.py������������������������������������������������������������������0100644�0001750�0000000�00000052537�11063022145�0016012�0����������������������������������������������������������������������������������������������������ustar�00henry���������������������������wheel������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������# -*- coding: utf-8 -*- """ jinja2.utils ~~~~~~~~~~~~ Utility functions. :copyright: 2008 by Armin Ronacher. :license: BSD, see LICENSE for more details. """ import re import sys import string try: from thread import allocate_lock except ImportError: from dummy_thread import allocate_lock from htmlentitydefs import name2codepoint from collections import deque from copy import deepcopy from itertools import imap _word_split_re = re.compile(r'(\s+)') _punctuation_re = re.compile( '^(?P<lead>(?:%s)*)(?P<middle>.*?)(?P<trail>(?:%s)*)$' % ( '|'.join(imap(re.escape, ('(', '<', '<'))), '|'.join(imap(re.escape, ('.', ',', ')', '>', '\n', '>'))) ) ) _simple_email_re = re.compile(r'^\S+@[a-zA-Z0-9._-]+\.[a-zA-Z0-9._-]+$') _striptags_re = re.compile(r'(<!--.*?-->|<[^>]*>)') _entity_re = re.compile(r'&([^;]+);') _entities = name2codepoint.copy() _entities['apos'] = 39 # special singleton representing missing values for the runtime missing = type('MissingType', (), {'__repr__': lambda x: 'missing'})() # concatenate a list of strings and convert them to unicode. # unfortunately there is a bug in python 2.4 and lower that causes # unicode.join trash the traceback. _concat = u''.join try: def _test_gen_bug(): raise TypeError(_test_gen_bug) yield None _concat(_test_gen_bug()) except TypeError, _error: if not _error.args or _error.args[0] is not _test_gen_bug: def concat(gen): try: return _concat(list(gen)) except: # this hack is needed so that the current frame # does not show up in the traceback. exc_type, exc_value, tb = sys.exc_info() raise exc_type, exc_value, tb.tb_next else: concat = _concat del _test_gen_bug, _error def contextfunction(f): """This decorator can be used to mark a function or method context callable. A context callable is passed the active :class:`Context` as first argument when called from the template. This is useful if a function wants to get access to the context or functions provided on the context object. For example a function that returns a sorted list of template variables the current template exports could look like this:: @contextfunction def get_exported_names(context): return sorted(context.exported_vars) """ f.contextfunction = True return f def environmentfunction(f): """This decorator can be used to mark a function or method as environment callable. This decorator works exactly like the :func:`contextfunction` decorator just that the first argument is the active :class:`Environment` and not context. """ f.environmentfunction = True return f def is_undefined(obj): """Check if the object passed is undefined. This does nothing more than performing an instance check against :class:`Undefined` but looks nicer. This can be used for custom filters or tests that want to react to undefined variables. For example a custom default filter can look like this:: def default(var, default=''): if is_undefined(var): return default return var """ from jinja2.runtime import Undefined return isinstance(obj, Undefined) def clear_caches(): """Jinja2 keeps internal caches for environments and lexers. These are used so that Jinja2 doesn't have to recreate environments and lexers all the time. Normally you don't have to care about that but if you are messuring memory consumption you may want to clean the caches. """ from jinja2.environment import _spontaneous_environments from jinja2.lexer import _lexer_cache _spontaneous_environments.clear() _lexer_cache.clear() def import_string(import_name, silent=False): """Imports an object based on a string. This use useful if you want to use import paths as endpoints or something similar. An import path can be specified either in dotted notation (``xml.sax.saxutils.escape``) or with a colon as object delimiter (``xml.sax.saxutils:escape``). If the `silent` is True the return value will be `None` if the import fails. :return: imported object """ try: if ':' in import_name: module, obj = import_name.split(':', 1) elif '.' in import_name: items = import_name.split('.') module = '.'.join(items[:-1]) obj = items[-1] else: return __import__(import_name) return getattr(__import__(module, None, None, [obj]), obj) except (ImportError, AttributeError): if not silent: raise 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 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 = '<a href="http://%s"%s>%s</a>' % (middle, nofollow_attr, trim_url(middle)) if middle.startswith('http://') or \ middle.startswith('https://'): middle = '<a href="%s"%s>%s</a>' % (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 = '<a href="mailto:%s">%s</a>' % (middle, middle) if lead + middle + trail != word: words[i] = lead + middle + trail return u''.join(words) def generate_lorem_ipsum(n=5, html=True, min=20, max=100): """Generate some lorem impsum for the template.""" from jinja2.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 Markup(u'\n'.join(u'<p>%s</p>' % escape(x) for x in result)) class Markup(unicode): r"""Marks a string as being safe for inclusion in HTML/XML output without needing to be escaped. This implements the `__html__` interface a couple of frameworks and web applications use. :class:`Markup` is a direct subclass of `unicode` and provides all the methods of `unicode` just that it escapes arguments passed and always returns `Markup`. The `escape` function returns markup objects so that double escaping can't happen. If you want to use autoescaping in Jinja just set the finalizer of the environment to `escape`. The constructor of the :class:`Markup` class can be used for three different things: When passed an unicode object it's assumed to be safe, when passed an object with an HTML representation (has an `__html__` method) that representation is used, otherwise the object passed is converted into a unicode string and then assumed to be safe: >>> Markup("Hello <em>World</em>!") Markup(u'Hello <em>World</em>!') >>> class Foo(object): ... def __html__(self): ... return '<a href="#">foo</a>' ... >>> Markup(Foo()) Markup(u'<a href="#">foo</a>') If you want object passed being always treated as unsafe you can use the :meth:`escape` classmethod to create a :class:`Markup` object: >>> Markup.escape("Hello <em>World</em>!") Markup(u'Hello <em>World</em>!') Operations on a markup string are markup aware which means that all arguments are passed through the :func:`escape` function: >>> em = Markup("<em>%s</em>") >>> em % "foo & bar" Markup(u'<em>foo & bar</em>') >>> strong = Markup("<strong>%(text)s</strong>") >>> strong % {'text': '<blink>hacker here</blink>'} Markup(u'<strong><blink>hacker here</blink></strong>') >>> Markup("<em>Hello</em> ") + "<foo>" Markup(u'<em>Hello</em> <foo>') """ __slots__ = () def __new__(cls, base=u'', encoding=None, errors='strict'): if hasattr(base, '__html__'): base = base.__html__() if encoding is None: return unicode.__new__(cls, base) return unicode.__new__(cls, base, encoding, errors) def __html__(self): return self def __add__(self, other): if hasattr(other, '__html__') or isinstance(other, basestring): return self.__class__(unicode(self) + unicode(escape(other))) return NotImplemented def __radd__(self, other): if hasattr(other, '__html__') or isinstance(other, basestring): return self.__class__(unicode(escape(other)) + unicode(self)) return NotImplemented def __mul__(self, num): if isinstance(num, (int, long)): return self.__class__(unicode.__mul__(self, num)) return NotImplemented __rmul__ = __mul__ def __mod__(self, arg): if isinstance(arg, tuple): arg = tuple(imap(_MarkupEscapeHelper, arg)) else: arg = _MarkupEscapeHelper(arg) return self.__class__(unicode.__mod__(self, arg)) def __repr__(self): return '%s(%s)' % ( self.__class__.__name__, unicode.__repr__(self) ) def join(self, seq): return self.__class__(unicode.join(self, imap(escape, seq))) join.__doc__ = unicode.join.__doc__ def split(self, *args, **kwargs): return map(self.__class__, unicode.split(self, *args, **kwargs)) split.__doc__ = unicode.split.__doc__ def rsplit(self, *args, **kwargs): return map(self.__class__, unicode.rsplit(self, *args, **kwargs)) rsplit.__doc__ = unicode.rsplit.__doc__ def splitlines(self, *args, **kwargs): return map(self.__class__, unicode.splitlines(self, *args, **kwargs)) splitlines.__doc__ = unicode.splitlines.__doc__ def unescape(self): r"""Unescape markup again into an unicode string. This also resolves known HTML4 and XHTML entities: >>> Markup("Main » <em>About</em>").unescape() u'Main \xbb <em>About</em>' """ def handle_match(m): name = m.group(1) if name in _entities: return unichr(_entities[name]) try: if name[:2] in ('#x', '#X'): return unichr(int(name[2:], 16)) elif name.startswith('#'): return unichr(int(name[1:])) except ValueError: pass return u'' return _entity_re.sub(handle_match, unicode(self)) def striptags(self): r"""Unescape markup into an unicode string and strip all tags. This also resolves known HTML4 and XHTML entities. Whitespace is normalized to one: >>> Markup("Main » <em>About</em>").striptags() u'Main \xbb About' """ stripped = u' '.join(_striptags_re.sub('', self).split()) return Markup(stripped).unescape() @classmethod def escape(cls, s): """Escape the string. Works like :func:`escape` with the difference that for subclasses of :class:`Markup` this function would return the correct subclass. """ rv = escape(s) if rv.__class__ is not cls: return cls(rv) return rv def make_wrapper(name): orig = getattr(unicode, name) def func(self, *args, **kwargs): args = _escape_argspec(list(args), enumerate(args)) _escape_argspec(kwargs, kwargs.iteritems()) return self.__class__(orig(self, *args, **kwargs)) func.__name__ = orig.__name__ func.__doc__ = orig.__doc__ return func for method in '__getitem__', '__getslice__', 'capitalize', \ 'title', 'lower', 'upper', 'replace', 'ljust', \ 'rjust', 'lstrip', 'rstrip', 'center', 'strip', \ 'translate', 'expandtabs', 'swapcase', 'zfill': locals()[method] = make_wrapper(method) # new in python 2.5 if hasattr(unicode, 'partition'): partition = make_wrapper('partition'), rpartition = make_wrapper('rpartition') # new in python 2.6 if hasattr(unicode, 'format'): format = make_wrapper('format') del method, make_wrapper def _escape_argspec(obj, iterable): """Helper for various string-wrapped functions.""" for key, value in iterable: if hasattr(value, '__html__') or isinstance(value, basestring): obj[key] = escape(value) return obj class _MarkupEscapeHelper(object): """Helper for Markup.__mod__""" def __init__(self, obj): self.obj = obj __getitem__ = lambda s, x: _MarkupEscapeHelper(s.obj[x]) __unicode__ = lambda s: unicode(escape(s.obj)) __str__ = lambda s: str(escape(s.obj)) __repr__ = lambda s: str(escape(repr(s.obj))) __int__ = lambda s: int(s.obj) __float__ = lambda s: float(s.obj) class LRUCache(object): """A simple LRU Cache implementation.""" # this is fast for small capacities (something below 1000) but doesn't # scale. But as long as it's only used as storage for templates this # won't do any harm. def __init__(self, capacity): self.capacity = capacity self._mapping = {} self._queue = deque() self._postinit() def _postinit(self): # alias all queue methods for faster lookup self._popleft = self._queue.popleft self._pop = self._queue.pop if hasattr(self._queue, 'remove'): self._remove = self._queue.remove self._wlock = allocate_lock() self._append = self._queue.append def _remove(self, obj): """Python 2.4 compatibility.""" for idx, item in enumerate(self._queue): if item == obj: del self._queue[idx] break def __getstate__(self): return { 'capacity': self.capacity, '_mapping': self._mapping, '_queue': self._queue } def __setstate__(self, d): self.__dict__.update(d) self._postinit() def __getnewargs__(self): return (self.capacity,) def copy(self): """Return an shallow copy of the instance.""" rv = self.__class__(self.capacity) rv._mapping.update(self._mapping) rv._queue = deque(self._queue) return rv def get(self, key, default=None): """Return an item from the cache dict or `default`""" try: return self[key] except KeyError: 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. """ try: return self[key] except KeyError: self[key] = default return default def clear(self): """Clear the cache.""" self._wlock.acquire() try: self._mapping.clear() self._queue.clear() finally: self._wlock.release() def __contains__(self, key): """Check if a key exists in this cache.""" return key in self._mapping def __len__(self): """Return the current size of the cache.""" 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. 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. """ self._wlock.acquire() try: 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 finally: self._wlock.release() def __delitem__(self, key): """Remove an item from the cache dict. Raise an `KeyError` if it does not exist. """ self._wlock.acquire() try: del self._mapping[key] self._remove(key) finally: self._wlock.release() def items(self): """Return a list of items.""" result = [(key, self._mapping[key]) for key in list(self._queue)] result.reverse() return result def iteritems(self): """Iterate over all items.""" return iter(self.items()) def values(self): """Return a list of all values.""" return [x[1] for x in self.items()] def itervalue(self): """Iterate over all values.""" return iter(self.values()) def keys(self): """Return a list of all keys ordered by most recent usage.""" return list(self) def iterkeys(self): """Iterate over all keys in the cache dict, ordered by the most recent usage. """ return reversed(tuple(self._queue)) __iter__ = iterkeys def __reversed__(self): """Iterate over the values in the cache dict, oldest items coming first. """ return iter(tuple(self._queue)) __copy__ = copy # register the LRU cache as mutable mapping if possible try: from collections import MutableMapping MutableMapping.register(LRUCache) except ImportError: pass # we have to import it down here as the speedups module imports the # markup type which is define above. try: from jinja2._speedups import escape, soft_unicode except ImportError: def escape(s): """Convert the characters &, <, >, ' and " in string s to HTML-safe sequences. Use this if you need to display text that might contain such characters in HTML. Marks return value as markup string. """ if hasattr(s, '__html__'): return s.__html__() return Markup(unicode(s) .replace('&', '&') .replace('>', '>') .replace('<', '<') .replace("'", ''') .replace('"', '"') ) def soft_unicode(s): """Make a string unicode if it isn't already. That way a markup string is not converted back to unicode. """ if not isinstance(s, unicode): s = unicode(s) return s # partials try: from functools import partial except ImportError: class partial(object): def __init__(self, _func, *args, **kwargs): self._func = _func self._args = args self._kwargs = kwargs def __call__(self, *args, **kwargs): kwargs.update(self._kwargs) return self._func(*(self._args + args), **kwargs) �����������������������������������������������������������������������������������������������������������������������������������������������������������������./weblog+jinja-1.1/jinja2/__init__.py���������������������������������������������������������������0100644�0001750�0000000�00000003741�11063022145�0016402�0����������������������������������������������������������������������������������������������������ustar�00henry���������������������������wheel������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������# -*- coding: utf-8 -*- """ jinja2 ~~~~~~ Jinja2 is a template engine written in pure Python. It provides a Django inspired non-XML syntax but supports inline expressions and an optional sandboxed environment. Nutshell -------- Here a small example of a Jinja2 template:: {% extends 'base.html' %} {% block title %}Memberlist{% endblock %} {% block content %} <ul> {% for user in users %} <li><a href="{{ user.url }}">{{ user.username }}</a></li> {% endfor %} </ul> {% endblock %} :copyright: 2008 by Armin Ronacher, Christoph Hack. :license: BSD, see LICENSE for more details. """ __docformat__ = 'restructuredtext en' try: __version__ = __import__('pkg_resources') \ .get_distribution('Jinja2').version except: __version__ = 'unknown' # high level interface from jinja2.environment import Environment, Template # loaders from jinja2.loaders import BaseLoader, FileSystemLoader, PackageLoader, \ DictLoader, FunctionLoader, PrefixLoader, ChoiceLoader # undefined types from jinja2.runtime import Undefined, DebugUndefined, StrictUndefined # exceptions from jinja2.exceptions import TemplateError, UndefinedError, \ TemplateNotFound, TemplateSyntaxError, TemplateAssertionError # decorators and public utilities from jinja2.filters import environmentfilter, contextfilter from jinja2.utils import Markup, escape, clear_caches, \ environmentfunction, contextfunction, is_undefined __all__ = [ 'Environment', 'Template', 'BaseLoader', 'FileSystemLoader', 'PackageLoader', 'DictLoader', 'FunctionLoader', 'PrefixLoader', 'ChoiceLoader', 'Undefined', 'DebugUndefined', 'StrictUndefined', 'TemplateError', 'UndefinedError', 'TemplateNotFound', 'TemplateSyntaxError', 'TemplateAssertionError', 'environmentfilter', 'contextfilter', 'Markup', 'escape', 'environmentfunction', 'contextfunction', 'clear_caches', 'is_undefined' ] �������������������������������./weblog+jinja-1.1/jinja2/LICENSE�������������������������������������������������������������������0100644�0001750�0000000�00000003021�11063022145�0015265�0����������������������������������������������������������������������������������������������������ustar�00henry���������������������������wheel������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������Copyright (c) 2006-2008 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-1.1/jinja2/AUTHORS�������������������������������������������������������������������0100644�0001750�0000000�00000000433�11063022145�0015334�0����������������������������������������������������������������������������������������������������ustar�00henry���������������������������wheel������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������Jinja is written and maintained by Armin Ronacher <armin.ronacher@active-4.com>. Other contributors (as mentionend in :copyright:s) are: - Armin Ronacher <armin.ronacher@active-4.com> - Georg Brandl - Lawrence Journal-World. - Bryan McLemore - Mickaël Guérin <kael@crocobox.org> �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������./weblog+jinja-1.1/weblog_run.py��������������������������������������������������������������������0100755�0001750�0000000�00000000375�11063022145�0015634�0����������������������������������������������������������������������������������������������������ustar�00henry���������������������������wheel������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������#!/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() ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������