forked from github/pelican
Merge branch 'pr/558'
Conflicts: docs/settings.rst pelican/signals.py
This commit is contained in:
commit
801bc755b5
16 changed files with 298 additions and 244 deletions
|
|
@ -2,10 +2,15 @@ language: python
|
||||||
python:
|
python:
|
||||||
- "2.6"
|
- "2.6"
|
||||||
- "2.7"
|
- "2.7"
|
||||||
|
before_install:
|
||||||
|
- sudo apt-get update -qq
|
||||||
|
- sudo apt-get install -qq ruby-sass
|
||||||
install:
|
install:
|
||||||
- pip install nose unittest2 mock --use-mirrors
|
- pip install nose unittest2 mock --use-mirrors
|
||||||
- pip install . --use-mirrors
|
- pip install . --use-mirrors
|
||||||
- pip install Markdown
|
- pip install Markdown
|
||||||
|
- pip install webassets
|
||||||
|
- pip install cssmin
|
||||||
script: nosetests -s tests
|
script: nosetests -s tests
|
||||||
notifications:
|
notifications:
|
||||||
irc:
|
irc:
|
||||||
|
|
|
||||||
|
|
@ -27,7 +27,7 @@ Pelican currently supports:
|
||||||
* Publication of articles in multiple languages
|
* Publication of articles in multiple languages
|
||||||
* Atom/RSS feeds
|
* Atom/RSS feeds
|
||||||
* Code syntax highlighting
|
* Code syntax highlighting
|
||||||
* Compilation of `LESS CSS`_ (optional)
|
* Asset management with `webassets`_ (optional)
|
||||||
* Import from WordPress, Dotclear, or RSS feeds
|
* Import from WordPress, Dotclear, or RSS feeds
|
||||||
* Integration with external tools: Twitter, Google Analytics, etc. (optional)
|
* Integration with external tools: Twitter, Google Analytics, etc. (optional)
|
||||||
|
|
||||||
|
|
@ -62,9 +62,9 @@ client handy, use the webchat_ for quick feedback.
|
||||||
.. _reStructuredText: http://docutils.sourceforge.net/rst.html
|
.. _reStructuredText: http://docutils.sourceforge.net/rst.html
|
||||||
.. _Markdown: http://daringfireball.net/projects/markdown/
|
.. _Markdown: http://daringfireball.net/projects/markdown/
|
||||||
.. _Jinja2: http://jinja.pocoo.org/
|
.. _Jinja2: http://jinja.pocoo.org/
|
||||||
.. _`LESS CSS`: http://lesscss.org/
|
|
||||||
.. _`Pelican documentation`: http://docs.getpelican.com/latest/
|
.. _`Pelican documentation`: http://docs.getpelican.com/latest/
|
||||||
.. _`Pelican's internals`: http://docs.getpelican.com/en/latest/internals.html
|
.. _`Pelican's internals`: http://docs.getpelican.com/en/latest/internals.html
|
||||||
.. _`#pelican on Freenode`: irc://irc.freenode.net/pelican
|
.. _`#pelican on Freenode`: irc://irc.freenode.net/pelican
|
||||||
.. _webchat: http://webchat.freenode.net/?channels=pelican&uio=d4
|
.. _webchat: http://webchat.freenode.net/?channels=pelican&uio=d4
|
||||||
.. _contribute: http://docs.getpelican.com/en/latest/contribute.html
|
.. _contribute: http://docs.getpelican.com/en/latest/contribute.html
|
||||||
|
.. _webassets: https://github.com/miracle2k/webassets
|
||||||
|
|
|
||||||
|
|
@ -23,7 +23,7 @@ Pelican currently supports:
|
||||||
* Publication of articles in multiple languages
|
* Publication of articles in multiple languages
|
||||||
* Atom/RSS feeds
|
* Atom/RSS feeds
|
||||||
* Code syntax highlighting
|
* Code syntax highlighting
|
||||||
* Compilation of `LESS CSS`_ (optional)
|
* Asset management with `webassets`_ (optional)
|
||||||
* Import from WordPress, Dotclear, or RSS feeds
|
* Import from WordPress, Dotclear, or RSS feeds
|
||||||
* Integration with external tools: Twitter, Google Analytics, etc. (optional)
|
* Integration with external tools: Twitter, Google Analytics, etc. (optional)
|
||||||
|
|
||||||
|
|
@ -75,8 +75,8 @@ A French version of the documentation is available at :doc:`fr/index`.
|
||||||
.. _reStructuredText: http://docutils.sourceforge.net/rst.html
|
.. _reStructuredText: http://docutils.sourceforge.net/rst.html
|
||||||
.. _Markdown: http://daringfireball.net/projects/markdown/
|
.. _Markdown: http://daringfireball.net/projects/markdown/
|
||||||
.. _Jinja2: http://jinja.pocoo.org/
|
.. _Jinja2: http://jinja.pocoo.org/
|
||||||
.. _`LESS CSS`: http://lesscss.org/
|
|
||||||
.. _`Pelican documentation`: http://docs.getpelican.com/latest/
|
.. _`Pelican documentation`: http://docs.getpelican.com/latest/
|
||||||
.. _`Pelican's internals`: http://docs.getpelican.com/en/latest/internals.html
|
.. _`Pelican's internals`: http://docs.getpelican.com/en/latest/internals.html
|
||||||
.. _`#pelican on Freenode`: irc://irc.freenode.net/pelican
|
.. _`#pelican on Freenode`: irc://irc.freenode.net/pelican
|
||||||
.. _webchat: http://webchat.freenode.net/?channels=pelican&uio=d4
|
.. _webchat: http://webchat.freenode.net/?channels=pelican&uio=d4
|
||||||
|
.. _webassets: https://github.com/miracle2k/webassets
|
||||||
|
|
|
||||||
|
|
@ -62,6 +62,7 @@ finalized pelican object invoked after al
|
||||||
usefull for custom post processing actions, such as:
|
usefull for custom post processing actions, such as:
|
||||||
- minifying js/css assets.
|
- minifying js/css assets.
|
||||||
- notify/ping search engines with an updated sitemap.
|
- notify/ping search engines with an updated sitemap.
|
||||||
|
generator_init generator invoked in the Generator.__init__
|
||||||
article_generate_context article_generator, metadata
|
article_generate_context article_generator, metadata
|
||||||
article_generate_preread article_generator invoked before a article is read in ArticlesGenerator.generate_context;
|
article_generate_preread article_generator invoked before a article is read in ArticlesGenerator.generate_context;
|
||||||
use if code needs to do something before every article is parsed
|
use if code needs to do something before every article is parsed
|
||||||
|
|
@ -101,6 +102,7 @@ List of plugins
|
||||||
|
|
||||||
The following plugins are currently included with Pelican under ``pelican.plugins``:
|
The following plugins are currently included with Pelican under ``pelican.plugins``:
|
||||||
|
|
||||||
|
* `Asset management`_
|
||||||
* `GitHub activity`_
|
* `GitHub activity`_
|
||||||
* `Global license`_
|
* `Global license`_
|
||||||
* `Gravatar`_
|
* `Gravatar`_
|
||||||
|
|
@ -116,6 +118,79 @@ Ideas for plugins that haven't been written yet:
|
||||||
Plugin descriptions
|
Plugin descriptions
|
||||||
===================
|
===================
|
||||||
|
|
||||||
|
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`, `yuicompressor`, ...)
|
||||||
|
* CSS compiler (`less`, `sass`, ...)
|
||||||
|
* JS minifier (`uglifyjs`, `yuicompressor`, `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 using 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 }}`` to
|
||||||
|
use in the templates is configured to be relative to the ``theme/`` url.
|
||||||
|
Hence, it must be used with the ``{{ SITEURL }}`` variable which allows to
|
||||||
|
have relative urls. 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:
|
||||||
|
|
||||||
|
.. 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. However, this also means that
|
||||||
|
the LESS and SASS files are not compiled. This should be fixed in a future
|
||||||
|
version of `webassets` (cf. the related `bug report
|
||||||
|
<https://github.com/getpelican/pelican/issues/481>`_).
|
||||||
|
|
||||||
|
.. _webassets: https://github.com/miracle2k/webassets
|
||||||
|
.. _webassets documentation: http://webassets.readthedocs.org/en/latest/builtin_filters.html
|
||||||
|
|
||||||
|
|
||||||
GitHub activity
|
GitHub activity
|
||||||
---------------
|
---------------
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -105,9 +105,6 @@ Setting name (default value) What doe
|
||||||
incorporated into the generated HTML via the `Typogrify
|
incorporated into the generated HTML via the `Typogrify
|
||||||
<http://static.mintchaos.com/projects/typogrify/>`_
|
<http://static.mintchaos.com/projects/typogrify/>`_
|
||||||
library, which can be installed via: ``pip install typogrify``
|
library, which can be installed via: ``pip install typogrify``
|
||||||
`LESS_GENERATOR` (``FALSE``) Set to True or complete path to `lessc` (if not
|
|
||||||
found in system PATH) to enable compiling less
|
|
||||||
css files. Requires installation of `less css`_.
|
|
||||||
`DIRECT_TEMPLATES` (``('index', 'tags', 'categories', 'archives')``) List of templates that are used directly to render
|
`DIRECT_TEMPLATES` (``('index', 'tags', 'categories', 'archives')``) List of templates that are used directly to render
|
||||||
content. Typically direct templates are used to generate
|
content. Typically direct templates are used to generate
|
||||||
index pages for collections of content e.g. tags and
|
index pages for collections of content e.g. tags and
|
||||||
|
|
@ -128,8 +125,6 @@ Setting name (default value) What doe
|
||||||
|
|
||||||
.. [#] Default is the system locale.
|
.. [#] Default is the system locale.
|
||||||
|
|
||||||
.. _less css: http://lesscss.org/
|
|
||||||
|
|
||||||
|
|
||||||
URL settings
|
URL settings
|
||||||
------------
|
------------
|
||||||
|
|
@ -436,7 +431,6 @@ Setting name (default value) What does it do?
|
||||||
value is `static`, but if your theme has
|
value is `static`, but if your theme has
|
||||||
other static paths, you can put them here.
|
other static paths, you can put them here.
|
||||||
`CSS_FILE` (``'main.css'``) Specify the CSS file you want to load.
|
`CSS_FILE` (``'main.css'``) Specify the CSS file you want to load.
|
||||||
`WEBASSETS` (``False``) Asset management with `webassets` (see below)
|
|
||||||
================================================ =====================================================
|
================================================ =====================================================
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -497,75 +491,6 @@ adding the following to your configuration::
|
||||||
|
|
||||||
CSS_FILE = "wide.css"
|
CSS_FILE = "wide.css"
|
||||||
|
|
||||||
Asset management
|
|
||||||
----------------
|
|
||||||
|
|
||||||
The `WEBASSETS` setting 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`, `yuicompressor`, ...)
|
|
||||||
* CSS compiler (`less`, `sass`, ...)
|
|
||||||
* JS minifier (`uglifyjs`, `yuicompressor`, `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 using 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. 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="{{ ASSET_URL }}">
|
|
||||||
{% endassets %}
|
|
||||||
|
|
||||||
... will produce a minified css file with a version identifier:
|
|
||||||
|
|
||||||
.. 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="{{ 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="{{ 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. However, this also means that
|
|
||||||
the LESS and SASS files are not compiled. This should be fixed in a future
|
|
||||||
version of `webassets` (cf. the related `bug report
|
|
||||||
<https://github.com/getpelican/pelican/issues/481>`_).
|
|
||||||
|
|
||||||
.. _webassets: https://github.com/miracle2k/webassets
|
|
||||||
.. _webassets documentation: http://webassets.readthedocs.org/en/latest/builtin_filters.html
|
|
||||||
|
|
||||||
Example settings
|
Example settings
|
||||||
================
|
================
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -9,8 +9,7 @@ from pelican import signals
|
||||||
|
|
||||||
from pelican.generators import (ArticlesGenerator, PagesGenerator,
|
from pelican.generators import (ArticlesGenerator, PagesGenerator,
|
||||||
StaticGenerator, PdfGenerator,
|
StaticGenerator, PdfGenerator,
|
||||||
LessCSSGenerator, SourceFileGenerator,
|
SourceFileGenerator, TemplatePagesGenerator)
|
||||||
TemplatePagesGenerator)
|
|
||||||
from pelican.log import init
|
from pelican.log import init
|
||||||
from pelican.settings import read_settings
|
from pelican.settings import read_settings
|
||||||
from pelican.utils import (clean_output_dir, files_changed, file_changed,
|
from pelican.utils import (clean_output_dir, files_changed, file_changed,
|
||||||
|
|
@ -159,11 +158,6 @@ class Pelican(object):
|
||||||
|
|
||||||
writer = self.get_writer()
|
writer = self.get_writer()
|
||||||
|
|
||||||
# pass the assets environment to the generators
|
|
||||||
if self.settings['WEBASSETS']:
|
|
||||||
generators[1].env.assets_environment = generators[0].assets_env
|
|
||||||
generators[2].env.assets_environment = generators[0].assets_env
|
|
||||||
|
|
||||||
for p in generators:
|
for p in generators:
|
||||||
if hasattr(p, 'generate_output'):
|
if hasattr(p, 'generate_output'):
|
||||||
p.generate_output(writer)
|
p.generate_output(writer)
|
||||||
|
|
@ -177,8 +171,6 @@ class Pelican(object):
|
||||||
generators.append(TemplatePagesGenerator)
|
generators.append(TemplatePagesGenerator)
|
||||||
if self.settings['PDF_GENERATOR']:
|
if self.settings['PDF_GENERATOR']:
|
||||||
generators.append(PdfGenerator)
|
generators.append(PdfGenerator)
|
||||||
if self.settings['LESS_GENERATOR']: # can be True or PATH to lessc
|
|
||||||
generators.append(LessCSSGenerator)
|
|
||||||
if self.settings['OUTPUT_SOURCES']:
|
if self.settings['OUTPUT_SOURCES']:
|
||||||
generators.append(SourceFileGenerator)
|
generators.append(SourceFileGenerator)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -62,6 +62,8 @@ class Generator(object):
|
||||||
custom_filters = self.settings.get('JINJA_FILTERS', {})
|
custom_filters = self.settings.get('JINJA_FILTERS', {})
|
||||||
self.env.filters.update(custom_filters)
|
self.env.filters.update(custom_filters)
|
||||||
|
|
||||||
|
signals.generator_init.send(self)
|
||||||
|
|
||||||
def get_template(self, name):
|
def get_template(self, name):
|
||||||
"""Return the template by name.
|
"""Return the template by name.
|
||||||
Use self.theme to get the templates to use, and return a list of
|
Use self.theme to get the templates to use, and return a list of
|
||||||
|
|
@ -477,37 +479,6 @@ class StaticGenerator(Generator):
|
||||||
copy(path, source, os.path.join(output_path, destination),
|
copy(path, source, os.path.join(output_path, destination),
|
||||||
final_path, overwrite=True)
|
final_path, overwrite=True)
|
||||||
|
|
||||||
def generate_context(self):
|
|
||||||
|
|
||||||
if self.settings['WEBASSETS']:
|
|
||||||
from webassets import Environment as AssetsEnvironment
|
|
||||||
|
|
||||||
# Define the assets environment that will be passed to the
|
|
||||||
# generators. The StaticGenerator must then be run first to have
|
|
||||||
# the assets in the output_path before generating the templates.
|
|
||||||
|
|
||||||
# Let ASSET_URL honor Pelican's RELATIVE_URLS setting.
|
|
||||||
# Hint for templates:
|
|
||||||
# Current version of webassets seem to remove any relative
|
|
||||||
# paths at the beginning of the URL. So, if RELATIVE_URLS
|
|
||||||
# is on, ASSET_URL will start with 'theme/', regardless if we
|
|
||||||
# set assets_url here to './theme/' or to 'theme/'.
|
|
||||||
# XXX However, this breaks the ASSET_URL if user navigates to
|
|
||||||
# a sub-URL, e.g. if he clicks on a category. To workaround this
|
|
||||||
# issue, I use
|
|
||||||
# <link rel="stylesheet" href="{{ SITEURL }}/{{ ASSET_URL }}">
|
|
||||||
# instead of
|
|
||||||
# <link rel="stylesheet" href="{{ ASSET_URL }}">
|
|
||||||
if self.settings.get('RELATIVE_URLS'):
|
|
||||||
assets_url = './theme/'
|
|
||||||
else:
|
|
||||||
assets_url = self.settings['SITEURL'] + '/theme/'
|
|
||||||
assets_src = os.path.join(self.output_path, 'theme')
|
|
||||||
self.assets_env = AssetsEnvironment(assets_src, assets_url)
|
|
||||||
|
|
||||||
if logging.getLevelName(logger.getEffectiveLevel()) == "DEBUG":
|
|
||||||
self.assets_env.debug = True
|
|
||||||
|
|
||||||
def generate_output(self, writer):
|
def generate_output(self, writer):
|
||||||
|
|
||||||
self._copy_paths(self.settings['STATIC_PATHS'], self.path,
|
self._copy_paths(self.settings['STATIC_PATHS'], self.path,
|
||||||
|
|
@ -583,49 +554,3 @@ class SourceFileGenerator(Generator):
|
||||||
logger.info(u' Generating source files...')
|
logger.info(u' Generating source files...')
|
||||||
for object in chain(self.context['articles'], self.context['pages']):
|
for object in chain(self.context['articles'], self.context['pages']):
|
||||||
self._create_source(object, self.output_path)
|
self._create_source(object, self.output_path)
|
||||||
|
|
||||||
class LessCSSGenerator(Generator):
|
|
||||||
"""Compile less css files."""
|
|
||||||
|
|
||||||
def _compile(self, less_file, source_dir, dest_dir):
|
|
||||||
base = os.path.relpath(less_file, source_dir)
|
|
||||||
target = os.path.splitext(
|
|
||||||
os.path.join(dest_dir, base))[0] + '.css'
|
|
||||||
target_dir = os.path.dirname(target)
|
|
||||||
|
|
||||||
if not os.path.exists(target_dir):
|
|
||||||
try:
|
|
||||||
os.makedirs(target_dir)
|
|
||||||
except OSError:
|
|
||||||
logger.error("Couldn't create the less css output folder in " +
|
|
||||||
target_dir)
|
|
||||||
|
|
||||||
subprocess.call([self._lessc, less_file, target])
|
|
||||||
logger.info(u' [ok] compiled %s' % base)
|
|
||||||
|
|
||||||
def generate_output(self, writer=None):
|
|
||||||
logger.info(u' Compiling less css')
|
|
||||||
|
|
||||||
# store out compiler here, so it won't be evaulted on each run of
|
|
||||||
# _compile
|
|
||||||
lg = self.settings['LESS_GENERATOR']
|
|
||||||
self._lessc = lg if isinstance(lg, basestring) else 'lessc'
|
|
||||||
|
|
||||||
# walk static paths
|
|
||||||
for static_path in self.settings['STATIC_PATHS']:
|
|
||||||
for f in self.get_files(
|
|
||||||
os.path.join(self.path, static_path),
|
|
||||||
extensions=['less']):
|
|
||||||
|
|
||||||
self._compile(f, self.path, self.output_path)
|
|
||||||
|
|
||||||
# walk theme static paths
|
|
||||||
theme_output_path = os.path.join(self.output_path, 'theme')
|
|
||||||
|
|
||||||
for static_path in self.settings['THEME_STATIC_PATHS']:
|
|
||||||
theme_static_path = os.path.join(self.theme, static_path)
|
|
||||||
for f in self.get_files(
|
|
||||||
theme_static_path,
|
|
||||||
extensions=['less']):
|
|
||||||
|
|
||||||
self._compile(f, theme_static_path, theme_output_path)
|
|
||||||
|
|
|
||||||
48
pelican/plugins/assets.py
Normal file
48
pelican/plugins/assets.py
Normal file
|
|
@ -0,0 +1,48 @@
|
||||||
|
# -*- 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/
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
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)
|
||||||
|
|
||||||
|
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)
|
||||||
|
|
@ -75,9 +75,7 @@ _DEFAULT_CONFIG = {'PATH': '.',
|
||||||
'DEFAULT_STATUS': 'published',
|
'DEFAULT_STATUS': 'published',
|
||||||
'ARTICLE_PERMALINK_STRUCTURE': '',
|
'ARTICLE_PERMALINK_STRUCTURE': '',
|
||||||
'TYPOGRIFY': False,
|
'TYPOGRIFY': False,
|
||||||
'LESS_GENERATOR': False,
|
|
||||||
'SUMMARY_MAX_LENGTH': 50,
|
'SUMMARY_MAX_LENGTH': 50,
|
||||||
'WEBASSETS': False,
|
|
||||||
'PLUGINS': [],
|
'PLUGINS': [],
|
||||||
'MARKDOWN_EXTENSIONS': ['toc', ],
|
'MARKDOWN_EXTENSIONS': ['toc', ],
|
||||||
'TEMPLATE_PAGES': {}
|
'TEMPLATE_PAGES': {}
|
||||||
|
|
@ -197,13 +195,9 @@ def configure_settings(settings):
|
||||||
"http://docs.notmyidea.org/alexis/pelican/settings.html#timezone "
|
"http://docs.notmyidea.org/alexis/pelican/settings.html#timezone "
|
||||||
"for more information")
|
"for more information")
|
||||||
|
|
||||||
if 'WEBASSETS' in settings and settings['WEBASSETS'] is not False:
|
if 'LESS_GENERATOR' in settings:
|
||||||
try:
|
logger.warn("The LESS_GENERATOR setting has been removed in favor "
|
||||||
from webassets.ext.jinja2 import AssetsExtension
|
"of the Webassets plugin")
|
||||||
settings['JINJA_EXTENSIONS'].append(AssetsExtension)
|
|
||||||
except ImportError:
|
|
||||||
logger.warn("You must install the webassets module to use WEBASSETS.")
|
|
||||||
settings['WEBASSETS'] = False
|
|
||||||
|
|
||||||
if 'OUTPUT_SOURCES_EXTENSION' in settings:
|
if 'OUTPUT_SOURCES_EXTENSION' in settings:
|
||||||
if not isinstance(settings['OUTPUT_SOURCES_EXTENSION'], str):
|
if not isinstance(settings['OUTPUT_SOURCES_EXTENSION'], str):
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@ from blinker import signal
|
||||||
initialized = signal('pelican_initialized')
|
initialized = signal('pelican_initialized')
|
||||||
finalized = signal('pelican_finalized')
|
finalized = signal('pelican_finalized')
|
||||||
article_generate_preread = signal('article_generate_preread')
|
article_generate_preread = signal('article_generate_preread')
|
||||||
|
generator_init = signal('generator_init')
|
||||||
article_generate_context = signal('article_generate_context')
|
article_generate_context = signal('article_generate_context')
|
||||||
article_generator_init = signal('article_generator_init')
|
article_generator_init = signal('article_generator_init')
|
||||||
article_generator_finalized = signal('article_generate_finalized')
|
article_generator_finalized = signal('article_generate_finalized')
|
||||||
|
|
|
||||||
|
|
@ -3,11 +3,12 @@ __all__ = [
|
||||||
'unittest',
|
'unittest',
|
||||||
]
|
]
|
||||||
|
|
||||||
import os
|
|
||||||
import subprocess
|
|
||||||
import re
|
|
||||||
import sys
|
|
||||||
import cStringIO
|
import cStringIO
|
||||||
|
import importlib
|
||||||
|
import os
|
||||||
|
import re
|
||||||
|
import subprocess
|
||||||
|
import sys
|
||||||
|
|
||||||
from functools import wraps
|
from functools import wraps
|
||||||
from contextlib import contextmanager
|
from contextlib import contextmanager
|
||||||
|
|
@ -138,3 +139,14 @@ def skipIfNoExecutable(executable):
|
||||||
return unittest.skip('{0} executable not found'.format(executable))
|
return unittest.skip('{0} executable not found'.format(executable))
|
||||||
|
|
||||||
return lambda func: func
|
return lambda func: func
|
||||||
|
|
||||||
|
|
||||||
|
def module_exists(module_name):
|
||||||
|
"""Test if a module is importable."""
|
||||||
|
|
||||||
|
try:
|
||||||
|
importlib.import_module(module_name)
|
||||||
|
except ImportError:
|
||||||
|
return False
|
||||||
|
else:
|
||||||
|
return True
|
||||||
|
|
|
||||||
|
|
@ -2,15 +2,16 @@
|
||||||
|
|
||||||
from mock import MagicMock
|
from mock import MagicMock
|
||||||
import os
|
import os
|
||||||
import re
|
|
||||||
|
from codecs import open
|
||||||
from tempfile import mkdtemp
|
from tempfile import mkdtemp
|
||||||
from shutil import rmtree
|
from shutil import rmtree
|
||||||
|
|
||||||
from pelican.generators import ArticlesGenerator, LessCSSGenerator, \
|
from pelican.generators import ArticlesGenerator, PagesGenerator, \
|
||||||
PagesGenerator, TemplatePagesGenerator
|
TemplatePagesGenerator
|
||||||
from pelican.writers import Writer
|
from pelican.writers import Writer
|
||||||
from pelican.settings import _DEFAULT_CONFIG
|
from pelican.settings import _DEFAULT_CONFIG
|
||||||
from .support import unittest, skipIfNoExecutable
|
from .support import unittest
|
||||||
|
|
||||||
CUR_DIR = os.path.dirname(__file__)
|
CUR_DIR = os.path.dirname(__file__)
|
||||||
|
|
||||||
|
|
@ -238,55 +239,3 @@ class TestTemplatePagesGenerator(unittest.TestCase):
|
||||||
# output content is correct
|
# output content is correct
|
||||||
with open(output_filename, 'r') as output_file:
|
with open(output_filename, 'r') as output_file:
|
||||||
self.assertEquals(output_file.read(), 'foo: bar')
|
self.assertEquals(output_file.read(), 'foo: bar')
|
||||||
|
|
||||||
|
|
||||||
class TestLessCSSGenerator(unittest.TestCase):
|
|
||||||
|
|
||||||
LESS_CONTENT = """
|
|
||||||
@color: #4D926F;
|
|
||||||
|
|
||||||
#header {
|
|
||||||
color: @color;
|
|
||||||
}
|
|
||||||
h2 {
|
|
||||||
color: @color;
|
|
||||||
}
|
|
||||||
"""
|
|
||||||
|
|
||||||
def setUp(self):
|
|
||||||
self.temp_content = mkdtemp()
|
|
||||||
self.temp_output = mkdtemp()
|
|
||||||
|
|
||||||
def tearDown(self):
|
|
||||||
rmtree(self.temp_content)
|
|
||||||
rmtree(self.temp_output)
|
|
||||||
|
|
||||||
@skipIfNoExecutable('lessc')
|
|
||||||
def test_less_compiler(self):
|
|
||||||
|
|
||||||
settings = _DEFAULT_CONFIG.copy()
|
|
||||||
settings['STATIC_PATHS'] = ['static']
|
|
||||||
settings['LESS_GENERATOR'] = True
|
|
||||||
|
|
||||||
generator = LessCSSGenerator(None, settings, self.temp_content,
|
|
||||||
_DEFAULT_CONFIG['THEME'], self.temp_output, None)
|
|
||||||
|
|
||||||
# create a dummy less file
|
|
||||||
less_dir = os.path.join(self.temp_content, 'static', 'css')
|
|
||||||
less_filename = os.path.join(less_dir, 'test.less')
|
|
||||||
|
|
||||||
less_output = os.path.join(self.temp_output, 'static', 'css',
|
|
||||||
'test.css')
|
|
||||||
|
|
||||||
os.makedirs(less_dir)
|
|
||||||
with open(less_filename, 'w') as less_file:
|
|
||||||
less_file.write(self.LESS_CONTENT)
|
|
||||||
|
|
||||||
generator.generate_output()
|
|
||||||
|
|
||||||
# we have the file ?
|
|
||||||
self.assertTrue(os.path.exists(less_output))
|
|
||||||
|
|
||||||
# was it compiled ?
|
|
||||||
self.assertIsNotNone(re.search(r'^\s+color:\s*#4D926F;$',
|
|
||||||
open(less_output).read(), re.MULTILINE | re.IGNORECASE))
|
|
||||||
|
|
|
||||||
101
tests/test_webassets.py
Normal file
101
tests/test_webassets.py
Normal file
|
|
@ -0,0 +1,101 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
import hashlib
|
||||||
|
import os
|
||||||
|
from codecs import open
|
||||||
|
from tempfile import mkdtemp
|
||||||
|
from shutil import rmtree
|
||||||
|
|
||||||
|
from pelican import Pelican
|
||||||
|
from pelican.settings import read_settings
|
||||||
|
from .support import unittest, skipIfNoExecutable, module_exists
|
||||||
|
|
||||||
|
CUR_DIR = os.path.dirname(__file__)
|
||||||
|
THEME_DIR = os.path.join(CUR_DIR, 'themes', 'assets')
|
||||||
|
CSS_REF = open(os.path.join(THEME_DIR, 'static', 'css',
|
||||||
|
'style.min.css')).read()
|
||||||
|
CSS_HASH = hashlib.md5(CSS_REF).hexdigest()[0:8]
|
||||||
|
|
||||||
|
|
||||||
|
@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):
|
||||||
|
self.temp_path = mkdtemp()
|
||||||
|
settings = {
|
||||||
|
'PATH': os.path.join(CUR_DIR, 'content', 'TestCategory'),
|
||||||
|
'OUTPUT_PATH': self.temp_path,
|
||||||
|
'PLUGINS': ['pelican.plugins.assets', ],
|
||||||
|
'THEME': THEME_DIR,
|
||||||
|
}
|
||||||
|
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 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-an-article-with-category.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/misc.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-an-article-with-category.html']
|
||||||
|
for f in html_files:
|
||||||
|
self.check_link_tag(css_file, os.path.join(self.temp_path, f))
|
||||||
1
tests/themes/assets/static/css/style.min.css
vendored
Normal file
1
tests/themes/assets/static/css/style.min.css
vendored
Normal file
|
|
@ -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
tests/themes/assets/static/css/style.scss
Normal file
19
tests/themes/assets/static/css/style.scss
Normal file
|
|
@ -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
tests/themes/assets/templates/base.html
Normal file
7
tests/themes/assets/templates/base.html
Normal file
|
|
@ -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 %}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue