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.
Files changed (48) hide show
  1. data/History.rdoc +29 -1
  2. data/Manifest.txt +6 -1
  3. data/README.rdoc +74 -28
  4. data/Rakefile +4 -3
  5. data/TODO.rdoc +7 -5
  6. data/bin/kronk +2 -11
  7. data/lib/kronk/async/em_ext.rb +34 -0
  8. data/lib/kronk/async/request.rb +73 -0
  9. data/lib/kronk/async/response.rb +70 -0
  10. data/lib/kronk/async.rb +118 -0
  11. data/lib/kronk/cmd.rb +111 -43
  12. data/lib/kronk/constants.rb +1 -0
  13. data/lib/kronk/core_ext.rb +1 -1
  14. data/lib/kronk/data_string.rb +251 -0
  15. data/lib/kronk/diff/output.rb +132 -100
  16. data/lib/kronk/diff.rb +20 -24
  17. data/lib/kronk/path/matcher.rb +8 -4
  18. data/lib/kronk/path/path_match.rb +48 -4
  19. data/lib/kronk/path/transaction.rb +74 -53
  20. data/lib/kronk/path.rb +11 -6
  21. data/lib/kronk/player/benchmark.rb +11 -12
  22. data/lib/kronk/player/input_reader.rb +40 -3
  23. data/lib/kronk/player/request_parser.rb +4 -1
  24. data/lib/kronk/player/stream.rb +2 -2
  25. data/lib/kronk/player/suite.rb +16 -9
  26. data/lib/kronk/player.rb +93 -143
  27. data/lib/kronk/queue_runner.rb +238 -0
  28. data/lib/kronk/request.rb +25 -20
  29. data/lib/kronk/response.rb +39 -10
  30. data/lib/kronk/test/assertions.rb +2 -2
  31. data/lib/kronk/test/helper_methods.rb +1 -1
  32. data/lib/kronk.rb +56 -24
  33. data/test/test_assertions.rb +4 -4
  34. data/test/test_cmd.rb +38 -10
  35. data/test/test_data_string.rb +242 -1
  36. data/test/test_diff.rb +8 -303
  37. data/test/test_helper.rb +1 -1
  38. data/test/test_kronk.rb +21 -28
  39. data/test/test_path.rb +29 -0
  40. data/test/test_path_match.rb +47 -2
  41. data/test/test_path_matcher.rb +42 -1
  42. data/test/test_player.rb +71 -72
  43. data/test/test_request.rb +31 -6
  44. data/test/test_request_parser.rb +7 -1
  45. data/test/test_response.rb +1 -1
  46. data/test/test_transaction.rb +78 -30
  47. metadata +64 -8
  48. 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][:address], options[:proxy][:port] = value.split ":", 2
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
- return options unless argv.include? "--"
447
+ path_index = argv.index("--")
448
+ return options unless path_index && path_index < argv.length - 1
413
449
 
414
- data_paths = argv.slice! argv.index("--")..-1
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
- if path[0,1] == "-"
419
- (options[:ignore_data] ||= []) << path[1..-1]
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
- (options[:only_data] ||= []) << path
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
- begin
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
- at_exit do
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, Response::MissingParser, Errno::ECONNRESET => e
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.retrieve uri
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 retrieve
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
- # Saves the raw http response to a cache file.
610
+ # Set Player async state.
561
611
 
562
- def self.cache_response resp, filepath=nil
563
- filepath ||= Kronk.config[:cache_file]
564
- return unless filepath
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
- File.open(filepath, "wb+") do |file|
568
- file.write resp.raw
569
- end
570
- rescue => e
571
- error "#{e.class}: #{e.message}"
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 and error string
645
+ # Print an error string
578
646
 
579
647
  def self.error str, more=nil
580
648
  $stderr.puts "\nError: #{str}"
@@ -78,6 +78,7 @@ class Kronk
78
78
 
79
79
  # Default config to use.
80
80
  DEFAULT_CONFIG = {
81
+ :async => 'auto',
81
82
  :content_types => DEFAULT_CONTENT_TYPES.dup,
82
83
  :cache_file => DEFAULT_CACHE_FILE,
83
84
  :context => 3,
@@ -11,7 +11,7 @@ class Kronk
11
11
 
12
12
  def has_path? path
13
13
  Kronk::Path.find path, self do |d,k,p|
14
- return !!p
14
+ return true
15
15
  end
16
16
 
17
17
  false
@@ -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