trenni 2.1.0 → 3.0.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.
- checksums.yaml +4 -4
- data/.rspec +1 -0
- data/.travis.yml +1 -0
- data/Gemfile +9 -6
- data/README.md +66 -61
- data/Rakefile +3 -7
- data/ext/trenni/escape.c +150 -0
- data/ext/trenni/escape.h +15 -0
- data/ext/trenni/extconf.rb +5 -0
- data/ext/trenni/markup.c +280 -258
- data/ext/trenni/markup.rl +12 -7
- data/ext/trenni/tag.c +202 -0
- data/ext/trenni/tag.h +21 -0
- data/ext/trenni/template.c +29 -29
- data/ext/trenni/trenni.c +20 -3
- data/ext/trenni/trenni.h +84 -9
- data/lib/trenni/buffer.rb +20 -4
- data/lib/trenni/builder.rb +15 -38
- data/lib/trenni/entities.rb +0 -2
- data/lib/trenni/entities.trenni +0 -2
- data/lib/trenni/fallback/markup.rb +1576 -1584
- data/lib/trenni/fallback/markup.rl +9 -1
- data/lib/trenni/fallback/template.rb +744 -753
- data/lib/trenni/markup.rb +18 -25
- data/lib/trenni/native.rb +4 -1
- data/lib/trenni/parsers.rb +1 -1
- data/lib/trenni/tag.rb +130 -0
- data/lib/trenni/template.rb +35 -15
- data/lib/trenni/version.rb +1 -1
- data/spec/spec_helper.rb +29 -0
- data/spec/trenni/builder_spec.rb +1 -1
- data/spec/trenni/markup_parser_spec.rb +20 -0
- data/spec/trenni/markup_performance_spec.rb +26 -0
- data/spec/trenni/markup_spec.rb +3 -1
- data/spec/trenni/tag_spec.rb +69 -0
- data/spec/trenni/template_performance_spec.rb +18 -0
- data/spec/trenni/template_spec/interpolations.trenni +6 -0
- metadata +16 -4
- data/lib/trenni/substitutions.rb +0 -45
data/lib/trenni/markup.rb
CHANGED
@@ -18,47 +18,46 @@
|
|
18
18
|
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
19
19
|
# THE SOFTWARE.
|
20
20
|
|
21
|
-
|
21
|
+
require 'cgi'
|
22
22
|
|
23
23
|
module Trenni
|
24
24
|
# A wrapper which indicates that `value` can be appended to the output buffer without any changes.
|
25
25
|
module Markup
|
26
|
-
#
|
27
|
-
|
26
|
+
# Converts special characters `<`, `>`, `&`, and `"` into their equivalent entities.
|
27
|
+
# @return [String] May return the original string if no changes were made.
|
28
|
+
def self.escape_string(string)
|
29
|
+
CGI.escape_html(string)
|
30
|
+
end
|
31
|
+
|
32
|
+
# Appends a string to the output buffer, escaping if if necessary.
|
33
|
+
def self.append(buffer, value)
|
28
34
|
if value.is_a? Markup
|
29
|
-
value
|
35
|
+
buffer << value
|
30
36
|
elsif value
|
31
|
-
|
32
|
-
else
|
33
|
-
# String#<< won't accept nil, so we return an empty string, thus ensuring a fixed point function:
|
34
|
-
EMPTY
|
37
|
+
buffer << self.escape_string(value.to_s)
|
35
38
|
end
|
36
39
|
end
|
37
|
-
|
38
|
-
def escape(value)
|
39
|
-
Markup.escape(value)
|
40
|
-
end
|
41
40
|
end
|
42
41
|
|
43
42
|
# Initialized from text which is escaped to use HTML entities.
|
44
43
|
class MarkupString < String
|
45
44
|
include Markup
|
46
45
|
|
47
|
-
#
|
48
|
-
|
49
|
-
|
50
|
-
# Convert ESCAPE characters into their corresponding entities.
|
46
|
+
# @param string [String] the string value itself.
|
47
|
+
# @param escape [Boolean] whether or not to escape the string.
|
51
48
|
def initialize(string = nil, escape = true)
|
52
49
|
if string
|
53
|
-
|
50
|
+
if escape
|
51
|
+
string = Markup.escape_string(string)
|
52
|
+
end
|
54
53
|
|
55
|
-
|
56
|
-
ESCAPE.gsub!(self) if escape
|
54
|
+
super(string)
|
57
55
|
else
|
58
56
|
super()
|
59
57
|
end
|
60
58
|
end
|
61
59
|
|
60
|
+
# Generate a valid MarkupString withot any escaping.
|
62
61
|
def self.raw(string)
|
63
62
|
self.new(string, false)
|
64
63
|
end
|
@@ -69,10 +68,4 @@ module Trenni
|
|
69
68
|
MarkupString.new(JSON.dump(value), false)
|
70
69
|
end
|
71
70
|
end
|
72
|
-
|
73
|
-
def self.MarkupString(value)
|
74
|
-
Markup.escape(value)
|
75
|
-
end
|
76
|
-
|
77
|
-
Markup::EMPTY = String.new.extend(Markup).freeze
|
78
71
|
end
|
data/lib/trenni/native.rb
CHANGED
@@ -20,9 +20,12 @@
|
|
20
20
|
|
21
21
|
require_relative 'parse_error'
|
22
22
|
|
23
|
+
# Methods on the following classes may be replaced by native implementations:
|
24
|
+
require_relative 'tag'
|
25
|
+
|
23
26
|
begin
|
24
27
|
# Load native code:
|
25
28
|
require_relative 'trenni'
|
26
29
|
rescue LoadError
|
27
30
|
warn "Could not load native implementation: #{$!}" if $VERBOSE
|
28
|
-
end
|
31
|
+
end unless ENV['TRENNI_PREFER_FALLBACK']
|
data/lib/trenni/parsers.rb
CHANGED
data/lib/trenni/tag.rb
ADDED
@@ -0,0 +1,130 @@
|
|
1
|
+
# Copyright, 2012, by Samuel G. D. Williams. <http://www.codeotaku.com>
|
2
|
+
#
|
3
|
+
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
4
|
+
# of this software and associated documentation files (the "Software"), to deal
|
5
|
+
# in the Software without restriction, including without limitation the rights
|
6
|
+
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
7
|
+
# copies of the Software, and to permit persons to whom the Software is
|
8
|
+
# furnished to do so, subject to the following conditions:
|
9
|
+
#
|
10
|
+
# The above copyright notice and this permission notice shall be included in
|
11
|
+
# all copies or substantial portions of the Software.
|
12
|
+
#
|
13
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
14
|
+
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
15
|
+
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
16
|
+
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
17
|
+
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
18
|
+
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
19
|
+
# THE SOFTWARE.
|
20
|
+
|
21
|
+
require_relative 'markup'
|
22
|
+
|
23
|
+
module Trenni
|
24
|
+
# This represents an individual SGML tag, e.g. <a>, </a> or <a />, with attributes. Attribute values must be escaped.
|
25
|
+
Tag = Struct.new(:name, :closed, :attributes) do
|
26
|
+
include Trenni::Markup
|
27
|
+
|
28
|
+
def self.split(qualified_name)
|
29
|
+
if i = qualified_name.index(':')
|
30
|
+
return qualified_name.slice(0...i), qualified_name.slice(i+1..-1)
|
31
|
+
else
|
32
|
+
return nil, qualified_name
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
def self.closed(name, **attributes)
|
37
|
+
self.new(name, true, attributes)
|
38
|
+
end
|
39
|
+
|
40
|
+
def self.opened(name, **attributes)
|
41
|
+
self.new(name, false, attributes)
|
42
|
+
end
|
43
|
+
|
44
|
+
def [] key
|
45
|
+
attributes[key]
|
46
|
+
end
|
47
|
+
|
48
|
+
alias to_hash attributes
|
49
|
+
|
50
|
+
def to_s(content = nil)
|
51
|
+
self.class.format_tag(name, attributes, content || !closed)
|
52
|
+
end
|
53
|
+
|
54
|
+
alias to_str to_s
|
55
|
+
|
56
|
+
def self_closed?
|
57
|
+
closed
|
58
|
+
end
|
59
|
+
|
60
|
+
def write_opening_tag(buffer)
|
61
|
+
buffer << '<' << name
|
62
|
+
|
63
|
+
self.class.append_attributes(buffer, attributes, nil)
|
64
|
+
|
65
|
+
if self_closed?
|
66
|
+
buffer << '/>'
|
67
|
+
else
|
68
|
+
buffer << '>'
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
def write_closing_tag(buffer)
|
73
|
+
buffer << '</' << name << '>'
|
74
|
+
end
|
75
|
+
|
76
|
+
def write(buffer, content = nil)
|
77
|
+
self.class.append_tag(buffer, name, attributes, content || !closed)
|
78
|
+
end
|
79
|
+
|
80
|
+
def self.format_tag(name, attributes, content)
|
81
|
+
buffer = String.new.force_encoding(name.encoding)
|
82
|
+
|
83
|
+
self.append_tag(buffer, name, attributes, content)
|
84
|
+
|
85
|
+
return buffer
|
86
|
+
end
|
87
|
+
|
88
|
+
def self.append_tag(buffer, name, attributes, content)
|
89
|
+
buffer << '<' << name.to_s
|
90
|
+
|
91
|
+
self.append_attributes(buffer, attributes, nil)
|
92
|
+
|
93
|
+
if !content
|
94
|
+
buffer << '/>'
|
95
|
+
else
|
96
|
+
buffer << '>'
|
97
|
+
unless content == true
|
98
|
+
Markup.append(buffer, content)
|
99
|
+
end
|
100
|
+
buffer << '</' << name.to_s << '>'
|
101
|
+
end
|
102
|
+
|
103
|
+
return nil
|
104
|
+
end
|
105
|
+
|
106
|
+
# Convert a set of attributes into a string suitable for use within a <tag>.
|
107
|
+
def self.append_attributes(buffer, attributes, prefix)
|
108
|
+
attributes.each do |key, value|
|
109
|
+
next unless value
|
110
|
+
|
111
|
+
attribute_key = prefix ? "#{prefix}-#{key}" : key
|
112
|
+
|
113
|
+
case value
|
114
|
+
when Hash
|
115
|
+
self.append_attributes(buffer, value, attribute_key)
|
116
|
+
when Array
|
117
|
+
self.append_attributes(buffer, value, attribute_key)
|
118
|
+
when TrueClass
|
119
|
+
buffer << ' ' << attribute_key.to_s
|
120
|
+
else
|
121
|
+
buffer << ' ' << attribute_key.to_s << '="'
|
122
|
+
Markup.append(buffer, value)
|
123
|
+
buffer << '"'
|
124
|
+
end
|
125
|
+
end
|
126
|
+
|
127
|
+
return nil
|
128
|
+
end
|
129
|
+
end
|
130
|
+
end
|
data/lib/trenni/template.rb
CHANGED
@@ -59,9 +59,8 @@ module Trenni
|
|
59
59
|
end
|
60
60
|
|
61
61
|
class Assembler
|
62
|
-
def initialize(
|
62
|
+
def initialize(encoding: Encoding::UTF_8)
|
63
63
|
@code = String.new.force_encoding(encoding)
|
64
|
-
@filter = filter
|
65
64
|
end
|
66
65
|
|
67
66
|
attr :code
|
@@ -83,20 +82,27 @@ module Trenni
|
|
83
82
|
# Output a string interpolation.
|
84
83
|
def expression(text)
|
85
84
|
# Double brackets are required here to handle expressions like #{foo rescue "bar"}.
|
86
|
-
@code << "#{OUT}
|
85
|
+
@code << "#{OUT}<<String(#{text});"
|
87
86
|
end
|
88
87
|
end
|
89
88
|
|
90
|
-
def self.load_file(path,
|
91
|
-
self.new(FileBuffer.new(path),
|
89
|
+
def self.load_file(path, *args)
|
90
|
+
self.new(FileBuffer.new(path), *args).freeze
|
92
91
|
end
|
93
92
|
|
94
|
-
def initialize(buffer
|
93
|
+
def initialize(buffer)
|
95
94
|
@buffer = buffer
|
96
|
-
@filter = filter
|
97
95
|
end
|
98
|
-
|
99
|
-
def
|
96
|
+
|
97
|
+
def freeze
|
98
|
+
return self if frozen?
|
99
|
+
|
100
|
+
to_proc
|
101
|
+
|
102
|
+
super
|
103
|
+
end
|
104
|
+
|
105
|
+
def to_string(scope = Object.new, output = nil)
|
100
106
|
output ||= output_buffer
|
101
107
|
|
102
108
|
scope.instance_exec(output, &to_proc)
|
@@ -105,9 +111,9 @@ module Trenni
|
|
105
111
|
def to_buffer(scope)
|
106
112
|
Buffer.new(to_string(scope), path: @buffer.path)
|
107
113
|
end
|
108
|
-
|
114
|
+
|
109
115
|
def to_proc(scope = nil)
|
110
|
-
@compiled_proc ||= eval("proc{|#{OUT}|;#{code};#{OUT}}", scope, @buffer.path)
|
116
|
+
@compiled_proc ||= eval("proc{|#{OUT}|;#{code};#{OUT}}", scope, @buffer.path).freeze
|
111
117
|
end
|
112
118
|
|
113
119
|
protected
|
@@ -120,8 +126,12 @@ module Trenni
|
|
120
126
|
@code ||= compile!
|
121
127
|
end
|
122
128
|
|
129
|
+
def make_assembler
|
130
|
+
Assembler.new
|
131
|
+
end
|
132
|
+
|
123
133
|
def compile!
|
124
|
-
assembler =
|
134
|
+
assembler = make_assembler
|
125
135
|
|
126
136
|
Parsers.parse_template(@buffer, assembler)
|
127
137
|
|
@@ -130,11 +140,21 @@ module Trenni
|
|
130
140
|
end
|
131
141
|
|
132
142
|
class MarkupTemplate < Template
|
133
|
-
|
134
|
-
|
143
|
+
class Assembler < Template::Assembler
|
144
|
+
# Output a string interpolation.
|
145
|
+
def expression(text)
|
146
|
+
@code << "Markup.append(#{OUT},(#{text}));"
|
147
|
+
end
|
148
|
+
end
|
149
|
+
|
150
|
+
protected
|
151
|
+
|
152
|
+
# We need an assembler which builds specific `Markup.append` sequences.
|
153
|
+
def make_assembler
|
154
|
+
Assembler.new
|
135
155
|
end
|
136
156
|
|
137
|
-
# The output of the markup template is encoded markup (e.g. with entities, tags, etc)
|
157
|
+
# The output of the markup template is encoded markup (e.g. with entities, tags, etc).
|
138
158
|
def output_buffer
|
139
159
|
MarkupString.new.force_encoding(code.encoding)
|
140
160
|
end
|
data/lib/trenni/version.rb
CHANGED
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
|
2
|
+
if ENV['COVERAGE']
|
3
|
+
begin
|
4
|
+
require 'simplecov'
|
5
|
+
|
6
|
+
SimpleCov.start do
|
7
|
+
add_filter "/spec/"
|
8
|
+
end
|
9
|
+
|
10
|
+
if ENV['TRAVIS']
|
11
|
+
require 'coveralls'
|
12
|
+
Coveralls.wear!
|
13
|
+
end
|
14
|
+
rescue LoadError
|
15
|
+
warn "Could not load simplecov: #{$!}"
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
require "bundler/setup"
|
20
|
+
require "trenni"
|
21
|
+
|
22
|
+
RSpec.configure do |config|
|
23
|
+
# Enable flags like --only-failures and --next-failure
|
24
|
+
config.example_status_persistence_file_path = ".rspec_status"
|
25
|
+
|
26
|
+
config.expect_with :rspec do |c|
|
27
|
+
c.syntax = :expect
|
28
|
+
end
|
29
|
+
end
|
data/spec/trenni/builder_spec.rb
CHANGED
@@ -115,6 +115,11 @@ RSpec.describe "<foo bar=\"20\" baz>Hello World</foo>" do
|
|
115
115
|
expect(events[4][1].encoding).to be == subject.encoding
|
116
116
|
expect(events[5][1].encoding).to be == subject.encoding
|
117
117
|
end
|
118
|
+
|
119
|
+
it "should track entities" do
|
120
|
+
expect(events[1][2]).to be_kind_of Trenni::Markup
|
121
|
+
expect(events[4][1]).to be_kind_of Trenni::Markup
|
122
|
+
end
|
118
123
|
end
|
119
124
|
|
120
125
|
RSpec.describe "<test><![CDATA[Hello World]]></test>" do
|
@@ -150,10 +155,25 @@ RSpec.describe "<p attr=\"foo&bar\">"</p>" do
|
|
150
155
|
let(:template_buffer) {Trenni::Buffer(template_text)}
|
151
156
|
let(:template) {Trenni::MarkupTemplate.new(template_buffer)}
|
152
157
|
|
158
|
+
it "should parse empty attributes" do
|
159
|
+
expect(events).to be == [
|
160
|
+
[:open_tag_begin, "p", 1],
|
161
|
+
[:attribute, "attr", "foo&bar"],
|
162
|
+
[:open_tag_end, false],
|
163
|
+
[:text, "\""],
|
164
|
+
[:close_tag, "p", 30]
|
165
|
+
]
|
166
|
+
end
|
167
|
+
|
153
168
|
it "generates same output as input" do
|
154
169
|
result = template.to_string(self)
|
155
170
|
expect(result).to be == subject
|
156
171
|
end
|
172
|
+
|
173
|
+
it "should track entities" do
|
174
|
+
expect(events[1][2]).to_not be_kind_of Trenni::Markup
|
175
|
+
expect(events[3][1]).to_not be_kind_of Trenni::Markup
|
176
|
+
end
|
157
177
|
end
|
158
178
|
|
159
179
|
RSpec.shared_examples "valid markup file" do |base|
|
@@ -0,0 +1,26 @@
|
|
1
|
+
|
2
|
+
require 'benchmark/ips'
|
3
|
+
require 'trenni/markup'
|
4
|
+
|
5
|
+
RSpec.describe Trenni::Markup do
|
6
|
+
let(:code_string) {'javascript:if (foo < bar) {alert("Hello World")}'}
|
7
|
+
let(:general_string) {"a" * code_string.size}
|
8
|
+
|
9
|
+
it "should be fast to parse large documents" do
|
10
|
+
Benchmark.ips do |x|
|
11
|
+
x.report("General String") do |times|
|
12
|
+
while (times -= 1) >= 0
|
13
|
+
Trenni::Markup.escape_string(general_string)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
x.report("Code String") do |times|
|
18
|
+
while (times -= 1) >= 0
|
19
|
+
Trenni::Markup.escape_string(code_string)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
x.compare!
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
data/spec/trenni/markup_spec.rb
CHANGED