From fe287c87f2860c6683c7ee88d3c5d72bcb4f107f Mon Sep 17 00:00:00 2001
From: Barry Coughlan
Date: Wed, 5 Sep 2012 12:05:55 +0200
Subject: [PATCH 01/48] Minor clarity change
---
readme.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/readme.md b/readme.md
index c9d5a64..c91fe71 100644
--- a/readme.md
+++ b/readme.md
@@ -115,6 +115,6 @@ HamlPy currently:
## Contributing
-Very happy to have contributions to this project. Please write tests for any new features and always ensure the current tests pass. You can run the tests from the hamlpy/test using nosetests by typing
+Very happy to have contributions to this project. Please write tests for any new features and always ensure the current tests pass. You can run the tests from the **hamlpy/test** folder using nosetests by typing
nosetests *.py
From 1c36d67f8cf2440f9352e00c9c118389446becc7 Mon Sep 17 00:00:00 2001
From: Jesse Miller
Date: Wed, 5 Sep 2012 12:46:38 -0700
Subject: [PATCH 02/48] roll version
---
setup.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/setup.py b/setup.py
index 2eef6cc..a9fe101 100644
--- a/setup.py
+++ b/setup.py
@@ -2,7 +2,7 @@
# Note to Jesse - only push sdist to PyPi, bdist seems to always break pip installer
setup(name='hamlpy',
- version = '0.82.1',
+ version = '0.82.2',
download_url = 'git@github.com:jessemiller/HamlPy.git',
packages = ['hamlpy', 'hamlpy.template'],
author = 'Jesse Miller',
From 16be72532990139865ef7c9282ab2c250e815059 Mon Sep 17 00:00:00 2001
From: Barry Coughlan
Date: Thu, 13 Sep 2012 22:26:22 +0100
Subject: [PATCH 03/48] Don't remove backslash at start of lines inside filter
nodes
---
hamlpy/hamlpy.py | 2 +-
hamlpy/nodes.py | 19 ++++++++++++-------
hamlpy/test/hamlpy_test.py | 7 +++++++
3 files changed, 20 insertions(+), 8 deletions(-)
diff --git a/hamlpy/hamlpy.py b/hamlpy/hamlpy.py
index 2bd3f1d..71ea75a 100755
--- a/hamlpy/hamlpy.py
+++ b/hamlpy/hamlpy.py
@@ -18,7 +18,7 @@ def process_lines(self, haml_lines, options=None):
for line_number, line in enumerate(line_iter):
node_lines = line
- if root.parent_of(HamlNode(line)).should_treat_children_as_multiline():
+ if not root.parent_of(HamlNode(line)).inside_filter_node():
if line.count('{') - line.count('}') == 1:
start_multiline=line_number # For exception handling
diff --git a/hamlpy/nodes.py b/hamlpy/nodes.py
index 1a7e7f8..763c117 100644
--- a/hamlpy/nodes.py
+++ b/hamlpy/nodes.py
@@ -49,7 +49,7 @@ def create_node(haml_line):
return PlaintextNode(haml_line)
if stripped_line[0] == HAML_ESCAPE:
- return PlaintextNode(haml_line.replace(HAML_ESCAPE, '', 1))
+ return PlaintextNode(haml_line)
if stripped_line.startswith(DOCTYPE):
return DoctypeNode(haml_line)
@@ -153,11 +153,11 @@ def parent_of(self, node):
else:
return self
- def should_treat_children_as_multiline(self):
+ def inside_filter_node(self):
if self.parent:
- return self.parent.should_treat_children_as_multiline()
+ return self.parent.inside_filter_node()
else:
- return True
+ return False
def _render_children(self):
for child in self.children:
@@ -223,7 +223,12 @@ def __repr__(self):
class PlaintextNode(HamlNode):
'''Node that is not modified or processed when rendering'''
def _render(self):
- self.before = '%s%s' % (self.spaces, self.replace_inline_variables(self.haml))
+ text = self.replace_inline_variables(self.haml)
+ # Remove escape character unless inside filter node
+ if text and text[0]==HAML_ESCAPE and not self.inside_filter_node():
+ text = text.replace(HAML_ESCAPE, '', 1)
+
+ self.before = '%s%s' % (self.spaces, text)
if self.children:
self.before += self.render_newlines()
else:
@@ -449,8 +454,8 @@ class FilterNode(HamlNode):
def add_node(self, node):
self.add_child(node)
- def should_treat_children_as_multiline(self):
- return False
+ def inside_filter_node(self):
+ return True
def _render_children_as_plain_text(self,remove_indentation=True):
if self.children:
diff --git a/hamlpy/test/hamlpy_test.py b/hamlpy/test/hamlpy_test.py
index c96b370..84d6122 100755
--- a/hamlpy/test/hamlpy_test.py
+++ b/hamlpy/test/hamlpy_test.py
@@ -274,6 +274,13 @@ def test_plain_filter_with_no_children(self):
result = hamlParser.process(haml)
eq_(html, result)
+ def test_filters_render_escaped_backslash(self):
+ haml = ":plain\n \\Something"
+ html = "\\Something\n"
+ hamlParser = hamlpy.Compiler()
+ result = hamlParser.process(haml)
+ eq_(html, result)
+
def test_xml_namespaces(self):
haml = "%fb:tag\n content"
html = "\n content\n\n"
From f93245f04ab3b8d90954dd9443bec4b25d343749 Mon Sep 17 00:00:00 2001
From: Barry Coughlan
Date: Thu, 27 Sep 2012 12:32:33 +0100
Subject: [PATCH 04/48] Allow use of filters in inline variables
---
hamlpy/nodes.py | 4 ++--
hamlpy/test/hamlpy_test.py | 7 +++++++
2 files changed, 9 insertions(+), 2 deletions(-)
diff --git a/hamlpy/nodes.py b/hamlpy/nodes.py
index 763c117..150052f 100644
--- a/hamlpy/nodes.py
+++ b/hamlpy/nodes.py
@@ -22,8 +22,8 @@
VARIABLE = '='
TAG = '-'
-INLINE_VARIABLE = re.compile(r'(?blah\n"
From 6520df023caf97fcab9a2e28df473baa715e2bdb Mon Sep 17 00:00:00 2001
From: Richard Eames
Date: Tue, 9 Oct 2012 17:05:06 -0600
Subject: [PATCH 05/48] - Fix space after key in attribute syntax
---
hamlpy/elements.py | 34 +++++++++++++++++-----------------
1 file changed, 17 insertions(+), 17 deletions(-)
diff --git a/hamlpy/elements.py b/hamlpy/elements.py
index de4539c..204ffdf 100644
--- a/hamlpy/elements.py
+++ b/hamlpy/elements.py
@@ -21,16 +21,16 @@ class Element(object):
(?P/)?
(?P=)?
(?P[^\w\.#\{].*)?
- """, re.X|re.MULTILINE|re.DOTALL)
+ """, re.X | re.MULTILINE | re.DOTALL)
_ATTRIBUTE_KEY_REGEX = r'(?P[a-zA-Z_][a-zA-Z0-9_-]*)'
#Single and double quote regexes from: http://stackoverflow.com/a/5453821/281469
_SINGLE_QUOTE_STRING_LITERAL_REGEX = r"'([^'\\]*(?:\\.[^'\\]*)*)'"
_DOUBLE_QUOTE_STRING_LITERAL_REGEX = r'"([^"\\]*(?:\\.[^"\\]*)*)"'
- _ATTRIBUTE_VALUE_REGEX = r'(?P\d+|None(?![A-Za-z0-9_])|%s|%s)'%(_SINGLE_QUOTE_STRING_LITERAL_REGEX, _DOUBLE_QUOTE_STRING_LITERAL_REGEX)
+ _ATTRIBUTE_VALUE_REGEX = r'(?P\d+|None(?![A-Za-z0-9_])|%s|%s)' % (_SINGLE_QUOTE_STRING_LITERAL_REGEX, _DOUBLE_QUOTE_STRING_LITERAL_REGEX)
- RUBY_HAML_REGEX = re.compile(r'(:|\")%s(\"|) =>'%(_ATTRIBUTE_KEY_REGEX))
- ATTRIBUTE_REGEX = re.compile(r'(?P\{\s*|,\s*)%s:\s*%s'%(_ATTRIBUTE_KEY_REGEX, _ATTRIBUTE_VALUE_REGEX))
+ RUBY_HAML_REGEX = re.compile(r'(:|\")%s(\"|) =>' % (_ATTRIBUTE_KEY_REGEX))
+ ATTRIBUTE_REGEX = re.compile(r'(?P\{\s*|,\s*)%s\s*:\s*%s' % (_ATTRIBUTE_KEY_REGEX, _ATTRIBUTE_VALUE_REGEX))
DJANGO_VARIABLE_REGEX = re.compile(r'^\s*=\s(?P[a-zA-Z_][a-zA-Z0-9._-]*)\s*$')
@@ -65,7 +65,7 @@ def _parse_class_from_attributes_dict(self):
if not isinstance(clazz, str):
clazz = ''
for one_class in self.attributes_dict.get('class'):
- clazz += ' '+one_class
+ clazz += ' ' + one_class
return clazz.strip()
def _parse_id(self, id_haml):
@@ -79,26 +79,26 @@ def _parse_id_dict(self, id_dict):
text = ''
id_dict = self.attributes_dict.get('id')
if isinstance(id_dict, str):
- text = '_'+id_dict
+ text = '_' + id_dict
else:
text = ''
for one_id in id_dict:
- text += '_'+one_id
+ text += '_' + one_id
return text
- def _escape_attribute_quotes(self,v):
+ def _escape_attribute_quotes(self, v):
'''
Escapes quotes with a backslash, except those inside a Django tag
'''
- escaped=[]
+ escaped = []
inside_tag = False
for i, _ in enumerate(v):
- if v[i:i+2] == '{%':
- inside_tag=True
- elif v[i:i+2] == '%}':
- inside_tag=False
+ if v[i:i + 2] == '{%':
+ inside_tag = True
+ elif v[i:i + 2] == '%}':
+ inside_tag = False
- if v[i]=="'" and not inside_tag:
+ if v[i] == "'" and not inside_tag:
escaped.append('\\')
escaped.append(v[i])
@@ -113,7 +113,7 @@ def _parse_attribute_dictionary(self, attribute_dict_string):
# converting all allowed attributes to python dictionary style
# Replace Ruby-style HAML with Python style
- attribute_dict_string = re.sub(self.RUBY_HAML_REGEX, '"\g":',attribute_dict_string)
+ attribute_dict_string = re.sub(self.RUBY_HAML_REGEX, '"\g":', attribute_dict_string)
# Put double quotes around key
attribute_dict_string = re.sub(self.ATTRIBUTE_REGEX, '\g"\g":\g', attribute_dict_string)
# Parse string as dictionary
@@ -129,7 +129,7 @@ def _parse_attribute_dictionary(self, attribute_dict_string):
v = re.sub(self.DJANGO_VARIABLE_REGEX, '{{\g}}', attributes_dict[k])
if v != attributes_dict[k]:
sys.stderr.write("\n---------------------\nDEPRECATION WARNING: %s" % self.haml.lstrip() + \
- "\nThe Django attribute variable feature is deprecated and may be removed in future versions." +
+ "\nThe Django attribute variable feature is deprecated and may be removed in future versions." +
"\nPlease use inline variables ={...} instead.\n-------------------\n")
attributes_dict[k] = v
@@ -137,7 +137,7 @@ def _parse_attribute_dictionary(self, attribute_dict_string):
self.attributes += "%s='%s' " % (k, self._escape_attribute_quotes(v))
self.attributes = self.attributes.strip()
except Exception, e:
- raise Exception('failed to decode: %s'%attribute_dict_string)
+ raise Exception('failed to decode: %s' % attribute_dict_string)
#raise Exception('failed to decode: %s. Details: %s'%(attribute_dict_string, e))
return attributes_dict
From 71aea50bac44e3aa9dbea0e4c271393aa1b05b2c Mon Sep 17 00:00:00 2001
From: Richard Eames
Date: Tue, 9 Oct 2012 17:49:32 -0600
Subject: [PATCH 06/48] - Fix the nuke whitespace template tests
---
hamlpy/test/template_compare_test.py | 40 ++++++++++++++--------------
1 file changed, 20 insertions(+), 20 deletions(-)
diff --git a/hamlpy/test/template_compare_test.py b/hamlpy/test/template_compare_test.py
index 23ad78d..0d77f9f 100644
--- a/hamlpy/test/template_compare_test.py
+++ b/hamlpy/test/template_compare_test.py
@@ -6,10 +6,10 @@
class TestTemplateCompare(unittest.TestCase):
def test_nuke_inner_whitespace(self):
- self._compare_test_files('nukeInnerWhitespace')
+ self._compare_test_files('nukeInnerWhiteSpace')
def test_nuke_outer_whitespace(self):
- self._compare_test_files('nukeOuterWhitespace')
+ self._compare_test_files('nukeOuterWhiteSpace')
def test_comparing_simple_templates(self):
self._compare_test_files('simple')
@@ -57,52 +57,52 @@ def test_whitespace_preservation(self):
self._compare_test_files('whitespacePreservation')
def _print_diff(self, s1, s2):
- if len(s1)>len(s2):
- shorter=s2
+ if len(s1) > len(s2):
+ shorter = s2
else:
- shorter=s1
+ shorter = s1
- line=1
- col=1
+ line = 1
+ col = 1
for i, _ in enumerate(shorter):
- if len(shorter) <= i+1:
+ if len(shorter) <= i + 1:
print 'Ran out of characters to compare!'
- print 'Actual len=%d'%len(s1)
- print 'Expected len=%d'%len(s2)
+ print 'Actual len=%d' % len(s1)
+ print 'Expected len=%d' % len(s2)
break
if s1[i] != s2[i]:
print 'Difference begins at line', line, 'column', col
- actual_line = s1.splitlines()[line-1]
- expected_line = s2.splitlines()[line-1]
+ actual_line = s1.splitlines()[line - 1]
+ expected_line = s2.splitlines()[line - 1]
print 'HTML (actual, len=%2d) : %s' % (len(actual_line), actual_line)
print 'HTML (expected, len=%2d) : %s' % (len(expected_line), expected_line)
print 'Character code (actual) : %d (%s)' % (ord(s1[i]), s1[i])
print 'Character code (expected): %d (%s)' % (ord(s2[i]), s2[i])
break
- if shorter[i]=='\n':
+ if shorter[i] == '\n':
line += 1
- col=1
+ col = 1
else:
- col+=1
+ col += 1
else:
print "No Difference Found"
def _compare_test_files(self, name):
- haml_lines = codecs.open('templates/'+name+'.hamlpy', encoding='utf-8').readlines()
- html = open('templates/'+name+'.html').read()
+ haml_lines = codecs.open('templates/' + name + '.hamlpy', encoding = 'utf-8').readlines()
+ html = open('templates/' + name + '.html').read()
haml_compiler = hamlpy.Compiler()
parsed = haml_compiler.process_lines(haml_lines)
# Ignore line ending differences
- parsed=parsed.replace('\r','')
- html=html.replace('\r','')
+ parsed = parsed.replace('\r', '')
+ html = html.replace('\r', '')
if parsed != html:
print '\nHTML (actual): '
- print '\n'.join(["%d. %s" % (i+1, l) for i, l in enumerate(parsed.split('\n')) ])
+ print '\n'.join(["%d. %s" % (i + 1, l) for i, l in enumerate(parsed.split('\n')) ])
self._print_diff(parsed, html)
eq_(parsed, html)
From 358b21e553e30453a6018343a17d18ad125e6461 Mon Sep 17 00:00:00 2001
From: Richard Eames
Date: Tue, 9 Oct 2012 17:55:43 -0600
Subject: [PATCH 07/48] - Add a test for whitespace after attribute key
---
hamlpy/test/regression.py | 8 +++++++-
1 file changed, 7 insertions(+), 1 deletion(-)
diff --git a/hamlpy/test/regression.py b/hamlpy/test/regression.py
index 5a4bff6..3309fc6 100644
--- a/hamlpy/test/regression.py
+++ b/hamlpy/test/regression.py
@@ -16,4 +16,10 @@ def test_haml_comment_nodes_dont_post_render_children(self):
hamlParser = hamlpy.Compiler()
result = hamlParser.process(haml)
eq_(html, result.strip())
-
\ No newline at end of file
+
+ def test_whitespace_after_attribute_key(self):
+ haml = '%form{id : "myform"}'
+ html = ""
+ hamlParser = hamlpy.Compiler()
+ result = hamlParser.process(haml)
+ eq_(html, result.strip())
From 6fb7fe118be3baee3f7b10a8de93154486061536 Mon Sep 17 00:00:00 2001
From: Richard Eames
Date: Fri, 19 Oct 2012 16:39:03 -0600
Subject: [PATCH 08/48] - Added options to hamlpy_watcher
---
hamlpy/hamlpy_watcher.py | 81 +++++++++++++++++++++++++++-------------
1 file changed, 56 insertions(+), 25 deletions(-)
diff --git a/hamlpy/hamlpy_watcher.py b/hamlpy/hamlpy_watcher.py
index add5f3c..dd5bd9b 100644
--- a/hamlpy/hamlpy_watcher.py
+++ b/hamlpy/hamlpy_watcher.py
@@ -4,6 +4,7 @@
# Watch a folder for files with the given extensions and call the HamlPy
# compiler if the modified time has changed since the last check.
from time import gmtime, strftime
+import argparse
import sys
import codecs
import os
@@ -11,37 +12,66 @@
import time
import hamlpy
-CHECK_INTERVAL = 3 # in seconds
-DEBUG = False # print file paths when a file is compiled
+try:
+ str = unicode
+except NameError:
+ pass
+
+class Options(object):
+ CHECK_INTERVAL = 3 # in seconds
+ DEBUG = False # print file paths when a file is compiled
+ VERBOSE = False
+ OUTPUT_EXT = '.html'
# dict of compiled files [fullpath : timestamp]
compiled = dict()
+
+arg_parser = argparse.ArgumentParser()
+arg_parser.add_argument('-v', '--verbose', help = 'Display verbose output', action = 'store_true')
+arg_parser.add_argument('-i', '--input-extension', metavar = 'EXT', default = '.hamlpy', help = 'The file extensions to look for', type = str, nargs = '+')
+arg_parser.add_argument('-ext', '--extension', metavar = 'EXT', default = Options.OUTPUT_EXT, help = 'The output file extension. Default is .html', type = str)
+arg_parser.add_argument('-r', '--refresh', metavar = 'S', default = Options.CHECK_INTERVAL, help = 'Refresh interval for files. Default is {} seconds'.format(Options.CHECK_INTERVAL), type = int)
+arg_parser.add_argument('input_dir', help = 'Folder to watch', type = str)
+arg_parser.add_argument('output_dir', help = 'Destination folder', type = str, nargs = '?')
+
def watched_extension(extension):
"""Return True if the given extension is one of the watched extensions"""
for ext in hamlpy.VALID_EXTENSIONS:
- if extension.endswith('.'+ext):
+ if extension.endswith('.' + ext):
return True
return False
def watch_folder():
"""Main entry point. Expects one or two arguments (the watch folder + optional destination folder)."""
- argv_len = len(sys.argv)
- if argv_len in (2, 3):
- folder = os.path.realpath(sys.argv[1])
- destination = os.path.realpath(argv_len == 3 and os.path.realpath(sys.argv[2]) or folder)
-
- print "Watching %s at refresh interval %s seconds" % (folder,CHECK_INTERVAL)
- while True:
- try:
- _watch_folder(folder, destination)
- time.sleep(CHECK_INTERVAL)
- except KeyboardInterrupt:
- # allow graceful exit (no stacktrace output)
- sys.exit(0)
- pass
+ argv = sys.argv[1:] if len(sys.argv) > 1 else []
+ args = arg_parser.parse_args(sys.argv[1:])
+
+ input_folder = os.path.realpath(args.input_dir)
+ if not args.output_dir:
+ output_folder = input_folder
else:
- print "Usage: hamlpy-watcher.py [destination_folder]"
+ output_folder = os.path.realpath(args.output_dir)
+
+ if args.verbose:
+ Options.VERBOSE = True
+ print "Watching {} at refresh interval {} seconds".format(input_folder, args.refresh)
+
+ if args.extension:
+ Options.OUTPUT_EXT = args.extension
+
+ print args
+
+ if args.input_extension:
+ hamlpy.VALID_EXTENSIONS += args.input_extension
+
+ while True:
+ try:
+ _watch_folder(input_folder, output_folder)
+ time.sleep(args.refresh)
+ except KeyboardInterrupt:
+ # allow graceful exit (no stacktrace output)
+ sys.exit(0)
def _watch_folder(folder, destination):
"""Compares "modified" timestamps against the "compiled" dict, calls compiler
@@ -53,12 +83,12 @@ def _watch_folder(folder, destination):
fullpath = os.path.join(dirpath, filename)
subfolder = os.path.relpath(dirpath, folder)
mtime = os.stat(fullpath).st_mtime
-
+
# Create subfolders in target directory if they don't exist
compiled_folder = os.path.join(destination, subfolder)
if not os.path.exists(compiled_folder):
os.makedirs(compiled_folder)
-
+
compiled_path = _compiled_path(compiled_folder, filename)
if (not fullpath in compiled or
compiled[fullpath] < mtime or
@@ -67,18 +97,19 @@ def _watch_folder(folder, destination):
compiled[fullpath] = mtime
def _compiled_path(destination, filename):
- return os.path.join(destination, filename[:filename.rfind('.')] + '.html')
+ return os.path.join(destination, filename[:filename.rfind('.')] + Options.OUTPUT_EXT)
def compile_file(fullpath, outfile_name):
"""Calls HamlPy compiler."""
- print '%s %s -> %s' % ( strftime("%H:%M:%S", gmtime()), fullpath, outfile_name )
+ if Options.VERBOSE:
+ print '%s %s -> %s' % (strftime("%H:%M:%S", gmtime()), fullpath, outfile_name)
try:
- if DEBUG:
+ if Options.DEBUG:
print "Compiling %s -> %s" % (fullpath, outfile_name)
- haml_lines = codecs.open(fullpath, 'r', encoding='utf-8').read().splitlines()
+ haml_lines = codecs.open(fullpath, 'r', encoding = 'utf-8').read().splitlines()
compiler = hamlpy.Compiler()
output = compiler.process_lines(haml_lines)
- outfile = codecs.open(outfile_name, 'w', encoding='utf-8')
+ outfile = codecs.open(outfile_name, 'w', encoding = 'utf-8')
outfile.write(output)
except Exception, e:
# import traceback
From cb42cd3bad8b87ea429be9caa12ec8540b7e700c Mon Sep 17 00:00:00 2001
From: Richard Eames
Date: Fri, 19 Oct 2012 16:41:24 -0600
Subject: [PATCH 09/48] - Update readme
---
readme.md | 16 +++++++++++++++-
1 file changed, 15 insertions(+), 1 deletion(-)
diff --git a/readme.md b/readme.md
index c91fe71..52ea81f 100644
--- a/readme.md
+++ b/readme.md
@@ -90,7 +90,21 @@ For caching, just add `django.template.loaders.cached.Loader` to your TEMPLATE_L
HamlPy can also be used as a stand-alone program. There is a script which will watch for changed hamlpy extensions and regenerate the html as they are edited:
- hamlpy-watcher [destination_folder]
+ hamlpy-watcher [-h] [-v] [-i EXT [EXT ...]] [-ext EXT] [-r S] input_dir [output_dir]
+
+ positional arguments:
+ input_dir Folder to watch
+ output_dir Destination folder
+
+ optional arguments:
+ -h, --help show this help message and exit
+ -v, --verbose Display verbose output
+ -i EXT [EXT ...], --input-extension EXT [EXT ...]
+ The file extensions to look for
+ -ext EXT, --extension EXT
+ The output file extension. Default is .html
+ -r S, --refresh S Refresh interval for files. Default is 3 seconds
+
Or to simply convert a file and output the result to your console:
From 7128e03ac24d681a5b9413636103999d1e5d41d9 Mon Sep 17 00:00:00 2001
From: Richard Eames
Date: Fri, 19 Oct 2012 16:51:45 -0600
Subject: [PATCH 10/48] - Remove debugging print
---
hamlpy/hamlpy_watcher.py | 1 -
1 file changed, 1 deletion(-)
diff --git a/hamlpy/hamlpy_watcher.py b/hamlpy/hamlpy_watcher.py
index dd5bd9b..7b9f308 100644
--- a/hamlpy/hamlpy_watcher.py
+++ b/hamlpy/hamlpy_watcher.py
@@ -60,7 +60,6 @@ def watch_folder():
if args.extension:
Options.OUTPUT_EXT = args.extension
- print args
if args.input_extension:
hamlpy.VALID_EXTENSIONS += args.input_extension
From 305115b9a645d6f1acd179a6efeb5bad67768275 Mon Sep 17 00:00:00 2001
From: Richard Eames
Date: Fri, 19 Oct 2012 17:27:21 -0600
Subject: [PATCH 11/48] - Allow custom self closing tags for templating
languages like jinja that use "macro" and "call"
---
hamlpy/hamlpy_watcher.py | 16 +++++++++++++++-
1 file changed, 15 insertions(+), 1 deletion(-)
diff --git a/hamlpy/hamlpy_watcher.py b/hamlpy/hamlpy_watcher.py
index 7b9f308..617d645 100644
--- a/hamlpy/hamlpy_watcher.py
+++ b/hamlpy/hamlpy_watcher.py
@@ -11,6 +11,7 @@
import os.path
import time
import hamlpy
+import nodes as hamlpynodes
try:
str = unicode
@@ -26,6 +27,16 @@ class Options(object):
# dict of compiled files [fullpath : timestamp]
compiled = dict()
+class StoreNameValueTagPair(argparse.Action):
+ def __call__(self, parser, namespace, values, option_string = None):
+ tags = getattr(namespace, 'tags', {})
+ if tags is None:
+ tags = {}
+ for item in values:
+ n, v = item.split(':')
+ tags[n] = v
+
+ setattr(namespace, 'tags', tags)
arg_parser = argparse.ArgumentParser()
arg_parser.add_argument('-v', '--verbose', help = 'Display verbose output', action = 'store_true')
@@ -34,6 +45,7 @@ class Options(object):
arg_parser.add_argument('-r', '--refresh', metavar = 'S', default = Options.CHECK_INTERVAL, help = 'Refresh interval for files. Default is {} seconds'.format(Options.CHECK_INTERVAL), type = int)
arg_parser.add_argument('input_dir', help = 'Folder to watch', type = str)
arg_parser.add_argument('output_dir', help = 'Destination folder', type = str, nargs = '?')
+arg_parser.add_argument('--tag', help = 'Add self closing tag. eg. --tag macro:endmacro', type = str, nargs = 1, action = StoreNameValueTagPair)
def watched_extension(extension):
"""Return True if the given extension is one of the watched extensions"""
@@ -59,7 +71,9 @@ def watch_folder():
if args.extension:
Options.OUTPUT_EXT = args.extension
-
+
+ if args.tags:
+ hamlpynodes.TagNode.self_closing.update(args.tags)
if args.input_extension:
hamlpy.VALID_EXTENSIONS += args.input_extension
From f6a788e65c9588a8a0a414eee7f1f0248535e232 Mon Sep 17 00:00:00 2001
From: Richard Eames
Date: Thu, 25 Oct 2012 17:19:28 -0600
Subject: [PATCH 12/48] - Add a new line after the closing conditional comment
---
hamlpy/nodes.py | 94 +++++++++----------
hamlpy/test/regression.py | 7 ++
.../test/templates/nukeOuterWhiteSpace.html | 3 +-
3 files changed, 56 insertions(+), 48 deletions(-)
diff --git a/hamlpy/nodes.py b/hamlpy/nodes.py
index 150052f..de745fe 100644
--- a/hamlpy/nodes.py
+++ b/hamlpy/nodes.py
@@ -42,7 +42,7 @@
def create_node(haml_line):
stripped_line = haml_line.strip()
- if len(stripped_line)==0:
+ if len(stripped_line) == 0:
return None
if re.match(INLINE_VARIABLE, stripped_line) or re.match(ESCAPED_INLINE_VARIABLE, stripped_line):
@@ -105,21 +105,21 @@ def create_node(haml_line):
class TreeNode(object):
''' Generic parent/child tree class'''
def __init__(self):
- self.parent=None
- self.children=[]
+ self.parent = None
+ self.children = []
def left_sibling(self):
- siblings=self.parent.children
- index=siblings.index(self)
- return siblings[index-1] if index>0 else None
+ siblings = self.parent.children
+ index = siblings.index(self)
+ return siblings[index - 1] if index > 0 else None
def right_sibling(self):
- siblings=self.parent.children
- index=siblings.index(self)
- return siblings[index+1] if index\n"
- self.before=''
+ self.before = ''
# Rendered text at end of node, e.g. "\n
"
- self.after=''
+ self.after = ''
# Indicates that a node does not render anything (for whitespace removal)
- self.empty_node=False
+ self.empty_node = False
def render(self):
# Render (sets self.before and self.after)
@@ -144,7 +144,7 @@ def render(self):
return self._generate_html()
def render_newlines(self):
- return '\n'*(self.newlines+1)
+ return '\n' * (self.newlines + 1)
def parent_of(self, node):
if (self._should_go_inside_last_node(node)):
@@ -168,7 +168,7 @@ def _post_render(self):
child._post_render()
def _generate_html(self):
- output=[]
+ output = []
output.append(self.before)
for child in self.children:
output.append(child.before)
@@ -184,7 +184,7 @@ def add_node(self, node):
self.add_child(node)
def _should_go_inside_last_node(self, node):
- return len(self.children)>0 and (node.indentation > self.children[-1].indentation
+ return len(self.children) > 0 and (node.indentation > self.children[-1].indentation
or (node.indentation == self.children[-1].indentation and self.children[-1].should_contain(node)))
def should_contain(self, node):
@@ -194,9 +194,9 @@ def debug_tree(self):
return '\n'.join(self._debug_tree([self]))
def _debug_tree(self, nodes):
- output=[]
+ output = []
for n in nodes:
- output.append('%s%s' % (' '*(n.indentation+2), n))
+ output.append('%s%s' % (' ' * (n.indentation + 2), n))
if n.children:
output += self._debug_tree(n.children)
return output
@@ -225,7 +225,7 @@ class PlaintextNode(HamlNode):
def _render(self):
text = self.replace_inline_variables(self.haml)
# Remove escape character unless inside filter node
- if text and text[0]==HAML_ESCAPE and not self.inside_filter_node():
+ if text and text[0] == HAML_ESCAPE and not self.inside_filter_node():
text = text.replace(HAML_ESCAPE, '', 1)
self.before = '%s%s' % (self.spaces, text)
@@ -238,7 +238,7 @@ def _render(self):
class ElementNode(HamlNode):
'''Node which represents a HTML tag'''
def __init__(self, haml):
- HamlNode.__init__(self,haml)
+ HamlNode.__init__(self, haml)
self.django_variable = False
def _render(self):
@@ -254,7 +254,7 @@ def _render_before(self, element):
if element.id:
start.append(" id='%s'" % self.replace_inline_variables(element.id))
if element.classes:
- start.append(" class='%s'" % self.replace_inline_variables(element.classes) )
+ start.append(" class='%s'" % self.replace_inline_variables(element.classes))
if element.attributes:
start.append(' ' + self.replace_inline_variables(element.attributes))
@@ -264,7 +264,7 @@ def _render_before(self, element):
content = content.strip()
if element.self_close and not content:
- start.append(" />" )
+ start.append(" />")
elif content:
start.append(">%s" % (content))
elif self.children:
@@ -291,18 +291,18 @@ def _post_render(self):
self.after = self.after.lstrip()
if self.children:
- node=self
+ node = self
# If node renders nothing, do removal on its first child instead
- if node.children[0].empty_node==True:
- node=node.children[0]
+ if node.children[0].empty_node == True:
+ node = node.children[0]
if node.children:
- node.children[0].before=node.children[0].before.lstrip()
+ node.children[0].before = node.children[0].before.lstrip()
- node=self
- if node.children[-1].empty_node==True:
- node=node.children[-1]
+ node = self
+ if node.children[-1].empty_node == True:
+ node = node.children[-1]
if node.children:
- node.children[-1].after=node.children[-1].after.rstrip()
+ node.children[-1].after = node.children[-1].after.rstrip()
# Outer whitespace removal
if self.element.nuke_outer_whitespace:
@@ -330,7 +330,7 @@ def _post_render(self):
super(ElementNode, self)._post_render()
def _render_inline_content(self, inline_content):
- if inline_content == None or len(inline_content)==0:
+ if inline_content == None or len(inline_content) == 0:
return None
if self.django_variable:
@@ -341,31 +341,31 @@ def _render_inline_content(self, inline_content):
class CommentNode(HamlNode):
def _render(self):
- self.after = "-->\n"
+ self.after = "-->\n"
if self.children:
- self.before =""
+ self.after = "\n"
self._render_children()
class DoctypeNode(HamlNode):
def _render(self):
doctype = self.haml.lstrip(DOCTYPE).strip()
- parts=doctype.split()
+ parts = doctype.split()
if parts and parts[0] == "XML":
encoding = parts[1] if len(parts) > 1 else 'utf-8'
self.before = "" % encoding
@@ -419,11 +419,11 @@ class TagNode(HamlNode):
'cache': 'endcache',
'localize': 'endlocalize',
'compress': 'endcompress'}
- may_contain = {'if':['else', 'elif'],
+ may_contain = {'if':['else', 'elif'],
'ifchanged':'else',
'ifequal':'else',
'ifnotequal':'else',
- 'for':'empty',
+ 'for':'empty',
'with':'with'}
def __init__(self, haml):
@@ -447,7 +447,7 @@ def _render(self):
self._render_children()
def should_contain(self, node):
- return isinstance(node,TagNode) and node.tag_name in self.may_contain.get(self.tag_name,'')
+ return isinstance(node, TagNode) and node.tag_name in self.may_contain.get(self.tag_name, '')
class FilterNode(HamlNode):
@@ -457,7 +457,7 @@ def add_node(self, node):
def inside_filter_node(self):
return True
- def _render_children_as_plain_text(self,remove_indentation=True):
+ def _render_children_as_plain_text(self, remove_indentation = True):
if self.children:
initial_indentation = len(self.children[0].spaces)
for child in self.children:
@@ -477,7 +477,7 @@ def _post_render(self):
class PlainFilterNode(FilterNode):
def __init__(self, haml):
FilterNode.__init__(self, haml)
- self.empty_node=True
+ self.empty_node = True
def _render(self):
if self.children:
@@ -515,19 +515,19 @@ class JavascriptFilterNode(FilterNode):
def _render(self):
self.before = '\n'
- self._render_children_as_plain_text(remove_indentation=False)
+ self._render_children_as_plain_text(remove_indentation = False)
class CoffeeScriptFilterNode(FilterNode):
def _render(self):
self.before = '\n'
- self._render_children_as_plain_text(remove_indentation=False)
+ self._render_children_as_plain_text(remove_indentation = False)
class CssFilterNode(FilterNode):
def _render(self):
self.before = '\n'
- self._render_children_as_plain_text(remove_indentation=False)
+ self._render_children_as_plain_text(remove_indentation = False)
class StylusFilterNode(FilterNode):
def _render(self):
@@ -539,7 +539,7 @@ class CDataFilterNode(FilterNode):
def _render(self):
self.before = self.spaces + '\n'
- self._render_children_as_plain_text(remove_indentation=False)
+ self._render_children_as_plain_text(remove_indentation = False)
class PygmentsFilterNode(FilterNode):
def _render(self):
diff --git a/hamlpy/test/regression.py b/hamlpy/test/regression.py
index 3309fc6..ded65c6 100644
--- a/hamlpy/test/regression.py
+++ b/hamlpy/test/regression.py
@@ -23,3 +23,10 @@ def test_whitespace_after_attribute_key(self):
hamlParser = hamlpy.Compiler()
result = hamlParser.process(haml)
eq_(html, result.strip())
+
+ def test_for_newline_after_conditional_comment(self):
+ haml = '/[if lte IE 7]\n\ttest\n#test'
+ haml = '\n'
+ hamlParser = hamlpy.Compiler()
+ result = hamlParser.process(haml)
+ eq_(html, result.strip())
diff --git a/hamlpy/test/templates/nukeOuterWhiteSpace.html b/hamlpy/test/templates/nukeOuterWhiteSpace.html
index c95a95a..171d1b3 100644
--- a/hamlpy/test/templates/nukeOuterWhiteSpace.html
+++ b/hamlpy/test/templates/nukeOuterWhiteSpace.html
@@ -13,7 +13,8 @@
+
+
\n'
self._render_children_as_plain_text(remove_indentation = False)
class CoffeeScriptFilterNode(FilterNode):
def _render(self):
- self.before = '\n'
self._render_children_as_plain_text(remove_indentation = False)
class CssFilterNode(FilterNode):
def _render(self):
- self.before = '\n'
self._render_children_as_plain_text(remove_indentation = False)
class StylusFilterNode(FilterNode):
def _render(self):
- self.before = '\n'
self._render_children_as_plain_text()
From 787ef4b23211dcf363550f97a4c32aa25fc1e3ba Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Micha=C5=82=20Bielawski?=
Date: Fri, 25 Jan 2013 09:31:40 +0100
Subject: [PATCH 28/48] Escaping attr_wrapper instead of hard-coded apostrophe.
---
hamlpy/elements.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/hamlpy/elements.py b/hamlpy/elements.py
index d1cb1d6..abff139 100644
--- a/hamlpy/elements.py
+++ b/hamlpy/elements.py
@@ -102,7 +102,7 @@ def _escape_attribute_quotes(self, v):
elif v[i:i + 2] == '%}':
inside_tag = False
- if v[i] == "'" and not inside_tag:
+ if v[i] == self.attr_wrapper and not inside_tag:
escaped.append('\\')
escaped.append(v[i])
From d1c055b03f6f08cc8ffb6e62e538fc6a1209b256 Mon Sep 17 00:00:00 2001
From: Richard Eames
Date: Mon, 4 Mar 2013 16:34:07 -0700
Subject: [PATCH 29/48] - Added a Jinja compatibility option for the watcher.
---
hamlpy/hamlpy_watcher.py | 19 +++++++++++++++++--
readme.md | 3 +++
2 files changed, 20 insertions(+), 2 deletions(-)
diff --git a/hamlpy/hamlpy_watcher.py b/hamlpy/hamlpy_watcher.py
index 084288b..87346dc 100644
--- a/hamlpy/hamlpy_watcher.py
+++ b/hamlpy/hamlpy_watcher.py
@@ -46,7 +46,8 @@ def __call__(self, parser, namespace, values, option_string = None):
arg_parser.add_argument('input_dir', help = 'Folder to watch', type = str)
arg_parser.add_argument('output_dir', help = 'Destination folder', type = str, nargs = '?')
arg_parser.add_argument('--tag', help = 'Add self closing tag. eg. --tag macro:endmacro', type = str, nargs = 1, action = StoreNameValueTagPair)
-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('--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('--jinja', help = 'Makes the necessary changes to be used with Jinja2', default = False, action = 'store_true')
def watched_extension(extension):
"""Return True if the given extension is one of the watched extensions"""
@@ -81,7 +82,21 @@ def watch_folder():
hamlpy.VALID_EXTENSIONS += args.input_extension
if args.attr_wrapper:
- compiler_args['attr_wrapper'] = args.attr_wrapper
+ compiler_args['attr_wrapper'] = args.attr_wrapper
+
+ if args.jinja:
+ for k in ('ifchanged', 'ifequal', 'ifnotequal', 'autoescape', 'blocktrans',
+ 'spaceless', 'comment', 'cache', 'localize', 'compress'):
+ del hamlpynodes.TagNode.self_closing[k]
+
+ hamlpynodes.TagNode.may_contain.pop(k, None)
+
+ hamlpynodes.TagNode.self_closing.update({
+ 'macro' : 'endmacro',
+ 'call' : 'endcall',
+ })
+
+ hamlpy.nodes.TagNode.may_contain['for'] = 'else'
while True:
try:
diff --git a/readme.md b/readme.md
index eb585a1..6355243 100644
--- a/readme.md
+++ b/readme.md
@@ -116,6 +116,7 @@ HamlPy can also be used as a stand-alone program. There is a script which will w
--tag TAG Add self closing tag. eg. --tag macro:endmacro
--attr-wrapper {",'} The character that should wrap element attributes.
This defaults to ' (an apostrophe).
+ --jinja Makes the necessary changes to be used with Jinja2
Or to simply convert a file and output the result to your console:
@@ -129,6 +130,8 @@ Optionally, `--attr-wrapper` can be specified:
hamlpy inputFile.haml --attr-wrapper='"'
+Using the `--jinja` compatibility option adds macro and call tags, and changes the `empty` node in the `for` tag to `else`.
+
For HamlPy developers, the `-d` switch can be used with `hamlpy` to debug the internal tree structure.
## Reference
From c873dd4b1e86160b6c5fc963fde7730ab674839d Mon Sep 17 00:00:00 2001
From: Richard Eames
Date: Mon, 4 Mar 2013 16:43:54 -0700
Subject: [PATCH 30/48] - Small fix to watcher option "tag"
---
hamlpy/hamlpy_watcher.py | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/hamlpy/hamlpy_watcher.py b/hamlpy/hamlpy_watcher.py
index 084288b..1ba8af2 100644
--- a/hamlpy/hamlpy_watcher.py
+++ b/hamlpy/hamlpy_watcher.py
@@ -46,7 +46,7 @@ def __call__(self, parser, namespace, values, option_string = None):
arg_parser.add_argument('input_dir', help = 'Folder to watch', type = str)
arg_parser.add_argument('output_dir', help = 'Destination folder', type = str, nargs = '?')
arg_parser.add_argument('--tag', help = 'Add self closing tag. eg. --tag macro:endmacro', type = str, nargs = 1, action = StoreNameValueTagPair)
-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('--attr-wrapper', dest = 'attr_wrapper', type = str, choices = ('"', "'"), default = "'", action = 'store', help = "The character that should wrap element attributes. This defaults to ' (an apostrophe).")
def watched_extension(extension):
"""Return True if the given extension is one of the watched extensions"""
@@ -74,7 +74,7 @@ def watch_folder():
if args.extension:
Options.OUTPUT_EXT = args.extension
- if args.tags:
+ if getattr(args, 'tags', False):
hamlpynodes.TagNode.self_closing.update(args.tags)
if args.input_extension:
From 954c81d25a0921594b25e2b9e37c8c6cf5f7e2cc Mon Sep 17 00:00:00 2001
From: Richard Eames
Date: Mon, 4 Mar 2013 16:56:09 -0700
Subject: [PATCH 31/48] - typo fix.
---
hamlpy/hamlpy_watcher.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/hamlpy/hamlpy_watcher.py b/hamlpy/hamlpy_watcher.py
index 87346dc..30a0c1c 100644
--- a/hamlpy/hamlpy_watcher.py
+++ b/hamlpy/hamlpy_watcher.py
@@ -96,7 +96,7 @@ def watch_folder():
'call' : 'endcall',
})
- hamlpy.nodes.TagNode.may_contain['for'] = 'else'
+ hamlpynodes.TagNode.may_contain['for'] = 'else'
while True:
try:
From 3859dc9ace8b5e78ad89b7994069fdb7f424da34 Mon Sep 17 00:00:00 2001
From: Jesse Miller
Date: Tue, 5 Mar 2013 19:39:16 -0800
Subject: [PATCH 32/48] Add tests around javascript filter with attr set
---
hamlpy/test/hamlpy_test.py | 7 ++++++-
hamlpy/test/templates/classIdMixtures.hamlpy | 2 +-
2 files changed, 7 insertions(+), 2 deletions(-)
diff --git a/hamlpy/test/hamlpy_test.py b/hamlpy/test/hamlpy_test.py
index a43a0e4..f0d050f 100755
--- a/hamlpy/test/hamlpy_test.py
+++ b/hamlpy/test/hamlpy_test.py
@@ -321,7 +321,8 @@ def test_attr_wrapper(self):
%html{'xmlns':'http://www.w3.org/1999/xhtml', 'xml:lang':'en', 'lang':'en'}
%body#main
%div.wrap
- %a{:href => '/'}"""
+ %a{:href => '/'}
+:javascript"""
hamlParser = hamlpy.Compiler(options_dict={'attr_wrapper': '"'})
result = hamlParser.process(haml)
self.assertEqual(result,
@@ -332,6 +333,10 @@ def test_attr_wrapper(self):