hamlet 0.2.1 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
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