octothorpe 0.2.0 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 44e56895848bd74e1a1c1e0fce48dd52d529bac4
4
- data.tar.gz: d930fc76de139a233b1b9695d4f1881fb545168a
3
+ metadata.gz: 4eb5a75a4f6e96e372ad54747fd18dbe5246e0e6
4
+ data.tar.gz: d8c0c8a82fef44e3c160bd327d4d1d32f6ce3e50
5
5
  SHA512:
6
- metadata.gz: b5fed196557a740921eded0a1ee32e7e2d92400a35a0498db54d98f895bc2af3f2e5cd6085bba9709526f624553ca93aad8041e3633fd711470d788524fd0761
7
- data.tar.gz: c0909d019f3509d7a8ab001d470652a0d1929bc67fbb9e9a2d3f3990133bc8d068cd9c5d8e976d01f747b06ba41be5e900b362d268f00f7393f4be464dc1440b
6
+ metadata.gz: 7abfb6d20290533da8a8a8e3357bf6244748c8479b1c5da22b1b7c852d4ca33e792ee72423dc48caf845209261b083494d11ab9642de5ad7fc331b91a7a05579
7
+ data.tar.gz: af00c9772425af4d6d4b40b729305af4126e2d8155bc15d438e6b8d2dd519c6bc5755463492a51fd64b52ff3d1b1579a754c83b1b976f0d3f32b1353e60eb6bb
data/.hgignore CHANGED
@@ -13,3 +13,4 @@ mkmf.log
13
13
  .ruby*
14
14
  .rbenv*
15
15
  .project
16
+ .Project
data/.hgtags CHANGED
@@ -1 +1,2 @@
1
1
  a17ae5a3dedd4d2b74b0840a7161e153efd72741 0.2.0
2
+ a7a29ac9aca7023491b7bb58e0516c115b7f2511 0.3.0
data/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # Octothorpe
2
2
 
3
- A very simple hash-like class that borrows a little from OpenStruct, etc.
3
+ A very simple hash-like class that borrows a little from OpenStruct, HashWithIndifferentAccess, etc.
4
4
 
5
5
  * Treats string and symbol keys as equal
6
6
  * Access member objects with ot.>>.keyname
@@ -29,28 +29,32 @@ Or install it yourself as:
29
29
 
30
30
  Simple example:
31
31
 
32
- ot = Octotghorpe.new(one: 1, "two" => 2, "weird key" => 3)
33
- ot.>>.one # -> 1
34
- ot.>>.two # -> 2
35
- ot.get("weird key") # -> 3
32
+ ```ruby
33
+ ot = Octotghorpe.new(one: 1, "two" => 2, "weird key" => 3)
34
+ ot.>>.one # -> 1
35
+ ot.>>.two # -> 2
36
+ ot.get("weird key") # -> 3
37
+ ```
36
38
 
37
39
  With guard conditions:
38
40
 
39
- ot = Octotghorpe.new(one: 1, "two" => 2)
40
- ot.guard(Array, :three)
41
- ot.freeze # optional step - makes OT truly read-only
42
- ot.>>.three # -> []
43
- ot.>>.three[9] # valid (of course; returns nil)
41
+ ```ruby
42
+ ot = Octotghorpe.new(one: 1, "two" => 2)
43
+ ot.guard(Array, :three)
44
+ ot.freeze # optional step - makes OT truly read-only
45
+ ot.>>.three # -> []
46
+ ot.>>.three[9] # valid (of course; returns nil)
47
+ ```
44
48
 
45
- Octothorpe responds to a good subset of the methods that hash does
46
- (although, not the write methods).
49
+ Octothorpe responds to a good subset of the methods that hash does (although, not the write
50
+ methods).
47
51
 
48
52
  ## FAQ
49
53
 
50
54
  ### Octo-what?
51
55
 
52
- An antiquated term for the pound, or, _hash_ key on a phone keyboard. It's a
53
- sort of a joke, you see. Or, very nearly.
56
+ An antiquated term for the pound, or, _hash_ key on a phone keyboard. It's a sort of a joke, you
57
+ see. Or, very nearly.
54
58
 
55
59
  ### This is a very small library. Was it really worth it?
56
60
 
@@ -58,21 +62,23 @@ Maybe not. Feel free to be your own judge.
58
62
 
59
63
  ### What possible use is it?
60
64
 
61
- If you are fed up with errors caused because Gem A gives you a hash with string
62
- keys and Gem B expects symbol keys; or you are tired of putting:
65
+ If you are fed up with errors caused because Gem A gives you a hash with string keys and Gem B
66
+ expects symbol keys; or you are tired of putting:
63
67
 
64
- hash && (hash[:key] || {})[4]
68
+ ```ruby
69
+ hash && (hash[:key] || {})[4]
70
+ ```
65
71
 
66
- ...then this might just possibly be of use.
72
+ ... and for some reason Ruby's new lonely operator is a problem, then this might just possibly be
73
+ of use.
67
74
 
68
- Alternatively you might try an OpenStruct, Rails' HashWithIndifferentAccess,
69
- the Hashie gem or the AndAnd gem.
75
+ Alternatively you might try an OpenStruct, Rails' HashWithIndifferentAccess, the Hashie gem or the
76
+ AndAnd gem.
70
77
 
71
78
  ### Why Read-Only?
72
79
 
73
80
  Functional programming.
74
81
 
75
- I find it very hard to fully realise the ideals of functional programming in
76
- Ruby; but as I get closer to those ideals, my code becomes clearer to read and
77
- my tests become much, much simpler.
82
+ I find it very hard to fully realise the ideals of functional programming in Ruby; but as I get
83
+ closer to those ideals, my code becomes clearer to read and my tests become much, much simpler.
78
84
 
@@ -26,13 +26,12 @@ require 'forwardable'
26
26
  # ot.>>.three # -> []
27
27
  # ot.>>.three[9] # valid (of course; returns nil)
28
28
  #
29
- # Octothorpe additionally responds to the following methods exactly as a Hash
30
- # would:
29
+ # Octothorpe additionally responds to the following methods exactly as a Hash would:
31
30
  #
32
31
  # empty?, has_key?, has_value?, include?
33
32
  # each, each_key, each_value, keys, values
34
33
  # select, map, reject, inject
35
- # merge, <, >
34
+ # merge, <, >< ==, >+, <=
36
35
  #
37
36
  class Octothorpe
38
37
  extend Forwardable
@@ -42,7 +41,7 @@ class Octothorpe
42
41
  def_delegators :@inner_hash, :select, :map, :reject, :inject
43
42
 
44
43
  # Gem version number
45
- VERSION = '0.2.0'
44
+ VERSION = '0.3.0'
46
45
 
47
46
 
48
47
  # Generic Octothorpe error class
@@ -56,8 +55,8 @@ class Octothorpe
56
55
 
57
56
 
58
57
  ##
59
- # Inner class for storage. This is to minimise namespace collision with key
60
- # names. Not exposed to Octothorpe's caller.
58
+ # Inner class for storage. This is to minimise namespace collision with key names. Not exposed to
59
+ # Octothorpe's caller.
61
60
  #
62
61
  class Storage
63
62
  attr_reader :octothorpe_store
@@ -81,11 +80,11 @@ class Octothorpe
81
80
  #
82
81
  # Initialise an Octothorpe object by passing it a hash.
83
82
  #
84
- # You can create an empty OT by calling Octothorpe.new, but there's probably
85
- # little utility in that, given that it is read-only.
83
+ # You can create an empty OT by calling Octothorpe.new, but there's probably little utility in
84
+ # that, given that it is read-only.
86
85
  #
87
- # If you pass anything other than nil or something OT can treat as a Hash,
88
- # you will cause an Octothorpe::BadHash exception.
86
+ # If you pass anything other than nil or something OT can treat as a Hash, you will cause an
87
+ # Octothorpe::BadHash exception.
89
88
  #
90
89
  def initialize(hash=nil)
91
90
  @store = Storage.new( symbol_hash(hash || {}) )
@@ -97,14 +96,13 @@ class Octothorpe
97
96
  # :call-seq:
98
97
  # ot.>>.keyname
99
98
  #
100
- # You can use >> to access member objects in somewhat the same way as an
101
- # OpenStruct.
99
+ # You can use >> to access member objects in somewhat the same way as an OpenStruct.
102
100
  #
103
101
  # ot = Octotghorpe.new(one: 1, "two" => 2)
104
102
  # ot.>>.one # -> 1
105
103
  #
106
- # This will not work for members that have keys with spaces in, or keys which
107
- # have the same name as methods on Object. Use _get_ for those.
104
+ # This will not work for members that have keys with spaces in, or keys which have the same name
105
+ # as methods on Object. Use _get_ for those.
108
106
  #
109
107
  def >>; @store; end
110
108
 
@@ -117,8 +115,8 @@ class Octothorpe
117
115
  #
118
116
  # You can use get to access member object values instead of the >> syntax.
119
117
  #
120
- # Unlike >>, this works for keys with spaces, or keys that have the same name
121
- # as methods on Object.
118
+ # Unlike >>, this works for keys with spaces, or keys that have the same name as methods on
119
+ # Object.
122
120
  #
123
121
  def get(key); @store.octothorpe_store[key.to_sym]; end
124
122
 
@@ -135,20 +133,31 @@ class Octothorpe
135
133
  ##
136
134
  # :call-seq:
137
135
  # ot.guard( class, key [, key, ...] )
136
+ # ot.guard( key, [,key, ...] ) {|k| ... }
138
137
  #
139
- # Guarantees the initial state of a memnber. Each key that is not already
140
- # present will be set to <class>.new. Has no effect if key is already
141
- # present.
138
+ # Guarantees the initial state of a memnber. Each key that is not already present will be set to
139
+ # <class>.new. Has no effect if key is already present. Class must be some class Thing that can
140
+ # respond to a vanilla Thing.new.
142
141
  #
143
- # Class must be some class Thing that can respond to a vanilla Thing.new.
142
+ # Alternatively, for the block form, the key is passed to the block, and the value of the key
143
+ # becomes the return value of the block ... but again, ONLY if the key is not already set.
144
144
  #
145
- # Note that this is the only time that you can modify an Octothorpe object
146
- # once it is created. If you call _freeze_ on an it, it will become genuinely
147
- # read-only, and any call to guard from then on will raise Octothorpe::Frozen.
145
+ # Note that this is the only time that you can modify an Octothorpe object once it is created. If
146
+ # you call _freeze_ on an it, it will become genuinely read-only, and any call to guard from then
147
+ # on will raise Octothorpe::Frozen.
148
148
  #
149
- def guard(klass, *keys)
149
+ def guard(*args)
150
150
  raise Frozen if self.frozen?
151
- keys.map(&:to_sym).each{|k| @store.octothorpe_store[k] ||= klass.new }
151
+
152
+ klass = args.shift unless block_given?
153
+ keys = args.map(&:to_sym)
154
+
155
+ if block_given?
156
+ keys.each{|k| @store.octothorpe_store[k] ||= yield k }
157
+ else
158
+ keys.each{|k| @store.octothorpe_store[k] ||= klass.new }
159
+ end
160
+
152
161
  self
153
162
  end
154
163
 
@@ -160,8 +169,7 @@ class Octothorpe
160
169
  #
161
170
  # Exactly as _Hash.merge_, but returns a new Octothorpe object.
162
171
  #
163
- # You may pass a hash or an octothorpe. Raises Octothorpe::BadHash
164
- # if it is anything else.
172
+ # You may pass a hash or an octothorpe. Raises Octothorpe::BadHash if it is anything else.
165
173
  #
166
174
  def merge(other)
167
175
  thisHash = @store.octothorpe_store
@@ -178,24 +186,15 @@ class Octothorpe
178
186
  end
179
187
 
180
188
 
181
- ##
182
- # Return true if this OT is a subset of the given OT or Hash
183
- #
184
- def <(other)
185
- thisHash = @store.octothorpe_store.to_h
186
- otherHash = symbol_hash(other)
187
- thisHash < otherHash
188
- end
189
-
190
-
191
- ##
192
- # Return true if this OT is a superset of the given OT or Hash
189
+ #
190
+ # Resolve some of the standard comparisons (with an OT or a hash)
193
191
  #
194
- def >(other)
195
- thisHash = @store.octothorpe_store.to_h
196
- otherHash = symbol_hash(other)
197
- thisHash > otherHash
198
- end
192
+
193
+ def ==(other); compare_as_hash(other, :==); end
194
+ def <(other); compare_as_hash(other, :<); end
195
+ def >(other); compare_as_hash(other, :>); end
196
+ def >=(other); compare_as_hash(other, :>=); end
197
+ def <=(other); compare_as_hash(other, :<=); end
199
198
 
200
199
 
201
200
  ##
@@ -223,5 +222,15 @@ class Octothorpe
223
222
  end
224
223
 
225
224
 
225
+ ##
226
+ # Given an 'other' - Hash or OT - render both self and other down to a hash then run the given
227
+ # comparason on them and return the result
228
+ #
229
+ def compare_as_hash(other, method)
230
+ thisHash = @store.octothorpe_store.to_h
231
+ otherHash = symbol_hash(other)
232
+ thisHash.send(method, otherHash)
233
+ end
234
+
226
235
  end
227
236
 
@@ -90,36 +90,65 @@ describe Octothorpe do
90
90
 
91
91
  describe "#guard" do
92
92
 
93
- it "sets the given fields with a default value for the class" do
94
- @ot.guard(Array, :alpha)
95
- @ot.guard(Hash, :beta)
96
-
97
- expect( @ot.>>.alpha ).to eq([])
98
- expect( @ot.>>.beta ).to eq({})
93
+ it "raises Octothorpe::Frozen if the OT is frozen" do
94
+ @ot.freeze
95
+ expect{ @ot.guard(Hash, :foo) }.to raise_exception Octothorpe::Frozen
99
96
  end
100
97
 
101
98
  it "returns self" do
102
99
  expect( @ot.guard(Array, :foo) ).to eq @ot
103
100
  end
104
101
 
105
- it "only sets the field if it does not already exist" do
106
- @ot.guard(Array, :one)
107
- expect( @ot.>>.one ).to eq @hash2[:one]
108
- end
102
+ context "when given a class" do
103
+
104
+ it "accepts a class and list of keys" do
105
+ @ot.guard(Array, :fred, :daphne, "velma")
106
+ otHash = @ot.to_h
107
+ expect( otHash[:fred] ).to eq([])
108
+ expect( otHash[:daphne] ).to eq([])
109
+ expect( otHash[:velma] ).to eq([])
110
+ end
111
+
112
+ it "sets the given fields with a default value for the class" do
113
+ @ot.guard(Array, :alpha)
114
+ @ot.guard(Hash, :beta)
115
+
116
+ expect( @ot.>>.alpha ).to eq([])
117
+ expect( @ot.>>.beta ).to eq({})
118
+ end
119
+
120
+ it "only sets the field if it does not already exist" do
121
+ @ot.guard(Array, :one)
122
+ expect( @ot.>>.one ).to eq @hash2[:one]
123
+ end
109
124
 
110
- it "accepts a list of keys" do
111
- @ot.guard(Array, :fred, :daphne, "velma")
112
- otHash = @ot.to_h
113
- expect( otHash[:fred] ).to eq([])
114
- expect( otHash[:daphne] ).to eq([])
115
- expect( otHash[:velma] ).to eq([])
116
125
  end
117
126
 
118
- it "raises Octothorpe::Frozen if the OT is frozen" do
119
- @ot.freeze
120
- expect{ @ot.guard(Hash, :foo) }.to raise_exception Octothorpe::Frozen
127
+ context "when given a block" do
128
+ let(:foo) do
129
+ @ot.guard(:fred, :daphne, "velma"){|k| "jinks!" }
130
+ end
131
+
132
+ it "accepts a list of keys" do
133
+ expect{foo}.not_to raise_exception
134
+ end
135
+
136
+ it "sets the given fields using the block" do
137
+ otHash = foo.to_h
138
+ expect( otHash[:fred] ).to eq("jinks!")
139
+ expect( otHash[:daphne] ).to eq("jinks!")
140
+ expect( otHash[:velma] ).to eq("jinks!")
141
+ end
142
+
143
+ it "only sets the field if it does not already exist" do
144
+ otHash = foo.to_h
145
+ expect( otHash[:one] ).to eq("a")
146
+ expect( otHash[:'weird key'] ).to eq(4)
147
+ end
148
+
121
149
  end
122
150
 
151
+
123
152
  end
124
153
 
125
154
 
@@ -163,6 +192,47 @@ describe Octothorpe do
163
192
  end
164
193
 
165
194
 
195
+ describe "#==" do
196
+
197
+ context 'when passed a hash' do
198
+ it 'returns true if the hash has the same keys' do
199
+ expect( @ot == @hash2 ).to eq true
200
+ end
201
+
202
+ it 'returns true if the hash has the same keys but as strings' do
203
+ expect( @ot == @hash ).to eq true
204
+ end
205
+
206
+ it 'returns false if the hash has different keys' do
207
+ expect( @ot == {one: 1, four: 2} ).to eq false
208
+ end
209
+
210
+ it 'returns false if the hash has the same keys but different values' do
211
+ h = @hash.merge(one: 'oneone')
212
+ expect( @ot == h ).to eq false
213
+ end
214
+ end
215
+
216
+ context 'when passed an OT' do
217
+ it 'returns true if the ot has the same keys' do
218
+ ot2 = Octothorpe.new(@hash)
219
+ expect( @ot == ot2 ).to eq true
220
+ end
221
+
222
+ it 'returns false if the ot has different keys' do
223
+ ot2 = Octothorpe.new( @hash.merge(four: 4) )
224
+ expect( @ot == ot2 ).to eq false
225
+ end
226
+
227
+ it 'returns false if the ot has the same keys but different values' do
228
+ ot2 = @hash.merge(one: 'oneone')
229
+ expect( @ot == ot2 ).to eq false
230
+ end
231
+ end
232
+
233
+ end
234
+
235
+
166
236
  describe "#>" do
167
237
 
168
238
  context 'when passed a hash' do
@@ -213,10 +283,39 @@ describe Octothorpe do
213
283
  end
214
284
 
215
285
 
286
+ ##
287
+ # Even blindfold I think it's fair to say that I must have implemented >= using > and ==, but a
288
+ # quick happy path test just to be paranoid...
289
+ #
290
+ describe '#<=' do
291
+
292
+ it "returns true if the other is a subset" do
293
+ expect( @ot <= @superset ).to eq true
294
+ end
295
+
296
+ it "returns true if the other is the same" do
297
+ expect( @ot <= @hash2 ).to eq true
298
+ end
299
+
300
+ end
301
+
302
+
303
+ describe '#>=' do
304
+
305
+ it "returns true if the other is a superset" do
306
+ expect( @ot >= @subset ).to eq true
307
+ end
308
+
309
+ it "returns true if the other is the same" do
310
+ expect( @ot >= @hash2 ).to eq true
311
+ end
312
+
313
+ end
314
+
315
+
216
316
  describe "(miscelaneous other stuff)" do
217
- # I "imagine" that the actual class uses Forwardable, but the test code
218
- # shouldn't know or care about that. In any case, just testing with
219
- # responds_to always feels like cheating.
317
+ # I "imagine" that the actual class uses Forwardable, but the test code shouldn't know or care
318
+ # about that. In any case, just testing with responds_to always feels like cheating.
220
319
 
221
320
  it "behaves like a Hash for a bunch of query methods" do
222
321
  expect( @ot.empty? ).not_to eq true
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: octothorpe
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.0
4
+ version: 0.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Andy Jones
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2016-04-19 00:00:00.000000000 Z
11
+ date: 2016-06-29 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler