diff --git a/hyde/ext/plugins/stylus.py b/hyde/ext/plugins/stylus.py new file mode 100644 index 0000000..98b8608 --- /dev/null +++ b/hyde/ext/plugins/stylus.py @@ -0,0 +1,102 @@ +# -*- coding: utf-8 -*- +""" +Less css plugin +""" + +from hyde.plugin import CLTransformer +from hyde.fs import File + +import re +import subprocess + + +class StylusPlugin(CLTransformer): + """ + The plugin class for less css + """ + + def __init__(self, site): + super(StylusPlugin, self).__init__(site) + + def begin_text_resource(self, resource, text): + """ + Replace @import statements with {% include %} statements. + """ + + if not resource.source_file.kind == 'styl': + return + import_finder = re.compile( + '^\\s*@import\s+(?:\'|\")([^\'\"]*)(?:\'|\")\s*\;?\s*$', + re.MULTILINE) + + def import_to_include(match): + """ + Converts a css import statement to include statemnt. + """ + if not match.lastindex: + return '' + path = match.groups(1)[0] + afile = File(resource.source_file.parent.child(path)) + if len(afile.kind.strip()) == 0: + afile = File(afile.path + '.styl') + ref = self.site.content.resource_from_path(afile.path) + if not ref: + raise self.template.exception_class( + "Cannot import from path [%s]" % afile.path) + ref.is_processable = False + return "\n" + \ + self.template.get_include_statement(ref.relative_path) + \ + "\n" + + text = import_finder.sub(import_to_include, text) + return text + + @property + def defaults(self): + """ + Returns `compress` if not in development mode. + """ + try: + mode = self.site.config.mode + except AttributeError: + mode = "production" + + defaults = {"compress":""} + if mode.startswith('dev'): + defaults = {} + return defaults + + @property + def plugin_name(self): + """ + The name of the plugin. + """ + return "stylus" + + def text_resource_complete(self, resource, text): + """ + Save the file to a temporary place and run stylus compiler. + Read the generated file and return the text as output. + Set the target path to have a css extension. + """ + if not resource.source_file.kind == 'styl': + return + stylus = self.app + source = File.make_temp(text) + target = source + supported = [("compress", "C")] + + args = [str(stylus)] + args.extend(self.process_args(supported)) + args.append(str(source)) + try: + self.call_app(args) + except subprocess.CalledProcessError: + raise self.template.exception_class( + "Cannot process %s. Error occurred when " + "processing [%s]" % (stylus.name, resource.source_file)) + out = target.read_all() + new_name = resource.source_file.name_without_extension + ".css" + target_folder = File(resource.relative_path).parent + resource.relative_deploy_path = target_folder.child(new_name) + return out diff --git a/hyde/plugin.py b/hyde/plugin.py index 8f09acd..7773757 100644 --- a/hyde/plugin.py +++ b/hyde/plugin.py @@ -209,6 +209,7 @@ class CLTransformer(Plugin): return self.__class__.__name__.replace('Plugin', '').lower() + @property def defaults(self): """ Default command line options. Can be overridden @@ -266,10 +267,12 @@ class CLTransformer(Plugin): Given a list of supported arguments, consutructs an argument list that could be passed on to the call_app function. """ + args = {} + args.update(self.defaults) try: - args = getattr(self.settings, 'args').to_dict() + args.update(getattr(self.settings, 'args').to_dict()) except AttributeError: - args = {} + pass result = [] for arg in supported: diff --git a/hyde/tests/ext/stylus/expected-site.css b/hyde/tests/ext/stylus/expected-site.css new file mode 100644 index 0000000..1efd373 --- /dev/null +++ b/hyde/tests/ext/stylus/expected-site.css @@ -0,0 +1,18 @@ +* { + border: 0; + padding: 0; + margin: 0; +} +#header { + color: #333; + border-left: 1px; + border-right: 2px; +} +#footer { + color: #444; +} +#content { + -webkit-border-radius: 10px; + -moz-border-radius: 10px; + border-radius: 10px; +} diff --git a/hyde/tests/ext/stylus/inc/mixin.styl b/hyde/tests/ext/stylus/inc/mixin.styl new file mode 100644 index 0000000..1a67e39 --- /dev/null +++ b/hyde/tests/ext/stylus/inc/mixin.styl @@ -0,0 +1,4 @@ +rounded(radius = 5px) + -webkit-border-radius radius + -moz-border-radius radius + border-radius radius diff --git a/hyde/tests/ext/stylus/inc/reset.css b/hyde/tests/ext/stylus/inc/reset.css new file mode 100644 index 0000000..6f4f9ef --- /dev/null +++ b/hyde/tests/ext/stylus/inc/reset.css @@ -0,0 +1,5 @@ +* { + border: 0; + padding: 0; + margin: 0; +} \ No newline at end of file diff --git a/hyde/tests/ext/stylus/inc/vars.styl b/hyde/tests/ext/stylus/inc/vars.styl new file mode 100644 index 0000000..d8ca9c3 --- /dev/null +++ b/hyde/tests/ext/stylus/inc/vars.styl @@ -0,0 +1,2 @@ +the-border = 1px +base-color = #111 diff --git a/hyde/tests/ext/stylus/site.styl b/hyde/tests/ext/stylus/site.styl new file mode 100644 index 0000000..53333ba --- /dev/null +++ b/hyde/tests/ext/stylus/site.styl @@ -0,0 +1,14 @@ +@import "inc/mixin" +@import "inc/vars" +@import "inc/reset.css" + +#header + color base-color * 3 + border-left the-border + border-right the-border * 2 + +#footer + color (base-color + #111) * 1.5 + +#content + rounded 10px \ No newline at end of file diff --git a/hyde/tests/ext/test_less.py b/hyde/tests/ext/test_less.py index f4cc009..04c0c9c 100644 --- a/hyde/tests/ext/test_less.py +++ b/hyde/tests/ext/test_less.py @@ -29,20 +29,20 @@ class TestLess(object): def test_can_execute_less(self): s = Site(TEST_SITE) s.config.plugins = ['hyde.ext.plugins.less.LessCSSPlugin'] - less_paths = ['/usr/local/share/npm/bin/lessc', '~/local/bin/lessc'] - for path in less_paths: - if File(path).exists: - s.config.less = Expando(dict(app=path)) - source = TEST_SITE.child('content/media/css/site.less') - target = File(Folder(s.config.deploy_root_path).child('media/css/site.css')) - gen = Generator(s) - gen.generate_resource_at_path(source) - - assert target.exists - text = target.read_all() - expected_text = File(LESS_SOURCE.child('expected-site.css')).read_all() - - assert text == expected_text - return - - assert "Cannot find the less css executable" \ No newline at end of file + 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" + less = less[0] + s.config.less = Expando(dict(app=less)) + source = TEST_SITE.child('content/media/css/site.less') + target = File(Folder(s.config.deploy_root_path).child('media/css/site.css')) + gen = Generator(s) + gen.generate_resource_at_path(source) + + assert target.exists + text = target.read_all() + expected_text = File(LESS_SOURCE.child('expected-site.css')).read_all() + + assert text == expected_text + return diff --git a/hyde/tests/ext/test_stylus.py b/hyde/tests/ext/test_stylus.py new file mode 100644 index 0000000..48b3fd4 --- /dev/null +++ b/hyde/tests/ext/test_stylus.py @@ -0,0 +1,47 @@ +# -*- 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 + +STYLUS_SOURCE = File(__file__).parent.child_folder('stylus') +TEST_SITE = File(__file__).parent.parent.child_folder('_test') + + +class TestLess(object): + + def setUp(self): + TEST_SITE.make() + TEST_SITE.parent.child_folder( + 'sites/test_jinja').copy_contents_to(TEST_SITE) + STYLUS_SOURCE.copy_contents_to(TEST_SITE.child('content/media/css')) + File(TEST_SITE.child('content/media/css/site.css')).delete() + + + def tearDown(self): + TEST_SITE.delete() + + def test_can_execute_stylus(self): + s = Site(TEST_SITE) + s.config.plugins = ['hyde.ext.plugins.stylus.StylusPlugin'] + paths = ['/usr/local/share/npm/bin/stylus', '~/local/bin/stylus'] + stylus = [path for path in paths if File(path).exists] + if not stylus: + assert False, "Cannot find the stylus executable" + + stylus = stylus[0] + s.config.stylus = Expando(dict(app=stylus)) + source = TEST_SITE.child('content/media/css/site.styl') + target = File(Folder(s.config.deploy_root_path).child('media/css/site.css')) + gen = Generator(s) + gen.generate_resource_at_path(source) + + assert target.exists + text = target.read_all() + expected_text = File(STYLUS_SOURCE.child('expected-site.css')).read_all() + assert text.strip() == expected_text.strip() \ No newline at end of file diff --git a/hyde/tests/ext/test_uglify.py b/hyde/tests/ext/test_uglify.py index 01563dd..57d1952 100644 --- a/hyde/tests/ext/test_uglify.py +++ b/hyde/tests/ext/test_uglify.py @@ -22,6 +22,7 @@ class TestLess(object): JS = TEST_SITE.child_folder('content/media/js') JS.make() UGLIFY_SOURCE.copy_contents_to(JS) + def tearDown(self): TEST_SITE.delete() @@ -36,7 +37,7 @@ class TestLess(object): assert False, "Cannot find the uglify executable" uglify = uglify[0] - s.config.uglify = Expando(dict(app=path)) + s.config.uglify = Expando(dict(app=uglify)) source = TEST_SITE.child('content/media/js/jquery.js') target = File(Folder(s.config.deploy_root_path).child('media/js/jquery.js')) gen = Generator(s) @@ -57,7 +58,7 @@ class TestLess(object): assert False, "Cannot find the uglify executable" uglify = uglify[0] - s.config.uglify = Expando(dict(app=path, args={"nc":""})) + s.config.uglify = Expando(dict(app=uglify, args={"nc":""})) source = TEST_SITE.child('content/media/js/jquery.js') target = File(Folder(s.config.deploy_root_path).child('media/js/jquery.js')) gen = Generator(s)