curly-templates 0.9.1 → 0.10.0
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG.md +10 -0
- data/curly-templates.gemspec +4 -2
- data/lib/curly.rb +1 -1
- data/lib/curly/compiler.rb +72 -44
- data/lib/curly/scanner.rb +111 -0
- data/spec/compiler_spec.rb +16 -0
- data/spec/scanner_spec.rb +55 -0
- metadata +6 -3
data/CHANGELOG.md
CHANGED
@@ -1,5 +1,15 @@
|
|
1
1
|
### Unreleased
|
2
2
|
|
3
|
+
### Curly 0.10.0 (July 11, 2013)
|
4
|
+
|
5
|
+
* Allow comments in Curly templates using the `{{! ... }}` syntax:
|
6
|
+
|
7
|
+
```
|
8
|
+
{{! This is a comment }}
|
9
|
+
```
|
10
|
+
|
11
|
+
*Daniel Schierbeck*
|
12
|
+
|
3
13
|
### Curly 0.9.1 (June 20, 2013)
|
4
14
|
|
5
15
|
* Better error handling. If a presenter class cannot be found, we not raise a
|
data/curly-templates.gemspec
CHANGED
@@ -4,8 +4,8 @@ Gem::Specification.new do |s|
|
|
4
4
|
s.rubygems_version = '1.3.5'
|
5
5
|
|
6
6
|
s.name = 'curly-templates'
|
7
|
-
s.version = '0.
|
8
|
-
s.date = '2013-
|
7
|
+
s.version = '0.10.0'
|
8
|
+
s.date = '2013-07-11'
|
9
9
|
|
10
10
|
s.summary = "Free your views!"
|
11
11
|
s.description = "A view layer for your Rails apps that separates structure and logic."
|
@@ -41,6 +41,7 @@ Gem::Specification.new do |s|
|
|
41
41
|
lib/curly/invalid_reference.rb
|
42
42
|
lib/curly/presenter.rb
|
43
43
|
lib/curly/railtie.rb
|
44
|
+
lib/curly/scanner.rb
|
44
45
|
lib/curly/template_handler.rb
|
45
46
|
lib/generators/curly/controller/controller_generator.rb
|
46
47
|
lib/generators/curly/controller/templates/presenter.rb.erb
|
@@ -49,6 +50,7 @@ Gem::Specification.new do |s|
|
|
49
50
|
spec/compiler_spec.rb
|
50
51
|
spec/generators/controller_generator_spec.rb
|
51
52
|
spec/presenter_spec.rb
|
53
|
+
spec/scanner_spec.rb
|
52
54
|
spec/spec_helper.rb
|
53
55
|
spec/template_handler_spec.rb
|
54
56
|
]
|
data/lib/curly.rb
CHANGED
data/lib/curly/compiler.rb
CHANGED
@@ -1,64 +1,92 @@
|
|
1
|
+
require 'curly/scanner'
|
1
2
|
require 'curly/invalid_reference'
|
2
3
|
|
3
4
|
module Curly
|
5
|
+
|
6
|
+
# Compiles Curly templates into executable Ruby code.
|
7
|
+
#
|
8
|
+
# A template must be accompanied by a presenter class. This class defines the
|
9
|
+
# references that are valid within the template.
|
10
|
+
#
|
4
11
|
class Compiler
|
5
|
-
|
12
|
+
# Compiles a Curly template to Ruby code.
|
13
|
+
#
|
14
|
+
# template - The template String that should be compiled.
|
15
|
+
# presenter_class - The presenter Class.
|
16
|
+
#
|
17
|
+
# Returns a String containing the Ruby code.
|
18
|
+
def self.compile(template, presenter_class)
|
19
|
+
new(template, presenter_class).compile
|
20
|
+
end
|
6
21
|
|
7
|
-
|
22
|
+
# Whether the Curly template is valid. This includes whether all
|
23
|
+
# references are available on the presenter class.
|
24
|
+
#
|
25
|
+
# template - The template String that should be validated.
|
26
|
+
# presenter_class - The presenter Class.
|
27
|
+
#
|
28
|
+
# Returns true if the template is valid, false otherwise.
|
29
|
+
def self.valid?(template, presenter_class)
|
30
|
+
compile(template, presenter_class)
|
31
|
+
|
32
|
+
true
|
33
|
+
rescue InvalidReference
|
34
|
+
false
|
35
|
+
end
|
8
36
|
|
9
|
-
|
10
|
-
#
|
11
|
-
# template - The template String that should be compiled.
|
12
|
-
#
|
13
|
-
# Returns a String containing the Ruby code.
|
14
|
-
def compile(template, presenter_class)
|
15
|
-
if presenter_class.nil?
|
16
|
-
raise ArgumentError, "presenter class cannot be nil"
|
17
|
-
end
|
37
|
+
attr_reader :template, :presenter_class
|
18
38
|
|
19
|
-
|
20
|
-
|
39
|
+
def initialize(template, presenter_class)
|
40
|
+
@template, @presenter_class = template, presenter_class
|
41
|
+
end
|
21
42
|
|
22
|
-
|
43
|
+
def compile
|
44
|
+
if presenter_class.nil?
|
45
|
+
raise ArgumentError, "presenter class cannot be nil"
|
23
46
|
end
|
24
47
|
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
# presenter_class - The presenter Class.
|
30
|
-
#
|
31
|
-
# Returns true if the template is valid, false otherwise.
|
32
|
-
def valid?(template, presenter_class)
|
33
|
-
compile(template, presenter_class)
|
34
|
-
|
35
|
-
true
|
36
|
-
rescue InvalidReference
|
37
|
-
false
|
48
|
+
tokens = Scanner.scan(template)
|
49
|
+
|
50
|
+
parts = tokens.map do |type, value|
|
51
|
+
send("compile_#{type}", value)
|
38
52
|
end
|
39
53
|
|
40
|
-
|
54
|
+
parts.join(" << ")
|
55
|
+
end
|
41
56
|
|
42
|
-
|
43
|
-
method, argument = reference.split(".", 2)
|
57
|
+
private
|
44
58
|
|
45
|
-
|
46
|
-
|
47
|
-
end
|
59
|
+
def compile_reference(reference)
|
60
|
+
method, argument = reference.split(".", 2)
|
48
61
|
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
presenter.#{method}(#{argument.inspect}) {|*args| yield(*args) }
|
53
|
-
RUBY
|
54
|
-
else
|
55
|
-
code = <<-RUBY
|
56
|
-
presenter.#{method} {|*args| yield(*args) }
|
57
|
-
RUBY
|
58
|
-
end
|
62
|
+
unless presenter_class.method_available?(method.to_sym)
|
63
|
+
raise Curly::InvalidReference.new(method.to_sym)
|
64
|
+
end
|
59
65
|
|
60
|
-
|
66
|
+
if presenter_class.instance_method(method).arity == 1
|
67
|
+
# The method accepts a single argument -- pass it in.
|
68
|
+
code = <<-RUBY
|
69
|
+
presenter.#{method}(#{argument.inspect}) {|*args| yield(*args) }
|
70
|
+
RUBY
|
71
|
+
else
|
72
|
+
code = <<-RUBY
|
73
|
+
presenter.#{method} {|*args| yield(*args) }
|
74
|
+
RUBY
|
61
75
|
end
|
76
|
+
|
77
|
+
'ERB::Util.html_escape(%s)' % code.strip
|
78
|
+
end
|
79
|
+
|
80
|
+
def compile_text(text)
|
81
|
+
text.inspect
|
82
|
+
end
|
83
|
+
|
84
|
+
def compile_comment_line(comment)
|
85
|
+
"''" # Replace the content with an empty string.
|
86
|
+
end
|
87
|
+
|
88
|
+
def compile_comment(comment)
|
89
|
+
"''" # Replace the content with an empty string.
|
62
90
|
end
|
63
91
|
end
|
64
92
|
end
|
@@ -0,0 +1,111 @@
|
|
1
|
+
require 'strscan'
|
2
|
+
|
3
|
+
module Curly
|
4
|
+
|
5
|
+
# Scans Curly templates for tokens.
|
6
|
+
#
|
7
|
+
# The Scanner goes through the template piece by piece, extracting tokens
|
8
|
+
# until the end of the template is reached.
|
9
|
+
#
|
10
|
+
class Scanner
|
11
|
+
REFERENCE_REGEX = %r(\{\{[\w\.]+\}\})
|
12
|
+
COMMENT_REGEX = %r(\{\{!.*\}\})
|
13
|
+
COMMENT_LINE_REGEX = %r(\s*#{COMMENT_REGEX}\s*\n)
|
14
|
+
|
15
|
+
# Scans a Curly template for tokens.
|
16
|
+
#
|
17
|
+
# source - The String source of the template.
|
18
|
+
#
|
19
|
+
# Example
|
20
|
+
#
|
21
|
+
# Curly::Scanner.scan("hello {{name}}!")
|
22
|
+
# #=> [[:text, "hello "], [:reference, "name"], [:text, "!"]]
|
23
|
+
#
|
24
|
+
# Returns an Array of type/value pairs representing the tokens in the
|
25
|
+
# template.
|
26
|
+
def self.scan(source)
|
27
|
+
new(source).scan
|
28
|
+
end
|
29
|
+
|
30
|
+
def initialize(source)
|
31
|
+
@scanner = StringScanner.new(source)
|
32
|
+
end
|
33
|
+
|
34
|
+
def scan
|
35
|
+
tokens = []
|
36
|
+
tokens << scan_token until @scanner.eos?
|
37
|
+
tokens
|
38
|
+
end
|
39
|
+
|
40
|
+
private
|
41
|
+
|
42
|
+
# Scans the next token in the template.
|
43
|
+
#
|
44
|
+
# Returns a two-element Array, the first element being the Symbol type of
|
45
|
+
# the token and the second being the String value.
|
46
|
+
def scan_token
|
47
|
+
scan_reference ||
|
48
|
+
scan_comment_line ||
|
49
|
+
scan_comment ||
|
50
|
+
scan_text ||
|
51
|
+
scan_remainder
|
52
|
+
end
|
53
|
+
|
54
|
+
# Scans a reference token, if a reference is the next token in the template.
|
55
|
+
#
|
56
|
+
# Returns an Array representing the token, or nil if no reference token can
|
57
|
+
# be found at the current position.
|
58
|
+
def scan_reference
|
59
|
+
if value = @scanner.scan(REFERENCE_REGEX)
|
60
|
+
# Return the reference name excluding "{{" and "}}".
|
61
|
+
[:reference, value[2..-3]]
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
# Scans a comment line token, if a comment line is the next token in the
|
66
|
+
# template.
|
67
|
+
#
|
68
|
+
# Returns an Array representing the token, or nil if no comment line token
|
69
|
+
# can be found at the current position.
|
70
|
+
def scan_comment_line
|
71
|
+
if value = @scanner.scan(COMMENT_LINE_REGEX)
|
72
|
+
# Returns the comment excluding "{{!" and "}}".
|
73
|
+
[:comment_line, value[3..-4]]
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
# Scans a comment token, if a comment is the next token in the template.
|
78
|
+
#
|
79
|
+
# Returns an Array representing the token, or nil if no comment token can
|
80
|
+
# be found at the current position.
|
81
|
+
def scan_comment
|
82
|
+
if value = @scanner.scan(COMMENT_REGEX)
|
83
|
+
# Returns the comment excluding "{{!" and "}}".
|
84
|
+
[:comment, value[3..-3]]
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
# Scans a text token, if a text is the next token in the template.
|
89
|
+
#
|
90
|
+
# Returns an Array representing the token, or nil if no text token can
|
91
|
+
# be found at the current position.
|
92
|
+
def scan_text
|
93
|
+
if value = @scanner.scan_until(/\{\{/)
|
94
|
+
# Rewind the scanner until before the "{{"
|
95
|
+
@scanner.pos -= 2
|
96
|
+
|
97
|
+
# Return the text up until "{{".
|
98
|
+
[:text, value[0..-3]]
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
# Scans the remainder of the template and treats it as a text token.
|
103
|
+
#
|
104
|
+
# Returns an Array representing the token, or nil if no text is remaining.
|
105
|
+
def scan_remainder
|
106
|
+
if value = @scanner.scan(/.+/m)
|
107
|
+
[:text, value]
|
108
|
+
end
|
109
|
+
end
|
110
|
+
end
|
111
|
+
end
|
data/spec/compiler_spec.rb
CHANGED
@@ -94,6 +94,22 @@ describe Curly::Compiler do
|
|
94
94
|
presenter.stub(:dirty) { "<p>dirty</p>".html_safe }
|
95
95
|
evaluate("{{dirty}}").should == "<p>dirty</p>"
|
96
96
|
end
|
97
|
+
|
98
|
+
it "removes comments from the output" do
|
99
|
+
evaluate("HELO{{! I'm a comment, yo }}WORLD").should == "HELOWORLD"
|
100
|
+
end
|
101
|
+
|
102
|
+
it "removes comment lines from the output" do
|
103
|
+
evaluate(<<-CURLY.strip_heredoc).should == "HELO\nWORLD\n"
|
104
|
+
HELO
|
105
|
+
{{! I'm a comment }}
|
106
|
+
WORLD
|
107
|
+
CURLY
|
108
|
+
end
|
109
|
+
|
110
|
+
it "does not execute arbitrary Ruby code" do
|
111
|
+
evaluate('#{foo}').should == '#{foo}'
|
112
|
+
end
|
97
113
|
end
|
98
114
|
|
99
115
|
describe ".valid?" do
|
@@ -0,0 +1,55 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Curly::Scanner, ".scan" do
|
4
|
+
it "returns the tokens in the source" do
|
5
|
+
scan("foo {{bar}} baz").should == [
|
6
|
+
[:text, "foo "],
|
7
|
+
[:reference, "bar"],
|
8
|
+
[:text, " baz"]
|
9
|
+
]
|
10
|
+
end
|
11
|
+
|
12
|
+
it "scans parameterized references" do
|
13
|
+
scan("{{foo.bar}}").should == [
|
14
|
+
[:reference, "foo.bar"]
|
15
|
+
]
|
16
|
+
end
|
17
|
+
|
18
|
+
it "scans comments in the source" do
|
19
|
+
scan("foo {{!bar}} baz").should == [
|
20
|
+
[:text, "foo "],
|
21
|
+
[:comment, "bar"],
|
22
|
+
[:text, " baz"]
|
23
|
+
]
|
24
|
+
end
|
25
|
+
|
26
|
+
it "scans comment lines in the source" do
|
27
|
+
scan("foo\n{{!bar}}\nbaz").should == [
|
28
|
+
[:text, "foo\n"],
|
29
|
+
[:comment_line, "bar"],
|
30
|
+
[:text, "baz"]
|
31
|
+
]
|
32
|
+
end
|
33
|
+
|
34
|
+
it "scans to the end of the source" do
|
35
|
+
scan("foo\n").should == [
|
36
|
+
[:text, "foo\n"]
|
37
|
+
]
|
38
|
+
end
|
39
|
+
|
40
|
+
it "treats quotes as text" do
|
41
|
+
scan('"').should == [
|
42
|
+
[:text, '"']
|
43
|
+
]
|
44
|
+
end
|
45
|
+
|
46
|
+
it "treats Ruby interpolation as text" do
|
47
|
+
scan('#{foo}').should == [
|
48
|
+
[:text, '#{foo}']
|
49
|
+
]
|
50
|
+
end
|
51
|
+
|
52
|
+
def scan(source)
|
53
|
+
Curly::Scanner.scan(source)
|
54
|
+
end
|
55
|
+
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: curly-templates
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.10.0
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2013-
|
12
|
+
date: 2013-07-11 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: actionpack
|
@@ -122,6 +122,7 @@ files:
|
|
122
122
|
- lib/curly/invalid_reference.rb
|
123
123
|
- lib/curly/presenter.rb
|
124
124
|
- lib/curly/railtie.rb
|
125
|
+
- lib/curly/scanner.rb
|
125
126
|
- lib/curly/template_handler.rb
|
126
127
|
- lib/generators/curly/controller/controller_generator.rb
|
127
128
|
- lib/generators/curly/controller/templates/presenter.rb.erb
|
@@ -130,6 +131,7 @@ files:
|
|
130
131
|
- spec/compiler_spec.rb
|
131
132
|
- spec/generators/controller_generator_spec.rb
|
132
133
|
- spec/presenter_spec.rb
|
134
|
+
- spec/scanner_spec.rb
|
133
135
|
- spec/spec_helper.rb
|
134
136
|
- spec/template_handler_spec.rb
|
135
137
|
homepage: https://github.com/zendesk/curly
|
@@ -148,7 +150,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
148
150
|
version: '0'
|
149
151
|
segments:
|
150
152
|
- 0
|
151
|
-
hash:
|
153
|
+
hash: 4015061898851795676
|
152
154
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
153
155
|
none: false
|
154
156
|
requirements:
|
@@ -165,5 +167,6 @@ test_files:
|
|
165
167
|
- spec/compiler_spec.rb
|
166
168
|
- spec/generators/controller_generator_spec.rb
|
167
169
|
- spec/presenter_spec.rb
|
170
|
+
- spec/scanner_spec.rb
|
168
171
|
- spec/template_handler_spec.rb
|
169
172
|
has_rdoc:
|