bundler 1.14.6 → 1.15.0.pre.1

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of bundler might be problematic. Click here for more details.

Files changed (131) hide show
  1. checksums.yaml +5 -5
  2. data/.rubocop.yml +1 -1
  3. data/.rubocop_todo.yml +131 -43
  4. data/.travis.yml +5 -5
  5. data/CHANGELOG.md +52 -8
  6. data/CONTRIBUTING.md +10 -29
  7. data/README.md +13 -6
  8. data/Rakefile +4 -2
  9. data/bin/rubocop +1 -1
  10. data/doc/README.md +30 -0
  11. data/doc/TROUBLESHOOTING.md +64 -0
  12. data/doc/contributing/BUG_TRIAGE.md +36 -0
  13. data/doc/contributing/COMMUNITY.md +13 -0
  14. data/doc/contributing/GETTING_HELP.md +11 -0
  15. data/doc/contributing/HOW_YOU_CAN_HELP.md +27 -0
  16. data/doc/contributing/ISSUES.md +51 -0
  17. data/doc/contributing/README.md +38 -0
  18. data/doc/development/NEW_FEATURES.md +10 -0
  19. data/doc/development/PULL_REQUESTS.md +40 -0
  20. data/doc/development/README.md +19 -0
  21. data/doc/development/RELEASING.md +9 -0
  22. data/doc/development/SETUP.md +29 -0
  23. data/doc/documentation/README.md +29 -0
  24. data/doc/documentation/VISION.md +26 -0
  25. data/doc/documentation/WRITING.md +54 -0
  26. data/exe/bundle +4 -1
  27. data/lib/bundler.rb +20 -13
  28. data/lib/bundler/cli.rb +67 -3
  29. data/lib/bundler/cli/add.rb +26 -0
  30. data/lib/bundler/cli/config.rb +24 -6
  31. data/lib/bundler/cli/gem.rb +13 -8
  32. data/lib/bundler/cli/info.rb +51 -0
  33. data/lib/bundler/cli/inject.rb +8 -2
  34. data/lib/bundler/cli/install.rb +1 -1
  35. data/lib/bundler/cli/issue.rb +40 -0
  36. data/lib/bundler/cli/outdated.rb +16 -18
  37. data/lib/bundler/cli/pristine.rb +33 -0
  38. data/lib/bundler/cli/viz.rb +1 -1
  39. data/lib/bundler/definition.rb +64 -48
  40. data/lib/bundler/dsl.rb +6 -0
  41. data/lib/bundler/endpoint_specification.rb +3 -9
  42. data/lib/bundler/env.rb +3 -3
  43. data/lib/bundler/errors.rb +1 -1
  44. data/lib/bundler/fetcher/downloader.rb +3 -2
  45. data/lib/bundler/gem_helper.rb +5 -0
  46. data/lib/bundler/index.rb +9 -3
  47. data/lib/bundler/injector.rb +32 -11
  48. data/lib/bundler/installer.rb +1 -1
  49. data/lib/bundler/installer/parallel_installer.rb +15 -1
  50. data/lib/bundler/lazy_specification.rb +6 -0
  51. data/lib/bundler/lockfile_parser.rb +42 -34
  52. data/lib/bundler/mirror.rb +2 -0
  53. data/lib/bundler/plugin.rb +5 -1
  54. data/lib/bundler/plugin/api/source.rb +1 -1
  55. data/lib/bundler/plugin/index.rb +2 -0
  56. data/lib/bundler/remote_specification.rb +16 -7
  57. data/lib/bundler/resolver.rb +13 -3
  58. data/lib/bundler/rubygems_ext.rb +8 -3
  59. data/lib/bundler/rubygems_integration.rb +85 -36
  60. data/lib/bundler/runtime.rb +4 -1
  61. data/lib/bundler/settings.rb +2 -1
  62. data/lib/bundler/setup.rb +1 -1
  63. data/lib/bundler/shared_helpers.rb +26 -1
  64. data/lib/bundler/source.rb +17 -1
  65. data/lib/bundler/source/git.rb +16 -0
  66. data/lib/bundler/source/path.rb +13 -3
  67. data/lib/bundler/source/path/installer.rb +2 -2
  68. data/lib/bundler/source/rubygems.rb +5 -2
  69. data/lib/bundler/spec_set.rb +22 -13
  70. data/lib/bundler/stub_specification.rb +64 -2
  71. data/lib/bundler/templates/Executable +1 -1
  72. data/lib/bundler/templates/Executable.standalone +5 -5
  73. data/lib/bundler/templates/newgem/Gemfile.tt +2 -2
  74. data/lib/bundler/templates/newgem/LICENSE.txt.tt +1 -1
  75. data/lib/bundler/templates/newgem/README.md.tt +12 -6
  76. data/lib/bundler/templates/newgem/Rakefile.tt +5 -5
  77. data/lib/bundler/templates/newgem/ext/newgem/newgem.c.tt +4 -4
  78. data/lib/bundler/templates/newgem/ext/newgem/newgem.h.tt +3 -3
  79. data/lib/bundler/templates/newgem/lib/newgem.rb.tt +6 -6
  80. data/lib/bundler/templates/newgem/lib/newgem/version.rb.tt +4 -4
  81. data/lib/bundler/templates/newgem/newgem.gemspec.tt +9 -9
  82. data/lib/bundler/templates/newgem/spec/spec_helper.rb.tt +3 -0
  83. data/lib/bundler/templates/newgem/test/newgem_test.rb.tt +1 -1
  84. data/lib/bundler/templates/newgem/test/test_helper.rb.tt +3 -3
  85. data/lib/bundler/ui/shell.rb +9 -6
  86. data/lib/bundler/vendor/thor/lib/thor.rb +31 -23
  87. data/lib/bundler/vendor/thor/lib/thor/actions.rb +21 -22
  88. data/lib/bundler/vendor/thor/lib/thor/actions/create_file.rb +1 -1
  89. data/lib/bundler/vendor/thor/lib/thor/actions/create_link.rb +1 -1
  90. data/lib/bundler/vendor/thor/lib/thor/actions/directory.rb +2 -2
  91. data/lib/bundler/vendor/thor/lib/thor/actions/empty_directory.rb +8 -8
  92. data/lib/bundler/vendor/thor/lib/thor/actions/file_manipulation.rb +23 -12
  93. data/lib/bundler/vendor/thor/lib/thor/actions/inject_into_file.rb +10 -14
  94. data/lib/bundler/vendor/thor/lib/thor/base.rb +30 -30
  95. data/lib/bundler/vendor/thor/lib/thor/command.rb +9 -9
  96. data/lib/bundler/vendor/thor/lib/thor/core_ext/hash_with_indifferent_access.rb +9 -1
  97. data/lib/bundler/vendor/thor/lib/thor/core_ext/io_binary_read.rb +7 -5
  98. data/lib/bundler/vendor/thor/lib/thor/core_ext/ordered_hash.rb +94 -63
  99. data/lib/bundler/vendor/thor/lib/thor/error.rb +3 -3
  100. data/lib/bundler/vendor/thor/lib/thor/group.rb +12 -12
  101. data/lib/bundler/vendor/thor/lib/thor/invocation.rb +4 -5
  102. data/lib/bundler/vendor/thor/lib/thor/parser/argument.rb +4 -7
  103. data/lib/bundler/vendor/thor/lib/thor/parser/arguments.rb +16 -16
  104. data/lib/bundler/vendor/thor/lib/thor/parser/option.rb +40 -19
  105. data/lib/bundler/vendor/thor/lib/thor/parser/options.rb +7 -5
  106. data/lib/bundler/vendor/thor/lib/thor/runner.rb +25 -25
  107. data/lib/bundler/vendor/thor/lib/thor/shell.rb +1 -1
  108. data/lib/bundler/vendor/thor/lib/thor/shell/basic.rb +41 -26
  109. data/lib/bundler/vendor/thor/lib/thor/shell/color.rb +1 -1
  110. data/lib/bundler/vendor/thor/lib/thor/shell/html.rb +4 -4
  111. data/lib/bundler/vendor/thor/lib/thor/util.rb +8 -7
  112. data/lib/bundler/vendor/thor/lib/thor/version.rb +1 -1
  113. data/lib/bundler/version.rb +14 -1
  114. data/lib/bundler/version_ranges.rb +75 -0
  115. data/lib/bundler/worker.rb +2 -1
  116. data/man/bundle-check.ronn +26 -0
  117. data/man/bundle-clean.ronn +18 -0
  118. data/man/bundle-config.ronn +4 -1
  119. data/man/bundle-info.ronn +17 -0
  120. data/man/bundle-init.ronn +18 -0
  121. data/man/bundle-inject.ronn +22 -0
  122. data/man/bundle-open.ronn +19 -0
  123. data/man/bundle-show.ronn +20 -0
  124. data/man/bundle-update.ronn +5 -2
  125. data/man/bundle-viz.ronn +30 -0
  126. data/man/bundle.ronn +3 -0
  127. data/man/gemfile.5.ronn +24 -64
  128. data/task/release.rake +115 -0
  129. metadata +49 -5
  130. data/DEVELOPMENT.md +0 -150
  131. data/ISSUES.md +0 -117
@@ -22,11 +22,8 @@ class Bundler::Thor
22
22
  # end
23
23
  #
24
24
  def insert_into_file(destination, *args, &block)
25
- if block_given?
26
- data, config = block, args.shift
27
- else
28
- data, config = args.shift, args.shift
29
- end
25
+ data = block_given? ? block : args.shift
26
+ config = args.shift
30
27
  action InjectIntoFile.new(self, destination, data, config)
31
28
  end
32
29
  alias_method :inject_into_file, :insert_into_file
@@ -39,9 +36,9 @@ class Bundler::Thor
39
36
 
40
37
  @behavior, @flag = if @config.key?(:after)
41
38
  [:after, @config.delete(:after)]
42
- else
43
- [:before, @config.delete(:before)]
44
- end
39
+ else
40
+ [:before, @config.delete(:before)]
41
+ end
45
42
 
46
43
  @replacement = data.is_a?(Proc) ? data.call : data
47
44
  @flag = Regexp.escape(@flag) unless @flag.is_a?(Regexp)
@@ -94,12 +91,11 @@ class Bundler::Thor
94
91
  # Adds the content to the file.
95
92
  #
96
93
  def replace!(regexp, string, force)
97
- unless base.options[:pretend]
98
- content = File.binread(destination)
99
- if force || !content.include?(replacement)
100
- content.gsub!(regexp, string)
101
- File.open(destination, "wb") { |file| file.write(content) }
102
- end
94
+ return if base.options[:pretend]
95
+ content = File.binread(destination)
96
+ if force || !content.include?(replacement)
97
+ content.gsub!(regexp, string)
98
+ File.open(destination, "wb") { |file| file.write(content) }
103
99
  end
104
100
  end
105
101
  end
@@ -14,11 +14,11 @@ class Bundler::Thor
14
14
  autoload :Group, "bundler/vendor/thor/lib/thor/group"
15
15
 
16
16
  # Shortcuts for help.
17
- HELP_MAPPINGS = %w[-h -? --help -D]
17
+ HELP_MAPPINGS = %w(-h -? --help -D)
18
18
 
19
19
  # Bundler::Thor methods that should not be overwritten by the user.
20
- THOR_RESERVED_WORDS = %w[invoke shell options behavior root destination_root relative_root
21
- action add_file create_file in_root inside run run_ruby_script]
20
+ THOR_RESERVED_WORDS = %w(invoke shell options behavior root destination_root relative_root
21
+ action add_file create_file in_root inside run run_ruby_script)
22
22
 
23
23
  TEMPLATE_EXTNAME = ".tt"
24
24
 
@@ -41,8 +41,8 @@ class Bundler::Thor
41
41
  #
42
42
  # config<Hash>:: Configuration for this Bundler::Thor class.
43
43
  #
44
- def initialize(args = [], local_options = {}, config = {}) # rubocop:disable MethodLength
45
- parse_options = self.class.class_options
44
+ def initialize(args = [], local_options = {}, config = {})
45
+ parse_options = config[:current_command] && config[:current_command].disable_class_options ? {} : self.class.class_options
46
46
 
47
47
  # The start method splits inbound arguments at the first argument
48
48
  # that looks like an option (starts with - or --). It then calls
@@ -52,11 +52,13 @@ class Bundler::Thor
52
52
  command_options = config.delete(:command_options) # hook for start
53
53
  parse_options = parse_options.merge(command_options) if command_options
54
54
  if local_options.is_a?(Array)
55
- array_options, hash_options = local_options, {}
55
+ array_options = local_options
56
+ hash_options = {}
56
57
  else
57
58
  # Handle the case where the class was explicitly instantiated
58
59
  # with pre-parsed options.
59
- array_options, hash_options = [], local_options
60
+ array_options = []
61
+ hash_options = local_options
60
62
  end
61
63
 
62
64
  # Let Bundler::Thor::Options parse the options first, so it can remove
@@ -205,7 +207,7 @@ class Bundler::Thor
205
207
  # ==== Errors
206
208
  # ArgumentError:: Raised if you supply a required argument after a non required one.
207
209
  #
208
- def argument(name, options = {}) # rubocop:disable MethodLength
210
+ def argument(name, options = {})
209
211
  is_thor_reserved_word?(name, :argument)
210
212
  no_commands { attr_accessor name }
211
213
 
@@ -219,11 +221,13 @@ class Bundler::Thor
219
221
 
220
222
  remove_argument name
221
223
 
222
- arguments.each do |argument|
223
- next if argument.required?
224
- fail ArgumentError, "You cannot have #{name.to_s.inspect} as required argument after " <<
225
- "the non-required argument #{argument.human_name.inspect}."
226
- end if required
224
+ if required
225
+ arguments.each do |argument|
226
+ next if argument.required?
227
+ raise ArgumentError, "You cannot have #{name.to_s.inspect} as required argument after " \
228
+ "the non-required argument #{argument.human_name.inspect}."
229
+ end
230
+ end
227
231
 
228
232
  options[:required] = required
229
233
 
@@ -343,7 +347,7 @@ class Bundler::Thor
343
347
  #
344
348
  def all_commands
345
349
  @all_commands ||= from_superclass(:all_commands, Bundler::Thor::CoreExt::OrderedHash.new)
346
- @all_commands.merge(commands)
350
+ @all_commands.merge!(commands)
347
351
  end
348
352
  alias_method :all_tasks, :all_commands
349
353
 
@@ -467,11 +471,8 @@ class Bundler::Thor
467
471
  alias_method :public_task, :public_command
468
472
 
469
473
  def handle_no_command_error(command, has_namespace = $thor_runner) #:nodoc:
470
- if has_namespace
471
- fail UndefinedCommandError, "Could not find command #{command.inspect} in #{namespace.inspect} namespace."
472
- else
473
- fail UndefinedCommandError, "Could not find command #{command.inspect}."
474
- end
474
+ raise UndefinedCommandError, "Could not find command #{command.inspect} in #{namespace.inspect} namespace." if has_namespace
475
+ raise UndefinedCommandError, "Could not find command #{command.inspect}."
475
476
  end
476
477
  alias_method :handle_no_task_error, :handle_no_command_error
477
478
 
@@ -480,7 +481,7 @@ class Bundler::Thor
480
481
  msg << "no arguments" if args.empty?
481
482
  msg << "arguments " << args.inspect unless args.empty?
482
483
  msg << "\nUsage: #{banner(command).inspect}"
483
- fail InvocationError, msg
484
+ raise InvocationError, msg
484
485
  end
485
486
 
486
487
  protected
@@ -513,14 +514,13 @@ class Bundler::Thor
513
514
  padding = options.map { |o| o.aliases.size }.max.to_i * 4
514
515
 
515
516
  options.each do |option|
516
- unless option.hide
517
- item = [option.usage(padding)]
518
- item.push(option.description ? "# #{option.description}" : "")
517
+ next if option.hide
518
+ item = [option.usage(padding)]
519
+ item.push(option.description ? "# #{option.description}" : "")
519
520
 
520
- list << item
521
- list << ["", "# Default: #{option.default}"] if option.show_default?
522
- list << ["", "# Possible values: #{option.enum.join(', ')}"] if option.enum
523
- end
521
+ list << item
522
+ list << ["", "# Default: #{option.default}"] if option.show_default?
523
+ list << ["", "# Possible values: #{option.enum.join(', ')}"] if option.enum
524
524
  end
525
525
 
526
526
  shell.say(group_name ? "#{group_name} options:" : "Options:")
@@ -531,7 +531,7 @@ class Bundler::Thor
531
531
  # Raises an error if the word given is a Bundler::Thor reserved word.
532
532
  def is_thor_reserved_word?(word, type) #:nodoc:
533
533
  return false unless THOR_RESERVED_WORDS.include?(word.to_s)
534
- fail "#{word.inspect} is a Bundler::Thor reserved word and cannot be defined as #{type}"
534
+ raise "#{word.inspect} is a Bundler::Thor reserved word and cannot be defined as #{type}"
535
535
  end
536
536
 
537
537
  # Build an option and adds it to the given scope.
@@ -566,7 +566,7 @@ class Bundler::Thor
566
566
  elsif command = all_commands[name.to_s] # rubocop:disable AssignmentInCondition
567
567
  commands[name.to_s] = command.clone
568
568
  else
569
- fail ArgumentError, "You supplied :for => #{name.inspect}, but the command #{name.inspect} could not be found."
569
+ raise ArgumentError, "You supplied :for => #{name.inspect}, but the command #{name.inspect} could not be found."
570
570
  end
571
571
  end
572
572
  alias_method :find_and_refresh_task, :find_and_refresh_command
@@ -649,7 +649,7 @@ class Bundler::Thor
649
649
 
650
650
  # SIGNATURE: The hook invoked by start.
651
651
  def dispatch(command, given_args, given_opts, config) #:nodoc:
652
- fail NotImplementedError
652
+ raise NotImplementedError
653
653
  end
654
654
  end
655
655
  end
@@ -1,9 +1,9 @@
1
1
  class Bundler::Thor
2
- class Command < Struct.new(:name, :description, :long_description, :usage, :options)
2
+ class Command < Struct.new(:name, :description, :long_description, :usage, :options, :disable_class_options)
3
3
  FILE_REGEXP = /^#{Regexp.escape(File.dirname(__FILE__))}/
4
4
 
5
- def initialize(name, description, long_description, usage, options = nil)
6
- super(name.to_s, description, long_description, usage, options || {})
5
+ def initialize(name, description, long_description, usage, options = nil, disable_class_options = false)
6
+ super(name.to_s, description, long_description, usage, options || {}, disable_class_options)
7
7
  end
8
8
 
9
9
  def initialize_copy(other) #:nodoc:
@@ -33,7 +33,7 @@ class Bundler::Thor
33
33
  rescue ArgumentError => e
34
34
  handle_argument_error?(instance, e, caller) ? instance.class.handle_argument_error(self, e, args, arity) : (raise e)
35
35
  rescue NoMethodError => e
36
- handle_no_method_error?(instance, e, caller) ? instance.class.handle_no_command_error(name) : (fail e)
36
+ handle_no_method_error?(instance, e, caller) ? instance.class.handle_no_command_error(name) : (raise e)
37
37
  end
38
38
 
39
39
  # Returns the formatted usage by injecting given required arguments
@@ -50,7 +50,7 @@ class Bundler::Thor
50
50
  # Add usage with required arguments
51
51
  formatted << if klass && !klass.arguments.empty?
52
52
  usage.to_s.gsub(/^#{name}/) do |match|
53
- match << " " << klass.arguments.map { |a| a.usage }.compact.join(" ")
53
+ match << " " << klass.arguments.map(&:usage).compact.join(" ")
54
54
  end
55
55
  else
56
56
  usage.to_s
@@ -88,7 +88,7 @@ class Bundler::Thor
88
88
  end
89
89
 
90
90
  def sans_backtrace(backtrace, caller) #:nodoc:
91
- saned = backtrace.reject { |frame| frame =~ FILE_REGEXP || (frame =~ /\.java:/ && RUBY_PLATFORM =~ /java/) || (frame =~ /^kernel\// && RUBY_ENGINE =~ /rbx/) }
91
+ saned = backtrace.reject { |frame| frame =~ FILE_REGEXP || (frame =~ /\.java:/ && RUBY_PLATFORM =~ /java/) || (frame =~ %r{^kernel/} && RUBY_ENGINE =~ /rbx/) }
92
92
  saned - caller
93
93
  end
94
94
 
@@ -105,7 +105,7 @@ class Bundler::Thor
105
105
  error.message =~ /^undefined method `#{name}' for #{Regexp.escape(instance.to_s)}$/
106
106
  end
107
107
  end
108
- Task = Command # rubocop:disable ConstantName
108
+ Task = Command
109
109
 
110
110
  # A command that is hidden in help messages but still invocable.
111
111
  class HiddenCommand < Command
@@ -113,7 +113,7 @@ class Bundler::Thor
113
113
  true
114
114
  end
115
115
  end
116
- HiddenTask = HiddenCommand # rubocop:disable ConstantName
116
+ HiddenTask = HiddenCommand
117
117
 
118
118
  # A dynamic command that handles method missing scenarios.
119
119
  class DynamicCommand < Command
@@ -129,5 +129,5 @@ class Bundler::Thor
129
129
  end
130
130
  end
131
131
  end
132
- DynamicTask = DynamicCommand # rubocop:disable ConstantName
132
+ DynamicTask = DynamicCommand
133
133
  end
@@ -28,6 +28,14 @@ class Bundler::Thor
28
28
  super(convert_key(key))
29
29
  end
30
30
 
31
+ def fetch(key, *args)
32
+ super(convert_key(key), *args)
33
+ end
34
+
35
+ def key?(key)
36
+ super(convert_key(key))
37
+ end
38
+
31
39
  def values_at(*indices)
32
40
  indices.map { |key| self[convert_key(key)] }
33
41
  end
@@ -60,7 +68,7 @@ class Bundler::Thor
60
68
  # options.shebang # => "/usr/lib/local/ruby"
61
69
  # options.test_framework?(:rspec) # => options[:test_framework] == :rspec
62
70
  #
63
- def method_missing(method, *args, &block)
71
+ def method_missing(method, *args)
64
72
  method = method.to_s
65
73
  if method =~ /^(\w+)\?$/
66
74
  if args.empty?
@@ -1,10 +1,12 @@
1
1
  class IO #:nodoc:
2
2
  class << self
3
- def binread(file, *args)
4
- fail ArgumentError, "wrong number of arguments (#{1 + args.size} for 1..3)" unless args.size < 3
5
- File.open(file, "rb") do |f|
6
- f.read(*args)
3
+ unless method_defined? :binread
4
+ def binread(file, *args)
5
+ raise ArgumentError, "wrong number of arguments (#{1 + args.size} for 1..3)" unless args.size < 3
6
+ File.open(file, "rb") do |f|
7
+ f.read(*args)
8
+ end
7
9
  end
8
- end unless method_defined? :binread
10
+ end
9
11
  end
10
12
  end
@@ -1,96 +1,127 @@
1
1
  class Bundler::Thor
2
- module CoreExt #:nodoc:
3
- if RUBY_VERSION >= "1.9"
4
- class OrderedHash < ::Hash
5
- end
6
- else
7
- # This class is based on the Ruby 1.9 ordered hashes.
8
- #
9
- # It keeps the semantics and most of the efficiency of normal hashes
10
- # while also keeping track of the order in which elements were set.
11
- #
12
- class OrderedHash #:nodoc:
13
- include Enumerable
14
-
15
- Node = Struct.new(:key, :value, :next, :prev)
16
-
17
- def initialize
18
- @hash = {}
2
+ module CoreExt
3
+ class OrderedHash < ::Hash
4
+ if RUBY_VERSION < "1.9"
5
+ def initialize(*args, &block)
6
+ super
7
+ @keys = []
19
8
  end
20
9
 
21
- def [](key)
22
- @hash[key] && @hash[key].value
10
+ def initialize_copy(other)
11
+ super
12
+ # make a deep copy of keys
13
+ @keys = other.keys
23
14
  end
24
15
 
25
16
  def []=(key, value)
26
- if node = @hash[key] # rubocop:disable AssignmentInCondition
27
- node.value = value
28
- else
29
- node = Node.new(key, value)
30
-
31
- if !defined?(@first) || @first.nil?
32
- @first = @last = node
33
- else
34
- node.prev = @last
35
- @last.next = node
36
- @last = node
37
- end
38
- end
39
-
40
- @hash[key] = node
41
- value
17
+ @keys << key unless key?(key)
18
+ super
42
19
  end
43
20
 
44
21
  def delete(key)
45
- if node = @hash[key] # rubocop:disable AssignmentInCondition
46
- prev_node = node.prev
47
- next_node = node.next
22
+ if key? key
23
+ index = @keys.index(key)
24
+ @keys.delete_at index
25
+ end
26
+ super
27
+ end
48
28
 
49
- next_node.prev = prev_node if next_node
50
- prev_node.next = next_node if prev_node
29
+ def delete_if
30
+ super
31
+ sync_keys!
32
+ self
33
+ end
51
34
 
52
- @first = next_node if @first == node
53
- @last = prev_node if @last == node
35
+ alias_method :reject!, :delete_if
54
36
 
55
- value = node.value
56
- end
57
-
58
- @hash.delete(key)
59
- value
37
+ def reject(&block)
38
+ dup.reject!(&block)
60
39
  end
61
40
 
62
41
  def keys
63
- map { |k, v| k }
42
+ @keys.dup
64
43
  end
65
44
 
66
45
  def values
67
- map { |k, v| v }
46
+ @keys.map { |key| self[key] }
47
+ end
48
+
49
+ def to_hash
50
+ self
51
+ end
52
+
53
+ def to_a
54
+ @keys.map { |key| [key, self[key]] }
55
+ end
56
+
57
+ def each_key
58
+ return to_enum(:each_key) unless block_given?
59
+ @keys.each { |key| yield(key) }
60
+ self
61
+ end
62
+
63
+ def each_value
64
+ return to_enum(:each_value) unless block_given?
65
+ @keys.each { |key| yield(self[key]) }
66
+ self
68
67
  end
69
68
 
70
69
  def each
71
- return unless defined?(@first) && @first
72
- yield [@first.key, @first.value]
73
- node = @first
74
- yield [node.key, node.value] while node = node.next # rubocop:disable AssignmentInCondition
70
+ return to_enum(:each) unless block_given?
71
+ @keys.each { |key| yield([key, self[key]]) }
75
72
  self
76
73
  end
77
74
 
78
- def merge(other)
79
- hash = self.class.new
75
+ def each_pair
76
+ return to_enum(:each_pair) unless block_given?
77
+ @keys.each { |key| yield(key, self[key]) }
78
+ self
79
+ end
80
80
 
81
- each do |key, value|
82
- hash[key] = value
83
- end
81
+ alias_method :select, :find_all
82
+
83
+ def clear
84
+ super
85
+ @keys.clear
86
+ self
87
+ end
88
+
89
+ def shift
90
+ k = @keys.first
91
+ v = delete(k)
92
+ [k, v]
93
+ end
84
94
 
85
- other.each do |key, value|
86
- hash[key] = value
95
+ def merge!(other_hash)
96
+ if block_given?
97
+ other_hash.each { |k, v| self[k] = key?(k) ? yield(k, self[k], v) : v }
98
+ else
99
+ other_hash.each { |k, v| self[k] = v }
87
100
  end
101
+ self
102
+ end
103
+
104
+ alias_method :update, :merge!
105
+
106
+ def merge(other_hash, &block)
107
+ dup.merge!(other_hash, &block)
108
+ end
88
109
 
89
- hash
110
+ # When replacing with another hash, the initial order of our keys must come from the other hash -ordered or not.
111
+ def replace(other)
112
+ super
113
+ @keys = other.keys
114
+ self
90
115
  end
91
116
 
92
- def empty?
93
- @hash.empty?
117
+ def inspect
118
+ "#<#{self.class} #{super}>"
119
+ end
120
+
121
+ private
122
+
123
+ def sync_keys!
124
+ @keys.delete_if { |k| !key?(k) }
94
125
  end
95
126
  end
96
127
  end