diff --git a/assets/Readme.rst b/assets/Readme.rst
new file mode 100644
index 0000000..42091cb
--- /dev/null
+++ b/assets/Readme.rst
@@ -0,0 +1,106 @@
+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 CSS URL rewriting, 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" %}
+
+ {% endassets %}
+
+... will produce a minified css file with a version identifier that looks like:
+
+.. code-block:: html
+
+
+
+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" %}
+
+ {% endassets %}
+
+Another example for Javascript:
+
+.. code-block:: jinja
+
+ {% assets filters="uglifyjs", output="js/packed.js", "js/jquery.js", "js/base.js", "js/widgets.js" %}
+
+ {% endassets %}
+
+The above will produce a minified JS file:
+
+.. code-block:: html
+
+
+
+Pelican's debug mode is propagated to Webassets to disable asset packaging
+and instead work with the uncompressed assets.
+
+If you need to create named bundles (for example, if you need to compile SASS
+files before minifying with other CSS files), you can use the ``ASSET_BUNDLES``
+variable in your settings file. This is an ordered sequence of 3-tuples, where
+the 3-tuple is defined as ``(name, args, kwargs)``. This tuple is passed to the
+`environment's register() method`_. The following will compile two SCSS files
+into a named bundle, using the ``pyscss`` filter:
+
+.. code-block:: python
+
+ ASSET_BUNDLES = (
+ ('scss', ['colors.scss', 'main.scss'], {'filters': 'pyscss'}),
+ )
+
+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'), )
+
+If you wish to place your assets in locations other than the theme output
+directory, you can use ``ASSET_SOURCE_PATHS`` in your settings file to provide
+webassets with a list of additional directories to search, relative to the
+theme's top-level directory:
+
+.. code-block:: python
+
+ ASSET_SOURCE_PATHS = [
+ 'vendor/css',
+ 'scss',
+ ]
+
+.. _Webassets: https://github.com/miracle2k/webassets
+.. _Webassets documentation: http://webassets.readthedocs.org/en/latest/builtin_filters.html
+.. _environment's register() method: http://webassets.readthedocs.org/en/latest/environment.html#registering-bundles
diff --git a/assets/__init__.py b/assets/__init__.py
new file mode 100644
index 0000000..67b75dd
--- /dev/null
+++ b/assets/__init__.py
@@ -0,0 +1 @@
+from .assets import *
diff --git a/assets/assets.py b/assets/assets.py
new file mode 100644
index 0000000..e204dd6
--- /dev/null
+++ b/assets/assets.py
@@ -0,0 +1,75 @@
+# -*- 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::
+
+
+
+.. _webassets: https://webassets.readthedocs.org/
+
+"""
+from __future__ import unicode_literals
+
+import os
+import logging
+
+from pelican import signals
+logger = logging.getLogger(__name__)
+
+try:
+ import webassets
+ from webassets import Environment
+ from webassets.ext.jinja2 import AssetsExtension
+except ImportError:
+ webassets = None
+
+def add_jinja2_ext(pelican):
+ """Add Webassets to Jinja2 extensions in Pelican settings."""
+
+ if 'JINJA_ENVIRONMENT' in pelican.settings: # pelican 3.7+
+ pelican.settings['JINJA_ENVIRONMENT']['extensions'].append(AssetsExtension)
+ else:
+ pelican.settings['JINJA_EXTENSIONS'].append(AssetsExtension)
+
+
+def create_assets_env(generator):
+ """Define the assets environment and pass it to the generator."""
+
+ theme_static_dir = generator.settings['THEME_STATIC_DIR']
+ assets_destination = os.path.join(generator.output_path, theme_static_dir)
+ generator.env.assets_environment = Environment(
+ assets_destination, theme_static_dir)
+
+ if 'ASSET_CONFIG' in generator.settings:
+ for item in generator.settings['ASSET_CONFIG']:
+ generator.env.assets_environment.config[item[0]] = item[1]
+
+ if 'ASSET_BUNDLES' in generator.settings:
+ for name, args, kwargs in generator.settings['ASSET_BUNDLES']:
+ generator.env.assets_environment.register(name, *args, **kwargs)
+
+ if 'ASSET_DEBUG' in generator.settings:
+ generator.env.assets_environment.debug = generator.settings['ASSET_DEBUG']
+ elif logging.getLevelName(logger.getEffectiveLevel()) == "DEBUG":
+ generator.env.assets_environment.debug = True
+
+ for path in (generator.settings['THEME_STATIC_PATHS'] +
+ generator.settings.get('ASSET_SOURCE_PATHS', [])):
+ full_path = os.path.join(generator.theme, path)
+ generator.env.assets_environment.append_path(full_path)
+
+
+def register():
+ """Plugin registration."""
+ if webassets:
+ signals.initialized.connect(add_jinja2_ext)
+ signals.generator_init.connect(create_assets_env)
+ else:
+ logger.warning('`assets` failed to load dependency `webassets`.'
+ '`assets` plugin not loaded.')
diff --git a/assets/requirements.txt b/assets/requirements.txt
new file mode 100644
index 0000000..7725d6a
--- /dev/null
+++ b/assets/requirements.txt
@@ -0,0 +1,2 @@
+cssmin
+webassets
\ No newline at end of file
diff --git a/assets/test_assets.py b/assets/test_assets.py
new file mode 100644
index 0000000..3001073
--- /dev/null
+++ b/assets/test_assets.py
@@ -0,0 +1,112 @@
+# -*- 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
+from pelican.tests.support import mute, skipIfNoExecutable, module_exists
+
+CUR_DIR = os.path.dirname(__file__)
+THEME_DIR = os.path.join(CUR_DIR, 'test_data')
+CSS_REF = open(os.path.join(THEME_DIR, 'static', 'css',
+ 'style.min.css')).read()
+CSS_HASH = hashlib.md5(CSS_REF.encode()).hexdigest()[0:8]
+
+
+@unittest.skipUnless(module_exists('webassets'), "webassets isn't installed")
+@skipIfNoExecutable(['sass', '-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(os.path.dirname(CUR_DIR), 'test_data', 'content'),
+ 'OUTPUT_PATH': self.temp_path,
+ 'PLUGINS': [assets],
+ 'THEME': THEME_DIR,
+ 'LOCALE': locale.normalize('en_US'),
+ 'CACHE_CONTENT': False
+ }
+ if override:
+ settings.update(override)
+
+ self.settings = read_settings(override=settings)
+ pelican = Pelican(settings=self.settings)
+ mute(True)(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 = (''
+ .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_ENVIRONMENT']['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))
diff --git a/assets/test_data/static/css/style.min.css b/assets/test_data/static/css/style.min.css
new file mode 100644
index 0000000..daf9c3c
--- /dev/null
+++ b/assets/test_data/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}
\ No newline at end of file
diff --git a/assets/test_data/static/css/style.scss b/assets/test_data/static/css/style.scss
new file mode 100644
index 0000000..10cd05b
--- /dev/null
+++ b/assets/test_data/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;
+ }
+}
diff --git a/assets/test_data/templates/base.html b/assets/test_data/templates/base.html
new file mode 100644
index 0000000..05a32d0
--- /dev/null
+++ b/assets/test_data/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" %}
+
+ {% endassets %}
+{% endblock %}