kronk 1.5.4 → 1.6.0
Sign up to get free protection for your applications and to get access to all the features.
- data/History.rdoc +14 -0
- data/Manifest.txt +8 -2
- data/README.rdoc +6 -0
- data/TODO.rdoc +12 -0
- data/lib/kronk.rb +6 -2
- data/lib/kronk/cmd.rb +18 -2
- data/lib/kronk/constants.rb +1 -0
- data/lib/kronk/data_renderer.rb +95 -22
- data/lib/kronk/diff.rb +18 -64
- data/lib/kronk/diff/ascii_format.rb +11 -0
- data/lib/kronk/diff/color_format.rb +14 -2
- data/lib/kronk/diff/output.rb +155 -0
- data/lib/kronk/path.rb +48 -153
- data/lib/kronk/path/matcher.rb +189 -0
- data/lib/kronk/path/path_match.rb +74 -0
- data/lib/kronk/path/transaction.rb +157 -47
- data/lib/kronk/player/benchmark.rb +2 -1
- data/lib/kronk/player/suite.rb +8 -0
- data/lib/kronk/response.rb +7 -6
- data/test/test_cmd.rb +29 -8
- data/test/test_data_string.rb +58 -0
- data/test/test_diff.rb +137 -36
- data/test/test_helper.rb +2 -0
- data/test/test_kronk.rb +19 -3
- data/test/test_path.rb +87 -170
- data/test/test_path_match.rb +60 -0
- data/test/test_path_matcher.rb +329 -0
- data/test/test_response.rb +10 -10
- data/test/test_transaction.rb +132 -3
- metadata +82 -75
@@ -118,7 +118,7 @@ class Kronk
|
|
118
118
|
end
|
119
119
|
end
|
120
120
|
|
121
|
-
perc_list.each{|
|
121
|
+
perc_list.each{|l| @percentages[l] ||= self.slowest }
|
122
122
|
@percentages[100] = self.slowest
|
123
123
|
@percentages
|
124
124
|
end
|
@@ -252,6 +252,7 @@ end
|
|
252
252
|
|
253
253
|
if Float.instance_method(:round).arity == 0
|
254
254
|
class Float
|
255
|
+
undef round
|
255
256
|
def round ndigits=0
|
256
257
|
num, dec = self.to_s.split(".")
|
257
258
|
num = "#{num}.#{dec[0,ndigits]}".sub(/\.$/, "")
|
data/lib/kronk/player/suite.rb
CHANGED
@@ -25,6 +25,14 @@ class Kronk
|
|
25
25
|
[status, time, text]
|
26
26
|
|
27
27
|
elsif kronk.response
|
28
|
+
begin
|
29
|
+
# Make sure response is parsable
|
30
|
+
kronk.response.stringify kronk.options
|
31
|
+
rescue => e
|
32
|
+
error e, kronk, mutex
|
33
|
+
return
|
34
|
+
end if kronk.response.success?
|
35
|
+
|
28
36
|
status = "F" if !kronk.response.success?
|
29
37
|
text = resp_text kronk if status == "F"
|
30
38
|
[status, kronk.response.time, text]
|
data/lib/kronk/response.rb
CHANGED
@@ -28,8 +28,6 @@ class Kronk
|
|
28
28
|
# Read http response from a file and return a HTTPResponse instance.
|
29
29
|
|
30
30
|
def self.read_file path
|
31
|
-
Kronk::Cmd.verbose "Reading file: #{path}\n"
|
32
|
-
|
33
31
|
file = File.open(path, "rb")
|
34
32
|
resp = new file
|
35
33
|
resp.uri = path
|
@@ -60,7 +58,8 @@ class Kronk
|
|
60
58
|
@_res, debug_io = request_from_io(io)
|
61
59
|
end
|
62
60
|
|
63
|
-
@headers = @_res.to_hash
|
61
|
+
@headers = @_res.to_hash.dup
|
62
|
+
@headers.keys.each{|h| @headers[h] = @headers[h].join(", ")}
|
64
63
|
|
65
64
|
@encoding = "utf-8" unless @_res["Content-Type"]
|
66
65
|
c_type = [*@headers["content-type"]].find{|ct| ct =~ ENCODING_MATCHER}
|
@@ -182,7 +181,7 @@ class Kronk
|
|
182
181
|
# Returns the parsed header hash.
|
183
182
|
|
184
183
|
def parsed_header include_headers=true
|
185
|
-
headers = @
|
184
|
+
headers = @headers.dup
|
186
185
|
|
187
186
|
case include_headers
|
188
187
|
when nil, false
|
@@ -277,7 +276,9 @@ class Kronk
|
|
277
276
|
end
|
278
277
|
|
279
278
|
if options[:with_headers]
|
280
|
-
|
279
|
+
header_data = parsed_header(options[:with_headers])
|
280
|
+
data &&= [header_data, data]
|
281
|
+
data ||= header_data
|
281
282
|
end
|
282
283
|
|
283
284
|
Path::Transaction.run data, options do |t|
|
@@ -300,7 +301,7 @@ class Kronk
|
|
300
301
|
# :with_headers:: Boolean/String/Array - defines which headers to include
|
301
302
|
|
302
303
|
def stringify options={}
|
303
|
-
if !options[:raw] && (options[:parser] || @parser)
|
304
|
+
if !options[:raw] && (options[:parser] || @parser || options[:no_body])
|
304
305
|
data = selective_data options
|
305
306
|
Diff.ordered_data_string data, options[:struct]
|
306
307
|
else
|
data/test/test_cmd.rb
CHANGED
@@ -145,6 +145,25 @@ class TestCmd < Test::Unit::TestCase
|
|
145
145
|
end
|
146
146
|
|
147
147
|
|
148
|
+
def test_parse_args_context
|
149
|
+
with_config do
|
150
|
+
opts = Kronk::Cmd.parse_args %w{uri --context}
|
151
|
+
assert_equal 3, opts[:context]
|
152
|
+
assert_equal nil, Kronk.config[:context]
|
153
|
+
|
154
|
+
Kronk.config[:context] = 4
|
155
|
+
opts = Kronk::Cmd.parse_args %w{uri --context}
|
156
|
+
assert_equal 4, opts[:context]
|
157
|
+
|
158
|
+
opts = Kronk::Cmd.parse_args %w{uri --context 5}
|
159
|
+
assert_equal 5, opts[:context]
|
160
|
+
|
161
|
+
opts = Kronk::Cmd.parse_args %w{uri --full}
|
162
|
+
assert_equal false, opts[:context]
|
163
|
+
end
|
164
|
+
end
|
165
|
+
|
166
|
+
|
148
167
|
def test_parse_args_completion
|
149
168
|
with_config Hash.new do
|
150
169
|
file = File.join(File.dirname(__FILE__), "../script/kronk_completion")
|
@@ -168,18 +187,19 @@ class TestCmd < Test::Unit::TestCase
|
|
168
187
|
|
169
188
|
def test_parse_args_kronk_configs
|
170
189
|
with_config Hash.new do
|
171
|
-
Kronk::Cmd.parse_args %w{uri -q -l --no-opts -V -t 1234 --ruby}
|
190
|
+
Kronk::Cmd.parse_args %w{uri -q -l --no-opts -V -t 1234 --ruby --paths}
|
172
191
|
assert Kronk.config[:brief]
|
173
192
|
assert Kronk.config[:show_lines]
|
174
193
|
assert Kronk.config[:no_uri_options]
|
175
194
|
assert Kronk.config[:verbose]
|
195
|
+
assert Kronk.config[:render_paths]
|
176
196
|
assert_equal 'ruby', Kronk.config[:render_lang]
|
177
197
|
assert_equal 1234, Kronk.config[:timeout]
|
178
198
|
end
|
179
199
|
end
|
180
200
|
|
181
201
|
|
182
|
-
def
|
202
|
+
def test_parse_args_http_headers
|
183
203
|
opts = Kronk::Cmd.parse_args %w{uri -i FOO -i BAR -iTWO,PART}
|
184
204
|
assert_equal %w{FOO BAR TWO PART}, opts[:with_headers]
|
185
205
|
assert_equal false, opts[:no_body]
|
@@ -380,25 +400,26 @@ class TestCmd < Test::Unit::TestCase
|
|
380
400
|
|
381
401
|
|
382
402
|
def test_run_compare
|
383
|
-
Kronk.expects(:load_config)
|
384
|
-
expect_compare_output mock_200_response
|
385
|
-
|
386
403
|
file = File.join(File.dirname(__FILE__), "mocks/200_response.txt")
|
387
404
|
file = File.expand_path file
|
388
405
|
|
406
|
+
Kronk.expects(:load_config)
|
407
|
+
expect_compare_output mock_200_response, :labels => [file, file]
|
408
|
+
|
389
409
|
Kronk::Cmd.run [file, file]
|
390
410
|
end
|
391
411
|
|
392
412
|
|
393
413
|
def test_run_compare_diff
|
394
|
-
Kronk.expects(:load_config)
|
395
|
-
expect_compare_output mock_200_response, mock_302_response
|
396
|
-
|
397
414
|
file1 = File.join(File.dirname(__FILE__), "mocks/200_response.txt")
|
398
415
|
file2 = File.join(File.dirname(__FILE__), "mocks/302_response.txt")
|
399
416
|
file1 = File.expand_path file1
|
400
417
|
file2 = File.expand_path file2
|
401
418
|
|
419
|
+
Kronk.expects(:load_config)
|
420
|
+
expect_compare_output mock_200_response, mock_302_response,
|
421
|
+
:labels => [file1, file2]
|
422
|
+
|
402
423
|
assert_exit 1 do
|
403
424
|
Kronk::Cmd.run [file1, file2]
|
404
425
|
end
|
@@ -0,0 +1,58 @@
|
|
1
|
+
require 'test/test_helper'
|
2
|
+
|
3
|
+
class TestDataString < Test::Unit::TestCase
|
4
|
+
|
5
|
+
def setup
|
6
|
+
@dstr = Kronk::DataString.new "foobar", "data0"
|
7
|
+
end
|
8
|
+
|
9
|
+
|
10
|
+
def test_new
|
11
|
+
expected_meta = ["data0"] * @dstr.length
|
12
|
+
assert_equal expected_meta, @dstr.meta
|
13
|
+
end
|
14
|
+
|
15
|
+
|
16
|
+
def test_append
|
17
|
+
@dstr.append "\nthingz", "data1"
|
18
|
+
expected_meta = (["data0"] * 6) + (["data1"] * 7)
|
19
|
+
assert_equal expected_meta, @dstr.meta
|
20
|
+
end
|
21
|
+
|
22
|
+
|
23
|
+
def test_insert
|
24
|
+
@dstr << "\nthingz"
|
25
|
+
expected_meta = ["data0"] * 13
|
26
|
+
assert_equal expected_meta, @dstr.meta
|
27
|
+
end
|
28
|
+
|
29
|
+
|
30
|
+
def test_select
|
31
|
+
@dstr.append "\nthingz", "data1"
|
32
|
+
new_dstr = @dstr[4..9]
|
33
|
+
expected_meta = (["data0"] * 2) + (["data1"] * 4)
|
34
|
+
assert_equal expected_meta, new_dstr.meta
|
35
|
+
end
|
36
|
+
|
37
|
+
|
38
|
+
def test_split
|
39
|
+
@dstr.append "\nthingz", "data1"
|
40
|
+
arr = @dstr.split
|
41
|
+
|
42
|
+
expected = ["data0"] * 6
|
43
|
+
assert_equal expected, arr.first.meta
|
44
|
+
|
45
|
+
expected = ["data1"] * 6
|
46
|
+
assert_equal expected, arr.last.meta
|
47
|
+
end
|
48
|
+
|
49
|
+
|
50
|
+
def test_split_chars
|
51
|
+
@dstr.append "\nthingz", "data1"
|
52
|
+
arr = @dstr.split ''
|
53
|
+
|
54
|
+
arr.each_with_index do |dstr, i|
|
55
|
+
assert_equal [@dstr.meta[i]], dstr.meta
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
data/test/test_diff.rb
CHANGED
@@ -174,12 +174,10 @@ STR
|
|
174
174
|
Kronk::Diff.ordered_data_string(mock_data, true)
|
175
175
|
end
|
176
176
|
|
177
|
-
|
178
|
-
|
179
|
-
with_config :render_lang => 'ruby' do
|
180
|
-
expected = <<STR
|
177
|
+
def test_ordered_data_string_json
|
178
|
+
expected = <<STR
|
181
179
|
{
|
182
|
-
"acks"
|
180
|
+
"acks": [
|
183
181
|
[
|
184
182
|
56,
|
185
183
|
78
|
@@ -189,7 +187,7 @@ STR
|
|
189
187
|
"34"
|
190
188
|
]
|
191
189
|
],
|
192
|
-
"root"
|
190
|
+
"root": [
|
193
191
|
[
|
194
192
|
"B1",
|
195
193
|
"B2"
|
@@ -207,11 +205,11 @@ STR
|
|
207
205
|
]
|
208
206
|
],
|
209
207
|
{
|
210
|
-
:tests
|
208
|
+
":tests": [
|
211
209
|
"D3a",
|
212
210
|
"D3b"
|
213
211
|
],
|
214
|
-
"test"
|
212
|
+
"test": [
|
215
213
|
[
|
216
214
|
"D1a\\nContent goes here",
|
217
215
|
"D1b"
|
@@ -220,13 +218,13 @@ STR
|
|
220
218
|
]
|
221
219
|
}
|
222
220
|
],
|
223
|
-
"subs"
|
221
|
+
"subs": [
|
224
222
|
"a",
|
225
223
|
"b"
|
226
224
|
],
|
227
|
-
"tests"
|
228
|
-
:foo
|
229
|
-
"test"
|
225
|
+
"tests": {
|
226
|
+
":foo": ":bar",
|
227
|
+
"test": [
|
230
228
|
[
|
231
229
|
1,
|
232
230
|
2
|
@@ -235,6 +233,40 @@ STR
|
|
235
233
|
]
|
236
234
|
}
|
237
235
|
}
|
236
|
+
STR
|
237
|
+
|
238
|
+
assert_equal expected.strip, Kronk::Diff.ordered_data_string(mock_data)
|
239
|
+
end
|
240
|
+
|
241
|
+
|
242
|
+
def test_ordered_data_string_ruby_paths
|
243
|
+
with_config :render_lang => 'ruby', :render_paths => true do
|
244
|
+
expected = <<STR
|
245
|
+
{
|
246
|
+
"/acks/0/0" => 56,
|
247
|
+
"/acks/0/1" => 78,
|
248
|
+
"/acks/1/0" => "12",
|
249
|
+
"/acks/1/1" => "34",
|
250
|
+
"/root/0/0" => "B1",
|
251
|
+
"/root/0/1" => "B2",
|
252
|
+
"/root/1/0" => "A1",
|
253
|
+
"/root/1/1" => "A2",
|
254
|
+
"/root/2/0" => "C1",
|
255
|
+
"/root/2/1" => "C2",
|
256
|
+
"/root/2/2/0" => "C3a",
|
257
|
+
"/root/2/2/1" => "C3b",
|
258
|
+
"/root/3/test/0/0" => "D1a\\nContent goes here",
|
259
|
+
"/root/3/test/0/1" => "D1b",
|
260
|
+
"/root/3/test/1" => "D2",
|
261
|
+
"/root/3/tests/0" => "D3a",
|
262
|
+
"/root/3/tests/1" => "D3b",
|
263
|
+
"/subs/0" => "a",
|
264
|
+
"/subs/1" => "b",
|
265
|
+
"/tests/foo" => :bar,
|
266
|
+
"/tests/test/0/0" => 1,
|
267
|
+
"/tests/test/0/1" => 2,
|
268
|
+
"/tests/test/1" => 2.123
|
269
|
+
}
|
238
270
|
STR
|
239
271
|
|
240
272
|
assert_equal expected.strip, Kronk::Diff.ordered_data_string(mock_data)
|
@@ -481,30 +513,43 @@ STR
|
|
481
513
|
|
482
514
|
|
483
515
|
def test_formatted_lines
|
484
|
-
|
516
|
+
@diff.output.show_lines = true
|
517
|
+
output = @diff.formatted
|
485
518
|
assert_equal diff_302_301_str_lines, output
|
486
519
|
end
|
487
520
|
|
488
521
|
|
489
522
|
def test_formatted_color
|
490
|
-
|
491
|
-
@diff.formatted(:formatter => Kronk::Diff::ColorFormat)
|
523
|
+
@diff.output.format = Kronk::Diff::ColorFormat
|
492
524
|
|
493
|
-
|
494
|
-
|
525
|
+
assert_equal diff_302_301_color,
|
526
|
+
@diff.formatted
|
495
527
|
end
|
496
528
|
|
497
529
|
|
498
530
|
def test_formatted_join_char
|
499
531
|
expected = diff_302_301_str.gsub(/\n/, "\r\n")
|
500
|
-
|
501
|
-
|
502
|
-
|
503
|
-
|
532
|
+
@diff.output.format = Kronk::Diff::AsciiFormat
|
533
|
+
@diff.output.join_ch = "\r\n"
|
534
|
+
|
535
|
+
assert_equal expected, @diff.formatted
|
504
536
|
end
|
505
537
|
|
506
538
|
|
507
|
-
|
539
|
+
def test_formatted_context
|
540
|
+
resp1 = Kronk::Response.read_file "test/mocks/200_response_diff.json"
|
541
|
+
resp2 = Kronk::Response.read_file "test/mocks/200_response.json"
|
542
|
+
@diff = Kronk::Diff.new resp1.stringify, resp2.stringify
|
543
|
+
|
544
|
+
@diff.output.format = Kronk::Diff::ColorFormat
|
545
|
+
@diff.output.context = 3
|
546
|
+
@diff.output.labels = [resp1.uri, resp2.uri]
|
547
|
+
|
548
|
+
assert_equal diff_json_color, @diff.formatted
|
549
|
+
end
|
550
|
+
|
551
|
+
|
552
|
+
class CustomFormat < Kronk::Diff::AsciiFormat
|
508
553
|
def self.added str
|
509
554
|
">>>301>>> #{str}"
|
510
555
|
end
|
@@ -519,9 +564,10 @@ STR
|
|
519
564
|
end
|
520
565
|
|
521
566
|
def test_formatted_custom
|
522
|
-
|
523
|
-
|
524
|
-
expected =
|
567
|
+
@diff.output.format = CustomFormat
|
568
|
+
str_diff = @diff.formatted
|
569
|
+
expected = diff_302_301_str.gsub(/^\+ /, ">>>301>>> ")
|
570
|
+
expected = expected.gsub(/^\- /, "<<<302<<< ")
|
525
571
|
expected = expected.gsub(/^\s\s/, "")
|
526
572
|
|
527
573
|
assert_equal expected, str_diff
|
@@ -529,6 +575,55 @@ STR
|
|
529
575
|
|
530
576
|
private
|
531
577
|
|
578
|
+
def diff_json_color
|
579
|
+
(<<-STR
|
580
|
+
\e[1;33m--- test/mocks/200_response_diff.json
|
581
|
+
+++ test/mocks/200_response.json\e[0m
|
582
|
+
\e[1;35m@@ -6,7 +6,7 @@\e[0m business/description
|
583
|
+
"additional_urls": [
|
584
|
+
{
|
585
|
+
"destination": "http://example.com",
|
586
|
+
\e[1;31m- "url_click": "http://google.com"\e[0m
|
587
|
+
\e[1;32m+ "url_click": "http://example.com"\e[0m
|
588
|
+
}
|
589
|
+
],
|
590
|
+
"general_info": "<p>A Paint Your Own Pottery Studios..</p>",
|
591
|
+
\e[1;35m@@ -15,11 +15,12 @@\e[0m business/description
|
592
|
+
"slogan": "<p>Pottery YOU dress up</p>"
|
593
|
+
},
|
594
|
+
"distance": 0.0,
|
595
|
+
\e[1;31m- "has_detail_page": false,\e[0m
|
596
|
+
\e[1;32m+ "has_detail_page": true,\e[0m
|
597
|
+
"headings": [
|
598
|
+
"Pottery"
|
599
|
+
],
|
600
|
+
\e[1;31m- "id": 1234,\e[0m
|
601
|
+
\e[1;32m+ "id": "1234",\e[0m
|
602
|
+
\e[1;32m+ "impression_id": "mock_iid",\e[0m
|
603
|
+
"improvable": true,
|
604
|
+
"latitude": 42.882561,
|
605
|
+
"listing_id": "1234",
|
606
|
+
\e[1;35m@@ -34,12 +35,12 @@\e[0m business
|
607
|
+
"rating_count": 0,
|
608
|
+
"red_listing": false,
|
609
|
+
"state": "MI",
|
610
|
+
\e[1;31m- "website": "http://google.com",\e[0m
|
611
|
+
\e[1;32m+ "website": "http://example.com",\e[0m
|
612
|
+
"year_established": "1996",
|
613
|
+
"zip": "49418"
|
614
|
+
},
|
615
|
+
\e[1;31m- "original": {\e[0m
|
616
|
+
\e[1;32m+ "original_request": {\e[0m
|
617
|
+
"id": "1234"
|
618
|
+
},
|
619
|
+
\e[1;31m- "request_id": "foobar"\e[0m
|
620
|
+
\e[1;32m+ "request_id": "mock_rid"\e[0m
|
621
|
+
}
|
622
|
+
STR
|
623
|
+
).strip
|
624
|
+
end
|
625
|
+
|
626
|
+
|
532
627
|
def diff_302_301
|
533
628
|
[[["HTTP/1.1 302 Found", "Location: http://igoogle.com/"],
|
534
629
|
["HTTP/1.1 301 Moved Permanently", "Location: http://www.google.com/"]],
|
@@ -570,6 +665,8 @@ STR
|
|
570
665
|
|
571
666
|
def diff_302_301_str
|
572
667
|
str = <<STR
|
668
|
+
--- left
|
669
|
+
+++ right
|
573
670
|
- HTTP/1.1 302 Found
|
574
671
|
- Location: http://igoogle.com/
|
575
672
|
+ HTTP/1.1 301 Moved Permanently
|
@@ -599,6 +696,8 @@ STR
|
|
599
696
|
|
600
697
|
def diff_302_301_str_lines
|
601
698
|
str = <<STR
|
699
|
+
--- left
|
700
|
+
+++ right
|
602
701
|
1| - HTTP/1.1 302 Found
|
603
702
|
2| - Location: http://igoogle.com/
|
604
703
|
| 1 + HTTP/1.1 301 Moved Permanently
|
@@ -629,27 +728,29 @@ STR
|
|
629
728
|
|
630
729
|
def diff_302_301_color
|
631
730
|
str = <<STR
|
632
|
-
\
|
633
|
-
\033[
|
634
|
-
\033[
|
635
|
-
\033[
|
731
|
+
\e[1;33m--- left
|
732
|
+
+++ right\033[0m
|
733
|
+
\033[1;31m- HTTP/1.1 302 Found\033[0m
|
734
|
+
\033[1;31m- Location: http://igoogle.com/\033[0m
|
735
|
+
\033[1;32m+ HTTP/1.1 301 Moved Permanently\033[0m
|
736
|
+
\033[1;32m+ Location: http://www.google.com/\033[0m
|
636
737
|
Content-Type: text/html; charset=UTF-8
|
637
738
|
Date: Fri, 26 Nov 2010 16:14:45 GMT
|
638
739
|
Expires: Sun, 26 Dec 2010 16:14:45 GMT
|
639
740
|
Cache-Control: public, max-age=2592000
|
640
741
|
Server: gws
|
641
|
-
\033[31m- Content-Length: 260\033[0m
|
642
|
-
\033[32m+ Content-Length: 219\033[0m
|
742
|
+
\033[1;31m- Content-Length: 260\033[0m
|
743
|
+
\033[1;32m+ Content-Length: 219\033[0m
|
643
744
|
X-XSS-Protection: 1; mode=block
|
644
745
|
|
645
746
|
<HTML><HEAD><meta http-equiv="content-type" content="text/html;charset=utf-8">
|
646
|
-
\033[31m- <TITLE>302 Found</TITLE></HEAD><BODY>\033[0m
|
647
|
-
\033[31m- <H1>302 Found</H1>\033[0m
|
648
|
-
\033[32m+ <TITLE>301 Moved</TITLE></HEAD><BODY>\033[0m
|
649
|
-
\033[32m+ <H1>301 Moved</H1>\033[0m
|
747
|
+
\033[1;31m- <TITLE>302 Found</TITLE></HEAD><BODY>\033[0m
|
748
|
+
\033[1;31m- <H1>302 Found</H1>\033[0m
|
749
|
+
\033[1;32m+ <TITLE>301 Moved</TITLE></HEAD><BODY>\033[0m
|
750
|
+
\033[1;32m+ <H1>301 Moved</H1>\033[0m
|
650
751
|
The document has moved
|
651
752
|
<A HREF="http://www.google.com/">here</A>.
|
652
|
-
\033[31m- <A HREF="http://igoogle.com/">here</A>.\033[0m
|
753
|
+
\033[1;31m- <A HREF="http://igoogle.com/">here</A>.\033[0m
|
653
754
|
</BODY></HTML>
|
654
755
|
STR
|
655
756
|
str.strip
|