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