diff --git a/hamlpy/compiler.py b/hamlpy/compiler.py index 265c0d8..3532d05 100755 --- a/hamlpy/compiler.py +++ b/hamlpy/compiler.py @@ -14,6 +14,7 @@ class Compiler: 'django_inline_style': False, # support both #{...} and ={...} 'tag_config': 'django', # Django vs Jinja2 tags 'custom_self_closing_tags': {}, # additional self-closing tags + 'cdata': True, # wrap CSS, Javascript etc content in CDATA section 'debug_tree': False, } diff --git a/hamlpy/parser/filters.py b/hamlpy/parser/filters.py index c26e67b..dd20623 100644 --- a/hamlpy/parser/filters.py +++ b/hamlpy/parser/filters.py @@ -1,7 +1,13 @@ from __future__ import unicode_literals +""" +Core HamlPy filters. + +The implementation of these should match https://github.com/haml/haml/blob/master/lib/haml/filters.rb as closely as +possible. Where we differ is that we don't compile Stylus, Coffeescript etc into CSS or Javascript - but place the +content into suitable ' +def stylus(text, options): + return style_filter(text, 'text/stylus', options) -def stylus(content, indent, options): - return indent + '' +def less(text, options): + return style_filter(text, 'text/less', options) -def javascript(content, indent, options): - return '' +def sass(text, options): + return style_filter(text, 'text/sass', options) -def coffeescript(content, indent, options): - return '' +def javascript(text, options): + return script_filter(text, 'text/javascript', '//', options) -def markdown(content, indent, options): + +def coffee(text, options): + return script_filter(text, 'text/coffeescript', '#', options) + + +def markdown(content, options): if not _markdown_available: raise ParseException("Markdown is not available") - return markdown_lib(textwrap.dedent(content)) + return markdown_lib(content) -def highlight(content, indent, options): +def highlight(content, options): if not _pygments_available: raise ParseException("Pygments is not available") if content: - content = textwrap.dedent(content) - # let Pygments try to guess syntax but default to Python try: lexer = guess_lexer(content) @@ -103,9 +111,8 @@ def highlight(content, indent, options): return '' -def python(content, indent, options): +def python(content, options): if content: - content = textwrap.dedent(content) compiled_code = compile(content, "", "exec") output_buffer = StringIO() sys.stdout = output_buffer @@ -123,21 +130,56 @@ def python(content, indent, options): return '' +# ---------------------------------------------------------------------------------- +# Helper functions +# ---------------------------------------------------------------------------------- + +def style_filter(text, mime_type, options): + indent = ' ' if options['cdata'] else ' ' + text = text.rstrip().replace('\n', '\n' + indent) + type_attr = ' type=%(attr_wrapper)s%(mime_type)s%(attr_wrapper)s' % \ + {'attr_wrapper': options['attr_wrapper'], 'mime_type': mime_type} + before, after = (' /**/\n') if options['cdata'] else ('', '') + + return '' % (type_attr, before, indent, text, after) + + +def script_filter(text, mime_type, comment, options): + indent = ' ' if options['cdata'] else ' ' + text = text.rstrip().replace('\n', '\n' + indent) + type_attr = ' type=%(attr_wrapper)s%(mime_type)s%(attr_wrapper)s' % \ + {'attr_wrapper': options['attr_wrapper'], 'mime_type': mime_type} + before, after = (' %s\n' % comment) if options['cdata'] else ('', '') + + return '' % (type_attr, before, indent, text, after) + + +# ---------------------------------------------------------------------------------- +# Filter registration +# ---------------------------------------------------------------------------------- + FILTERS = { 'plain': plain, 'preserve': preserve, + 'escaped': escaped, 'cdata': cdata, 'css': css, 'stylus': stylus, + 'less': less, + 'sass': sass, 'javascript': javascript, - 'coffee': coffeescript, - 'coffeescript': coffeescript, + 'coffee': coffee, + 'coffeescript': coffee, 'markdown': markdown, 'highlight': highlight, 'python': python } +def register_filter(name, callback): + FILTERS[name] = callback + + def get_filter(name): if name not in FILTERS: raise ParseException("No such filter: " + name) diff --git a/hamlpy/parser/nodes.py b/hamlpy/parser/nodes.py index 4055490..f8dd87d 100644 --- a/hamlpy/parser/nodes.py +++ b/hamlpy/parser/nodes.py @@ -1,5 +1,7 @@ from __future__ import print_function, unicode_literals +import textwrap + from .core import ParseException, TreeNode, read_line, read_whitespace, peek_indentation from .elements import read_element from .filters import get_filter @@ -460,10 +462,14 @@ def __init__(self, filter_name, content, indent, compiler): self.content = content def _render(self): + content = textwrap.dedent(self.content) + filter_func = get_filter(self.filter_name) - filtered_content = filter_func(self.content, self.indent, self.compiler.options) + content = filter_func(content, self.compiler.options) + + content = self.indent + content.replace('\n', '\n' + self.indent) - self.before = filtered_content + self.before = content self.after = self.render_newlines() if self.content else '' def _post_render(self): diff --git a/hamlpy/parser/utils.py b/hamlpy/parser/utils.py new file mode 100644 index 0000000..ec5b909 --- /dev/null +++ b/hamlpy/parser/utils.py @@ -0,0 +1,13 @@ +from __future__ import unicode_literals + + +def html_escape(s): + """ + Escapes HTML entities, matching substitutions used the Ruby Haml library + """ + s = s.replace("&", "&") + s = s.replace("<", "<") + s = s.replace(">", ">") + s = s.replace('"', """) + s = s.replace("'", "'") + return s diff --git a/hamlpy/test/templates/filterMultilineIgnore.hamlpy b/hamlpy/test/templates/filterMultilineIgnore.hamlpy index d19a380..ea77609 100644 --- a/hamlpy/test/templates/filterMultilineIgnore.hamlpy +++ b/hamlpy/test/templates/filterMultilineIgnore.hamlpy @@ -9,14 +9,14 @@ should } not be interpreted as a multiline string :css - .test { - display: inline; - } + .test { + display: inline; + } :javascript - These { - Braces should { - also - } be { ignored + These { + Braces should { + also + } be { ignored .multilinetest2{id:'{{myId}}', class:'{{myClass}}', diff --git a/hamlpy/test/templates/filterMultilineIgnore.html b/hamlpy/test/templates/filterMultilineIgnore.html index 879f299..dd1ba2e 100644 --- a/hamlpy/test/templates/filterMultilineIgnore.html +++ b/hamlpy/test/templates/filterMultilineIgnore.html @@ -2,78 +2,77 @@ These { { braces should } not be interpreted as a multiline string -These { { braces should } not be interpreted as a multiline string +These { { braces should } not be interpreted as a multiline string
test1 -test2
+ test2blah - test3 - test4
+ test3 + test4test5 -test6 -test7 -test8 -test9
+ test6 + test7 + test8 + test9 diff --git a/hamlpy/test/templates/nukeOuterWhiteSpace.html b/hamlpy/test/templates/nukeOuterWhiteSpace.html index 171d1b3..ab227d1 100644 --- a/hamlpy/test/templates/nukeOuterWhiteSpace.html +++ b/hamlpy/test/templates/nukeOuterWhiteSpace.html @@ -16,11 +16,11 @@
-
+
blah
diff --git a/hamlpy/test/templates/whitespacePreservation.html b/hamlpy/test/templates/whitespacePreservation.html index 552d78a..a469dfd 100644 --- a/hamlpy/test/templates/whitespacePreservation.html +++ b/hamlpy/test/templates/whitespacePreservation.html @@ -43,67 +43,62 @@ One more text