stella 0.5.1 → 0.5.3

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.
Files changed (100) hide show
  1. data/README.txt +84 -24
  2. data/Rakefile +6 -1
  3. data/lib/stella.rb +5 -4
  4. data/lib/stella/adapter/ab.rb +31 -4
  5. data/lib/stella/adapter/base.rb +15 -1
  6. data/lib/stella/adapter/httperf.rb +35 -4
  7. data/lib/stella/adapter/siege.rb +51 -29
  8. data/lib/stella/cli.rb +45 -22
  9. data/lib/stella/cli/agents.rb +73 -0
  10. data/lib/stella/cli/language.rb +2 -0
  11. data/lib/stella/cli/localtest.rb +5 -0
  12. data/lib/stella/command/base.rb +1 -1
  13. data/lib/stella/command/localtest.rb +84 -68
  14. data/lib/stella/test/{summarybase.rb → base.rb} +3 -7
  15. data/lib/stella/test/definition.rb +10 -5
  16. data/lib/stella/test/{runsummary.rb → run/summary.rb} +4 -6
  17. data/lib/stella/test/{testsummary.rb → summary.rb} +27 -29
  18. data/lib/utils/escape.rb +302 -0
  19. data/lib/utils/mathutil.rb +36 -34
  20. data/support/text/en.yaml +7 -1
  21. metadata +8 -102
  22. data/doc/classes/Crypto.html +0 -248
  23. data/doc/classes/Crypto/Key.html +0 -308
  24. data/doc/classes/Enumerable.html +0 -309
  25. data/doc/classes/FileUtil.html +0 -310
  26. data/doc/classes/HTTPUtil.html +0 -530
  27. data/doc/classes/MathUtil.html +0 -210
  28. data/doc/classes/Stella.html +0 -238
  29. data/doc/classes/Stella/Adapter.html +0 -127
  30. data/doc/classes/Stella/Adapter/ApacheBench.html +0 -1076
  31. data/doc/classes/Stella/Adapter/Base.html +0 -432
  32. data/doc/classes/Stella/Adapter/CommandNotReady.html +0 -146
  33. data/doc/classes/Stella/Adapter/Httperf.html +0 -949
  34. data/doc/classes/Stella/Adapter/Siege.html +0 -1011
  35. data/doc/classes/Stella/CLI.html +0 -914
  36. data/doc/classes/Stella/CLI/Base.html +0 -186
  37. data/doc/classes/Stella/CLI/Language.html +0 -149
  38. data/doc/classes/Stella/CLI/LocalTest.html +0 -268
  39. data/doc/classes/Stella/Command.html +0 -111
  40. data/doc/classes/Stella/Command/Base.html +0 -335
  41. data/doc/classes/Stella/Config.html +0 -292
  42. data/doc/classes/Stella/InvalidArgument.html +0 -242
  43. data/doc/classes/Stella/LocalTest.html +0 -450
  44. data/doc/classes/Stella/Logger.html +0 -548
  45. data/doc/classes/Stella/Response.html +0 -846
  46. data/doc/classes/Stella/Storable.html +0 -928
  47. data/doc/classes/Stella/Test.html +0 -142
  48. data/doc/classes/Stella/Test/DaySummary.html +0 -249
  49. data/doc/classes/Stella/Test/Definition.html +0 -294
  50. data/doc/classes/Stella/Test/Definition/Rampup.html +0 -215
  51. data/doc/classes/Stella/Test/RunSummary.html +0 -315
  52. data/doc/classes/Stella/Test/SummaryBase.html +0 -286
  53. data/doc/classes/Stella/Test/TestSummary.html +0 -200
  54. data/doc/classes/Stella/Text.html +0 -581
  55. data/doc/classes/Stella/Text/Resource.html +0 -289
  56. data/doc/classes/Stella/UnavailableAdapter.html +0 -242
  57. data/doc/classes/Stella/UnknownValue.html +0 -242
  58. data/doc/classes/Stella/UnsupportedLanguage.html +0 -115
  59. data/doc/classes/Stella/Util.html +0 -348
  60. data/doc/classes/TextGraph.html +0 -460
  61. data/doc/classes/TimerUtil.html +0 -431
  62. data/doc/created.rid +0 -1
  63. data/doc/files/LICENSE_txt.html +0 -129
  64. data/doc/files/README_txt.html +0 -209
  65. data/doc/files/lib/stella/adapter/ab_rb.html +0 -101
  66. data/doc/files/lib/stella/adapter/base_rb.html +0 -101
  67. data/doc/files/lib/stella/adapter/httperf_rb.html +0 -101
  68. data/doc/files/lib/stella/adapter/siege_rb.html +0 -101
  69. data/doc/files/lib/stella/cli/base_rb.html +0 -101
  70. data/doc/files/lib/stella/cli/language_rb.html +0 -101
  71. data/doc/files/lib/stella/cli/localtest_rb.html +0 -101
  72. data/doc/files/lib/stella/cli_rb.html +0 -112
  73. data/doc/files/lib/stella/command/base_rb.html +0 -101
  74. data/doc/files/lib/stella/command/localtest_rb.html +0 -101
  75. data/doc/files/lib/stella/logger_rb.html +0 -101
  76. data/doc/files/lib/stella/response_rb.html +0 -101
  77. data/doc/files/lib/stella/storable_rb.html +0 -109
  78. data/doc/files/lib/stella/support_rb.html +0 -101
  79. data/doc/files/lib/stella/test/daysummary_rb.html +0 -101
  80. data/doc/files/lib/stella/test/definition_rb.html +0 -101
  81. data/doc/files/lib/stella/test/runsummary_rb.html +0 -101
  82. data/doc/files/lib/stella/test/summarybase_rb.html +0 -101
  83. data/doc/files/lib/stella/test/testsummary_rb.html +0 -108
  84. data/doc/files/lib/stella/text/resource_rb.html +0 -108
  85. data/doc/files/lib/stella/text_rb.html +0 -108
  86. data/doc/files/lib/stella_rb.html +0 -128
  87. data/doc/files/lib/utils/crypto-key_rb.html +0 -116
  88. data/doc/files/lib/utils/fileutil_rb.html +0 -108
  89. data/doc/files/lib/utils/httputil_rb.html +0 -110
  90. data/doc/files/lib/utils/mathutil_rb.html +0 -101
  91. data/doc/files/lib/utils/textgraph_rb.html +0 -138
  92. data/doc/files/lib/utils/timerutil_rb.html +0 -108
  93. data/doc/fr_class_index.html +0 -66
  94. data/doc/fr_file_index.html +0 -62
  95. data/doc/fr_method_index.html +0 -309
  96. data/doc/index.html +0 -24
  97. data/doc/rdoc-style.css +0 -208
  98. data/lib/stella/test/daysummary.rb +0 -93
  99. data/spec/base.rb +0 -26
  100. data/spec/shell_spec.rb +0 -12
@@ -1,23 +1,19 @@
1
1
 
2
2
  module Stella::Test
3
3
 
4
- module SummaryBase
4
+ module Base
5
5
 
6
6
  attr_reader :message
7
7
  attr_reader :elapsed_time_avg, :transaction_rate_avg, :vusers_avg, :response_time_avg
8
8
  attr_reader :elapsed_time_sdev, :transaction_rate_sdev, :vusers_sdev, :response_time_sdev
9
9
  attr_accessor :transactions_total, :headers_transferred_total, :data_transferred_total
10
- attr_accessor :successful_total, :failed_total, :elapsed_time_total
10
+ attr_accessor :successful_total, :failed_total, :elapsed_time_total, :throughput_avg, :throughput_sdev
11
11
 
12
12
  def availability
13
13
  return 0 if @successful_total == 0
14
14
  (@transactions_total / @successful_total).to_f * 100
15
15
  end
16
16
 
17
- def throughput
18
- return 0 if @elapsed_time_total == 0
19
- (@data_transferred_total / @elapsed_time_total).to_f
20
- end
21
17
 
22
18
  # Defines the fields the are output by to_hash and to_csv.
23
19
  # For to_csv, this also determines the field order
@@ -30,7 +26,7 @@ module Stella::Test
30
26
  :transactions_total, :successful_total, :failed_total,
31
27
  :data_transferred_total, :headers_transferred_total,
32
28
 
33
- :elapsed_time_total, :availability, :throughput
29
+ :elapsed_time_total, :availability, :throughput_avg, :throughput_sdev
34
30
  ]
35
31
  end
36
32
 
@@ -2,13 +2,18 @@
2
2
 
3
3
  module Stella
4
4
 
5
- # Stella::Test::Definition
6
- #
7
- # This class defines the properties of load test. These are "generic" properties
8
- # in that they don't relate to a specific tool.
5
+
9
6
  module Test
7
+
8
+ # Stella::Test::Definition
9
+ #
10
+ # This class defines the properties of load test. These are "generic" properties
11
+ # in that they don't relate to a specific tool.
10
12
  class Definition
11
13
 
14
+ # Stella::Test::Definition::Rampup
15
+ #
16
+ # This class holds the values for a rampup: interval and ceiling.
12
17
  class Rampup
13
18
  attr_accessor :interval
14
19
  attr_accessor :ceiling
@@ -47,7 +52,7 @@ module Stella
47
52
  attr_accessor :message
48
53
 
49
54
  def initialize
50
- @repetitions = 1
55
+ @repetitions = 3
51
56
  end
52
57
 
53
58
  def repetitions=(v)
@@ -1,12 +1,9 @@
1
1
 
2
2
 
3
3
 
4
- module Stella
5
- module Test
4
+ module Stella::Test::Run
6
5
 
7
-
8
-
9
- class RunSummary < Stella::Storable
6
+ class Summary < Stella::Storable
10
7
 
11
8
  attr_accessor :note
12
9
  attr_accessor :tool, :version
@@ -32,6 +29,8 @@ module Test
32
29
  (@transactions / @successful).to_f * 100
33
30
  end
34
31
 
32
+ # We calculate the throughput because Apache Bench does not provide this
33
+ # value in the output.
35
34
  def throughput
36
35
  return 0 if !@elapsed_time || @elapsed_time == 0
37
36
  (@data_transferred / @elapsed_time).to_f
@@ -48,5 +47,4 @@ module Test
48
47
  end
49
48
 
50
49
 
51
- end
52
50
  end
@@ -1,10 +1,11 @@
1
1
 
2
- require 'stella/test/summarybase'
2
+ require 'stella/test/base'
3
3
 
4
4
  module Stella::Test
5
5
 
6
- class TestSummary < Stella::Storable
7
- include SummaryBase
6
+ # Stella::Test::Summary
7
+ class Summary < Stella::Storable
8
+ include Base
8
9
 
9
10
  attr_reader :runs
10
11
 
@@ -15,34 +16,32 @@ module Stella::Test
15
16
 
16
17
  # Add a TestRun object to the list
17
18
  def add_run(run)
18
- raise "I got a #{run.class} but I wanted a RunSummary" unless run.is_a?(RunSummary)
19
+ raise "I got a #{run.class} but I wanted a Run::Summary" unless run.is_a?(Run::Summary)
19
20
  @runs << run
20
21
  calculate
21
22
  end
22
23
 
23
24
  private
24
25
  def calculate
26
+ # We simply keep a running tally of these
25
27
  @transactions_total = 0
26
28
  @headers_transferred_total = 0
27
29
  @data_transferred_total = 0
28
-
29
- @elapsed_time_avg = 0
30
- @response_time_avg = 0
31
- @transaction_rate_avg = 0
32
- @vusers_avg = 0
33
-
30
+ @elapsed_time_total = 0
34
31
  @successful_total = 0
35
32
  @failed_total = 0
36
33
 
34
+ # We keep a list of the values for averaging and std dev
37
35
  elapsed_times = []
38
36
  transaction_rates = []
39
37
  vusers_list = []
40
38
  response_times = []
41
- @elapsed_time_total = 0
42
- response_time = 0
43
- transaction_rate = 0
44
- vusers = 0
39
+ response_time = []
40
+ transaction_rate = []
41
+ throughput = []
42
+ vusers = []
45
43
 
44
+ # Each run is the summary of a single run (i.e. run01/SUMMARY.csv)
46
45
  runs.each do |run|
47
46
  # These are totaled
48
47
  @transactions_total += run.transactions
@@ -50,33 +49,32 @@ module Stella::Test
50
49
  @data_transferred_total += run.data_transferred
51
50
  @successful_total += run.successful
52
51
  @failed_total += run.failed
53
-
54
52
  @elapsed_time_total += run.elapsed_time
55
53
 
56
- response_time += run.response_time
57
-
58
- transaction_rate += run.transaction_rate
59
- vusers += run.vusers
60
-
61
54
  # These are used for standard deviation
62
55
  elapsed_times << run.elapsed_time
63
56
  transaction_rates << run.transaction_rate
64
57
  vusers_list << run.vusers
65
58
  response_times << run.response_time
59
+ throughput << run.throughput
60
+ response_time << run.response_time
61
+ transaction_rate << run.transaction_rate
62
+ vusers << run.vusers
66
63
  end
67
64
 
68
-
69
65
  # Calculate Averages
70
- @elapsed_time_avg = @elapsed_time_total / runs.size
71
- @transaction_rate_avg = transaction_rate / runs.size
72
- @vusers_avg = (vusers / runs.size)
73
- @response_time_avg = response_time / runs.size
66
+ @elapsed_time_avg = elapsed_times.average
67
+ @throughput_avg = throughput.average
68
+ @response_time_avg = response_time.average
69
+ @transaction_rate_avg = transaction_rate.average
70
+ @vusers_avg = vusers.average
74
71
 
75
72
  # Calculate Standard Deviations
76
- @elapsed_time_sdev = MathUtil.standard_deviation(elapsed_times)
77
- @transaction_rate_sdev = MathUtil.standard_deviation(transaction_rates)
78
- @vusers_sdev = MathUtil.standard_deviation(vusers_list)
79
- @response_time_sdev = MathUtil.standard_deviation(response_times)
73
+ @elapsed_time_sdev = elapsed_times.standard_deviation
74
+ @throughput_sdev= throughput.standard_deviation
75
+ @transaction_rate_sdev = transaction_rates.standard_deviation
76
+ @vusers_sdev = vusers_list.standard_deviation
77
+ @response_time_sdev = response_times.standard_deviation
80
78
 
81
79
  end
82
80
  end
@@ -0,0 +1,302 @@
1
+ # escape.rb - escape/unescape library for several formats
2
+ #
3
+ # Copyright (C) 2006,2007 Tanaka Akira <akr@fsij.org>
4
+ #
5
+ # Redistribution and use in source and binary forms, with or without
6
+ # modification, are permitted provided that the following conditions are met:
7
+ #
8
+ # 1. Redistributions of source code must retain the above copyright notice, this
9
+ # list of conditions and the following disclaimer.
10
+ # 2. Redistributions in binary form must reproduce the above copyright notice,
11
+ # this list of conditions and the following disclaimer in the documentation
12
+ # and/or other materials provided with the distribution.
13
+ # 3. The name of the author may not be used to endorse or promote products
14
+ # derived from this software without specific prior written permission.
15
+ #
16
+ # THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
17
+ # WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
18
+ # MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
19
+ # EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
20
+ # EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT
21
+ # OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
22
+ # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
23
+ # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
24
+ # IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
25
+ # OF SUCH DAMAGE.
26
+
27
+ # Escape module provides several escape functions.
28
+ # * URI
29
+ # * HTML
30
+ # * shell command
31
+ module EscapeUtil
32
+ module_function
33
+
34
+ class StringWrapper
35
+ class << self
36
+ alias new_no_dup new
37
+ def new(str)
38
+ new_no_dup(str.dup)
39
+ end
40
+ end
41
+
42
+ def initialize(str)
43
+ @str = str
44
+ end
45
+
46
+ def to_s
47
+ @str.dup
48
+ end
49
+
50
+ def inspect
51
+ "\#<#{self.class}: #{@str}>"
52
+ end
53
+
54
+ def ==(other)
55
+ other.class == self.class && @str == other.instance_variable_get(:@str)
56
+ end
57
+ alias eql? ==
58
+
59
+ def hash
60
+ @str.hash
61
+ end
62
+ end
63
+
64
+ class ShellEscaped < StringWrapper
65
+ end
66
+
67
+ # Escape.shell_command composes
68
+ # a sequence of words to
69
+ # a single shell command line.
70
+ # All shell meta characters are quoted and
71
+ # the words are concatenated with interleaving space.
72
+ # It returns an instance of ShellEscaped.
73
+ #
74
+ # Escape.shell_command(["ls", "/"]) #=> #<Escape::ShellEscaped: ls />
75
+ # Escape.shell_command(["echo", "*"]) #=> #<Escape::ShellEscaped: echo '*'>
76
+ #
77
+ # Note that system(*command) and
78
+ # system(Escape.shell_command(command)) is roughly same.
79
+ # There are two exception as follows.
80
+ # * The first is that the later may invokes /bin/sh.
81
+ # * The second is an interpretation of an array with only one element:
82
+ # the element is parsed by the shell with the former but
83
+ # it is recognized as single word with the later.
84
+ # For example, system(*["echo foo"]) invokes echo command with an argument "foo".
85
+ # But system(Escape.shell_command(["echo foo"])) invokes "echo foo" command without arguments (and it probably fails).
86
+ def shell_command(command)
87
+ s = command.map {|word| shell_single_word(word) }.join(' ')
88
+ ShellEscaped.new_no_dup(s)
89
+ end
90
+
91
+ # Escape.shell_single_word quotes shell meta characters.
92
+ # It returns an instance of ShellEscaped.
93
+ #
94
+ # The result string is always single shell word, even if
95
+ # the argument is "".
96
+ # Escape.shell_single_word("") returns #<Escape::ShellEscaped: ''>.
97
+ #
98
+ # Escape.shell_single_word("") #=> #<Escape::ShellEscaped: ''>
99
+ # Escape.shell_single_word("foo") #=> #<Escape::ShellEscaped: foo>
100
+ # Escape.shell_single_word("*") #=> #<Escape::ShellEscaped: '*'>
101
+ def shell_single_word(str)
102
+ if str && str.empty?
103
+ ShellEscaped.new_no_dup("''")
104
+ elsif %r{\A[0-9A-Za-z+,./:=@_-]+\z} =~ str
105
+ ShellEscaped.new(str)
106
+ else
107
+ result = ''
108
+ str.scan(/('+)|[^']+/) {
109
+ if $1
110
+ result << %q{\'} * $1.length
111
+ else
112
+ result << "'#{$&}'"
113
+ end
114
+ }
115
+ ShellEscaped.new_no_dup(result)
116
+ end
117
+ end
118
+
119
+ class PercentEncoded < StringWrapper
120
+ end
121
+
122
+ # Escape.uri_segment escapes URI segment using percent-encoding.
123
+ # It returns an instance of PercentEncoded.
124
+ #
125
+ # Escape.uri_segment("a/b") #=> #<Escape::PercentEncoded: a%2Fb>
126
+ #
127
+ # The segment is "/"-splitted element after authority before query in URI, as follows.
128
+ #
129
+ # scheme://authority/segment1/segment2/.../segmentN?query#fragment
130
+ #
131
+ # See RFC 3986 for details of URI.
132
+ def uri_segment(str)
133
+ # pchar - pct-encoded = unreserved / sub-delims / ":" / "@"
134
+ # unreserved = ALPHA / DIGIT / "-" / "." / "_" / "~"
135
+ # sub-delims = "!" / "$" / "&" / "'" / "(" / ")" / "*" / "+" / "," / ";" / "="
136
+ s = str.gsub(%r{[^A-Za-z0-9\-._~!$&'()*+,;=:@]}n) {
137
+ '%' + $&.unpack("H2")[0].upcase
138
+ }
139
+ PercentEncoded.new_no_dup(s)
140
+ end
141
+
142
+ # Escape.uri_path escapes URI path using percent-encoding.
143
+ # The given path should be a sequence of (non-escaped) segments separated by "/".
144
+ # The segments cannot contains "/".
145
+ # It returns an instance of PercentEncoded.
146
+ #
147
+ # Escape.uri_path("a/b/c") #=> #<Escape::PercentEncoded: a/b/c>
148
+ # Escape.uri_path("a?b/c?d/e?f") #=> #<Escape::PercentEncoded: a%3Fb/c%3Fd/e%3Ff>
149
+ #
150
+ # The path is the part after authority before query in URI, as follows.
151
+ #
152
+ # scheme://authority/path#fragment
153
+ #
154
+ # See RFC 3986 for details of URI.
155
+ #
156
+ # Note that this function is not appropriate to convert OS path to URI.
157
+ def uri_path(str)
158
+ s = str.gsub(%r{[^/]+}n) { uri_segment($&) }
159
+ PercentEncoded.new_no_dup(s)
160
+ end
161
+
162
+ # :stopdoc:
163
+ def html_form_fast(pairs, sep='&')
164
+ s = pairs.map {|k, v|
165
+ # query-chars - pct-encoded - x-www-form-urlencoded-delimiters =
166
+ # unreserved / "!" / "$" / "'" / "(" / ")" / "*" / "," / ":" / "@" / "/" / "?"
167
+ # query-char - pct-encoded = unreserved / sub-delims / ":" / "@" / "/" / "?"
168
+ # query-char = pchar / "/" / "?" = unreserved / pct-encoded / sub-delims / ":" / "@" / "/" / "?"
169
+ # unreserved = ALPHA / DIGIT / "-" / "." / "_" / "~"
170
+ # sub-delims = "!" / "$" / "&" / "'" / "(" / ")" / "*" / "+" / "," / ";" / "="
171
+ # x-www-form-urlencoded-delimiters = "&" / "+" / ";" / "="
172
+ k = k.gsub(%r{[^0-9A-Za-z\-\._~:/?@!\$'()*,]}n) {
173
+ '%' + $&.unpack("H2")[0].upcase
174
+ }
175
+ v = v.gsub(%r{[^0-9A-Za-z\-\._~:/?@!\$'()*,]}n) {
176
+ '%' + $&.unpack("H2")[0].upcase
177
+ }
178
+ "#{k}=#{v}"
179
+ }.join(sep)
180
+ PercentEncoded.new_no_dup(s)
181
+ end
182
+ # :startdoc:
183
+
184
+ # Escape.html_form composes HTML form key-value pairs as a x-www-form-urlencoded encoded string.
185
+ # It returns an instance of PercentEncoded.
186
+ #
187
+ # Escape.html_form takes an array of pair of strings or
188
+ # an hash from string to string.
189
+ #
190
+ # Escape.html_form([["a","b"], ["c","d"]]) #=> #<Escape::PercentEncoded: a=b&c=d>
191
+ # Escape.html_form({"a"=>"b", "c"=>"d"}) #=> #<Escape::PercentEncoded: a=b&c=d>
192
+ #
193
+ # In the array form, it is possible to use same key more than once.
194
+ # (It is required for a HTML form which contains
195
+ # checkboxes and select element with multiple attribute.)
196
+ #
197
+ # Escape.html_form([["k","1"], ["k","2"]]) #=> #<Escape::PercentEncoded: k=1&k=2>
198
+ #
199
+ # If the strings contains characters which must be escaped in x-www-form-urlencoded,
200
+ # they are escaped using %-encoding.
201
+ #
202
+ # Escape.html_form([["k=","&;="]]) #=> #<Escape::PercentEncoded: k%3D=%26%3B%3D>
203
+ #
204
+ # The separator can be specified by the optional second argument.
205
+ #
206
+ # Escape.html_form([["a","b"], ["c","d"]], ";") #=> #<Escape::PercentEncoded: a=b;c=d>
207
+ #
208
+ # See HTML 4.01 for details.
209
+ def html_form(pairs, sep='&')
210
+ r = ''
211
+ first = true
212
+ pairs.each {|k, v|
213
+ # query-chars - pct-encoded - x-www-form-urlencoded-delimiters =
214
+ # unreserved / "!" / "$" / "'" / "(" / ")" / "*" / "," / ":" / "@" / "/" / "?"
215
+ # query-char - pct-encoded = unreserved / sub-delims / ":" / "@" / "/" / "?"
216
+ # query-char = pchar / "/" / "?" = unreserved / pct-encoded / sub-delims / ":" / "@" / "/" / "?"
217
+ # unreserved = ALPHA / DIGIT / "-" / "." / "_" / "~"
218
+ # sub-delims = "!" / "$" / "&" / "'" / "(" / ")" / "*" / "+" / "," / ";" / "="
219
+ # x-www-form-urlencoded-delimiters = "&" / "+" / ";" / "="
220
+ r << sep if !first
221
+ first = false
222
+ k.each_byte {|byte|
223
+ ch = byte.chr
224
+ if %r{[^0-9A-Za-z\-\._~:/?@!\$'()*,]}n =~ ch
225
+ r << "%" << ch.unpack("H2")[0].upcase
226
+ else
227
+ r << ch
228
+ end
229
+ }
230
+ r << '='
231
+ v.each_byte {|byte|
232
+ ch = byte.chr
233
+ if %r{[^0-9A-Za-z\-\._~:/?@!\$'()*,]}n =~ ch
234
+ r << "%" << ch.unpack("H2")[0].upcase
235
+ else
236
+ r << ch
237
+ end
238
+ }
239
+ }
240
+ PercentEncoded.new_no_dup(r)
241
+ end
242
+
243
+ class HTMLEscaped < StringWrapper
244
+ end
245
+
246
+ # :stopdoc:
247
+ HTML_TEXT_ESCAPE_HASH = {
248
+ '&' => '&amp;',
249
+ '<' => '&lt;',
250
+ '>' => '&gt;',
251
+ }
252
+ # :startdoc:
253
+
254
+ # Escape.html_text escapes a string appropriate for HTML text using character references.
255
+ # It returns an instance of HTMLEscaped.
256
+ #
257
+ # It escapes 3 characters:
258
+ # * '&' to '&amp;'
259
+ # * '<' to '&lt;'
260
+ # * '>' to '&gt;'
261
+ #
262
+ # Escape.html_text("abc") #=> #<Escape::HTMLEscaped: abc>
263
+ # Escape.html_text("a & b < c > d") #=> #<Escape::HTMLEscaped: a &amp; b &lt; c &gt; d>
264
+ #
265
+ # This function is not appropriate for escaping HTML element attribute
266
+ # because quotes are not escaped.
267
+ def html_text(str)
268
+ s = str.gsub(/[&<>]/) {|ch| HTML_TEXT_ESCAPE_HASH[ch] }
269
+ HTMLEscaped.new_no_dup(s)
270
+ end
271
+
272
+ # :stopdoc:
273
+ HTML_ATTR_ESCAPE_HASH = {
274
+ '&' => '&amp;',
275
+ '<' => '&lt;',
276
+ '>' => '&gt;',
277
+ '"' => '&quot;',
278
+ }
279
+ # :startdoc:
280
+
281
+ class HTMLAttrValue < StringWrapper
282
+ end
283
+
284
+ # Escape.html_attr_value encodes a string as a double-quoted HTML attribute using character references.
285
+ # It returns an instance of HTMLAttrValue.
286
+ #
287
+ # Escape.html_attr_value("abc") #=> #<Escape::HTMLAttrValue: "abc">
288
+ # Escape.html_attr_value("a&b") #=> #<Escape::HTMLAttrValue: "a&amp;b">
289
+ # Escape.html_attr_value("ab&<>\"c") #=> #<Escape::HTMLAttrValue: "ab&amp;&lt;&gt;&quot;c">
290
+ # Escape.html_attr_value("a'c") #=> #<Escape::HTMLAttrValue: "a'c">
291
+ #
292
+ # It escapes 4 characters:
293
+ # * '&' to '&amp;'
294
+ # * '<' to '&lt;'
295
+ # * '>' to '&gt;'
296
+ # * '"' to '&quot;'
297
+ #
298
+ def html_attr_value(str)
299
+ s = '"' + str.gsub(/[&<>"]/) {|ch| HTML_ATTR_ESCAPE_HASH[ch] } + '"'
300
+ HTMLAttrValue.new_no_dup(s)
301
+ end
302
+ end