express_templates 0.2.7 → 0.3.0

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 (41) hide show
  1. checksums.yaml +4 -4
  2. data/lib/core_extensions/proc.rb +72 -20
  3. data/lib/express_templates/compiler.rb +9 -1
  4. data/lib/express_templates/components/base.rb +1 -1
  5. data/lib/express_templates/components/capabilities/building.rb +8 -0
  6. data/lib/express_templates/components/capabilities/conditionality.rb +8 -6
  7. data/lib/express_templates/components/capabilities/configurable.rb +3 -4
  8. data/lib/express_templates/components/capabilities/iterating.rb +18 -23
  9. data/lib/express_templates/components/capabilities/parenting.rb +4 -18
  10. data/lib/express_templates/components/capabilities/rendering.rb +2 -65
  11. data/lib/express_templates/components/capabilities/templating.rb +24 -22
  12. data/lib/express_templates/components/capabilities/wrapping.rb +23 -39
  13. data/lib/express_templates/components/column.rb +1 -1
  14. data/lib/express_templates/components/for_each.rb +28 -0
  15. data/lib/express_templates/components/form_for.rb +19 -0
  16. data/lib/express_templates/components/form_rails_support.rb +1 -1
  17. data/lib/express_templates/components/null_wrap.rb +9 -0
  18. data/lib/express_templates/components/row.rb +1 -1
  19. data/lib/express_templates/components/table_for.rb +119 -0
  20. data/lib/express_templates/components/tree_for.rb +41 -0
  21. data/lib/express_templates/components/unless_block.rb +40 -0
  22. data/lib/express_templates/components.rb +4 -0
  23. data/lib/express_templates/expander.rb +15 -4
  24. data/lib/express_templates/indenter.rb +8 -5
  25. data/lib/express_templates/markup/tag.rb +62 -30
  26. data/lib/express_templates/markup/wrapper.rb +1 -1
  27. data/lib/express_templates/version.rb +1 -1
  28. data/test/components/base_test.rb +5 -38
  29. data/test/components/conditionality_test.rb +1 -1
  30. data/test/components/configurable_test.rb +2 -2
  31. data/test/components/container_test.rb +1 -1
  32. data/test/components/iterating_test.rb +30 -9
  33. data/test/components/table_for_test.rb +116 -0
  34. data/test/core_extensions/proc_test.rb +35 -5
  35. data/test/dummy/log/test.log +645 -0
  36. data/test/fixtures/{a_big_page.html → a_big_page2.html} +0 -0
  37. data/test/indenter_test.rb +6 -4
  38. data/test/markup/tag_test.rb +15 -2
  39. data/test/performance_test.rb +1 -1
  40. data/test/test_helper.rb +2 -0
  41. metadata +26 -3
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: b560214168f6753cf3a9d186ee89c8a12f277816
4
- data.tar.gz: 99a27ad570c78b032251ce6c6472c2aebbfb8dc0
3
+ metadata.gz: 16e00189a36058717818d7a7c66638c0164fcef6
4
+ data.tar.gz: 559bf6b66e66bf2caacd9c7594a9227ed2208cee
5
5
  SHA512:
6
- metadata.gz: 16523908d29f4f748c767a40b98a45e97b69c7e33b5ee5bb3e0085438e2a8282929ed60e176a12dfacc97ea6673cb04582c0ad3f1e6b2f3af69c93ba62a96e1d
7
- data.tar.gz: 2885c82c2e0b31dc34866324d701362d2ff007b316e821a1713588362dad690e873430be2f906b371e5201c9a9b6205aa1aa72ca8515a084fd5eb199edcde50d
6
+ metadata.gz: bfbbac386e1b3db30466f8517dc7c9a82f65ebce01c548357ba2754399f3808ef9601eb157cc78775fc67f571930f06dfc4da57ed7ee57e267b5624ad9a82b83
7
+ data.tar.gz: 20b4d748cb8780f9366ae4875f1795af827ce0db5b59c128d096a5cb47de38d7639bb90d55d53183ce4df2349341d68d52e6084f328e510090cc899ae706fb07
@@ -2,9 +2,13 @@ require 'ripper'
2
2
  require 'pp'
3
3
  class Proc
4
4
 
5
- TOKEN_PAIRS = {[:on_lbrace, '{'] => [:on_rbrace, '}'],
5
+ TLAMBEG = [:on_tlambeg, "{"]
6
+ TLAMBDA = [:on_tlambda, "->"]
7
+ LBRACE = [:on_lbrace, '{']
8
+
9
+ TOKEN_PAIRS = {LBRACE => [:on_rbrace, '}'],
6
10
  [:on_kw, 'do'] => [:on_kw, 'end'],
7
- [:on_tlambeg, '{'] => [:on_rbrace, '}']}
11
+ TLAMBDA => [:on_rbrace, '}']}
8
12
 
9
13
  # Make a best effort to provide the original source for a block
10
14
  # based on extracting a string from the file identified in
@@ -18,25 +22,73 @@ class Proc
18
22
  # If you require only the source of blocks-within-other-blocks, start them
19
23
  # on a new line as would be best practice for clarity and readability.
20
24
  def source
21
- file, line_no = source_location
22
- raise "no line number provided for source_location: #{self}" if line_no.nil?
23
- tokens = Ripper.lex File.read(file)
24
- tokens_on_line = tokens.select {|pos, lbl, str| pos[0].eql?(line_no) }
25
- starting_token = tokens_on_line.detect do |pos, lbl, str|
26
- TOKEN_PAIRS.keys.include? [lbl, str]
25
+ @source ||= begin
26
+ file, line_no = source_location
27
+ raise "no line number provided for source_location: #{self}" if line_no.nil?
28
+ tokens = Ripper.lex File.read(file)
29
+ tokens_on_line = tokens.select {|pos, lbl, str| pos[0].eql?(line_no) }
30
+ starting_token = tokens_on_line.detect do |pos, lbl, str|
31
+ TOKEN_PAIRS.keys.include? [lbl, str]
32
+ end
33
+ starting_token_type = [starting_token[1], starting_token[2]]
34
+ ending_token_type = TOKEN_PAIRS[starting_token_type]
35
+ source_str = ""
36
+ remaining_tokens = tokens[tokens.index(starting_token)..-1]
37
+ nesting = -1
38
+ starting_nesting_token_types = if [TLAMBDA, LBRACE].include?(starting_token_type)
39
+ [TLAMBDA, LBRACE]
40
+ else
41
+ [starting_token_type]
42
+ end
43
+
44
+ while token = remaining_tokens.shift
45
+ token = [token[1], token[2]] # strip position
46
+ source_str << token[1]
47
+ nesting += 1 if starting_nesting_token_types.include? token
48
+ is_ending_token = token.eql?(ending_token_type)
49
+ break if is_ending_token && nesting.eql?(0)
50
+ nesting -= 1 if is_ending_token
51
+ end
52
+ source_str
53
+ end
54
+ end
55
+
56
+ # Examines the source of a proc to extract the body by
57
+ # removing the outermost block delimiters and any surrounding.
58
+ # whitespace.
59
+ #
60
+ # Raises exception if the block takes arguments.
61
+ #
62
+ def source_body
63
+ raise "Cannot extract proc body on non-zero arity" unless arity.eql?(0)
64
+ tokens = Ripper.lex source
65
+ body_start_idx = 2
66
+ body_end_idx = -1
67
+ if tokens[0][1].eql?(:on_tlambda)
68
+ body_start_idx = tokens.index(tokens.detect { |t| t[1].eql?(:on_tlambeg) }) + 1
27
69
  end
28
- starting_token_type = [starting_token[1], starting_token[2]]
29
- ending_token_type = TOKEN_PAIRS[starting_token_type]
30
- source_str = ""
31
- remaining_tokens = tokens.slice(tokens.index(starting_token)..-1)
32
- nesting = -1
33
- while token = remaining_tokens.shift
34
- source_str << token[2]
35
- nesting += 1 if [token[1], token[2]] == starting_token_type
36
- is_ending_token = [token[1], token[2]].eql?(ending_token_type)
37
- break if is_ending_token && nesting.eql?(0)
38
- nesting -= 1 if is_ending_token
70
+ body_tokens = tokens[body_start_idx..-1]
71
+
72
+ body_tokens.pop # ending token of proc
73
+ # remove trailing whitespace
74
+ whitespace = [:on_sp, :on_nl, :on_ignored_nl]
75
+ body_tokens.pop while whitespace.include?(body_tokens[-1][1])
76
+ # remove leading whitespace
77
+ body_tokens.shift while whitespace.include?(body_tokens[0][1])
78
+
79
+ # put them back together
80
+ body_tokens.map {|token| token[2] }.join
81
+ end
82
+
83
+ def self.from_source(prc_src)
84
+ raise ArgumentError unless prc_src.kind_of?(String)
85
+ prc = begin
86
+ eval(prc_src)
87
+ rescue ArgumentError => e
88
+ binding.pry
39
89
  end
40
- source_str
90
+ prc.instance_variable_set(:@source, prc_src)
91
+ prc
41
92
  end
93
+
42
94
  end
@@ -5,7 +5,15 @@ module ExpressTemplates
5
5
 
6
6
  expander = Expander.new(template)
7
7
 
8
- compiled = expander.expand(src, &block).map(&:compile)
8
+ Thread.current[:first_whitepace_removed] ||= 0
9
+ Thread.current[:first_whitepace_removed] += 1
10
+ begin
11
+ compiled = expander.expand(src, &block).map(&:compile)
12
+ compiled.first.sub!(/^"\n+/, '"') if Thread.current[:first_whitepace_removed].eql?(1)
13
+ Thread.current[:first_whitepace_removed] -= 1
14
+ ensure
15
+ Thread.current[:first_whitepace_removed] = nil if Thread.current[:first_whitepace_removed].eql?(0)
16
+ end
9
17
 
10
18
  return compiled.join("+").gsub('"+"', '').tap do |s|
11
19
  puts("\n"+template.inspect+"\n"+s) if ENV['DEBUG'].eql?('true')
@@ -37,7 +37,7 @@ module ExpressTemplates
37
37
  # * Capabilities::Wrapping
38
38
  # * Capabilities::Iterating
39
39
  #
40
- class Base
40
+ class Base < Expander
41
41
  include ExpressTemplates::Macro
42
42
  include Capabilities::Templating
43
43
  include Capabilities::Rendering
@@ -0,0 +1,8 @@
1
+ module ExpressTemplates
2
+ module Components
3
+ module Capabilities
4
+ module Building
5
+ end
6
+ end
7
+ end
8
+ end
@@ -37,12 +37,14 @@ module ExpressTemplates
37
37
  end
38
38
 
39
39
  def only_if condition_proc
40
- @condition_proc = condition_proc
41
-
42
- using_logic do |component, options|
43
- condition = instance_exec(&component.condition_proc)
44
- eval(component[:markup]) if condition
45
- end
40
+ @condition_proc = Proc.from_source "-> {!(#{condition_proc.source_body})}"
41
+ inner_src = self[:markup]
42
+ fragment_src = %Q(-> {
43
+ unless_block(Proc.from_source(#{@condition_proc.source.inspect})) {
44
+ #{inner_src.source_body}
45
+ }
46
+ })
47
+ _store :markup, Proc.from_source(fragment_src)
46
48
  end
47
49
 
48
50
  end
@@ -28,7 +28,6 @@ module ExpressTemplates
28
28
  # Stores arguments for later processing, eg., compile time
29
29
  def initialize(*args)
30
30
  @args = args.dup
31
- @config = {}
32
31
  _process_args!(args)
33
32
  super(*args)
34
33
  end
@@ -57,7 +56,7 @@ module ExpressTemplates
57
56
  module InstanceMethods
58
57
 
59
58
  def config
60
- @config
59
+ @config ||= {}
61
60
  end
62
61
 
63
62
  alias :my :config
@@ -67,8 +66,8 @@ module ExpressTemplates
67
66
  end
68
67
 
69
68
  # Override Templating#lookup to pass locals
70
- def lookup(fragment_name)
71
- self.class.send(:_lookup, fragment_name, expand_locals)
69
+ def lookup(fragment_name, options = {})
70
+ super(fragment_name, options.merge(expand_locals))
72
71
  end
73
72
 
74
73
 
@@ -47,31 +47,26 @@ module ExpressTemplates
47
47
  # An <tt>:empty</tt> option specifies a fragment to use for the
48
48
  # empty state when the iterator returns an empty collection.
49
49
  def for_each(iterator, as: :item, emit: :markup, empty: nil)
50
- if iterator.kind_of?(Symbol)
51
- var_name = iterator.to_s.gsub(/^@/,'').singularize.to_sym
50
+ as = as.to_sym
51
+ emit = emit.to_sym
52
+ iterator = iterator.kind_of?(Proc) ? iterator.source : iterator
53
+ fragment_src = if empty
54
+ %Q(-> {
55
+ unless_block(Proc.from_source("-> {#{iterator}.call.empty?}"), alt: #{self[empty].source}) {
56
+ for_each(Proc.from_source("#{iterator}"), as: #{as.inspect}) {
57
+ #{self[emit].source_body}
58
+ }
59
+ }
60
+ })
52
61
  else
53
- var_name = as
54
- end
55
- using_logic do |component, options|
56
- collection = if iterator.kind_of?(Proc)
57
- if iterator.arity.eql?(1)
58
- instance_exec(options, &iterator)
59
- else
60
- instance_exec &iterator
61
- end
62
- else
63
- eval(iterator.to_s)
64
- end
65
- if collection.empty?
66
- empty ? component[empty] : ''
67
- else
68
- collection.map do |item|
69
- b = binding
70
- b.local_variable_set(var_name, item)
71
- b.eval(component[emit], __FILE__)
72
- end.join
73
- end
62
+ %Q(-> {
63
+ for_each(Proc.from_source("#{iterator}"), as: #{as.inspect}) {
64
+ #{self[emit].source_body}
65
+ }
66
+ })
74
67
  end
68
+ fragment = Proc.from_source(fragment_src)
69
+ _store :markup, fragment
75
70
  end
76
71
  end
77
72
  end
@@ -30,20 +30,10 @@ module ExpressTemplates
30
30
  #
31
31
  def self.included(base)
32
32
  base.class_eval do
33
- extend ClassMethods
34
33
  include InstanceMethods
35
34
  end
36
35
  end
37
36
 
38
- module ClassMethods
39
- def render_with_children(context, locals = {}, child_markup_src = nil)
40
- _wrap_it(context, locals) do |component|
41
- child_markup_src
42
- end
43
- end
44
-
45
- end
46
-
47
37
  module InstanceMethods
48
38
  def children
49
39
  @children ||= []
@@ -54,13 +44,9 @@ module ExpressTemplates
54
44
  end
55
45
 
56
46
  def compile
57
- locals = (expand_locals rescue nil).inspect
58
- args = %w(self)
59
- args << locals
60
- compiled_children = compile_children
61
- args << compiled_children unless compiled_children.empty?
62
- closing_paren = compiled_children.empty? ? ')' : "\n#{Indenter.for(:compile)})"
63
- "#{self.class.to_s}.render_with_children(#{args.join(', ')}#{closing_paren}"
47
+ null_wrapped_children = "null_wrap { #{compile_children} }"
48
+ wrap_children_src = self.class[:markup].source.gsub(/\W_yield\W/, null_wrapped_children)
49
+ _compile_fragment(Proc.from_source(wrap_children_src))
64
50
  end
65
51
 
66
52
  def compile_children
@@ -69,7 +55,7 @@ module ExpressTemplates
69
55
  compiled_children = children.map do |child|
70
56
  indent_with_newline +
71
57
  (child.compile rescue %Q("#{child}")) # Bare strings may be children
72
- end.join("+")
58
+ end.join("+\n")
73
59
  compiled_children.gsub!('"+"', '') # avoid unnecessary string concatenation
74
60
  end
75
61
  return compiled_children
@@ -15,78 +15,15 @@ module ExpressTemplates
15
15
  def self.included(base)
16
16
  base.class_eval do
17
17
  extend ClassMethods
18
- include InstanceMethods
19
18
  end
20
19
  end
21
20
 
22
21
  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
22
+ def render_in(context, &view_code)
23
+ context.instance_eval(&view_code)
63
24
  end
64
-
65
- private
66
-
67
- def _control_flow
68
- @control_flow
69
- end
70
-
71
25
  end
72
26
 
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
27
  end
91
28
  end
92
29
  end
@@ -51,11 +51,13 @@ module ExpressTemplates
51
51
  #
52
52
  def emits(*args, &template_code)
53
53
  if args.first.respond_to?(:call) or template_code
54
- _store :markup, _compile_fragment(args.first||template_code) # default fragment is named :markup
54
+ fragment = (args.first||template_code)
55
+ raise "must use stabby lambda" unless fragment.lambda?
56
+ _store :markup, fragment# default fragment is named :markup
55
57
  else
56
58
  args.first.to_a.each do |name, block|
57
59
  raise ArgumentError unless name.is_a?(Symbol) and block.is_a?(Proc)
58
- _store(name, _compile_fragment(block))
60
+ _store(name, block)
59
61
  end
60
62
  end
61
63
  end
@@ -116,26 +118,9 @@ module ExpressTemplates
116
118
  # Returns a string containing ruby code which evaluates to markup.
117
119
  def _lookup(name, options = {})
118
120
  @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
121
+ @fragments[name] or binding.pry #raise "no template fragment supplied for: #{name}"
125
122
  end
126
123
 
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
124
  private
140
125
 
141
126
 
@@ -183,10 +168,27 @@ module ExpressTemplates
183
168
 
184
169
  module InstanceMethods
185
170
 
186
- def lookup(fragment_name)
187
- self.class[fragment_name]
171
+ def lookup(fragment_name, options={})
172
+ fragment = self.class[fragment_name]
173
+ if fragment.kind_of?(Proc)
174
+ _compile_fragment(fragment, options)
175
+ else
176
+ fragment
177
+ end
188
178
  end
189
179
 
180
+ # Expands and compiles the supplied block representing a
181
+ # template fragment.
182
+ #
183
+ # Any supplied options are passed as locals for use during expansion.
184
+ #
185
+ # Returns a string containing ruby code which evaluates to markup.
186
+ def _compile_fragment(block, options = {})
187
+ initialize_expander(nil, self.class.special_handlers, options)
188
+ expand(&block).map(&:compile).join("+").gsub('"+"', '')
189
+ end
190
+
191
+
190
192
  end
191
193
 
192
194
 
@@ -30,6 +30,13 @@ module ExpressTemplates
30
30
  def self.included(base)
31
31
  base.class_eval do
32
32
  extend ClassMethods
33
+ include InstanceMethods
34
+ end
35
+ end
36
+
37
+ module InstanceMethods
38
+ def compile
39
+ lookup :markup
33
40
  end
34
41
  end
35
42
 
@@ -37,19 +44,24 @@ module ExpressTemplates
37
44
 
38
45
  # Enclose whatever the component would already generate
39
46
  # 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} )
47
+ def wrap_with(fragment, dont_wrap_if: false )
44
48
  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
49
+ wrapper_src = _lookup(fragment).source
50
+ inner_src = _lookup(:markup).source_body
51
+ wrapped_src = wrapper_src.gsub!(/\W_yield\W/, inner_src)
52
+
53
+ fragment_src = if dont_wrap_if
54
+ %Q(-> {
55
+ unless_block(Proc.from_source(#{dont_wrap_if.source.inspect}), alt: Proc.from_source(%q(-> {#{inner_src}}))) {
56
+ #{Proc.from_source(wrapped_src).source_body}
57
+ }
58
+ })
59
+ else
60
+ wrapped_src
52
61
  end
62
+
63
+ _store :markup, Proc.from_source(fragment_src)
64
+
53
65
  end
54
66
 
55
67
  def wrapper_name(name = nil)
@@ -60,34 +72,6 @@ module ExpressTemplates
60
72
  end
61
73
  end
62
74
 
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
75
  def _yield(*args)
92
76
  "{{_yield}}"
93
77
  end
@@ -3,7 +3,7 @@ module ExpressTemplates
3
3
  class Column < Container
4
4
  include Capabilities::Configurable
5
5
 
6
- emits {
6
+ emits -> {
7
7
  div.column(my[:id]) {
8
8
  _yield
9
9
  }
@@ -0,0 +1,28 @@
1
+ module ExpressTemplates
2
+ module Components
3
+ class ForEach < Components::Container
4
+ attr :collection, :member
5
+
6
+ def initialize(*args)
7
+ iterator = args.shift
8
+ options = args.first.kind_of?(Hash) ? args.shift : {}
9
+ expander = args.shift
10
+ @collection, @member = nil, (options[:as]||"item")
11
+ if iterator.kind_of?(Symbol)
12
+ @collection = iterator.to_s
13
+ @member = collection.sub(/^@/, '').singularize
14
+ elsif iterator.kind_of?(Proc)
15
+ @collection = "(#{iterator.source}.call)"
16
+ elsif iterator.kind_of?(String)
17
+ @collection = "(#{iterator}.call)"
18
+ else
19
+ raise "ForEach unknown iterator: #{iterator}"
20
+ end
21
+ end
22
+
23
+ def compile
24
+ %Q((#{@collection}.each_with_index.map do |#{@member}, #{@member}_index|#{compile_children}\nend).join)
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,19 @@
1
+ module ExpressTemplates
2
+ module Components
3
+ #
4
+ # Create an html <tt>form</tt> for a model object.
5
+ #
6
+ # Example:
7
+ #
8
+ # ```ruby
9
+ # form_for(:people) do |t|
10
+ # t.text_field :name
11
+ # t.email_field :email
12
+ # t.phone_field :phone
13
+ # end
14
+ # ```
15
+ #
16
+ class FormFor < Container
17
+ end
18
+ end
19
+ end
@@ -7,7 +7,7 @@ module ExpressTemplates
7
7
  # An optional method may be speficied. Defaults to 'post'.
8
8
  class FormRailsSupport < Base
9
9
  include Capabilities::Configurable
10
- emits {
10
+ emits -> {
11
11
  div(style: 'display:none') {
12
12
  utf8_enforcer_tag
13
13
  method_tag(my[:id] || :post)
@@ -0,0 +1,9 @@
1
+ module ExpressTemplates
2
+ module Components
3
+ class NullWrap < Components::Container
4
+ def compile
5
+ compile_children
6
+ end
7
+ end
8
+ end
9
+ end
@@ -18,7 +18,7 @@ module ExpressTemplates
18
18
  class Row < Container
19
19
  include Capabilities::Configurable
20
20
 
21
- emits {
21
+ emits -> {
22
22
  div.row(my[:id]) {
23
23
  _yield
24
24
  }