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
@@ -4,72 +4,87 @@ module ExpressTemplates
4
4
 
5
5
  cattr_accessor :module_search_space
6
6
 
7
- attr_accessor :stack
7
+ attr_accessor :stack, :handlers, :locals
8
8
 
9
- def self.expand(template, source)
10
- expanded = new(template).expand(source)
11
- compiled = expanded.map(&:compile)
12
- return compiled.join("+")
13
- end
14
-
15
- def initialize(template)
9
+ def initialize(template, handlers = {}, locals = {})
16
10
  @template = template
17
11
  @stack = Stack.new
12
+ @handlers = handlers
13
+ @locals = locals
18
14
  end
19
15
 
20
16
  def expand(source=nil, &block)
21
- if source
22
- modified = source.gsub(/(\W)(yield)(\.*)?/, '\1 (stack << ExpressTemplates::Components::Yielder.new(\3))')
23
- modified.gsub!(/(\W)(@\w+)(\W)?/, '\1 (stack << ExpressTemplates::Components::Wrapper.new("\2") )\3')
17
+ case
18
+ when block.nil? && source
19
+ modified = _wrap_instance_vars( _replace_yield_with_yielder(source) )
24
20
  instance_eval(modified, @template.inspect)
25
- stack.current
21
+ when block
22
+ instance_exec &block
26
23
  else
27
- instance_eval &block
28
- stack.current
24
+ raise ArgumentError
29
25
  end
26
+ stack.current
27
+ end
28
+
29
+ def process_children!(parent, &block)
30
+ begin
31
+ stack.descend!
32
+ result = instance_exec &block
33
+ if stack.current.empty? && result.is_a?(String)
34
+ stack << result
35
+ end
36
+ parent.children += stack.current
37
+ stack.ascend!
38
+ end
39
+ stack.current
30
40
  end
31
41
 
32
42
  # define a "macro" method for a component
33
43
  # these methods accept args which are passed to the
34
44
  # initializer for the component
35
- # blocks supplied are evaluated and any returned objects are
36
- # added as children to the component
45
+ # blocks supplied are evaluated and children added to the "stack"
46
+ # are added as children to the component
37
47
  def self.register_macros_for(*components)
38
48
  components.each do |component|
39
49
  define_method(component.macro_name.to_sym) do |*args, &block|
40
- stack << if block
41
- begin
42
- stack.descend!
43
- block.call
44
- # anything stored on stack.current or on stack.next is added as a child
45
- # this is a bit problematic in the case where we would have
46
- # blocks and helpers or locals mixed
47
- component.new(*(args.push(*(stack.current))))
48
- ensure
49
- stack.ascend!
50
- end
51
- else
52
- component.new(*(args))
53
- end
50
+ new_component = component.new(*(args.push(self)))
51
+ process_children!(new_component, &block) unless block.nil?
52
+ stack << new_component
54
53
  end
55
54
  end
56
55
  end
57
56
 
58
57
 
59
- @module_search_space = [ExpressTemplates::Components]
58
+ @module_search_space = [ExpressTemplates::Markup, ExpressTemplates::Components]
60
59
 
61
60
  @module_search_space.each do |mod|
62
61
  register_macros_for(*
63
62
  mod.constants.map { |const| [mod.to_s, const.to_s].join("::").constantize }.
64
- select { |klass| klass.ancestors.include? (ExpressTemplates::Component) }
63
+ select { |klass| klass.ancestors.include? (ExpressTemplates::Markup::Tag) }
65
64
  )
66
65
  end
67
66
 
68
- def method_missing(name, *args)
69
- stack.current << ExpressTemplates::Components::Wrapper.new(name.to_s, *args)
67
+ def method_missing(name, *args, &block)
68
+ return locals[name] if locals.keys.include?(name)
69
+
70
+ if handlers.keys.include?(name)
71
+ stack << handlers[name].send(name, *args)
72
+ else
73
+ stack << ExpressTemplates::Markup::Wrapper.new(name.to_s, *args, &block)
74
+ end
70
75
  nil
71
76
  end
72
77
 
78
+ private
79
+
80
+ def _replace_yield_with_yielder(source)
81
+ source.gsub(/(\W)(yield)(\([^\)]*\))?/, '\1 (stack << ExpressTemplates::Markup::Yielder.new\3)')
82
+ end
83
+
84
+ def _wrap_instance_vars(source)
85
+ source.gsub(/(\W)(@\w+)(\W)?/, '\1 (stack << ExpressTemplates::Markup::Wrapper.new("\2") )\3')
86
+ end
87
+
73
88
  class Stack
74
89
  def initialize
75
90
  @stack = [[]]
@@ -82,7 +97,8 @@ module ExpressTemplates
82
97
 
83
98
  def dump
84
99
  puts "Current frame: #{@frame}"
85
- puts all.map(&:inspect).join("\n")
100
+ require 'pp'
101
+ pp all
86
102
  end
87
103
 
88
104
  def <<(child)
@@ -0,0 +1,44 @@
1
+ module ExpressTemplates
2
+
3
+ # Tracks current indent level scoped to the current thread.
4
+ #
5
+ # May be used to track multiple indents simultaneously through
6
+ # namespacing.
7
+ class Indenter
8
+
9
+ DEFAULT = 2
10
+ WHITESPACE = " "*DEFAULT
11
+
12
+ # Returns whitespace for the named indenter or yields to a block
13
+ # for the named indentor.
14
+ #
15
+ # The block is passed the current whitespace indent.
16
+ #
17
+ # For convenience an optional second parameter is passed to the block
18
+ # containing a newline at the beginning of the indent.
19
+ def self.for name
20
+ if block_given?
21
+ current_indenters[name] += 1
22
+ begin
23
+ indent = WHITESPACE * current_indenters[name]
24
+ yield indent, "\n#{indent}"
25
+ ensure
26
+ current_indenters[name] -= 1
27
+ # if we have long-lived threads for some reason
28
+ # we want to clean up after ourselves
29
+ current_indenters.delete(name) if current_indenters[name].eql?(0)
30
+ end
31
+ else
32
+ return WHITESPACE * current_indenters[name]
33
+ end
34
+ end
35
+
36
+ private
37
+ # For thread safety, scope indentation to the current thread
38
+ def self.current_indenters
39
+ Thread.current[:indenters] ||= Hash.new {|hsh, key| hsh[key] = 0 }
40
+ end
41
+
42
+ end
43
+
44
+ end
@@ -0,0 +1,44 @@
1
+ module ExpressTemplates
2
+ module Macro
3
+
4
+ def self.included(base)
5
+ base.class_eval do
6
+ extend ClassMethods
7
+ include InstanceMethods
8
+ end
9
+ end
10
+
11
+ module ClassMethods
12
+ def macro_name
13
+ to_s.split('::').last.underscore
14
+ end
15
+ end
16
+ module InstanceMethods
17
+ def macro_name ; self.class.macro_name end
18
+
19
+ def initialize(*children_or_options)
20
+ @children = []
21
+ @options = {}.with_indifferent_access
22
+ # expander passes itself as last arg
23
+ @expander = children_or_options.pop if children_or_options.last.kind_of?(ExpressTemplates::Expander)
24
+ _process(*children_or_options)
25
+ end
26
+
27
+ private
28
+
29
+ def _process(*children_or_options)
30
+ children_or_options.each do |child_or_option|
31
+ case
32
+ when child_or_option.kind_of?(Hash)
33
+ @options.merge!(child_or_option)
34
+ when child_or_option.kind_of?(Symbol)
35
+ @options.merge!(id: child_or_option.to_s)
36
+ else
37
+ @children << child_or_option
38
+ end
39
+ end
40
+ end
41
+
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,9 @@
1
+ module ExpressTemplates
2
+ module Markup
3
+ end
4
+ end
5
+
6
+ require 'express_templates/markup/tag'
7
+ require 'express_templates/markup/html_tag'
8
+ require 'express_templates/markup/wrapper'
9
+ require 'express_templates/markup/yielder'
@@ -1,6 +1,6 @@
1
1
  module ExpressTemplates
2
- module Components
3
- class HtmlTag < ExpressTemplates::Component
2
+ module Markup
3
+ class HtmlTag < Tag
4
4
  TAGS = [ :a, :abbr, :address, :area, :article, :aside, :audio,
5
5
  :b, :base, :bdi, :bdo, :blockquote, :body, :br, :button,
6
6
  :canvas, :caption, :cite, :code, :col, :colgroup,
@@ -29,7 +29,13 @@ module ExpressTemplates
29
29
 
30
30
  HtmlTag::TAGS.each do |tag|
31
31
  klass = tag.to_s.titleize
32
- ExpressTemplates::Components.module_eval "class #{klass} < HtmlTag ; end"
32
+ ExpressTemplates::Markup.module_eval "class #{klass} < ExpressTemplates::Markup::HtmlTag ; end"
33
+ end
34
+
35
+ I.class_eval do
36
+ def should_not_abbreviate?
37
+ true
38
+ end
33
39
  end
34
40
 
35
41
  end
@@ -0,0 +1,118 @@
1
+ module ExpressTemplates
2
+ module Markup
3
+ class Tag
4
+ include ExpressTemplates::Macro
5
+
6
+ attr_accessor :children
7
+
8
+ INDENT = ' '
9
+
10
+ # These come from Macro but must remain overridden here for performance reasons.
11
+ # To verify, comment these two methods and
12
+ # run rake test and observe the performance hit. Why?
13
+ def self.macro_name
14
+ @macro_name ||= to_s.split('::').last.underscore
15
+ end
16
+
17
+ def macro_name ; self.class.macro_name end
18
+
19
+
20
+ def html_options
21
+ @options.each_pair.map do |name, value|
22
+ case
23
+ when name.to_sym.eql?(:data) && value.kind_of?(Hash)
24
+ value.each_pair.map {|k,v| %Q(data-#{k}=\\"#{v}\\") }.join(" ")
25
+ when code = value.to_s.match(/^\{\{(.*)\}\}$/).try(:[], 1)
26
+ %Q(#{name}=\\"\#{#{code}}\\")
27
+ else
28
+ %Q(#{name}=\\"#{value}\\")
29
+ end
30
+ end.join(" ")
31
+ end
32
+
33
+ def start_tag
34
+ "<#{macro_name}#{html_options.empty? ? '' : ' '+html_options}>"
35
+ end
36
+
37
+ def close_tag
38
+ "</#{macro_name}>"
39
+ end
40
+
41
+ def add_css_class(css_class)
42
+ @options['class'] ||= ''
43
+ css_class = css_class.to_s.gsub('_', '-').gsub(/^-/,'') if css_class.to_s.match /^_.*_/
44
+ @options['class'] = (@options['class'].split + [css_class]).uniq.join(" ")
45
+ end
46
+
47
+ def method_missing(name, *args, &children)
48
+ add_css_class(name)
49
+ _process(*args) unless args.empty?
50
+ if children # in the case where CSS classes are specified via method
51
+ unless @expander.nil?
52
+ @expander.process_children! self, &children
53
+ else
54
+ raise "block passed without expander"
55
+ end
56
+ end
57
+ return self
58
+ end
59
+
60
+ def should_not_abbreviate?
61
+ false
62
+ end
63
+
64
+ def compile
65
+ ruby_fragments = @children.map do |child|
66
+ if child.respond_to?(:compile)
67
+ child.compile
68
+ else
69
+ if code = child.to_s.match(/\{\{(.*)\}\}/).try(:[], 1)
70
+ %Q("\#\{#{code}\}")
71
+ else
72
+ %Q("#{child}")
73
+ end
74
+ end
75
+ end
76
+ unless ruby_fragments.empty?
77
+ _wrap_with_tags(ruby_fragments)
78
+ else
79
+ if should_not_abbreviate?
80
+ _wrap_with_tags(ruby_fragments)
81
+ else
82
+ %Q("#{start_tag.gsub(/>$/, ' />')}")
83
+ end
84
+ end
85
+ end
86
+
87
+ def to_template(depth = 0)
88
+ template_fragments = @children.map do |child|
89
+ if child.respond_to?(:to_template)
90
+ child.to_template(depth+1)
91
+ else
92
+ child
93
+ end
94
+ end
95
+ indent = INDENT*(depth+1)
96
+ macro_name + _blockify(template_fragments.join("\n#{indent}"), depth)
97
+ end
98
+
99
+ private
100
+
101
+ def _wrap_with_tags(ruby_fragments)
102
+ ruby_fragments.unshift %Q("#{start_tag}")
103
+ ruby_fragments.push %Q("#{close_tag}")
104
+ ruby_fragments.reject {|frag| frag.empty? }.join("+")
105
+ end
106
+
107
+ def _indent(code)
108
+ code.split("\n").map {|line| INDENT + line }.join("\n")
109
+ end
110
+
111
+ def _blockify(code, depth)
112
+ indent = INDENT*depth
113
+ code.empty? ? code : " {\n#{_indent(code)}\n}\n"
114
+ end
115
+
116
+ end
117
+ end
118
+ end
@@ -0,0 +1,88 @@
1
+ module ExpressTemplates
2
+ module Markup
3
+ # wrap locals and helpers for evaluation during render
4
+ class Wrapper
5
+
6
+ attr_accessor :name, :args
7
+
8
+ def initialize(name, *args, &block)
9
+ @name = name
10
+ @args = args
11
+ @block_src = block ? block.source : nil
12
+ end
13
+
14
+ def compile
15
+ # insure nils do not blow up view
16
+ %Q("\#\{#{_compile}\}")
17
+ end
18
+
19
+ def to_template
20
+ _compile
21
+ end
22
+
23
+ def children
24
+ []
25
+ end
26
+
27
+ private
28
+ def _compile
29
+ string = "#{name}"
30
+
31
+ if !@args.empty?
32
+ args_string = args.slice(0..-2).map(&:inspect).map(&:_remove_double_braces).join(', ')
33
+ last_arg = ''
34
+ if args.last.is_a?(Hash) # expand a hash
35
+ last_arg = _convert_hash_to_argument_string(args.last)
36
+ else
37
+ last_arg = args.last.inspect._remove_double_braces
38
+ end
39
+ args_string << (args_string.empty? ? last_arg : ", #{last_arg}")
40
+ string << "(#{args_string})"
41
+ end
42
+
43
+ if @block_src
44
+ string << " #{@block_src}"
45
+ end
46
+
47
+ return string
48
+ end
49
+
50
+ def _convert_hash_to_argument_string(hash)
51
+ use_hashrockets = hash.keys.any? {|key| key.to_s.match /-/}
52
+ unless hash.empty?
53
+ return hash.map do |key, value|
54
+ s = if use_hashrockets
55
+ if key.to_s.match /-/
56
+ "'#{key}' => "
57
+ else
58
+ ":#{key} => "
59
+ end
60
+ else
61
+ "#{key}: "
62
+ end
63
+
64
+ case
65
+ when value.is_a?(String)
66
+ s << '"'+value+'"'
67
+ when value.is_a?(Hash)
68
+ s << value.inspect
69
+ when value.is_a?(Proc)
70
+ s << "(-> #{value.source}).call"
71
+ else
72
+ s << value.inspect # immediate values 1, 2.0, true etc
73
+ end
74
+ s
75
+ end.join(", ")
76
+ else
77
+ "{}" # empty hash
78
+ end
79
+ end
80
+ end
81
+ end
82
+ end
83
+
84
+ class String
85
+ def _remove_double_braces
86
+ match(/\{\{(.*)\}\}/).try(:[], 1) || self
87
+ end
88
+ end