express_templates 0.2.0 → 0.2.2
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/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
|