temple 0.7.6 → 0.7.7

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 49450d407bc6ae1762271d1ece670cdb9913b787
4
- data.tar.gz: 946bbd14856936cb2d7dbbb431ecc8a9df55f26a
3
+ metadata.gz: 70234f9ea5cb84da4b1a8cdcfc8f63056d8af5ce
4
+ data.tar.gz: 4b76d19c5643183eb268c83dffc968f777c8d97a
5
5
  SHA512:
6
- metadata.gz: 26a6a8e828240ea1293548173b2816b4378fce9f1609b946c48317652b02c2eb3e29aec99570e313de2a2155cab975cdc135c3529a209be693f6a9e8279ce97a
7
- data.tar.gz: 542489791fab203a48b9810def3718e6416c00be2aa0a86587f4234b4f8d292977e29b4322b709ed2d9164ed1d21c71900c1feb58863a6c74952688622a7a954
6
+ metadata.gz: 9c7ec6543f4fc8d3d95fc5046a606135605886b5e0ab67deb50e311ae50b5ff7b3030971173ca42d1ca8b67a45c33427b2b2cbaaac0b071f02d17dfcae0ef274
7
+ data.tar.gz: a8fb82e723319e0b4f1b11cd33c7b155f1cebda73345bf418b22626bd23b566b85904e45474535c8406fdd9693c6e1a7d75547b9c9d323d8b298cb9bffa86c50
@@ -4,12 +4,17 @@ rvm:
4
4
  - 1.9.3
5
5
  - 2.0.0
6
6
  - 2.1.0
7
+ - 2.3.0
7
8
  - ruby-head
8
9
  - jruby-19mode
9
10
  - rbx-2
10
11
 
11
12
  sudo: false
12
13
 
14
+ env:
15
+ - ESCAPE_UTILS=1
16
+ - ""
17
+
13
18
  matrix:
14
19
  allow_failures:
15
20
  - rvm: ruby-head
data/CHANGES CHANGED
@@ -1,3 +1,8 @@
1
+ 0.7.7
2
+
3
+ * Add StaticAnalyzer, StringSplitter
4
+ * Freeze string literals
5
+
1
6
  0.7.6
2
7
 
3
8
  * EngineDSL - add support for use(:Filter) { FilterClassName }
data/Gemfile CHANGED
@@ -1,3 +1,3 @@
1
1
  source 'https://rubygems.org/'
2
2
  gemspec
3
-
3
+ gem 'escape_utils' if ENV['ESCAPE_UTILS']
@@ -43,7 +43,9 @@ module Temple
43
43
  autoload :CodeMerger, 'temple/filters/code_merger'
44
44
  autoload :ControlFlow, 'temple/filters/control_flow'
45
45
  autoload :MultiFlattener, 'temple/filters/multi_flattener'
46
+ autoload :StaticAnalyzer, 'temple/filters/static_analyzer'
46
47
  autoload :StaticMerger, 'temple/filters/static_merger'
48
+ autoload :StringSplitter, 'temple/filters/string_splitter'
47
49
  autoload :DynamicInliner, 'temple/filters/dynamic_inliner'
48
50
  autoload :Escapable, 'temple/filters/escapable'
49
51
  autoload :Eraser, 'temple/filters/eraser'
@@ -6,7 +6,7 @@ module Temple
6
6
  class RemoveBOM < Parser
7
7
  def call(s)
8
8
  return s if s.encoding.name !~ /^UTF-(8|16|32)(BE|LE)?/
9
- s.gsub(Regexp.new("\\A\uFEFF".encode(s.encoding.name)), '')
9
+ s.gsub(Regexp.new("\\A\uFEFF".encode(s.encoding.name)), ''.freeze)
10
10
  end
11
11
  end
12
12
  end
@@ -0,0 +1,87 @@
1
+ begin
2
+ require 'ripper'
3
+ rescue LoadError
4
+ end
5
+
6
+ module Temple
7
+ module Filters
8
+ # Convert [:dynamic, code] to [:static, text] if code is static Ruby expression.
9
+ class StaticAnalyzer < Filter
10
+ STATIC_TOKENS = [
11
+ :on_tstring_beg, :on_tstring_end, :on_tstring_content,
12
+ :on_embexpr_beg, :on_embexpr_end,
13
+ :on_lbracket, :on_rbracket,
14
+ :on_qwords_beg, :on_words_sep, :on_qwords_sep,
15
+ :on_lparen, :on_rparen,
16
+ :on_lbrace, :on_rbrace, :on_label,
17
+ :on_int, :on_float, :on_imaginary,
18
+ :on_comma, :on_sp,
19
+ ].freeze
20
+
21
+ DYNAMIC_TOKENS = [
22
+ :on_ident, :on_period,
23
+ ].freeze
24
+
25
+ STATIC_KEYWORDS = [
26
+ 'true', 'false', 'nil',
27
+ ].freeze
28
+
29
+ STATIC_OPERATORS = [
30
+ '=>',
31
+ ].freeze
32
+
33
+ if defined?(Ripper)
34
+ def self.static?(code)
35
+ return false if code.nil? || code.strip.empty?
36
+ return false if SyntaxChecker.syntax_error?(code)
37
+
38
+ Ripper.lex(code).each do |(_, col), token, str|
39
+ case token
40
+ when *STATIC_TOKENS
41
+ # noop
42
+ when :on_kw
43
+ return false unless STATIC_KEYWORDS.include?(str)
44
+ when :on_op
45
+ return false unless STATIC_OPERATORS.include?(str)
46
+ when *DYNAMIC_TOKENS
47
+ return false
48
+ else
49
+ return false
50
+ end
51
+ end
52
+ true
53
+ end
54
+
55
+ def on_dynamic(code)
56
+ if StaticAnalyzer.static?(code)
57
+ [:static, eval(code).to_s]
58
+ else
59
+ [:dynamic, code]
60
+ end
61
+ end
62
+
63
+ class SyntaxChecker < Ripper
64
+ class ParseError < StandardError; end
65
+
66
+ def self.syntax_error?(code)
67
+ self.new(code).parse
68
+ false
69
+ rescue ParseError
70
+ true
71
+ end
72
+
73
+ private
74
+
75
+ def on_parse_error(*)
76
+ raise ParseError
77
+ end
78
+ end
79
+ else
80
+ # Do nothing if ripper is unavailable
81
+ def call(ast)
82
+ ast
83
+ end
84
+ end
85
+ end
86
+ end
87
+ end
@@ -0,0 +1,130 @@
1
+ begin
2
+ require 'ripper'
3
+ rescue LoadError
4
+ end
5
+
6
+ module Temple
7
+ module Filters
8
+ # Compile [:dynamic, "foo#{bar}"] to [:multi, [:static, 'foo'], [:dynamic, 'bar']]
9
+ class StringSplitter < Filter
10
+ if defined?(Ripper) && RUBY_VERSION >= "2.0.0"
11
+ class << self
12
+ # `code` param must be valid string literal
13
+ def compile(code)
14
+ [].tap do |exps|
15
+ tokens = Ripper.lex(code.strip)
16
+ tokens.pop while tokens.last && [:on_comment, :on_sp].include?(tokens.last[1])
17
+
18
+ if tokens.size < 2
19
+ raise "Expected token size >= 2 but got: #{tokens.size}"
20
+ end
21
+ compile_tokens!(exps, tokens)
22
+ end
23
+ end
24
+
25
+ private
26
+
27
+ def strip_quotes!(tokens)
28
+ _, type, beg_str = tokens.shift
29
+ if type != :on_tstring_beg
30
+ raise "Expected :on_tstring_beg but got: #{type}"
31
+ end
32
+
33
+ _, type, end_str = tokens.pop
34
+ if type != :on_tstring_end
35
+ raise "Expected :on_tstring_end but got: #{type}"
36
+ end
37
+
38
+ [beg_str, end_str]
39
+ end
40
+
41
+ def compile_tokens!(exps, tokens)
42
+ beg_str, end_str = strip_quotes!(tokens)
43
+
44
+ until tokens.empty?
45
+ _, type, str = tokens.shift
46
+
47
+ case type
48
+ when :on_tstring_content
49
+ exps << [:static, eval("#{beg_str}#{str}#{end_str}").to_s]
50
+ when :on_embexpr_beg
51
+ embedded = shift_balanced_embexpr(tokens)
52
+ exps << [:dynamic, embedded] unless embedded.empty?
53
+ end
54
+ end
55
+ end
56
+
57
+ def shift_balanced_embexpr(tokens)
58
+ String.new.tap do |embedded|
59
+ embexpr_open = 1
60
+
61
+ until tokens.empty?
62
+ _, type, str = tokens.shift
63
+ case type
64
+ when :on_embexpr_beg
65
+ embexpr_open += 1
66
+ when :on_embexpr_end
67
+ embexpr_open -= 1
68
+ break if embexpr_open == 0
69
+ end
70
+
71
+ embedded << str
72
+ end
73
+ end
74
+ end
75
+ end
76
+
77
+ def on_dynamic(code)
78
+ return [:dynamic, code] unless string_literal?(code)
79
+ return [:dynamic, code] if code.include?("\n")
80
+
81
+ temple = [:multi]
82
+ StringSplitter.compile(code).each do |type, content|
83
+ case type
84
+ when :static
85
+ temple << [:static, content]
86
+ when :dynamic
87
+ temple << on_dynamic(content)
88
+ end
89
+ end
90
+ temple
91
+ end
92
+
93
+ private
94
+
95
+ def string_literal?(code)
96
+ return false if SyntaxChecker.syntax_error?(code)
97
+
98
+ type, instructions = Ripper.sexp(code)
99
+ return false if type != :program
100
+ return false if instructions.size > 1
101
+
102
+ type, _ = instructions.first
103
+ type == :string_literal
104
+ end
105
+
106
+ class SyntaxChecker < Ripper
107
+ class ParseError < StandardError; end
108
+
109
+ def self.syntax_error?(code)
110
+ self.new(code).parse
111
+ false
112
+ rescue ParseError
113
+ true
114
+ end
115
+
116
+ private
117
+
118
+ def on_parse_error(*)
119
+ raise ParseError
120
+ end
121
+ end
122
+ else
123
+ # Do nothing if ripper is unavailable
124
+ def call(ast)
125
+ ast
126
+ end
127
+ end
128
+ end
129
+ end
130
+ end
@@ -46,7 +46,7 @@ module Temple
46
46
  end
47
47
 
48
48
  def on_multi(*exp)
49
- exp.map {|e| compile(e) }.join('; ')
49
+ exp.map {|e| compile(e) }.join('; '.freeze)
50
50
  end
51
51
 
52
52
  def on_newline
@@ -5,7 +5,7 @@ module Temple
5
5
  # _buf = []
6
6
  # _buf << "static"
7
7
  # _buf << dynamic
8
- # _buf.join
8
+ # _buf.join("")
9
9
  #
10
10
  # @api public
11
11
  class ArrayBuffer < Array
@@ -21,7 +21,8 @@ module Temple
21
21
  end
22
22
 
23
23
  def return_buffer
24
- "#{buffer} = #{buffer}.join"
24
+ freeze = options[:freeze_static] ? '.freeze' : ''
25
+ "#{buffer} = #{buffer}.join(\"\"#{freeze})"
25
26
  end
26
27
  end
27
28
  end
@@ -9,7 +9,7 @@ module Temple
9
9
  end
10
10
 
11
11
  def on_multi(*exp)
12
- exp.map {|e| compile(e) }.join
12
+ exp.map {|e| compile(e) }.join('')
13
13
  end
14
14
 
15
15
  def on_capture(name, exp)
@@ -16,7 +16,7 @@ module Temple
16
16
  capture_generator: RailsOutputBuffer
17
17
 
18
18
  def call(exp)
19
- [preamble, compile(exp), postamble].flatten.compact.join('; ')
19
+ [preamble, compile(exp), postamble].flatten.compact.join('; '.freeze)
20
20
  end
21
21
 
22
22
  def create_buffer
@@ -6,40 +6,35 @@ module Temple
6
6
  define_options merge_attrs: {'id' => '_', 'class' => ' '}
7
7
 
8
8
  def on_html_attrs(*attrs)
9
- names = []
10
9
  values = {}
11
10
 
12
- attrs.each do |attr|
13
- name, value = attr[2].to_s, attr[3]
11
+ attrs.each do |_, _, name, value|
12
+ name = name.to_s
14
13
  if values[name]
15
14
  raise(FilterError, "Multiple #{name} attributes specified") unless options[:merge_attrs][name]
16
15
  values[name] << value
17
16
  else
18
17
  values[name] = [value]
19
- names << name
20
18
  end
21
19
  end
22
20
 
23
- attrs = names.map do |name|
24
- value = values[name]
21
+ attrs = values.map do |name, value|
25
22
  if (delimiter = options[:merge_attrs][name]) && value.size > 1
26
23
  exp = [:multi]
27
24
  if value.all? {|v| contains_nonempty_static?(v) }
28
25
  exp << value.first
29
26
  value[1..-1].each {|v| exp << [:static, delimiter] << v }
30
- [:html, :attr, name, exp]
31
27
  else
32
28
  captures = unique_name
33
29
  exp << [:code, "#{captures} = []"]
34
30
  value.each_with_index {|v, i| exp << [:capture, "#{captures}[#{i}]", v] }
35
31
  exp << [:dynamic, "#{captures}.reject(&:empty?).join(#{delimiter.inspect})"]
36
32
  end
37
- [:html, :attr, name, exp]
38
33
  else
39
- [:html, :attr, name, value.first]
34
+ exp = value.first
40
35
  end
36
+ [:html, :attr, name, exp]
41
37
  end
42
-
43
38
  [:html, :attrs, *attrs]
44
39
  end
45
40
  end
@@ -26,8 +26,8 @@ module Temple
26
26
  def on_static(content)
27
27
  return [:static, content] unless @pretty
28
28
  unless @pre_tags && @pre_tags =~ content
29
- content = content.sub(/\A\s*\n?/, "\n") if @indent_next
30
- content = content.gsub("\n", indent)
29
+ content = content.sub(/\A\s*\n?/, "\n".freeze) if @indent_next
30
+ content = content.gsub("\n".freeze, indent)
31
31
  end
32
32
  @indent_next = false
33
33
  [:static, content]
@@ -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
 
@@ -91,7 +93,7 @@ end}
91
93
  code = "case(exp[#{level}])\n"
92
94
  each do |key, child|
93
95
  code << "when #{key.inspect}\n " <<
94
- child.compile(level + 1, call_method).gsub("\n", "\n ") << "\n"
96
+ child.compile(level + 1, call_method).gsub("\n".freeze, "\n ".freeze) << "\n".freeze
95
97
  end
96
98
  code << "else\n " << (call_method || 'exp') << "\nend"
97
99
  end
@@ -1,7 +1,10 @@
1
1
  begin
2
2
  require 'escape_utils'
3
3
  rescue LoadError
4
- # Loading EscapeUtils failed
4
+ begin
5
+ require 'cgi/escape'
6
+ rescue LoadError
7
+ end
5
8
  end
6
9
 
7
10
  module Temple
@@ -26,6 +29,14 @@ module Temple
26
29
  def escape_html(html)
27
30
  EscapeUtils.escape_html(html.to_s, false)
28
31
  end
32
+ elsif defined?(CGI.escapeHTML)
33
+ # Returns an escaped copy of `html`.
34
+ #
35
+ # @param html [String] The string to escape
36
+ # @return [String] The escaped string
37
+ def escape_html(html)
38
+ CGI.escapeHTML(html.to_s)
39
+ end
29
40
  else
30
41
  # Used by escape_html
31
42
  # @api private
@@ -54,7 +65,7 @@ module Temple
54
65
  # @return [String] Variable name
55
66
  def unique_name(prefix = nil)
56
67
  @unique_name ||= 0
57
- prefix ||= (@unique_prefix ||= self.class.name.gsub('::', '_').downcase)
68
+ prefix ||= (@unique_prefix ||= self.class.name.gsub('::'.freeze, '_'.freeze).downcase)
58
69
  "_#{prefix}#{@unique_name += 1}"
59
70
  end
60
71
 
@@ -81,8 +92,8 @@ module Temple
81
92
  level = text.scan(/^\s*/).map(&:size).min
82
93
  text = text.gsub(/(?!\A)^\s{#{level}}/, '') if level > 0
83
94
 
84
- text = text.sub(/\A\s*\n?/, "\n") if indent_next
85
- text = text.gsub("\n", indent)
95
+ text = text.sub(/\A\s*\n?/, "\n".freeze) if indent_next
96
+ text = text.gsub("\n".freeze, indent)
86
97
 
87
98
  safe ? text.html_safe : text
88
99
  end
@@ -1,3 +1,3 @@
1
1
  module Temple
2
- VERSION = '0.7.6'
2
+ VERSION = '0.7.7'
3
3
  end
@@ -0,0 +1,23 @@
1
+ require 'helper'
2
+ begin
3
+ require 'ripper'
4
+ rescue LoadError
5
+ end
6
+
7
+ if defined?(Ripper)
8
+ describe Temple::Filters::StaticAnalyzer do
9
+ before do
10
+ @filter = Temple::Filters::StaticAnalyzer.new
11
+ end
12
+
13
+ it 'should convert :dynamic to :static if code is static' do
14
+ @filter.call([:dynamic, '"#{"hello"}#{100}"']
15
+ ).should.equal [:static, 'hello100']
16
+ end
17
+
18
+ it 'should not convert :dynamic if code is dynamic' do
19
+ exp = [:dynamic, '"#{hello}#{100}"']
20
+ @filter.call(exp).should.equal(exp)
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,18 @@
1
+ require 'helper'
2
+ begin
3
+ require 'ripper'
4
+ rescue LoadError
5
+ end
6
+
7
+ if defined?(Ripper) && RUBY_VERSION >= "2.0.0"
8
+ describe Temple::Filters::StringSplitter do
9
+ before do
10
+ @filter = Temple::Filters::StringSplitter.new
11
+ end
12
+
13
+ it 'should split :dynamic with string literal' do
14
+ @filter.call([:dynamic, '"static#{dynamic}"']
15
+ ).should.equal [:multi, [:static, 'static'], [:dynamic, 'dynamic']]
16
+ end
17
+ end
18
+ end
@@ -97,16 +97,16 @@ describe Temple::Generators::ArrayBuffer do
97
97
  gen = Temple::Generators::ArrayBuffer.new(freeze_static: false)
98
98
  gen.call([:static, 'test']).should.equal '_buf = "test"'
99
99
  gen.call([:dynamic, 'test']).should.equal '_buf = (test).to_s'
100
- gen.call([:code, 'test']).should.equal '_buf = []; test; _buf = _buf.join'
100
+ gen.call([:code, 'test']).should.equal '_buf = []; test; _buf = _buf.join("")'
101
101
 
102
- gen.call([:multi, [:static, 'a'], [:static, 'b']]).should.equal '_buf = []; _buf << ("a"); _buf << ("b"); _buf = _buf.join'
103
- gen.call([:multi, [:static, 'a'], [:dynamic, 'b']]).should.equal '_buf = []; _buf << ("a"); _buf << (b); _buf = _buf.join'
102
+ gen.call([:multi, [:static, 'a'], [:static, 'b']]).should.equal '_buf = []; _buf << ("a"); _buf << ("b"); _buf = _buf.join("")'
103
+ gen.call([:multi, [:static, 'a'], [:dynamic, 'b']]).should.equal '_buf = []; _buf << ("a"); _buf << (b); _buf = _buf.join("")'
104
104
  end
105
105
 
106
106
  it 'should freeze static' do
107
107
  gen = Temple::Generators::ArrayBuffer.new(freeze_static: true)
108
108
  gen.call([:static, 'test']).should.equal '_buf = "test"'
109
- gen.call([:multi, [:dynamic, '1'], [:static, 'test']]).should.equal '_buf = []; _buf << (1); _buf << ("test".freeze); _buf = _buf.join'
109
+ gen.call([:multi, [:dynamic, '1'], [:static, 'test']]).should.equal '_buf = []; _buf << (1); _buf << ("test".freeze); _buf = _buf.join("".freeze)'
110
110
  end
111
111
  end
112
112
 
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: temple
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.7.6
4
+ version: 0.7.7
5
5
  platform: ruby
6
6
  authors:
7
7
  - Magnus Holm
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2015-06-07 00:00:00.000000000 Z
12
+ date: 2016-05-22 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: tilt
@@ -100,7 +100,9 @@ files:
100
100
  - lib/temple/filters/escapable.rb
101
101
  - lib/temple/filters/multi_flattener.rb
102
102
  - lib/temple/filters/remove_bom.rb
103
+ - lib/temple/filters/static_analyzer.rb
103
104
  - lib/temple/filters/static_merger.rb
105
+ - lib/temple/filters/string_splitter.rb
104
106
  - lib/temple/filters/validator.rb
105
107
  - lib/temple/generator.rb
106
108
  - lib/temple/generators/array.rb
@@ -136,7 +138,9 @@ files:
136
138
  - test/filters/test_eraser.rb
137
139
  - test/filters/test_escapable.rb
138
140
  - test/filters/test_multi_flattener.rb
141
+ - test/filters/test_static_analyzer.rb
139
142
  - test/filters/test_static_merger.rb
143
+ - test/filters/test_string_splitter.rb
140
144
  - test/helper.rb
141
145
  - test/html/test_attribute_merger.rb
142
146
  - test/html/test_attribute_remover.rb
@@ -177,4 +181,3 @@ signing_key:
177
181
  specification_version: 4
178
182
  summary: Template compilation framework in Ruby
179
183
  test_files: []
180
- has_rdoc: