temple 0.7.6 → 0.7.7

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