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.
- checksums.yaml +4 -4
- data/README.md +10 -6
- data/lib/core_extensions/proc.rb +42 -0
- data/lib/express_templates.rb +7 -2
- data/lib/express_templates/compiler.rb +27 -0
- data/lib/express_templates/components.rb +7 -4
- data/lib/express_templates/components/base.rb +54 -0
- data/lib/express_templates/components/capabilities/conditionality.rb +52 -0
- data/lib/express_templates/components/capabilities/configurable.rb +91 -0
- data/lib/express_templates/components/capabilities/iterating.rb +80 -0
- data/lib/express_templates/components/capabilities/parenting.rb +74 -0
- data/lib/express_templates/components/capabilities/rendering.rb +93 -0
- data/lib/express_templates/components/capabilities/templating.rb +196 -0
- data/lib/express_templates/components/capabilities/wrapping.rb +100 -0
- data/lib/express_templates/components/column.rb +13 -0
- data/lib/express_templates/components/container.rb +7 -0
- data/lib/express_templates/components/form_rails_support.rb +19 -0
- data/lib/express_templates/components/row.rb +28 -0
- data/lib/express_templates/expander.rb +51 -35
- data/lib/express_templates/indenter.rb +44 -0
- data/lib/express_templates/macro.rb +44 -0
- data/lib/express_templates/markup.rb +9 -0
- data/lib/express_templates/{components → markup}/html_tag.rb +9 -3
- data/lib/express_templates/markup/tag.rb +118 -0
- data/lib/express_templates/markup/wrapper.rb +88 -0
- data/lib/express_templates/{components → markup}/yielder.rb +2 -2
- data/lib/express_templates/renderer.rb +3 -8
- data/lib/express_templates/template/handler.rb +1 -1
- data/lib/express_templates/version.rb +1 -1
- data/lib/tasks/{gara_tasks.rake → express_templates.rake} +0 -0
- data/test/compiler_test.rb +9 -0
- data/test/components/base_test.rb +77 -0
- data/test/components/column_test.rb +11 -0
- data/test/components/conditionality_test.rb +37 -0
- data/test/components/configurable_test.rb +43 -0
- data/test/components/container_test.rb +49 -0
- data/test/components/iterating_test.rb +85 -0
- data/test/components/row_test.rb +16 -0
- data/test/core_extensions/proc_test.rb +41 -0
- data/test/dummy/log/test.log +72489 -0
- data/test/expander_test.rb +55 -13
- data/test/{gara_test.rb → express_templates_test.rb} +0 -0
- data/test/handler_test.rb +4 -4
- data/test/indenter_test.rb +25 -0
- data/test/markup/tag_test.rb +127 -0
- data/test/markup/wrapper_test.rb +42 -0
- data/test/markup/yielder_test.rb +9 -0
- data/test/performance_test.rb +2 -2
- data/test/test_helper.rb +6 -0
- metadata +78 -24
- data/lib/express_templates/component.rb +0 -92
- data/lib/express_templates/components/wrapper.rb +0 -51
- data/lib/express_templates/html5_emitter.rb +0 -45
- data/test/component_test.rb +0 -77
- 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
|
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
|
-
|
22
|
-
|
23
|
-
modified
|
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
|
-
|
21
|
+
when block
|
22
|
+
instance_exec &block
|
26
23
|
else
|
27
|
-
|
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
|
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
|
-
|
41
|
-
|
42
|
-
|
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::
|
63
|
+
select { |klass| klass.ancestors.include? (ExpressTemplates::Markup::Tag) }
|
65
64
|
)
|
66
65
|
end
|
67
66
|
|
68
|
-
def method_missing(name, *args)
|
69
|
-
|
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
|
-
|
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
|
@@ -1,6 +1,6 @@
|
|
1
1
|
module ExpressTemplates
|
2
|
-
module
|
3
|
-
class HtmlTag <
|
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::
|
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
|