| @@ -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) | |||
| @@ -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 | |||
| @@ -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) | |||
| @@ -0,0 +1 @@ | |||
| ../../../../resources/hyde-lt-b.png | |||
| @@ -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') | |||
| @@ -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 | |||
| @@ -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() | |||