changeset 206:cba781477bd0

processing: Add `concat`, `uglifyjs` and `cleancss` processors.
author Ludovic Chabant <ludovic@chabant.com>
date Sun, 18 Jan 2015 12:13:28 -0800
parents e725af1d48fb
children c5330cb35794
files piecrust/plugins/builtin.py piecrust/processing/compressors.py piecrust/processing/util.py
diffstat 3 files changed, 183 insertions(+), 1 deletions(-) [+]
line wrap: on
line diff
--- a/piecrust/plugins/builtin.py	Sun Jan 18 12:12:57 2015 -0800
+++ b/piecrust/plugins/builtin.py	Sun Jan 18 12:13:28 2015 -0800
@@ -21,10 +21,13 @@
 from piecrust.plugins.base import PieCrustPlugin
 from piecrust.processing.base import CopyFileProcessor
 from piecrust.processing.compass import CompassProcessor
+from piecrust.processing.compressors import (
+        CleanCssProcessor, UglifyJSProcessor)
 from piecrust.processing.less import LessProcessor
 from piecrust.processing.requirejs import RequireJSProcessor
 from piecrust.processing.sass import SassProcessor
 from piecrust.processing.sitemap import SitemapProcessor
+from piecrust.processing.util import ConcatProcessor
 from piecrust.sources.base import DefaultPageSource
 from piecrust.sources.posts import (
         FlatPostsSource, ShallowPostsSource, HierarchyPostsSource)
@@ -90,11 +93,14 @@
     def getProcessors(self):
         return [
                 CopyFileProcessor(),
+                ConcatProcessor(),
                 CompassProcessor(),
                 LessProcessor(),
                 SassProcessor(),
                 RequireJSProcessor(),
-                SitemapProcessor()]
+                SitemapProcessor(),
+                CleanCssProcessor(),
+                UglifyJSProcessor()]
 
     def getImporters(self):
         return [
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/piecrust/processing/compressors.py	Sun Jan 18 12:13:28 2015 -0800
@@ -0,0 +1,93 @@
+import os
+import os.path
+import logging
+import platform
+import subprocess
+from piecrust.processing.base import SimpleFileProcessor
+
+
+logger = logging.getLogger(__name__)
+
+
+class CleanCssProcessor(SimpleFileProcessor):
+    PROCESSOR_NAME = 'cleancss'
+
+    def __init__(self):
+        super(CleanCssProcessor, self).__init__({'css': 'css'})
+        self._conf = None
+
+    def _doProcess(self, in_path, out_path):
+        self._ensureInitialized()
+
+        args = [self._conf['bin'], '-o', out_path]
+        args += self._conf['options']
+        args.append(in_path)
+        logger.debug("Cleaning CSS file: %s" % args)
+
+        # On Windows, we need to run the process in a shell environment
+        # otherwise it looks like `PATH` isn't taken into account.
+        shell = (platform.system() == 'Windows')
+        try:
+            retcode = subprocess.call(args, shell=shell)
+        except FileNotFoundError as ex:
+            logger.error("Tried running CleanCSS processor with command: %s" %
+                         args)
+            raise Exception("Error running CleanCSS processor. "
+                            "Did you install it?") from ex
+        if retcode != 0:
+            raise Exception("Error occured in CleanCSS. Please check "
+                            "log messages above for more information.")
+        return True
+
+    def _ensureInitialized(self):
+        if self._conf is not None:
+            return
+
+        self._conf = self.app.config.get('cleancss') or {}
+        self._conf.setdefault('bin', 'cleancss')
+        self._conf.setdefault('options', ['--skip-rebase'])
+        if not isinstance(self._conf['options'], list):
+            raise Exception("The `cleancss/options` configuration setting "
+                            "must be an array of arguments.")
+
+
+class UglifyJSProcessor(SimpleFileProcessor):
+    PROCESSOR_NAME = 'uglifyjs'
+
+    def __init__(self):
+        super(UglifyJSProcessor, self).__init__({'js': 'js'})
+        self._conf = None
+
+    def _doProcess(self, in_path, out_path):
+        self._ensureInitialized()
+
+        args = [self._conf['bin'], in_path, '-o', out_path]
+        args += self._conf['options']
+        logger.debug("Uglifying JS file: %s" % args)
+
+        # On Windows, we need to run the process in a shell environment
+        # otherwise it looks like `PATH` isn't taken into account.
+        shell = (platform.system() == 'Windows')
+        try:
+            retcode = subprocess.call(args, shell=shell)
+        except FileNotFoundError as ex:
+            logger.error("Tried running UglifyJS processor with command: %s" %
+                         args)
+            raise Exception("Error running UglifyJS processor. "
+                            "Did you install it?") from ex
+        if retcode != 0:
+            raise Exception("Error occured in UglifyJS. Please check "
+                            "log messages above for more information.")
+        return True
+
+    def _ensureInitialized(self):
+        if self._conf is not None:
+            return
+
+        self._conf = self.app.config.get('uglifyjs') or {}
+        self._conf.setdefault('bin', 'uglifyjs')
+        self._conf.setdefault('options', ['--compress'])
+        if not isinstance(self._conf['options'], list):
+            raise Exception("The `uglify/options` configuration setting "
+                            "must be an array of arguments.")
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/piecrust/processing/util.py	Sun Jan 18 12:13:28 2015 -0800
@@ -0,0 +1,83 @@
+import os.path
+import time
+import logging
+import yaml
+from piecrust.processing.base import Processor
+
+
+logger = logging.getLogger(__name__)
+
+
+class _ConcatInfo(object):
+    timestamp = 0
+    files = None
+    delim = "\n"
+
+
+class ConcatProcessor(Processor):
+    PROCESSOR_NAME = 'concat'
+
+    def __init__(self):
+        super(ConcatProcessor, self).__init__()
+        self._cache = {}
+
+    def matches(self, path):
+        return path.endswith('.concat')
+
+    def getDependencies(self, path):
+        info = self._load(path)
+        return info.files
+
+    def getOutputFilenames(self, filename):
+        return [filename[:-7]]
+
+    def process(self, path, out_dir):
+        dirname, filename = os.path.split(path)
+        out_path = os.path.join(out_dir, filename[:-7])
+        info = self._load(path)
+        if not info.files:
+            raise Exception("No files specified in: %s" %
+                            os.path.relpath(path, self.app.root_dir))
+
+        logger.debug("Concatenating %d files to: %s" %
+                     (len(info.files), out_path))
+        encoded_delim = info.delim.encode('utf8')
+        with open(out_path, 'wb') as ofp:
+            for p in info.files:
+                with open(p, 'rb') as ifp:
+                    ofp.write(ifp.read())
+                if info.delim:
+                    ofp.write(encoded_delim)
+        return True
+
+    def _load(self, path):
+        cur_time = time.time()
+        info = self._cache.get(path)
+        if (info is not None and
+                (cur_time - info.timestamp <= 1 or
+                 os.path.getmtime(path) < info.timestamp)):
+            return info
+
+        if info is None:
+            info = _ConcatInfo()
+            self._cache[path] = info
+
+        with open(path, 'r') as fp:
+            config = yaml.load(fp)
+
+        info.files = config.get('files', [])
+        info.delim = config.get('delim', "\n")
+        info.timestamp = cur_time
+
+        path_mode = config.get('path_mode', 'relative')
+        if path_mode == 'relative':
+            dirname, _ = os.path.split(path)
+            info.files = [os.path.join(dirname, f) for f in info.files]
+        elif path_mode == 'absolute':
+            info.files = [os.path.join(self.app.root_dir, f)
+                          for f in info.files]
+        else:
+            raise Exception("Unknown path mode: %s" % path_mode)
+
+        return info
+