temple 0.6.7 → 0.10.0

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 (79) hide show
  1. checksums.yaml +5 -5
  2. data/.github/workflows/test.yml +34 -0
  3. data/.gitignore +1 -0
  4. data/CHANGES +106 -1
  5. data/EXPRESSIONS.md +3 -2
  6. data/Gemfile +0 -1
  7. data/README.md +14 -10
  8. data/Rakefile +4 -11
  9. data/lib/temple/engine.rb +7 -3
  10. data/lib/temple/erb/engine.rb +5 -3
  11. data/lib/temple/erb/parser.rb +1 -1
  12. data/lib/temple/erb/trimming.rb +11 -26
  13. data/lib/temple/filters/ambles.rb +21 -0
  14. data/lib/temple/filters/encoding.rb +1 -1
  15. data/lib/temple/filters/eraser.rb +1 -1
  16. data/lib/temple/filters/escapable.rb +2 -2
  17. data/lib/temple/filters/remove_bom.rb +2 -9
  18. data/lib/temple/filters/static_analyzer.rb +30 -0
  19. data/lib/temple/filters/string_splitter.rb +141 -0
  20. data/lib/temple/filters/validator.rb +1 -1
  21. data/lib/temple/generator.rb +32 -6
  22. data/lib/temple/generators/array.rb +2 -2
  23. data/lib/temple/generators/array_buffer.rb +6 -5
  24. data/lib/temple/generators/erb.rb +1 -5
  25. data/lib/temple/generators/rails_output_buffer.rb +7 -8
  26. data/lib/temple/generators/string_buffer.rb +2 -2
  27. data/lib/temple/html/attribute_merger.rb +6 -11
  28. data/lib/temple/html/attribute_remover.rb +1 -1
  29. data/lib/temple/html/attribute_sorter.rb +1 -1
  30. data/lib/temple/html/fast.rb +49 -44
  31. data/lib/temple/html/pretty.rb +34 -43
  32. data/lib/temple/html/safe.rb +23 -0
  33. data/lib/temple/map.rb +105 -0
  34. data/lib/temple/mixins/dispatcher.rb +10 -7
  35. data/lib/temple/mixins/engine_dsl.rb +42 -67
  36. data/lib/temple/mixins/grammar_dsl.rb +10 -8
  37. data/lib/temple/mixins/options.rb +26 -24
  38. data/lib/temple/mixins/template.rb +3 -3
  39. data/lib/temple/static_analyzer.rb +77 -0
  40. data/lib/temple/templates/rails.rb +17 -36
  41. data/lib/temple/templates/tilt.rb +7 -13
  42. data/lib/temple/utils.rb +27 -29
  43. data/lib/temple/version.rb +1 -1
  44. data/lib/temple.rb +8 -4
  45. data/spec/engine_spec.rb +189 -0
  46. data/{test/test_erb.rb → spec/erb_spec.rb} +12 -13
  47. data/spec/filter_spec.rb +29 -0
  48. data/{test/filters/test_code_merger.rb → spec/filters/code_merger_spec.rb} +7 -7
  49. data/{test/filters/test_control_flow.rb → spec/filters/control_flow_spec.rb} +13 -13
  50. data/{test/filters/test_dynamic_inliner.rb → spec/filters/dynamic_inliner_spec.rb} +18 -18
  51. data/{test/filters/test_eraser.rb → spec/filters/eraser_spec.rb} +13 -13
  52. data/{test/filters/test_escapable.rb → spec/filters/escapable_spec.rb} +15 -13
  53. data/{test/filters/test_multi_flattener.rb → spec/filters/multi_flattener_spec.rb} +4 -4
  54. data/spec/filters/static_analyzer_spec.rb +35 -0
  55. data/{test/filters/test_static_merger.rb → spec/filters/static_merger_spec.rb} +7 -7
  56. data/spec/filters/string_splitter_spec.rb +50 -0
  57. data/spec/generator_spec.rb +158 -0
  58. data/spec/grammar_spec.rb +47 -0
  59. data/{test/html/test_attribute_merger.rb → spec/html/attribute_merger_spec.rb} +11 -11
  60. data/{test/html/test_attribute_remover.rb → spec/html/attribute_remover_spec.rb} +7 -7
  61. data/{test/html/test_attribute_sorter.rb → spec/html/attribute_sorter_spec.rb} +8 -8
  62. data/{test/html/test_fast.rb → spec/html/fast_spec.rb} +23 -23
  63. data/{test/html/test_pretty.rb → spec/html/pretty_spec.rb} +9 -15
  64. data/spec/map_spec.rb +39 -0
  65. data/{test/mixins/test_dispatcher.rb → spec/mixins/dispatcher_spec.rb} +12 -12
  66. data/{test/mixins/test_grammar_dsl.rb → spec/mixins/grammar_dsl_spec.rb} +19 -19
  67. data/{test/helper.rb → spec/spec_helper.rb} +9 -15
  68. data/spec/static_analyzer_spec.rb +39 -0
  69. data/spec/utils_spec.rb +39 -0
  70. data/temple.gemspec +4 -2
  71. metadata +62 -63
  72. data/.travis.yml +0 -13
  73. data/lib/temple/hash.rb +0 -104
  74. data/test/test_engine.rb +0 -170
  75. data/test/test_filter.rb +0 -29
  76. data/test/test_generator.rb +0 -136
  77. data/test/test_grammar.rb +0 -47
  78. data/test/test_hash.rb +0 -39
  79. 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 %{def dispatcher(exp)
64
- return replace_dispatcher(exp) if self.class != #{self.class}
65
- #{tree.compile.gsub("\n", "\n ")}
66
- end}
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 = "case(exp[#{level}])\n"
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 = false
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
- found, i = false, 0
38
- while i < chain.size
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
- found, i = false, 0
55
- while i < chain.size
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
- found = false
71
- chain.each_with_index do |c, i|
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
- { :filter => Temple::Filters,
83
- :generator => Temple::Generators,
84
- :html => Temple::HTML }.each do |method, mod|
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
- name = Class === name ? name.name.to_sym : name
94
- raise(ArgumentError, 'Name argument must be Class or Symbol') unless Symbol === name
95
- name
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, option_filter)
99
- local_options = option_filter.last.respond_to?(:to_hash) ? option_filter.pop.to_hash : {}
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
- filter.new({}.update(engine.options).delete_if {|k,v| !option_filter.include?(k) }.update(local_options))
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
- if Class === self
111
- define_method(method_name, &filter)
112
- filter = instance_method(method_name)
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 {|engine| filter.bind(engine) }
91
+ # the proc takes one argument, e.g. use(:Filter) {|exp| exp }
92
+ filter.bind(engine)
115
93
  else
116
- proc do |engine|
117
- f = filter.bind(engine).call
118
- raise 'Constructor must return callable object' unless f.respond_to?(:call)
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
- [name, chain_class_constructor(filter, args)]
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
- [name, chain_callable_constructor(filter)]
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
- if elem =~ /^(.*)(\*|\?|\+)$/
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 DefaultOptions
4
+ module ClassOptions
5
5
  def set_default_options(opts)
6
- default_options.update(opts)
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
- @default_options ||= OptionHash.new(superclass.respond_to?(:default_options) ?
11
- superclass.default_options : nil) do |hash, key, deprecated|
12
- unless @option_validator_disabled
13
- if deprecated
14
- warn "Option #{key.inspect} is deprecated by #{self}"
15
- else
16
- # TODO: This will raise an exception in the future!
17
- # raise ArgumentError, "Option #{key.inspect} is not supported by #{self}"
18
- warn "Option #{key.inspect} is not supported by #{self}"
19
- end
20
- end
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
- default_options.add_valid_keys(hash.keys)
28
- default_options.update(hash)
29
+ options.add_valid_keys(hash.keys)
30
+ options.update(hash)
29
31
  end
30
- default_options.add_valid_keys(opts)
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
- default_options.add_deprecated_keys(hash.keys)
37
- default_options.update(hash)
38
+ options.add_deprecated_keys(hash.keys)
39
+ options.update(hash)
38
40
  end
39
- default_options.add_deprecated_keys(opts)
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] = ImmutableHash.new(options, thread_options)
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 DefaultOptions
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.default_options.validate_hash!(opts)
80
- self.class.default_options.validate_hash!(self.class.thread_options) if self.class.thread_options
81
- @options = ImmutableHash.new({}.update(self.class.default_options).update(self.class.thread_options || {}).update(opts))
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 DefaultOptions
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.default_options[:engine] = engine
22
- template.default_options.update(options)
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
- if ::ActionPack::VERSION::MAJOR == 3 && ::ActionPack::VERSION::MINOR < 1
12
- class Rails < ActionView::TemplateHandler
13
- include ActionView::TemplateHandlers::Compilable
14
- extend Mixins::Template
3
+ class Rails
4
+ extend Mixins::Template
15
5
 
16
- def compile(template)
17
- # Overwrite option: No streaming support in Rails < 3.1
18
- opts = {}.update(self.class.default_options).update(:file => template.identifier, :streaming => false)
19
- self.class.compile(template.source, opts)
20
- end
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
- def call(template)
33
- opts = {}.update(self.class.default_options).update(:file => template.identifier)
34
- self.class.compile(template.source, opts)
35
- end
15
+ def supports_streaming?
16
+ self.class.options[:streaming]
17
+ end
36
18
 
37
- def supports_streaming?
38
- self.class.default_options[:streaming]
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
- def self.register_as(*names)
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 :mime_type => 'text/html'
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
- # Overwrite option: No streaming support in Tilt
25
- opts = {}.update(self.class.default_options).update(options).update(:file => eval_file, :streaming => false)
26
- opts.delete(:mime_type)
27
- opts.delete(:outvar) # Sinatra gives us this invalid variable
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 'escape_utils'
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
- html.html_safe? ? html : escape_html(html)
17
+ s = html.to_s
18
+ s.html_safe? || html.html_safe? ? s : escape_html(s)
19
19
  end
20
20
 
21
- if defined?(EscapeUtils)
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
- EscapeUtils.escape_html(html.to_s, false)
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
  '>' => '&gt;'
38
38
  }.freeze
39
39
 
40
- if //.respond_to?(:encoding)
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
- if RUBY_VERSION > '1.9'
49
- # Returns an escaped copy of `html`.
50
- #
51
- # @param html [String] The string to escape
52
- # @return [String] The escaped string
53
- def escape_html(html)
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
@@ -1,3 +1,3 @@
1
1
  module Temple
2
- VERSION = '0.6.7'
2
+ VERSION = '0.10.0'
3
3
  end