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, InvalidCertificate, MissingDependency, Multipart, MultipartIO, NotFoundError, OAuthConfig, ParserError, Player, PlistParser, QueueRunner, Request, Response, TimeoutError, XMLParser, YamlParser

Constant Summary collapse

VERSION =

This gem’s version.

'1.9.6'
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_OAUTH_FILE =

Default file where oauth credentials are stored.

File.join CONFIG_DIR, "oauth"
DEFAULT_OAUTH_LIST_FILE =

Default file of oauth names. (Used for autocomplete)

File.join CONFIG_DIR, "oauth-list"
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
RUBY_ENGINE =
'ruby'
DEFAULT_USER_AGENT =

The default Kronk user agent.

"Kronk/#{VERSION} (#{RUBY_PLATFORM}; U; en-US; http://jcasts.me/kronk) \
#{RUBY_ENGINE}/#{RUBY_VERSION}p#{RUBY_PATCHLEVEL}"
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     => true,
  :context        => 3,
  :cookies_file   => DEFAULT_COOKIES_FILE,
  :default_host   => "http://localhost:3000",
  :diff_format    => 'color',
  :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

:oauth

Hash - :consumer_key, :token :consumer_secret, :token_secret

:proxy

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

: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



306
307
308
309
310
311
312
313
314
# File 'lib/kronk.rb', line 306

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.



280
281
282
# File 'lib/kronk.rb', line 280

def diff
  @diff
end

#optionsObject

Returns the value of attribute options.



280
281
282
# File 'lib/kronk.rb', line 280

def options
  @options
end

#responseObject

Returns the value of attribute response.



280
281
282
# File 'lib/kronk.rb', line 280

def response
  @response
end

#responsesObject

Returns the value of attribute responses.



280
281
282
# File 'lib/kronk.rb', line 280

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.



187
188
189
# File 'lib/kronk.rb', line 187

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)


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

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

.configObject

Read the Kronk config hash.



51
52
53
# File 'lib/kronk.rb', line 51

def self.config
  @config ||= DEFAULT_CONFIG
end

Returns the kronk cookie jar.



195
196
197
# File 'lib/kronk.rb', line 195

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


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
151
152
153
154
155
156
157
158
159
160
161
162
163
# File 'lib/kronk.rb', line 107

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.



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

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.



67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
# File 'lib/kronk.rb', line 67

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

  @oauth_config = Kronk::OAuthConfig.load_file(DEFAULT_OAUTH_FILE) if File.file?(DEFAULT_OAUTH_FILE)
end

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



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

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.



249
250
251
# File 'lib/kronk.rb', line 249

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

.oauth_configObject

Returns the Kronk::OAuthConfig instance



59
60
61
# File 'lib/kronk.rb', line 59

def self.oauth_config
  @oauth_config ||= Kronk::OAuthConfig.new
end

.parser_for(content_type) ⇒ Object

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



169
170
171
172
173
174
175
176
177
178
179
180
# File 'lib/kronk.rb', line 169

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)


275
276
277
# File 'lib/kronk.rb', line 275

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

Save the cookie jar to file.



215
216
217
218
219
220
# File 'lib/kronk.rb', line 215

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.



237
238
239
240
241
242
243
# File 'lib/kronk.rb', line 237

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.



257
258
259
# File 'lib/kronk.rb', line 257

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.



324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
# File 'lib/kronk.rb', line 324

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

#oauth_options_for_uri(uri) ⇒ Object

Returns oauth config for the given uri



422
423
424
425
426
# File 'lib/kronk.rb', line 422

def oauth_options_for_uri uri
  uri = "http://#{uri}" unless uri.start_with?("http")
  uri_host = URI.parse(uri.to_s).host
  return self.class.oauth_config.get_active_for_host(uri_host)
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.



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
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
# File 'lib/kronk.rb', line 434

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, :oauth
        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

  if !out_opts[:oauth]
    oauth = oauth_options_for_uri(uri)
    out_opts[:oauth] = oauth if oauth
  end

  out_opts
end

#request(uri) ⇒ Object

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



353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
# File 'lib/kronk.rb', line 353

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 OpenSSL::SSL::SSLError
  raise InvalidCertificate, "The remote SSL certificate could not be trusted"

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.



393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
# File 'lib/kronk.rb', line 393

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, opts

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

  else

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

    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