express_templates 0.2.0 → 0.2.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +10 -6
- data/lib/core_extensions/proc.rb +42 -0
- data/lib/express_templates.rb +7 -2
- data/lib/express_templates/compiler.rb +27 -0
- data/lib/express_templates/components.rb +7 -4
- data/lib/express_templates/components/base.rb +54 -0
- data/lib/express_templates/components/capabilities/conditionality.rb +52 -0
- data/lib/express_templates/components/capabilities/configurable.rb +91 -0
- data/lib/express_templates/components/capabilities/iterating.rb +80 -0
- data/lib/express_templates/components/capabilities/parenting.rb +74 -0
- data/lib/express_templates/components/capabilities/rendering.rb +93 -0
- data/lib/express_templates/components/capabilities/templating.rb +196 -0
- data/lib/express_templates/components/capabilities/wrapping.rb +100 -0
- data/lib/express_templates/components/column.rb +13 -0
- data/lib/express_templates/components/container.rb +7 -0
- data/lib/express_templates/components/form_rails_support.rb +19 -0
- data/lib/express_templates/components/row.rb +28 -0
- data/lib/express_templates/expander.rb +51 -35
- data/lib/express_templates/indenter.rb +44 -0
- data/lib/express_templates/macro.rb +44 -0
- data/lib/express_templates/markup.rb +9 -0
- data/lib/express_templates/{components → markup}/html_tag.rb +9 -3
- data/lib/express_templates/markup/tag.rb +118 -0
- data/lib/express_templates/markup/wrapper.rb +88 -0
- data/lib/express_templates/{components → markup}/yielder.rb +2 -2
- data/lib/express_templates/renderer.rb +3 -8
- data/lib/express_templates/template/handler.rb +1 -1
- data/lib/express_templates/version.rb +1 -1
- data/lib/tasks/{gara_tasks.rake → express_templates.rake} +0 -0
- data/test/compiler_test.rb +9 -0
- data/test/components/base_test.rb +77 -0
- data/test/components/column_test.rb +11 -0
- data/test/components/conditionality_test.rb +37 -0
- data/test/components/configurable_test.rb +43 -0
- data/test/components/container_test.rb +49 -0
- data/test/components/iterating_test.rb +85 -0
- data/test/components/row_test.rb +16 -0
- data/test/core_extensions/proc_test.rb +41 -0
- data/test/dummy/log/test.log +72489 -0
- data/test/expander_test.rb +55 -13
- data/test/{gara_test.rb → express_templates_test.rb} +0 -0
- data/test/handler_test.rb +4 -4
- data/test/indenter_test.rb +25 -0
- data/test/markup/tag_test.rb +127 -0
- data/test/markup/wrapper_test.rb +42 -0
- data/test/markup/yielder_test.rb +9 -0
- data/test/performance_test.rb +2 -2
- data/test/test_helper.rb +6 -0
- metadata +78 -24
- data/lib/express_templates/component.rb +0 -92
- data/lib/express_templates/components/wrapper.rb +0 -51
- data/lib/express_templates/html5_emitter.rb +0 -45
- data/test/component_test.rb +0 -77
- data/test/wrapper_test.rb +0 -23
@@ -0,0 +1,93 @@
|
|
1
|
+
module ExpressTemplates
|
2
|
+
module Components
|
3
|
+
module Capabilities
|
4
|
+
|
5
|
+
# Adds the capability for a component to render itself in a context.
|
6
|
+
#
|
7
|
+
# Provides both:
|
8
|
+
#
|
9
|
+
# * Rendering::ClassMethods
|
10
|
+
# * Rendering::InstanceMethods
|
11
|
+
#
|
12
|
+
# Used in ExpressTemplates::Components::Base.
|
13
|
+
#
|
14
|
+
module Rendering
|
15
|
+
def self.included(base)
|
16
|
+
base.class_eval do
|
17
|
+
extend ClassMethods
|
18
|
+
include InstanceMethods
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
module ClassMethods
|
23
|
+
|
24
|
+
# Store a block of logic which will be evalutated in a context
|
25
|
+
# during rendering.
|
26
|
+
#
|
27
|
+
# The block will be passed a reference to the component's class.
|
28
|
+
#
|
29
|
+
def using_logic(&block)
|
30
|
+
@control_flow = block
|
31
|
+
end
|
32
|
+
|
33
|
+
# Returns a string containing markup generated by evaluating
|
34
|
+
# blocks of supplied logic or compiled template code in a <tt>context</tt>.
|
35
|
+
#
|
36
|
+
# The context may be any object however, it is generally an
|
37
|
+
# ActionView::Context.
|
38
|
+
#
|
39
|
+
# Components when compiled may yield something like:
|
40
|
+
#
|
41
|
+
# "MyComponent.render(self, {id: 'foo'})"
|
42
|
+
#
|
43
|
+
# The supplied options hash may be used to modify any fragments
|
44
|
+
# or the behavior of any logic.
|
45
|
+
#
|
46
|
+
# If a fragment identifier is passed as a symbol in the first
|
47
|
+
# option position, this will render that fragment only.
|
48
|
+
#
|
49
|
+
def render(context, *opts, &context_logic_block)
|
50
|
+
fragment = opts.shift if opts.first.is_a?(Symbol)
|
51
|
+
begin
|
52
|
+
if fragment
|
53
|
+
context.instance_eval(_lookup(fragment, opts)) || ''
|
54
|
+
else
|
55
|
+
flow = context_logic_block || @control_flow
|
56
|
+
exec_args = [self, opts].take(flow.arity)
|
57
|
+
context.instance_exec(*exec_args, &flow) || ''
|
58
|
+
end
|
59
|
+
rescue => e
|
60
|
+
binding.pry if ENV['DEBUG'].eql?("true")
|
61
|
+
raise "Rendering error in #{self}: #{e.to_s}"
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
private
|
66
|
+
|
67
|
+
def _control_flow
|
68
|
+
@control_flow
|
69
|
+
end
|
70
|
+
|
71
|
+
end
|
72
|
+
|
73
|
+
module InstanceMethods
|
74
|
+
|
75
|
+
def compile
|
76
|
+
if _provides_logic?
|
77
|
+
"#{self.class.to_s}.render(self)"
|
78
|
+
else
|
79
|
+
lookup :markup
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
private
|
84
|
+
|
85
|
+
def _provides_logic?
|
86
|
+
!!self.class.send(:_control_flow)
|
87
|
+
end
|
88
|
+
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
@@ -0,0 +1,196 @@
|
|
1
|
+
module ExpressTemplates
|
2
|
+
module Components
|
3
|
+
module Capabilities
|
4
|
+
|
5
|
+
# The Templating capability module provides Components with the ability
|
6
|
+
# to store, reference and compile template fragments.
|
7
|
+
#
|
8
|
+
# It extends the including class with Templating::ClassMethods.
|
9
|
+
#
|
10
|
+
# It also provides helpers which are snippets of code in the form of
|
11
|
+
# lambdas that may be evaluated in the view context.
|
12
|
+
#
|
13
|
+
module Templating
|
14
|
+
def self.included(base)
|
15
|
+
base.class_eval do
|
16
|
+
extend ClassMethods
|
17
|
+
include InstanceMethods
|
18
|
+
end
|
19
|
+
class << base
|
20
|
+
alias_method :fragments, :emits
|
21
|
+
alias_method :has_markup, :emits
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
module ClassMethods
|
26
|
+
|
27
|
+
# Store fragments of ExpressTemplate style markup for use in
|
28
|
+
# generating the HTML representation of a component.
|
29
|
+
#
|
30
|
+
# For example in your class, simply place the following:
|
31
|
+
#
|
32
|
+
# class MyComponent < ET::Components::Base
|
33
|
+
# emits {
|
34
|
+
# ul {
|
35
|
+
# li "one"
|
36
|
+
# li "two"
|
37
|
+
# li "three"
|
38
|
+
# }
|
39
|
+
# }
|
40
|
+
#
|
41
|
+
# end
|
42
|
+
#
|
43
|
+
# By default this template code is stored under the label :markup
|
44
|
+
#
|
45
|
+
# You may specify several fragments with a hash containing lambdas:
|
46
|
+
#
|
47
|
+
# emits body: -> { li "item" },
|
48
|
+
# wrapper: -> { ul { _yield } }
|
49
|
+
#
|
50
|
+
# This method is aliased as <tt>fragments</tt> and <tt>has_markup</tt>
|
51
|
+
#
|
52
|
+
def emits(*args, &template_code)
|
53
|
+
if args.first.respond_to?(:call) or template_code
|
54
|
+
_store :markup, _compile_fragment(args.first||template_code) # default fragment is named :markup
|
55
|
+
else
|
56
|
+
args.first.to_a.each do |name, block|
|
57
|
+
raise ArgumentError unless name.is_a?(Symbol) and block.is_a?(Proc)
|
58
|
+
_store(name, _compile_fragment(block))
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
def [](label)
|
64
|
+
_lookup(label)
|
65
|
+
end
|
66
|
+
|
67
|
+
# Stores a block given for later evaluation in context.
|
68
|
+
#
|
69
|
+
# Example:
|
70
|
+
#
|
71
|
+
# class TitleComponent < ECB
|
72
|
+
# helper :title_helper do
|
73
|
+
# @resource.name
|
74
|
+
# end
|
75
|
+
#
|
76
|
+
# emits {
|
77
|
+
# h1 {
|
78
|
+
# title_helper
|
79
|
+
# }
|
80
|
+
# }
|
81
|
+
#
|
82
|
+
# end
|
83
|
+
#
|
84
|
+
# In this example <tt>@resource.name</tt> is evaluated in the
|
85
|
+
# provided context during page rendering and not during template
|
86
|
+
# expansion or compilation.
|
87
|
+
#
|
88
|
+
# This is the recommended for encapsulation of "helper" type
|
89
|
+
# functionality which is of concern only to the component and
|
90
|
+
# used only in its own markup fragments.
|
91
|
+
def helper(name, &block)
|
92
|
+
_helpers[name] = block
|
93
|
+
_define_helper_methods name
|
94
|
+
end
|
95
|
+
|
96
|
+
def special_handlers
|
97
|
+
{insert: self, _yield: self}.merge(Hash[*(_helpers.keys.map {|k| [k, self] }.flatten)])
|
98
|
+
end
|
99
|
+
|
100
|
+
protected
|
101
|
+
|
102
|
+
# Stores a fragment for use during compilation and rendering
|
103
|
+
# of a component.
|
104
|
+
def _store(name, fragment)
|
105
|
+
@fragments ||= Hash.new
|
106
|
+
@fragments[name] = fragment
|
107
|
+
end
|
108
|
+
|
109
|
+
# Looks up a template fragment for this component and returns
|
110
|
+
# compiled template code.
|
111
|
+
#
|
112
|
+
# If the template fragment is not already compiled, it compiles it
|
113
|
+
# with the supplied options as locals. Locals may be used within
|
114
|
+
# the template during expansion.
|
115
|
+
#
|
116
|
+
# Returns a string containing ruby code which evaluates to markup.
|
117
|
+
def _lookup(name, options = {})
|
118
|
+
@fragments ||= Hash.new
|
119
|
+
fragment = @fragments[name] or raise "no template fragment supplied for: #{name}"
|
120
|
+
if fragment.kind_of?(Proc)
|
121
|
+
_compile_fragment(fragment, options)
|
122
|
+
else
|
123
|
+
fragment
|
124
|
+
end
|
125
|
+
end
|
126
|
+
|
127
|
+
# Expands and compiles the supplied block representing a
|
128
|
+
# template fragment.
|
129
|
+
#
|
130
|
+
# Any supplied options are passed as locals for use during expansion.
|
131
|
+
#
|
132
|
+
# Returns a string containing ruby code which evaluates to markup.
|
133
|
+
def _compile_fragment(block, options = {})
|
134
|
+
expander = ExpressTemplates::Expander.new(nil, special_handlers, options)
|
135
|
+
expander.expand(&block).map(&:compile).join("+")
|
136
|
+
end
|
137
|
+
|
138
|
+
|
139
|
+
private
|
140
|
+
|
141
|
+
|
142
|
+
def _helpers
|
143
|
+
@helpers ||= Hash.new
|
144
|
+
end
|
145
|
+
|
146
|
+
def _define_helper_methods(name)
|
147
|
+
method_definition= <<-RUBY
|
148
|
+
class << self
|
149
|
+
|
150
|
+
# called during expansion
|
151
|
+
define_method(:#{name}) do |*args|
|
152
|
+
helper_args = %w(self)
|
153
|
+
helper_args += args.map(&:inspect)
|
154
|
+
'\#\{#{self.to_s}._#{name}('+_interpolate(helper_args).join(', ')+')\}'
|
155
|
+
end
|
156
|
+
|
157
|
+
# called during rendering in view context
|
158
|
+
define_method(:_#{name}) do |context, *args|
|
159
|
+
begin
|
160
|
+
helper_proc = _helpers[:#{name}]
|
161
|
+
helper_args = args.take(helper_proc.arity)
|
162
|
+
context.instance_exec *helper_args, &helper_proc
|
163
|
+
rescue => e
|
164
|
+
raise "#{name} raised: \#\{e.to_s\}"
|
165
|
+
end.to_s
|
166
|
+
end
|
167
|
+
end
|
168
|
+
RUBY
|
169
|
+
eval(method_definition)
|
170
|
+
end
|
171
|
+
|
172
|
+
def _interpolate(args)
|
173
|
+
args.map do |arg|
|
174
|
+
if arg.kind_of?(String) && match = arg.match(/"\{\{(.*)\}\}"/)
|
175
|
+
match[1]
|
176
|
+
else
|
177
|
+
arg
|
178
|
+
end
|
179
|
+
end
|
180
|
+
end
|
181
|
+
|
182
|
+
end
|
183
|
+
|
184
|
+
module InstanceMethods
|
185
|
+
|
186
|
+
def lookup(fragment_name)
|
187
|
+
self.class[fragment_name]
|
188
|
+
end
|
189
|
+
|
190
|
+
end
|
191
|
+
|
192
|
+
|
193
|
+
end
|
194
|
+
end
|
195
|
+
end
|
196
|
+
end
|
@@ -0,0 +1,100 @@
|
|
1
|
+
module ExpressTemplates
|
2
|
+
module Components
|
3
|
+
module Capabilities
|
4
|
+
|
5
|
+
# Add the ability for a component template to wrap or decorate a fragment
|
6
|
+
# with another fragment.
|
7
|
+
#
|
8
|
+
# The insertion point for the inner fragment is marked with <tt>_yield</tt>
|
9
|
+
#
|
10
|
+
# Example:
|
11
|
+
#
|
12
|
+
# class MenuComponent < ExpressTemplates::Components::Base
|
13
|
+
#
|
14
|
+
# fragments :menu_item, -> { li { menu_link(item) } },
|
15
|
+
# :menu_wrapper, -> { ul { _yield } }
|
16
|
+
#
|
17
|
+
# for_each -> { menu_items }
|
18
|
+
#
|
19
|
+
# wrap_with :wrapper
|
20
|
+
#
|
21
|
+
# end
|
22
|
+
#
|
23
|
+
# Note this example also uses Capabilities::Iterating.
|
24
|
+
#
|
25
|
+
# Provides:
|
26
|
+
#
|
27
|
+
# * Wrapping::ClassMethods
|
28
|
+
#
|
29
|
+
module Wrapping
|
30
|
+
def self.included(base)
|
31
|
+
base.class_eval do
|
32
|
+
extend ClassMethods
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
module ClassMethods
|
37
|
+
|
38
|
+
# Enclose whatever the component would already generate
|
39
|
+
# inside the specified fragment wherever we encounter _yield
|
40
|
+
#
|
41
|
+
# Note: this must come after any statements that affect the logic
|
42
|
+
# flow.
|
43
|
+
def wrap_with(fragment, dont_wrap_if: -> {false} )
|
44
|
+
wrapper_name(fragment)
|
45
|
+
prior_logic = @control_flow
|
46
|
+
using_logic do |component|
|
47
|
+
if instance_exec(&dont_wrap_if)
|
48
|
+
eval(component.render((self||Object.new), &prior_logic))
|
49
|
+
else
|
50
|
+
component._wrap_it(self, &prior_logic)
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
def wrapper_name(name = nil)
|
56
|
+
if name.nil?
|
57
|
+
@wrapper_name || :markup
|
58
|
+
else
|
59
|
+
@wrapper_name = name
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
# added back in for compatability with prior interface
|
64
|
+
# should probably be refactored away
|
65
|
+
def _wrap_using(fragment, context=nil, options={}, &to_be_wrapped)
|
66
|
+
old_wrapper_name = @wrapper_name
|
67
|
+
@wrapper_name = fragment
|
68
|
+
result = _wrap_it(context, options, &to_be_wrapped)
|
69
|
+
@wrapper_name = old_wrapper_name
|
70
|
+
return result
|
71
|
+
end
|
72
|
+
|
73
|
+
def _wrap_it(context=nil, options={}, &to_be_wrapped)
|
74
|
+
body = ''
|
75
|
+
if to_be_wrapped
|
76
|
+
body = render((context||Object.new), &to_be_wrapped)
|
77
|
+
end
|
78
|
+
if compiled_src = _lookup(wrapper_name, options)
|
79
|
+
if context.nil?
|
80
|
+
eval(compiled_src).gsub(/\{\{_yield\}\}/, body)
|
81
|
+
else
|
82
|
+
ctx = context.instance_eval("binding")
|
83
|
+
ctx.local_variable_set(:_yield, body)
|
84
|
+
ctx.eval(compiled_src)
|
85
|
+
end
|
86
|
+
else
|
87
|
+
raise "No wrapper fragment provided for '#{wrapper_name}'"
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
def _yield(*args)
|
92
|
+
"{{_yield}}"
|
93
|
+
end
|
94
|
+
|
95
|
+
end
|
96
|
+
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
module ExpressTemplates
|
2
|
+
module Components
|
3
|
+
# Provide hidden fields such as the authenticity token
|
4
|
+
# and the utf8 enforcer tag as well as a method tag as
|
5
|
+
# would be provided by Rails' form helpers.
|
6
|
+
#
|
7
|
+
# An optional method may be speficied. Defaults to 'post'.
|
8
|
+
class FormRailsSupport < Base
|
9
|
+
include Capabilities::Configurable
|
10
|
+
emits {
|
11
|
+
div(style: 'display:none') {
|
12
|
+
utf8_enforcer_tag
|
13
|
+
method_tag(my[:id] || :post)
|
14
|
+
token_tag
|
15
|
+
}
|
16
|
+
}
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
module ExpressTemplates
|
2
|
+
module Components
|
3
|
+
# A Row is a Container implemented as a div with
|
4
|
+
# a CSS class "row"
|
5
|
+
#
|
6
|
+
# An optional dom ID may be specified as a symbol.
|
7
|
+
#
|
8
|
+
# Example:
|
9
|
+
#
|
10
|
+
# row(:main) {
|
11
|
+
# p "Some content"
|
12
|
+
# }
|
13
|
+
#
|
14
|
+
# This will render as:
|
15
|
+
#
|
16
|
+
# <div id="main" class="row"><p>Some content</p></div>
|
17
|
+
#
|
18
|
+
class Row < Container
|
19
|
+
include Capabilities::Configurable
|
20
|
+
|
21
|
+
emits {
|
22
|
+
div.row(my[:id]) {
|
23
|
+
_yield
|
24
|
+
}
|
25
|
+
}
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|