octothorpe 0.2.0 → 0.3.0

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.
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