kronk 1.2.5 → 1.3.0

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