kronk 1.6.2 → 1.7.0
Sign up to get free protection for your applications and to get access to all the features.
- data/History.rdoc +29 -1
- data/Manifest.txt +6 -1
- data/README.rdoc +74 -28
- data/Rakefile +4 -3
- data/TODO.rdoc +7 -5
- data/bin/kronk +2 -11
- data/lib/kronk/async/em_ext.rb +34 -0
- data/lib/kronk/async/request.rb +73 -0
- data/lib/kronk/async/response.rb +70 -0
- data/lib/kronk/async.rb +118 -0
- data/lib/kronk/cmd.rb +111 -43
- data/lib/kronk/constants.rb +1 -0
- data/lib/kronk/core_ext.rb +1 -1
- data/lib/kronk/data_string.rb +251 -0
- data/lib/kronk/diff/output.rb +132 -100
- data/lib/kronk/diff.rb +20 -24
- data/lib/kronk/path/matcher.rb +8 -4
- data/lib/kronk/path/path_match.rb +48 -4
- data/lib/kronk/path/transaction.rb +74 -53
- data/lib/kronk/path.rb +11 -6
- data/lib/kronk/player/benchmark.rb +11 -12
- data/lib/kronk/player/input_reader.rb +40 -3
- data/lib/kronk/player/request_parser.rb +4 -1
- data/lib/kronk/player/stream.rb +2 -2
- data/lib/kronk/player/suite.rb +16 -9
- data/lib/kronk/player.rb +93 -143
- data/lib/kronk/queue_runner.rb +238 -0
- data/lib/kronk/request.rb +25 -20
- data/lib/kronk/response.rb +39 -10
- data/lib/kronk/test/assertions.rb +2 -2
- data/lib/kronk/test/helper_methods.rb +1 -1
- data/lib/kronk.rb +56 -24
- data/test/test_assertions.rb +4 -4
- data/test/test_cmd.rb +38 -10
- data/test/test_data_string.rb +242 -1
- data/test/test_diff.rb +8 -303
- data/test/test_helper.rb +1 -1
- data/test/test_kronk.rb +21 -28
- data/test/test_path.rb +29 -0
- data/test/test_path_match.rb +47 -2
- data/test/test_path_matcher.rb +42 -1
- data/test/test_player.rb +71 -72
- data/test/test_request.rb +31 -6
- data/test/test_request_parser.rb +7 -1
- data/test/test_response.rb +1 -1
- data/test/test_transaction.rb +78 -30
- metadata +64 -8
- data/lib/kronk/data_renderer.rb +0 -219
data/lib/kronk/cmd.rb
CHANGED
@@ -5,6 +5,23 @@ class Kronk
|
|
5
5
|
|
6
6
|
class Cmd
|
7
7
|
|
8
|
+
##
|
9
|
+
# Saves the raw http response to a cache file.
|
10
|
+
|
11
|
+
def self.cache_response resp, filepath=nil
|
12
|
+
filepath ||= Kronk.config[:cache_file]
|
13
|
+
return unless filepath
|
14
|
+
|
15
|
+
begin
|
16
|
+
File.open(filepath, "wb+") do |file|
|
17
|
+
file.write resp.raw
|
18
|
+
end
|
19
|
+
rescue => e
|
20
|
+
error "#{e.class}: #{e.message}"
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
|
8
25
|
##
|
9
26
|
# Start an IRB console with the given Kronk::Response object.
|
10
27
|
|
@@ -26,6 +43,22 @@ class Kronk
|
|
26
43
|
end
|
27
44
|
|
28
45
|
|
46
|
+
##
|
47
|
+
# Try to load the config file. If not found, create the default one
|
48
|
+
# and exit.
|
49
|
+
|
50
|
+
def self.load_config_file
|
51
|
+
Kronk.load_config
|
52
|
+
|
53
|
+
rescue Errno::ENOENT
|
54
|
+
make_config_file
|
55
|
+
error "No config file was found.\n" +
|
56
|
+
"Created default config in #{DEFAULT_CONFIG_FILE}\n" +
|
57
|
+
"Edit file if necessary and try again.\n"
|
58
|
+
exit 2
|
59
|
+
end
|
60
|
+
|
61
|
+
|
29
62
|
##
|
30
63
|
# Load the config-based requires.
|
31
64
|
|
@@ -48,7 +81,7 @@ class Kronk
|
|
48
81
|
|
49
82
|
|
50
83
|
##
|
51
|
-
# Parse ARGV
|
84
|
+
# Parse ARGV into options and Kronk config.
|
52
85
|
|
53
86
|
def self.parse_args argv
|
54
87
|
options = {
|
@@ -362,7 +395,7 @@ Parse and run diffs against data from live and cached http responses.
|
|
362
395
|
|
363
396
|
opt.on('-x', '--proxy STR', String,
|
364
397
|
'Use HTTP proxy on given port: host[:port]') do |value|
|
365
|
-
options[:proxy][:
|
398
|
+
options[:proxy][:host], options[:proxy][:port] = value.split ":", 2
|
366
399
|
end
|
367
400
|
|
368
401
|
opt.separator nil
|
@@ -372,6 +405,7 @@ Parse and run diffs against data from live and cached http responses.
|
|
372
405
|
|
373
406
|
unless options[:player].empty?
|
374
407
|
options[:player] = Player.new options[:player]
|
408
|
+
set_player_backend Kronk.config[:async]
|
375
409
|
else
|
376
410
|
options.delete :player
|
377
411
|
end
|
@@ -393,7 +427,8 @@ Parse and run diffs against data from live and cached http responses.
|
|
393
427
|
|
394
428
|
argv.clear
|
395
429
|
|
396
|
-
raise "You must enter at least one URI" if options[:uris].empty?
|
430
|
+
raise "You must enter at least one URI" if options[:uris].empty? &&
|
431
|
+
options[:player].nil?
|
397
432
|
|
398
433
|
options
|
399
434
|
|
@@ -409,16 +444,29 @@ Parse and run diffs against data from live and cached http responses.
|
|
409
444
|
# Returns the array [only_paths, except_paths]
|
410
445
|
|
411
446
|
def self.parse_data_path_args options, argv
|
412
|
-
|
447
|
+
path_index = argv.index("--")
|
448
|
+
return options unless path_index && path_index < argv.length - 1
|
413
449
|
|
414
|
-
data_paths = argv.slice!
|
450
|
+
data_paths = argv.slice! path_index..-1
|
415
451
|
data_paths.shift
|
416
452
|
|
453
|
+
options[:transform] = []
|
454
|
+
|
417
455
|
data_paths.each do |path|
|
418
|
-
|
419
|
-
|
456
|
+
action, path = process_path path
|
457
|
+
|
458
|
+
# Merge identical actions into the same transaction action
|
459
|
+
if options[:transform][-1] && options[:transform][-1][0] == action
|
460
|
+
options[:transform][-1][1] << path
|
461
|
+
|
462
|
+
# Merge successive maps and selects together
|
463
|
+
elsif options[:transform][-1] &&
|
464
|
+
[options[:transform][-1][0], action, :map, :select].uniq.length == 2
|
465
|
+
options[:transform][-1][0] = :map
|
466
|
+
options[:transform][-1][1] << path
|
467
|
+
|
420
468
|
else
|
421
|
-
|
469
|
+
options[:transform] << [action, [path]]
|
422
470
|
end
|
423
471
|
end
|
424
472
|
|
@@ -426,15 +474,34 @@ Parse and run diffs against data from live and cached http responses.
|
|
426
474
|
end
|
427
475
|
|
428
476
|
|
477
|
+
##
|
478
|
+
# Determine the cmd-given path's action and Kronk::Path representation.
|
479
|
+
|
480
|
+
def self.process_path path
|
481
|
+
case path
|
482
|
+
when /^-/
|
483
|
+
[:delete, path[1..-1]]
|
484
|
+
when /([^\\]>>)/
|
485
|
+
index = path.index $1
|
486
|
+
[:move, [path[0..index], path[index+3..-1]]]
|
487
|
+
when /([^\\]>)/
|
488
|
+
index = path.index $1
|
489
|
+
[:map, [path[0..index], path[index+2..-1]]]
|
490
|
+
else
|
491
|
+
[:select, path]
|
492
|
+
end
|
493
|
+
end
|
494
|
+
|
495
|
+
|
429
496
|
##
|
430
497
|
# Ask the user for a password from stdin the command line.
|
431
498
|
|
432
499
|
def self.query_password str=nil
|
433
500
|
$stderr << "#{(str || "Password:")} "
|
434
|
-
system "stty -echo"
|
501
|
+
system "stty -echo" unless windows?
|
435
502
|
password = $stdin.gets.chomp
|
436
503
|
ensure
|
437
|
-
system "stty echo"
|
504
|
+
system "stty echo" unless windows?
|
438
505
|
$stderr << "\n"
|
439
506
|
password
|
440
507
|
end
|
@@ -444,31 +511,14 @@ Parse and run diffs against data from live and cached http responses.
|
|
444
511
|
# Runs the kronk command with the given terminal args.
|
445
512
|
|
446
513
|
def self.run argv=ARGV
|
447
|
-
|
448
|
-
Kronk.load_config
|
449
|
-
|
450
|
-
rescue Errno::ENOENT
|
451
|
-
make_config_file
|
452
|
-
error "No config file was found.\n" +
|
453
|
-
"Created default config in #{DEFAULT_CONFIG_FILE}\n" +
|
454
|
-
"Edit file if necessary and try again.\n"
|
455
|
-
exit 2
|
456
|
-
end
|
457
|
-
|
458
|
-
options = parse_args argv
|
514
|
+
load_config_file
|
459
515
|
|
460
516
|
Kronk.load_cookie_jar
|
461
517
|
|
518
|
+
options = parse_args argv
|
462
519
|
load_requires options.delete(:requires)
|
463
520
|
|
464
|
-
|
465
|
-
Kronk.save_cookie_jar
|
466
|
-
Kronk.save_history
|
467
|
-
end
|
468
|
-
|
469
|
-
trap 'INT' do
|
470
|
-
exit 2
|
471
|
-
end
|
521
|
+
set_exit_behavior
|
472
522
|
|
473
523
|
uri1, uri2 = options.delete :uris
|
474
524
|
runner = options.delete(:player) || self
|
@@ -482,7 +532,7 @@ Parse and run diffs against data from live and cached http responses.
|
|
482
532
|
|
483
533
|
exit 1 unless success
|
484
534
|
|
485
|
-
rescue Kronk::Exception,
|
535
|
+
rescue Kronk::Exception, Errno::ECONNRESET => e
|
486
536
|
error e.message, e.backtrace
|
487
537
|
exit 2
|
488
538
|
end
|
@@ -503,13 +553,13 @@ Parse and run diffs against data from live and cached http responses.
|
|
503
553
|
|
504
554
|
def self.request uri, options={}
|
505
555
|
kronk = Kronk.new options
|
506
|
-
kronk.
|
556
|
+
kronk.request uri
|
507
557
|
render kronk, options
|
508
558
|
end
|
509
559
|
|
510
560
|
|
511
561
|
##
|
512
|
-
# Renders the results of a Kronk compare or
|
562
|
+
# Renders the results of a Kronk compare or request
|
513
563
|
# to $stdout.
|
514
564
|
|
515
565
|
def self.render kronk, options={}
|
@@ -557,24 +607,42 @@ Parse and run diffs against data from live and cached http responses.
|
|
557
607
|
|
558
608
|
|
559
609
|
##
|
560
|
-
#
|
610
|
+
# Set Player async state.
|
561
611
|
|
562
|
-
def self.
|
563
|
-
|
564
|
-
|
612
|
+
def self.set_player_backend async=false
|
613
|
+
return Kronk::Player.async = false unless
|
614
|
+
async == true || async.to_s == 'auto'
|
565
615
|
|
566
616
|
begin
|
567
|
-
|
568
|
-
|
569
|
-
|
570
|
-
rescue => e
|
571
|
-
|
617
|
+
require 'kronk/async'
|
618
|
+
Kronk::Player.async = true
|
619
|
+
|
620
|
+
rescue LoadError => e
|
621
|
+
Kronk::Player.async = false
|
622
|
+
|
623
|
+
raise Kronk::Exception, "Async mode requires the em-http-request gem" if
|
624
|
+
async == true
|
625
|
+
end
|
626
|
+
end
|
627
|
+
|
628
|
+
|
629
|
+
##
|
630
|
+
# Assign at_exit and trap :INT behavior.
|
631
|
+
|
632
|
+
def self.set_exit_behavior
|
633
|
+
at_exit do
|
634
|
+
Kronk.save_cookie_jar
|
635
|
+
Kronk.save_history
|
636
|
+
end
|
637
|
+
|
638
|
+
trap 'INT' do
|
639
|
+
exit 2
|
572
640
|
end
|
573
641
|
end
|
574
642
|
|
575
643
|
|
576
644
|
##
|
577
|
-
# Print
|
645
|
+
# Print an error string
|
578
646
|
|
579
647
|
def self.error str, more=nil
|
580
648
|
$stderr.puts "\nError: #{str}"
|
data/lib/kronk/constants.rb
CHANGED
data/lib/kronk/core_ext.rb
CHANGED
@@ -0,0 +1,251 @@
|
|
1
|
+
class Kronk
|
2
|
+
|
3
|
+
##
|
4
|
+
# Creates ordered data string renders for diffing with character-precise
|
5
|
+
# path information.
|
6
|
+
#
|
7
|
+
# dstr = DataString.new({'a' => 'foo', 'b' => 'bar', 'c' => ["one", "two"]})
|
8
|
+
# # {
|
9
|
+
# # "a": "foo",
|
10
|
+
# # "b": "bar",
|
11
|
+
# # "c": [
|
12
|
+
# # "one",
|
13
|
+
# # "two"
|
14
|
+
# # ]
|
15
|
+
# # }
|
16
|
+
#
|
17
|
+
# dstr.meta[dstr.index("\"a\"")]
|
18
|
+
# # /
|
19
|
+
#
|
20
|
+
# dstr.meta[dstr.index("\"foo\"")]
|
21
|
+
# # /a
|
22
|
+
#
|
23
|
+
# dstr.meta[dstr.index("\"two\"")]
|
24
|
+
# # /c/1
|
25
|
+
|
26
|
+
class DataString < String
|
27
|
+
|
28
|
+
TO_RUBY = proc do |type, obj|
|
29
|
+
case type
|
30
|
+
when :key_assign then " => "
|
31
|
+
when :key then obj.inspect
|
32
|
+
when :value then obj.inspect
|
33
|
+
when :struct
|
34
|
+
(obj == true || obj == false) ? "Boolean" : obj.class
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
|
39
|
+
TO_JSON = proc do |type, obj|
|
40
|
+
case type
|
41
|
+
when :key_assign then ": "
|
42
|
+
when :key
|
43
|
+
(Symbol === obj ? obj.inspect : obj.to_s).to_json
|
44
|
+
when :value
|
45
|
+
(Symbol === obj ? obj.inspect : obj).to_json
|
46
|
+
when :struct
|
47
|
+
((obj == true || obj == false) ? "Boolean" : obj.class).to_json
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
|
52
|
+
##
|
53
|
+
# Returns a ruby data string that is diff-able, meaning sorted by
|
54
|
+
# Hash keys when available.
|
55
|
+
|
56
|
+
def self.ruby data, opts={}
|
57
|
+
new(data, opts, &TO_RUBY)
|
58
|
+
end
|
59
|
+
|
60
|
+
|
61
|
+
##
|
62
|
+
# Returns a json data string that is diff-able, meaning sorted by
|
63
|
+
# Hash keys when available.
|
64
|
+
|
65
|
+
def self.json data, opts={}
|
66
|
+
new(data, opts, &TO_JSON)
|
67
|
+
end
|
68
|
+
|
69
|
+
|
70
|
+
attr_accessor :data, :meta, :struct_only
|
71
|
+
|
72
|
+
|
73
|
+
##
|
74
|
+
# Create a new DataString that is diff-able, meaning sorted by
|
75
|
+
# Hash keys when available.
|
76
|
+
#
|
77
|
+
# Options supported are:
|
78
|
+
# :indentation:: Integer - how many spaces to indent by; default 1
|
79
|
+
# :render_lang:: String - output to 'ruby' or 'json'; default 'json'
|
80
|
+
# :struct:: Boolean - class names used instead of values; default nil
|
81
|
+
#
|
82
|
+
# If block is given, will yield the type of object to render and
|
83
|
+
# an optional object to render. Types given are :key_assign, :key, :value,
|
84
|
+
# or :struct. By default DataString uses the TO_JSON proc.
|
85
|
+
|
86
|
+
def initialize data=nil, opts={}, &block
|
87
|
+
@struct_only = opts[:struct]
|
88
|
+
@indentation = opts[:indentation] || 1
|
89
|
+
@meta = []
|
90
|
+
|
91
|
+
if String === data
|
92
|
+
super data
|
93
|
+
@data = nil
|
94
|
+
|
95
|
+
else
|
96
|
+
super ""
|
97
|
+
data = Kronk::Path.pathed(data) if Kronk.config[:render_paths]
|
98
|
+
@data = data
|
99
|
+
block ||= Kronk.config[:render_lang].to_s == 'ruby' ? TO_RUBY : TO_JSON
|
100
|
+
end
|
101
|
+
|
102
|
+
render data, &block if data && block
|
103
|
+
end
|
104
|
+
|
105
|
+
|
106
|
+
##
|
107
|
+
# Turns a data set into an ordered string output for diff-ing.
|
108
|
+
|
109
|
+
def render data, path=[], &block
|
110
|
+
indent = (path.length + 1) * @indentation
|
111
|
+
pad = " " * indent
|
112
|
+
|
113
|
+
case data
|
114
|
+
|
115
|
+
when Hash
|
116
|
+
append("{}", path) and return if data.empty?
|
117
|
+
append "{\n", path
|
118
|
+
|
119
|
+
sort_any(data.keys).each_with_index do |key, i|
|
120
|
+
append "#{pad}#{ yield(:key, key) }#{ yield(:key_assign) }", path
|
121
|
+
|
122
|
+
value = data[key]
|
123
|
+
new_path = path.dup << key
|
124
|
+
render value, new_path, &block
|
125
|
+
|
126
|
+
append(",", path) unless i == data.length - 1
|
127
|
+
append("\n", path)
|
128
|
+
end
|
129
|
+
|
130
|
+
append(("#{" " * (indent - @indentation)}}"), path)
|
131
|
+
|
132
|
+
when Array
|
133
|
+
append("[]", path) and return if data.empty?
|
134
|
+
append "[\n", path
|
135
|
+
|
136
|
+
(0...data.length).each do |key|
|
137
|
+
append pad, path
|
138
|
+
|
139
|
+
value = data[key]
|
140
|
+
new_path = path.dup << key
|
141
|
+
render value, new_path, &block
|
142
|
+
|
143
|
+
append(",", path) unless key == data.length - 1
|
144
|
+
append("\n", path)
|
145
|
+
end
|
146
|
+
|
147
|
+
append(("#{" " * (indent - @indentation)}]"), path)
|
148
|
+
|
149
|
+
else
|
150
|
+
output = @struct_only ? yield(:struct, data) : yield(:value, data)
|
151
|
+
append output.to_s, path
|
152
|
+
end
|
153
|
+
end
|
154
|
+
|
155
|
+
|
156
|
+
alias append_arrow <<
|
157
|
+
|
158
|
+
##
|
159
|
+
# Add a string with metadata to the data string.
|
160
|
+
|
161
|
+
def append str, metadata=nil
|
162
|
+
@meta.concat([metadata] * str.length)
|
163
|
+
self.append_arrow str
|
164
|
+
end
|
165
|
+
|
166
|
+
##
|
167
|
+
# Similar to String#<< but adds metadata.
|
168
|
+
|
169
|
+
def << str
|
170
|
+
if str.class == self.class
|
171
|
+
@meta.concat str.meta
|
172
|
+
else
|
173
|
+
@meta.concat([@meta.last] * str.length)
|
174
|
+
end
|
175
|
+
super str
|
176
|
+
end
|
177
|
+
|
178
|
+
|
179
|
+
##
|
180
|
+
# Similar to String#[] but keeps metadata.
|
181
|
+
|
182
|
+
def [] arg
|
183
|
+
dstr = self.class.new super
|
184
|
+
dstr.meta = @meta[arg]
|
185
|
+
dstr
|
186
|
+
end
|
187
|
+
|
188
|
+
|
189
|
+
##
|
190
|
+
# Similar to String#split but keeps metadata.
|
191
|
+
|
192
|
+
def split pattern=$;, *more
|
193
|
+
arr = super
|
194
|
+
i = 0
|
195
|
+
interval = 0
|
196
|
+
interval = (self.length - arr.join.length) / (arr.length - 1) if
|
197
|
+
arr.length > 1
|
198
|
+
|
199
|
+
arr.map do |str|
|
200
|
+
ds = self.class.new str
|
201
|
+
ds.meta = @meta[i,str.length]
|
202
|
+
i += str.length + interval
|
203
|
+
ds
|
204
|
+
end
|
205
|
+
end
|
206
|
+
|
207
|
+
|
208
|
+
protected
|
209
|
+
|
210
|
+
##
|
211
|
+
# Sorts an array of any combination of string, integer, or symbols.
|
212
|
+
|
213
|
+
def sort_any arr
|
214
|
+
i = 1
|
215
|
+
until i >= arr.length
|
216
|
+
j = i-1
|
217
|
+
val = arr[i]
|
218
|
+
prev_val = arr[j]
|
219
|
+
|
220
|
+
loop do
|
221
|
+
if smaller?(val, arr[j])
|
222
|
+
arr[j+1] = arr[j]
|
223
|
+
j = j - 1
|
224
|
+
break if j < 0
|
225
|
+
|
226
|
+
else
|
227
|
+
break
|
228
|
+
end
|
229
|
+
end
|
230
|
+
|
231
|
+
arr[j+1] = val
|
232
|
+
|
233
|
+
i = i.next
|
234
|
+
end
|
235
|
+
|
236
|
+
arr
|
237
|
+
end
|
238
|
+
|
239
|
+
|
240
|
+
CLASS_ORDER = {Fixnum => 2, String => 1, Symbol => 0}
|
241
|
+
|
242
|
+
##
|
243
|
+
# Compares Numerics, Strings, and Symbols and returns true if the left
|
244
|
+
# side is 'smaller' than the right side.
|
245
|
+
|
246
|
+
def smaller? left, right
|
247
|
+
left.class == right.class && left.to_s < right.to_s ||
|
248
|
+
CLASS_ORDER[left.class] < CLASS_ORDER[right.class]
|
249
|
+
end
|
250
|
+
end
|
251
|
+
end
|