hamlet 0.2.1 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/README.md CHANGED
@@ -29,22 +29,32 @@ see, it is just HTML! Designers love Hamlet because it is just HTML! Closing tag
29
29
  You can see the [original hamlet templating langauge](http://www.yesodweb.com/book/templates) and the
30
30
  [javascript port](hamlet: https://github.com/gregwebs/hamlet.js).
31
31
 
32
- This Hamlet works on top of [slim](https://github.com/stonean/slim/). Please see [slim documentation](http://slim-lang.com). There is one important difference: hamlet always defers to HTML syntax. In slim you have:
32
+ This Hamlet works on top of [slim](https://github.com/stonean/slim/). Please take a look at the [slim documentation](http://slim-lang.com) if you are looking to see if a more advanced feature is supported.
33
33
 
34
- p data-attr=foo Text
34
+ ## Difference with Slim
35
+
36
+ The most important difference is that hamlet always uses angle brackets. Hamlet also does not require attributes to be quoted - unquoted is considered a normal html attribute value and quotes will be added. Hamlet also uses a '#' for code comments and the normal <!-- for HTML comments. Hamlet also uses different whitespace indicators - see the next section.
37
+
38
+ In Slim you have:
39
+
40
+ /! HTML comment
41
+ p data-attr="foo" Text
35
42
  | More Text
43
+ / Comment
36
44
 
37
45
  In hamlet you have:
38
46
 
47
+ <!-- HTML comment
39
48
  <p data-attr=foo>Text
40
49
  More Text
50
+ # Comment
41
51
 
42
52
  ## Whitespace
43
53
 
44
- This is currently a bit of a Slim Frankenstein, but I added the same syntax that hamlet.js uses to indicate whitespace: a closing bracket
54
+ I added the same syntax that hamlet.js uses to indicate whitespace: a closing bracket to the left and a code comment to the right. I will probably take out some of the slim white space techniques.
45
55
 
46
- <p> White space to the left
47
- > White space to the left again
56
+ <p> White space to the left # and to the right
57
+ > White space to the left again# None to the right
48
58
 
49
59
 
50
60
  ## Limitations
data/hamlet.gemspec CHANGED
@@ -4,7 +4,7 @@ require 'date'
4
4
 
5
5
  Gem::Specification.new do |s|
6
6
  s.name = 'hamlet'
7
- s.version = '0.2.1'
7
+ s.version = '0.3.0'
8
8
  s.date = Date.today.to_s
9
9
  s.authors = ['Greg Weber']
10
10
  s.email = ['greg@gregweber.info']
@@ -16,7 +16,7 @@ Gem::Specification.new do |s|
16
16
  s.rubyforge_project = s.name
17
17
 
18
18
  s.files = `git ls-files`.split("\n")
19
- s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
19
+ #s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
20
20
  s.require_paths = %w(lib)
21
21
 
22
22
  s.add_dependency('slim', ['~> 1.0.0'])
@@ -29,6 +29,9 @@ Gem::Specification.new do |s|
29
29
  s.add_development_dependency('creole', ['>= 0'])
30
30
  s.add_development_dependency('builder', ['>= 0'])
31
31
  s.add_development_dependency('pry', ['>= 0'])
32
+ if RUBY_VERSION =~ /1.9/
33
+ s.add_development_dependency('ruby-debug19', ['>= 0'])
34
+ end
32
35
 
33
36
  unless defined?(RUBY_ENGINE) && RUBY_ENGINE == 'rbx'
34
37
  s.add_development_dependency('rcov', ['>= 0'])
@@ -74,12 +74,6 @@ module ForkedSlim
74
74
  DELIMITER_REGEX = /\A[\(\[\{]/
75
75
  ATTR_NAME_REGEX = '\A\s*(\w[:\w-]*)'
76
76
 
77
- if RUBY_VERSION > '1.9'
78
- CLASS_ID_REGEX = /\A(#|\.)([\w\u00c0-\uFFFF][\w:\u00c0-\uFFFF-]*)/
79
- else
80
- CLASS_ID_REGEX = /\A(#|\.)(\w[\w:-]*)/
81
- end
82
-
83
77
  def reset(lines = nil, stacks = nil)
84
78
  # Since you can indent however you like in Slim, we need to keep a list
85
79
  # of how deeply indented you are. For instance, in a template like this:
data/lib/hamlet/parser.rb CHANGED
@@ -3,77 +3,114 @@ require 'hamlet/forked_slim_parser'
3
3
  # @api private
4
4
  module Hamlet
5
5
  class Parser < ForkedSlim::Parser
6
- private
6
+ if RUBY_VERSION > '1.9'
7
+ CLASS_ID_REGEX = /\A\s*(#|\.)([\w\u00c0-\uFFFF][\w:\u00c0-\uFFFF-]*)/
8
+ else
9
+ CLASS_ID_REGEX = /\A\s*(#|\.)(\w[\w:-]*)/
10
+ end
11
+
12
+ # Compile string to Temple expression
13
+ #
14
+ # @param [String] str Slim code
15
+ # @return [Array] Temple expression representing the code]]
16
+ def call(str)
17
+ # Set string encoding if option is set
18
+ if options[:encoding] && str.respond_to?(:encoding)
19
+ old = str.encoding
20
+ str = str.dup if str.frozen?
21
+ str.force_encoding(options[:encoding])
22
+ # Fall back to old encoding if new encoding is invalid
23
+ str.force_encoding(old_enc) unless str.valid_encoding?
24
+ end
25
+
26
+ result = [:multi]
27
+ reset(str.split($/), [result])
28
+
29
+ while @lines.first && @lines.first =~ /\A\s*\Z/
30
+ @stacks.last << [:newline]
31
+ next_line
32
+ end
33
+ if @lines.first and @lines.first =~ /\A<doctype\s+([^>]*)/i
34
+ if !$'.empty? and $'[0] !~ /\s*#/
35
+ fail("did not expect content after doctype")
36
+ end
37
+ @stacks.last << [:html, :doctype, $1]
38
+ next_line
39
+ end
40
+
41
+ parse_line while next_line
42
+
43
+ reset
44
+ result
45
+ end
46
+
47
+ private
7
48
 
8
49
  def parse_line_indicators
9
50
  case @line
10
- when /\A\//
11
- # Found a comment block.
12
- if @line =~ %r{\A/!( ?)(.*)\Z}
13
- # HTML comment
14
- block = [:multi]
15
- @stacks.last << [:html, :comment, block]
16
- @stacks << block
17
- @stacks.last << [:slim, :interpolate, $2] unless $2.empty?
18
- parse_text_block($2.empty? ? nil : @indents.last + $1.size + 2)
19
- elsif @line =~ %r{\A/\[\s*(.*?)\s*\]\s*\Z}
20
- # HTML conditional comment
21
- block = [:multi]
22
- @stacks.last << [:slim, :condcomment, $1, block]
23
- @stacks << block
24
- else
25
- # Slim comment
26
- parse_comment_block
27
- end
28
- when /\A-/
29
- # Found a code block.
30
- # We expect the line to be broken or the next line to be indented.
51
+ when /\A-/ # code block.
31
52
  block = [:multi]
32
53
  @line.slice!(0)
33
54
  @stacks.last << [:slim, :control, parse_broken_line, block]
34
55
  @stacks << block
35
- when /\A=/
36
- # Found an output block.
37
- # We expect the line to be broken or the next line to be indented.
56
+ when /\A=/ # output block.
38
57
  @line =~ /\A=(=?)('?)/
39
58
  @line = $'
40
59
  block = [:multi]
41
60
  @stacks.last << [:slim, :output, $1.empty?, parse_broken_line, block]
42
61
  @stacks.last << [:static, ' '] unless $2.empty?
43
62
  @stacks << block
44
- when /\A<(\w+):\s*\Z/
45
- # Embedded template detected. It is treated as block.
63
+ when /\A<(\w+):\s*\Z/ # Embedded template. It is treated as block.
46
64
  block = [:multi]
47
65
  @stacks.last << [:newline] << [:slim, :embedded, $1, block]
48
66
  @stacks << block
49
- parse_text_block
67
+ parse_text_block(nil, true)
50
68
  return # Don't append newline, this has already been done before
51
- when /\Adoctype\s+/i
52
- # Found doctype declaration
53
- @stacks.last << [:html, :doctype, $'.strip]
54
- when /\A<([#\.]|\w[:\w-]*)/
55
- # Found a HTML tag.
69
+ when /\A<([#\.]|\w[:\w-]*)/ # HTML tag.
56
70
  parse_tag($1)
57
- when /\A(?:>( *))?(.*)?\Z/
58
- # Found a text block.
59
- @stacks.last << [:slim, :interpolate, $1 ? $1 << $2 : $2] unless $2.empty?
60
- parse_text_block($2.empty? ? nil : @indents.last + $1.to_s.size)
71
+ when /\A<!--( ?)(.*)\Z/ # HTML comment
72
+ block = [:multi]
73
+ @stacks.last << [:html, :comment, block]
74
+ @stacks << block
75
+ @stacks.last << [:slim, :interpolate, $2] unless $2.empty?
76
+ parse_text_block($2.empty? ? nil : @indents.last + $1.size + 2)
77
+ when %r{\A#\[\s*(.*?)\s*\]\s*\Z} # HTML conditional comment
78
+ block = [:multi]
79
+ @stacks.last << [:slim, :condcomment, $1, block]
80
+ @stacks << block
81
+ when /\A(?:\s*>( *))?/ # text block.
82
+ @stacks.last << [:slim, :interpolate, $1 ? $1 << $' : $']
83
+ parse_text_block($'.empty? ? nil : @indents.last + $1.to_s.size)
61
84
  else
62
85
  syntax_error! 'Unknown line indicator'
63
86
  end
64
87
  @stacks.last << [:newline]
65
88
  end
66
89
 
67
- def parse_text_block(text_indent = nil)
90
+ def parse_text_block(text_indent = nil, special = nil)
68
91
  empty_lines = 0
92
+ multi_line = false
93
+ if special == :from_tag
94
+ multi_line = true
95
+ special = nil
96
+ end
97
+
98
+ first_line = true
99
+ close_bracket = false
69
100
  until @lines.empty?
70
- if @lines.first =~ /\A\s*\Z/
101
+ if @lines.first =~ /\A\s*>?\s*\Z/
71
102
  next_line
72
103
  @stacks.last << [:newline]
73
104
  empty_lines += 1 if text_indent
74
105
  else
75
106
  indent = get_indent(@lines.first)
76
107
  break if indent <= @indents.last
108
+ if @lines.first =~ /\A\s*>/
109
+ indent += 1 #$1.size if $1
110
+ close_bracket = true
111
+ else
112
+ close_bracket = false
113
+ end
77
114
 
78
115
  if empty_lines > 0
79
116
  @stacks.last << [:slim, :interpolate, "\n" * empty_lines]
@@ -85,16 +122,28 @@ module Hamlet
85
122
  # The text block lines must be at least indented
86
123
  # as deep as the first line.
87
124
  if text_indent && indent < text_indent
88
- @line.lstrip!
89
- syntax_error!('Unexpected text indentation')
125
+ # special case for a leading '>' being back 1 char
126
+ unless first_line && close_bracket && (text_indent - indent == 1)
127
+ @line.lstrip!
128
+ syntax_error!('Unexpected text indentation')
129
+ end
90
130
  end
91
131
 
92
132
  @line.slice!(0, text_indent || indent)
133
+ @line = $' if @line =~ /\A>/
134
+ # a code comment
135
+ if @line =~ /(\A|[^\\])#([^{]|\Z)/
136
+ @line = $` + $1
137
+ end
138
+ @stacks.last << [:newline] if multi_line && !special
93
139
  @stacks.last << [:slim, :interpolate, (text_indent ? "\n" : '') + @line] << [:newline]
94
140
 
95
141
  # The indentation of first line of the text block
96
142
  # determines the text base indentation.
97
143
  text_indent ||= indent
144
+
145
+ first_line = false
146
+ multi_line = true
98
147
  end
99
148
  end
100
149
  end
@@ -111,27 +160,25 @@ module Hamlet
111
160
  @stacks.last << tag
112
161
 
113
162
  case @line
114
- when /\A\s*>?=(=?)('?)/
115
- # Handle output code
163
+ when /\A=(=?)('?)/ # Handle output code
116
164
  block = [:multi]
117
165
  @line = $'
118
166
  content = [:slim, :output, $1 != '=', parse_broken_line, block]
119
167
  tag << content
120
168
  @stacks.last << [:static, ' '] unless $2.empty?
121
169
  @stacks << block
122
- when /\A\s*\//
123
- # Closed tag. Do nothing
124
- when /\A\s*>?\s*\Z/
170
+ when /\A\s*\Z/
125
171
  # Empty content
126
172
  content = [:multi]
127
173
  tag << content
128
174
  @stacks << content
129
- when /\A( ?)>?(.*)\Z/
130
- # Text content
131
- content = [:multi, [:slim, :interpolate, $2]]
175
+ when %r!\A/>!
176
+ # Do nothing for closing tag
177
+ else # Text content
178
+ content = [:multi, [:slim, :interpolate, @line]]
132
179
  tag << content
133
180
  @stacks << content
134
- parse_text_block(@orig_line.size - @line.size + $1.size)
181
+ parse_text_block(@orig_line.size - @line.size, :from_tag)
135
182
  end
136
183
  end
137
184
 
@@ -147,44 +194,45 @@ module Hamlet
147
194
  end
148
195
 
149
196
  # Check to see if there is a delimiter right after the tag name
150
- delimiter = nil
151
- if @line =~ DELIMITER_REGEX
152
- delimiter = DELIMITERS[$&]
153
- @line.slice!(0)
154
- end
197
+ delimiter = '>'
155
198
 
156
199
  orig_line = @orig_line
157
200
  lineno = @lineno
158
201
  while true
159
202
  # Parse attributes
160
- attr_regex = delimiter ? /#{ATTR_NAME_REGEX}(=|\s|(?=#{Regexp.escape delimiter}))/ : /#{ATTR_NAME_REGEX}=/
161
- while @line =~ attr_regex
162
- @line = $'
203
+ while @line =~ /#{ATTR_NAME_REGEX}\s*(=\s*)?/
163
204
  name = $1
164
- if delimiter && $2 != '='
205
+ @line = $'
206
+ if !$2
165
207
  attributes << [:slim, :attr, name, false, 'true']
166
208
  elsif @line =~ /\A["']/
167
209
  # Value is quoted (static)
168
210
  @line = $'
169
211
  attributes << [:html, :attr, name, [:slim, :interpolate, parse_quoted_attribute($&)]]
170
- else
171
- @line =~ /[^ >]+/
212
+ elsif @line =~ /\A[^ >]+/
172
213
  @line = $'
173
214
  attributes << [:html, :attr, name, [:slim, :interpolate, $&]]
174
215
  end
175
216
  end
176
217
 
177
- # No ending delimiter, attribute end
178
- break unless delimiter
218
+ @line.lstrip!
179
219
 
180
220
  # Find ending delimiter
181
- if @line =~ /\A\s*#{Regexp.escape delimiter}/
221
+ if @line =~ /\A(>|\Z)/
182
222
  @line = $'
183
223
  break
224
+ elsif @line =~ %r!\A/>!
225
+ # Do nothing for closing tag
226
+ # don't eat the line either, we check for it again
227
+ if not $'.empty? and $' !~ /\s*#/
228
+ syntax_error!("Did not expect any content after self closing tag",
229
+ :orig_line => orig_line,
230
+ :lineno => lineno,
231
+ :column => orig_line.size)
232
+ end
233
+ break
184
234
  end
185
235
 
186
- # Found something where an attribute should be
187
- @line.lstrip!
188
236
  syntax_error!('Expected attribute') unless @line.empty?
189
237
 
190
238
  # Attributes span multiple lines
@@ -19,7 +19,7 @@ p Test
19
19
  p Test
20
20
  }
21
21
  chain = proc do |engine|
22
- engine.before(Slim::Parser, :WrapInput) do |input|
22
+ engine.before(Hamlet::Parser, :WrapInput) do |input|
23
23
  "p Header\n#{input}\np Footer"
24
24
  end
25
25
  end
@@ -32,7 +32,7 @@ p Test
32
32
  p Test
33
33
  }
34
34
  chain = proc do |engine|
35
- engine.after(Slim::Parser, :ReplaceParsedExp) do |exp|
35
+ engine.after(Hamlet::Parser, :ReplaceParsedExp) do |exp|
36
36
  [:slim, :output, false, '1+1', [:multi]]
37
37
  end
38
38
  end
@@ -3,9 +3,9 @@ require 'helper'
3
3
  class TestSlimCodeBlocks < TestSlim
4
4
  def test_render_with_output_code_block
5
5
  source = %q{
6
- p
6
+ <p
7
7
  = hello_world "Hello Ruby!" do
8
- | Hello from within a block!
8
+ Hello from within a block!
9
9
  }
10
10
 
11
11
  assert_html '<p>Hello Ruby! Hello from within a block! Hello Ruby!</p>', source
@@ -13,7 +13,7 @@ p
13
13
 
14
14
  def test_render_with_output_code_within_block
15
15
  source = %q{
16
- p
16
+ <p
17
17
  = hello_world "Hello Ruby!" do
18
18
  = hello_world "Hello from within a block!"
19
19
  }
@@ -23,7 +23,7 @@ p
23
23
 
24
24
  def test_render_with_output_code_within_block_2
25
25
  source = %q{
26
- p
26
+ <p
27
27
  = hello_world "Hello Ruby!" do
28
28
  = hello_world "Hello from within a block!" do
29
29
  = hello_world "And another one!"
@@ -34,10 +34,10 @@ p
34
34
 
35
35
  def test_output_block_with_arguments
36
36
  source = %q{
37
- p
37
+ <p
38
38
  = define_macro :person do |first_name, last_name|
39
- .first_name = first_name
40
- .last_name = last_name
39
+ <.first_name>= first_name
40
+ <.last_name>= last_name
41
41
  == call_macro :person, 'John', 'Doe'
42
42
  == call_macro :person, 'Max', 'Mustermann'
43
43
  }
@@ -48,9 +48,9 @@ p
48
48
 
49
49
  def test_render_with_control_code_loop
50
50
  source = %q{
51
- p
51
+ <p
52
52
  - 3.times do
53
- | Hey!
53
+ Hey!
54
54
  }
55
55
 
56
56
  assert_html '<p>Hey!Hey!Hey!</p>', source
@@ -60,7 +60,7 @@ p
60
60
  source = %q{
61
61
  = hello_world "Hello Ruby!" do
62
62
  - if true
63
- | Hello from within a block!
63
+ Hello from within a block!
64
64
  }
65
65
 
66
66
  assert_html 'Hello Ruby! Hello from within a block! Hello Ruby!', source
@@ -3,7 +3,7 @@ require 'helper'
3
3
  class TestSlimCodeEscaping < TestSlim
4
4
  def test_escaping_evil_method
5
5
  source = %q{
6
- p = evil_method
6
+ <p>= evil_method
7
7
  }
8
8
 
9
9
  assert_html '<p>&lt;script&gt;do_something_evil();&lt;&#47;script&gt;</p>', source
@@ -11,7 +11,7 @@ p = evil_method
11
11
 
12
12
  def test_render_without_html_safe
13
13
  source = %q{
14
- p = "<strong>Hello World\\n, meet \\"Slim\\"</strong>."
14
+ <p>= "<strong>Hello World\\n, meet \\"Slim\\"</strong>."
15
15
  }
16
16
 
17
17
  assert_html "<p>&lt;strong&gt;Hello World\n, meet \&quot;Slim\&quot;&lt;&#47;strong&gt;.</p>", source
@@ -19,7 +19,7 @@ p = "<strong>Hello World\\n, meet \\"Slim\\"</strong>."
19
19
 
20
20
  def test_render_with_html_safe_false
21
21
  source = %q{
22
- p = HtmlUnsafeString.new("<strong>Hello World\\n, meet \\"Slim\\"</strong>.")
22
+ <p>= HtmlUnsafeString.new("<strong>Hello World\\n, meet \\"Slim\\"</strong>.")
23
23
  }
24
24
 
25
25
  assert_html "<p>&lt;strong&gt;Hello World\n, meet \&quot;Slim\&quot;&lt;&#47;strong&gt;.</p>", source, :use_html_safe => true
@@ -27,7 +27,7 @@ p = HtmlUnsafeString.new("<strong>Hello World\\n, meet \\"Slim\\"</strong>.")
27
27
 
28
28
  def test_render_with_html_safe_true
29
29
  source = %q{
30
- p = HtmlSafeString.new("<strong>Hello World\\n, meet \\"Slim\\"</strong>.")
30
+ <p>= HtmlSafeString.new("<strong>Hello World\\n, meet \\"Slim\\"</strong>.")
31
31
  }
32
32
 
33
33
  assert_html "<p><strong>Hello World\n, meet \"Slim\"</strong>.</p>", source, :use_html_safe => true