kronk 1.6.2 → 1.7.0

Sign up to get free protection for your applications and to get access to all the features.
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