thor 0.19.1 → 0.19.2

Sign up to get free protection for your applications and to get access to all the features.
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