temple 0.6.7 → 0.10.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +5 -5
- data/.github/workflows/test.yml +34 -0
- data/.gitignore +1 -0
- data/CHANGES +106 -1
- data/EXPRESSIONS.md +3 -2
- data/Gemfile +0 -1
- data/README.md +14 -10
- data/Rakefile +4 -11
- data/lib/temple/engine.rb +7 -3
- data/lib/temple/erb/engine.rb +5 -3
- data/lib/temple/erb/parser.rb +1 -1
- data/lib/temple/erb/trimming.rb +11 -26
- data/lib/temple/filters/ambles.rb +21 -0
- data/lib/temple/filters/encoding.rb +1 -1
- data/lib/temple/filters/eraser.rb +1 -1
- data/lib/temple/filters/escapable.rb +2 -2
- data/lib/temple/filters/remove_bom.rb +2 -9
- data/lib/temple/filters/static_analyzer.rb +30 -0
- data/lib/temple/filters/string_splitter.rb +141 -0
- data/lib/temple/filters/validator.rb +1 -1
- data/lib/temple/generator.rb +32 -6
- data/lib/temple/generators/array.rb +2 -2
- data/lib/temple/generators/array_buffer.rb +6 -5
- data/lib/temple/generators/erb.rb +1 -5
- data/lib/temple/generators/rails_output_buffer.rb +7 -8
- data/lib/temple/generators/string_buffer.rb +2 -2
- data/lib/temple/html/attribute_merger.rb +6 -11
- data/lib/temple/html/attribute_remover.rb +1 -1
- data/lib/temple/html/attribute_sorter.rb +1 -1
- data/lib/temple/html/fast.rb +49 -44
- data/lib/temple/html/pretty.rb +34 -43
- data/lib/temple/html/safe.rb +23 -0
- data/lib/temple/map.rb +105 -0
- data/lib/temple/mixins/dispatcher.rb +10 -7
- data/lib/temple/mixins/engine_dsl.rb +42 -67
- data/lib/temple/mixins/grammar_dsl.rb +10 -8
- data/lib/temple/mixins/options.rb +26 -24
- data/lib/temple/mixins/template.rb +3 -3
- data/lib/temple/static_analyzer.rb +77 -0
- data/lib/temple/templates/rails.rb +17 -36
- data/lib/temple/templates/tilt.rb +7 -13
- data/lib/temple/utils.rb +27 -29
- data/lib/temple/version.rb +1 -1
- data/lib/temple.rb +8 -4
- data/spec/engine_spec.rb +189 -0
- data/{test/test_erb.rb → spec/erb_spec.rb} +12 -13
- data/spec/filter_spec.rb +29 -0
- data/{test/filters/test_code_merger.rb → spec/filters/code_merger_spec.rb} +7 -7
- data/{test/filters/test_control_flow.rb → spec/filters/control_flow_spec.rb} +13 -13
- data/{test/filters/test_dynamic_inliner.rb → spec/filters/dynamic_inliner_spec.rb} +18 -18
- data/{test/filters/test_eraser.rb → spec/filters/eraser_spec.rb} +13 -13
- data/{test/filters/test_escapable.rb → spec/filters/escapable_spec.rb} +15 -13
- data/{test/filters/test_multi_flattener.rb → spec/filters/multi_flattener_spec.rb} +4 -4
- data/spec/filters/static_analyzer_spec.rb +35 -0
- data/{test/filters/test_static_merger.rb → spec/filters/static_merger_spec.rb} +7 -7
- data/spec/filters/string_splitter_spec.rb +50 -0
- data/spec/generator_spec.rb +158 -0
- data/spec/grammar_spec.rb +47 -0
- data/{test/html/test_attribute_merger.rb → spec/html/attribute_merger_spec.rb} +11 -11
- data/{test/html/test_attribute_remover.rb → spec/html/attribute_remover_spec.rb} +7 -7
- data/{test/html/test_attribute_sorter.rb → spec/html/attribute_sorter_spec.rb} +8 -8
- data/{test/html/test_fast.rb → spec/html/fast_spec.rb} +23 -23
- data/{test/html/test_pretty.rb → spec/html/pretty_spec.rb} +9 -15
- data/spec/map_spec.rb +39 -0
- data/{test/mixins/test_dispatcher.rb → spec/mixins/dispatcher_spec.rb} +12 -12
- data/{test/mixins/test_grammar_dsl.rb → spec/mixins/grammar_dsl_spec.rb} +19 -19
- data/{test/helper.rb → spec/spec_helper.rb} +9 -15
- data/spec/static_analyzer_spec.rb +39 -0
- data/spec/utils_spec.rb +39 -0
- data/temple.gemspec +4 -2
- metadata +62 -63
- data/.travis.yml +0 -13
- data/lib/temple/hash.rb +0 -104
- data/test/test_engine.rb +0 -170
- data/test/test_filter.rb +0 -29
- data/test/test_generator.rb +0 -136
- data/test/test_grammar.rb +0 -47
- data/test/test_hash.rb +0 -39
- data/test/test_utils.rb +0 -39
@@ -58,12 +58,14 @@ module Temple
|
|
58
58
|
def replace_dispatcher(exp)
|
59
59
|
tree = DispatchNode.new
|
60
60
|
dispatched_methods.each do |method|
|
61
|
-
method.split('_')[1..-1].inject(tree) {|node, type| node[type.to_sym] }.method = method
|
61
|
+
method.split('_'.freeze)[1..-1].inject(tree) {|node, type| node[type.to_sym] }.method = method
|
62
62
|
end
|
63
|
-
self.class.class_eval
|
64
|
-
|
65
|
-
|
66
|
-
|
63
|
+
self.class.class_eval <<-RUBY, __FILE__, __LINE__ + 1
|
64
|
+
def dispatcher(exp)
|
65
|
+
return replace_dispatcher(exp) if self.class != #{self.class}
|
66
|
+
#{tree.compile.gsub("\n", "\n ")}
|
67
|
+
end
|
68
|
+
RUBY
|
67
69
|
dispatcher(exp)
|
68
70
|
end
|
69
71
|
|
@@ -88,10 +90,11 @@ end}
|
|
88
90
|
raise 'Invalid dispatcher node' unless method
|
89
91
|
call_method
|
90
92
|
else
|
91
|
-
code =
|
93
|
+
code = String.new
|
94
|
+
code << "case(exp[#{level}])\n"
|
92
95
|
each do |key, child|
|
93
96
|
code << "when #{key.inspect}\n " <<
|
94
|
-
child.compile(level + 1, call_method).gsub("\n", "\n ") << "\n"
|
97
|
+
child.compile(level + 1, call_method).gsub("\n".freeze, "\n ".freeze) << "\n".freeze
|
95
98
|
end
|
96
99
|
code << "else\n " << (call_method || 'exp') << "\nend"
|
97
100
|
end
|
@@ -17,15 +17,7 @@ module Temple
|
|
17
17
|
|
18
18
|
def remove(name)
|
19
19
|
name = chain_name(name)
|
20
|
-
found
|
21
|
-
chain.reject! do |i|
|
22
|
-
if i.first == name
|
23
|
-
found = true
|
24
|
-
else
|
25
|
-
false
|
26
|
-
end
|
27
|
-
end
|
28
|
-
raise "#{name} not found" unless found
|
20
|
+
raise "#{name} not found" unless chain.reject! {|i| name === i.first }
|
29
21
|
chain_modified!
|
30
22
|
end
|
31
23
|
|
@@ -34,54 +26,31 @@ module Temple
|
|
34
26
|
def before(name, *args, &block)
|
35
27
|
name = chain_name(name)
|
36
28
|
e = chain_element(args, block)
|
37
|
-
|
38
|
-
|
39
|
-
if chain[i].first == name
|
40
|
-
found = true
|
41
|
-
chain.insert(i, e)
|
42
|
-
i += 2
|
43
|
-
else
|
44
|
-
i += 1
|
45
|
-
end
|
46
|
-
end
|
47
|
-
raise "#{name} not found" unless found
|
29
|
+
chain.map! {|f| name === f.first ? [e, f] : [f] }.flatten!(1)
|
30
|
+
raise "#{name} not found" unless chain.include?(e)
|
48
31
|
chain_modified!
|
49
32
|
end
|
50
33
|
|
51
34
|
def after(name, *args, &block)
|
52
35
|
name = chain_name(name)
|
53
36
|
e = chain_element(args, block)
|
54
|
-
|
55
|
-
|
56
|
-
if chain[i].first == name
|
57
|
-
found = true
|
58
|
-
i += 1
|
59
|
-
chain.insert(i, e)
|
60
|
-
end
|
61
|
-
i += 1
|
62
|
-
end
|
63
|
-
raise "#{name} not found" unless found
|
37
|
+
chain.map! {|f| name === f.first ? [f, e] : [f] }.flatten!(1)
|
38
|
+
raise "#{name} not found" unless chain.include?(e)
|
64
39
|
chain_modified!
|
65
40
|
end
|
66
41
|
|
67
42
|
def replace(name, *args, &block)
|
68
43
|
name = chain_name(name)
|
69
44
|
e = chain_element(args, block)
|
70
|
-
|
71
|
-
|
72
|
-
if c.first == name
|
73
|
-
found = true
|
74
|
-
chain[i] = e
|
75
|
-
end
|
76
|
-
end
|
77
|
-
raise "#{name} not found" unless found
|
45
|
+
chain.map! {|f| name === f.first ? e : f }
|
46
|
+
raise "#{name} not found" unless chain.include?(e)
|
78
47
|
chain_modified!
|
79
48
|
end
|
80
49
|
|
81
50
|
# Shortcuts to access namespaces
|
82
|
-
{ :
|
83
|
-
:
|
84
|
-
:
|
51
|
+
{ filter: Temple::Filters,
|
52
|
+
generator: Temple::Generators,
|
53
|
+
html: Temple::HTML }.each do |method, mod|
|
85
54
|
define_method(method) do |name, *options|
|
86
55
|
use(name, mod.const_get(name), *options)
|
87
56
|
end
|
@@ -90,47 +59,51 @@ module Temple
|
|
90
59
|
private
|
91
60
|
|
92
61
|
def chain_name(name)
|
93
|
-
|
94
|
-
|
95
|
-
|
62
|
+
case name
|
63
|
+
when Class
|
64
|
+
name.name.to_sym
|
65
|
+
when Symbol, String
|
66
|
+
name.to_sym
|
67
|
+
when Regexp
|
68
|
+
name
|
69
|
+
else
|
70
|
+
raise(ArgumentError, 'Name argument must be Class, Symbol, String or Regexp')
|
71
|
+
end
|
96
72
|
end
|
97
73
|
|
98
|
-
def chain_class_constructor(filter,
|
99
|
-
|
100
|
-
raise(ArgumentError, 'Only symbols allowed in option filter') unless option_filter.all? {|o| Symbol === o }
|
101
|
-
define_options(*option_filter) if respond_to?(:define_options)
|
74
|
+
def chain_class_constructor(filter, local_options)
|
75
|
+
define_options(filter.options.valid_keys) if respond_to?(:define_options) && filter.respond_to?(:options)
|
102
76
|
proc do |engine|
|
103
|
-
|
77
|
+
opts = {}.update(engine.options)
|
78
|
+
opts.delete_if {|k,v| !filter.options.valid_key?(k) } if filter.respond_to?(:options)
|
79
|
+
opts.update(local_options) if local_options
|
80
|
+
filter.new(opts)
|
104
81
|
end
|
105
82
|
end
|
106
83
|
|
107
84
|
def chain_proc_constructor(name, filter)
|
108
85
|
raise(ArgumentError, 'Proc or blocks must have arity 0 or 1') if filter.arity > 1
|
109
86
|
method_name = "FILTER #{name}"
|
110
|
-
|
111
|
-
|
112
|
-
|
87
|
+
c = Class === self ? self : singleton_class
|
88
|
+
filter = c.class_eval { define_method(method_name, &filter); instance_method(method_name) }
|
89
|
+
proc do |engine|
|
113
90
|
if filter.arity == 1
|
114
|
-
proc
|
91
|
+
# the proc takes one argument, e.g. use(:Filter) {|exp| exp }
|
92
|
+
filter.bind(engine)
|
115
93
|
else
|
116
|
-
|
117
|
-
|
118
|
-
|
94
|
+
f = filter.bind(engine).call
|
95
|
+
if f.respond_to? :call
|
96
|
+
# the proc returns a callable object, e.g. use(:Filter) { Filter.new }
|
119
97
|
f
|
98
|
+
else
|
99
|
+
raise(ArgumentError, 'Proc or blocks must return a Callable or a Class') unless f.respond_to? :new
|
100
|
+
# the proc returns a class, e.g. use(:Filter) { Filter }
|
101
|
+
f.new(f.respond_to?(:options) ? engine.options.to_hash.select {|k,v| f.options.valid_key?(k) } : engine.options)
|
120
102
|
end
|
121
103
|
end
|
122
|
-
else
|
123
|
-
(class << self; self; end).class_eval { define_method(method_name, &filter) }
|
124
|
-
filter = method(method_name)
|
125
|
-
proc {|engine| filter }
|
126
104
|
end
|
127
105
|
end
|
128
106
|
|
129
|
-
def chain_callable_constructor(filter)
|
130
|
-
raise(ArgumentError, 'Class or callable argument is required') unless filter.respond_to?(:call)
|
131
|
-
proc {|engine| filter }
|
132
|
-
end
|
133
|
-
|
134
107
|
def chain_element(args, block)
|
135
108
|
name = args.shift
|
136
109
|
if Class === name
|
@@ -157,12 +130,14 @@ module Temple
|
|
157
130
|
when Class
|
158
131
|
# Class argument (e.g Filter class)
|
159
132
|
# The options are passed to the classes constructor.
|
160
|
-
|
133
|
+
raise(ArgumentError, 'Too many arguments') if args.size > 1
|
134
|
+
[name, chain_class_constructor(filter, args.first)]
|
161
135
|
else
|
162
136
|
# Other callable argument (e.g. Object of class which implements #call or Method)
|
163
137
|
# The callable has no access to the option hash of the engine.
|
164
138
|
raise(ArgumentError, 'Too many arguments') unless args.empty?
|
165
|
-
|
139
|
+
raise(ArgumentError, 'Class or callable argument is required') unless filter.respond_to?(:call)
|
140
|
+
[name, proc { filter }]
|
166
141
|
end
|
167
142
|
end
|
168
143
|
end
|
@@ -7,6 +7,12 @@ module Temple
|
|
7
7
|
@grammar = grammar
|
8
8
|
end
|
9
9
|
|
10
|
+
def match?(exp)
|
11
|
+
match(exp, [])
|
12
|
+
end
|
13
|
+
alias === match?
|
14
|
+
alias =~ match?
|
15
|
+
|
10
16
|
def |(rule)
|
11
17
|
Or.new(@grammar, self, rule)
|
12
18
|
end
|
@@ -53,10 +59,6 @@ module Temple
|
|
53
59
|
success
|
54
60
|
end
|
55
61
|
|
56
|
-
def match?(exp)
|
57
|
-
match(exp, [])
|
58
|
-
end
|
59
|
-
|
60
62
|
def validate!(exp)
|
61
63
|
unmatched = []
|
62
64
|
unless match(exp, unmatched)
|
@@ -120,14 +122,13 @@ module Temple
|
|
120
122
|
def match?(exp)
|
121
123
|
const_get(:Expression).match?(exp)
|
122
124
|
end
|
125
|
+
alias === match?
|
126
|
+
alias =~ match?
|
123
127
|
|
124
128
|
def validate!(exp)
|
125
129
|
const_get(:Expression).validate!(exp)
|
126
130
|
end
|
127
131
|
|
128
|
-
alias === match?
|
129
|
-
alias =~ match?
|
130
|
-
|
131
132
|
def Value(value)
|
132
133
|
Value.new(self, value)
|
133
134
|
end
|
@@ -142,7 +143,8 @@ module Temple
|
|
142
143
|
start = Or.new(self)
|
143
144
|
curr = [start]
|
144
145
|
rule.each do |elem|
|
145
|
-
|
146
|
+
case elem
|
147
|
+
when /^(.*)(\*|\?|\+)$/
|
146
148
|
elem = Element.new(self, const_get($1))
|
147
149
|
curr.each {|c| c << elem }
|
148
150
|
elem << elem if $2 != '?'
|
@@ -1,42 +1,44 @@
|
|
1
1
|
module Temple
|
2
2
|
module Mixins
|
3
3
|
# @api public
|
4
|
-
module
|
4
|
+
module ClassOptions
|
5
5
|
def set_default_options(opts)
|
6
|
-
|
6
|
+
warn 'set_default_options has been deprecated, use set_options'
|
7
|
+
set_options(opts)
|
7
8
|
end
|
8
9
|
|
9
10
|
def default_options
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
11
|
+
warn 'default_options has been deprecated, use options'
|
12
|
+
options
|
13
|
+
end
|
14
|
+
|
15
|
+
def set_options(opts)
|
16
|
+
options.update(opts)
|
17
|
+
end
|
18
|
+
|
19
|
+
def options
|
20
|
+
@options ||= OptionMap.new(superclass.respond_to?(:options) ?
|
21
|
+
superclass.options : nil) do |hash, key, what|
|
22
|
+
warn "#{self}: Option #{key.inspect} is #{what}" unless @option_validator_disabled
|
21
23
|
end
|
22
24
|
end
|
23
25
|
|
24
26
|
def define_options(*opts)
|
25
27
|
if opts.last.respond_to?(:to_hash)
|
26
28
|
hash = opts.pop.to_hash
|
27
|
-
|
28
|
-
|
29
|
+
options.add_valid_keys(hash.keys)
|
30
|
+
options.update(hash)
|
29
31
|
end
|
30
|
-
|
32
|
+
options.add_valid_keys(opts)
|
31
33
|
end
|
32
34
|
|
33
35
|
def define_deprecated_options(*opts)
|
34
36
|
if opts.last.respond_to?(:to_hash)
|
35
37
|
hash = opts.pop.to_hash
|
36
|
-
|
37
|
-
|
38
|
+
options.add_deprecated_keys(hash.keys)
|
39
|
+
options.update(hash)
|
38
40
|
end
|
39
|
-
|
41
|
+
options.add_deprecated_keys(opts)
|
40
42
|
end
|
41
43
|
|
42
44
|
def disable_option_validator!
|
@@ -47,7 +49,7 @@ module Temple
|
|
47
49
|
module ThreadOptions
|
48
50
|
def with_options(options)
|
49
51
|
old_options = thread_options
|
50
|
-
Thread.current[thread_options_key] =
|
52
|
+
Thread.current[thread_options_key] = ImmutableMap.new(options, thread_options)
|
51
53
|
yield
|
52
54
|
ensure
|
53
55
|
Thread.current[thread_options_key] = old_options
|
@@ -68,7 +70,7 @@ module Temple
|
|
68
70
|
module Options
|
69
71
|
def self.included(base)
|
70
72
|
base.class_eval do
|
71
|
-
extend
|
73
|
+
extend ClassOptions
|
72
74
|
extend ThreadOptions
|
73
75
|
end
|
74
76
|
end
|
@@ -76,9 +78,9 @@ module Temple
|
|
76
78
|
attr_reader :options
|
77
79
|
|
78
80
|
def initialize(opts = {})
|
79
|
-
self.class.
|
80
|
-
self.class.
|
81
|
-
@options =
|
81
|
+
self.class.options.validate_map!(opts)
|
82
|
+
self.class.options.validate_map!(self.class.thread_options) if self.class.thread_options
|
83
|
+
@options = ImmutableMap.new({}.update(self.class.options).update(self.class.thread_options || {}).update(opts))
|
82
84
|
end
|
83
85
|
end
|
84
86
|
end
|
@@ -2,7 +2,7 @@ module Temple
|
|
2
2
|
module Mixins
|
3
3
|
# @api private
|
4
4
|
module Template
|
5
|
-
include
|
5
|
+
include ClassOptions
|
6
6
|
|
7
7
|
def compile(code, options)
|
8
8
|
engine = options.delete(:engine)
|
@@ -18,8 +18,8 @@ module Temple
|
|
18
18
|
register_as = options.delete(:register_as)
|
19
19
|
template = Class.new(self)
|
20
20
|
template.disable_option_validator!
|
21
|
-
template.
|
22
|
-
template.
|
21
|
+
template.options[:engine] = engine
|
22
|
+
template.options.update(options)
|
23
23
|
template.register_as(*register_as) if register_as
|
24
24
|
template
|
25
25
|
end
|
@@ -0,0 +1,77 @@
|
|
1
|
+
begin
|
2
|
+
require 'ripper'
|
3
|
+
rescue LoadError
|
4
|
+
end
|
5
|
+
|
6
|
+
module Temple
|
7
|
+
module StaticAnalyzer
|
8
|
+
STATIC_TOKENS = [
|
9
|
+
:on_tstring_beg, :on_tstring_end, :on_tstring_content,
|
10
|
+
:on_embexpr_beg, :on_embexpr_end,
|
11
|
+
:on_lbracket, :on_rbracket,
|
12
|
+
:on_qwords_beg, :on_words_sep, :on_qwords_sep,
|
13
|
+
:on_lparen, :on_rparen,
|
14
|
+
:on_lbrace, :on_rbrace, :on_label,
|
15
|
+
:on_int, :on_float, :on_imaginary,
|
16
|
+
:on_comma, :on_sp, :on_ignored_nl,
|
17
|
+
].freeze
|
18
|
+
|
19
|
+
DYNAMIC_TOKENS = [
|
20
|
+
:on_ident, :on_period,
|
21
|
+
].freeze
|
22
|
+
|
23
|
+
STATIC_KEYWORDS = [
|
24
|
+
'true', 'false', 'nil',
|
25
|
+
].freeze
|
26
|
+
|
27
|
+
STATIC_OPERATORS = [
|
28
|
+
'=>',
|
29
|
+
].freeze
|
30
|
+
|
31
|
+
class << self
|
32
|
+
def available?
|
33
|
+
defined?(Ripper) && Ripper.respond_to?(:lex)
|
34
|
+
end
|
35
|
+
|
36
|
+
def static?(code)
|
37
|
+
return false if code.nil? || code.strip.empty?
|
38
|
+
return false if syntax_error?(code)
|
39
|
+
|
40
|
+
Ripper.lex(code).each do |_, token, str|
|
41
|
+
case token
|
42
|
+
when *STATIC_TOKENS
|
43
|
+
# noop
|
44
|
+
when :on_kw
|
45
|
+
return false unless STATIC_KEYWORDS.include?(str)
|
46
|
+
when :on_op
|
47
|
+
return false unless STATIC_OPERATORS.include?(str)
|
48
|
+
when *DYNAMIC_TOKENS
|
49
|
+
return false
|
50
|
+
else
|
51
|
+
return false
|
52
|
+
end
|
53
|
+
end
|
54
|
+
true
|
55
|
+
end
|
56
|
+
|
57
|
+
def syntax_error?(code)
|
58
|
+
SyntaxChecker.new(code).parse
|
59
|
+
false
|
60
|
+
rescue SyntaxChecker::ParseError
|
61
|
+
true
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
if defined?(Ripper)
|
66
|
+
class SyntaxChecker < Ripper
|
67
|
+
class ParseError < StandardError; end
|
68
|
+
|
69
|
+
private
|
70
|
+
|
71
|
+
def on_parse_error(*)
|
72
|
+
raise ParseError
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
@@ -1,47 +1,28 @@
|
|
1
|
-
unless defined?(ActionView)
|
2
|
-
raise "Rails is not loaded - Temple::Templates::Rails cannot be used"
|
3
|
-
end
|
4
|
-
|
5
|
-
if ::ActionPack::VERSION::MAJOR < 3
|
6
|
-
raise "Temple supports only Rails 3.x and greater, your Rails version is #{::ActionPack::VERSION::STRING}"
|
7
|
-
end
|
8
|
-
|
9
1
|
module Temple
|
10
2
|
module Templates
|
11
|
-
|
12
|
-
|
13
|
-
include ActionView::TemplateHandlers::Compilable
|
14
|
-
extend Mixins::Template
|
3
|
+
class Rails
|
4
|
+
extend Mixins::Template
|
15
5
|
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
def self.register_as(*names)
|
23
|
-
names.each do |name|
|
24
|
-
ActionView::Template.register_template_handler name.to_sym, self
|
25
|
-
end
|
6
|
+
def call(template, source = nil)
|
7
|
+
opts = {}.update(self.class.options).update(file: template.identifier)
|
8
|
+
if ActionView::Base.try(:annotate_rendered_view_with_filenames) && template.format == :html
|
9
|
+
opts[:preamble] = "<!-- BEGIN #{template.short_identifier} -->\n"
|
10
|
+
opts[:postamble] = "<!-- END #{template.short_identifier} -->\n"
|
26
11
|
end
|
12
|
+
self.class.compile((source || template.source), opts)
|
27
13
|
end
|
28
|
-
else
|
29
|
-
class Rails
|
30
|
-
extend Mixins::Template
|
31
14
|
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
end
|
15
|
+
def supports_streaming?
|
16
|
+
self.class.options[:streaming]
|
17
|
+
end
|
36
18
|
|
37
|
-
|
38
|
-
|
19
|
+
def self.register_as(*names)
|
20
|
+
raise 'Rails is not loaded - Temple::Templates::Rails cannot be used' unless defined?(::ActionView)
|
21
|
+
if ::ActiveSupport::VERSION::MAJOR < 5
|
22
|
+
raise "Temple supports only Rails 5 and greater, your Rails version is #{::ActiveSupport::VERSION::STRING}"
|
39
23
|
end
|
40
|
-
|
41
|
-
|
42
|
-
names.each do |name|
|
43
|
-
ActionView::Template.register_template_handler name.to_sym, new
|
44
|
-
end
|
24
|
+
names.each do |name|
|
25
|
+
::ActionView::Template.register_template_handler name.to_sym, new
|
45
26
|
end
|
46
27
|
end
|
47
28
|
end
|
@@ -5,15 +5,7 @@ module Temple
|
|
5
5
|
class Tilt < ::Tilt::Template
|
6
6
|
extend Mixins::Template
|
7
7
|
|
8
|
-
define_options :
|
9
|
-
|
10
|
-
def self.default_mime_type
|
11
|
-
default_options[:mime_type]
|
12
|
-
end
|
13
|
-
|
14
|
-
def self.default_mime_type=(mime_type)
|
15
|
-
default_options[:mime_type] = mime_type
|
16
|
-
end
|
8
|
+
define_options mime_type: 'text/html'
|
17
9
|
|
18
10
|
# Prepare Temple template
|
19
11
|
#
|
@@ -21,10 +13,12 @@ module Temple
|
|
21
13
|
#
|
22
14
|
# @return [void]
|
23
15
|
def prepare
|
24
|
-
|
25
|
-
|
26
|
-
opts.
|
27
|
-
|
16
|
+
opts = {}.update(self.class.options).update(options).update(file: eval_file)
|
17
|
+
metadata[:mime_type] = opts.delete(:mime_type)
|
18
|
+
if opts.include?(:outvar)
|
19
|
+
opts[:buffer] = opts.delete(:outvar)
|
20
|
+
opts[:save_buffer] = true
|
21
|
+
end
|
28
22
|
@src = self.class.compile(data, opts)
|
29
23
|
end
|
30
24
|
|
data/lib/temple/utils.rb
CHANGED
@@ -1,7 +1,6 @@
|
|
1
1
|
begin
|
2
|
-
require '
|
2
|
+
require 'cgi/escape'
|
3
3
|
rescue LoadError
|
4
|
-
# Loading EscapeUtils failed
|
5
4
|
end
|
6
5
|
|
7
6
|
module Temple
|
@@ -15,16 +14,17 @@ module Temple
|
|
15
14
|
# @param html [String] The string to escape
|
16
15
|
# @return [String] The escaped string
|
17
16
|
def escape_html_safe(html)
|
18
|
-
|
17
|
+
s = html.to_s
|
18
|
+
s.html_safe? || html.html_safe? ? s : escape_html(s)
|
19
19
|
end
|
20
20
|
|
21
|
-
if defined?(
|
21
|
+
if defined?(CGI.escapeHTML)
|
22
22
|
# Returns an escaped copy of `html`.
|
23
23
|
#
|
24
24
|
# @param html [String] The string to escape
|
25
25
|
# @return [String] The escaped string
|
26
26
|
def escape_html(html)
|
27
|
-
|
27
|
+
CGI.escapeHTML(html.to_s)
|
28
28
|
end
|
29
29
|
else
|
30
30
|
# Used by escape_html
|
@@ -37,30 +37,14 @@ module Temple
|
|
37
37
|
'>' => '>'
|
38
38
|
}.freeze
|
39
39
|
|
40
|
-
|
41
|
-
ESCAPE_HTML_PATTERN = Regexp.union(*ESCAPE_HTML.keys)
|
42
|
-
else
|
43
|
-
# On 1.8, there is a kcode = 'u' bug that allows for XSS otherwise
|
44
|
-
# TODO doesn't apply to jruby, so a better condition above might be preferable?
|
45
|
-
ESCAPE_HTML_PATTERN = /#{Regexp.union(*ESCAPE_HTML.keys)}/n
|
46
|
-
end
|
40
|
+
ESCAPE_HTML_PATTERN = Regexp.union(*ESCAPE_HTML.keys)
|
47
41
|
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
html.to_s.gsub(ESCAPE_HTML_PATTERN, ESCAPE_HTML)
|
55
|
-
end
|
56
|
-
else
|
57
|
-
# Returns an escaped copy of `html`.
|
58
|
-
#
|
59
|
-
# @param html [String] The string to escape
|
60
|
-
# @return [String] The escaped string
|
61
|
-
def escape_html(html)
|
62
|
-
html.to_s.gsub(ESCAPE_HTML_PATTERN) {|c| ESCAPE_HTML[c] }
|
63
|
-
end
|
42
|
+
# Returns an escaped copy of `html`.
|
43
|
+
#
|
44
|
+
# @param html [String] The string to escape
|
45
|
+
# @return [String] The escaped string
|
46
|
+
def escape_html(html)
|
47
|
+
html.to_s.gsub(ESCAPE_HTML_PATTERN, ESCAPE_HTML)
|
64
48
|
end
|
65
49
|
end
|
66
50
|
|
@@ -70,7 +54,7 @@ module Temple
|
|
70
54
|
# @return [String] Variable name
|
71
55
|
def unique_name(prefix = nil)
|
72
56
|
@unique_name ||= 0
|
73
|
-
prefix ||= (@unique_prefix ||= self.class.name.gsub('::', '_').downcase)
|
57
|
+
prefix ||= (@unique_prefix ||= self.class.name.gsub('::'.freeze, '_'.freeze).downcase)
|
74
58
|
"_#{prefix}#{@unique_name += 1}"
|
75
59
|
end
|
76
60
|
|
@@ -88,5 +72,19 @@ module Temple
|
|
88
72
|
false
|
89
73
|
end
|
90
74
|
end
|
75
|
+
|
76
|
+
def indent_dynamic(text, indent_next, indent, pre_tags = nil)
|
77
|
+
text = text.to_s
|
78
|
+
safe = text.respond_to?(:html_safe?) && text.html_safe?
|
79
|
+
return text if pre_tags && text =~ pre_tags
|
80
|
+
|
81
|
+
level = text.scan(/^\s*/).map(&:size).min
|
82
|
+
text = text.gsub(/(?!\A)^\s{#{level}}/, '') if level > 0
|
83
|
+
|
84
|
+
text = text.sub(/\A\s*\n?/, "\n".freeze) if indent_next
|
85
|
+
text = text.gsub("\n".freeze, indent)
|
86
|
+
|
87
|
+
safe ? text.html_safe : text
|
88
|
+
end
|
91
89
|
end
|
92
90
|
end
|
data/lib/temple/version.rb
CHANGED