thor 0.19.1 → 0.19.2

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 (105) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +24 -0
  3. data/CONTRIBUTING.md +15 -0
  4. data/README.md +7 -1
  5. data/lib/thor.rb +31 -23
  6. data/lib/thor/actions.rb +21 -22
  7. data/lib/thor/actions/create_file.rb +1 -1
  8. data/lib/thor/actions/create_link.rb +1 -1
  9. data/lib/thor/actions/directory.rb +2 -2
  10. data/lib/thor/actions/empty_directory.rb +8 -8
  11. data/lib/thor/actions/file_manipulation.rb +23 -12
  12. data/lib/thor/actions/inject_into_file.rb +10 -14
  13. data/lib/thor/base.rb +33 -33
  14. data/lib/thor/command.rb +9 -9
  15. data/lib/thor/core_ext/hash_with_indifferent_access.rb +9 -1
  16. data/lib/thor/core_ext/io_binary_read.rb +7 -5
  17. data/lib/thor/core_ext/ordered_hash.rb +94 -63
  18. data/lib/thor/error.rb +3 -3
  19. data/lib/thor/group.rb +12 -12
  20. data/lib/thor/invocation.rb +4 -5
  21. data/lib/thor/parser/argument.rb +4 -7
  22. data/lib/thor/parser/arguments.rb +16 -16
  23. data/lib/thor/parser/option.rb +39 -19
  24. data/lib/thor/parser/options.rb +7 -5
  25. data/lib/thor/runner.rb +25 -25
  26. data/lib/thor/shell.rb +1 -1
  27. data/lib/thor/shell/basic.rb +41 -26
  28. data/lib/thor/shell/color.rb +1 -1
  29. data/lib/thor/shell/html.rb +4 -4
  30. data/lib/thor/util.rb +8 -7
  31. data/lib/thor/version.rb +1 -1
  32. data/thor.gemspec +6 -9
  33. metadata +6 -148
  34. data/Thorfile +0 -29
  35. data/spec/actions/create_file_spec.rb +0 -168
  36. data/spec/actions/create_link_spec.rb +0 -96
  37. data/spec/actions/directory_spec.rb +0 -169
  38. data/spec/actions/empty_directory_spec.rb +0 -129
  39. data/spec/actions/file_manipulation_spec.rb +0 -392
  40. data/spec/actions/inject_into_file_spec.rb +0 -135
  41. data/spec/actions_spec.rb +0 -331
  42. data/spec/base_spec.rb +0 -298
  43. data/spec/command_spec.rb +0 -79
  44. data/spec/core_ext/hash_with_indifferent_access_spec.rb +0 -48
  45. data/spec/core_ext/ordered_hash_spec.rb +0 -115
  46. data/spec/exit_condition_spec.rb +0 -19
  47. data/spec/fixtures/application.rb +0 -2
  48. data/spec/fixtures/app{1}/README +0 -3
  49. data/spec/fixtures/bundle/execute.rb +0 -6
  50. data/spec/fixtures/bundle/main.thor +0 -1
  51. data/spec/fixtures/command.thor +0 -10
  52. data/spec/fixtures/doc/%file_name%.rb.tt +0 -1
  53. data/spec/fixtures/doc/COMMENTER +0 -11
  54. data/spec/fixtures/doc/README +0 -3
  55. data/spec/fixtures/doc/block_helper.rb +0 -3
  56. data/spec/fixtures/doc/config.rb +0 -1
  57. data/spec/fixtures/doc/config.yaml.tt +0 -1
  58. data/spec/fixtures/doc/excluding/%file_name%.rb.tt +0 -1
  59. data/spec/fixtures/enum.thor +0 -10
  60. data/spec/fixtures/group.thor +0 -128
  61. data/spec/fixtures/invoke.thor +0 -131
  62. data/spec/fixtures/path with spaces b/data/spec/fixtures/path with → spaces +0 -0
  63. data/spec/fixtures/preserve/script.sh +0 -3
  64. data/spec/fixtures/script.thor +0 -220
  65. data/spec/fixtures/subcommand.thor +0 -17
  66. data/spec/group_spec.rb +0 -222
  67. data/spec/helper.rb +0 -80
  68. data/spec/invocation_spec.rb +0 -120
  69. data/spec/line_editor/basic_spec.rb +0 -28
  70. data/spec/line_editor/readline_spec.rb +0 -69
  71. data/spec/line_editor_spec.rb +0 -43
  72. data/spec/parser/argument_spec.rb +0 -53
  73. data/spec/parser/arguments_spec.rb +0 -66
  74. data/spec/parser/option_spec.rb +0 -210
  75. data/spec/parser/options_spec.rb +0 -414
  76. data/spec/quality_spec.rb +0 -75
  77. data/spec/rake_compat_spec.rb +0 -72
  78. data/spec/register_spec.rb +0 -227
  79. data/spec/runner_spec.rb +0 -246
  80. data/spec/sandbox/application.rb +0 -2
  81. data/spec/sandbox/app{1}/README +0 -3
  82. data/spec/sandbox/bundle/execute.rb +0 -6
  83. data/spec/sandbox/bundle/main.thor +0 -1
  84. data/spec/sandbox/command.thor +0 -10
  85. data/spec/sandbox/doc/%file_name%.rb.tt +0 -1
  86. data/spec/sandbox/doc/COMMENTER +0 -11
  87. data/spec/sandbox/doc/README +0 -3
  88. data/spec/sandbox/doc/block_helper.rb +0 -3
  89. data/spec/sandbox/doc/config.rb +0 -1
  90. data/spec/sandbox/doc/config.yaml.tt +0 -1
  91. data/spec/sandbox/doc/excluding/%file_name%.rb.tt +0 -1
  92. data/spec/sandbox/enum.thor +0 -10
  93. data/spec/sandbox/group.thor +0 -128
  94. data/spec/sandbox/invoke.thor +0 -131
  95. data/spec/sandbox/path with spaces b/data/spec/sandbox/path with → spaces +0 -0
  96. data/spec/sandbox/preserve/script.sh +0 -3
  97. data/spec/sandbox/script.thor +0 -220
  98. data/spec/sandbox/subcommand.thor +0 -17
  99. data/spec/shell/basic_spec.rb +0 -337
  100. data/spec/shell/color_spec.rb +0 -119
  101. data/spec/shell/html_spec.rb +0 -31
  102. data/spec/shell_spec.rb +0 -47
  103. data/spec/subcommand_spec.rb +0 -48
  104. data/spec/thor_spec.rb +0 -505
  105. data/spec/util_spec.rb +0 -196
@@ -22,11 +22,8 @@ class 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 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 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 Thor
14
14
  autoload :Group, "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
  # 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 Thor
41
41
  #
42
42
  # config<Hash>:: Configuration for this 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 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 Thor::Options parse the options first, so it can remove
@@ -205,8 +207,8 @@ class 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
209
- is_thor_reserved_word?(name, :argument)
210
+ def argument(name, options = {})
211
+ thor_reserved_word?(name, :argument)
210
212
  no_commands { attr_accessor name }
211
213
 
212
214
  required = if options.key?(:optional)
@@ -219,11 +221,13 @@ class 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 Thor
343
347
  #
344
348
  def all_commands
345
349
  @all_commands ||= from_superclass(:all_commands, 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 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 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 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:")
@@ -529,9 +529,9 @@ class Thor
529
529
  end
530
530
 
531
531
  # Raises an error if the word given is a Thor reserved word.
532
- def is_thor_reserved_word?(word, type) #:nodoc:
532
+ def thor_reserved_word?(word, type) #:nodoc:
533
533
  return false unless THOR_RESERVED_WORDS.include?(word.to_s)
534
- fail "#{word.inspect} is a Thor reserved word and cannot be defined as #{type}"
534
+ raise "#{word.inspect} is a 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 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
@@ -594,7 +594,7 @@ class Thor
594
594
  @no_commands ||= false
595
595
  return if @no_commands || !create_command(meth)
596
596
 
597
- is_thor_reserved_word?(meth, :command)
597
+ thor_reserved_word?(meth, :command)
598
598
  Thor::Base.register_klass_file(self)
599
599
  end
600
600
 
@@ -649,7 +649,7 @@ class 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 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 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 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 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 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 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 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 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 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 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