Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merge recent changes from the master branch into element_parser branch #147

Open
wants to merge 73 commits into
base: element_parser
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
73 commits
Select commit Hold shift + click to select a range
fe287c8
Minor clarity change
Sep 5, 2012
1c36d67
roll version
jessemiller Sep 5, 2012
16be725
Don't remove backslash at start of lines inside filter nodes
Sep 13, 2012
f93245f
Allow use of filters in inline variables
Sep 27, 2012
6520df0
- Fix space after key in attribute syntax
richardasymmetric Oct 9, 2012
71aea50
- Fix the nuke whitespace template tests
richardasymmetric Oct 9, 2012
358b21e
- Add a test for whitespace after attribute key
richardasymmetric Oct 9, 2012
a5a377e
Merge pull request #101 from Naddiseo/key_space_fix
Oct 14, 2012
90be5af
Merge pull request #100 from Naddiseo/nuke_whitespace_test_fix
Oct 14, 2012
6fb7fe1
- Added options to hamlpy_watcher
richardasymmetric Oct 19, 2012
cb42cd3
- Update readme
richardasymmetric Oct 19, 2012
7128e03
- Remove debugging print
richardasymmetric Oct 19, 2012
305115b
- Allow custom self closing tags for templating languages like jinja …
richardasymmetric Oct 19, 2012
1007021
Merge pull request #102 from Naddiseo/watcher_options
Oct 22, 2012
f6a788e
- Add a new line after the closing conditional comment
richardasymmetric Oct 25, 2012
b750151
Merge pull request #104 from Naddiseo/newline_after_comment
Oct 27, 2012
9427bb6
Changed 'athelete' to 'athlete'.
caseycrites Nov 2, 2012
d9cf774
Merge pull request #105 from caseycrites/fix_athlete_typo
Nov 2, 2012
89e5212
Add --attr-wrapper option to command line and HAMLPY_ATTR_WRAPPER set…
avsd Nov 12, 2012
15bd2d4
Add --attr-wrapper option to watcher
avsd Nov 12, 2012
2a153af
Update docs about attr_wrapper
avsd Nov 12, 2012
fc3f480
Update docs about attr_wrapper in hamlpy command
avsd Nov 12, 2012
451beb1
Merge pull request #106 from chameleonpage/master
Nov 12, 2012
a205faa
added jinja2 extension
andreif Nov 13, 2012
39c1d47
Merge pull request #108 from andreif/add-jinja2-extension
Nov 16, 2012
0a5ed5f
- Fix for watcher options that broke in pull request #106
Naddiseo Dec 20, 2012
a37a974
Merge pull request #113 from Naddiseo/fix_pull_request_106
jessemiller Dec 21, 2012
fa66d21
Fix to #114 HamlPyExtension filename extension check invalid.
a-krebs Dec 26, 2012
15b7c90
Added tests to support pull request #115, which addresses issue #114.
a-krebs Dec 26, 2012
e56c81b
Merge pull request #115 from a-krebs/master
jessemiller Jan 1, 2013
a3b5273
Tests to demonstrate #116.
a-krebs Jan 4, 2013
e290f73
Fixed #116
a-krebs Jan 4, 2013
a90c37a
fix parser source string to unicode
fizista Jan 16, 2013
05ecfda
How to generate translation files.
fizista Jan 16, 2013
2da3c9e
fix readme
Jan 16, 2013
132dcae
Changed filters to use attr_wrapper instead of hard-coded apostrophe.
Jan 18, 2013
787ef4b
Escaping attr_wrapper instead of hard-coded apostrophe.
Jan 25, 2013
d1c055b
- Added a Jinja compatibility option for the watcher.
richardasymmetric Mar 4, 2013
c873dd4
- Small fix to watcher option "tag"
richardasymmetric Mar 4, 2013
954c81d
- typo fix.
richardasymmetric Mar 4, 2013
09984be
Merge pull request #117 from a-krebs/fix_116
jessemiller Mar 6, 2013
8d10605
Merge branch 'fixes' of https://github.com/D3X/HamlPy into D3X-fixes
jessemiller Mar 6, 2013
3859dc9
Add tests around javascript filter with attr set
jessemiller Mar 6, 2013
ac3d6f5
Merge branch 'django_translations' of https://github.com/fizista/Haml…
jessemiller Mar 6, 2013
6d95cc7
Merge pull request #129 from Naddiseo/fix_watcher_option_tags
jessemiller Mar 6, 2013
284faac
Added to test to confirm fixes issue #125
jessemiller Mar 6, 2013
1204b3a
Merge branch 'master' of github.com:jessemiller/HamlPy
jessemiller Mar 6, 2013
d434fd4
- Added a basic testcase and output.
Naddiseo Mar 6, 2013
1b08af8
Merge branch 'jinja_compat' of https://github.com/Naddiseo/HamlPy int…
jessemiller Mar 6, 2013
3f6b962
remove unused test templates
jessemiller Mar 6, 2013
bd5c044
Updated reference.md (:stylus, :coffeescript, :css)
V-Alexeev Mar 29, 2013
1bbb479
Merge pull request #130 from V-Alexeev/patch-1
jessemiller Mar 29, 2013
adc1840
Remove strict dependencies on django, pygments, markdown, jinja2
danring May 9, 2013
52ec4d3
Output local time instead of gmttime in hamlpy_watcher when in verbos…
JobJob May 10, 2013
f813582
Merge pull request #134 from JobJob/master
jessemiller May 11, 2013
32cd4d0
allow attributes to have any letter at all (not just ascii)
nsh-ableton May 29, 2013
6db210f
Merge pull request #133 from danring/master
jessemiller Jun 5, 2013
1dbce73
Merge pull request #135 from AbletonAG/permit_more_valid_attributes
jessemiller Jun 5, 2013
eb2e665
make the test of non-ascii filenames actually pass, by marking string…
nsh-ableton Jun 10, 2013
8b72bd0
Merge pull request #137 from jessemiller/actually_fix_test
noelbush-xx Jun 10, 2013
92021ae
Markdown filter: put back in trailing spaces to handle implicit <br>
boscoh Jun 14, 2013
beda2a0
Fixed the markdown unit test
boscoh Jun 17, 2013
f6a56cb
newline ambiguity resolved in reading raw_haml
boscoh Jun 21, 2013
7f702b7
- Added missing Jinja2 tag `raw`
Jun 24, 2013
b364bd2
Update reference.md
tsouvarev Jul 12, 2013
fe3ba8b
Merge pull request #138 from boscoh/markdown_br_fix
noelbush-xx Jul 29, 2013
fd3c93d
Modified templatize to ignore non-haml files
cordery Aug 23, 2013
62bc8cb
Modified templatize to ignore non-haml files (fix)
cordery Aug 23, 2013
f6d66fb
Merge pull request #141 from tsouvarev/patch-1
jessemiller Aug 28, 2013
ab51afb
Merge pull request #139 from Naddiseo/patch-1
jessemiller Aug 28, 2013
acb79e1
Merge pull request #144 from cordery/master
jessemiller Aug 28, 2013
6939896
merge master branch with element_parser
a1s Sep 3, 2013
7da6f02
Support trailing comma in attribute dictionaries
a1s Sep 4, 2013
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 1 addition & 4 deletions hamlpy/__init__.py
Original file line number Diff line number Diff line change
@@ -1,4 +1 @@
try:
import templatize
except ImportError:
pass
import templatize
44 changes: 22 additions & 22 deletions hamlpy/attribute_dict_parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,24 +7,24 @@
re_whitespace = re.compile(r'([ \t]+)')
re_leading_spaces = re.compile(r'^\s+', re.MULTILINE)
re_line = re.compile(r'.*')
re_sq= re.compile(r'(.*?)(?<!\\)(?:\')')
re_dq= re.compile(r'(.*?)(?<!\\)(?:")')
re_sq = re.compile(r'\'([^\'\\]|\\.)*\'')
re_dq = re.compile(r'\"([^\"\\]|\\.)*\"')

class AttributeParser:
"""Parses comma-separated HamlPy attribute values"""

def __init__(self, data, terminator):
self.terminator=terminator
self.s = data.lstrip()
self.length=len(self.s)
# Index of current character being read
self.ptr=1


def consume_whitespace(self, include_newlines=False):
"""Moves the pointer to the next non-whitespace character"""
whitespace = (' ', '\t', '\r', '\n') if include_newlines else (' ', '\t')

while self.ptr<self.length and self.s[self.ptr] in whitespace:
self.ptr+=1
return self.ptr
Expand All @@ -34,7 +34,8 @@ def consume_end_of_value(self):
self.consume_whitespace()
if self.s[self.ptr] != self.terminator:
if self.s[self.ptr] == ',':
self.ptr+=1
self.ptr += 1
self.consume_whitespace()
else:
raise Exception("Expected comma for end of value (after ...%s), but got '%s' instead" % (self.s[max(self.ptr-10,0):self.ptr], self.s[self.ptr]))

Expand All @@ -51,16 +52,17 @@ def read_until_unescaped_character(self, closing):
elif closing=='"':
r=re_dq
else:
r=re.compile(r'(.*?)(?<!\\)(?:%s)'%closing)
r = re.compile(r'%(c)s([^%(c)s\\]|\\.)*%(c)s' % dict(c=closing))

m=r.match(self.s, pos=self.ptr)
if m is None:
raise Exception ("Closing character not found")

value = m.group(1)
self.ptr+=len(value)+1

return value.replace('\\'+closing,closing)
value = m.group(0)
self.ptr += len(value)

# Return all values in unicode
return eval('u' + value)

def parse_value(self):
self.consume_whitespace()
Expand All @@ -69,11 +71,10 @@ def parse_value(self):
val=False
if self.s[self.ptr]==self.terminator:
return val

# String
if self.s[self.ptr] in ("'",'"'):
quote=self.s[self.ptr]
self.ptr += 1
val = self.read_until_unescaped_character(quote)
# Boolean Attributes
elif self.s[self.ptr:self.ptr+4] in ['none','None']:
Expand All @@ -90,7 +91,7 @@ def parse_value(self):
raise Exception("Failed to parse dictionary value beginning at: %s" % self.s[self.ptr:])

self.consume_end_of_value()

return val


Expand All @@ -104,7 +105,7 @@ class AttributeDictParser(AttributeParser):
def __init__(self, s):
AttributeParser.__init__(self, s, '}')
self.dict={}

def parse(self):
while self.ptr<self.length-1:
key = self.__parse_key()
Expand All @@ -126,19 +127,19 @@ def parse(self):

self.dict[key]=val
return self.dict

def __parse_haml(self):
def whitespace_length():
r = re_whitespace.match(self.s, pos=self.ptr)
return len(r.group(0))

initial_indentation=whitespace_length()
lines = []
while whitespace_length() >= initial_indentation:
line=re_line.match(self.s, pos=self.ptr).group(0)
lines.append(line)
self.ptr += len(line)+1

h=hamlpy.Compiler()
html = h.process_lines(lines)
return re.sub(re_leading_spaces, ' ', html).replace('\n', '').strip()
Expand All @@ -150,12 +151,11 @@ def __parse_key(self):

if self.s[self.ptr] == ':':
self.ptr+=1

# Consume opening quote
quote=None
if self.s[self.ptr] in ("'",'"'):
quote = self.s[self.ptr]
self.ptr += 1

# Extract key
if quote:
Expand Down Expand Up @@ -197,9 +197,9 @@ def parse(self):
lst.append(val)

self.ptr +=1

if self.terminator==')':
return tuple(lst)
else:
return lst

47 changes: 25 additions & 22 deletions hamlpy/elements.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

class Element(object):
"""contains the pieces of an element and can populate itself from haml element text"""

self_closing_tags = ('meta', 'img', 'link', 'br', 'hr', 'input', 'source', 'track', 'area', 'base', 'col', 'command', 'embed', 'keygen', 'param', 'wbr')

ELEMENT = '%'
Expand All @@ -20,11 +20,12 @@ class Element(object):
(?P<selfclose>/)?
(?P<django>=)?
(?P<inline>[^\w\.#\{].*)?
""", re.X|re.MULTILINE|re.DOTALL)
""", re.X | re.MULTILINE | re.DOTALL | re.UNICODE)


def __init__(self, haml):
def __init__(self, haml, attr_wrapper="'"):
self.haml = haml
self.attr_wrapper = attr_wrapper
self.tag = None
self.id = None
self.classes = None
Expand All @@ -35,7 +36,10 @@ def __init__(self, haml):
self.nuke_outer_whitespace = False
self.inline_content = ''
self._parse_haml()


def attr_wrap(self, value):
return '%s%s%s' % (self.attr_wrapper, value, self.attr_wrapper)

def _parse_haml(self):
split_tags = self.HAML_REGEX.search(self.haml).groupdict('')

Expand All @@ -46,7 +50,7 @@ def _parse_haml(self):
self.id = self._parse_id(split_tags.get('id'))
self.classes = self._parse_classes(split_tags.get('class'))
self.self_close = split_tags.get('selfclose') or self.tag in self.self_closing_tags

self.attributes = self._render_attributes(self.attributes_dict, self.id, self.classes)

self.nuke_inner_whitespace = split_tags.get('nuke_inner_whitespace') != ''
Expand All @@ -58,7 +62,7 @@ def _parse_classes(self, classes):
tag_classes = classes.lstrip(self.CLASS).replace('.', ' ')
dict_classes = self._parse_class_from_attributes_dict()
return ('%s %s' % (tag_classes, dict_classes)).strip()

def _parse_class_from_attributes_dict(self):
cla = self.attributes_dict.get('class', '')
if isinstance(cla, list) or isinstance(cla, tuple):
Expand All @@ -73,7 +77,7 @@ def _parse_id(self, id_haml):
id_text += '_'
id_text += self._parse_id_dict(self.attributes_dict['id'])
return id_text

def _parse_id_dict(self, id_dict):
id_dict = self.attributes_dict.get('id')

Expand All @@ -84,11 +88,11 @@ def _parse_id_dict(self, id_dict):

def _render_attributes(self, dict, id, classes):
attributes=[]

if len(id) > 0:
attributes.append("id='%s'" % self.id)
attributes.append("id=%s" % self.attr_wrap(self.id))
if len(classes) > 0:
attributes.append("class='%s'" % self.classes)
attributes.append("class=%s" % self.attr_wrap(self.classes))

for k, v in dict.items():
if k != 'id' and k != 'class':
Expand All @@ -97,24 +101,23 @@ def _render_attributes(self, dict, id, classes):
attributes.append( "%s" % (k,))
else:
value = self._escape_attribute_quotes(v)
attributes.append( "%s='%s'" % (k, value))
attributes.append( "%s=%s" % (k, self.attr_wrap(value)))

return ' '.join(attributes)


def _escape_attribute_quotes(self,v):

def _escape_attribute_quotes(self, v):
'''
Escapes single quotes with a backslash, except those inside a Django tag
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] == self.attr_wrapper and not inside_tag:
escaped.append('\\')

escaped.append(v[i])
Expand Down
42 changes: 42 additions & 0 deletions hamlpy/ext.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
# coding=utf-8
try:
import jinja2.ext
_jinja2_available = True
except ImportError, e:
_jinja2_available = False

import hamlpy
import os

HAML_FILE_NAME_EXTENSIONS = ['haml', 'hamlpy']


def clean_extension(file_ext):
if not isinstance(file_ext, basestring):
raise Exception('Wrong file extension format: %r' % file_ext)
if len(file_ext) > 1 and file_ext.startswith('.'):
file_ext = file_ext[1:]
return file_ext.lower().strip()


def get_file_extension(file_path):
file_ext = os.path.splitext(file_path)[1]
return clean_extension(file_ext)


def has_any_extension(file_path, extensions):
file_ext = get_file_extension(file_path)
return file_ext and extensions and file_ext in [clean_extension(e) for e in extensions]

if _jinja2_available:
class HamlPyExtension(jinja2.ext.Extension):

def preprocess(self, source, name, filename=None):
if name and has_any_extension(name, HAML_FILE_NAME_EXTENSIONS):
compiler = hamlpy.Compiler()
try:
return compiler.process(source)
except Exception as e:
raise jinja2.TemplateSyntaxError(e, 1, name=name, filename=filename)
else:
return source
40 changes: 27 additions & 13 deletions hamlpy/hamlpy.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,19 +5,25 @@
VALID_EXTENSIONS=['haml', 'hamlpy']

class Compiler:
def process(self, raw_text, options=None):

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

def process(self, raw_text):
split_text = raw_text.split('\n')
return self.process_lines(split_text, options)
return self.process_lines(split_text)

def process_lines(self, haml_lines, options=None):
root = RootNode()
def process_lines(self, haml_lines):
root = RootNode(**self.options_dict)
line_iter = iter(haml_lines)

haml_node=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

Expand All @@ -35,8 +41,8 @@ def process_lines(self, haml_lines, options=None):
haml_node = create_node(node_lines)
if haml_node:
root.add_node(haml_node)
if options and options.debug_tree:

if self.options_dict and self.options_dict.get('debug_tree'):
return root.debug_tree()
else:
return root.render()
Expand All @@ -45,19 +51,27 @@ def convert_files():
import codecs

parser = OptionParser()
parser.add_option("-d", "--debug-tree", dest="debug_tree",
action="store_true", help="Print the generated tree instead of the HTML")
parser.add_option(
"-d", "--debug-tree", dest="debug_tree",
action="store_true",
help="Print the generated tree instead of the HTML")
parser.add_option(
"--attr-wrapper", dest="attr_wrapper",
type="choice", choices=('"', "'"), default="'",
action="store",
help="The character that should wrap element attributes. "
"This defaults to ' (an apostrophe).")
(options, args) = parser.parse_args()

if len(args) < 1:
print "Specify the input file as the first argument."
else:
else:
infile = args[0]
haml_lines = codecs.open(infile, 'r', encoding='utf-8').read().splitlines()

compiler = Compiler()
output = compiler.process_lines(haml_lines, options=options)
compiler = Compiler(options.__dict__)
output = compiler.process_lines(haml_lines)

if len(args) == 2:
outfile = codecs.open(args[1], 'w', encoding='utf-8')
outfile.write(output)
Expand Down
Loading