| @@ -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 | |||
| @@ -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: | |||
| @@ -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; | |||
| } | |||
| @@ -0,0 +1,4 @@ | |||
| rounded(radius = 5px) | |||
| -webkit-border-radius radius | |||
| -moz-border-radius radius | |||
| border-radius radius | |||
| @@ -0,0 +1,5 @@ | |||
| * { | |||
| border: 0; | |||
| padding: 0; | |||
| margin: 0; | |||
| } | |||
| @@ -0,0 +1,2 @@ | |||
| the-border = 1px | |||
| base-color = #111 | |||
| @@ -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 | |||
| @@ -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" | |||
| 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 | |||
| @@ -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() | |||
| @@ -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) | |||