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.
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