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.
Files changed (55) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +10 -6
  3. data/lib/core_extensions/proc.rb +42 -0
  4. data/lib/express_templates.rb +7 -2
  5. data/lib/express_templates/compiler.rb +27 -0
  6. data/lib/express_templates/components.rb +7 -4
  7. data/lib/express_templates/components/base.rb +54 -0
  8. data/lib/express_templates/components/capabilities/conditionality.rb +52 -0
  9. data/lib/express_templates/components/capabilities/configurable.rb +91 -0
  10. data/lib/express_templates/components/capabilities/iterating.rb +80 -0
  11. data/lib/express_templates/components/capabilities/parenting.rb +74 -0
  12. data/lib/express_templates/components/capabilities/rendering.rb +93 -0
  13. data/lib/express_templates/components/capabilities/templating.rb +196 -0
  14. data/lib/express_templates/components/capabilities/wrapping.rb +100 -0
  15. data/lib/express_templates/components/column.rb +13 -0
  16. data/lib/express_templates/components/container.rb +7 -0
  17. data/lib/express_templates/components/form_rails_support.rb +19 -0
  18. data/lib/express_templates/components/row.rb +28 -0
  19. data/lib/express_templates/expander.rb +51 -35
  20. data/lib/express_templates/indenter.rb +44 -0
  21. data/lib/express_templates/macro.rb +44 -0
  22. data/lib/express_templates/markup.rb +9 -0
  23. data/lib/express_templates/{components → markup}/html_tag.rb +9 -3
  24. data/lib/express_templates/markup/tag.rb +118 -0
  25. data/lib/express_templates/markup/wrapper.rb +88 -0
  26. data/lib/express_templates/{components → markup}/yielder.rb +2 -2
  27. data/lib/express_templates/renderer.rb +3 -8
  28. data/lib/express_templates/template/handler.rb +1 -1
  29. data/lib/express_templates/version.rb +1 -1
  30. data/lib/tasks/{gara_tasks.rake → express_templates.rake} +0 -0
  31. data/test/compiler_test.rb +9 -0
  32. data/test/components/base_test.rb +77 -0
  33. data/test/components/column_test.rb +11 -0
  34. data/test/components/conditionality_test.rb +37 -0
  35. data/test/components/configurable_test.rb +43 -0
  36. data/test/components/container_test.rb +49 -0
  37. data/test/components/iterating_test.rb +85 -0
  38. data/test/components/row_test.rb +16 -0
  39. data/test/core_extensions/proc_test.rb +41 -0
  40. data/test/dummy/log/test.log +72489 -0
  41. data/test/expander_test.rb +55 -13
  42. data/test/{gara_test.rb → express_templates_test.rb} +0 -0
  43. data/test/handler_test.rb +4 -4
  44. data/test/indenter_test.rb +25 -0
  45. data/test/markup/tag_test.rb +127 -0
  46. data/test/markup/wrapper_test.rb +42 -0
  47. data/test/markup/yielder_test.rb +9 -0
  48. data/test/performance_test.rb +2 -2
  49. data/test/test_helper.rb +6 -0
  50. metadata +78 -24
  51. data/lib/express_templates/component.rb +0 -92
  52. data/lib/express_templates/components/wrapper.rb +0 -51
  53. data/lib/express_templates/html5_emitter.rb +0 -45
  54. data/test/component_test.rb +0 -77
  55. 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,13 @@
1
+ module ExpressTemplates
2
+ module Components
3
+ class Column < Container
4
+ include Capabilities::Configurable
5
+
6
+ emits {
7
+ div.column(my[:id]) {
8
+ _yield
9
+ }
10
+ }
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,7 @@
1
+ module ExpressTemplates
2
+ module Components
3
+ class Container < Base
4
+ include Capabilities::Parenting
5
+ end
6
+ end
7
+ 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