diff --git a/hyde/ext/plugins/optipng.py b/hyde/ext/plugins/optipng.py new file mode 100644 index 0000000..5ba8309 --- /dev/null +++ b/hyde/ext/plugins/optipng.py @@ -0,0 +1,70 @@ +# -*- coding: utf-8 -*- +""" +OPTIPNG plugin +""" + +from hyde.plugin import CLTransformer +from hyde.fs import File + +class OptiPNGPlugin(CLTransformer): + """ + The plugin class for OptiPNG + """ + + def __init__(self, site): + super(OptiPNGPlugin, self).__init__(site) + + @property + def plugin_name(self): + """ + The name of the plugin. + """ + return "optipng" + + def option_prefix(self, option): + return "-" + + def binary_resource_complete(self, resource): + """ + If the site is in development mode, just return. + Otherwise, run optipng to compress the png file. + """ + + try: + mode = self.site.config.mode + except AttributeError: + mode = "production" + + if not resource.source_file.kind == 'png': + return + + if mode.startswith('dev'): + self.logger.debug("Skipping optipng in development mode.") + return + + supported = [ + "o", + "fix", + "force", + "preserve", + "quiet", + "log", + "f", + "i", + "zc", + "zm", + "zs", + "zw", + "full", + "nb", + "nc", + "np", + "nz" + ] + target = File(self.site.config.deploy_root_path.child( + resource.relative_deploy_path)) + optipng = self.app + args = [str(optipng)] + args.extend(self.process_args(supported)) + args.extend([str(target)]) + self.call_app(args) diff --git a/hyde/fs.py b/hyde/fs.py index 4eb528e..7afc2d1 100644 --- a/hyde/fs.py +++ b/hyde/fs.py @@ -13,6 +13,7 @@ import os import shutil from distutils import dir_util import functools +import fnmatch from hyde.util import getLoggerWithNullHandler @@ -94,12 +95,12 @@ class FS(object): Generates the parents until stop or the absolute root directory is reached. """ - f = self - while f.parent != stop: - if f.parent == f: + folder = self + while folder.parent != stop: + if folder.parent == folder: return - yield f.parent - f = f.parent + yield folder.parent + folder = folder.parent def is_descendant_of(self, ancestor): """ @@ -185,9 +186,22 @@ class File(FS): """ return self.extension.lstrip(".") + @property + def size(self): + """ + Size of this file. + """ + + if not self.exists: + return -1 + return os.path.getsize(self.path) + @property def mimetype(self): - (mime, encoding) = mimetypes.guess_type(self.path) + """ + Gets the mimetype of this file. + """ + (mime, _) = mimetypes.guess_type(self.path) return mime @property @@ -246,9 +260,9 @@ class File(FS): import tempfile (handle, path) = tempfile.mkstemp(text=True) os.close(handle) - f = File(path) - f.write(text) - return f + afile = File(path) + afile.write(text) + return afile def read_all(self, encoding='utf-8'): """ @@ -296,26 +310,26 @@ class FSVisitor(object): self.folder = folder self.pattern = pattern - def folder_visitor(self, f): + def folder_visitor(self, function): """ Decorator for `visit_folder` protocol """ - self.visit_folder = f - return f + self.visit_folder = function + return function - def file_visitor(self, f): + def file_visitor(self, function): """ Decorator for `visit_file` protocol """ - self.visit_file = f - return f + self.visit_file = function + return function - def finalizer(self, f): + def finalizer(self, function): """ Decorator for `visit_complete` protocol """ - self.visit_complete = f - return f + self.visit_complete = function + return function def __enter__(self): return self @@ -341,7 +355,7 @@ class FolderWalker(FSVisitor): if not walk_files and not walk_folders: return - for root, dirs, a_files in os.walk(self.folder.path, followlinks=True): + for root, _, a_files in os.walk(self.folder.path, followlinks=True): folder = Folder(root) if walk_folders: yield folder diff --git a/hyde/plugin.py b/hyde/plugin.py index 7773757..b4b7569 100644 --- a/hyde/plugin.py +++ b/hyde/plugin.py @@ -262,6 +262,9 @@ class CLTransformer(Plugin): return app + def option_prefix(self, option): + return "--" + def process_args(self, supported): """ Given a list of supported arguments, consutructs an argument @@ -282,7 +285,8 @@ class CLTransformer(Plugin): descriptive = short = arg if descriptive in args or short in args: - result.append("--%s" % descriptive) + result.append("%s%s" % (self.option_prefix(descriptive), + descriptive)) val = args[descriptive if descriptive in args else short] if val: result.append(val) diff --git a/hyde/tests/ext/optipng/hyde-lt-b.png b/hyde/tests/ext/optipng/hyde-lt-b.png new file mode 120000 index 0000000..30dbddc --- /dev/null +++ b/hyde/tests/ext/optipng/hyde-lt-b.png @@ -0,0 +1 @@ +../../../../resources/hyde-lt-b.png \ No newline at end of file diff --git a/hyde/tests/ext/test_less.py b/hyde/tests/ext/test_less.py index 04c0c9c..1a9cc1a 100644 --- a/hyde/tests/ext/test_less.py +++ b/hyde/tests/ext/test_less.py @@ -32,7 +32,7 @@ class TestLess(object): paths = ['/usr/local/share/npm/bin/lessc', '~/local/bin/lessc'] less = [path for path in paths if File(path).exists] if not less: - assert False, "Cannot find the uglify executable" + assert False, "Cannot find the lessc executable" less = less[0] s.config.less = Expando(dict(app=less)) source = TEST_SITE.child('content/media/css/site.less') diff --git a/hyde/tests/ext/test_optipng.py b/hyde/tests/ext/test_optipng.py new file mode 100644 index 0000000..8154880 --- /dev/null +++ b/hyde/tests/ext/test_optipng.py @@ -0,0 +1,45 @@ +# -*- coding: utf-8 -*- +""" +Use nose +`$ pip install nose` +`$ nosetests` +""" +from hyde.fs import File, Folder +from hyde.model import Expando +from hyde.generator import Generator +from hyde.site import Site + +OPTIPNG_SOURCE = File(__file__).parent.child_folder('optipng') +TEST_SITE = File(__file__).parent.parent.child_folder('_test') + + +class TestOptipng(object): + + def setUp(self): + TEST_SITE.make() + TEST_SITE.parent.child_folder( + 'sites/test_jinja').copy_contents_to(TEST_SITE) + IMAGES = TEST_SITE.child_folder('content/media/images') + IMAGES.make() + OPTIPNG_SOURCE.copy_contents_to(IMAGES) + + + def tearDown(self): + TEST_SITE.delete() + + def test_can_execute_optipng(self): + s = Site(TEST_SITE) + s.config.mode = "production" + s.config.plugins = ['hyde.ext.plugins.optipng.OptiPNGPlugin'] + paths = ['/usr/local/bin/optipng'] + optipng = [path for path in paths if File(path).exists] + if not optipng: + assert False, "Cannot find the optipng executable" + optipng = optipng[0] + s.config.optipng = Expando(dict(app=optipng, args=dict(quiet=""))) + source =File(TEST_SITE.child('content/media/images/hyde-lt-b.png')) + target = File(Folder(s.config.deploy_root_path).child('media/images/hyde-lt-b.png')) + gen = Generator(s) + gen.generate_resource_at_path(source) + assert target.exists + assert target.size < source.size diff --git a/hyde/tests/test_fs.py b/hyde/tests/test_fs.py index 3c97073..386fd8f 100644 --- a/hyde/tests/test_fs.py +++ b/hyde/tests/test_fs.py @@ -204,6 +204,9 @@ def test_is_image(): assert not HELPERS.is_image assert LOGO.is_image +def test_file_size(): + assert LOGO.size == 1942 + @nottest def setup_data(): DATA_ROOT.make()