diff --git a/hamlpy/hamlpy.py b/hamlpy/hamlpy.py index 640b6d6..dd92f25 100755 --- a/hamlpy/hamlpy.py +++ b/hamlpy/hamlpy.py @@ -8,26 +8,32 @@ VALID_EXTENSIONS = ['haml', 'hamlpy'] -class Compiler: +DEFAULT_OPTIONS = { + 'attr_wrapper': '\'', # how to render attribute values, e.g. foo='bar' + 'django_inline_style': True, # support both #{...} and ={...} + 'debug_tree': False +} + +class Compiler: def __init__(self, options_dict=None): - options_dict = options_dict or {} - self.debug_tree = options_dict.pop('debug_tree', False) - self.options_dict = options_dict + self.options = DEFAULT_OPTIONS.copy() + if options_dict: + self.options.update(options_dict) def process(self, raw_text): split_text = raw_text.split('\n') return self.process_lines(split_text) def process_lines(self, haml_lines): - root = RootNode(**self.options_dict) + root = RootNode(self.options) line_iter = iter(haml_lines) haml_node=None for line_number, line in enumerate(line_iter): node_lines = line - if not root.parent_of(HamlNode(line)).inside_filter_node(): + if not root.parent_of(HamlNode(line, self.options)).inside_filter_node(): if line.count('{') - line.count('}') == 1: start_multiline = line_number # For exception handling @@ -42,11 +48,11 @@ def process_lines(self, haml_lines): if haml_node is not None and len(node_lines.strip()) == 0: haml_node.newlines += 1 else: - haml_node = create_node(node_lines) + haml_node = create_node(node_lines, self.options) if haml_node: root.add_node(haml_node) - if self.options_dict and self.options_dict.get('debug_tree'): + if self.options['debug_tree']: return root.debug_tree() else: return root.render() diff --git a/hamlpy/hamlpy_watcher.py b/hamlpy/hamlpy_watcher.py index 98b9f63..89d0f34 100644 --- a/hamlpy/hamlpy_watcher.py +++ b/hamlpy/hamlpy_watcher.py @@ -51,6 +51,8 @@ def __call__(self, parser, namespace, values, option_string=None): help='Add self closing tag. eg. --tag macro:endmacro') arg_parser.add_argument('--attr-wrapper', dest='attr_wrapper', type=str, choices=('"', "'"), default="'", action='store', help="The character that should wrap element attributes. This defaults to ' (an apostrophe).") +arg_parser.add_argument('--django-inline', dest='django_inline', action='store_true', + help="Whether to support ={...} syntax for inline variables in addition to #{...}") arg_parser.add_argument('--jinja', default=False, action='store_true', help='Makes the necessary changes to be used with Jinja2.') arg_parser.add_argument('--once', default=False, action='store_true', @@ -91,6 +93,9 @@ def watch_folder(): if args.attr_wrapper: compiler_args['attr_wrapper'] = args.attr_wrapper + + if args.django_inline: + compiler_args['django_inline_style'] = args.django_inline if args.jinja: for k in ('ifchanged', 'ifequal', 'ifnotequal', 'autoescape', 'blocktrans', diff --git a/hamlpy/nodes.py b/hamlpy/nodes.py index a6d3e91..116fe34 100644 --- a/hamlpy/nodes.py +++ b/hamlpy/nodes.py @@ -40,9 +40,6 @@ class NotAvailableError(Exception): VARIABLE = '=' TAG = '-' -INLINE_VARIABLE = re.compile(r'(? 1 else 'utf-8' self.before = "" % ( - self.attr_wrapper, self.attr_wrapper, - self.attr_wrapper, encoding, self.attr_wrapper, + attr_wrapper, attr_wrapper, + attr_wrapper, encoding, attr_wrapper, ) else: types = { @@ -418,8 +440,9 @@ def _post_render(self): pass class VariableNode(ElementNode): - def __init__(self, haml): - ElementNode.__init__(self, haml) + def __init__(self, haml, options): + super(VariableNode, self).__init__(haml, options) + self.django_variable = True def _render(self): @@ -455,8 +478,9 @@ class TagNode(HamlNode): 'for':'empty', 'with':'with'} - def __init__(self, haml): - HamlNode.__init__(self, haml) + def __init__(self, haml, options): + super(TagNode, self).__init__(haml, options) + self.tag_statement = self.haml.lstrip(TAG).strip() self.tag_name = self.tag_statement.split(' ')[0] @@ -478,7 +502,6 @@ def _render(self): def should_contain(self, node): return isinstance(node, TagNode) and node.tag_name in self.may_contain.get(self.tag_name, '') - class FilterNode(HamlNode): def add_node(self, node): self.add_child(node) @@ -502,10 +525,10 @@ def _post_render(self): # Don't post-render children of filter nodes as we don't want them to be interpreted as HAML pass - class PlainFilterNode(FilterNode): - def __init__(self, haml): - FilterNode.__init__(self, haml) + def __init__(self, haml, options): + super(PlainFilterNode, self).__init__(haml, options) + self.empty_node = True def _render(self): @@ -541,7 +564,7 @@ def _render(self): class JavascriptFilterNode(FilterNode): def _render(self): self.before = '\n' @@ -550,7 +573,7 @@ def _render(self): class CoffeeScriptFilterNode(FilterNode): def _render(self): self.before = '\n' @@ -559,7 +582,7 @@ def _render(self): class CssFilterNode(FilterNode): def _render(self): self.before = '\n' @@ -568,7 +591,7 @@ def _render(self): class StylusFilterNode(FilterNode): def _render(self): self.before = '\n' diff --git a/hamlpy/template/loaders.py b/hamlpy/template/loaders.py index 6a393e4..c7d16e4 100644 --- a/hamlpy/template/loaders.py +++ b/hamlpy/template/loaders.py @@ -10,10 +10,13 @@ from hamlpy.template.utils import get_django_template_loaders # Get options from Django settings -options_dict = {} +options = {} if hasattr(settings, 'HAMLPY_ATTR_WRAPPER'): - options_dict.update(attr_wrapper=settings.HAMLPY_ATTR_WRAPPER) + options.update(attr_wrapper=settings.HAMLPY_ATTR_WRAPPER) + +if hasattr(settings, 'HAMLPY_DJANGO_INLINE_STYLE'): + options.update(django_inline_style=settings.HAMLPY_DJANGO_INLINE_STYLE) def get_haml_loader(loader): @@ -26,7 +29,7 @@ def get_contents(self, origin): extension = _extension.lstrip('.') if extension in hamlpy.VALID_EXTENSIONS: - compiler = hamlpy.Compiler(options_dict=options_dict) + compiler = hamlpy.Compiler(options_dict=options) return compiler.process(contents) return contents @@ -45,7 +48,7 @@ def load_template_source(self, template_name, *args, **kwargs): except TemplateDoesNotExist: pass else: - hamlParser = hamlpy.Compiler(options_dict=options_dict) + hamlParser = hamlpy.Compiler(options_dict=options) html = hamlParser.process(haml_source) return html, template_path diff --git a/hamlpy/test/test_compiler.py b/hamlpy/test/test_compiler.py index 4c126cf..11f4886 100755 --- a/hamlpy/test/test_compiler.py +++ b/hamlpy/test/test_compiler.py @@ -3,7 +3,7 @@ import unittest -from hamlpy import hamlpy +from hamlpy import hamlpy, nodes class CompilerTest(unittest.TestCase): @@ -104,6 +104,10 @@ def test_django_variables(self): self._test("\\={name}, how are you?", "={name}, how are you?") self._test("\\#{name}, how are you?", "#{name}, how are you?") + # can disable use of ={...} syntax + options = {'django_inline_style': False} + self._test("Dear ={title} #{name} href={{ var }}", "Dear ={title} {{ name }} href={{ var }}", options) + def test_django_tags(self): # if/else self._test('- if something\n %p hello\n- else\n %p goodbye', @@ -165,6 +169,8 @@ def test_attr_wrapper(self): ''', options={'attr_wrapper': '"'}) def _test(self, haml, expected_html, options=None): + nodes._inline_variable_regexes = None # clear cached regexes + parser = hamlpy.Compiler(options) result = parser.process(haml) diff --git a/hamlpy/test/test_hamlnode.py b/hamlpy/test/test_hamlnode.py index 594daed..813080f 100644 --- a/hamlpy/test/test_hamlnode.py +++ b/hamlpy/test/test_hamlnode.py @@ -3,50 +3,51 @@ import unittest from hamlpy import nodes +from hamlpy import hamlpy class ElementNodeTest(unittest.TestCase): def test_calculates_indentation_properly(self): - no_indentation = nodes.ElementNode('%div') + no_indentation = nodes.ElementNode('%div', hamlpy.DEFAULT_OPTIONS) self.assertEqual(0, no_indentation.indentation) - three_indentation = nodes.ElementNode(' %div') + three_indentation = nodes.ElementNode(' %div', hamlpy.DEFAULT_OPTIONS) self.assertEqual(3, three_indentation.indentation) - six_indentation = nodes.ElementNode(' %div') + six_indentation = nodes.ElementNode(' %div', hamlpy.DEFAULT_OPTIONS) self.assertEqual(6, six_indentation.indentation) def test_indents_tabs_properly(self): - no_indentation = nodes.ElementNode('%div') + no_indentation = nodes.ElementNode('%div', hamlpy.DEFAULT_OPTIONS) self.assertEqual('', no_indentation.spaces) - one_tab = nodes.HamlNode(' %div') + one_tab = nodes.HamlNode(' %div', hamlpy.DEFAULT_OPTIONS) self.assertEqual('\t', one_tab.spaces) - one_space = nodes.HamlNode(' %div') + one_space = nodes.HamlNode(' %div', hamlpy.DEFAULT_OPTIONS) self.assertEqual(' ', one_space.spaces) - three_tabs = nodes.HamlNode(' %div') + three_tabs = nodes.HamlNode(' %div', hamlpy.DEFAULT_OPTIONS) self.assertEqual('\t\t\t', three_tabs.spaces) - tab_space = nodes.HamlNode(' %div') + tab_space = nodes.HamlNode(' %div', hamlpy.DEFAULT_OPTIONS) self.assertEqual('\t\t', tab_space.spaces) - space_tab = nodes.HamlNode(' %div') + space_tab = nodes.HamlNode(' %div', hamlpy.DEFAULT_OPTIONS) self.assertEqual(' ', space_tab.spaces) def test_lines_are_always_stripped_of_whitespace(self): - some_space = nodes.ElementNode(' %div') + some_space = nodes.ElementNode(' %div', hamlpy.DEFAULT_OPTIONS) self.assertEqual('%div', some_space.haml) - lots_of_space = nodes.ElementNode(' %div ') + lots_of_space = nodes.ElementNode(' %div ', hamlpy.DEFAULT_OPTIONS) self.assertEqual('%div', lots_of_space.haml) def test_inserts_nodes_into_proper_tree_depth(self): - no_indentation_node = nodes.ElementNode('%div') - one_indentation_node = nodes.ElementNode(' %div') - two_indentation_node = nodes.ElementNode(' %div') - another_one_indentation_node = nodes.ElementNode(' %div') + no_indentation_node = nodes.ElementNode('%div', hamlpy.DEFAULT_OPTIONS) + one_indentation_node = nodes.ElementNode(' %div', hamlpy.DEFAULT_OPTIONS) + two_indentation_node = nodes.ElementNode(' %div', hamlpy.DEFAULT_OPTIONS) + another_one_indentation_node = nodes.ElementNode(' %div', hamlpy.DEFAULT_OPTIONS) no_indentation_node.add_node(one_indentation_node) no_indentation_node.add_node(two_indentation_node) @@ -57,10 +58,10 @@ def test_inserts_nodes_into_proper_tree_depth(self): self.assertEqual(another_one_indentation_node, no_indentation_node.children[1]) def test_adds_multiple_nodes_to_one(self): - start = nodes.ElementNode('%div') - one = nodes.ElementNode(' %div') - two = nodes.ElementNode(' %div') - three = nodes.ElementNode(' %div') + start = nodes.ElementNode('%div', hamlpy.DEFAULT_OPTIONS) + one = nodes.ElementNode(' %div', hamlpy.DEFAULT_OPTIONS) + two = nodes.ElementNode(' %div', hamlpy.DEFAULT_OPTIONS) + three = nodes.ElementNode(' %div', hamlpy.DEFAULT_OPTIONS) start.add_node(one) start.add_node(two) @@ -69,13 +70,13 @@ def test_adds_multiple_nodes_to_one(self): self.assertEqual(3, len(start.children)) def test_node_parent_function(self): - root = nodes.ElementNode('%div.a') + root = nodes.ElementNode('%div.a', hamlpy.DEFAULT_OPTIONS) elements = [ - {'node': nodes.ElementNode(' %div.b'), 'expected_parent': 'root'}, - {'node': nodes.ElementNode(' %div.c'), 'expected_parent': 'root'}, - {'node': nodes.ElementNode(' %div.d'), 'expected_parent': 'elements[1]["node"]'}, - {'node': nodes.ElementNode(' %div.e'), 'expected_parent': 'elements[2]["node"]'}, - {'node': nodes.ElementNode(' %div.f'), 'expected_parent': 'root'}, + {'node': nodes.ElementNode(' %div.b', hamlpy.DEFAULT_OPTIONS), 'expected_parent': 'root'}, + {'node': nodes.ElementNode(' %div.c', hamlpy.DEFAULT_OPTIONS), 'expected_parent': 'root'}, + {'node': nodes.ElementNode(' %div.d', hamlpy.DEFAULT_OPTIONS), 'expected_parent': 'elements[1]["node"]'}, + {'node': nodes.ElementNode(' %div.e', hamlpy.DEFAULT_OPTIONS), 'expected_parent': 'elements[2]["node"]'}, + {'node': nodes.ElementNode(' %div.f', hamlpy.DEFAULT_OPTIONS), 'expected_parent': 'root'}, ] for el in elements: diff --git a/hamlpy/test/test_loader.py b/hamlpy/test/test_loader.py index 32ac4b0..e9c1682 100644 --- a/hamlpy/test/test_loader.py +++ b/hamlpy/test/test_loader.py @@ -47,12 +47,16 @@ def test_compiler_settings(self, mock_compiler_class): mock_compiler_class.assert_called_once_with(options_dict={}) mock_compiler_class.reset_mock() - with override_settings(HAMLPY_ATTR_WRAPPER='"'): + with override_settings(HAMLPY_ATTR_WRAPPER='"', HAMLPY_DJANGO_INLINE_STYLE=False): reload_module(hamlpy.template.loaders) rendered = render_to_string('simple.hamlpy') - mock_compiler_class.assert_called_once_with(options_dict={'attr_wrapper': '"'}) + mock_compiler_class.assert_called_once_with(options_dict={ + 'attr_wrapper': '"', + 'django_inline_style': False + }) + assert '"someClass"' in rendered def test_template_rendering(self): diff --git a/hamlpy/test/test_node_factory.py b/hamlpy/test/test_node_factory.py index e551f86..5bc1a26 100644 --- a/hamlpy/test/test_node_factory.py +++ b/hamlpy/test/test_node_factory.py @@ -3,68 +3,73 @@ import unittest from hamlpy import nodes +from hamlpy import hamlpy class NodeFactoryTest(unittest.TestCase): def test_creates_element_node_with_percent(self): - node = nodes.create_node('%div') + node = self._create_node('%div') assert isinstance(node, nodes.ElementNode) - node = nodes.create_node(' %html') + node = self._create_node(' %html') assert isinstance(node, nodes.ElementNode) def test_creates_element_node_with_dot(self): - node = nodes.create_node('.className') + node = self._create_node('.className') assert isinstance(node, nodes.ElementNode) - node = nodes.create_node(' .className') + node = self._create_node(' .className') assert isinstance(node, nodes.ElementNode) def test_creates_element_node_with_hash(self): - node = nodes.create_node('#idName') + node = self._create_node('#idName') assert isinstance(node, nodes.ElementNode) - node = nodes.create_node(' #idName') + node = self._create_node(' #idName') assert isinstance(node, nodes.ElementNode) def test_creates_html_comment_node_with_front_slash(self): - node = nodes.create_node('/ some Comment') + node = self._create_node('/ some Comment') assert isinstance(node, nodes.CommentNode) - node = nodes.create_node(' / some Comment') + node = self._create_node(' / some Comment') assert isinstance(node, nodes.CommentNode) def test_random_text_returns_haml_node(self): - node = nodes.create_node('just some random text') + node = self._create_node('just some random text') assert isinstance(node, nodes.HamlNode) - node = nodes.create_node(' more random text') + node = self._create_node(' more random text') assert isinstance(node, nodes.HamlNode) def test_correct_symbol_creates_haml_comment(self): - node = nodes.create_node('-# This is a haml comment') + node = self._create_node('-# This is a haml comment') assert isinstance(node, nodes.HamlCommentNode) def test_equals_symbol_creates_variable_node(self): - node = nodes.create_node('= some.variable') + node = self._create_node('= some.variable') assert isinstance(node, nodes.VariableNode) def test_dash_symbol_creates_tag_node(self): - node = nodes.create_node('- for something in somethings') + node = self._create_node('- for something in somethings') assert isinstance(node, nodes.TagNode) def test_backslash_symbol_creates_tag_node(self): - node = nodes.create_node('\\= some.variable') + node = self._create_node('\\= some.variable') assert isinstance(node, nodes.HamlNode) - node = nodes.create_node(' \\= some.variable') + node = self._create_node(' \\= some.variable') assert isinstance(node, nodes.HamlNode) def test_python_creates_python_node(self): - node = nodes.create_node(':python') + node = self._create_node(':python') assert isinstance(node, nodes.PythonFilterNode) def test_slash_with_if_creates_a_conditional_comment_node(self): - node = nodes.create_node('/[if IE 5]') + node = self._create_node('/[if IE 5]') assert isinstance(node, nodes.ConditionalCommentNode) + + @staticmethod + def _create_node(line): + return nodes.create_node(line, hamlpy.DEFAULT_OPTIONS) diff --git a/readme.md b/readme.md index e16e66c..5b35ea6 100644 --- a/readme.md +++ b/readme.md @@ -116,7 +116,9 @@ TEMPLATE_LOADERS = ( Following values in Django settings affect haml processing: - * `HAMLPY_ATTR_WRAPPER` -- The character that should wrap element attributes. This defaults to ' (an apostrophe). + * `HAMLPY_ATTR_WRAPPER` -- The character that should wrap element attributes. Defaults to `'` (an apostrophe). + * `HAMLPY_DJANGO_INLINE_STYLE` -- Whether to support `={...}` syntax for inline variables in addition to `#{...}`. + Defaults to `True`. ### Option 2: Watcher diff --git a/reference.md b/reference.md index ed03865..8262830 100644 --- a/reference.md +++ b/reference.md @@ -386,12 +386,12 @@ is compiled to: ``` -### Inline Django Variables: ={...} +### Inline Django Variables: #{...} -You can also use inline variables by surrounding the variable name with curly braces. For example: +You can also use inline variables using the `#{...}` syntax. For example: ```haml -Hello ={name}, how are you today? +Hello #{name}, how are you today? ``` is compiled to @@ -403,7 +403,7 @@ Hello {{ name }}, how are you today? Inline variables can also be used in an element's attribute values. For example: ```haml -%a{'title':'Hello ={name}, how are you?'} Hello +%a{'title':'Hello #{name}, how are you?'} Hello ``` is compiled to: @@ -415,16 +415,17 @@ is compiled to: Inline variables can be escaped by placing a `\` before them. For example: ```haml -Hello \={name} +Hello \#{name} ``` -is compiled to +is compiled to: ```htmldjango -Hello ={name} +Hello #{name} ``` -The Ruby style (`#{...}` rather than `={...}`) is also supported and the two can be used interchangeably. +Django style `={...}` syntax is also optionally supported. If you are using the template loader +then ensure `HAMLPY_DJANGO_INLINE_STYLE` is `True`, and the two syntaxes can then be used interchangeably.