Class: Kronk

Inherits:
Object
  • Object
show all
Defined in:
lib/kronk.rb,
lib/kronk/cmd.rb,
lib/kronk/diff.rb,
lib/kronk/http.rb,
lib/kronk/test.rb,
lib/kronk/player.rb,
lib/kronk/request.rb,
lib/kronk/response.rb,
lib/kronk/constants.rb,
lib/kronk/multipart.rb,
lib/kronk/player/tsv.rb,
lib/kronk/xml_parser.rb,
lib/kronk/buffered_io.rb,
lib/kronk/data_string.rb,
lib/kronk/yaml_parser.rb,
lib/kronk/multipart_io.rb,
lib/kronk/player/suite.rb,
lib/kronk/plist_parser.rb,
lib/kronk/queue_runner.rb,
lib/kronk/player/stream.rb,
lib/kronk/player/download.rb,
lib/kronk/test/assertions.rb,
lib/kronk/player/benchmark.rb,
lib/kronk/diff/ascii_format.rb,
lib/kronk/diff/color_format.rb,
lib/kronk/player/input_reader.rb,
lib/kronk/test/helper_methods.rb,
lib/kronk/player/request_parser.rb

Defined Under Namespace

Modules: Test Classes: BufferedIO, Cmd, DataString, Diff, Error, HTTP, HTTPBadResponse, MissingDependency, Multipart, MultipartIO, NotFoundError, ParserError, Player, PlistParser, QueueRunner, Request, Response, TimeoutError, XMLParser, YamlParser

Constant Summary collapse

VERSION =

This gem’s version.

'1.9.0'
CONFIG_DIR =

Config directory.

File.expand_path "~/.kronk"
DEFAULT_CONFIG_FILE =

Default config file to load. Defaults to ~/.kronk.

File.join CONFIG_DIR, "rc"
DEFAULT_CACHE_FILE =

Default cache file.

File.join CONFIG_DIR, "cache"
DEFAULT_COOKIES_FILE =

Default cookies file.

File.join CONFIG_DIR, "cookies"
DEFAULT_HISTORY_FILE =

Default file with history of unique URIs. (Used for autocomplete)

File.join CONFIG_DIR, "history"
DEFAULT_CONTENT_TYPES =

Default Content-Type header to parser mapping.

{
  'js'    => 'JSON',
  'json'  => 'JSON',
  'plist' => 'PlistParser',
  'xml'   => 'XMLParser',
  'yaml'  => 'YamlParser',
  'yml'   => 'YamlParser'
}
DEEP_MERGE =

Recursive Hash merge proc.

proc do |key,v1,v2|
  Hash === v1 && Hash === v2 ? v1.merge(v2,&DEEP_MERGE) : v2
end
DEFAULT_USER_AGENT =

The default Kronk user agent.

"Kronk/#{VERSION} (http://github.com/yaksnrainbows/kronk)"
USER_AGENTS =

Aliases for various user-agents. Thanks Mechanize! :)

{
  'iphone'          =>
  "Mozilla/5.0 (iPhone; U; CPU like Mac OS X; en) AppleWebKit/420+ (KHTML, like Gecko) Version/3.0 Mobile/1C28 Safari/419.3",
  'linux_firefox'   =>
  "Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.9.2.1) Gecko/20100122 firefox/3.6.1",
  'linux_mozilla'   =>
  "Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.4) Gecko/20030624",
  'mac_mozilla'     =>
  "Mozilla/5.0 (Macintosh; U; PPC Mac OS X Mach-O; en-US; rv:1.4a) Gecko/20030401",
  'linux_konqueror' =>
  "Mozilla/5.0 (compatible; Konqueror/3; Linux)",
  'mac_firefox'     =>
  "Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10.6; en-US; rv:1.9.2) Gecko/20100115 Firefox/3.6",
  'mac_safari'      =>
  "Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10_6_2; de-at) AppleWebKit/531.21.8 (KHTML, like Gecko) Version/4.0.4 Safari/531.21.10",
  'win_ie6'         =>
  "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1)",
  'win_ie7'         =>
  "Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 5.1; .NET CLR 1.1.4322; .NET CLR 2.0.50727)",
  'win_mozilla'     =>
  "Mozilla/5.0 (Windows; U; Windows NT 5.0; en-US; rv:1.4b) Gecko/20030516 Mozilla Firebird/0.6"
}
DEFAULT_CONFIG =

Default config to use.

{
  :content_types  => DEFAULT_CONTENT_TYPES.dup,
  :cache_file     => DEFAULT_CACHE_FILE,
  :color_data     => false,
  :context        => 3,
  :cookies_file   => DEFAULT_COOKIES_FILE,
  :default_host   => "http://localhost:3000",
  :diff_format    => 'ascii',
  :history_file   => DEFAULT_HISTORY_FILE,
  :indentation    => 1,
  :max_history    => 100,
  :requires       => [],
  :show_lines     => false,
  :uri_options    => {},
  :use_cookies    => true,
  :user_agents    => USER_AGENTS.dup
}
RESCUABLE =

Errors to rescue from the Cmd or from Player.

[
  Kronk::Error, Timeout::Error,
  SocketError, SystemCallError, URI::InvalidURIError
]

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(opts = {}) ⇒ Kronk

Create a Kronk instance to keep references to all request, response, and diff data.

Supports the following options:

:data

Hash/String - the data to pass to the http request

:query

Hash/String - the data to append to the http request path

:follow_redirects

Integer/Boolean - number of times to follow redirects

:headers

Hash - extra headers to pass to the request

:http_method

Symbol - the http method to use; defaults to :get

:user_agent

String - user agent string or alias; defaults to ‘kronk’

:auth

Hash - must contain :username and :password; defaults to nil

:proxy

Hash/String - http proxy to use; defaults to nil

:keep_indicies

Boolean - indicies of modified arrays display as hashes

:show_headers

Boolean/String/Array - which headers to show in output

:parser

Object/String - the parser to use for the body; default nil

:raw

Boolean - run diff on raw strings

:transform

Array - Action/path(s) pairs to modify data.

Deprecated Options:

:ignore_data

String/Array - Removes the data from given data paths

:only_data

String/Array - Extracts the data from given data paths



293
294
295
296
297
298
299
300
301
# File 'lib/kronk.rb', line 293

def initialize opts={}
  @options   = opts
  @diff      = nil
  @responses = []
  @response  = nil

  meth = method(:request_explicit)
  @app = Kronk.middleware.inject(meth){|app, mware| mware.new(app) }
end

Instance Attribute Details

#diffObject

Returns the value of attribute diff.



267
268
269
# File 'lib/kronk.rb', line 267

def diff
  @diff
end

#optionsObject

Returns the value of attribute options.



267
268
269
# File 'lib/kronk.rb', line 267

def options
  @options
end

#responseObject

Returns the value of attribute response.



267
268
269
# File 'lib/kronk.rb', line 267

def response
  @response
end

#responsesObject

Returns the value of attribute responses.



267
268
269
# File 'lib/kronk.rb', line 267

def responses
  @responses
end

Class Method Details

.clear_cookies!Object

Deletes all cookies from the runtime. If Kronk.run is in use, will write the change to the cookies file as well.



174
175
176
# File 'lib/kronk.rb', line 174

def self.clear_cookies!
  @cookie_jar = CookieJar::Jar.new
end

.compare(uri1, uri2, opts = {}) ⇒ Object

See Kronk#compare. Short for:

Kronk.new(opts).compare(uri1, uri2)


253
254
255
# File 'lib/kronk.rb', line 253

def self.compare uri1, uri2, opts={}
  new(opts).compare uri1, uri2
end

.configObject

Read the Kronk config hash.



48
49
50
# File 'lib/kronk.rb', line 48

def self.config
  @config ||= DEFAULT_CONFIG
end

Returns the kronk cookie jar.



182
183
184
# File 'lib/kronk.rb', line 182

def self.cookie_jar
  @cookie_jar ||= load_cookie_jar
end

.find_const(name_or_file, case_insensitive = false) ⇒ Object

Find a fully qualified ruby namespace/constant. Supports file paths, constants, or path:Constant combinations:

Kronk.find_const "json"
#=> JSON

Kronk.find_const "namespace/mylib"
#=> Namespace::MyLib

Kronk.find_const "path/to/somefile.rb:Namespace::MyLib"
#=> Namespace::MyLib


94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
# File 'lib/kronk.rb', line 94

def self.find_const name_or_file, case_insensitive=false
  return name_or_file unless String === name_or_file

  if name_or_file =~ /[^:]:([^:]+)$/
    req_file = $1
    i        = $1.length + 2
    const    = name_or_file[0..-i]

    begin
      require req_file
    rescue LoadError
      require File.expand_path(req_file)
    end

    find_const const

  elsif name_or_file.include? File::SEPARATOR
    begin
      require name_or_file
    rescue LoadError
      require File.expand_path(name_or_file)
    end

    namespace = File.basename name_or_file, ".rb"
    consts    = File.dirname(name_or_file).split(File::SEPARATOR)
    consts   << namespace

    name = ""
    until consts.empty?
      name  = "::" << consts.pop.to_s << name
      const = find_const name, true rescue nil
      return const if const
    end

    raise NameError, "no constant match for #{name_or_file}"

  else
    consts = name_or_file.to_s.split "::"
    curr = self

    until consts.empty? do
      const = consts.shift
      next if const.to_s.empty?

      if case_insensitive
        const.gsub!(/(^|[\-_.]+)([a-z0-9])/i){|m| m[-1,1].upcase}
        const = (curr.constants | Object.constants).find do |c|
          c.to_s.downcase == const.to_s.downcase
        end
      end

      curr = curr.const_get const.to_s
    end

    curr
  end
end

.historyObject

Returns the Kronk history array of accessed URLs.



213
214
215
216
217
218
# File 'lib/kronk.rb', line 213

def self.history
  path = self.config[:history_file]
  @history ||= File.read(path).split($/) if File.file?(path)
  @history ||= []
  @history
end

.load_config(filepath = DEFAULT_CONFIG_FILE) ⇒ Object

Load a config file and apply to Kronk.config.



56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
# File 'lib/kronk.rb', line 56

def self.load_config filepath=DEFAULT_CONFIG_FILE
  conf = YAML.load_file filepath

  conf.each do |key, value|
    skey = key.to_sym

    case skey
    when :content_types, :user_agents
      conf[key].each{|k,v| self.config[skey][k.to_s] = v }

    when :requires
      self.config[skey].concat Array(value)

    when :uri_options
      conf[key].each do |matcher, opts|
        self.config[skey][matcher.to_s] = opts
        opts.keys.each{|k| opts[k.to_sym] = opts.delete(k) if String === k}
      end

    else
      self.config[skey] = value
    end
  end
end

Load the saved cookies file. Defaults to Kronk::config.



190
191
192
193
194
195
196
# File 'lib/kronk.rb', line 190

def self.load_cookie_jar file=nil
  file ||= config[:cookies_file]
  @cookie_jar = YAML.load_file file if File.file? file
  @cookie_jar ||= CookieJar::Jar.new
  @cookie_jar.expire_cookies
  @cookie_jar
end

.middlewareObject

Returns an array of middleware to use around requests.



236
237
238
# File 'lib/kronk.rb', line 236

def self.middleware
  @middleware ||= []
end

.parser_for(content_type) ⇒ Object

Returns the config-defined parser class for a given content type.



156
157
158
159
160
161
162
163
164
165
166
167
# File 'lib/kronk.rb', line 156

def self.parser_for content_type
  parser_pair =
    config[:content_types].select do |key, value|
      (content_type =~ %r{#{key}([^\w]|$)}) && value
    end.to_a

  return if parser_pair.empty?

  parser = parser_pair[0][1]
  parser = find_const parser if String === parser || Symbol === parser
  parser
end

.request(uri, opts = {}) ⇒ Object

See Kronk#request. Short for:

Kronk.new(opts).request(uri)


262
263
264
# File 'lib/kronk.rb', line 262

def self.request uri, opts={}
  new(opts).request uri
end

Save the cookie jar to file.



202
203
204
205
206
207
# File 'lib/kronk.rb', line 202

def self.save_cookie_jar file=nil
  file ||= config[:cookies_file]
  File.open(file, "w") do |f|
    f.write @cookie_jar.to_yaml
  end
end

.save_historyObject

Writes the URL history to the history file.



224
225
226
227
228
229
230
# File 'lib/kronk.rb', line 224

def self.save_history
  history_str = self.history.uniq[0..self.config[:max_history]].join($/)

  File.open self.config[:history_file], "w" do |file|
    file.write history_str
  end
end

.use(mware) ⇒ Object

Assign middleware to use.



244
245
246
# File 'lib/kronk.rb', line 244

def self.use mware
  self.middleware.unshift mware
end

Instance Method Details

#compare(uri1, uri2) ⇒ Object

Make requests, parse the responses and compare the data. Query arguments may be set to the special value :cache to use the last live http response retrieved.

Assigns @response, @responses, @diff. Returns the Diff instance.



311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
# File 'lib/kronk.rb', line 311

def compare uri1, uri2
  str1 = str2 = ""
  res1 = res2 = nil

  t1 = Thread.new do
        res1 = request uri1
        str1 = res1.stringify
       end

  t2 = Thread.new do
        res2 = request uri2
        str2 = res2.stringify
       end

  t1.join
  t2.join

  @responses = [res1, res2]
  @response  = res2

  opts = {:labels => [res1.uri, res2.uri]}.merge @options
  @diff = Diff.new str1, str2, opts
end

#options_for_uri(uri) ⇒ Object

Returns merged config-defined options for a given uri. Values in cmd_opts take precedence. Returns cmd_opts Hash if none found.



407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
# File 'lib/kronk.rb', line 407

def options_for_uri uri
  out_opts = @options.dup

  Kronk.config[:uri_options].each do |matcher, opts|
    next unless (uri == matcher || uri =~ %r{#{matcher}}) && Hash === opts

    opts.each do |key, val|
      if out_opts[key].nil?
        out_opts[key] = val
        next
      end

      case key

      # Hash or uri query String
      when :data, :query, :form, :form_upload
        val = Request.parse_nested_query val if String === val

        out_opts[key] = Request.parse_nested_query out_opts[key] if
          String === out_opts[key]

        out_opts[key] = val.merge out_opts[key], &DEEP_MERGE

      # Hashes
      when :headers, :auth
        out_opts[key] = val.merge out_opts[key]

      # Proxy hash or String
      when :proxy
        if Hash === val && Hash === out_opts[key]
          out_opts[key] = val.merge out_opts[key]

        elsif Hash === val && String === out_opts[key]
          val[:address] = out_opts[key]
          out_opts[key] = val

        elsif String === val && Hash === out_opts[key]
          out_opts[key][:address] ||= val
        end

      # Response headers - Boolean, String, or Array
      when :show_headers
        next if out_opts.has_key?(key) &&
                (out_opts[key].class != Array || val == true || val == false)
        out_opts[key] = (val == true || val == false) ? val :
                                    Array(out_opts[key]) | Array(val)

      # String or Array
      when :only_data, :ignore_data
        out_opts[key] = Array(out_opts[key]) | Array(val)

      # Array concatination
      when :transform
        out_opts[key] = Array(val).concat Array(out_opts[key])
      end
    end
  end

  out_opts
end

#request(uri) ⇒ Object

Returns a Response instance from a url, file, or IO as a String. Assigns @response, @responses, @diff.



340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
# File 'lib/kronk.rb', line 340

def request uri
  options = Kronk.config[:no_uri_options] ? @options : options_for_uri(uri)
  options.merge!(:uri => uri)

  resp = @app.call options

  rdir = options[:follow_redirects]
  while resp.redirect? && (rdir == true || rdir.to_s.to_i > 0)
    uri = resp.location
    Cmd.verbose "Following redirect to #{resp.location}" if defined?(Cmd)
    resp = resp.follow_redirect options_for_uri(resp.location)
    rdir = rdir - 1 if Fixnum === rdir
  end

  resp.parser         = options[:parser] if options[:parser]
  resp.stringify_opts = options

  @responses = [resp]
  @response  = resp
  @diff      = nil

  resp

rescue Errno::ENOENT => e
  raise NotFoundError, e.message

rescue SocketError, Errno::ECONNREFUSED => e
  raise NotFoundError, "#{uri} could not be found (#{e.class})"

rescue Timeout::Error
  raise TimeoutError, "#{uri} took too long to respond"
end

#request_explicit(opts) ⇒ Object

Request without autofilling options.



377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
# File 'lib/kronk.rb', line 377

def request_explicit opts
  uri = opts.delete(:uri)

  if IO === uri || StringIO === uri || BufferedIO === uri
    Cmd.verbose "Reading IO #{uri}" if defined?(Cmd)
    Response.new uri, options

  elsif File.file? uri.to_s
    Cmd.verbose "Reading file:  #{uri}\n" if defined?(Cmd)
    Response.read_file uri, options

  else
    req = Request.new uri, options
    Cmd.verbose "Retrieving URL:  #{req.uri}\n" if defined?(Cmd)
    resp = req.retrieve options

    hist_uri = req.uri.to_s[0..-req.uri.request_uri.length]
    hist_uri = hist_uri[(req.uri.scheme.length + 3)..-1]
    Kronk.history << hist_uri

    resp
  end
end