temple 0.6.7 → 0.10.0

Sign up to get free protection for your applications and to get access to all the features.
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