curlybars 0.9.13
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/lib/curlybars.rb +108 -0
- data/lib/curlybars/configuration.rb +41 -0
- data/lib/curlybars/dependency_tracker.rb +8 -0
- data/lib/curlybars/error/base.rb +18 -0
- data/lib/curlybars/error/compile.rb +11 -0
- data/lib/curlybars/error/lex.rb +22 -0
- data/lib/curlybars/error/parse.rb +41 -0
- data/lib/curlybars/error/presenter/not_found.rb +23 -0
- data/lib/curlybars/error/render.rb +11 -0
- data/lib/curlybars/error/validate.rb +18 -0
- data/lib/curlybars/lexer.rb +60 -0
- data/lib/curlybars/method_whitelist.rb +69 -0
- data/lib/curlybars/node/block_helper_else.rb +108 -0
- data/lib/curlybars/node/boolean.rb +24 -0
- data/lib/curlybars/node/each_else.rb +69 -0
- data/lib/curlybars/node/if_else.rb +33 -0
- data/lib/curlybars/node/item.rb +31 -0
- data/lib/curlybars/node/literal.rb +28 -0
- data/lib/curlybars/node/option.rb +25 -0
- data/lib/curlybars/node/output.rb +24 -0
- data/lib/curlybars/node/partial.rb +24 -0
- data/lib/curlybars/node/path.rb +137 -0
- data/lib/curlybars/node/root.rb +29 -0
- data/lib/curlybars/node/string.rb +24 -0
- data/lib/curlybars/node/template.rb +32 -0
- data/lib/curlybars/node/text.rb +24 -0
- data/lib/curlybars/node/unless_else.rb +33 -0
- data/lib/curlybars/node/variable.rb +34 -0
- data/lib/curlybars/node/with_else.rb +54 -0
- data/lib/curlybars/parser.rb +183 -0
- data/lib/curlybars/position.rb +7 -0
- data/lib/curlybars/presenter.rb +288 -0
- data/lib/curlybars/processor/tilde.rb +31 -0
- data/lib/curlybars/processor/token_factory.rb +9 -0
- data/lib/curlybars/railtie.rb +18 -0
- data/lib/curlybars/rendering_support.rb +222 -0
- data/lib/curlybars/safe_buffer.rb +11 -0
- data/lib/curlybars/template_handler.rb +93 -0
- data/lib/curlybars/version.rb +3 -0
- data/spec/acceptance/application_layout_spec.rb +60 -0
- data/spec/acceptance/collection_blocks_spec.rb +28 -0
- data/spec/acceptance/global_helper_spec.rb +25 -0
- data/spec/curlybars/configuration_spec.rb +57 -0
- data/spec/curlybars/error/base_spec.rb +41 -0
- data/spec/curlybars/error/compile_spec.rb +19 -0
- data/spec/curlybars/error/lex_spec.rb +25 -0
- data/spec/curlybars/error/parse_spec.rb +74 -0
- data/spec/curlybars/error/render_spec.rb +19 -0
- data/spec/curlybars/error/validate_spec.rb +19 -0
- data/spec/curlybars/lexer_spec.rb +466 -0
- data/spec/curlybars/method_whitelist_spec.rb +168 -0
- data/spec/curlybars/processor/tilde_spec.rb +60 -0
- data/spec/curlybars/rendering_support_spec.rb +426 -0
- data/spec/curlybars/safe_buffer_spec.rb +46 -0
- data/spec/curlybars/template_handler_spec.rb +222 -0
- data/spec/integration/cache_spec.rb +124 -0
- data/spec/integration/comment_spec.rb +60 -0
- data/spec/integration/exception_spec.rb +31 -0
- data/spec/integration/node/block_helper_else_spec.rb +422 -0
- data/spec/integration/node/each_else_spec.rb +204 -0
- data/spec/integration/node/each_spec.rb +291 -0
- data/spec/integration/node/escape_spec.rb +27 -0
- data/spec/integration/node/helper_spec.rb +176 -0
- data/spec/integration/node/if_else_spec.rb +129 -0
- data/spec/integration/node/if_spec.rb +143 -0
- data/spec/integration/node/output_spec.rb +68 -0
- data/spec/integration/node/partial_spec.rb +66 -0
- data/spec/integration/node/path_spec.rb +286 -0
- data/spec/integration/node/root_spec.rb +15 -0
- data/spec/integration/node/template_spec.rb +86 -0
- data/spec/integration/node/unless_else_spec.rb +129 -0
- data/spec/integration/node/unless_spec.rb +130 -0
- data/spec/integration/node/with_spec.rb +116 -0
- data/spec/integration/processor/tilde_spec.rb +38 -0
- data/spec/integration/processors_spec.rb +30 -0
- metadata +358 -0
@@ -0,0 +1,29 @@
|
|
1
|
+
module Curlybars
|
2
|
+
module Node
|
3
|
+
Root = Struct.new(:template, :position) do
|
4
|
+
def compile
|
5
|
+
# NOTE: the following is a heredoc string, representing the ruby code fragment
|
6
|
+
# outputted by this node.
|
7
|
+
<<-RUBY
|
8
|
+
contexts = [presenter]
|
9
|
+
variables = [{}]
|
10
|
+
rendering = ::Curlybars::RenderingSupport.new(
|
11
|
+
::Curlybars.configuration.rendering_timeout,
|
12
|
+
contexts,
|
13
|
+
variables,
|
14
|
+
#{position.file_name.inspect},
|
15
|
+
global_helpers_providers,
|
16
|
+
::Curlybars.configuration.cache
|
17
|
+
)
|
18
|
+
buffer = ::Curlybars::SafeBuffer.new
|
19
|
+
#{template.compile}
|
20
|
+
buffer
|
21
|
+
RUBY
|
22
|
+
end
|
23
|
+
|
24
|
+
def validate(branches)
|
25
|
+
template.validate(branches)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
module Curlybars
|
2
|
+
module Node
|
3
|
+
String = Struct.new(:string) do
|
4
|
+
def compile
|
5
|
+
# NOTE: the following is a heredoc string, representing the ruby code fragment
|
6
|
+
# outputted by this node.
|
7
|
+
<<-RUBY
|
8
|
+
->() { #{string.inspect} }
|
9
|
+
RUBY
|
10
|
+
end
|
11
|
+
|
12
|
+
def validate(branches)
|
13
|
+
# Nothing to validate here.
|
14
|
+
end
|
15
|
+
|
16
|
+
def cache_key
|
17
|
+
[
|
18
|
+
string,
|
19
|
+
self.class.name
|
20
|
+
].join("/")
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
module Curlybars
|
2
|
+
module Node
|
3
|
+
Template = Struct.new(:items, :position) do
|
4
|
+
def compile
|
5
|
+
compiled_items = items.map(&:compile).join("\n")
|
6
|
+
|
7
|
+
# NOTE: the following is a heredoc string, representing the ruby code fragment
|
8
|
+
# outputted by this node.
|
9
|
+
<<-RUBY
|
10
|
+
::Module.new do
|
11
|
+
def self.exec(contexts, rendering, variables, buffer)
|
12
|
+
unless contexts.length < ::Curlybars.configuration.nesting_limit
|
13
|
+
message = "Nesting too deep"
|
14
|
+
position = rendering.position(#{position.line_number}, #{position.line_offset})
|
15
|
+
raise ::Curlybars::Error::Render.new('nesting_too_deep', message, position)
|
16
|
+
end
|
17
|
+
#{compiled_items}
|
18
|
+
end
|
19
|
+
end.exec(contexts, rendering, variables, buffer)
|
20
|
+
RUBY
|
21
|
+
end
|
22
|
+
|
23
|
+
def validate(branches)
|
24
|
+
items.map { |item| item.validate(branches) }
|
25
|
+
end
|
26
|
+
|
27
|
+
def cache_key
|
28
|
+
Digest::MD5.hexdigest(items.map(&:cache_key).join("/"))
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
module Curlybars
|
2
|
+
module Node
|
3
|
+
Text = Struct.new(:text) do
|
4
|
+
def compile
|
5
|
+
# NOTE: the following is a heredoc string, representing the ruby code fragment
|
6
|
+
# outputted by this node.
|
7
|
+
<<-RUBY
|
8
|
+
buffer.concat(#{text.inspect}.html_safe)
|
9
|
+
RUBY
|
10
|
+
end
|
11
|
+
|
12
|
+
def validate(branches)
|
13
|
+
# Nothing to validate here.
|
14
|
+
end
|
15
|
+
|
16
|
+
def cache_key
|
17
|
+
[
|
18
|
+
text.to_s,
|
19
|
+
self.class.name
|
20
|
+
].join("/")
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
module Curlybars
|
2
|
+
module Node
|
3
|
+
UnlessElse = Struct.new(:expression, :unless_template, :else_template) do
|
4
|
+
def compile
|
5
|
+
# NOTE: the following is a heredoc string, representing the ruby code fragment
|
6
|
+
# outputted by this node.
|
7
|
+
<<-RUBY
|
8
|
+
unless rendering.to_bool(rendering.cached_call(#{expression.compile}))
|
9
|
+
#{unless_template.compile}
|
10
|
+
else
|
11
|
+
#{else_template.compile}
|
12
|
+
end
|
13
|
+
RUBY
|
14
|
+
end
|
15
|
+
|
16
|
+
def validate(branches)
|
17
|
+
[
|
18
|
+
expression.validate(branches),
|
19
|
+
unless_template.validate(branches),
|
20
|
+
else_template.validate(branches)
|
21
|
+
]
|
22
|
+
end
|
23
|
+
|
24
|
+
def cache_key
|
25
|
+
[
|
26
|
+
expression,
|
27
|
+
unless_template,
|
28
|
+
else_template
|
29
|
+
].map(&:cache_key).push(self.class.name).join("/")
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
module Curlybars
|
2
|
+
module Node
|
3
|
+
Variable = Struct.new(:variable, :position) do
|
4
|
+
def compile
|
5
|
+
# NOTE: the following is a heredoc string, representing the ruby code fragment
|
6
|
+
# outputted by this node.
|
7
|
+
<<-RUBY
|
8
|
+
-> {
|
9
|
+
position = rendering.position(
|
10
|
+
#{position.line_number},
|
11
|
+
#{position.line_offset}
|
12
|
+
)
|
13
|
+
rendering.variable(#{variable.inspect}, position)
|
14
|
+
}
|
15
|
+
RUBY
|
16
|
+
end
|
17
|
+
|
18
|
+
def validate(branches)
|
19
|
+
# Nothing to validate here.
|
20
|
+
end
|
21
|
+
|
22
|
+
def validate_as_value(branches)
|
23
|
+
# It is always a value.
|
24
|
+
end
|
25
|
+
|
26
|
+
def cache_key
|
27
|
+
[
|
28
|
+
variable,
|
29
|
+
self.class.name
|
30
|
+
].join("/")
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,54 @@
|
|
1
|
+
module Curlybars
|
2
|
+
module Node
|
3
|
+
WithElse = Struct.new(:path, :with_template, :else_template, :position) do
|
4
|
+
def compile
|
5
|
+
# NOTE: the following is a heredoc string, representing the ruby code fragment
|
6
|
+
# outputted by this node.
|
7
|
+
<<-RUBY
|
8
|
+
compiled_path = rendering.cached_call(#{path.compile})
|
9
|
+
|
10
|
+
if rendering.to_bool(compiled_path)
|
11
|
+
position = rendering.position(#{position.line_number}, #{position.line_offset})
|
12
|
+
rendering.check_context_is_presenter(compiled_path, #{path.path.inspect}, position)
|
13
|
+
|
14
|
+
contexts.push(compiled_path)
|
15
|
+
begin
|
16
|
+
#{with_template.compile}
|
17
|
+
ensure
|
18
|
+
contexts.pop
|
19
|
+
end
|
20
|
+
else
|
21
|
+
#{else_template.compile}
|
22
|
+
end
|
23
|
+
RUBY
|
24
|
+
end
|
25
|
+
|
26
|
+
def validate(branches)
|
27
|
+
sub_tree = path.resolve_and_check!(branches, check_type: :presenter)
|
28
|
+
with_template_errors = begin
|
29
|
+
branches.push(sub_tree)
|
30
|
+
with_template.validate(branches)
|
31
|
+
ensure
|
32
|
+
branches.pop
|
33
|
+
end
|
34
|
+
|
35
|
+
else_template_errors = else_template.validate(branches)
|
36
|
+
|
37
|
+
[
|
38
|
+
with_template_errors,
|
39
|
+
else_template_errors
|
40
|
+
]
|
41
|
+
rescue Curlybars::Error::Validate => path_error
|
42
|
+
path_error
|
43
|
+
end
|
44
|
+
|
45
|
+
def cache_key
|
46
|
+
[
|
47
|
+
path,
|
48
|
+
with_template,
|
49
|
+
else_template
|
50
|
+
].map(&:cache_key).push(self.class.name).join("/")
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
@@ -0,0 +1,183 @@
|
|
1
|
+
require 'rltk/parser'
|
2
|
+
|
3
|
+
require 'curlybars/node/root'
|
4
|
+
require 'curlybars/node/template'
|
5
|
+
require 'curlybars/node/item'
|
6
|
+
require 'curlybars/node/text'
|
7
|
+
require 'curlybars/node/if_else'
|
8
|
+
require 'curlybars/node/unless_else'
|
9
|
+
require 'curlybars/node/each_else'
|
10
|
+
require 'curlybars/node/path'
|
11
|
+
require 'curlybars/node/literal'
|
12
|
+
require 'curlybars/node/variable'
|
13
|
+
require 'curlybars/node/with_else'
|
14
|
+
require 'curlybars/node/block_helper_else'
|
15
|
+
require 'curlybars/node/option'
|
16
|
+
require 'curlybars/node/partial'
|
17
|
+
require 'curlybars/node/output'
|
18
|
+
|
19
|
+
module Curlybars
|
20
|
+
class Parser < RLTK::Parser
|
21
|
+
start :root
|
22
|
+
|
23
|
+
production(:root, 'template?') { |template| Node::Root.new(template || VOID, pos(0)) }
|
24
|
+
production(:template, 'items') { |items| Node::Template.new(items || [], pos(0)) }
|
25
|
+
|
26
|
+
production(:items) do
|
27
|
+
clause('items item') { |items, item| items << Node::Item.new(item) }
|
28
|
+
clause('item') { |item| [Node::Item.new(item)] }
|
29
|
+
end
|
30
|
+
|
31
|
+
production(:item) do
|
32
|
+
clause('TEXT') { |text| Node::Text.new(text) }
|
33
|
+
|
34
|
+
clause('START HASH .path .expressions? .options? END
|
35
|
+
.template?
|
36
|
+
START SLASH .path END') do |helper, arguments, options, template, helperclose|
|
37
|
+
Node::BlockHelperElse.new(
|
38
|
+
helper,
|
39
|
+
arguments || [],
|
40
|
+
options || [],
|
41
|
+
template || VOID,
|
42
|
+
VOID,
|
43
|
+
helperclose,
|
44
|
+
pos(0)
|
45
|
+
)
|
46
|
+
end
|
47
|
+
|
48
|
+
clause('START HASH .path .expressions? .options? END
|
49
|
+
.template?
|
50
|
+
START ELSE END
|
51
|
+
.template?
|
52
|
+
START SLASH .path END') do |helper, arguments, options, helper_template, else_template, helperclose|
|
53
|
+
Node::BlockHelperElse.new(
|
54
|
+
helper,
|
55
|
+
arguments || [],
|
56
|
+
options || [],
|
57
|
+
helper_template || VOID,
|
58
|
+
else_template || VOID,
|
59
|
+
helperclose,
|
60
|
+
pos(0)
|
61
|
+
)
|
62
|
+
end
|
63
|
+
|
64
|
+
clause('START .path .expressions? .options? END') do |helper, arguments, options|
|
65
|
+
Node::BlockHelperElse.new(
|
66
|
+
helper,
|
67
|
+
arguments || [],
|
68
|
+
options || [],
|
69
|
+
VOID,
|
70
|
+
VOID,
|
71
|
+
helper,
|
72
|
+
pos(0)
|
73
|
+
)
|
74
|
+
end
|
75
|
+
|
76
|
+
clause('START .value END') do |value|
|
77
|
+
Node::Output.new(value)
|
78
|
+
end
|
79
|
+
|
80
|
+
clause('START HASH IF .expression END
|
81
|
+
.template?
|
82
|
+
START SLASH IF END') do |expression, if_template|
|
83
|
+
Node::IfElse.new(expression, if_template || VOID, VOID)
|
84
|
+
end
|
85
|
+
|
86
|
+
clause('START HASH IF .expression END
|
87
|
+
.template?
|
88
|
+
START ELSE END
|
89
|
+
.template?
|
90
|
+
START SLASH IF END') do |expression, if_template, else_template|
|
91
|
+
Node::IfElse.new(expression, if_template || VOID, else_template || VOID)
|
92
|
+
end
|
93
|
+
|
94
|
+
clause('START HASH UNLESS .expression END
|
95
|
+
.template?
|
96
|
+
START SLASH UNLESS END') do |expression, unless_template|
|
97
|
+
Node::UnlessElse.new(expression, unless_template || VOID, VOID)
|
98
|
+
end
|
99
|
+
|
100
|
+
clause('START HASH UNLESS .expression END
|
101
|
+
.template?
|
102
|
+
START ELSE END
|
103
|
+
.template?
|
104
|
+
START SLASH UNLESS END') do |expression, unless_template, else_template|
|
105
|
+
Node::UnlessElse.new(expression, unless_template || VOID, else_template || VOID)
|
106
|
+
end
|
107
|
+
|
108
|
+
clause('START HASH EACH .path END
|
109
|
+
.template?
|
110
|
+
START SLASH EACH END') do |path, each_template|
|
111
|
+
Node::EachElse.new(path, each_template || VOID, VOID, pos(0))
|
112
|
+
end
|
113
|
+
|
114
|
+
clause('START HASH EACH .path END
|
115
|
+
.template?
|
116
|
+
START ELSE END
|
117
|
+
.template?
|
118
|
+
START SLASH EACH END') do |path, each_template, else_template|
|
119
|
+
Node::EachElse.new(path, each_template || VOID, else_template || VOID, pos(0))
|
120
|
+
end
|
121
|
+
|
122
|
+
clause('START HASH WITH .path END
|
123
|
+
.template?
|
124
|
+
START SLASH WITH END') do |path, with_template|
|
125
|
+
Node::WithElse.new(path, with_template || VOID, VOID, pos(0))
|
126
|
+
end
|
127
|
+
|
128
|
+
clause('START HASH WITH .path END
|
129
|
+
.template?
|
130
|
+
START ELSE END
|
131
|
+
.template?
|
132
|
+
START SLASH WITH END') do |path, with_template, else_template|
|
133
|
+
Node::WithElse.new(path, with_template || VOID, else_template || VOID, pos(0))
|
134
|
+
end
|
135
|
+
|
136
|
+
clause('START GT .path END') do |path|
|
137
|
+
Node::Partial.new(path)
|
138
|
+
end
|
139
|
+
end
|
140
|
+
|
141
|
+
production(:options) do
|
142
|
+
clause('options option') { |options, option| options << option }
|
143
|
+
clause('option') { |option| [option] }
|
144
|
+
end
|
145
|
+
|
146
|
+
production(:option, '.KEY .expression') do |key, expression|
|
147
|
+
Node::Option.new(key, expression)
|
148
|
+
end
|
149
|
+
|
150
|
+
production(:expressions) do
|
151
|
+
clause('expressions expression') { |expressions, expression| expressions << expression }
|
152
|
+
clause('expression') { |expression| [expression] }
|
153
|
+
end
|
154
|
+
|
155
|
+
production(:expression) do
|
156
|
+
clause('value') { |value| value }
|
157
|
+
clause('path') { |path| path }
|
158
|
+
end
|
159
|
+
|
160
|
+
production(:value) do
|
161
|
+
clause('LITERAL') { |literal| Node::Literal.new(literal) }
|
162
|
+
clause('VARIABLE') { |variable| Node::Variable.new(variable, pos(0)) }
|
163
|
+
end
|
164
|
+
|
165
|
+
production(:path, 'PATH') { |path| Node::Path.new(path, pos(0)) }
|
166
|
+
|
167
|
+
finalize
|
168
|
+
|
169
|
+
VOID = Class.new do
|
170
|
+
def compile
|
171
|
+
# Nothing to compile.
|
172
|
+
end
|
173
|
+
|
174
|
+
def validate(branches)
|
175
|
+
[] # Nothing to validate.
|
176
|
+
end
|
177
|
+
|
178
|
+
def cache_key
|
179
|
+
# Empty cache key.
|
180
|
+
end
|
181
|
+
end.new
|
182
|
+
end
|
183
|
+
end
|
@@ -0,0 +1,288 @@
|
|
1
|
+
require 'curlybars/method_whitelist'
|
2
|
+
require 'curlybars/configuration'
|
3
|
+
|
4
|
+
module Curlybars
|
5
|
+
# A base class that can be subclassed by concrete presenters.
|
6
|
+
#
|
7
|
+
# A Curlybars presenter is responsible for delivering data to templates, in the
|
8
|
+
# form of simple strings. Each public instance method on the presenter class
|
9
|
+
# can be referenced in a template. When a template is evaluated with a
|
10
|
+
# presenter, the referenced methods will be called with no arguments, and
|
11
|
+
# the returned strings inserted in place of the components in the template.
|
12
|
+
#
|
13
|
+
# Note that strings that are not HTML safe will be escaped.
|
14
|
+
#
|
15
|
+
# A presenter is always instantiated with a context to which it delegates
|
16
|
+
# unknown messages, usually an instance of ActionView::Base provided by
|
17
|
+
# Rails. See Curlybars::TemplateHandler for a typical use.
|
18
|
+
#
|
19
|
+
# Examples
|
20
|
+
#
|
21
|
+
# class BlogPresenter < Curlybars::Presenter
|
22
|
+
# presents :post
|
23
|
+
# allow_methods :title, :body, :author
|
24
|
+
#
|
25
|
+
# def title
|
26
|
+
# @post.title
|
27
|
+
# end
|
28
|
+
#
|
29
|
+
# def body
|
30
|
+
# markdown(@post.body)
|
31
|
+
# end
|
32
|
+
#
|
33
|
+
# def author
|
34
|
+
# @post.author.full_name
|
35
|
+
# end
|
36
|
+
# end
|
37
|
+
#
|
38
|
+
# presenter = BlogPresenter.new(context, post: post)
|
39
|
+
# presenter.author #=> "Jackie Chan"
|
40
|
+
#
|
41
|
+
class Presenter
|
42
|
+
extend Curlybars::MethodWhitelist
|
43
|
+
|
44
|
+
# Initializes the presenter with the given context and options.
|
45
|
+
#
|
46
|
+
# context - An ActionView::Base context.
|
47
|
+
# options - A Hash of options given to the presenter.
|
48
|
+
def initialize(context, options = {})
|
49
|
+
@_context = context
|
50
|
+
options.stringify_keys!
|
51
|
+
|
52
|
+
self.class.presented_names.each do |name|
|
53
|
+
value = options.fetch(name) do
|
54
|
+
default_values.fetch(name) do
|
55
|
+
block = default_blocks.fetch(name) do
|
56
|
+
raise ArgumentError.new("required identifier `#{name}` missing")
|
57
|
+
end
|
58
|
+
|
59
|
+
instance_exec(name, &block)
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
instance_variable_set("@#{name}", value)
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
# Sets up the view.
|
68
|
+
#
|
69
|
+
# Override this method in your presenter in order to do setup before the
|
70
|
+
# template is rendered. One use case is to call `content_for` in order
|
71
|
+
# to inject content into other templates, e.g. a layout.
|
72
|
+
#
|
73
|
+
# Examples
|
74
|
+
#
|
75
|
+
# class Posts::ShowPresenter < Curlybars::Presenter
|
76
|
+
# presents :post
|
77
|
+
#
|
78
|
+
# def setup!
|
79
|
+
# content_for :page_title, @post.title
|
80
|
+
# end
|
81
|
+
# end
|
82
|
+
#
|
83
|
+
# Returns nothing.
|
84
|
+
def setup!
|
85
|
+
# Does nothing.
|
86
|
+
end
|
87
|
+
|
88
|
+
# The key that should be used to cache the view.
|
89
|
+
#
|
90
|
+
# Unless `#cache_key` returns nil, the result of rendering the template
|
91
|
+
# that the presenter supports will be cached. The return value will be
|
92
|
+
# part of the final cache key, along with a digest of the template itself.
|
93
|
+
#
|
94
|
+
# Any object can be used as a cache key, so long as it
|
95
|
+
#
|
96
|
+
# - is a String,
|
97
|
+
# - responds to #cache_key itself, or
|
98
|
+
# - is an Array or a Hash whose items themselves fit either of these
|
99
|
+
# criteria.
|
100
|
+
#
|
101
|
+
# Returns the cache key Object or nil if no caching should be performed.
|
102
|
+
def cache_key
|
103
|
+
nil
|
104
|
+
end
|
105
|
+
|
106
|
+
# The options that should be passed to the cache backend when caching the
|
107
|
+
# view. The exact options may vary depending on the backend you're using.
|
108
|
+
#
|
109
|
+
# The most common option is `:expires_in`, which controls the duration of
|
110
|
+
# time that the cached view should be considered fresh. Because it's so
|
111
|
+
# common, you can set that option simply by defining `#cache_duration`.
|
112
|
+
#
|
113
|
+
# Note: if you set the `:expires_in` option through this method, the
|
114
|
+
# `#cache_duration` value will be ignored.
|
115
|
+
#
|
116
|
+
# Returns a Hash.
|
117
|
+
def cache_options
|
118
|
+
{}
|
119
|
+
end
|
120
|
+
|
121
|
+
# The duration that the view should be cached for. Only relevant if
|
122
|
+
# `#cache_key` returns a non nil value.
|
123
|
+
#
|
124
|
+
# If nil, the view will not have an expiration time set. See also
|
125
|
+
# `#cache_options` for a more flexible way to set cache options.
|
126
|
+
#
|
127
|
+
# Examples
|
128
|
+
#
|
129
|
+
# def cache_duration
|
130
|
+
# 10.minutes
|
131
|
+
# end
|
132
|
+
#
|
133
|
+
# Returns the Fixnum duration of the cache item, in seconds, or nil if no
|
134
|
+
# duration should be set.
|
135
|
+
def cache_duration
|
136
|
+
nil
|
137
|
+
end
|
138
|
+
|
139
|
+
class << self
|
140
|
+
# The name of the presenter class for a given view path.
|
141
|
+
#
|
142
|
+
# path - The String path of a view.
|
143
|
+
#
|
144
|
+
# Examples
|
145
|
+
#
|
146
|
+
# Curlybars::TemplateHandler.presenter_name_for_path("foo/bar")
|
147
|
+
# #=> "Foo::BarPresenter"
|
148
|
+
#
|
149
|
+
# Returns the String name of the matching presenter class.
|
150
|
+
def presenter_name_for_path(path)
|
151
|
+
"#{path}_presenter".camelize
|
152
|
+
end
|
153
|
+
|
154
|
+
# Returns the presenter class for the given path.
|
155
|
+
#
|
156
|
+
# path - The String path of a template.
|
157
|
+
#
|
158
|
+
# Returns the Class or nil if the constant cannot be found.
|
159
|
+
def presenter_for_path(path)
|
160
|
+
name_space = Curlybars.configuration.presenters_namespace
|
161
|
+
name_spaced_path = File.join(name_space, path)
|
162
|
+
full_class_name = presenter_name_for_path(name_spaced_path)
|
163
|
+
begin
|
164
|
+
full_class_name.constantize
|
165
|
+
rescue NameError
|
166
|
+
nil
|
167
|
+
end
|
168
|
+
end
|
169
|
+
|
170
|
+
# The set of view paths that the presenter depends on.
|
171
|
+
#
|
172
|
+
# Examples
|
173
|
+
#
|
174
|
+
# class Posts::ShowPresenter < Curlybars::Presenter
|
175
|
+
# version 2
|
176
|
+
# depends_on 'posts/comment', 'posts/comment_form'
|
177
|
+
# end
|
178
|
+
#
|
179
|
+
# Posts::ShowPresenter.dependencies
|
180
|
+
# #=> ['posts/comment', 'posts/comment_form']
|
181
|
+
#
|
182
|
+
# Returns a Set of String view paths.
|
183
|
+
def dependencies
|
184
|
+
# The base presenter doesn't have any dependencies.
|
185
|
+
return SortedSet.new if self == Curlybars::Presenter
|
186
|
+
|
187
|
+
@dependencies ||= SortedSet.new
|
188
|
+
@dependencies.union(superclass.dependencies)
|
189
|
+
end
|
190
|
+
|
191
|
+
# Indicate that the presenter depends a list of other views.
|
192
|
+
#
|
193
|
+
# deps - A list of String view paths that the presenter depends on.
|
194
|
+
#
|
195
|
+
# Returns nothing.
|
196
|
+
def depends_on(*dependencies)
|
197
|
+
@dependencies ||= SortedSet.new
|
198
|
+
@dependencies.merge(dependencies)
|
199
|
+
end
|
200
|
+
|
201
|
+
# Get or set the version of the presenter.
|
202
|
+
#
|
203
|
+
# version - The Integer version that should be set. If nil, no version
|
204
|
+
# is set.
|
205
|
+
#
|
206
|
+
# Returns the current Integer version of the presenter.
|
207
|
+
def version(version = nil)
|
208
|
+
@version = version if version.present?
|
209
|
+
@version || 0
|
210
|
+
end
|
211
|
+
|
212
|
+
# The cache key for the presenter class. Includes all dependencies as
|
213
|
+
# well.
|
214
|
+
#
|
215
|
+
# Returns a String cache key.
|
216
|
+
def cache_key
|
217
|
+
@cache_key ||= compute_cache_key
|
218
|
+
end
|
219
|
+
|
220
|
+
private
|
221
|
+
|
222
|
+
def compute_cache_key
|
223
|
+
dependency_cache_keys = dependencies.map do |path|
|
224
|
+
presenter = presenter_for_path(path)
|
225
|
+
if presenter.present?
|
226
|
+
presenter.cache_key
|
227
|
+
else
|
228
|
+
path
|
229
|
+
end
|
230
|
+
end
|
231
|
+
|
232
|
+
[name, version, dependency_cache_keys].flatten.join("/")
|
233
|
+
end
|
234
|
+
|
235
|
+
def presents(*args, **options, &block)
|
236
|
+
if options.key?(:default) && block_given?
|
237
|
+
raise ArgumentError, "Cannot provide both `default:` and block"
|
238
|
+
end
|
239
|
+
|
240
|
+
self.presented_names += args.map(&:to_s)
|
241
|
+
|
242
|
+
if options.key?(:default)
|
243
|
+
args.each do |arg|
|
244
|
+
self.default_values = default_values.merge(arg.to_s => options[:default]).freeze
|
245
|
+
end
|
246
|
+
end
|
247
|
+
|
248
|
+
return unless block_given?
|
249
|
+
|
250
|
+
args.each do |arg|
|
251
|
+
self.default_blocks = default_blocks.merge(arg.to_s => block).freeze
|
252
|
+
end
|
253
|
+
end
|
254
|
+
|
255
|
+
def exposes_helper(*methods)
|
256
|
+
methods.each do |method_name|
|
257
|
+
define_method(method_name) do |*args|
|
258
|
+
@_context.public_send(method_name, *args)
|
259
|
+
end
|
260
|
+
end
|
261
|
+
end
|
262
|
+
|
263
|
+
alias_method :exposes_helpers, :exposes_helper
|
264
|
+
end
|
265
|
+
|
266
|
+
private
|
267
|
+
|
268
|
+
class_attribute :presented_names, :default_values, :default_blocks
|
269
|
+
|
270
|
+
self.presented_names = [].freeze
|
271
|
+
self.default_values = {}.freeze
|
272
|
+
self.default_blocks = {}.freeze
|
273
|
+
|
274
|
+
delegate :render, to: :@_context
|
275
|
+
|
276
|
+
# Delegates private method calls to the current view context.
|
277
|
+
#
|
278
|
+
# The view context, an instance of ActionView::Base, is set by Rails.
|
279
|
+
def method_missing(method, *args, &block)
|
280
|
+
@_context.public_send(method, *args, &block)
|
281
|
+
end
|
282
|
+
|
283
|
+
# Tells ruby (and developers) what methods we can accept.
|
284
|
+
def respond_to_missing?(method, include_private = false)
|
285
|
+
@_context.respond_to?(method, false)
|
286
|
+
end
|
287
|
+
end
|
288
|
+
end
|