kronk 1.2.5 → 1.3.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.
@@ -0,0 +1,469 @@
1
+ class Kronk
2
+
3
+ ##
4
+ # Command line interface.
5
+
6
+ class Cmd
7
+
8
+ ##
9
+ # Start an IRB console with the given http response object.
10
+
11
+ def self.irb resp
12
+ require 'irb'
13
+
14
+ $http_response = resp
15
+ $response = begin
16
+ resp.parsed_body
17
+ rescue Response::MissingParser
18
+ resp.body
19
+ end
20
+
21
+ puts "\nHTTP Response is in $http_response"
22
+ puts "Response data is in $response\n\n"
23
+
24
+ IRB.start
25
+ exit 1
26
+ end
27
+
28
+
29
+ ##
30
+ # Load the config-based requires.
31
+
32
+ def self.load_requires more_requires=nil
33
+ return unless Kronk.config[:requires] || more_requires
34
+ (Kronk.config[:requires] | more_requires.to_a).each{|lib| require lib }
35
+ end
36
+
37
+
38
+ ##
39
+ # Creates the default config file at the given path.
40
+
41
+ def self.make_config_file
42
+ Dir.mkdir Kronk::CONFIG_DIR unless File.directory? Kronk::CONFIG_DIR
43
+
44
+ File.open Kronk::DEFAULT_CONFIG_FILE, "w+" do |file|
45
+ file << Kronk::DEFAULT_CONFIG.to_yaml
46
+ end
47
+ end
48
+
49
+
50
+ ##
51
+ # Moves the old config file to the new directory structure.
52
+
53
+ def self.move_config_file
54
+ require 'fileutils'
55
+
56
+ kronk_tmp_config = ".kronk.tmp"
57
+ File.rename Kronk::CONFIG_DIR, kronk_tmp_config
58
+
59
+ Dir.mkdir Kronk::CONFIG_DIR
60
+
61
+ FileUtils.mv kronk_tmp_config, Kronk::DEFAULT_CONFIG_FILE
62
+ end
63
+
64
+
65
+ ##
66
+ # Parse ARGV
67
+
68
+ def self.parse_args argv
69
+ options = {
70
+ :auth => {},
71
+ :no_body => false,
72
+ :proxy => {},
73
+ :uris => [],
74
+ :with_headers => false
75
+ }
76
+
77
+ options = parse_data_path_args options, argv
78
+
79
+ opts = OptionParser.new do |opt|
80
+ opt.program_name = File.basename $0
81
+ opt.version = Kronk::VERSION
82
+ opt.release = nil
83
+
84
+ opt.banner = <<-STR
85
+
86
+ #{opt.program_name} #{opt.version}
87
+
88
+ Parse and run diffs against data from live and cached http responses.
89
+
90
+ Usage:
91
+ #{opt.program_name} --help
92
+ #{opt.program_name} --version
93
+ #{opt.program_name} uri1 [uri2] [options...] [-- data-paths]
94
+
95
+ Examples:
96
+ #{opt.program_name} http://example.com/A
97
+ #{opt.program_name} http://example.com/B --prev --raw
98
+ #{opt.program_name} http://example.com/B.xml local/file/B.json
99
+ #{opt.program_name} file1.json file2.json -- **/key1=val1 -root/key?
100
+
101
+ Arguments after -- will be used to focus the diff on specific data points.
102
+ If the data paths start with a '-' the matched data points will be removed.
103
+ If the data paths start with a ":" the parent of the matched data is used.
104
+ The ':' and '-' modifiers may be used together in that order (':-').
105
+
106
+ Options:
107
+ STR
108
+
109
+ opt.on('--ascii', 'Return ascii formatted diff') do
110
+ Kronk.config[:diff_format] = :ascii_diff
111
+ end
112
+
113
+
114
+ opt.on('--color', 'Return color formatted diff') do
115
+ Kronk.config[:diff_format] = :color_diff
116
+ end
117
+
118
+
119
+ opt.on('--completion', 'Print bash completion file path and exit') do
120
+ file = File.join(File.dirname(__FILE__), "../script/kronk_completion")
121
+ puts File.expand_path(file)
122
+ exit 2
123
+ end
124
+
125
+
126
+ opt.on('--config STR', String,
127
+ 'Load the given Kronk config file') do |value|
128
+ Kronk.load_config value
129
+ end
130
+
131
+
132
+ opt.on('-q', '--brief', 'Output only whether URI responses differ') do
133
+ Kronk.config[:brief] = true
134
+ end
135
+
136
+
137
+ opt.on('--format STR', String,
138
+ 'Use a custom diff formatter') do |value|
139
+ Kronk.config[:diff_format] = value
140
+ end
141
+
142
+
143
+ opt.on('-i', '--include [header1,header2]', Array,
144
+ 'Include all or given headers in response') do |value|
145
+ options[:with_headers] ||= []
146
+
147
+ if value
148
+ options[:with_headers].concat value if
149
+ Array === options[:with_headers]
150
+ else
151
+ options[:with_headers] = true
152
+ end
153
+ end
154
+
155
+
156
+ opt.on('-I', '--head [header1,header2]', Array,
157
+ 'Use all or given headers only in the response') do |value|
158
+ options[:with_headers] ||= []
159
+
160
+ if value
161
+ options[:with_headers].concat value if
162
+ Array === options[:with_headers]
163
+ else
164
+ options[:with_headers] = true
165
+ end
166
+
167
+ options[:no_body] = true
168
+ end
169
+
170
+
171
+ opt.on('--irb', 'Start an IRB console') do
172
+ options[:irb] = true
173
+ end
174
+
175
+
176
+ opt.on('-l', '--lines', 'Show line numbers') do
177
+ Kronk.config[:show_lines] = true
178
+ end
179
+
180
+
181
+ opt.on('--no-opts', 'Turn off config URI options') do
182
+ Kronk.config[:no_uri_options] = true
183
+ end
184
+
185
+
186
+ opt.on('-P', '--parser STR', String,
187
+ 'Override default parser') do |value|
188
+ options[:parser] = value
189
+ end
190
+
191
+
192
+ opt.on('--prev', 'Use last response to diff against') do
193
+ options[:uris].unshift :cache
194
+ end
195
+
196
+
197
+ opt.on('-R', '--raw', 'Run diff on the raw data returned') do
198
+ options[:raw] = true
199
+ end
200
+
201
+
202
+ opt.on('-r', '--require lib1,lib2', Array,
203
+ 'Require a library or gem') do |value|
204
+ options[:requires] ||= []
205
+ options[:requires].concat value
206
+ end
207
+
208
+
209
+ opt.on('--struct', 'Run diff on the data structure') do
210
+ options[:struct] = true
211
+ end
212
+
213
+
214
+ opt.on('-V', '--verbose', 'Make the operation more talkative') do
215
+ Kronk.config[:verbose] = true
216
+ end
217
+
218
+
219
+ opt.separator <<-STR
220
+
221
+ HTTP Options:
222
+ STR
223
+
224
+ opt.on('--clear-cookies', 'Delete all saved cookies') do
225
+ Kronk.clear_cookies!
226
+ end
227
+
228
+
229
+ opt.on('-d', '--data STR', String,
230
+ 'Post data with the request') do |value|
231
+ options[:data] = value
232
+ options[:http_method] ||= 'POST'
233
+ end
234
+
235
+
236
+ opt.on('-H', '--header STR', String,
237
+ 'Header to pass to the server request') do |value|
238
+ options[:headers] ||= {}
239
+
240
+ key, value = value.split ": ", 2
241
+ options[:headers][key] = value.strip
242
+ end
243
+
244
+
245
+ opt.on('-A', '--user-agent STR', String,
246
+ 'User-Agent to send to server or a valid alias') do |value|
247
+ options[:user_agent] = value
248
+ end
249
+
250
+
251
+ opt.on('-L', '--location [NUM]', Integer,
252
+ 'Follow the location header always or num times') do |value|
253
+ options[:follow_redirects] = value || true
254
+ end
255
+
256
+
257
+ opt.on('--no-cookies', 'Don\'t use cookies for this session') do
258
+ options[:no_cookies] = true
259
+ end
260
+
261
+
262
+ opt.on('-?', '--query STR', String,
263
+ 'Append query to URLs') do |value|
264
+ options[:query] = value
265
+ end
266
+
267
+
268
+ opt.on('--suff STR', String,
269
+ 'Add common path items to the end of each URL') do |value|
270
+ options[:uri_suffix] = value
271
+ end
272
+
273
+
274
+ opt.on('--timeout INT', Integer,
275
+ 'Timeout for http connection in seconds') do |value|
276
+ Kronk.config[:timeout] = value
277
+ end
278
+
279
+
280
+ opt.on('-U', '--proxy-user STR', String,
281
+ 'Set proxy user and/or password: usr[:pass]') do |value|
282
+ options[:proxy][:username], options[:proxy][:password] =
283
+ value.split ":", 2
284
+
285
+ options[:proxy][:password] ||= query_password "Proxy password:"
286
+ end
287
+
288
+
289
+ opt.on('-u', '--user STR', String,
290
+ 'Set server auth user and/or password: usr[:pass]') do |value|
291
+ options[:auth][:username], options[:auth][:password] =
292
+ value.split ":", 2
293
+
294
+ options[:auth][:password] ||= query_password "Server password:"
295
+ end
296
+
297
+
298
+ opt.on('-X', '--request STR', String,
299
+ 'The request method to use') do |value|
300
+ options[:http_method] = value
301
+ end
302
+
303
+
304
+ opt.on('-x', '--proxy STR', String,
305
+ 'Use HTTP proxy on given port: host[:port]') do |value|
306
+ options[:proxy][:address], options[:proxy][:port] = value.split ":", 2
307
+ end
308
+
309
+ opt.separator nil
310
+ end
311
+
312
+ opts.parse! argv
313
+
314
+ unless $stdin.tty?
315
+ io = StringIO.new $stdin.read
316
+ options[:uris] << io
317
+ end
318
+
319
+ options[:uris].concat argv
320
+ options[:uris].slice!(2..-1)
321
+
322
+ argv.clear
323
+
324
+ raise OptionParser::MissingArgument, "You must enter at least one URI" if
325
+ options[:uris].empty?
326
+
327
+ options
328
+
329
+ rescue => e
330
+ $stderr << "\nError: #{e.message}\n"
331
+ $stderr << "See 'kronk --help' for usage\n\n"
332
+ exit 1
333
+ end
334
+
335
+
336
+ ##
337
+ # Searches ARGV and returns data paths to add or exclude in the diff.
338
+ # Returns the array [only_paths, except_paths]
339
+
340
+ def self.parse_data_path_args options, argv
341
+ return options unless argv.include? "--"
342
+
343
+ data_paths = argv.slice! argv.index("--")..-1
344
+ data_paths.shift
345
+
346
+ data_paths.each do |path|
347
+ if path[0,1] == "-"
348
+ (options[:ignore_data] ||= []) << path[1..-1]
349
+
350
+ elsif path[0,2] == ":-"
351
+ (options[:ignore_data_with] ||= []) << path[2..-1]
352
+
353
+ elsif path[0,1] == ":"
354
+ (options[:only_data_with] ||= []) << path[1..-1]
355
+
356
+ else
357
+ (options[:only_data] ||= []) << path
358
+ end
359
+ end
360
+
361
+ options
362
+ end
363
+
364
+
365
+ ##
366
+ # Ask the user for a password from stdin the command line.
367
+
368
+ def self.query_password str=nil
369
+ $stderr << "#{(str || "Password:")} "
370
+ system "stty -echo"
371
+ password = $stdin.gets.chomp
372
+ ensure
373
+ system "stty echo"
374
+ $stderr << "\n"
375
+ password
376
+ end
377
+
378
+
379
+ ##
380
+ # Runs the kronk command with the given terminal args.
381
+
382
+ def self.run argv=ARGV
383
+
384
+ options = parse_args argv
385
+
386
+ begin
387
+ Kronk.load_config
388
+
389
+ rescue Errno::ENOENT
390
+ make_config_file
391
+
392
+ $stderr << "\nNo config file was found.\n"
393
+ $stderr << "Created default config in #{DEFAULT_CONFIG_FILE}\n"
394
+ $stderr << "Edit file if necessary and try again.\n"
395
+ exit 2
396
+
397
+ rescue Errno::ENOTDIR
398
+ move_config_file
399
+
400
+ $stderr << "\nOld config file was moved to #{DEFAULT_CONFIG_FILE}\n"
401
+ $stderr << "Edit file if necessary and try again.\n"
402
+ exit 2
403
+ end
404
+
405
+ Kronk.load_cookie_jar
406
+
407
+ load_requires options[:requires]
408
+
409
+ at_exit do
410
+ Kronk.save_cookie_jar
411
+ Kronk.save_history
412
+ end
413
+
414
+ trap 'INT' do
415
+ exit 2
416
+ end
417
+
418
+
419
+ options[:cache_response] =
420
+ Kronk.config[:cache_file] if Kronk.config[:cache_file]
421
+
422
+ uri1, uri2 = options.delete :uris
423
+
424
+ if uri1 && uri2
425
+ diff = Kronk.compare uri1, uri2, options
426
+ puts "#{diff.formatted}\n" unless Kronk.config[:brief]
427
+
428
+ if Kronk.config[:verbose] || Kronk.config[:brief]
429
+ $stdout << "Found #{diff.count} diff(s).\n"
430
+ end
431
+
432
+ exit 1 if diff.count > 0
433
+
434
+ else
435
+ out = Kronk.retrieve_data_string uri1, options
436
+ out = Diff.insert_line_nums out if Kronk.config[:show_lines]
437
+ puts out
438
+ end
439
+
440
+ rescue Request::Exception, Response::MissingParser, Errno::ECONNRESET => e
441
+ $stderr << "\nError: #{e.message}\n"
442
+ exit 2
443
+ end
444
+
445
+
446
+ ##
447
+ # Print string only if verbose
448
+
449
+ def self.verbose str
450
+ $stdout << "#{str}\n" if Kronk.config[:verbose]
451
+ end
452
+
453
+
454
+ ##
455
+ # Write a warning to stderr.
456
+
457
+ def self.warn str
458
+ $stderr << "Warning: #{str}\n"
459
+ end
460
+
461
+
462
+ ##
463
+ # Returns true if kronk is running on ruby for windows.
464
+
465
+ def self.windows?
466
+ !!(RUBY_PLATFORM.downcase =~ /mswin|mingw|cygwin/)
467
+ end
468
+ end
469
+ end