Browse Source

import plugins from core and restructure repo

Deniz Turgut 11 years ago
parent
commit
9e70c17839
100 changed files with 2019 additions and 204 deletions
  1. 1 0
      .gitignore
  2. 13 0
      .travis.yml
  3. 29 0
      Contributing.rst
  4. 0 11
      README.rst
  5. 41 0
      Readme.rst
  6. 80 0
      assets/Readme.rst
  7. 1 0
      assets/__init__.py
  8. 53 0
      assets/assets.py
  9. 30 0
      github_activity/Readme.rst
  10. 1 0
      github_activity/__init__.py
  11. 67 0
      github_activity/github_activity.py
  12. 6 0
      global_license/Readme.rst
  13. 1 0
      global_license/__init__.py
  14. 18 0
      global_license/global_license.py
  15. 0 0
      goodreads_activity/Readme.md
  16. 1 0
      goodreads_activity/__init__.py
  17. 9 0
      pelicanext/goodreads_activity/goodreads_activity.py
  18. 15 0
      gravatar/Readme.rst
  19. 1 0
      gravatar/__init__.py
  20. 31 0
      gravatar/gravatar.py
  21. 10 0
      gzip_cache/Readme.rst
  22. 1 0
      gzip_cache/__init__.py
  23. 84 0
      gzip_cache/gzip_cache.py
  24. 45 0
      html_rst_directive/Readme.rst
  25. 1 0
      html_rst_directive/__init__.py
  26. 30 0
      html_rst_directive/html_rst_directive.py
  27. 0 0
      latex/Readme.md
  28. 1 0
      latex/__init__.py
  29. 47 0
      latex/latex.py
  30. 3 1
      pelicanext/multi_part/README.md
  31. 1 0
      multi_part/__init__.py
  32. 1 26
      pelicanext/multi_part/multi_part.py
  33. 1 18
      pelicanext/neighbors/README.rst
  34. 1 0
      neighbors/__init__.py
  35. 0 0
      neighbors/neighbors.py
  36. 0 112
      pelicanext/latex/latex.py
  37. 0 36
      pelicanext/random_article/Readme.md
  38. 0 0
      pelicanext/sitemap/__init__.py
  39. 19 0
      random_article/Readme.md
  40. 1 0
      random_article/__init__.py
  41. 9 0
      pelicanext/random_article/random_article.py
  42. 19 0
      related_posts/Readme.rst
  43. 1 0
      related_posts/__init__.py
  44. 35 0
      related_posts/related_posts.py
  45. 65 0
      sitemap/Readme.rst
  46. 1 0
      sitemap/__init__.py
  47. 7 0
      pelicanext/sitemap/sitemap.py
  48. 27 0
      summary/Readme.rst
  49. 1 0
      summary/__init__.py
  50. 61 0
      summary/summary.py
  51. 13 0
      tests/Readme.rst
  52. 2 0
      tests/__init__.py
  53. 142 0
      tests/test_assets.py
  54. 4 0
      tests/test_data/content/2012-11-30_filename-metadata.rst
  55. 7 0
      tests/test_data/content/another_super_article-fr.rst
  56. 20 0
      tests/test_data/content/another_super_article.rst
  57. 9 0
      tests/test_data/content/article2-fr.rst
  58. 9 0
      tests/test_data/content/article2.rst
  59. 7 0
      tests/test_data/content/cat1/article1.rst
  60. 6 0
      tests/test_data/content/cat1/article2.rst
  61. 6 0
      tests/test_data/content/cat1/article3.rst
  62. 7 0
      tests/test_data/content/cat1/markdown-article.md
  63. 7 0
      tests/test_data/content/draft_article.rst
  64. 2 0
      tests/test_data/content/extra/robots.txt
  65. 9 0
      tests/test_data/content/pages/hidden_page.rst
  66. 6 0
      tests/test_data/content/pages/jinja2_template.html
  67. 9 0
      tests/test_data/content/pages/override_url_saveas.rst
  68. 12 0
      tests/test_data/content/pages/test_page.rst
  69. BIN
      tests/test_data/content/pictures/Fat_Cat.jpg
  70. BIN
      tests/test_data/content/pictures/Sushi.jpg
  71. BIN
      tests/test_data/content/pictures/Sushi_Macro.jpg
  72. 36 0
      tests/test_data/content/super_article.rst
  73. 9 0
      tests/test_data/content/unbelievable.rst
  74. 1 0
      tests/test_data/content/unwanted_file
  75. 45 0
      tests/test_data/pelican.conf.py
  76. 1 0
      tests/test_data/themes/assets_theme/static/css/style.min.css
  77. 19 0
      tests/test_data/themes/assets_theme/static/css/style.scss
  78. 7 0
      tests/test_data/themes/assets_theme/templates/base.html
  79. 446 0
      tests/test_data/themes/notmyidea/static/css/main.css
  80. 205 0
      tests/test_data/themes/notmyidea/static/css/pygment.css
  81. 52 0
      tests/test_data/themes/notmyidea/static/css/reset.css
  82. 3 0
      tests/test_data/themes/notmyidea/static/css/typogrify.css
  83. 48 0
      tests/test_data/themes/notmyidea/static/css/wide.css
  84. BIN
      tests/test_data/themes/notmyidea/static/images/icons/aboutme.png
  85. BIN
      tests/test_data/themes/notmyidea/static/images/icons/bitbucket.png
  86. BIN
      tests/test_data/themes/notmyidea/static/images/icons/delicious.png
  87. BIN
      tests/test_data/themes/notmyidea/static/images/icons/facebook.png
  88. BIN
      tests/test_data/themes/notmyidea/static/images/icons/github.png
  89. BIN
      tests/test_data/themes/notmyidea/static/images/icons/gitorious.png
  90. BIN
      tests/test_data/themes/notmyidea/static/images/icons/gittip.png
  91. BIN
      tests/test_data/themes/notmyidea/static/images/icons/google-groups.png
  92. BIN
      tests/test_data/themes/notmyidea/static/images/icons/google-plus.png
  93. BIN
      tests/test_data/themes/notmyidea/static/images/icons/hackernews.png
  94. BIN
      tests/test_data/themes/notmyidea/static/images/icons/lastfm.png
  95. BIN
      tests/test_data/themes/notmyidea/static/images/icons/linkedin.png
  96. BIN
      tests/test_data/themes/notmyidea/static/images/icons/reddit.png
  97. BIN
      tests/test_data/themes/notmyidea/static/images/icons/rss.png
  98. BIN
      tests/test_data/themes/notmyidea/static/images/icons/slideshare.png
  99. BIN
      tests/test_data/themes/notmyidea/static/images/icons/speakerdeck.png
  100. 0 0
      tests/test_data/themes/notmyidea/static/images/icons/twitter.png

+ 1 - 0
.gitignore

@@ -0,0 +1 @@
+*.pyc

+ 13 - 0
.travis.yml

@@ -0,0 +1,13 @@
+language: python
+python:
+    - "2.7"
+    - "3.2"
+before_install:
+ - sudo apt-get update -qq
+ - sudo apt-get install -qq --no-install-recommends ruby-sass
+install:
+    - pip install nose
+    - pip install -e git://github.com/getpelican/pelican.git#egg=pelican
+    - pip install --use-mirrors Markdown
+    - if [[ $TRAVIS_PYTHON_VERSION == '2.7' ]]; then pip install --use-mirrors webassets cssmin; fi
+script: nosetests tests

+ 29 - 0
Contributing.rst

@@ -0,0 +1,29 @@
+Contributing a plugin
+=====================
+
+Details of how to write a plugin is explained in the official Pelican `docs`_.
+
+If you want to contribute, please fork this repository and issue your pull 
+request. Make sure that your plugin follows the structure below::
+
+    my_plugin
+       ├──  __init__.py
+       ├──  my_plugin.py
+       └──  Readme.rst / Readme.md
+
+``my_plugin.py`` is the actual plugin implementation. Include a brief
+explanation of what the plugin does as a module docstring. Leave any further
+explanations and usage details to ``Readme`` file.
+
+``__init__.py`` should contain a single line with ``from .my_plugin import *``.
+
+If you have tests for your plugin, place them in the ``tests`` folder with name
+``test_my_plugin.py``. You can use ``test_data`` folder inside, if you need content 
+or templates in your tests.
+
+**Note:** Plugins in the repository are licensed with *GNU AFFERO GENERAL PUBLIC LICENSE
+Version 3*. By submitting a pull request, you accept to release your 
+contribution under same license. Please refer to the ``LICENSE`` file for
+full text of the license.
+
+.. _docs: http://docs.getpelican.com/en/latest/plugins.html#how-to-create-plugins

+ 0 - 11
README.rst

@@ -1,11 +0,0 @@
-Pelican Plugins
-###############
-
-This repository contains plugins for Pelican. At the moment, this is only a
-subset of the available plugins, with others currently residing in the primary
-Pelican repository. Those latter plugins will eventually be moved here, so all
-plugins will be in one place.
-
-That being the case, if you have a plugin to contribute, please fork this
-Pelican Plugins repository and issue your pull request from there (as opposed
-to the primary Pelican repository).

+ 41 - 0
Readme.rst

@@ -0,0 +1,41 @@
+Pelican Plugins
+###############
+
+Beginning with version 3.0, Pelican supports plugins. Plugins are a way to add
+features to Pelican without having to directly modify the Pelican core. Starting
+with 3.2, all plugins (including the ones previously in the core) are 
+moved here, so this is the central place for all plugins. 
+
+How to use plugins
+==================
+
+Easiest way to install and use these plugins is cloning this repo::
+
+    git clone https://github.com/getpelican/pelican-plugins
+
+and activating the ones you want in your settings file::
+
+    PLUGIN_PATH = 'path/to/pelican-plugins'
+    PLUGINS = ['assets', 'sitemap', 'gravatar']
+
+``PLUGIN_PATH`` can be a path relative to your settings file or an absolute path.
+
+Alternatively, if plugins are in an importable path, you can omit ``PLUGIN_PATH``
+and list them::
+
+    PLUGINS = ['assets', 'sitemap', 'gravatar']
+
+or you can ``import`` the plugin directly and give that::
+
+    import my_plugin
+    PLUGINS = [my_plugin, 'assets']
+
+Please refer to the ``Readme`` file in a plugin's folder for detailed information about 
+that plugin.
+
+Contributing a plugin
+=====================
+
+Please refer to the `Contributing`_ file.
+
+.. _Contributing: Contributing.rst

+ 80 - 0
assets/Readme.rst

@@ -0,0 +1,80 @@
+Asset management
+----------------
+
+This plugin allows you to use the `Webassets`_ module to manage assets such as
+CSS and JS files. The module must first be installed::
+
+    pip install webassets
+
+The Webassets module allows you to perform a number of useful asset management
+functions, including:
+
+* CSS minifier (``cssmin``, ``yui_css``, ...)
+* CSS compiler (``less``, ``sass``, ...)
+* JS minifier (``uglifyjs``, ``yui_js``, ``closure``, ...)
+
+Others filters include gzip compression, integration of images in CSS via data
+URIs, and more. Webassets can also append a version identifier to your asset
+URL to convince browsers to download new versions of your assets when you use
+far-future expires headers. Please refer to the `Webassets documentation`_ for
+more information.
+
+When used with Pelican, Webassets is configured to process assets in the
+``OUTPUT_PATH/theme`` directory. You can use Webassets in your templates by
+including one or more template tags. The Jinja variable ``{{ ASSET_URL }}`` can
+be used in templates and is relative to the ``theme/`` url. The
+``{{ ASSET_URL }}`` variable should be used in conjunction with the
+``{{ SITEURL }}`` variable in order to generate URLs properly. For example:
+
+.. code-block:: jinja
+
+    {% assets filters="cssmin", output="css/style.min.css", "css/inuit.css", "css/pygment-monokai.css", "css/main.css" %}
+        <link rel="stylesheet" href="{{ SITEURL }}/{{ ASSET_URL }}">
+    {% endassets %}
+
+... will produce a minified css file with a version identifier that looks like:
+
+.. code-block:: html
+
+    <link href="http://{SITEURL}/theme/css/style.min.css?b3a7c807" rel="stylesheet">
+
+These filters can be combined. Here is an example that uses the SASS compiler
+and minifies the output:
+
+.. code-block:: jinja
+
+    {% assets filters="sass,cssmin", output="css/style.min.css", "css/style.scss" %}
+        <link rel="stylesheet" href="{{ SITEURL }}/{{ ASSET_URL }}">
+    {% endassets %}
+
+Another example for Javascript:
+
+.. code-block:: jinja
+
+    {% assets filters="uglifyjs,gzip", output="js/packed.js", "js/jquery.js", "js/base.js", "js/widgets.js" %}
+        <script src="{{ SITEURL }}/{{ ASSET_URL }}"></script>
+    {% endassets %}
+
+The above will produce a minified and gzipped JS file:
+
+.. code-block:: html
+
+    <script src="http://{SITEURL}/theme/js/packed.js?00703b9d"></script>
+
+Pelican's debug mode is propagated to Webassets to disable asset packaging
+and instead work with the uncompressed assets.
+
+Many of Webasset's available compilers have additional configuration options
+(i.e. 'Less', 'Sass', 'Stylus', 'Closure_js').  You can pass these options to
+Webassets using the ``ASSET_CONFIG`` in your settings file.
+
+The following will handle Google Closure's compilation level and locate
+LessCSS's binary:
+
+.. code-block:: python
+
+    ASSET_CONFIG = (('closure_compressor_optimization', 'WHITESPACE_ONLY'),
+                    ('less_bin', 'lessc.cmd'), )
+
+.. _Webassets: https://github.com/miracle2k/webassets
+.. _Webassets documentation: http://webassets.readthedocs.org/en/latest/builtin_filters.html

+ 1 - 0
assets/__init__.py

@@ -0,0 +1 @@
+from .assets import *

+ 53 - 0
assets/assets.py

@@ -0,0 +1,53 @@
+# -*- coding: utf-8 -*-
+"""
+Asset management plugin for Pelican
+===================================
+
+This plugin allows you to use the `webassets`_ module to manage assets such as
+CSS and JS files.
+
+The ASSET_URL is set to a relative url to honor Pelican's RELATIVE_URLS
+setting. This requires the use of SITEURL in the templates::
+
+    <link rel="stylesheet" href="{{ SITEURL }}/{{ ASSET_URL }}">
+
+.. _webassets: https://webassets.readthedocs.org/
+
+"""
+from __future__ import unicode_literals
+
+import os
+import logging
+
+from pelican import signals
+from webassets import Environment
+from webassets.ext.jinja2 import AssetsExtension
+
+
+def add_jinja2_ext(pelican):
+    """Add Webassets to Jinja2 extensions in Pelican settings."""
+
+    pelican.settings['JINJA_EXTENSIONS'].append(AssetsExtension)
+
+
+def create_assets_env(generator):
+    """Define the assets environment and pass it to the generator."""
+
+    assets_url = 'theme/'
+    assets_src = os.path.join(generator.output_path, 'theme')
+    generator.env.assets_environment = Environment(assets_src, assets_url)
+
+    if 'ASSET_CONFIG' in generator.settings:
+        for item in generator.settings['ASSET_CONFIG']:
+            generator.env.assets_environment.config[item[0]] = item[1]
+
+    logger = logging.getLogger(__name__)
+    if logging.getLevelName(logger.getEffectiveLevel()) == "DEBUG":
+        generator.env.assets_environment.debug = True
+
+
+def register():
+    """Plugin registration."""
+
+    signals.initialized.connect(add_jinja2_ext)
+    signals.generator_init.connect(create_assets_env)

+ 30 - 0
github_activity/Readme.rst

@@ -0,0 +1,30 @@
+GitHub activity
+---------------
+
+This plugin makes use of the `feedparser`_ library that you'll need to
+install.
+
+Set the ``GITHUB_ACTIVITY_FEED`` parameter to your GitHub activity feed.
+For example, to track Pelican project activity, the setting would be::
+
+     GITHUB_ACTIVITY_FEED = 'https://github.com/getpelican.atom'
+
+On the template side, you just have to iterate over the ``github_activity``
+variable, as in this example::
+
+     {% if GITHUB_ACTIVITY_FEED %}
+        <div class="social">
+                <h2>Github Activity</h2>
+                <ul>
+
+                {% for entry in github_activity %}
+                    <li><b>{{ entry[0] }}</b><br /> {{ entry[1] }}</li>
+                {% endfor %}
+                </ul>
+        </div><!-- /.github_activity -->
+     {% endif %}
+
+``github_activity`` is a list of lists. The first element is the title,
+and the second element is the raw HTML from GitHub.
+
+.. _feedparser: https://crate.io/packages/feedparser/

+ 1 - 0
github_activity/__init__.py

@@ -0,0 +1 @@
+from .github_activity import *

+ 67 - 0
github_activity/github_activity.py

@@ -0,0 +1,67 @@
+# -*- coding: utf-8 -*-
+
+# NEEDS WORK
+"""
+Copyright (c) Marco Milanesi <kpanic@gnufunk.org>
+
+Github Activity
+---------------
+A plugin to list your Github Activity
+"""
+
+from __future__ import unicode_literals, print_function
+
+from pelican import signals
+
+
+class GitHubActivity():
+    """
+        A class created to fetch github activity with feedparser
+    """
+    def __init__(self, generator):
+        try:
+            import feedparser
+            self.activities = feedparser.parse(
+                generator.settings['GITHUB_ACTIVITY_FEED'])
+        except ImportError:
+            raise Exception("Unable to find feedparser")
+
+    def fetch(self):
+        """
+            returns a list of html snippets fetched from github actitivy feed
+        """
+
+        entries = []
+        for activity in self.activities['entries']:
+            entries.append(
+                    [element for element in [activity['title'],
+                        activity['content'][0]['value']]])
+
+        return entries
+
+
+def fetch_github_activity(gen, metadata):
+    """
+        registered handler for the github activity plugin
+        it puts in generator.context the html needed to be displayed on a
+        template
+    """
+
+    if 'GITHUB_ACTIVITY_FEED' in gen.settings.keys():
+        gen.context['github_activity'] = gen.plugin_instance.fetch()
+
+
+def feed_parser_initialization(generator):
+    """
+        Initialization of feed parser
+    """
+
+    generator.plugin_instance = GitHubActivity(generator)
+
+
+def register():
+    """
+        Plugin registration
+    """
+    signals.article_generator_init.connect(feed_parser_initialization)
+    signals.article_generate_context.connect(fetch_github_activity)

+ 6 - 0
global_license/Readme.rst

@@ -0,0 +1,6 @@
+Global license
+--------------
+
+This plugin allows you to define a ``LICENSE`` setting and adds the contents of that
+license variable to the article's context, making that variable available to use
+from within your theme's templates.

+ 1 - 0
global_license/__init__.py

@@ -0,0 +1 @@
+from .global_license import *

+ 18 - 0
global_license/global_license.py

@@ -0,0 +1,18 @@
+"""
+License plugin for Pelican
+==========================
+
+This plugin allows you to define a LICENSE setting and adds the contents of that
+license variable to the article's context, making that variable available to use
+from within your theme's templates.
+"""
+
+from pelican import signals
+
+def add_license(generator, metadata):
+    if 'license' not in metadata.keys()\
+        and 'LICENSE' in generator.settings.keys():
+            metadata['license'] = generator.settings['LICENSE']
+
+def register():
+    signals.article_generate_context.connect(add_license)

pelicanext/goodreads_activity/README.md → goodreads_activity/Readme.md


+ 1 - 0
goodreads_activity/__init__.py

@@ -0,0 +1 @@
+from .goodreads_activity import *

+ 9 - 0
pelicanext/goodreads_activity/goodreads_activity.py

@@ -1,4 +1,13 @@
 # -*- coding: utf-8 -*-
+"""
+Goodreads Activity
+==================
+
+A Pelican plugin to lists books from your Goodreads shelves.
+
+Copyright (c) Talha Mansoor
+"""
+
 from __future__ import unicode_literals
 from pelican import signals
 import feedparser

+ 15 - 0
gravatar/Readme.rst

@@ -0,0 +1,15 @@
+Gravatar
+--------
+
+This plugin assigns the ``author_gravatar`` variable to the Gravatar URL and
+makes the variable available within the article's context. You can add
+``AUTHOR_EMAIL`` to your settings file to define the default author's email
+address. Obviously, that email address must be associated with a Gravatar
+account.
+
+Alternatively, you can provide an email address from within article metadata::
+
+    :email:  john.doe@example.com
+
+If the email address is defined via at least one of the two methods above,
+the ``author_gravatar`` variable is added to the article's context.

+ 1 - 0
gravatar/__init__.py

@@ -0,0 +1 @@
+from .gravatar import *

+ 31 - 0
gravatar/gravatar.py

@@ -0,0 +1,31 @@
+"""
+Gravatar plugin for Pelican
+===========================
+
+This plugin assigns the ``author_gravatar`` variable to the Gravatar URL and
+makes the variable available within the article's context.
+"""
+
+import hashlib
+import six
+
+from pelican import signals
+
+
+def add_gravatar(generator, metadata):
+
+    #first check email
+    if 'email' not in metadata.keys()\
+        and 'AUTHOR_EMAIL' in generator.settings.keys():
+            metadata['email'] = generator.settings['AUTHOR_EMAIL']
+
+    #then add gravatar url
+    if 'email' in metadata.keys():
+        email_bytes = six.b(metadata['email']).lower()
+        gravatar_url = "http://www.gravatar.com/avatar/" + \
+                        hashlib.md5(email_bytes).hexdigest()
+        metadata["author_gravatar"] = gravatar_url
+
+
+def register():
+    signals.article_generate_context.connect(add_gravatar)

+ 10 - 0
gzip_cache/Readme.rst

@@ -0,0 +1,10 @@
+Gzip cache
+----------
+
+Certain web servers (e.g., Nginx) can use a static cache of gzip-compressed
+files to prevent the server from compressing files during an HTTP call. Since
+compression occurs at another time, these compressed files can be compressed
+at a higher compression level for increased optimization.
+
+The ``gzip_cache`` plugin compresses all common text type files into a ``.gz``
+file within the same directory as the original file.

+ 1 - 0
gzip_cache/__init__.py

@@ -0,0 +1 @@
+from .gzip_cache import *

+ 84 - 0
gzip_cache/gzip_cache.py

@@ -0,0 +1,84 @@
+'''
+Copyright (c) 2012 Matt Layman
+
+Gzip cache
+----------
+
+A plugin to create .gz cache files for optimization.
+'''
+
+import gzip
+import logging
+import os
+
+from pelican import signals
+
+logger = logging.getLogger(__name__)
+
+# A list of file types to exclude from possible compression
+EXCLUDE_TYPES = [
+    # Compressed types
+    '.bz2',
+    '.gz',
+
+    # Audio types
+    '.aac',
+    '.flac',
+    '.mp3',
+    '.wma',
+
+    # Image types
+    '.gif',
+    '.jpg',
+    '.jpeg',
+    '.png',
+
+    # Video types
+    '.avi',
+    '.mov',
+    '.mp4',
+]
+
+def create_gzip_cache(pelican):
+    '''Create a gzip cache file for every file that a webserver would
+    reasonably want to cache (e.g., text type files).
+
+    :param pelican: The Pelican instance
+    '''
+    for dirpath, _, filenames in os.walk(pelican.settings['OUTPUT_PATH']):
+        for name in filenames:
+            if should_compress(name):
+                filepath = os.path.join(dirpath, name)
+                create_gzip_file(filepath)
+
+def should_compress(filename):
+    '''Check if the filename is a type of file that should be compressed.
+
+    :param filename: A file name to check against
+    '''
+    for extension in EXCLUDE_TYPES:
+        if filename.endswith(extension):
+            return False
+
+    return True
+
+def create_gzip_file(filepath):
+    '''Create a gzipped file in the same directory with a filepath.gz name.
+
+    :param filepath: A file to compress
+    '''
+    compressed_path = filepath + '.gz'
+
+    with open(filepath, 'rb') as uncompressed:
+        try:
+            logger.debug('Compressing: %s' % filepath)
+            compressed = gzip.open(compressed_path, 'wb')
+            compressed.writelines(uncompressed)
+        except Exception as ex:
+            logger.critical('Gzip compression failed: %s' % ex)
+        finally:
+            compressed.close()
+
+def register():
+    signals.finalized.connect(create_gzip_cache)
+

+ 45 - 0
html_rst_directive/Readme.rst

@@ -0,0 +1,45 @@
+HTML tags for reStructuredText
+------------------------------
+
+This plugin allows you to use HTML tags from within reST documents. 
+
+
+Directives
+----------
+
+
+::
+
+    .. html::
+
+        (HTML code)
+
+
+Example
+-------
+
+A search engine::
+
+    .. html::
+
+       <form action="http://seeks.fr/search" method="GET">
+         <input type="text" value="Pelican v2" title="Search" maxlength="2048" name="q" autocomplete="on" />
+         <input type="hidden" name="lang" value="en" />
+         <input type="submit" value="Seeks !" id="search_button" />
+       </form>
+
+
+A contact form::
+
+    .. html::
+
+        <form method="GET" action="mailto:some email">
+          <p>
+            <input type="text" placeholder="Subject" name="subject">
+            <br />
+            <textarea name="body" placeholder="Message">
+            </textarea>
+            <br />
+            <input type="reset"><input type="submit">
+          </p>
+        </form>

+ 1 - 0
html_rst_directive/__init__.py

@@ -0,0 +1 @@
+from .html_rst_directive import *

+ 30 - 0
html_rst_directive/html_rst_directive.py

@@ -0,0 +1,30 @@
+# -*- coding: utf-8 -*-
+"""
+HTML tags for reStructuredText
+==============================
+
+This plugin allows you to use HTML tags from within reST documents. 
+
+"""
+
+from __future__ import unicode_literals
+from docutils import nodes
+from docutils.parsers.rst import directives, Directive
+
+
+class RawHtml(Directive):
+    required_arguments = 0
+    optional_arguments = 0
+    final_argument_whitespace = True
+    has_content = True
+
+    def run(self):
+        html = ' '.join(self.content)
+        node = nodes.raw('', html, format='html')
+        return [node]
+
+
+
+def register():
+    directives.register_directive('html', RawHtml)
+

pelicanext/latex/Readme.md → latex/Readme.md


+ 1 - 0
latex/__init__.py

@@ -0,0 +1 @@
+from .latex import *

+ 47 - 0
latex/latex.py

@@ -0,0 +1,47 @@
+# -*- coding: utf-8 -*-
+"""
+Latex Plugin For Pelican
+========================
+
+This plugin allows you to write mathematical equations in your articles using Latex.
+It uses the MathJax Latex JavaScript library to render latex that is embedded in
+between `$..$` for inline math and `$$..$$` for displayed math. It also allows for 
+writing equations in by using `\begin{equation}`...`\end{equation}`.
+"""
+
+from pelican import signals
+
+latexScript = """
+    <script src="http://cdn.mathjax.org/mathjax/latest/MathJax.js?config=TeX-AMS-MML_HTMLorMML" type= "text/javascript">
+       MathJax.Hub.Config({
+           config: ["MMLorHTML.js"],
+           jax: ["input/TeX","input/MathML","output/HTML-CSS","output/NativeMML"],
+           TeX: { extensions: ["AMSmath.js","AMSsymbols.js","noErrors.js","noUndefined.js"], equationNumbers: { autoNumber: "AMS" } },
+           extensions: ["tex2jax.js","mml2jax.js","MathMenu.js","MathZoom.js"],
+           tex2jax: { 
+               inlineMath: [ [\'$\',\'$\'] ],
+               displayMath: [ [\'$$\',\'$$\'] ],
+               processEscapes: true },
+           "HTML-CSS": {
+               styles: { ".MathJax .mo, .MathJax .mi": {color: "black ! important"}}
+           }
+       });
+    </script>
+"""
+
+def addLatex(gen, metadata):
+    """
+        The registered handler for the latex plugin. It will add 
+        the latex script to the article metadata
+    """
+    if 'LATEX' in gen.settings.keys() and gen.settings['LATEX'] == 'article':
+        if 'latex' in metadata.keys():
+            metadata['latex'] = latexScript
+    else:
+        metadata['latex'] = latexScript
+
+def register():
+    """
+        Plugin registration
+    """
+    signals.article_generate_context.connect(addLatex)

+ 3 - 1
pelicanext/multi_part/README.md

@@ -7,7 +7,9 @@ In order to mark posts as part of a multi-part post, use the `:parts:` metadata:
 
     :parts:  MY_AWESOME_MULTI_PART_POST
 
-You can then use the `article.related_posts` variable in your templates to display other parts of current post.
+You can then use the `article.metadata.parts_articles` variable in your templates 
+to display other parts of current post.
+
 For example:
 
     {% if article.metadata.parts_articles %}

+ 1 - 0
multi_part/__init__.py

@@ -0,0 +1 @@
+from .multi_part import *

+ 1 - 26
pelicanext/multi_part/multi_part.py

@@ -6,33 +6,8 @@ Multiple part support
 =====================
 
 Create a navigation menu for multi-part related_posts
-
-Article metadata:
-------------------
-
-:parts:  a unique identifier for multi-part posts, must be the same in each
-post part.
-
-Usage
------
-    {% if article.metadata.parts_articles %}
-        <ol>
-        {% for part_article in article.metadata.parts_articles %}
-            {% if part_article == article %}
-                <li>
-                    <a href='{{ SITEURL }}/{{ part_article.url }}'><b>{{ part_article.title }}</b>
-                    </a>
-                </li>
-            {% else %}
-                <li>
-                    <a href='{{ SITEURL }}/{{ part_article.url }}'>{{ part_article.title }}
-                    </a>
-                </li>
-            {% endif %}
-        {% endfor %}
-        </ol>
-    {% endif %}
 """
+
 from collections import defaultdict
 
 from pelican import signals

+ 1 - 18
pelicanext/neighbors/README.rst

@@ -4,23 +4,6 @@ Neighbor Articles Plugin for Pelican
 This plugin adds ``next_article`` (newer) and ``prev_article`` (older) 
 variables to the article's context
 
-Installation
-------------
-To enable, ensure that ``neighbors.py`` is in somewhere you can ``import``.
-Then use the following in your `settings`::
-
-    PLUGINS = ["neighbors"]
-
-Or you can put the plugin in ``plugins`` folder in pelican installation. You 
-can find the location by typing::
-
-    python -c 'import pelican.plugins as p, os; print os.path.dirname(p.__file__)'
-
-Once you get the folder, copy the ``neighbors.py`` there and use the following
-in your settings::
-
-    PLUGINS = ["pelican.plugins.neighbors"]
-
 Usage
 -----
 
@@ -41,4 +24,4 @@ Usage
             </a>
         </li>
     {% endif %}
-    </ul>
+    </ul>

+ 1 - 0
neighbors/__init__.py

@@ -0,0 +1 @@
+from .neighbors import *

pelicanext/neighbors/neighbors.py → neighbors/neighbors.py


+ 0 - 112
pelicanext/latex/latex.py

@@ -1,112 +0,0 @@
-# -*- coding: utf-8 -*-
-"""
-Latex Plugin For Pelican
-========================
-
-This plugin allows you to write mathematical equations in your articles using Latex.
-It uses the MathJax Latex JavaScript library to render latex that is embedded in
-between `$..$` for inline math and `$$..$$` for displayed math. It also allows for 
-writing equations in by using `\begin{equation}`...`\end{equation}`.
-
-Installation
-------------
-
-To enable, ensure that `latex.py` is put somewhere that is accessible.
-Then use as follows by adding the following to your settings.py:
-
-    PLUGINS = ["latex"]
-
-Be careful: Not loading the plugin is easy to do, and difficult to detect. To
-make life easier, find where pelican is installed, and then copy the plugin
-there. An easy way to find where pelican is installed is to verbose list the
-available themes by typing `pelican-themes -l -v`. 
-
-Once the pelican folder is found, copy `latex.py` to the `plugins` folder. Then 
-add to settings.py like this:
-
-    PLUGINS = ["pelican.plugins.latex"]
-
-Now all that is left to do is to embed the following to your template file 
-between the `<head>` parameters (for the NotMyIdea template, this file is base.html)
-
-    {% if article and article.latex %}
-        {{ article.latex }}
-    {% endif %}
-
-Usage
------
-Latex will be embedded in every article. If however you want latex only for
-selected articles, then in settings.py, add
-
-    LATEX = 'article'
-
-And in each article, add the metadata key `latex:`. For example, with the above
-settings, creating an article that I want to render latex math, I would just 
-include 'Latex' as part of the metadata without any value:
-
-    Date: 1 sep 2012
-    Status: draft
-    Latex:
-
-Latex Examples
---------------
-###Inline
-Latex between `$`..`$`, for example, `$`x^2`$`, will be rendered inline 
-with respect to the current html block.
-
-###Displayed Math
-Latex between `$$`..`$$`, for example, `$$`x^2`$$`, will be rendered centered in a 
-new paragraph.
-
-###Equations
-Latex between `\begin` and `\end`, for example, `begin{equation}` x^2 `\end{equation}`, 
-will be rendered centered in a new paragraph with a right justified equation number 
-at the top of the paragraph. This equation number can be referenced in the document. 
-To do this, use a `label` inside of the equation format and then refer to that label 
-using `ref`. For example: `begin{equation}` `\label{eq}` X^2 `\end{equation}`. Now 
-refer to that equation number by `$`\ref{eq}`$`.
-   
-Template And Article Examples
------------------------------
-To see an example of this plugin in action, look at 
-[this article](http://doctrina.org/How-RSA-Works-With-Examples.html). To see how 
-this plugin works with a template, look at 
-[this template](https://github.com/barrysteyn/pelican_theme-personal_blog).
-"""
-
-from pelican import signals
-
-latexScript = """
-    <script src="http://cdn.mathjax.org/mathjax/latest/MathJax.js?config=TeX-AMS-MML_HTMLorMML" type= "text/javascript">
-       MathJax.Hub.Config({
-           config: ["MMLorHTML.js"],
-           jax: ["input/TeX","input/MathML","output/HTML-CSS","output/NativeMML"],
-           TeX: { extensions: ["AMSmath.js","AMSsymbols.js","noErrors.js","noUndefined.js"], equationNumbers: { autoNumber: "AMS" } },
-           extensions: ["tex2jax.js","mml2jax.js","MathMenu.js","MathZoom.js"],
-           tex2jax: { 
-               inlineMath: [ [\'$\',\'$\'] ],
-               displayMath: [ [\'$$\',\'$$\'] ],
-               processEscapes: true },
-           "HTML-CSS": {
-               styles: { ".MathJax .mo, .MathJax .mi": {color: "black ! important"}}
-           }
-       });
-    </script>
-"""
-
-def addLatex(gen, metadata):
-    """
-        The registered handler for the latex plugin. It will add 
-        the latex script to the article metadata
-    """
-    if 'LATEX' in gen.settings.keys() and gen.settings['LATEX'] == 'article':
-        if 'latex' in metadata.keys():
-            metadata['latex'] = latexScript
-    else:
-        metadata['latex'] = latexScript
-
-def register():
-    """
-        Plugin registration
-    """
-    signals.article_generate_context.connect(addLatex)

+ 0 - 36
pelicanext/random_article/Readme.md

@@ -1,36 +0,0 @@
-Random Article Plugin For Pelican
-========================
-
-This plugin generates a html file which redirect to a random article
-using javascript's window.location. The generated html file is 
-saved at SITEURL.
-
-Only published articles are listed to redirect.
-
-
-Installation
-------------
-
-To enable, ensure that `random_article.py` is put somewhere that is accessible.
-Then use as follows by adding the following to your settings.py:
-
-    PLUGINS = ["random_article"]
-
-An easy way to find where pelican is installed is to verbose list the
-available themes by typing `pelican-themes -l -v`.
-
-Once the pelican folder is found, copy `random_article.py` to the `plugins` folder. Then
-add to settings.py like this:
-
-    PLUGINS = ["pelican.plugins.random_article"]
-
-Usage
------
-
-To use it you have to add in your config file the name of the file to use:
-
-    RANDOM = 'random.html'
-
-Then in some template you add:
-
-    <a href="{{ SITEURL }}/{{ RANDOM }}">random article</a>

+ 0 - 0
pelicanext/sitemap/__init__.py


+ 19 - 0
random_article/Readme.md

@@ -0,0 +1,19 @@
+Random Article Plugin For Pelican
+========================
+
+This plugin generates a html file which redirect to a random article
+using javascript's `window.location`. The generated html file is 
+saved at `SITEURL`.
+
+Only published articles are listed to redirect.
+
+Usage
+-----
+
+To use it you have to add in your config file the name of the file to use:
+
+    RANDOM = 'random.html'
+
+Then in some template you add:
+
+    <a href="{{ SITEURL }}/{{ RANDOM }}">random article</a>

+ 1 - 0
random_article/__init__.py

@@ -0,0 +1 @@
+from .random_article import *

+ 9 - 0
pelicanext/random_article/random_article.py

@@ -1,4 +1,13 @@
 # -*- coding: utf-8 -*-
+"""
+Random Article Plugin For Pelican
+========================
+
+This plugin generates a html file which redirect to a random article
+using javascript's window.location. The generated html file is 
+saved at SITEURL.
+"""
+
 from __future__ import unicode_literals
 
 import os.path

+ 19 - 0
related_posts/Readme.rst

@@ -0,0 +1,19 @@
+Related posts
+-------------
+
+This plugin adds the ``related_posts`` variable to the article's context.
+By default, up to 5 articles are listed. You can customize this value by 
+defining ``RELATED_POSTS_MAX`` in your settings file::
+
+    RELATED_POSTS_MAX = 10
+
+You can then use the ``article.related_posts`` variable in your templates.
+For example::
+
+    {% if article.related_posts %}
+        <ul>
+        {% for related_post in article.related_posts %}
+            <li><a href="{{ SITEURL }}/{{ related_post.url }}">{{ related_post.title }}</a></li>
+        {% endfor %}
+        </ul>
+    {% endif %}

+ 1 - 0
related_posts/__init__.py

@@ -0,0 +1 @@
+from .related_posts import *

+ 35 - 0
related_posts/related_posts.py

@@ -0,0 +1,35 @@
+"""
+Related posts plugin for Pelican
+================================
+
+Adds related_posts variable to article's context
+"""
+
+from pelican import signals
+from collections import Counter
+
+
+def add_related_posts(generator):
+    # get the max number of entries from settings
+    # or fall back to default (5)
+    numentries = generator.settings.get('RELATED_POSTS_MAX', 5)
+
+    for article in generator.articles:
+        # no tag, no relation
+        if not hasattr(article, 'tags'):
+            continue
+
+        # score = number of common tags
+        scores = Counter()
+        for tag in article.tags:
+            scores += Counter(generator.tags[tag])
+
+        # remove itself
+        scores.pop(article)
+
+        article.related_posts = [other for other, count 
+            in scores.most_common(numentries)]
+
+
+def register():
+    signals.article_generator_finalized.connect(add_related_posts)

+ 65 - 0
sitemap/Readme.rst

@@ -0,0 +1,65 @@
+Sitemap
+-------
+
+The sitemap plugin generates plain-text or XML sitemaps. You can use the
+``SITEMAP`` variable in your settings file to configure the behavior of the
+plugin.
+
+The ``SITEMAP`` variable must be a Python dictionary and can contain three keys:
+
+- ``format``, which sets the output format of the plugin (``xml`` or ``txt``)
+
+- ``priorities``, which is a dictionary with three keys:
+
+  - ``articles``, the priority for the URLs of the articles and their
+    translations
+
+  - ``pages``, the priority for the URLs of the static pages
+
+  - ``indexes``, the priority for the URLs of the index pages, such as tags,
+     author pages, categories indexes, archives, etc...
+
+  All the values of this dictionary must be decimal numbers between ``0`` and ``1``.
+
+- ``changefreqs``, which is a dictionary with three items:
+
+  - ``articles``, the update frequency of the articles
+
+  - ``pages``, the update frequency of the pages
+
+  - ``indexes``, the update frequency of the index pages
+
+  Valid frequency values are ``always``, ``hourly``, ``daily``, ``weekly``, ``monthly``,
+  ``yearly`` and ``never``.
+
+If a key is missing or a value is incorrect, it will be replaced with the
+default value.
+
+The sitemap is saved in ``<output_path>/sitemap.<format>``.
+
+.. note::
+   ``priorities`` and ``changefreqs`` are information for search engines.
+   They are only used in the XML sitemaps.
+   For more information: <http://www.sitemaps.org/protocol.html#xmlTagDefinitions>
+
+**Example**
+
+Here is an example configuration (it's also the default settings):
+
+.. code-block:: python
+
+    PLUGINS=['pelican.plugins.sitemap',]
+
+    SITEMAP = {
+        'format': 'xml',
+        'priorities': {
+            'articles': 0.5,
+            'indexes': 0.5,
+            'pages': 0.5
+        },
+        'changefreqs': {
+            'articles': 'monthly',
+            'indexes': 'daily',
+            'pages': 'monthly'
+        }
+    }

+ 1 - 0
sitemap/__init__.py

@@ -0,0 +1 @@
+from .sitemap import *

+ 7 - 0
pelicanext/sitemap/sitemap.py

@@ -1,4 +1,11 @@
 # -*- coding: utf-8 -*-
+'''
+Sitemap
+-------
+
+The sitemap plugin generates plain-text or XML sitemaps.
+'''
+
 from __future__ import unicode_literals
 
 import collections

+ 27 - 0
summary/Readme.rst

@@ -0,0 +1,27 @@
+Summary
+-------
+
+This plugin allows easy, variable length summaries directly embedded into the 
+body of your articles. It introduces two new settings: ``SUMMARY_BEGIN_MARKER``
+and ``SUMMARY_END_MARKER``: strings which can be placed directly into an article
+to mark the beginning and end of a summary. When found, the standard 
+``SUMMARY_MAX_LENGTH`` setting will be ignored. The markers themselves will also
+be removed from your articles before they are published. The default values
+are ``<!-- PELICAN_BEGIN_SUMMARY -->`` and ``<!-- PELICAN_END_SUMMARY -->``.
+For example::
+
+    Title: My super title
+    Date: 2010-12-03 10:20
+    Tags: thats, awesome
+    Category: yeah
+    Slug: my-super-post
+    Author: Alexis Metaireau
+    
+    This is the content of my super blog post.
+    <!-- PELICAN_END_SUMMARY -->
+    and this content occurs after the summary.
+
+Here, the summary is taken to be the first line of the post. Because no
+beginning marker was found, it starts at the top of the body. It is possible
+to leave out the end marker instead, in which case the summary will start at the
+beginning marker and continue to the end of the body.

+ 1 - 0
summary/__init__.py

@@ -0,0 +1 @@
+from .summary import *

+ 61 - 0
summary/summary.py

@@ -0,0 +1,61 @@
+"""
+Summary
+-------
+
+This plugin allows easy, variable length summaries directly embedded into the 
+body of your articles.
+"""
+
+import types
+
+from pelican import signals
+
+def initialized(pelican):
+    from pelican.settings import _DEFAULT_CONFIG
+    _DEFAULT_CONFIG.setdefault('SUMMARY_BEGIN_MARKER',
+                               '<!-- PELICAN_BEGIN_SUMMARY -->')
+    _DEFAULT_CONFIG.setdefault('SUMMARY_END_MARKER',
+                               '<!-- PELICAN_END_SUMMARY -->')
+    if pelican:
+        pelican.settings.setdefault('SUMMARY_BEGIN_MARKER',
+                                    '<!-- PELICAN_BEGIN_SUMMARY -->')
+        pelican.settings.setdefault('SUMMARY_END_MARKER',
+                                    '<!-- PELICAN_END_SUMMARY -->')
+
+def content_object_init(instance):
+    # if summary is already specified, use it
+    if 'summary' in instance.metadata:
+        return
+
+    def _get_content(self):
+        content = self._content
+        if self.settings['SUMMARY_BEGIN_MARKER']:
+            content = content.replace(
+                self.settings['SUMMARY_BEGIN_MARKER'], '', 1)
+        if self.settings['SUMMARY_END_MARKER']:
+            content = content.replace(
+                self.settings['SUMMARY_END_MARKER'], '', 1)
+        return content
+    instance._get_content = types.MethodType(_get_content, instance)
+
+    # extract out our summary
+    if not hasattr(instance, '_summary') and instance._content is not None:
+        content = instance._content
+        begin_summary = -1
+        end_summary = -1
+        if instance.settings['SUMMARY_BEGIN_MARKER']:
+            begin_summary = content.find(instance.settings['SUMMARY_BEGIN_MARKER'])
+        if instance.settings['SUMMARY_END_MARKER']:
+            end_summary = content.find(instance.settings['SUMMARY_END_MARKER'])
+        if begin_summary != -1 or end_summary != -1:
+            # the beginning position has to take into account the length
+            # of the marker
+            begin_summary = (begin_summary +
+                            len(instance.settings['SUMMARY_BEGIN_MARKER'])
+                            if begin_summary != -1 else 0)
+            end_summary = end_summary if end_summary != -1 else None
+            instance._summary = content[begin_summary:end_summary]
+
+def register():
+    signals.initialized.connect(initialized)
+    signals.content_object_init.connect(content_object_init)

+ 13 - 0
tests/Readme.rst

@@ -0,0 +1,13 @@
+Test Data
+---------
+
+Place tests for your plugin here. ``test_data`` folder contains following
+common data for your tests, if you need them. 
+
+===============   ===========================
+File/Folder       Description
+===============   ===========================
+content           A sample content folder
+themes            Default themes from Pelican
+pelican.conf.py   A sample settings file
+===============   ===========================

+ 2 - 0
tests/__init__.py

@@ -0,0 +1,2 @@
+import logging
+logging.getLogger().addHandler(logging.NullHandler())

+ 142 - 0
tests/test_assets.py

@@ -0,0 +1,142 @@
+# -*- coding: utf-8 -*-
+# from __future__ import unicode_literals
+
+import hashlib
+import locale
+import os
+from codecs import open
+from tempfile import mkdtemp
+from shutil import rmtree
+import unittest
+import subprocess
+
+from pelican import Pelican
+from pelican.settings import read_settings
+
+CUR_DIR = os.path.dirname(__file__)
+THEME_DIR = os.path.join(CUR_DIR, 'test_data', 'themes', 'assets_theme')
+CSS_REF = open(os.path.join(THEME_DIR, 'static', 'css',
+                            'style.min.css')).read()
+CSS_HASH = hashlib.md5(CSS_REF).hexdigest()[0:8]
+
+
+def skipIfNoExecutable(executable):
+    """Skip test if `executable` is not found
+
+    Tries to run `executable` with subprocess to make sure it's in the path,
+    and skips the tests if not found (if subprocess raises a `OSError`).
+    """
+
+    with open(os.devnull, 'w') as fnull:
+        try:
+            res = subprocess.call(executable, stdout=fnull, stderr=fnull)
+        except OSError:
+            res = None
+
+    if res is None:
+        return unittest.skip('{0} executable not found'.format(executable))
+
+    return lambda func: func
+
+
+def module_exists(module_name):
+    """Test if a module is importable."""
+
+    try:
+        __import__(module_name)
+    except ImportError:
+        return False
+    else:
+        return True
+
+
+
+@unittest.skipUnless(module_exists('webassets'), "webassets isn't installed")
+@skipIfNoExecutable(['scss', '-v'])
+@skipIfNoExecutable(['cssmin', '--version'])
+class TestWebAssets(unittest.TestCase):
+    """Base class for testing webassets."""
+
+    def setUp(self, override=None):
+        import assets
+        
+        self.temp_path = mkdtemp(prefix='pelicantests.')
+        settings = {
+            'PATH': os.path.join(CUR_DIR, 'test_data', 'content'),
+            'OUTPUT_PATH': self.temp_path,
+            'PLUGINS': [assets],
+            'THEME': THEME_DIR,
+            'LOCALE': locale.normalize('en_US'),
+        }
+        if override:
+            settings.update(override)
+
+        self.settings = read_settings(override=settings)
+        pelican = Pelican(settings=self.settings)
+        pelican.run()
+
+    def tearDown(self):
+        rmtree(self.temp_path)
+
+    def check_link_tag(self, css_file, html_file):
+        """Check the presence of `css_file` in `html_file`."""
+
+        link_tag = ('<link rel="stylesheet" href="{css_file}">'
+                    .format(css_file=css_file))
+        html = open(html_file).read()
+        self.assertRegexpMatches(html, link_tag)
+
+
+class TestWebAssetsRelativeURLS(TestWebAssets):
+    """Test pelican with relative urls."""
+
+
+    def setUp(self):
+        TestWebAssets.setUp(self, override={'RELATIVE_URLS': True})
+
+    def test_jinja2_ext(self):
+        # Test that the Jinja2 extension was correctly added.
+
+        from webassets.ext.jinja2 import AssetsExtension
+        self.assertIn(AssetsExtension, self.settings['JINJA_EXTENSIONS'])
+
+    def test_compilation(self):
+        # Compare the compiled css with the reference.
+
+        gen_file = os.path.join(self.temp_path, 'theme', 'gen',
+                                'style.{0}.min.css'.format(CSS_HASH))
+        self.assertTrue(os.path.isfile(gen_file))
+
+        css_new = open(gen_file).read()
+        self.assertEqual(css_new, CSS_REF)
+
+    def test_template(self):
+        # Look in the output files for the link tag.
+
+        css_file = './theme/gen/style.{0}.min.css'.format(CSS_HASH)
+        html_files = ['index.html', 'archives.html',
+                      'this-is-a-super-article.html']
+        for f in html_files:
+            self.check_link_tag(css_file, os.path.join(self.temp_path, f))
+
+        self.check_link_tag(
+            '../theme/gen/style.{0}.min.css'.format(CSS_HASH),
+            os.path.join(self.temp_path, 'category/yeah.html'))
+
+
+class TestWebAssetsAbsoluteURLS(TestWebAssets):
+    """Test pelican with absolute urls."""
+
+    def setUp(self):
+        TestWebAssets.setUp(self, override={'RELATIVE_URLS': False,
+                                            'SITEURL': 'http://localhost'})
+
+    def test_absolute_url(self):
+        # Look in the output files for the link tag with absolute url.
+
+        css_file = ('http://localhost/theme/gen/style.{0}.min.css'
+                    .format(CSS_HASH))
+        html_files = ['index.html', 'archives.html',
+                      'this-is-a-super-article.html']
+        for f in html_files:
+            self.check_link_tag(css_file, os.path.join(self.temp_path, f))

+ 4 - 0
tests/test_data/content/2012-11-30_filename-metadata.rst

@@ -0,0 +1,4 @@
+FILENAME_METADATA example
+#########################
+
+Some cool stuff!

+ 7 - 0
tests/test_data/content/another_super_article-fr.rst

@@ -0,0 +1,7 @@
+Trop bien !
+###########
+
+:lang: fr
+:slug: oh-yeah
+
+Et voila du contenu en français

+ 20 - 0
tests/test_data/content/another_super_article.rst

@@ -0,0 +1,20 @@
+Oh yeah !
+#########
+
+:tags: oh, bar, yeah
+:date: 2010-10-20 10:14
+:category: bar
+:author: Alexis Métaireau
+:slug: oh-yeah
+:license: WTFPL
+
+Why not ?
+=========
+
+After all, why not ? It's pretty simple to do it, and it will allow me to write my blogposts in rst !
+YEAH !
+
+.. image:: |filename|/pictures/Sushi.jpg
+   :height: 450 px
+   :width: 600 px
+   :alt: alternate text

+ 9 - 0
tests/test_data/content/article2-fr.rst

@@ -0,0 +1,9 @@
+Deuxième article
+################
+
+:tags: foo, bar, baz
+:date: 2012-02-29
+:lang: fr
+:slug: second-article
+
+Ceci est un article, en français.

+ 9 - 0
tests/test_data/content/article2.rst

@@ -0,0 +1,9 @@
+Second article
+##############
+
+:tags: foo, bar, baz
+:date: 2012-02-29
+:lang: en
+:slug: second-article
+
+This is some article, in english

+ 7 - 0
tests/test_data/content/cat1/article1.rst

@@ -0,0 +1,7 @@
+Article 1
+#########
+
+:date: 2011-02-17
+:yeah: oh yeah !
+
+Article 1

+ 6 - 0
tests/test_data/content/cat1/article2.rst

@@ -0,0 +1,6 @@
+Article 2
+#########
+
+:date: 2011-02-17
+
+Article 2

+ 6 - 0
tests/test_data/content/cat1/article3.rst

@@ -0,0 +1,6 @@
+Article 3
+#########
+
+:date: 2011-02-17
+
+Article 3

+ 7 - 0
tests/test_data/content/cat1/markdown-article.md

@@ -0,0 +1,7 @@
+Title: A markdown powered article
+Date: 2011-04-20
+
+You're mutually oblivious.
+
+[a root-relative link to unbelievable](|filename|/unbelievable.rst)
+[a file-relative link to unbelievable](|filename|../unbelievable.rst)

+ 7 - 0
tests/test_data/content/draft_article.rst

@@ -0,0 +1,7 @@
+A draft article
+###############
+
+:status: draft
+
+This is a draft article, it should live under the /drafts/ folder and not be
+listed anywhere else.

+ 2 - 0
tests/test_data/content/extra/robots.txt

@@ -0,0 +1,2 @@
+User-agent: *
+Disallow: /static/pictures

+ 9 - 0
tests/test_data/content/pages/hidden_page.rst

@@ -0,0 +1,9 @@
+This is a test hidden page
+##########################
+
+:category: test
+:status: hidden
+
+This is great for things like error(404) pages
+Anyone can see this page but it's not linked to anywhere!
+

+ 6 - 0
tests/test_data/content/pages/jinja2_template.html

@@ -0,0 +1,6 @@
+{% extends "base.html" %}
+{% block content %}
+
+Some text
+
+{% endblock %}

+ 9 - 0
tests/test_data/content/pages/override_url_saveas.rst

@@ -0,0 +1,9 @@
+Override url/save_as
+####################
+
+:date: 2012-12-07
+:url: override/
+:save_as: override/index.html
+
+Test page which overrides save_as and url so that this page will be generated
+at a custom location.

+ 12 - 0
tests/test_data/content/pages/test_page.rst

@@ -0,0 +1,12 @@
+This is a test page
+###################
+
+:category: test
+
+Just an image.
+
+.. image:: |filename|/pictures/Fat_Cat.jpg
+   :height: 450 px
+   :width: 600 px
+   :alt: alternate text
+

BIN
tests/test_data/content/pictures/Fat_Cat.jpg


BIN
tests/test_data/content/pictures/Sushi.jpg


BIN
tests/test_data/content/pictures/Sushi_Macro.jpg


+ 36 - 0
tests/test_data/content/super_article.rst

@@ -0,0 +1,36 @@
+This is a super article !
+#########################
+
+:tags: foo, bar, foobar
+:date: 2010-12-02 10:14
+:category: yeah
+:author: Alexis Métaireau
+:summary:
+    Multi-line metadata should be supported
+    as well as **inline markup**.
+
+Some content here !
+
+This is a simple title
+======================
+
+And here comes the cool stuff_.
+
+.. image:: |filename|/pictures/Sushi.jpg
+   :height: 450 px
+   :width: 600 px
+   :alt: alternate text
+
+.. image:: |filename|/pictures/Sushi_Macro.jpg
+   :height: 450 px
+   :width: 600 px
+   :alt: alternate text
+
+::
+
+   >>> from ipdb import set_trace
+   >>> set_trace()
+
+→ And now try with some utf8 hell: ééé
+
+.. _stuff: http://books.couchdb.org/relax/design-documents/views

+ 9 - 0
tests/test_data/content/unbelievable.rst

@@ -0,0 +1,9 @@
+Unbelievable !
+##############
+
+:date: 2010-10-15 20:30
+
+Or completely awesome. Depends the needs.
+
+`a root-relative link to markdown-article <|filename|/cat1/markdown-article.md>`_
+`a file-relative link to markdown-article <|filename|cat1/markdown-article.md>`_

+ 1 - 0
tests/test_data/content/unwanted_file

@@ -0,0 +1 @@
+not to be parsed

+ 45 - 0
tests/test_data/pelican.conf.py

@@ -0,0 +1,45 @@
+# -*- coding: utf-8 -*-
+from __future__ import unicode_literals
+
+AUTHOR = 'Alexis Métaireau'
+SITENAME = "Alexis' log"
+SITEURL = 'http://blog.notmyidea.org'
+TIMEZONE = "Europe/Paris"
+
+GITHUB_URL = 'http://github.com/ametaireau/'
+DISQUS_SITENAME = "blog-notmyidea"
+PDF_GENERATOR = False
+REVERSE_CATEGORY_ORDER = True
+LOCALE = "C"
+DEFAULT_PAGINATION = 4
+DEFAULT_DATE = (2012, 3, 2, 14, 1, 1)
+
+FEED_ALL_RSS = 'feeds/all.rss.xml'
+CATEGORY_FEED_RSS = 'feeds/%s.rss.xml'
+
+LINKS = (('Biologeek', 'http://biologeek.org'),
+         ('Filyb', "http://filyb.info/"),
+         ('Libert-fr', "http://www.libert-fr.com"),
+         ('N1k0', "http://prendreuncafe.com/blog/"),
+         ('Tarek Ziadé', "http://ziade.org/blog"),
+         ('Zubin Mithra', "http://zubin71.wordpress.com/"),)
+
+SOCIAL = (('twitter', 'http://twitter.com/ametaireau'),
+          ('lastfm', 'http://lastfm.com/user/akounet'),
+          ('github', 'http://github.com/ametaireau'),)
+
+# global metadata to all the contents
+DEFAULT_METADATA = (('yeah', 'it is'),)
+
+# static paths will be copied under the same name
+STATIC_PATHS = ["pictures", ]
+
+# A list of files to copy from the source to the destination
+FILES_TO_COPY = (('extra/robots.txt', 'robots.txt'),)
+
+# custom page generated with a jinja2 template
+TEMPLATE_PAGES = {'pages/jinja2_template.html': 'jinja2_template.html'}
+
+# foobar will not be used, because it's not in caps. All configuration keys
+# have to be in caps
+foobar = "barbaz"

+ 1 - 0
tests/test_data/themes/assets_theme/static/css/style.min.css

@@ -0,0 +1 @@
+body{font:14px/1.5 "Droid Sans",sans-serif;background-color:#e4e4e4;color:#242424}a{color:red}a:hover{color:orange}

+ 19 - 0
tests/test_data/themes/assets_theme/static/css/style.scss

@@ -0,0 +1,19 @@
+/* -*- scss-compile-at-save: nil -*- */
+
+$baseFontFamily : "Droid Sans", sans-serif;
+$textColor      : #242424;
+$bodyBackground : #e4e4e4;
+
+body {
+  font: 14px/1.5 $baseFontFamily;
+  background-color: $bodyBackground;
+  color: $textColor;
+}
+
+a {
+  color: red;
+
+  &:hover {
+    color: orange;
+  }
+}

+ 7 - 0
tests/test_data/themes/assets_theme/templates/base.html

@@ -0,0 +1,7 @@
+{% extends "!simple/base.html" %}
+
+{% block head %}
+  {% assets filters="scss,cssmin", output="gen/style.%(version)s.min.css", "css/style.scss" %}
+    <link rel="stylesheet" href="{{ SITEURL }}/{{ ASSET_URL }}">
+  {% endassets %}
+{% endblock %}

+ 446 - 0
tests/test_data/themes/notmyidea/static/css/main.css

@@ -0,0 +1,446 @@
+/*
+	Name: Smashing HTML5
+	Date: July 2009
+	Description: Sample layout for HTML5 and CSS3 goodness.
+	Version: 1.0
+	Author: Enrique Ramírez
+	Autor URI: http://enrique-ramirez.com
+*/
+
+/* Imports */
+@import url("reset.css");
+@import url("pygment.css");
+@import url("typogrify.css");
+@import url(//fonts.googleapis.com/css?family=Yanone+Kaffeesatz&subset=latin);
+
+/***** Global *****/
+/* Body */
+body {
+    background: #F5F4EF;
+    color: #000305;
+    font-size: 87.5%; /* Base font size: 14px */
+    font-family: 'Trebuchet MS', Trebuchet, 'Lucida Sans Unicode', 'Lucida Grande', 'Lucida Sans', Arial, sans-serif;
+    line-height: 1.429;
+    margin: 0;
+    padding: 0;
+    text-align: left;
+}
+
+/* Headings */
+h1 {font-size: 2em }
+h2 {font-size: 1.571em}	/* 22px */
+h3 {font-size: 1.429em}	/* 20px */
+h4 {font-size: 1.286em}	/* 18px */
+h5 {font-size: 1.143em}	/* 16px */
+h6 {font-size: 1em}		/* 14px */
+
+h1, h2, h3, h4, h5, h6 {
+	font-weight: 400;
+	line-height: 1.1;
+	margin-bottom: .8em;
+    font-family: 'Yanone Kaffeesatz', arial, serif;
+}
+
+h3, h4, h5, h6 { margin-top: .8em; }
+	
+hr { border: 2px solid #EEEEEE; }
+
+/* Anchors */
+a {outline: 0;}
+a img {border: 0px; text-decoration: none;}
+a:link, a:visited {
+	color: #C74350;
+	padding: 0 1px;
+	text-decoration: underline;
+}
+a:hover, a:active {
+	background-color: #C74350;
+	color: #fff;
+	text-decoration: none;
+	text-shadow: 1px 1px 1px #333;
+}
+
+h1 a:hover {
+    background-color: inherit
+}
+	
+/* Paragraphs */
+div.line-block,
+p { margin-top: 1em;
+    margin-bottom: 1em;}
+
+strong, b {font-weight: bold;}
+em, i {font-style: italic;}
+
+/* Lists */
+ul {
+	list-style: outside disc;
+	margin: 0em 0 0 1.5em;
+}
+
+ol {
+	list-style: outside decimal;
+	margin: 0em 0 0 1.5em;
+}
+
+li { margin-top: 0.5em;}
+
+.post-info {
+    float:right;
+    margin:10px;
+    padding:5px;
+}
+
+.post-info p{
+    margin-top: 1px;
+    margin-bottom: 1px;
+}
+
+.readmore { float: right }
+
+dl {margin: 0 0 1.5em 0;}
+dt {font-weight: bold;}
+dd {margin-left: 1.5em;}
+
+pre{background-color:  rgb(238, 238, 238); padding: 10px; margin: 10px; overflow: auto;}
+
+/* Quotes */
+blockquote {
+    margin: 20px;
+    font-style: italic;
+}
+cite {}
+
+q {}
+
+div.note {
+   float: right;
+   margin: 5px;
+   font-size: 85%;
+   max-width: 300px;
+}
+
+/* Tables */
+table {margin: .5em auto 1.5em auto; width: 98%;}
+	
+	/* Thead */
+	thead th {padding: .5em .4em; text-align: left;}
+	thead td {}
+
+	/* Tbody */
+	tbody td {padding: .5em .4em;}
+	tbody th {}
+	
+	tbody .alt td {}
+	tbody .alt th {}
+	
+	/* Tfoot */
+	tfoot th {}
+	tfoot td {}
+	
+/* HTML5 tags */
+header, section, footer,
+aside, nav, article, figure {
+	display: block;
+}
+
+/***** Layout *****/
+.body {clear: both; margin: 0 auto; width: 800px;}
+img.right, figure.right {float: right; margin: 0 0 2em 2em;}
+img.left, figure.left {float: left; margin: 0 2em 2em 0;}
+
+/*
+	Header
+*****************/
+#banner {
+	margin: 0 auto;
+	padding: 2.5em 0 0 0;
+}
+
+	/* Banner */
+	#banner h1 {font-size: 3.571em; line-height: 0;}
+	#banner h1 a:link, #banner h1 a:visited {
+		color: #000305;
+		display: block;
+		font-weight: bold;
+		margin: 0 0 .6em .2em;
+		text-decoration: none;
+	}
+	#banner h1 a:hover, #banner h1 a:active {
+		background: none;
+		color: #C74350;
+		text-shadow: none;
+	}
+	
+	#banner h1 strong {font-size: 0.36em; font-weight: normal;}
+	
+	/* Main Nav */
+	#banner nav {
+		background: #000305;
+		font-size: 1.143em;
+		height: 40px;
+		line-height: 30px;
+		margin: 0 auto 2em auto;
+		padding: 0;
+		text-align: center;
+		width: 800px;
+		
+		border-radius: 5px;
+		-moz-border-radius: 5px;
+		-webkit-border-radius: 5px;
+	}
+	
+	#banner nav ul {list-style: none; margin: 0 auto; width: 800px;}
+	#banner nav li {float: left; display: inline; margin: 0;}
+	
+	#banner nav a:link, #banner nav a:visited {
+		color: #fff;
+		display: inline-block;
+		height: 30px;
+		padding: 5px 1.5em;
+		text-decoration: none;
+	}
+	#banner nav a:hover, #banner nav a:active,
+	#banner nav .active a:link, #banner nav .active a:visited {
+		background: #C74451;
+		color: #fff;
+		text-shadow: none !important;
+	}
+	
+	#banner nav li:first-child a {
+		border-top-left-radius: 5px;
+		-moz-border-radius-topleft: 5px;
+		-webkit-border-top-left-radius: 5px;
+		
+		border-bottom-left-radius: 5px;
+		-moz-border-radius-bottomleft: 5px;
+		-webkit-border-bottom-left-radius: 5px;
+	}
+
+/*
+	Featured
+*****************/
+#featured {
+	background: #fff;
+	margin-bottom: 2em;
+	overflow: hidden;
+	padding: 20px;
+	width: 760px;
+	
+	border-radius: 10px;
+	-moz-border-radius: 10px;
+	-webkit-border-radius: 10px;
+}
+
+#featured figure {
+	border: 2px solid #eee;
+	float: right;
+	margin: 0.786em 2em 0 5em;
+	width: 248px;
+}
+#featured figure img {display: block; float: right;}
+
+#featured h2 {color: #C74451; font-size: 1.714em; margin-bottom: 0.333em;}
+#featured h3 {font-size: 1.429em; margin-bottom: .5em;}
+
+#featured h3 a:link, #featured h3 a:visited {color: #000305; text-decoration: none;}
+#featured h3 a:hover, #featured h3 a:active {color: #fff;}
+
+/*
+	Body
+*****************/
+#content {
+	background: #fff;
+	margin-bottom: 2em;
+	overflow: hidden;
+	padding: 20px 20px;
+	width: 760px;
+	
+	border-radius: 10px;
+	-moz-border-radius: 10px;
+	-webkit-border-radius: 10px;
+}
+
+/*
+	Extras
+*****************/
+#extras {margin: 0 auto 3em auto; overflow: hidden;}
+
+#extras ul {list-style: none; margin: 0;}
+#extras li {border-bottom: 1px solid #fff;}
+#extras h2 {
+	color: #C74350;
+	font-size: 1.429em;
+	margin-bottom: .25em;
+	padding: 0 3px;
+}
+
+#extras a:link, #extras a:visited {
+	color: #444;
+	display: block;
+	border-bottom: 1px solid #F4E3E3;
+	text-decoration: none;
+	padding: .3em .25em;
+}
+
+#extras a:hover, #extras a:active {color: #fff;}
+
+	/* Blogroll */
+	#extras .blogroll {
+		float: left;
+		width: 615px;
+	}
+	
+	#extras .blogroll li {float: left; margin: 0 20px 0 0; width: 185px;}
+	
+	/* Social */
+	#extras .social {
+		float: right;
+		width: 175px;
+	}
+	
+	#extras div[class='social'] a {
+		background-repeat: no-repeat;
+		background-position: 3px 6px;
+		padding-left: 25px;
+	}
+	
+		/* Icons */
+		.social a[href*='about.me'] {background-image: url('../images/icons/aboutme.png');}
+		.social a[href*='bitbucket.org'] {background-image: url('../images/icons/bitbucket.png');}
+		.social a[href*='delicious.com'] {background-image: url('../images/icons/delicious.png');}
+		.social a[href*='digg.com'] {background-image: url('../images/icons/digg.png');}
+		.social a[href*='facebook.com'] {background-image: url('../images/icons/facebook.png');}
+		.social a[href*='gitorious.org'] {background-image: url('../images/icons/gitorious.png');}
+		.social a[href*='github.com'],
+		.social a[href*='git.io'] {background-image: url('../images/icons/github.png');}
+		.social a[href*='gittip.com'] {background-image: url('../images/icons/gittip.png');}
+		.social a[href*='plus.google.com'] {background-image: url('../images/icons/google-plus.png');}
+		.social a[href*='groups.google.com'] {background-image: url('../images/icons/google-groups.png');}
+		.social a[href*='news.ycombinator.com'],
+		.social a[href*='hackernewsers.com'] {background-image: url('../images/icons/hackernews.png');}
+		.social a[href*='last.fm'], .social a[href*='lastfm.'] {background-image: url('../images/icons/lastfm.png');}
+		.social a[href*='linkedin.com'] {background-image: url('../images/icons/linkedin.png');}
+		.social a[href*='reddit.com'] {background-image: url('../images/icons/reddit.png');}
+		.social a[type$='atom+xml'], .social a[type$='rss+xml'] {background-image: url('../images/icons/rss.png');}
+		.social a[href*='slideshare.net'] {background-image: url('../images/icons/slideshare.png');}
+		.social a[href*='speakerdeck.com'] {background-image: url('../images/icons/speakerdeck.png');}
+		.social a[href*='twitter.com'] {background-image: url('../images/icons/twitter.png');}
+		.social a[href*='vimeo.com'] {background-image: url('../images/icons/vimeo.png');}
+		.social a[href*='youtube.com'] {background-image: url('../images/icons/youtube.png');}
+
+/*
+	About
+*****************/
+#about {
+	background: #fff;
+	font-style: normal;
+	margin-bottom: 2em;
+	overflow: hidden;
+	padding: 20px;
+	text-align: left;
+	width: 760px;
+	
+	border-radius: 10px;
+	-moz-border-radius: 10px;
+	-webkit-border-radius: 10px;
+}
+
+#about .primary {float: left; width: 165px;}
+#about .primary strong {color: #C64350; display: block; font-size: 1.286em;}
+#about .photo {float: left; margin: 5px 20px;}
+
+#about .url:link, #about .url:visited {text-decoration: none;}
+
+#about .bio {float: right; width: 500px;}
+
+/*
+	Footer
+*****************/
+#contentinfo {padding-bottom: 2em; text-align: right;}
+
+/***** Sections *****/
+/* Blog */
+.hentry {
+	display: block;
+	clear: both;
+	border-bottom: 1px solid #eee;
+	padding: 1.5em 0;
+}
+li:last-child .hentry, #content > .hentry {border: 0; margin: 0;}
+#content > .hentry {padding: 1em 0;}
+.hentry img{display : none ;}
+.entry-title {font-size: 3em; margin-bottom: 10px; margin-top: 0;}
+.entry-title a:link, .entry-title a:visited {text-decoration: none; color: #333;}
+.entry-title a:visited {background-color: #fff;}
+
+.hentry .post-info * {font-style: normal;}
+
+	/* Content */
+	.hentry footer {margin-bottom: 2em;}
+	.hentry footer address {display: inline;}
+	#posts-list footer address {display: block;}
+
+	/* Blog Index */
+	#posts-list {list-style: none; margin: 0;}
+	#posts-list .hentry {padding-left: 10px; position: relative;}
+	
+	#posts-list footer {
+		left: 10px;
+		position: relative;
+        float: left;
+		top: 0.5em;
+		width: 190px;
+	}
+	
+	/* About the Author */
+	#about-author {
+		background: #f9f9f9;
+		clear: both;
+		font-style: normal;
+		margin: 2em 0;
+		padding: 10px 20px 15px 20px;
+		
+		border-radius: 5px;
+		-moz-border-radius: 5px;
+		-webkit-border-radius: 5px;
+	}
+	
+	#about-author strong {
+		color: #C64350;
+		clear: both;
+		display: block;
+		font-size: 1.429em;
+	}
+	
+	#about-author .photo {border: 1px solid #ddd; float: left; margin: 5px 1em 0 0;}
+	
+	/* Comments */
+	#comments-list {list-style: none; margin: 0 1em;}
+	#comments-list blockquote {
+		background: #f8f8f8;
+		clear: both;
+		font-style: normal;
+		margin: 0;
+		padding: 15px 20px;
+		
+		border-radius: 5px;
+		-moz-border-radius: 5px;
+		-webkit-border-radius: 5px;
+	}
+	#comments-list footer {color: #888; padding: .5em 1em 0 0; text-align: right;}
+	
+	#comments-list li:nth-child(2n) blockquote {background: #F5f5f5;}
+	
+	/* Add a Comment */
+	#add-comment label {clear: left; float: left; text-align: left; width: 150px;}
+	#add-comment input[type='text'],
+	#add-comment input[type='email'],
+	#add-comment input[type='url'] {float: left; width: 200px;}
+	
+	#add-comment textarea {float: left; height: 150px; width: 495px;}
+	
+	#add-comment p.req {clear: both; margin: 0 .5em 1em 0; text-align: right;}
+	
+	#add-comment input[type='submit'] {float: right; margin: 0 .5em;}
+	#add-comment * {margin-bottom: .5em;}

+ 205 - 0
tests/test_data/themes/notmyidea/static/css/pygment.css

@@ -0,0 +1,205 @@
+.hll {
+background-color:#eee;
+}
+.c {
+color:#408090;
+font-style:italic;
+}
+.err {
+border:1px solid #FF0000;
+}
+.k {
+color:#007020;
+font-weight:bold;
+}
+.o {
+color:#666666;
+}
+.cm {
+color:#408090;
+font-style:italic;
+}
+.cp {
+color:#007020;
+}
+.c1 {
+color:#408090;
+font-style:italic;
+}
+.cs {
+background-color:#FFF0F0;
+color:#408090;
+}
+.gd {
+color:#A00000;
+}
+.ge {
+font-style:italic;
+}
+.gr {
+color:#FF0000;
+}
+.gh {
+color:#000080;
+font-weight:bold;
+}
+.gi {
+color:#00A000;
+}
+.go {
+color:#303030;
+}
+.gp {
+color:#C65D09;
+font-weight:bold;
+}
+.gs {
+font-weight:bold;
+}
+.gu {
+color:#800080;
+font-weight:bold;
+}
+.gt {
+color:#0040D0;
+}
+.kc {
+color:#007020;
+font-weight:bold;
+}
+.kd {
+color:#007020;
+font-weight:bold;
+}
+.kn {
+color:#007020;
+font-weight:bold;
+}
+.kp {
+color:#007020;
+}
+.kr {
+color:#007020;
+font-weight:bold;
+}
+.kt {
+color:#902000;
+}
+.m {
+color:#208050;
+}
+.s {
+color:#4070A0;
+}
+.na {
+color:#4070A0;
+}
+.nb {
+color:#007020;
+}
+.nc {
+color:#0E84B5;
+font-weight:bold;
+}
+.no {
+color:#60ADD5;
+}
+.nd {
+color:#555555;
+font-weight:bold;
+}
+.ni {
+color:#D55537;
+font-weight:bold;
+}
+.ne {
+color:#007020;
+}
+.nf {
+color:#06287E;
+}
+.nl {
+color:#002070;
+font-weight:bold;
+}
+.nn {
+color:#0E84B5;
+font-weight:bold;
+}
+.nt {
+color:#062873;
+font-weight:bold;
+}
+.nv {
+color:#BB60D5;
+}
+.ow {
+color:#007020;
+font-weight:bold;
+}
+.w {
+color:#BBBBBB;
+}
+.mf {
+color:#208050;
+}
+.mh {
+color:#208050;
+}
+.mi {
+color:#208050;
+}
+.mo {
+color:#208050;
+}
+.sb {
+color:#4070A0;
+}
+.sc {
+color:#4070A0;
+}
+.sd {
+color:#4070A0;
+font-style:italic;
+}
+.s2 {
+color:#4070A0;
+}
+.se {
+color:#4070A0;
+font-weight:bold;
+}
+.sh {
+color:#4070A0;
+}
+.si {
+color:#70A0D0;
+font-style:italic;
+}
+.sx {
+color:#C65D09;
+}
+.sr {
+color:#235388;
+}
+.s1 {
+color:#4070A0;
+}
+.ss {
+color:#517918;
+}
+.bp {
+color:#007020;
+}
+.vc {
+color:#BB60D5;
+}
+.vg {
+color:#BB60D5;
+}
+.vi {
+color:#BB60D5;
+}
+.il {
+color:#208050;
+}

+ 52 - 0
tests/test_data/themes/notmyidea/static/css/reset.css

@@ -0,0 +1,52 @@
+/*
+	Name: Reset Stylesheet
+	Description: Resets browser's default CSS
+	Author: Eric Meyer
+	Author URI: http://meyerweb.com/eric/tools/css/reset/
+*/
+
+/* v1.0 | 20080212 */
+html, body, div, span, applet, object, iframe,
+h1, h2, h3, h4, h5, h6, p, blockquote, pre,
+a, abbr, acronym, address, big, cite, code,
+del, dfn, em, font, img, ins, kbd, q, s, samp,
+small, strike, strong, sub, sup, tt, var,
+b, u, i, center,
+dl, dt, dd, ol, ul, li,
+fieldset, form, label, legend,
+table, caption, tbody, tfoot, thead, tr, th, td {
+	background: transparent;
+	border: 0;
+	font-size: 100%;
+	margin: 0;
+	outline: 0;
+	padding: 0;
+	vertical-align: baseline;
+}
+
+body {line-height: 1;}
+
+ol, ul {list-style: none;}
+
+blockquote, q {quotes: none;}
+
+blockquote:before, blockquote:after,
+q:before, q:after {
+	content: '';
+	content: none;
+}
+
+/* remember to define focus styles! */
+:focus {
+	outline: 0;
+}
+
+/* remember to highlight inserts somehow! */
+ins {text-decoration: none;}
+del {text-decoration: line-through;}
+
+/* tables still need 'cellspacing="0"' in the markup */
+table {
+	border-collapse: collapse;
+	border-spacing: 0;
+}

+ 3 - 0
tests/test_data/themes/notmyidea/static/css/typogrify.css

@@ -0,0 +1,3 @@
+.caps {font-size:.92em;}
+.amp {color:#666; font-size:1.05em;font-family:"Warnock Pro", "Goudy Old Style","Palatino","Book Antiqua",serif; font-style:italic;}    
+.dquo {margin-left:-.38em;}

+ 48 - 0
tests/test_data/themes/notmyidea/static/css/wide.css

@@ -0,0 +1,48 @@
+@import url("main.css");
+
+body {
+    font:1.3em/1.3 "Hoefler Text","Georgia",Georgia,serif,sans-serif;
+}
+
+.post-info{
+    display: none;
+}
+
+#banner nav {
+    display: none;
+    -moz-border-radius: 0px;
+    margin-bottom: 20px;
+    overflow: hidden;
+    font-size: 1em;
+    background: #F5F4EF;
+}
+
+#banner nav ul{
+    padding-right: 50px;
+}
+
+#banner nav li{
+    float: right;
+    color: #000;
+}
+
+#banner nav li a {
+    color: #000;
+}
+
+#banner h1 {
+    margin-bottom: -18px;
+}
+
+#featured, #extras {
+    padding: 50px;
+}
+
+#featured {
+    padding-top: 20px;
+}
+
+#extras {
+    padding-top: 0px;
+    padding-bottom: 0px;
+}

BIN
tests/test_data/themes/notmyidea/static/images/icons/aboutme.png


BIN
tests/test_data/themes/notmyidea/static/images/icons/bitbucket.png


BIN
tests/test_data/themes/notmyidea/static/images/icons/delicious.png


BIN
tests/test_data/themes/notmyidea/static/images/icons/facebook.png


BIN
tests/test_data/themes/notmyidea/static/images/icons/github.png


BIN
tests/test_data/themes/notmyidea/static/images/icons/gitorious.png


BIN
tests/test_data/themes/notmyidea/static/images/icons/gittip.png


BIN
tests/test_data/themes/notmyidea/static/images/icons/google-groups.png


BIN
tests/test_data/themes/notmyidea/static/images/icons/google-plus.png


BIN
tests/test_data/themes/notmyidea/static/images/icons/hackernews.png


BIN
tests/test_data/themes/notmyidea/static/images/icons/lastfm.png


BIN
tests/test_data/themes/notmyidea/static/images/icons/linkedin.png


BIN
tests/test_data/themes/notmyidea/static/images/icons/reddit.png


BIN
tests/test_data/themes/notmyidea/static/images/icons/rss.png


BIN
tests/test_data/themes/notmyidea/static/images/icons/slideshare.png


BIN
tests/test_data/themes/notmyidea/static/images/icons/speakerdeck.png


+ 0 - 0
tests/test_data/themes/notmyidea/static/images/icons/twitter.png


Some files were not shown because too many files changed in this diff