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