├── .gitignore ├── MANIFEST.in ├── pelican.png ├── .travis.yml ├── setup.py ├── UNLICENSE ├── tests.py ├── minify.py └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | dist 2 | *.egg-info 3 | *.pyc 4 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include README.md UNLICENSE pelican.png tests.py 2 | -------------------------------------------------------------------------------- /pelican.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rdegges/pelican-minify/HEAD/pelican.png -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: python 2 | python: 3 | - "2.7" 4 | install: 5 | - python setup.py develop 6 | script: 7 | - python tests.py 8 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | from os.path import abspath, dirname, join, normpath 2 | 3 | from setuptools import setup 4 | 5 | 6 | setup( 7 | 8 | # Basic package information: 9 | name = 'pelican-minify', 10 | version = '0.9', 11 | py_modules = ('minify',), 12 | 13 | # Packaging options: 14 | zip_safe = False, 15 | include_package_data = True, 16 | 17 | # Package dependencies: 18 | install_requires = ['htmlmin>=0.1.5', 'pelican>=3.1.1', 'joblib>=0.9'], 19 | 20 | # Metadata for PyPI: 21 | author = 'Randall Degges', 22 | author_email = 'r@rdegges.com', 23 | license = 'UNLICENSE', 24 | url = 'https://github.com/rdegges/pelican-minify', 25 | keywords = 'pelican blog static minify html minification', 26 | description = ('An HTML minification plugin for Pelican, the static ' 27 | 'site generator.'), 28 | long_description = open(normpath(join(dirname(abspath(__file__)), 29 | 'README.md'))).read() 30 | 31 | ) 32 | -------------------------------------------------------------------------------- /UNLICENSE: -------------------------------------------------------------------------------- 1 | This is free and unencumbered software released into the public domain. 2 | 3 | Anyone is free to copy, modify, publish, use, compile, sell, or 4 | distribute this software, either in source code form or as a compiled 5 | binary, for any purpose, commercial or non-commercial, and by any 6 | means. 7 | 8 | In jurisdictions that recognize copyright laws, the author or authors 9 | of this software dedicate any and all copyright interest in the 10 | software to the public domain. We make this dedication for the benefit 11 | of the public at large and to the detriment of our heirs and 12 | successors. We intend this dedication to be an overt act of 13 | relinquishment in perpetuity of all present and future rights to this 14 | software under copyright law. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 19 | IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR 20 | OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 21 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 22 | OTHER DEALINGS IN THE SOFTWARE. 23 | 24 | For more information, please refer to 25 | -------------------------------------------------------------------------------- /tests.py: -------------------------------------------------------------------------------- 1 | from contextlib import contextmanager 2 | from shutil import rmtree 3 | from tempfile import mkdtemp, mkstemp 4 | from unittest import TestCase, main 5 | 6 | from minify import create_minified_file 7 | 8 | 9 | @contextmanager 10 | def temporary_folder(): 11 | """Creates a temporary folder, return it and delete it afterwards. 12 | 13 | This allows to do something like this in tests: 14 | 15 | >>> with temporary_folder() as d: 16 | # do whatever you want 17 | """ 18 | tempdir = mkdtemp() 19 | try: 20 | yield tempdir 21 | finally: 22 | rmtree(tempdir) 23 | 24 | 25 | class TestMinify(TestCase): 26 | 27 | def test_create_minified_file(self): 28 | """Test that a file matching the input filename is compressed.""" 29 | with temporary_folder() as tempdir: 30 | (_, a_html_filename) = mkstemp(suffix='.html', dir=tempdir) 31 | f = open(a_html_filename, 'w') 32 | f.write(' hi ') 33 | f.close() 34 | create_minified_file(a_html_filename, {'remove_all_empty_space': True}) 35 | self.assertEqual(open(a_html_filename).read(), 'hi') 36 | 37 | 38 | if __name__ == '__main__': 39 | main() 40 | -------------------------------------------------------------------------------- /minify.py: -------------------------------------------------------------------------------- 1 | """A Pelican plugin which minifies HTML pages.""" 2 | 3 | 4 | from logging import getLogger 5 | from os import walk 6 | from os.path import join 7 | 8 | from htmlmin import minify 9 | from pelican import signals 10 | from joblib import Parallel, delayed 11 | 12 | # We need save unicode strings to files. 13 | try: 14 | from codecs import open 15 | except ImportError: 16 | pass 17 | 18 | logger = getLogger(__name__) 19 | 20 | 21 | def minify_html(pelican): 22 | """Minify all HTML files. 23 | 24 | :param pelican: The Pelican instance. 25 | """ 26 | options = pelican.settings.get('MINIFY', {}) 27 | files_to_minify = [] 28 | 29 | for dirpath, _, filenames in walk(pelican.settings['OUTPUT_PATH']): 30 | files_to_minify += [join(dirpath, name) for name in filenames if name.endswith('.html') or name.endswith('.htm')] 31 | 32 | Parallel(n_jobs=-1)(delayed(create_minified_file)(filepath, options) for filepath in files_to_minify) 33 | 34 | 35 | def create_minified_file(filename, options): 36 | """Create a minified HTML file, overwriting the original. 37 | 38 | :param str filename: The file to minify. 39 | """ 40 | uncompressed = open(filename, encoding='utf-8').read() 41 | 42 | with open(filename, 'w', encoding='utf-8') as f: 43 | try: 44 | logger.debug('Minifying: %s' % filename) 45 | compressed = minify(uncompressed, **options) 46 | f.write(compressed) 47 | except Exception as ex: 48 | logger.critical('HTML Minification failed: %s' % ex) 49 | finally: 50 | f.close() 51 | 52 | 53 | def register(): 54 | """Run the HTML minification stuff after all articles have been generated, 55 | at the very end of the processing loop. 56 | """ 57 | signals.finalized.connect(minify_html) 58 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # pelican-minify 2 | 3 | [![PyPI Version](https://img.shields.io/pypi/v/pelican-minify.svg)](https://pypi.python.org/pypi/pelican-minify) 4 | [![PyPI Downloads](https://img.shields.io/pypi/dm/pelican-minify.svg)](https://pypi.python.org/pypi/pelican-minify) 5 | [![Build Status](https://secure.travis-ci.org/rdegges/pelican-minify.png?branch=master)](https://travis-ci.org/rdegges/pelican-minify) 6 | 7 | An HTML minification plugin for 8 | [Pelican](http://pelican.readthedocs.org/en/latest/), the static site generator. 9 | 10 | ![Pelican Logo](https://github.com/rdegges/pelican-minify/raw/master/pelican.png) 11 | 12 | 13 | ## Install 14 | 15 | To install the library, you can use 16 | [pip](http://www.pip-installer.org/en/latest/). 17 | 18 | ```bash 19 | $ pip install pelican-minify 20 | ``` 21 | 22 | 23 | ## Usage 24 | 25 | To use `pelican-minify`, you need to make only a single change to your 26 | `pelicanconf.py` file (the configuration file that Pelican uses to generate 27 | your static site. 28 | 29 | Update your `PLUGINS` global, and append `minify` to the list, eg: 30 | 31 | ``` python 32 | # pelicanconf.py 33 | 34 | # ... 35 | 36 | PLUGINS = [ 37 | # ... 38 | 'minify', 39 | # ... 40 | ] 41 | 42 | # ... 43 | ``` 44 | 45 | The next time you build your Pelican site, `pelican-minify` will automatically 46 | minify your Pelican pages after they've been generated. 47 | 48 | `pelican-minify` can also be configured by setting `MINIFY` to a hash containing 49 | [parameters to htmlmin](https://htmlmin.readthedocs.org/en/latest/reference.html#htmlmin.minify), eg: 50 | 51 | ``` python 52 | # pelicanconf.py 53 | 54 | # ... 55 | 56 | MINIFY = { 57 | 'remove_comments': True, 58 | 'remove_all_empty_space': True, 59 | 'remove_optional_attribute_quotes': False 60 | } 61 | 62 | # ... 63 | ``` 64 | 65 | This reduces file size and obscures the public source code, but keep in 66 | mind--minifying your static site will increase your Pelican build times, as it 67 | adds extra file processing for each page generated. 68 | 69 | **NOTE**: You should probably include the `minify` plugin at the very bottom of 70 | your `PLUGINS` array. This will ensure it is the last thing to run, and 71 | doesn't prematurely gzip any files. 72 | 73 | 74 | ## Changelog 75 | 76 | v0.1: 12-4-2012 77 | 78 | - First release! 79 | 80 | v0.2: 2-12-2013 81 | 82 | - Fixing issue with unicode characters. 83 | - Upgrading django-htmlmin dependency. 84 | 85 | v0.3: 2-12-2013 86 | 87 | - Fixing tests. 88 | 89 | v0.4: 2-15-2013 90 | 91 | - Upgrading django-htmlmin. 92 | 93 | v0.5: 8-28-2014 94 | 95 | - Python 3 compatibility (thanks @AlexJF!). 96 | 97 | v0.6: 9-9-2014 98 | 99 | - Fixing unicode bug (thanks @kura!). 100 | 101 | v0.7: 11-4-2014 102 | 103 | - Making minification work on .htm files (thanks @Undeterminant!). 104 | 105 | v0.8: 5-12-2015 106 | 107 | - No longer removing optional quotes from HTML elements. This provides 108 | better compatibility across browsers / etc. 109 | 110 | v0.9: 11-25-2015 111 | 112 | - Making minify library configurable. 113 | - Removing aggressive whitespace removal (*avoids issues*). 114 | --------------------------------------------------------------------------------