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 +4 -4
- data/.hgignore +1 -0
- data/.hgtags +1 -0
- data/README.md +29 -23
- data/lib/octothorpe.rb +53 -44
- data/spec/octothorpe_spec.rb +121 -22
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 4eb5a75a4f6e96e372ad54747fd18dbe5246e0e6
|
4
|
+
data.tar.gz: d8c0c8a82fef44e3c160bd327d4d1d32f6ce3e50
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 7abfb6d20290533da8a8a8e3357bf6244748c8479b1c5da22b1b7c852d4ca33e792ee72423dc48caf845209261b083494d11ab9642de5ad7fc331b91a7a05579
|
7
|
+
data.tar.gz: af00c9772425af4d6d4b40b729305af4126e2d8155bc15d438e6b8d2dd519c6bc5755463492a51fd64b52ff3d1b1579a754c83b1b976f0d3f32b1353e60eb6bb
|
data/.hgignore
CHANGED
data/.hgtags
CHANGED
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
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
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
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
68
|
+
```ruby
|
69
|
+
hash && (hash[:key] || {})[4]
|
70
|
+
```
|
65
71
|
|
66
|
-
...then this might just possibly be
|
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
|
-
|
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
|
-
|
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
|
|
data/lib/octothorpe.rb
CHANGED
@@ -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.
|
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
|
-
#
|
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
|
-
#
|
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
|
-
#
|
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
|
-
#
|
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
|
-
#
|
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
|
-
#
|
141
|
-
#
|
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
|
-
#
|
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
|
-
#
|
147
|
-
#
|
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(
|
149
|
+
def guard(*args)
|
150
150
|
raise Frozen if self.frozen?
|
151
|
-
|
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
|
-
#
|
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
|
-
|
195
|
-
|
196
|
-
|
197
|
-
|
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
|
|
data/spec/octothorpe_spec.rb
CHANGED
@@ -90,36 +90,65 @@ describe Octothorpe do
|
|
90
90
|
|
91
91
|
describe "#guard" do
|
92
92
|
|
93
|
-
it "
|
94
|
-
@ot.
|
95
|
-
@ot.guard(Hash,
|
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
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
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
|
-
|
119
|
-
|
120
|
-
|
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
|
-
#
|
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.
|
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-
|
11
|
+
date: 2016-06-29 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|