Skip to content

Commit

Permalink
Merge pull request #2638 from getnikola/configurable-filters
Browse files Browse the repository at this point in the history
Allowing to configure executables for filters (fixes #2615).
  • Loading branch information
felixfontein committed Jan 15, 2017
2 parents 6b6e2a9 + 5e57eee commit 741a5df
Show file tree
Hide file tree
Showing 4 changed files with 72 additions and 14 deletions.
6 changes: 5 additions & 1 deletion CHANGES.txt
Expand Up @@ -3,8 +3,12 @@ New in master

Features
--------
* Add META_GENERATOR_TAG option in conf.py allowing the meta generator
* Add ``META_GENERATOR_TAG`` option in conf.py allowing the meta generator
to be disabled if needed. (Issue #2628)
* Add ``YUI_COMPRESSOR_EXECUTABLE``, ``CLOSURE_COMPILER_EXECUTABLE``,
``OPTIPNG_EXECUTABLE``, ``JPEGOPTIM_EXECUTABLE`` and
``HTML_TIDY_EXECUTABLE`` to configure executables for built-in filters.
(Issue #2615)

Bugfixes
--------
Expand Down
19 changes: 19 additions & 0 deletions nikola/conf.py.in
Expand Up @@ -562,6 +562,25 @@ GITHUB_COMMIT_SOURCE = True
# ".jpg": ["jpegoptim --strip-all -m75 -v %s"],
# }

# Executable for the "yui_compressor" filter (defaults to 'yui-compressor').
# YUI_COMPRESSOR_EXECUTABLE = 'yui-compressor'

# Executable for the "closure_compiler" filter (defaults to 'closure-compiler').
# CLOSURE_COMPILER_EXECUTABLE = 'closure-compiler'

# Executable for the "optipng" filter (defaults to 'optipng').
# OPTIPNG_EXECUTABLE = 'optipng'

# Executable for the "jpegoptim" filter (defaults to 'jpegoptim').
# JPEGOPTIM_EXECUTABLE = 'jpegoptim'

# Executable for the "html_tidy_withconfig", "html_tidy_nowrap",
# "html_tidy_wrap", "html_tidy_wrap_attr" and "html_tidy_mini" filters
# (defaults to 'tidy5').
# HTML_TIDY_EXECUTABLE = 'tidy5'



# Expert setting! Create a gzipped copy of each generated file. Cheap server-
# side optimization for very high traffic sites or low memory servers.
# GZIP_FILES = False
Expand Down
49 changes: 36 additions & 13 deletions nikola/filters.py
Expand Up @@ -45,6 +45,19 @@
from .utils import req_missing, LOGGER


class _ConfigurableFilter(object):
"""Allow Nikola to configure filter with site's config."""

def __init__(self, **configuration_variables):
"""Define which arguments to configure from which configuration variables."""
self.configuration_variables = configuration_variables

def __call__(self, f):
"""Store configuration_variables as attribute of function."""
f.configuration_variables = self.configuration_variables
return f


def apply_to_binary_file(f):
"""Apply a filter to a binary file.
Expand Down Expand Up @@ -126,14 +139,16 @@ def runinplace(command, infile):
shutil.rmtree(tmpdir)


def yui_compressor(infile):
@_ConfigurableFilter(executable='YUI_COMPRESSOR_EXECUTABLE')
def yui_compressor(infile, executable=None):
"""Run YUI Compressor on a file."""
yuicompressor = False
try:
subprocess.call('yui-compressor', stdout=open(os.devnull, 'w'), stderr=open(os.devnull, 'w'))
yuicompressor = 'yui-compressor'
except Exception:
pass
yuicompressor = executable
if not yuicompressor:
try:
subprocess.call('yui-compressor', stdout=open(os.devnull, 'w'), stderr=open(os.devnull, 'w'))
yuicompressor = 'yui-compressor'
except Exception:
pass
if not yuicompressor:
try:
subprocess.call('yuicompressor', stdout=open(os.devnull, 'w'), stderr=open(os.devnull, 'w'))
Expand All @@ -145,41 +160,49 @@ def yui_compressor(infile):
return runinplace('{} --nomunge %1 -o %2'.format(yuicompressor), infile)


def closure_compiler(infile):
@_ConfigurableFilter(executable='CLOSURE_COMPILER_EXECUTABLE')
def closure_compiler(infile, executable='closure-compiler'):
"""Run closure-compiler on a file."""
return runinplace('closure-compiler --warning_level QUIET --js %1 --js_output_file %2', infile)
return runinplace('{} --warning_level QUIET --js %1 --js_output_file %2'.format(executable), infile)


def optipng(infile):
@_ConfigurableFilter(executable='OPTIPNG_EXECUTABLE')
def optipng(infile, executable='optiong'):
"""Run optipng on a file."""
return runinplace("optipng -preserve -o2 -quiet %1", infile)
return runinplace("{} -preserve -o2 -quiet %1".format(executable), infile)


def jpegoptim(infile):
@_ConfigurableFilter(executable='JPEGOPTIM_EXECUTABLE')
def jpegoptim(infile, executable='jpegoptim'):
"""Run jpegoptim on a file."""
return runinplace("jpegoptim -p --strip-all -q %1", infile)
return runinplace("{} -p --strip-all -q %1".format(executable), infile)


@_ConfigurableFilter(executable='HTML_TIDY_EXECUTABLE')
def html_tidy_withconfig(infile, executable='tidy5'):
"""Run HTML Tidy with tidy5.conf as config file."""
return _html_tidy_runner(infile, "-quiet --show-info no --show-warnings no -utf8 -indent -config tidy5.conf -modify %1", executable=executable)


@_ConfigurableFilter(executable='HTML_TIDY_EXECUTABLE')
def html_tidy_nowrap(infile, executable='tidy5'):
"""Run HTML Tidy without line wrapping."""
return _html_tidy_runner(infile, "-quiet --show-info no --show-warnings no -utf8 -indent --indent-attributes no --sort-attributes alpha --wrap 0 --wrap-sections no --drop-empty-elements no --tidy-mark no -modify %1", executable=executable)


@_ConfigurableFilter(executable='HTML_TIDY_EXECUTABLE')
def html_tidy_wrap(infile, executable='tidy5'):
"""Run HTML Tidy with line wrapping."""
return _html_tidy_runner(infile, "-quiet --show-info no --show-warnings no -utf8 -indent --indent-attributes no --sort-attributes alpha --wrap 80 --wrap-sections no --drop-empty-elements no --tidy-mark no -modify %1", executable=executable)


@_ConfigurableFilter(executable='HTML_TIDY_EXECUTABLE')
def html_tidy_wrap_attr(infile, executable='tidy5'):
"""Run HTML tidy with line wrapping and attribute indentation."""
return _html_tidy_runner(infile, "-quiet --show-info no --show-warnings no -utf8 -indent --indent-attributes yes --sort-attributes alpha --wrap 80 --wrap-sections no --drop-empty-elements no --tidy-mark no -modify %1", executable=executable)


@_ConfigurableFilter(executable='HTML_TIDY_EXECUTABLE')
def html_tidy_mini(infile, executable='tidy5'):
"""Run HTML tidy with minimal settings."""
return _html_tidy_runner(infile, "-quiet --show-info no --show-warnings no -utf8 --indent-attributes no --sort-attributes alpha --wrap 0 --wrap-sections no --tidy-mark no --drop-empty-elements no -modify %1", executable=executable)
Expand Down
12 changes: 12 additions & 0 deletions nikola/nikola.py
Expand Up @@ -32,6 +32,7 @@
from copy import copy
from pkg_resources import resource_filename
import datetime
import functools
import locale
import os
import json
Expand Down Expand Up @@ -925,6 +926,17 @@ def __init__(self, **config):
utils.LOGGER.warn('The STORY_INDEX option is deprecated, use PAGE_INDEX instead.')
self.config['PAGE_INDEX'] = config['STORY_INDEX']

# Configure filters
for actions in self.config['FILTERS'].values():
for i, f in enumerate(actions):
if hasattr(f, 'configuration_variables'):
args = {}
for arg, config in f.configuration_variables.items():
if config in self.config:
args[arg] = self.config[config]
if args:
actions[i] = functools.partial(f, **args)

# We use one global tzinfo object all over Nikola.
try:
self.tzinfo = dateutil.tz.gettz(self.config['TIMEZONE'])
Expand Down

0 comments on commit 741a5df

Please sign in to comment.