Skip to content

Commit 919e13a

Browse files
committed
Implement merge!/update on Thor::CoreExt::OrderedHash
1 parent 7d6694a commit 919e13a

File tree

1 file changed

+94
-63
lines changed

1 file changed

+94
-63
lines changed

lib/thor/core_ext/ordered_hash.rb

Lines changed: 94 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -1,96 +1,127 @@
11
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 = []
198
end
209

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
2314
end
2415

2516
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
4219
end
4320

4421
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
4828

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
5134

52-
@first = next_node if @first == node
53-
@last = prev_node if @last == node
35+
alias_method :reject!, :delete_if
5436

55-
value = node.value
56-
end
57-
58-
@hash.delete(key)
59-
value
37+
def reject(&block)
38+
dup.reject!(&block)
6039
end
6140

6241
def keys
63-
map { |k, v| k }
42+
@keys.dup
6443
end
6544

6645
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
6867
end
6968

7069
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]]) }
7572
self
7673
end
7774

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
8080

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
8494

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 }
87100
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
88109

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
90115
end
91116

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) }
94125
end
95126
end
96127
end

0 commit comments

Comments
 (0)