trenni 3.7.1 → 3.12.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (104) hide show
  1. checksums.yaml +4 -4
  2. data/bake/trenni/entities.rb +57 -0
  3. data/bake/trenni/parsers.rb +66 -0
  4. data/ext/Rakefile +1 -0
  5. data/ext/tmp/x86_64-linux/lib/trenni/trenni.so +0 -0
  6. data/ext/tmp/x86_64-linux/trenni/2.4.0/Makefile +263 -0
  7. data/ext/tmp/x86_64-linux/trenni/2.4.0/escape.o +0 -0
  8. data/ext/tmp/x86_64-linux/trenni/2.4.0/markup.o +0 -0
  9. data/ext/tmp/x86_64-linux/trenni/2.4.0/mkmf.log +108 -0
  10. data/ext/tmp/x86_64-linux/trenni/2.4.0/tag.o +0 -0
  11. data/ext/tmp/x86_64-linux/trenni/2.4.0/template.o +0 -0
  12. data/ext/tmp/x86_64-linux/trenni/2.4.0/trenni.o +0 -0
  13. data/ext/tmp/x86_64-linux/trenni/2.4.0/trenni.so +0 -0
  14. data/ext/tmp/x86_64-linux/trenni/2.7.0/Makefile +266 -0
  15. data/ext/tmp/x86_64-linux/trenni/2.7.0/escape.o +0 -0
  16. data/ext/tmp/x86_64-linux/trenni/2.7.0/markup.o +0 -0
  17. data/ext/tmp/x86_64-linux/trenni/2.7.0/mkmf.log +113 -0
  18. data/ext/tmp/x86_64-linux/trenni/2.7.0/query.o +0 -0
  19. data/ext/tmp/x86_64-linux/trenni/2.7.0/tag.o +0 -0
  20. data/ext/tmp/x86_64-linux/trenni/2.7.0/template.o +0 -0
  21. data/ext/tmp/x86_64-linux/trenni/2.7.0/trenni.o +0 -0
  22. data/ext/tmp/x86_64-linux/trenni/2.7.0/trenni.so +0 -0
  23. data/ext/tmp/x86_64-linux/trenni/2.7.1/Makefile +266 -0
  24. data/ext/tmp/x86_64-linux/trenni/2.7.1/escape.o +0 -0
  25. data/ext/tmp/x86_64-linux/trenni/2.7.1/markup.o +0 -0
  26. data/ext/tmp/x86_64-linux/trenni/2.7.1/mkmf.log +113 -0
  27. data/ext/tmp/x86_64-linux/trenni/2.7.1/query.o +0 -0
  28. data/ext/tmp/x86_64-linux/trenni/2.7.1/tag.o +0 -0
  29. data/ext/tmp/x86_64-linux/trenni/2.7.1/template.o +0 -0
  30. data/ext/tmp/x86_64-linux/trenni/2.7.1/trenni.o +0 -0
  31. data/ext/tmp/x86_64-linux/trenni/2.7.1/trenni.so +0 -0
  32. data/ext/trenni/extconf.rb +1 -0
  33. data/ext/trenni/markup.c +85 -85
  34. data/ext/trenni/markup.rl +11 -11
  35. data/ext/trenni/query.c +619 -0
  36. data/ext/trenni/query.h +6 -0
  37. data/ext/trenni/query.rl +82 -0
  38. data/ext/trenni/tag.c +8 -6
  39. data/ext/trenni/template.c +57 -57
  40. data/ext/trenni/template.rl +4 -4
  41. data/ext/trenni/trenni.c +9 -1
  42. data/ext/trenni/trenni.h +8 -3
  43. data/lib/trenni.rb +4 -0
  44. data/lib/trenni/buffer.rb +2 -0
  45. data/lib/trenni/builder.rb +64 -12
  46. data/lib/trenni/entities.rb +2082 -2082
  47. data/lib/trenni/entities.trenni +1 -3
  48. data/lib/trenni/{parse_error.rb → error.rb} +6 -1
  49. data/lib/trenni/fallback/markup.rb +1623 -1575
  50. data/lib/trenni/fallback/markup.rl +3 -2
  51. data/lib/trenni/fallback/markup.rl.dot +278 -0
  52. data/lib/trenni/fallback/markup.rl.pdf +0 -0
  53. data/lib/trenni/fallback/query.rb +565 -0
  54. data/lib/trenni/fallback/query.rl +105 -0
  55. data/lib/trenni/fallback/query.rl.dot +54 -0
  56. data/lib/trenni/fallback/query.rl.pdf +0 -0
  57. data/lib/trenni/fallback/template.rb +756 -747
  58. data/lib/trenni/fallback/template.rl +1 -1
  59. data/lib/trenni/fallback/template.rl.dot +270 -0
  60. data/lib/trenni/fallback/template.rl.pdf +0 -0
  61. data/lib/trenni/markup.rb +2 -0
  62. data/lib/trenni/native.rb +3 -1
  63. data/lib/trenni/parse_delegate.rb +2 -0
  64. data/lib/trenni/parsers.rb +2 -0
  65. data/lib/trenni/query.rb +94 -0
  66. data/lib/trenni/reference.rb +125 -0
  67. data/lib/trenni/strings.rb +17 -4
  68. data/lib/trenni/tag.rb +2 -0
  69. data/lib/trenni/template.rb +25 -22
  70. data/lib/trenni/trenni.so +0 -0
  71. data/lib/trenni/uri.rb +3 -0
  72. data/lib/trenni/version.rb +3 -1
  73. data/parsers/trenni/query.rl +23 -0
  74. data/spec/spec_helper.rb +1 -0
  75. data/spec/trenni/builder_spec.rb +14 -1
  76. data/spec/trenni/corpus/large.rb +2 -0
  77. data/spec/trenni/markup_parser_spec.rb +1 -0
  78. data/spec/trenni/markup_performance_spec.rb +15 -2
  79. data/spec/trenni/markup_spec.rb +1 -0
  80. data/spec/trenni/parsers_performance_spec.rb +32 -0
  81. data/spec/trenni/query_spec.rb +51 -0
  82. data/spec/trenni/reference_spec.rb +87 -0
  83. data/spec/trenni/strings_spec.rb +1 -0
  84. data/spec/trenni/tag_spec.rb +2 -0
  85. data/spec/trenni/template_error_spec.rb +1 -0
  86. data/spec/trenni/template_performance_spec.rb +1 -0
  87. data/spec/trenni/template_spec.rb +1 -0
  88. data/spec/trenni/template_spec/builder.trenni +2 -2
  89. data/spec/trenni/uri_spec.rb +1 -0
  90. metadata +95 -62
  91. data/.gitignore +0 -19
  92. data/.rspec +0 -5
  93. data/.simplecov +0 -9
  94. data/.travis.yml +0 -23
  95. data/Gemfile +0 -20
  96. data/README.md +0 -312
  97. data/Rakefile +0 -19
  98. data/benchmark/call_vs_yield.rb +0 -51
  99. data/benchmark/interpolation_vs_concat.rb +0 -29
  100. data/benchmark/io_vs_string.rb +0 -90
  101. data/entities.json +0 -2233
  102. data/tasks/entities.rake +0 -33
  103. data/tasks/parsers.rake +0 -43
  104. data/trenni.gemspec +0 -33
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  # Copyright, 2012, by Samuel G. D. Williams. <http://www.codeotaku.com>
2
4
  #
3
5
  # Permission is hereby granted, free of charge, to any person obtaining a copy
@@ -28,7 +30,11 @@ module Trenni
28
30
  end
29
31
 
30
32
  def self.to_quoted_string(string)
31
- '"' + string.gsub('"', '\\"').gsub(/\r/, "\\r").gsub(/\n/, "\\n") + '"'
33
+ string = string.gsub('"', '\\"')
34
+ string.gsub!(/\r/, "\\r")
35
+ string.gsub!(/\n/, "\\n")
36
+
37
+ return "\"#{string}\""
32
38
  end
33
39
 
34
40
  # `value` must already be escaped.
@@ -41,11 +47,18 @@ module Trenni
41
47
  end
42
48
 
43
49
  def self.to_title(string)
44
- string.gsub(/(^|[ \-_])(.)/){" " + $2.upcase}.strip
50
+ string = string.gsub(/(^|[ \-_])(.)/){" " + $2.upcase}
51
+ string.strip!
52
+
53
+ return string
45
54
  end
46
-
55
+
47
56
  def self.to_snake(string)
48
- string.gsub("::", "").gsub(/([A-Z]+)/){"_" + $1.downcase}.sub(/^_+/, "")
57
+ string = string.gsub("::", "")
58
+ string.gsub!(/([A-Z]+)/){"_" + $1.downcase}
59
+ string.sub!(/^_+/, "")
60
+
61
+ return string
49
62
  end
50
63
  end
51
64
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  # Copyright, 2012, by Samuel G. D. Williams. <http://www.codeotaku.com>
2
4
  #
3
5
  # Permission is hereby granted, free of charge, to any person obtaining a copy
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  # Copyright, 2012, by Samuel G. D. Williams. <http://www.codeotaku.com>
2
4
  #
3
5
  # Permission is hereby granted, free of charge, to any person obtaining a copy
@@ -27,36 +29,27 @@ module Trenni
27
29
  BINDING = binding
28
30
 
29
31
  class Builder
30
- def >> block
31
- if block
32
- Template.buffer(block.binding) << self
33
- return nil
34
- else
35
- return self
36
- end
37
- end
38
-
39
- def capture(*args, &block)
40
- self.append Template.capture(*args, &block)
32
+ def capture(*arguments, &block)
33
+ Template.capture(*arguments, output: self, &block)
41
34
  end
42
35
  end
43
36
 
44
37
  class Template
45
38
  # Returns the output produced by calling the given block.
46
- def self.capture(*args, &block)
39
+ def self.capture(*arguments, output: nil, &block)
47
40
  scope = block.binding
48
- output_buffer = scope.local_variable_get(OUT)
41
+ previous_output = scope.local_variable_get(OUT)
49
42
 
50
- capture_buffer = String.new.force_encoding(output_buffer.encoding)
51
- scope.local_variable_set(OUT, capture_buffer)
43
+ output ||= previous_output.class.new(encoding: previous_output.encoding)
44
+ scope.local_variable_set(OUT, output)
52
45
 
53
46
  begin
54
- block.call(*args)
47
+ block.call(*arguments)
55
48
  ensure
56
- scope.local_variable_set(OUT, output_buffer)
49
+ scope.local_variable_set(OUT, previous_output)
57
50
  end
58
51
 
59
- return capture_buffer
52
+ return output
60
53
  end
61
54
 
62
55
  # Returns the buffer used for capturing output.
@@ -123,7 +116,7 @@ module Trenni
123
116
  end
124
117
 
125
118
  def to_proc(scope = @binding.dup)
126
- @compiled_proc ||= eval("proc{|#{OUT}|;#{code}}", scope, @buffer.path).freeze
119
+ @compiled_proc ||= eval("\# frozen_string_literal: true\nproc{|#{OUT}|;#{code}}", scope, @buffer.path, 0).freeze
127
120
  end
128
121
 
129
122
  protected
@@ -152,11 +145,21 @@ module Trenni
152
145
  class MarkupTemplate < Template
153
146
  class Assembler < Template::Assembler
154
147
  # Output a string interpolation.
155
- def expression(text)
156
- @code << "Trenni::Markup.append(#{OUT},(#{text}));"
148
+ def expression(code)
149
+ @code << "#{OUT}<<(#{code});"
150
+ end
151
+
152
+ # Output raw text to the template.
153
+ def text(text)
154
+ text = text.gsub("'", "\\\\'")
155
+ @code << "#{OUT}.raw('#{text}');"
157
156
  end
158
157
  end
159
158
 
159
+ def to_string(scope = Object.new, output = nil)
160
+ super.output
161
+ end
162
+
160
163
  protected
161
164
 
162
165
  # We need an assembler which builds specific `Markup.append` sequences.
@@ -166,7 +169,7 @@ module Trenni
166
169
 
167
170
  # The output of the markup template is encoded markup (e.g. with entities, tags, etc).
168
171
  def output_buffer
169
- MarkupString.new.force_encoding(code.encoding)
172
+ Builder.new(encoding: code.encoding)
170
173
  end
171
174
  end
172
175
  end
Binary file
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  # Copyright, 2017, by Samuel G. D. Williams. <http://www.codeotaku.com>
2
4
  #
3
5
  # Permission is hereby granted, free of charge, to any person obtaining a copy
@@ -19,6 +21,7 @@
19
21
  # THE SOFTWARE.
20
22
 
21
23
  module Trenni
24
+ # This class is superceeded by `Trenni::Reference`.
22
25
  class URI
23
26
  def initialize(path, query_string, fragment, parameters)
24
27
  @path = path
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  # Copyright, 2012, by Samuel G. D. Williams. <http://www.codeotaku.com>
2
4
  #
3
5
  # Permission is hereby granted, free of charge, to any person obtaining a copy
@@ -19,5 +21,5 @@
19
21
  # THE SOFTWARE.
20
22
 
21
23
  module Trenni
22
- VERSION = "3.7.1"
24
+ VERSION = "3.12.0"
23
25
  end
@@ -0,0 +1,23 @@
1
+ %%{
2
+ machine query;
3
+
4
+ # An application/x-www-form-urlencoded parser based on the definition by WhatWG.
5
+ # Based on https://url.spec.whatwg.org/#application/x-www-form-urlencoded
6
+ pchar = any - [&=\[\]%+];
7
+ echar = pchar | ('+' | '%' xdigit xdigit) >encoded;
8
+
9
+ integer = ([0-9]+) >integer_begin %integer_end;
10
+ string = (echar+ - integer) >string_begin %string_end;
11
+
12
+ value = (echar*) >value_begin %value_end;
13
+
14
+ index = string (
15
+ '[' (integer | string) ']'
16
+ )* ('[]' %append)?;
17
+
18
+ pair = (
19
+ index ('=' value)?
20
+ ) %pair;
21
+
22
+ main := ((pair '&')* pair)?;
23
+ }%%
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
 
2
3
  require 'covered/rspec'
3
4
  require 'trenni'
@@ -1,4 +1,5 @@
1
1
  #!/usr/bin/env rspec
2
+ # frozen_string_literal: true
2
3
 
3
4
  # Copyright, 2012, by Samuel G. D. Williams. <http://www.codeotaku.com>
4
5
  #
@@ -49,11 +50,23 @@ module Trenni::BuilderSpec
49
50
  result = Trenni::Builder.fragment(builder) do |builder|
50
51
  end
51
52
 
52
- expect(result).to_not be_nil
53
+ expect(result).to be_nil
53
54
  end
54
55
  end
55
56
 
56
57
  describe Trenni::Builder do
58
+ describe '#<<' do
59
+ it 'can append text' do
60
+ subject << 'text'
61
+ expect(subject.output).to be == "text"
62
+ end
63
+
64
+ it "doesn't append nil" do
65
+ subject << nil
66
+ expect(subject.output).to be == ""
67
+ end
68
+ end
69
+
57
70
  it 'should be able to append nil' do
58
71
  expect{subject.append(nil)}.to_not raise_error
59
72
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  [
2
4
  [:instruction, "<?xml version=\"1.0\" encoding=\"UTF-8\"?>"],
3
5
  [:text, "\n"],
@@ -1,4 +1,5 @@
1
1
  #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
2
3
 
3
4
  # Copyright, 2012, by Samuel G. D. Williams. <http://www.codeotaku.com>
4
5
  #
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
 
2
3
  require 'benchmark/ips'
3
4
  require 'trenni/markup'
@@ -8,13 +9,25 @@ RSpec.describe Trenni::Markup do
8
9
 
9
10
  it "should be fast to parse large documents" do
10
11
  Benchmark.ips do |x|
11
- x.report("General String") do |times|
12
+ x.report("CGI.escapeHTML(general_string)") do |times|
13
+ while (times -= 1) >= 0
14
+ CGI.escapeHTML(general_string)
15
+ end
16
+ end
17
+
18
+ x.report("CGI.escapeHTML(code_string)") do |times|
19
+ while (times -= 1) >= 0
20
+ CGI.escapeHTML(code_string)
21
+ end
22
+ end
23
+
24
+ x.report("Trenni::Markup.escape_string(general_string)") do |times|
12
25
  while (times -= 1) >= 0
13
26
  Trenni::Markup.escape_string(general_string)
14
27
  end
15
28
  end
16
29
 
17
- x.report("Code String") do |times|
30
+ x.report("Trenni::Markup.escape_string(code_string)") do |times|
18
31
  while (times -= 1) >= 0
19
32
  Trenni::Markup.escape_string(code_string)
20
33
  end
@@ -1,4 +1,5 @@
1
1
  #!/usr/bin/env rspec
2
+ # frozen_string_literal: true
2
3
 
3
4
  # Copyright, 2012, by Samuel G. D. Williams. <http://www.codeotaku.com>
4
5
  #
@@ -1,8 +1,12 @@
1
+ # frozen_string_literal: true
1
2
 
2
3
  require 'benchmark/ips'
3
4
  require 'trenni/parsers'
4
5
  require 'trenni/entities'
5
6
 
7
+ require 'trenni/query'
8
+ require 'rack/utils'
9
+
6
10
  require 'nokogiri'
7
11
 
8
12
  RSpec.describe Trenni::Parsers do
@@ -70,4 +74,32 @@ RSpec.describe Trenni::Parsers do
70
74
  end
71
75
  end
72
76
  end
77
+
78
+ describe '#parse_query' do
79
+ let(:string) {"foo=hi%20there&bar[blah]=123&bar[quux][0]=1&bar[quux][1]=2&bar[quux][2]=3"}
80
+
81
+ it "should be fast to parse large query strings" do
82
+ # query = Trenni::Query.new
83
+ # query.parse(Trenni::Buffer.new string)
84
+ # pp query
85
+ #
86
+ # pp Rack::Utils.parse_nested_query(string)
87
+
88
+ Benchmark.ips do |x|
89
+ x.report("Large (Trenni)") do |times|
90
+ while (times -= 1) >= 0
91
+ Trenni::Query.new.parse(Trenni::Buffer.new string)
92
+ end
93
+ end
94
+
95
+ x.report("Large (Rack)") do |times|
96
+ while (times -= 1) >= 0
97
+ Rack::Utils.parse_nested_query(string)
98
+ end
99
+ end
100
+
101
+ x.compare!
102
+ end
103
+ end
104
+ end
73
105
  end
@@ -0,0 +1,51 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Copyright, 2020, by Samuel G. D. Williams. <http://www.codeotaku.com>
4
+ #
5
+ # Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ # of this software and associated documentation files (the "Software"), to deal
7
+ # in the Software without restriction, including without limitation the rights
8
+ # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ # copies of the Software, and to permit persons to whom the Software is
10
+ # furnished to do so, subject to the following conditions:
11
+ #
12
+ # The above copyright notice and this permission notice shall be included in
13
+ # all copies or substantial portions of the Software.
14
+ #
15
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ # THE SOFTWARE.
22
+
23
+ require 'trenni/query'
24
+
25
+ RSpec.describe Trenni::Query do
26
+ def parse(string)
27
+ subject.parse(Trenni::Buffer.new(string))
28
+
29
+ return subject
30
+ end
31
+
32
+ it "can parse query string with integer key" do
33
+ expect(parse "q[0]=0").to be == {q: {0 => "0"}}
34
+ end
35
+
36
+ it "can parse query string with mixed integer/string key" do
37
+ expect(parse "q[2d]=3d").to be == {q: {:'2d' => "3d"}}
38
+ end
39
+
40
+ it "can parse query string appending items to array" do
41
+ expect(parse "q[]=a&q[]=b").to be == {q: ["a", "b"]}
42
+ end
43
+
44
+ it "can decode encoded keys" do
45
+ expect(parse "hello+world=true").to be == {:"hello world" => "true"}
46
+ end
47
+
48
+ it "can decode encoded values" do
49
+ expect(parse "message=hello+world").to be == {message: "hello world"}
50
+ end
51
+ end
@@ -0,0 +1,87 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ # Copyright, 2012, by Samuel G. D. Williams. <http://www.codeotaku.com>
5
+ #
6
+ # Permission is hereby granted, free of charge, to any person obtaining a copy
7
+ # of this software and associated documentation files (the "Software"), to deal
8
+ # in the Software without restriction, including without limitation the rights
9
+ # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10
+ # copies of the Software, and to permit persons to whom the Software is
11
+ # furnished to do so, subject to the following conditions:
12
+ #
13
+ # The above copyright notice and this permission notice shall be included in
14
+ # all copies or substantial portions of the Software.
15
+ #
16
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18
+ # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19
+ # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20
+ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21
+ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22
+ # THE SOFTWARE.
23
+
24
+ require 'trenni/reference'
25
+
26
+ RSpec.describe Trenni::Reference do
27
+ describe Trenni::Reference('path with spaces/image.jpg') do
28
+ it "encodes whitespace" do
29
+ expect(subject.to_s).to be == "path%20with%20spaces/image.jpg"
30
+ end
31
+ end
32
+
33
+ describe Trenni::Reference('path', array: [1, 2, 3]) do
34
+ it "encodes array" do
35
+ expect(subject.to_s).to be == "path?array[]=1&array[]=2&array[]=3"
36
+ end
37
+ end
38
+
39
+ describe Trenni::Reference('path_with_underscores/image.jpg') do
40
+ it "doesn't touch underscores" do
41
+ expect(subject.to_s).to be == "path_with_underscores/image.jpg"
42
+ end
43
+ end
44
+
45
+ describe Trenni::Reference('index', :'my name' => 'Bob Dole') do
46
+ it "encodes query" do
47
+ expect(subject.to_s).to be == "index?my%20name=Bob%20Dole"
48
+ end
49
+ end
50
+
51
+ describe Trenni::Reference('index#All Your Base') do
52
+ it "encodes fragment" do
53
+ expect(subject.to_s).to be == "index\#All%20Your%20Base"
54
+ end
55
+ end
56
+
57
+ describe Trenni::Reference('I/❤️/UNICODE', face: '😀') do
58
+ it "encodes unicode" do
59
+ expect(subject.to_s).to be == "I/%E2%9D%A4%EF%B8%8F/UNICODE?face=%F0%9F%98%80"
60
+ end
61
+ end
62
+
63
+ it "can be an attribute" do
64
+ tag = Trenni::Tag.closed('img', src: Trenni::Reference('image.jpg', x: 10))
65
+
66
+ expect(tag.to_s).to be == '<img src="image.jpg?x=10"/>'
67
+ end
68
+
69
+ describe Trenni::Reference("foo?bar=10&baz=20", yes: 'no') do
70
+ it "can use existing query parameters" do
71
+ expect(subject.to_s).to be == "foo?bar=10&baz=20&yes=no"
72
+ end
73
+ end
74
+
75
+ describe Trenni::Reference("foo?yes=yes", yes: 'no') do
76
+ it "overrides existing parameters" do
77
+ expect(subject.to_s).to be == "foo?yes=no"
78
+ end
79
+ end
80
+
81
+ describe Trenni::Reference('foo#frag') do
82
+ it "can use existing fragment" do
83
+ expect(subject.fragment).to be == "frag"
84
+ expect(subject.to_s).to be == 'foo#frag'
85
+ end
86
+ end
87
+ end