kronk 1.6.2 → 1.7.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.
- 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
|