htty 1.4.1 → 1.5.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.
Files changed (120) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +2 -1
  3. data/.rspec +2 -0
  4. data/.travis.yml +21 -3
  5. data/.yardopts +1 -1
  6. data/Gemfile +2 -2
  7. data/Guardfile +12 -10
  8. data/History.markdown +14 -0
  9. data/{MIT-LICENSE.markdown → License.markdown} +1 -1
  10. data/README.markdown +50 -65
  11. data/Rakefile +2 -2
  12. data/htty.gemspec +2 -2
  13. data/lib/htty/cli.rb +59 -74
  14. data/lib/htty/cli/command.rb +21 -20
  15. data/lib/htty/cli/commands/body_edit.rb +89 -0
  16. data/lib/htty/cli/commands/query_add.rb +3 -0
  17. data/lib/htty/cli/commands/query_set.rb +3 -0
  18. data/lib/htty/cli/commands/userinfo_set.rb +8 -14
  19. data/lib/htty/cli/display.rb +14 -12
  20. data/lib/htty/cli/input_device.rb +75 -0
  21. data/lib/htty/cli/url_escaping.rb +2 -2
  22. data/lib/htty/headers.rb +81 -0
  23. data/lib/htty/no_header_error.rb +10 -0
  24. data/lib/htty/payload.rb +22 -9
  25. data/lib/htty/request.rb +67 -43
  26. data/lib/htty/requests_util.rb +22 -8
  27. data/lib/htty/response.rb +20 -1
  28. data/lib/htty/uri.rb +16 -0
  29. data/lib/htty/version.rb +1 -1
  30. data/spec/integration/htty/cli/commands/query_add_spec.rb +7 -1
  31. data/spec/integration/htty/cli/commands/query_remove_spec.rb +19 -1
  32. data/spec/integration/htty/cli/commands/query_set_spec.rb +9 -3
  33. data/spec/integration/htty/cli/commands/query_unset_spec.rb +8 -2
  34. data/spec/spec_helper.rb +69 -0
  35. data/spec/unit/htty/cli/commands/address_spec.rb +1 -1
  36. data/spec/unit/htty/cli/commands/body_clear_spec.rb +1 -1
  37. data/spec/unit/htty/cli/commands/body_edit_spec.rb +18 -0
  38. data/spec/unit/htty/cli/commands/body_request_spec.rb +1 -1
  39. data/spec/unit/htty/cli/commands/body_response_spec.rb +1 -1
  40. data/spec/unit/htty/cli/commands/body_set_spec.rb +1 -1
  41. data/spec/unit/htty/cli/commands/body_unset_spec.rb +1 -1
  42. data/spec/unit/htty/cli/commands/cd_spec.rb +1 -1
  43. data/spec/unit/htty/cli/commands/cookie_add_spec.rb +1 -1
  44. data/spec/unit/htty/cli/commands/cookie_remove_spec.rb +1 -1
  45. data/spec/unit/htty/cli/commands/cookies_add_spec.rb +1 -1
  46. data/spec/unit/htty/cli/commands/cookies_clear_spec.rb +1 -1
  47. data/spec/unit/htty/cli/commands/cookies_remove_all_spec.rb +1 -1
  48. data/spec/unit/htty/cli/commands/cookies_remove_spec.rb +1 -1
  49. data/spec/unit/htty/cli/commands/cookies_spec.rb +1 -1
  50. data/spec/unit/htty/cli/commands/cookies_use_spec.rb +1 -1
  51. data/spec/unit/htty/cli/commands/delete_spec.rb +1 -1
  52. data/spec/unit/htty/cli/commands/exit_spec.rb +1 -1
  53. data/spec/unit/htty/cli/commands/follow_spec.rb +1 -1
  54. data/spec/unit/htty/cli/commands/form_add_spec.rb +1 -1
  55. data/spec/unit/htty/cli/commands/form_clear_spec.rb +1 -1
  56. data/spec/unit/htty/cli/commands/form_remove_all_spec.rb +1 -1
  57. data/spec/unit/htty/cli/commands/form_remove_spec.rb +1 -1
  58. data/spec/unit/htty/cli/commands/form_spec.rb +1 -1
  59. data/spec/unit/htty/cli/commands/fragment_clear_spec.rb +1 -1
  60. data/spec/unit/htty/cli/commands/fragment_set_spec.rb +1 -1
  61. data/spec/unit/htty/cli/commands/fragment_unset_spec.rb +1 -1
  62. data/spec/unit/htty/cli/commands/get_spec.rb +1 -1
  63. data/spec/unit/htty/cli/commands/header_set_spec.rb +1 -1
  64. data/spec/unit/htty/cli/commands/header_unset_spec.rb +1 -1
  65. data/spec/unit/htty/cli/commands/headers_clear_spec.rb +1 -1
  66. data/spec/unit/htty/cli/commands/headers_request_spec.rb +1 -1
  67. data/spec/unit/htty/cli/commands/headers_response_spec.rb +1 -1
  68. data/spec/unit/htty/cli/commands/headers_set_spec.rb +1 -1
  69. data/spec/unit/htty/cli/commands/headers_unset_all_spec.rb +1 -1
  70. data/spec/unit/htty/cli/commands/headers_unset_spec.rb +1 -1
  71. data/spec/unit/htty/cli/commands/help_spec.rb +1 -1
  72. data/spec/unit/htty/cli/commands/history_spec.rb +1 -1
  73. data/spec/unit/htty/cli/commands/history_verbose_spec.rb +1 -1
  74. data/spec/unit/htty/cli/commands/host_set_spec.rb +1 -1
  75. data/spec/unit/htty/cli/commands/http_delete_spec.rb +1 -1
  76. data/spec/unit/htty/cli/commands/http_get_spec.rb +1 -1
  77. data/spec/unit/htty/cli/commands/http_head_spec.rb +1 -1
  78. data/spec/unit/htty/cli/commands/http_options_spec.rb +1 -1
  79. data/spec/unit/htty/cli/commands/http_patch_spec.rb +1 -1
  80. data/spec/unit/htty/cli/commands/http_post_spec.rb +1 -1
  81. data/spec/unit/htty/cli/commands/http_put_spec.rb +1 -1
  82. data/spec/unit/htty/cli/commands/http_trace_spec.rb +1 -1
  83. data/spec/unit/htty/cli/commands/patch_spec.rb +1 -1
  84. data/spec/unit/htty/cli/commands/path_set_spec.rb +1 -1
  85. data/spec/unit/htty/cli/commands/port_set_spec.rb +1 -1
  86. data/spec/unit/htty/cli/commands/post_spec.rb +1 -1
  87. data/spec/unit/htty/cli/commands/put_spec.rb +1 -1
  88. data/spec/unit/htty/cli/commands/query_add_spec.rb +1 -1
  89. data/spec/unit/htty/cli/commands/query_clear_spec.rb +1 -1
  90. data/spec/unit/htty/cli/commands/query_remove_spec.rb +1 -1
  91. data/spec/unit/htty/cli/commands/query_set_spec.rb +1 -1
  92. data/spec/unit/htty/cli/commands/query_unset_all_spec.rb +1 -1
  93. data/spec/unit/htty/cli/commands/query_unset_spec.rb +1 -1
  94. data/spec/unit/htty/cli/commands/quit_spec.rb +1 -1
  95. data/spec/unit/htty/cli/commands/reuse_spec.rb +1 -1
  96. data/spec/unit/htty/cli/commands/scheme_set_spec.rb +1 -1
  97. data/spec/unit/htty/cli/commands/shared_examples_for_commands.rb +55 -0
  98. data/spec/unit/htty/cli/commands/ssl_verification_off_spec.rb +1 -1
  99. data/spec/unit/htty/cli/commands/ssl_verification_on_spec.rb +1 -1
  100. data/spec/unit/htty/cli/commands/ssl_verification_spec.rb +1 -1
  101. data/spec/unit/htty/cli/commands/status_spec.rb +1 -1
  102. data/spec/unit/htty/cli/commands/undo_spec.rb +1 -1
  103. data/spec/unit/htty/cli/commands/userinfo_clear_spec.rb +1 -1
  104. data/spec/unit/htty/cli/commands/userinfo_set_spec.rb +23 -1
  105. data/spec/unit/htty/cli/commands/userinfo_unset_spec.rb +1 -1
  106. data/spec/unit/htty/cli/display_spec.rb +84 -0
  107. data/spec/unit/htty/cli_spec.rb +1 -1
  108. data/spec/unit/htty/command.rb +47 -0
  109. data/spec/unit/htty/{ordered_hash_spec.rb → headers_spec.rb} +4 -4
  110. data/spec/unit/htty/payload_spec.rb +60 -0
  111. data/spec/unit/htty/preferences_spec.rb +1 -1
  112. data/spec/unit/htty/request_follow_spec.rb +94 -0
  113. data/spec/unit/htty/request_spec.rb +5 -187
  114. data/spec/unit/htty/request_userinfo_spec.rb +208 -0
  115. data/spec/unit/htty/session_spec.rb +1 -1
  116. data/spec/unit/htty/shared_examples_for_requests.rb +32 -0
  117. data/spec/unit/htty/url_escaping.rb +70 -0
  118. data/spec/unit/htty/version_spec.rb +1 -1
  119. metadata +43 -30
  120. data/lib/htty/ordered_hash.rb +0 -68
@@ -27,18 +27,18 @@ class HTTY::CLI::Command
27
27
  def self.build_for(command_line, attributes={})
28
28
  command_line_regexp = make_command_line_regexp
29
29
  if (match = (command_line_regexp.match(command_line)))
30
- all_attributes = attributes
31
- if (arguments = match.captures[0])
32
- begin
33
- split_arguments = arguments.strip.shellsplit
34
- rescue ArgumentError
35
- return :unclosed_quote
30
+ begin
31
+ if (arguments = match.captures[0])
32
+ attributes = attributes.merge(
33
+ :arguments => sanitize_arguments(arguments.strip.shellsplit)
34
+ )
36
35
  end
37
- return new(attributes.merge(:arguments => split_arguments))
36
+ rescue ArgumentError
37
+ :unclosed_quote
38
+ else
39
+ new(attributes)
38
40
  end
39
- return new(attributes)
40
41
  end
41
- nil
42
42
  end
43
43
 
44
44
  # Returns the name of a category under which help for the command should
@@ -114,14 +114,19 @@ class HTTY::CLI::Command
114
114
  Array(alias_for)
115
115
  end
116
116
 
117
+ # Escape, split, chop, ... arguments before they are used to build the
118
+ # command
119
+ def self.sanitize_arguments(arguments)
120
+ arguments
121
+ end
122
+
117
123
  protected
118
124
 
119
125
  # Displays a notice if cookies are cleared on the specified _request_ in the
120
126
  # course of the block.
121
127
  def self.notify_if_cookies_cleared(request)
122
- had_cookies = cookies?(request)
123
128
  changed_request = yield
124
- puts notice('Cookies cleared') if had_cookies && !cookies?(changed_request)
129
+ puts notice('Cookies cleared') if request.cookies? && !changed_request.cookies?
125
130
  changed_request
126
131
  end
127
132
 
@@ -140,10 +145,6 @@ private
140
145
  "(?:#{text[0...char_length]}#{rest})?"
141
146
  end
142
147
 
143
- def self.cookies?(request)
144
- !request.cookies.empty?
145
- end
146
-
147
148
  def self.make_command_line_regexp
148
149
  pattern = Regexp.escape(command_line).gsub(/\\\[(.+)\\\]/) do |optional|
149
150
  completion_optional($1)
@@ -203,11 +204,11 @@ protected
203
204
  # Yields the last request in #session. If the block returns a different
204
205
  # request, it is added to the requests of #session.
205
206
  def add_request_if_new
206
- requests = session.requests
207
- last_request = requests.last
208
- unless (new_request = yield(last_request)).equal?(last_request)
209
- requests << new_request
210
- end
207
+ last_request = session.requests.last
208
+ maybe_next_request = yield(last_request)
209
+ session.requests << maybe_next_request unless (
210
+ maybe_next_request.nil? or maybe_next_request.equal?(last_request)
211
+ )
211
212
  self
212
213
  end
213
214
 
@@ -0,0 +1,89 @@
1
+ require 'tempfile'
2
+ require File.expand_path("#{File.dirname __FILE__}/../command")
3
+ require File.expand_path("#{File.dirname __FILE__}/../display")
4
+ require File.expand_path("#{File.dirname __FILE__}/body_request")
5
+ require File.expand_path("#{File.dirname __FILE__}/body_unset")
6
+ require File.expand_path("#{File.dirname __FILE__}/body_set")
7
+
8
+ module HTTY; end
9
+
10
+ class HTTY::CLI; end
11
+
12
+ module HTTY::CLI::Commands; end
13
+
14
+ # Encapsulates the _body-edit_ command.
15
+ class HTTY::CLI::Commands::BodyEdit < HTTY::CLI::Command
16
+
17
+ include HTTY::CLI::Display
18
+
19
+ # Returns the name of a category under which help for the _body-edit_ command
20
+ # should appear.
21
+ def self.category
22
+ 'Building Requests'
23
+ end
24
+
25
+ # Returns the help text for the _body-edit_ command.
26
+ def self.help
27
+ 'Sets the body of the request using a text editor'
28
+ end
29
+
30
+ # Returns the extended help text for the _body-edit_ command.
31
+ def self.help_extended
32
+ <<-EOH.gsub(/^(?: |\t)+/, '')
33
+ Sets the body content used for the request. Does not communicate with
34
+ the host.
35
+
36
+ Uses the editor specified in EDITOR environment variable. Your editor will
37
+ open with a temporary file, fill the file with the request body. When you
38
+ are done save the file and quit the editor. The exact content of the
39
+ temporary file will be used as the request body. The temporary file will be
40
+ removed after this command.
41
+
42
+ If you want to choose your editor, start htty with `EDITOR=vim htty`
43
+ replacing 'vim' with your editor of choice.
44
+ EOH
45
+ end
46
+
47
+ # Returns related command classes for the _body-edit_ command.
48
+ def self.see_also_commands
49
+ [ HTTY::CLI::Commands::BodyRequest,
50
+ HTTY::CLI::Commands::BodyUnset,
51
+ HTTY::CLI::Commands::BodySet
52
+ ]
53
+ end
54
+
55
+ # Performs the _body-edit_ command.
56
+ def perform
57
+ add_request_if_new do |request|
58
+ request.body_set(with_editor do |editor|
59
+ file = Tempfile.new('htty')
60
+ if last_request_body = session.requests.last.body
61
+ File.open(file.path, 'w+') {|f| f.write(last_request_body)}
62
+ end
63
+ result = system(editor + ' ' + file.path)
64
+ if not result
65
+ return empty_body_because("Unable to use '#{editor}' to edit request's body")
66
+ end
67
+ body = File.read(file.path)
68
+ file.unlink
69
+ body
70
+ end)
71
+ end
72
+ end
73
+
74
+ private
75
+
76
+ def with_editor
77
+ editor = ENV['EDITOR']
78
+ if editor.nil? || editor.empty?
79
+ return empty_body_because('EDITOR environment variable is not set.')
80
+ end
81
+ yield editor
82
+ end
83
+
84
+ def empty_body_because(reason)
85
+ puts notice(reason)
86
+ puts notice("See 'help body-edit' for further informations.")
87
+ empty_body = ''
88
+ end
89
+ end
@@ -60,6 +60,9 @@ class HTTY::CLI::Commands::QueryAdd < HTTY::CLI::Command
60
60
 
61
61
  # Performs the _query-add_ command.
62
62
  def perform
63
+ if arguments.empty?
64
+ raise ArgumentError, 'wrong number of arguments (0 for N)'
65
+ end
63
66
  add_request_if_new do |request|
64
67
  self.class.notify_if_cookies_cleared request do
65
68
  escaped_arguments = escape_or_warn_of_escape_sequences(arguments)
@@ -60,6 +60,9 @@ class HTTY::CLI::Commands::QuerySet < HTTY::CLI::Command
60
60
 
61
61
  # Performs the _query-set_ command.
62
62
  def perform
63
+ if arguments.empty?
64
+ raise ArgumentError, 'wrong number of arguments (0 for N)'
65
+ end
63
66
  add_request_if_new do |request|
64
67
  self.class.notify_if_cookies_cleared request do
65
68
  escaped_arguments = escape_or_warn_of_escape_sequences(arguments)
@@ -13,7 +13,7 @@ module HTTY::CLI::Commands; end
13
13
  # Encapsulates the _userinfo-set_ command.
14
14
  class HTTY::CLI::Commands::UserinfoSet < HTTY::CLI::Command
15
15
 
16
- include HTTY::CLI::UrlEscaping
16
+ extend HTTY::CLI::UrlEscaping
17
17
 
18
18
  # Returns the name of a category under which help for the _userinfo-set_
19
19
  # command should appear.
@@ -53,26 +53,20 @@ class HTTY::CLI::Commands::UserinfoSet < HTTY::CLI::Command
53
53
  [HTTY::CLI::Commands::UserinfoUnset, HTTY::CLI::Commands::Address]
54
54
  end
55
55
 
56
+ def self.sanitize_arguments(arguments)
57
+ if (arguments.length == 1) && (arguments.first.scan(':').length == 1)
58
+ arguments = arguments.first.split(':')
59
+ end
60
+ escape_or_warn_of_escape_sequences(arguments)
61
+ end
62
+
56
63
  # Performs the _userinfo-set_ command.
57
64
  def perform
58
65
  add_request_if_new do |request|
59
- arguments = self.arguments
60
- if (arguments.length == 1) && (arguments.first.scan(':').length == 1)
61
- arguments = arguments.first.split(':')
62
- end
63
- arguments = escape_mercantile(escape_or_warn_of_escape_sequences(arguments))
64
66
  self.class.notify_if_cookies_cleared request do
65
67
  request.userinfo_set(*arguments)
66
68
  end
67
69
  end
68
70
  end
69
71
 
70
- private
71
-
72
- def escape_mercantile(arguments)
73
- arguments.collect do |a|
74
- a.gsub '@', '%40'
75
- end
76
- end
77
-
78
72
  end
@@ -39,10 +39,10 @@ module HTTY::CLI::Display
39
39
 
40
40
  def format(string, *attributes)
41
41
  segments = attributes.collect do |a|
42
- "\x1b[#{FORMATS[a]}m"
42
+ "\e[#{FORMATS[a]}m"
43
43
  end
44
44
  segments << string
45
- segments << "\x1b[0m"
45
+ segments << "\e[0m"
46
46
  segments.join ''
47
47
  end
48
48
 
@@ -69,7 +69,6 @@ module HTTY::CLI::Display
69
69
 
70
70
  def normal(string)
71
71
  return string
72
- # format string, :foreground_dark_default
73
72
  end
74
73
 
75
74
  def pluralize(word, number)
@@ -83,7 +82,7 @@ module HTTY::CLI::Display
83
82
  end
84
83
  end
85
84
 
86
- def prompt(request)
85
+ def formatted_prompt_for(request)
87
86
  format_request_uri(request.uri) + normal('> ')
88
87
  end
89
88
 
@@ -96,7 +95,7 @@ module HTTY::CLI::Display
96
95
  end
97
96
 
98
97
  def say_header(message, style=:normal)
99
- puts send(style, notice('')) + highlight(messag)
98
+ puts send(style, notice('')) + highlight(message)
100
99
  end
101
100
 
102
101
  def say_hello
@@ -104,13 +103,17 @@ module HTTY::CLI::Display
104
103
  strong('HTTP TTY') + normal('. Heck To The Yeah!')
105
104
  end
106
105
 
106
+ def break
107
+ puts ''
108
+ end
109
+
107
110
  def show_headers(headers, options={})
108
- show_asterisk_next_to = options[:show_asterisk_next_to]
109
- show_mercantile_next_to = options[:show_mercantile_next_to]
111
+ show_asterisk_next_to = (options[:show_asterisk_next_to] || '').downcase
112
+ show_mercantile_next_to = (options[:show_mercantile_next_to] || '').downcase
110
113
 
111
114
  asterisk_symbol, mercantile_symbol = nil, nil
112
115
  margin = headers.inject 0 do |result, header|
113
- header_name = header.first
116
+ header_name = header.first.downcase
114
117
  asterisk_symbol ||= (header_name == show_asterisk_next_to) ? '*' : nil
115
118
  mercantile_symbol ||= (header_name == show_mercantile_next_to) ? '@' : nil
116
119
  asterisk = (header_name == show_asterisk_next_to) ? asterisk_symbol : ''
@@ -119,10 +122,9 @@ module HTTY::CLI::Display
119
122
  result].max
120
123
  end
121
124
  headers.each do |name, value|
122
- asterisk = (name == show_asterisk_next_to) ? asterisk_symbol : nil
123
- mercantile = (name == show_mercantile_next_to) ? mercantile_symbol : nil
124
- justified_name = name.rjust margin -
125
- [asterisk.to_s.length, mercantile.to_s.length].max
125
+ asterisk = (name.downcase == show_asterisk_next_to) ? asterisk_symbol : nil
126
+ mercantile = (name.downcase == show_mercantile_next_to) ? mercantile_symbol : nil
127
+ justified_name = name.rjust margin - [asterisk.to_s.length, mercantile.to_s.length].max
126
128
  puts "#{justified_name}:#{strong(asterisk || mercantile)} " + value
127
129
  end
128
130
  end
@@ -0,0 +1,75 @@
1
+ require 'readline'
2
+
3
+ module HTTY::CLI::InputDevice
4
+ def self.new(display)
5
+ if STDIN.tty?
6
+ TTY.new(display)
7
+ else
8
+ Pipe.new(display)
9
+ end
10
+ end
11
+
12
+ class Pipe
13
+ def initialize(display)
14
+ @display = display
15
+ end
16
+
17
+ def commands
18
+ STDIN.each_line do |command_line|
19
+ command_line.chomp!
20
+ command_line.strip!
21
+ next if command_line.empty?
22
+ @display.line(command_line)
23
+ yield command_line
24
+ end
25
+ end
26
+ end
27
+
28
+ class TTY
29
+ def initialize(display)
30
+ enable_completion
31
+ @display = display
32
+ end
33
+
34
+ def commands
35
+ loop do
36
+ begin
37
+ command_line = ''
38
+ while command_line.empty? do
39
+ if (command_line = Readline.readline(@display.formatted_prompt, true)).nil?
40
+ raise Interrupt
41
+ end
42
+ if whitespace?(command_line) || repeat?(command_line)
43
+ Readline::HISTORY.pop
44
+ end
45
+ command_line.chomp!
46
+ command_line.strip!
47
+ end
48
+ yield command_line
49
+ rescue Interrupt
50
+ @display.break
51
+ yield 'quit'
52
+ end
53
+ end
54
+ end
55
+
56
+ private
57
+
58
+ def enable_completion
59
+ Readline.completion_proc = proc do |input|
60
+ autocomplete_list = HTTY::CLI::Commands.select do |c|
61
+ c.complete_for? input
62
+ end
63
+ autocomplete_list.collect(&:raw_name)
64
+ end
65
+ end
66
+
67
+ def repeat?(command_line)
68
+ command_line == Readline::HISTORY.to_a[-2]
69
+ end
70
+
71
+ def whitespace?(command_line)
72
+ command_line.strip.empty?
73
+ end
74
+ end
75
+ end
@@ -1,5 +1,5 @@
1
- require 'uri'
2
1
  require File.expand_path("#{File.dirname __FILE__}/display")
2
+ require File.expand_path("#{File.dirname __FILE__}/../uri")
3
3
 
4
4
  module HTTY; end
5
5
 
@@ -17,7 +17,7 @@ module HTTY::CLI::UrlEscaping
17
17
  'sequences'
18
18
  a
19
19
  else
20
- URI.escape a
20
+ HTTY::URI.escape_component(a)
21
21
  end
22
22
  end
23
23
  end
@@ -0,0 +1,81 @@
1
+ require 'uri'
2
+ require 'base64'
3
+
4
+ module HTTY; end
5
+
6
+ # Represents the Headers of a Request or a Response. Headers preserve the
7
+ # insertion order and are case insensitive. A custom Hash is used because Hash
8
+ # did not have this behavior in Ruby v1.8.7 and earlier.
9
+ class HTTY::Headers
10
+
11
+ def initialize(hash={})
12
+ @inner_hash = {}
13
+ @inner_keys = {}
14
+ @ordered_keys = []
15
+ hash.each_pair do |key, value|
16
+ self[key] = value
17
+ end
18
+ end
19
+
20
+ def [](key)
21
+ @inner_hash[key.downcase]
22
+ end
23
+
24
+ def []=(key, value)
25
+ @ordered_keys << key.downcase unless @inner_hash.key?(key.downcase)
26
+ @inner_keys[key.downcase] = key
27
+ @inner_hash[key.downcase] = value
28
+ self
29
+ end
30
+
31
+ def ==(other_hash)
32
+ if other_hash.kind_of?(self.class)
33
+ return (other_hash.instance_variable_get('@inner_hash') == @inner_hash) &&
34
+ (other_hash.instance_variable_get('@inner_keys') == @inner_keys)
35
+ end
36
+ if other_hash.kind_of?(@inner_hash.class)
37
+ return other_hash.keys.all? do |k|
38
+ other_hash[k] == @inner_hash[k]
39
+ end
40
+ end
41
+ false
42
+ end
43
+
44
+ def clear
45
+ @inner_hash.clear
46
+ @inner_keys.clear
47
+ @ordered_keys.clear
48
+ end
49
+
50
+ def delete(key)
51
+ @ordered_keys.delete(key.downcase) if @inner_hash.key?(key.downcase)
52
+ @inner_keys.delete(key.downcase)
53
+ @inner_hash.delete(key.downcase)
54
+ end
55
+
56
+ def empty?
57
+ @inner_hash.empty?
58
+ end
59
+
60
+ def to_a
61
+ @ordered_keys.inject([]) do |result, normalized_key|
62
+ result + [[@inner_keys[normalized_key], @inner_hash[normalized_key]]]
63
+ end
64
+ end
65
+
66
+ def self.basic_authentication_for(username, password = nil)
67
+ ['Authorization',
68
+ 'Basic ' + Base64.encode64(
69
+ URI.unescape([username, password].compact.join(':'))
70
+ ).chomp
71
+ ]
72
+ end
73
+
74
+ def self.credentials_from(basic_authentication)
75
+ if match = /^Basic (.+)$/.match(basic_authentication)
76
+ credentials = Base64.decode64(match[1]).split(':')
77
+ return yield *credentials if block_given?
78
+ return credentials
79
+ end
80
+ end
81
+ end