curly-templates 0.12.0 → 1.0.0rc1
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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +2 -0
- data/CONTRIBUTING.md +17 -0
- data/README.md +39 -2
- data/curly-templates.gemspec +7 -2
- data/lib/curly.rb +1 -1
- data/lib/curly/compilation_error.rb +3 -1
- data/lib/curly/compiler.rb +52 -1
- data/lib/curly/error.rb +4 -0
- data/lib/curly/incomplete_block_error.rb +11 -0
- data/lib/curly/incorrect_ending_error.rb +11 -0
- data/lib/curly/invalid_block_error.rb +9 -0
- data/lib/curly/invalid_reference.rb +1 -1
- data/lib/curly/scanner.rb +30 -2
- data/lib/curly/syntax_error.rb +3 -1
- data/spec/compiler_spec.rb +60 -1
- data/spec/presenter_spec.rb +27 -17
- data/spec/scanner_spec.rb +18 -0
- metadata +10 -5
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: c1d51d31a9e02cb1bccbdac92afeee3faa5ae093
|
4
|
+
data.tar.gz: 08eb307eecf7be51a180cc11a257b3ad460d6fa6
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 62a6e70667b692c94e5b48dae4dffa84d49f096a8a34104dc827e4a5087f53df8a870b684cf106c606e6b208067ec233cf35ada8f9dfa580910ec1d55278472b
|
7
|
+
data.tar.gz: a07ef3ed26980e8751287a9a00770f32badd4c6fa3e1e50b16be83d23ae0cd3f17cbfd910add61050d780cee9e88abbf03a308c75d6c0d11058a3981d34173a4
|
data/CHANGELOG.md
CHANGED
data/CONTRIBUTING.md
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
In order to keep the Curly code base nice and tidy, please observe these best practises when making contributions:
|
2
|
+
|
3
|
+
- Add tests for all your code. Make sure the tests are clear and fail with proper error messages. It's a good idea to let your test fail in order to review whether the message makes sense; then make the test pass.
|
4
|
+
- Document any unclear things in the code. Even better, don't make the code do unclear things.
|
5
|
+
- Use the coding style already present in the code base.
|
6
|
+
- Make your commit messages precise and to the point. Add a short summary (50 chars max) followed by a blank line and then a longer description, if necessary, e.g.
|
7
|
+
|
8
|
+
> Make invalid references raise an exception
|
9
|
+
>
|
10
|
+
> In order to avoid nasty errors when doing stuff, make the Curly compiler
|
11
|
+
> fail early when an invalid reference is encountered.
|
12
|
+
|
13
|
+
|
14
|
+
Before making a contribution, you should make sure to understand what Curly is and isn't:
|
15
|
+
|
16
|
+
- The template language will never be super advanced: one of the primary use cases for Curly is to allow end users to mess around with Curly templates and have them safely compiled and rendered on a server. As such, the template language will always be as simple as possible.
|
17
|
+
- The template language is declarative, and is going to stay that way.
|
data/README.md
CHANGED
@@ -34,9 +34,11 @@ at Zendesk, where it performs admirably.
|
|
34
34
|
|
35
35
|
1. [Installing](#installing)
|
36
36
|
2. [How to use Curly](#how-to-use-curly)
|
37
|
+
1. [Conditional blocks](#conditional-blocks)
|
38
|
+
2. [Escaping Curly syntax](#escaping-curly-syntax)
|
37
39
|
3. [Presenters](#presenters)
|
38
40
|
1. [Layouts and Content Blocks](#layouts-and-content-blocks)
|
39
|
-
|
41
|
+
2. [Examples](#examples)
|
40
42
|
4. [Caching](#caching)
|
41
43
|
|
42
44
|
|
@@ -71,6 +73,10 @@ Add some HTML to the partial template along with some Curly variables:
|
|
71
73
|
</p>
|
72
74
|
|
73
75
|
{{body}}
|
76
|
+
|
77
|
+
{{#author?}}
|
78
|
+
<a href="{{delete_url}}">delete comment</a>
|
79
|
+
{{/author?}}
|
74
80
|
</div>
|
75
81
|
```
|
76
82
|
|
@@ -93,6 +99,10 @@ class Posts::CommentPresenter < Curly::Presenter
|
|
93
99
|
def time_ago
|
94
100
|
time_ago_in_words(@comment.created_at)
|
95
101
|
end
|
102
|
+
|
103
|
+
def author?
|
104
|
+
@comment.author == current_user
|
105
|
+
end
|
96
106
|
end
|
97
107
|
```
|
98
108
|
|
@@ -105,9 +115,36 @@ render collection: post.comments
|
|
105
115
|
```
|
106
116
|
|
107
117
|
|
118
|
+
### Conditional blocks
|
119
|
+
|
120
|
+
If there is some content you only want rendered under specific circumstances, you can
|
121
|
+
use _conditional blocks_. The `{{#admin?}}...{{/admin?}}` syntax will only render the
|
122
|
+
content of the block if the `admin?` method on the presenter returns true, while the
|
123
|
+
`{{^admin?}}...{{/admin?}}` syntax will only render the content if it returns false.
|
124
|
+
|
125
|
+
Both forms can be parameterized by a single argument: `{{#locale.en?}}...{{/locale.en?}}`
|
126
|
+
will only render the block if the `locale?` method on the presenter returns true given the
|
127
|
+
argument `"en"`. Here's how to implement that method in the presenter:
|
128
|
+
|
129
|
+
```ruby
|
130
|
+
class SomePresenter < Curly::Presenter
|
131
|
+
# Allows rendering content only if the locale matches a specified identifier.
|
132
|
+
def locale?(identifier)
|
133
|
+
current_locale == identifier
|
134
|
+
end
|
135
|
+
end
|
136
|
+
```
|
137
|
+
|
138
|
+
|
108
139
|
### Escaping Curly syntax
|
109
140
|
|
110
|
-
|
141
|
+
In order to have `{{` appear verbatim in the rendered HTML, use the triple Curly escape syntax:
|
142
|
+
|
143
|
+
```
|
144
|
+
This is {{{escaped}}.
|
145
|
+
```
|
146
|
+
|
147
|
+
You don't need to escape the closing `}}`.
|
111
148
|
|
112
149
|
|
113
150
|
Presenters
|
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 = '
|
7
|
+
s.version = '1.0.0rc1'
|
8
|
+
s.date = '2014-02-18'
|
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."
|
@@ -29,6 +29,7 @@ Gem::Specification.new do |s|
|
|
29
29
|
# = MANIFEST =
|
30
30
|
s.files = %w[
|
31
31
|
CHANGELOG.md
|
32
|
+
CONTRIBUTING.md
|
32
33
|
Gemfile
|
33
34
|
README.md
|
34
35
|
Rakefile
|
@@ -38,6 +39,10 @@ Gem::Specification.new do |s|
|
|
38
39
|
lib/curly/compilation_error.rb
|
39
40
|
lib/curly/compiler.rb
|
40
41
|
lib/curly/dependency_tracker.rb
|
42
|
+
lib/curly/error.rb
|
43
|
+
lib/curly/incomplete_block_error.rb
|
44
|
+
lib/curly/incorrect_ending_error.rb
|
45
|
+
lib/curly/invalid_block_error.rb
|
41
46
|
lib/curly/invalid_reference.rb
|
42
47
|
lib/curly/presenter.rb
|
43
48
|
lib/curly/railtie.rb
|
data/lib/curly.rb
CHANGED
data/lib/curly/compiler.rb
CHANGED
@@ -1,5 +1,9 @@
|
|
1
1
|
require 'curly/scanner'
|
2
|
+
require 'curly/error'
|
2
3
|
require 'curly/invalid_reference'
|
4
|
+
require 'curly/invalid_block_error'
|
5
|
+
require 'curly/incorrect_ending_error'
|
6
|
+
require 'curly/incomplete_block_error'
|
3
7
|
|
4
8
|
module Curly
|
5
9
|
|
@@ -30,7 +34,7 @@ module Curly
|
|
30
34
|
compile(template, presenter_class)
|
31
35
|
|
32
36
|
true
|
33
|
-
rescue
|
37
|
+
rescue Error
|
34
38
|
false
|
35
39
|
end
|
36
40
|
|
@@ -47,10 +51,16 @@ module Curly
|
|
47
51
|
|
48
52
|
tokens = Scanner.scan(template)
|
49
53
|
|
54
|
+
@blocks = []
|
55
|
+
|
50
56
|
parts = tokens.map do |type, value|
|
51
57
|
send("compile_#{type}", value)
|
52
58
|
end
|
53
59
|
|
60
|
+
if @blocks.any?
|
61
|
+
raise IncompleteBlockError.new(@blocks.pop)
|
62
|
+
end
|
63
|
+
|
54
64
|
<<-RUBY
|
55
65
|
buffer = ActiveSupport::SafeBuffer.new
|
56
66
|
#{parts.join("\n")}
|
@@ -60,6 +70,47 @@ module Curly
|
|
60
70
|
|
61
71
|
private
|
62
72
|
|
73
|
+
def compile_block_start(reference)
|
74
|
+
compile_conditional_block "if", reference
|
75
|
+
end
|
76
|
+
|
77
|
+
def compile_inverse_block_start(reference)
|
78
|
+
compile_conditional_block "unless", reference
|
79
|
+
end
|
80
|
+
|
81
|
+
def compile_conditional_block(keyword, reference)
|
82
|
+
m = reference.match(/\A(.+?)(?:\.(.+))?\?\z/)
|
83
|
+
method, argument = "#{m[1]}?", m[2]
|
84
|
+
|
85
|
+
@blocks.push reference
|
86
|
+
|
87
|
+
unless presenter_class.method_available?(method.to_sym)
|
88
|
+
raise Curly::InvalidReference.new(method.to_sym)
|
89
|
+
end
|
90
|
+
|
91
|
+
if presenter_class.instance_method(method).arity == 1
|
92
|
+
<<-RUBY
|
93
|
+
#{keyword} presenter.#{method}(#{argument.inspect})
|
94
|
+
RUBY
|
95
|
+
else
|
96
|
+
<<-RUBY
|
97
|
+
#{keyword} presenter.#{method}
|
98
|
+
RUBY
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
def compile_block_end(reference)
|
103
|
+
last_block = @blocks.pop
|
104
|
+
|
105
|
+
unless last_block == reference
|
106
|
+
raise Curly::IncorrectEndingError.new(reference, last_block)
|
107
|
+
end
|
108
|
+
|
109
|
+
<<-RUBY
|
110
|
+
end
|
111
|
+
RUBY
|
112
|
+
end
|
113
|
+
|
63
114
|
def compile_reference(reference)
|
64
115
|
method, argument = reference.split(".", 2)
|
65
116
|
|
data/lib/curly/error.rb
ADDED
@@ -0,0 +1,11 @@
|
|
1
|
+
module Curly
|
2
|
+
class IncorrectEndingError < Error
|
3
|
+
def initialize(reference, last_block)
|
4
|
+
@reference, @last_block = reference, last_block
|
5
|
+
end
|
6
|
+
|
7
|
+
def message
|
8
|
+
"error compiling `{{##{@last_block}}}`: expected `{{/#{@last_block}}}`, got `{{/#{@reference}}}`"
|
9
|
+
end
|
10
|
+
end
|
11
|
+
end
|
data/lib/curly/scanner.rb
CHANGED
@@ -15,6 +15,10 @@ module Curly
|
|
15
15
|
ESCAPED_CURLY_START = /\{\{\{/
|
16
16
|
|
17
17
|
COMMENT_MARKER = /!/
|
18
|
+
BLOCK_MARKER = /#/
|
19
|
+
INVERSE_BLOCK_MARKER = /\^/
|
20
|
+
END_BLOCK_MARKER = /\//
|
21
|
+
|
18
22
|
|
19
23
|
# Scans a Curly template for tokens.
|
20
24
|
#
|
@@ -59,13 +63,19 @@ module Curly
|
|
59
63
|
|
60
64
|
def scan_curly
|
61
65
|
if @scanner.scan(CURLY_START)
|
62
|
-
|
66
|
+
scan_tag or syntax_error!
|
63
67
|
end
|
64
68
|
end
|
65
69
|
|
66
|
-
def
|
70
|
+
def scan_tag
|
67
71
|
if @scanner.scan(COMMENT_MARKER)
|
68
72
|
scan_comment
|
73
|
+
elsif @scanner.scan(BLOCK_MARKER)
|
74
|
+
scan_block_start
|
75
|
+
elsif @scanner.scan(INVERSE_BLOCK_MARKER)
|
76
|
+
scan_inverse_block_start
|
77
|
+
elsif @scanner.scan(END_BLOCK_MARKER)
|
78
|
+
scan_block_end
|
69
79
|
else
|
70
80
|
scan_reference
|
71
81
|
end
|
@@ -77,6 +87,24 @@ module Curly
|
|
77
87
|
end
|
78
88
|
end
|
79
89
|
|
90
|
+
def scan_block_start
|
91
|
+
if value = scan_until_end_of_curly
|
92
|
+
[:block_start, value]
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
def scan_inverse_block_start
|
97
|
+
if value = scan_until_end_of_curly
|
98
|
+
[:inverse_block_start, value]
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
def scan_block_end
|
103
|
+
if value = scan_until_end_of_curly
|
104
|
+
[:block_end, value]
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
80
108
|
def scan_reference
|
81
109
|
if value = scan_until_end_of_curly
|
82
110
|
[:reference, value]
|
data/lib/curly/syntax_error.rb
CHANGED
data/spec/compiler_spec.rb
CHANGED
@@ -15,6 +15,10 @@ describe Curly::Compiler do
|
|
15
15
|
"#{yield :foo}, please?"
|
16
16
|
end
|
17
17
|
|
18
|
+
def hello?(value)
|
19
|
+
value == "world"
|
20
|
+
end
|
21
|
+
|
18
22
|
def unicorns
|
19
23
|
"UNICORN"
|
20
24
|
end
|
@@ -23,12 +27,21 @@ describe Curly::Compiler do
|
|
23
27
|
nil
|
24
28
|
end
|
25
29
|
|
30
|
+
def false?
|
31
|
+
false
|
32
|
+
end
|
33
|
+
|
34
|
+
def true?
|
35
|
+
true
|
36
|
+
end
|
37
|
+
|
26
38
|
def parameterized(value)
|
27
39
|
value
|
28
40
|
end
|
29
41
|
|
30
42
|
def self.method_available?(method)
|
31
|
-
[:foo, :parameterized, :high_yield, :yield_value, :dirty
|
43
|
+
[:foo, :parameterized, :high_yield, :yield_value, :dirty,
|
44
|
+
:false?, :true?, :hello?].include?(method)
|
32
45
|
end
|
33
46
|
|
34
47
|
def self.available_methods
|
@@ -108,6 +121,44 @@ describe Curly::Compiler do
|
|
108
121
|
evaluate("HELO{{! I'm a comment, yo }}WORLD").should == "HELOWORLD"
|
109
122
|
end
|
110
123
|
|
124
|
+
it "removes text in false blocks" do
|
125
|
+
evaluate("test{{#false?}}bar{{/false?}}").should == "test"
|
126
|
+
end
|
127
|
+
|
128
|
+
it "keeps text in true blocks" do
|
129
|
+
evaluate("test{{#true?}}bar{{/true?}}").should == "testbar"
|
130
|
+
end
|
131
|
+
|
132
|
+
it "removes text in inverse true blocks" do
|
133
|
+
evaluate("test{{^true?}}bar{{/true?}}").should == "test"
|
134
|
+
end
|
135
|
+
|
136
|
+
it "keeps kext in inverse false blocks" do
|
137
|
+
evaluate("test{{^false?}}bar{{/false?}}").should == "testbar"
|
138
|
+
end
|
139
|
+
|
140
|
+
it "passes an argument to blocks" do
|
141
|
+
evaluate("{{#hello.world?}}foo{{/hello.world?}}{{#hello.foo?}}bar{{/hello.foo?}}").should == "foo"
|
142
|
+
end
|
143
|
+
|
144
|
+
it "gives an error on mismatching blocks" do
|
145
|
+
expect do
|
146
|
+
evaluate("test{{#false?}}bar{{/true?}}")
|
147
|
+
end.to raise_exception(Curly::IncorrectEndingError)
|
148
|
+
end
|
149
|
+
|
150
|
+
it "gives an error on incomplete blocks" do
|
151
|
+
expect do
|
152
|
+
evaluate("test{{#false?}}bar")
|
153
|
+
end.to raise_exception(Curly::IncompleteBlockError)
|
154
|
+
end
|
155
|
+
|
156
|
+
it "gives an error on mismatching block ends" do
|
157
|
+
expect do
|
158
|
+
evaluate("{{#true?}}test{{#false?}}bar{{/true?}}{{/false?}}")
|
159
|
+
end.to raise_exception(Curly::IncorrectEndingError)
|
160
|
+
end
|
161
|
+
|
111
162
|
it "does not execute arbitrary Ruby code" do
|
112
163
|
evaluate('#{foo}').should == '#{foo}'
|
113
164
|
end
|
@@ -127,6 +178,14 @@ describe Curly::Compiler do
|
|
127
178
|
validate("Hello, {{inspect}}").should == false
|
128
179
|
end
|
129
180
|
|
181
|
+
it "returns true with a block" do
|
182
|
+
validate("Hello {{#true?}}world{{/true?}}").should == true
|
183
|
+
end
|
184
|
+
|
185
|
+
it "returns false with an incomplete block" do
|
186
|
+
validate("Hello {{#true?}}world").should == false
|
187
|
+
end
|
188
|
+
|
130
189
|
def validate(template)
|
131
190
|
Curly.valid?(template, presenter_class)
|
132
191
|
end
|
data/spec/presenter_spec.rb
CHANGED
@@ -19,28 +19,38 @@ describe Curly::Presenter do
|
|
19
19
|
presents :elephant, default: "Babar"
|
20
20
|
end
|
21
21
|
|
22
|
-
|
23
|
-
|
22
|
+
class FancyCircusPresenter < CircusPresenter
|
23
|
+
presents :champagne
|
24
|
+
end
|
24
25
|
|
25
|
-
|
26
|
-
|
27
|
-
clown: "Bubbles"
|
28
|
-
)
|
26
|
+
describe "#initialize" do
|
27
|
+
let(:context) { double("context") }
|
29
28
|
|
30
|
-
|
31
|
-
|
32
|
-
|
29
|
+
it "sets the presented parameters as instance variables" do
|
30
|
+
presenter = CircusPresenter.new(context,
|
31
|
+
midget: "Meek Harolson",
|
32
|
+
clown: "Bubbles"
|
33
|
+
)
|
34
|
+
|
35
|
+
presenter.midget.should == "Meek Harolson"
|
36
|
+
presenter.clown.should == "Bubbles"
|
37
|
+
end
|
33
38
|
|
34
|
-
|
35
|
-
|
39
|
+
it "raises an exception if a required parameter is not specified" do
|
40
|
+
expect {
|
41
|
+
FancyCircusPresenter.new(context, {})
|
42
|
+
}.to raise_exception(ArgumentError, "required parameter `champagne` missing")
|
43
|
+
end
|
36
44
|
|
37
|
-
|
38
|
-
|
39
|
-
|
45
|
+
it "allows specifying default values for parameters" do
|
46
|
+
# Make sure subclasses can change default values.
|
47
|
+
french_presenter = FrenchCircusPresenter.new(context)
|
48
|
+
french_presenter.elephant.should == "Babar"
|
40
49
|
|
41
|
-
|
42
|
-
|
43
|
-
|
50
|
+
# The subclass shouldn't change the superclass' defaults, though.
|
51
|
+
presenter = CircusPresenter.new(context)
|
52
|
+
presenter.elephant.should == "Dumbo"
|
53
|
+
end
|
44
54
|
end
|
45
55
|
|
46
56
|
describe ".presenter_for_path" do
|
data/spec/scanner_spec.rb
CHANGED
@@ -59,6 +59,24 @@ describe Curly::Scanner, ".scan" do
|
|
59
59
|
]
|
60
60
|
end
|
61
61
|
|
62
|
+
it "scans block tags" do
|
63
|
+
scan('foo {{#bar}} hello {{/bar}}').should == [
|
64
|
+
[:text, "foo "],
|
65
|
+
[:block_start, "bar"],
|
66
|
+
[:text, " hello "],
|
67
|
+
[:block_end, "bar"]
|
68
|
+
]
|
69
|
+
end
|
70
|
+
|
71
|
+
it "scans inverse block tags" do
|
72
|
+
scan('foo {{^bar}} hello {{/bar}}').should == [
|
73
|
+
[:text, "foo "],
|
74
|
+
[:inverse_block_start, "bar"],
|
75
|
+
[:text, " hello "],
|
76
|
+
[:block_end, "bar"]
|
77
|
+
]
|
78
|
+
end
|
79
|
+
|
62
80
|
it "treats quotes as text" do
|
63
81
|
scan('"').should == [
|
64
82
|
[:text, '"']
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: curly-templates
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 1.0.0rc1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Daniel Schierbeck
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2014-02-18 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: actionpack
|
@@ -99,6 +99,7 @@ extensions: []
|
|
99
99
|
extra_rdoc_files: []
|
100
100
|
files:
|
101
101
|
- CHANGELOG.md
|
102
|
+
- CONTRIBUTING.md
|
102
103
|
- Gemfile
|
103
104
|
- README.md
|
104
105
|
- Rakefile
|
@@ -108,6 +109,10 @@ files:
|
|
108
109
|
- lib/curly/compilation_error.rb
|
109
110
|
- lib/curly/compiler.rb
|
110
111
|
- lib/curly/dependency_tracker.rb
|
112
|
+
- lib/curly/error.rb
|
113
|
+
- lib/curly/incomplete_block_error.rb
|
114
|
+
- lib/curly/incorrect_ending_error.rb
|
115
|
+
- lib/curly/invalid_block_error.rb
|
111
116
|
- lib/curly/invalid_reference.rb
|
112
117
|
- lib/curly/presenter.rb
|
113
118
|
- lib/curly/railtie.rb
|
@@ -141,12 +146,12 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
141
146
|
version: '0'
|
142
147
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
143
148
|
requirements:
|
144
|
-
- - '
|
149
|
+
- - '>'
|
145
150
|
- !ruby/object:Gem::Version
|
146
|
-
version:
|
151
|
+
version: 1.3.1
|
147
152
|
requirements: []
|
148
153
|
rubyforge_project:
|
149
|
-
rubygems_version: 2.
|
154
|
+
rubygems_version: 2.0.14
|
150
155
|
signing_key:
|
151
156
|
specification_version: 2
|
152
157
|
summary: Free your views!
|