bahuvrihi-constants 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,133 @@
1
+ require 'constants/constant_library'
2
+
3
+ module Constants
4
+
5
+ # Library adds methods for convenient indexing and access of constants
6
+ # in a module. Usually Library is included after the constants have
7
+ # been defined.
8
+ #
9
+ # module Color
10
+ # RED = 'red'
11
+ # GREEN = 'green'
12
+ # BLUE = 'blue'
13
+ # GREY = 'grey'
14
+ #
15
+ # include Constants::Library
16
+ # library.index_by('name') {|c| c }
17
+ # end
18
+ #
19
+ # Now the Color constants can be accessed through the indicies hash
20
+ # or through [], which searches all indicies for the first match.
21
+ #
22
+ # Color.index('name')
23
+ # # => {
24
+ # # 'red' => Color::RED,
25
+ # # 'blue' => Color::BLUE,
26
+ # # 'green' => Color::GREEN,
27
+ # # 'grey' => Color::GREY}
28
+ #
29
+ # Color['red'] # => Color::RED
30
+ #
31
+ # Indexing is simplified for attributes. Notice that multiple
32
+ # values matching the same key are stashed into one group:
33
+ #
34
+ # Color.library.index_by_attribute 'length'
35
+ # Color.index('length')
36
+ # # => {
37
+ # # 3 => Color::RED,
38
+ # # 4 => [Color::BLUE, Color::GREY],
39
+ # # 5 => Color::GREEN}
40
+ #
41
+ # Color[4] # => [Color::BLUE, Color::GREY]
42
+ #
43
+ # Constants may also be assembled into ordered collections, which
44
+ # may or may not contain all the constants.
45
+ #
46
+ # Color.library.collect('gstar') {|c| c =~ /^g/ ? c : nil }
47
+ # Color.collection('gstar') # => [Color::GREEN, Color::GREY]
48
+ #
49
+ # Color.library.collect_attribute 'length'
50
+ # Color.collection('length') # => [3,5,4,4]
51
+ #
52
+ # New constants (even 'constants' that are not declared in the module) may be
53
+ # added manually, or by resetting the library. All indexes and collections
54
+ # are updated automatically.
55
+ #
56
+ # Color.library.add('yellow')
57
+ # Color.index('length')
58
+ # # => {
59
+ # # 3 => Color::RED,
60
+ # # 4 => [Color::BLUE, Color::GREY],
61
+ # # 5 => Color::GREEN,
62
+ # # 6 => 'yellow'}
63
+ #
64
+ # module Color
65
+ # ORANGE = 'orange'
66
+ # reset_library
67
+ # end
68
+ #
69
+ # Color.index('length')
70
+ # # => {
71
+ # # 3 => Color::RED,
72
+ # # 4 => [Color::BLUE, Color::GREY],
73
+ # # 5 => Color::GREEN,
74
+ # # 6 => Color::ORANGE}
75
+ #
76
+ # Notice 'yellow' was removed when the library was reset. The yellow example
77
+ # illustrates the fact that library only tracks the values of constants, not
78
+ # the constants themselves. If, for some reason, you dynamically reset a constant
79
+ # value then the change will not be reflected in the library until the library
80
+ # is reset.
81
+ #
82
+ # ==== Ruby 1.8
83
+ # Ruby doesn't track of the order of constant declaration until Ruby 1.9... this
84
+ # may cause collected values to be re-ordered in an unpredictable fashion. For
85
+ # instance in the Color example:
86
+ #
87
+ # Color[4] # may equal [Color::BLUE, Color::GREY] or [Color::GREY, Color::BLUE].
88
+ #
89
+ # In any case, the constants will be ordered as in Color.constants.
90
+ #
91
+ # == Performance Considerations
92
+ # ConstantsLibrary makes access of constants easier, but at the expense of
93
+ # performance. Naturally the constant itself is the highest-performing
94
+ # way of accessing the constant value.
95
+ #
96
+ module Library
97
+
98
+ # Accesses the module constant library.
99
+ attr_accessor :library
100
+
101
+ def self.included(mod)
102
+ mod.extend self
103
+ end
104
+
105
+ def self.extended(mod)
106
+ mod.library = ConstantLibrary.new
107
+ mod.reset_library
108
+ end
109
+
110
+ # Alias for library[identifier] (see Constants::ConstantLibrary#[])
111
+ def [](identifier)
112
+ library[identifier]
113
+ end
114
+
115
+ # Returns the index by the specified name.
116
+ def index(name)
117
+ library.indicies[name]
118
+ end
119
+
120
+ # Returns the collection by the specified name.
121
+ def collection(name)
122
+ library.collections[name]
123
+ end
124
+
125
+ # Resets the library using constants from self. A block
126
+ # can be provided to filter constants, see
127
+ # Constants::ConstantLibrary#add_constants_from
128
+ def reset_library(&block)
129
+ library.clear
130
+ library.add_constants_from(self, &block)
131
+ end
132
+ end
133
+ end
@@ -0,0 +1,94 @@
1
+ module Constants
2
+
3
+ # Stash provides methods to store values by a non-unique key. The
4
+ # initial stored value is directly stored by the key, subsequent
5
+ # values are grouped into a StashArray.
6
+ #
7
+ # class StashingHash < Hash
8
+ # include Constants::Stash
9
+ #
10
+ # def initialize(*args)
11
+ # super(*args)
12
+ # @nil_value = nil
13
+ # end
14
+ # end
15
+ #
16
+ # s = StashingHash.new
17
+ #
18
+ # s.stash('key', 'one')
19
+ # s['key'] # => 'one'
20
+ #
21
+ # s.stash('key', 'two')
22
+ # s['key'] # => ['one' , 'two']
23
+ # s['key'].class # => Constants::Stash::StashArray
24
+ #
25
+ # The stash method requires some kind of flag to differentiate when a new
26
+ # value should be stored and when the existing value and new value
27
+ # should be converted into a StashArray. If the existing value as
28
+ # determined by [] is equal to the nil_value, then the new value is
29
+ # stored through []=.
30
+ #
31
+ # s = StashingHash.new
32
+ # s['key'] # => nil
33
+ # s.nil_value # => nil
34
+ #
35
+ # s.stash('key', 1)
36
+ # s['key'] # => 1
37
+ #
38
+ # s.stash('key', 2)
39
+ # s['key'] # => [1, 2]
40
+ #
41
+ # In the first case, the existing value for 'key' is equals the nil_value,
42
+ # so the new value is set. In the second case, the existing value for 'key'
43
+ # does not equal the nil_value; stash takes this as a signal that a
44
+ # non-unique key was specified and collects the values into a StashArray.
45
+ # As a consequence, neither the nil_value nor StashArrays may be stashed.
46
+ #
47
+ # s.stash('key', nil) # ! ArgumentError
48
+ #
49
+ module Stash
50
+
51
+ # A subclass of Array with no new functionality, necessary
52
+ # to differentiate between regular arrays and collections
53
+ # in a Stash.
54
+ class StashArray < Array
55
+ end
56
+
57
+ # The value considered to be nil in store (used to
58
+ # signal when a new value can be stashed for a given
59
+ # key; if store[key] == nil_value, then a new value
60
+ # can be stashed).
61
+ attr_accessor :nil_value
62
+
63
+ # Assigns the value to key in store. If the store already has a
64
+ # non-nil_value at key (as determined by []), then the existing
65
+ # and new value will be concatenated into a StashArray. All
66
+ # subsequent values are added to the StashArray. stash uses
67
+ # the []= method to set values.
68
+ #
69
+ # nil_value and StashArray values cannot be stashed; either raises
70
+ # an error.
71
+ def stash(key, value)
72
+ case value
73
+ when nil_value
74
+ raise ArgumentError.new("the nil_value for self cannot be stashed")
75
+ when StashArray
76
+ raise ArgumentError.new("StashArrays cannot be stashed")
77
+ end
78
+
79
+ current_value = self[key]
80
+
81
+ case current_value
82
+ when nil_value
83
+ self[key] = value
84
+ when StashArray
85
+ current_value << value
86
+ else
87
+ self[key] = StashArray.new([current_value, value])
88
+ end
89
+
90
+ self
91
+ end
92
+ end
93
+
94
+ end
@@ -0,0 +1,8 @@
1
+ module Constants
2
+
3
+ # Simply defines the values for exact and unknown uncertainty.
4
+ module Uncertainty
5
+ UNKNOWN = nil
6
+ EXACT = 0
7
+ end
8
+ end
@@ -0,0 +1,304 @@
1
+ require File.join(File.dirname(__FILE__), '../constants_test_helper.rb')
2
+ require 'constants/constant_library'
3
+
4
+ class ConstantLibraryTest < Test::Unit::TestCase
5
+ include Constants
6
+
7
+ attr_accessor :lib
8
+
9
+ def setup
10
+ @lib = ConstantLibrary.new 'one', 'two', :one
11
+ end
12
+
13
+ #
14
+ # documentation test
15
+ #
16
+
17
+ def test_documentation
18
+ lib = ConstantLibrary.new('one', 'two', :three)
19
+ lib.index_by('upcase') {|value| value.to_s.upcase }
20
+ assert_equal({'ONE' => 'one', 'TWO' => 'two', 'THREE' => :three}, lib.indicies['upcase'])
21
+
22
+ lib.collect("string") {|value| value.to_s }
23
+ assert_equal(['one', 'two', 'three'], lib.collections['string'])
24
+ end
25
+
26
+ #
27
+ # initialize test
28
+ #
29
+
30
+ def test_initialize
31
+ lib = ConstantLibrary.new
32
+
33
+ assert_equal([], lib.values)
34
+ assert_equal({}, lib.indicies)
35
+ assert_equal({}, lib.collections)
36
+ end
37
+
38
+ def test_initialize_with_values
39
+ assert_equal(['one', 'two', :one], lib.values)
40
+ end
41
+
42
+ def test_initialize_removes_duplicate_values
43
+ lib = ConstantLibrary.new 'one', 'two', 'one'
44
+ assert_equal(['one', 'two'], lib.values)
45
+ end
46
+
47
+ #
48
+ # index_by test
49
+ #
50
+
51
+ def test_index_by_documentation
52
+ lib = ConstantLibrary.new('one', 'two', :one)
53
+ lib.index_by("string") {|value| value.to_s }
54
+ assert_equal({
55
+ 'one' => ['one', :one],
56
+ 'two' => 'two'},
57
+ lib.indicies['string'])
58
+
59
+ lib = ConstantLibrary.new(1,2,nil)
60
+ assert_raise(ArgumentError) { lib.index_by("error", false, nil) {|value| value } }
61
+
62
+
63
+ obj = Object.new
64
+ index = lib.index_by("ok", false, obj) {|value| value }
65
+ assert_equal 1, index[1]
66
+ assert_equal nil, index[nil]
67
+
68
+ assert_equal obj, index['non-existant']
69
+ end
70
+
71
+ def test_index_by_creates_a_new_index_for_the_specified_inputs
72
+ block = lambda {|v| }
73
+ lib.index_by("name", "nil", "nil_value", &block)
74
+
75
+ assert lib.indicies.has_key?('name')
76
+ index = lib.indicies['name']
77
+
78
+ assert_equal ConstantLibrary::Index, index.class
79
+ assert_equal "nil", index.exclusion_value
80
+ assert_equal "nil_value", index.nil_value
81
+ assert_equal block, index.block
82
+ end
83
+
84
+ def test_index_by_uses_name_as_indicies_key
85
+ lib.index_by(:sym) {|v| v.to_s }
86
+ assert lib.indicies.has_key?(:sym)
87
+ assert !lib.indicies.has_key?('sym')
88
+
89
+ lib.index_by('str') {|v| v.to_s }
90
+ assert !lib.indicies.has_key?(:str)
91
+ assert lib.indicies.has_key?('str')
92
+ end
93
+
94
+ def test_index_stashes_values_by_block
95
+ lib.index_by("string") {|v| v.to_s }
96
+ assert_equal({'one' => ['one', :one], 'two' => 'two'}, lib.indicies["string"])
97
+ end
98
+
99
+ def test_index_stashes_key_value_pairs_if_returned
100
+ lib.index_by("pairs") {|v| [v, v.to_s.upcase] }
101
+ assert_equal({'one' => 'ONE', :one => 'ONE', 'two' => 'TWO'}, lib.indicies["pairs"])
102
+ end
103
+
104
+ def test_index_excludes_values_which_return_the_exclusion_value
105
+ lib.index_by("exclusion", nil) do |v|
106
+ v.kind_of?(String) ? nil : [v, v.to_s.upcase]
107
+ end
108
+
109
+ assert_equal({:one => 'ONE'}, lib.indicies["exclusion"])
110
+ end
111
+
112
+ def test_index_by_default_exclusion_value_is_nil
113
+ lib.index_by("name") {|v|}
114
+ assert_equal nil, lib.indicies['name'].exclusion_value
115
+ end
116
+
117
+ def test_index_by_raises_error_for_no_block_given
118
+ assert_raise(ArgumentError) { lib.index_by('name') }
119
+ end
120
+
121
+ def test_index_by_returns_index
122
+ result = lib.index_by("name") {|v|}
123
+ assert_equal(lib.indicies['name'], result)
124
+ end
125
+
126
+ #
127
+ # index_by_attribute test
128
+ #
129
+
130
+ def test_index_by_attribute_stashes_values_by_method_value
131
+ lib.index_by_attribute("to_s")
132
+ assert_equal({'one' => ['one', :one], 'two' => 'two'}, lib.indicies["to_s"])
133
+ end
134
+
135
+ def test_index_by_attribute_returns_index
136
+ result = lib.index_by_attribute("object_id")
137
+ assert_equal(lib.indicies['object_id'], result)
138
+ end
139
+
140
+ def test_index_by_attribute_raises_error_if_objects_dont_respond_to_attribute
141
+ assert_raise(NoMethodError) { lib.index_by_attribute("non_existant") }
142
+ end
143
+
144
+ #
145
+ # collect test
146
+ #
147
+
148
+ def test_collect_documentation
149
+ lib = ConstantLibrary.new('one', 'two', :three)
150
+ lib.collect("string") {|value| value.to_s }
151
+ assert_equal ['one', 'two', 'three'], lib.collections['string']
152
+
153
+ lib.collect("length") {|value| [value, value.to_s.length] }
154
+ assert_equal [nil, nil, nil, ['one', 'two'], nil, :three], lib.collections['length']
155
+ end
156
+
157
+ def test_collect_creates_a_new_collection_for_the_specified_inputs
158
+ block = lambda {|v| }
159
+ lib.collect("name", &block)
160
+
161
+ assert lib.collections.has_key?('name')
162
+ collection = lib.collections['name']
163
+
164
+ assert_equal ConstantLibrary::Collection, collection.class
165
+ assert_equal nil, collection.nil_value
166
+ assert_equal block, collection.block
167
+ end
168
+
169
+ def test_collect_uses_name_as_collections_key
170
+ lib.collect(:sym) {|v| v.to_s }
171
+ assert lib.collections.has_key?(:sym)
172
+ assert !lib.collections.has_key?('sym')
173
+
174
+ lib.collect('str') {|v| v.to_s }
175
+ assert !lib.collections.has_key?(:str)
176
+ assert lib.collections.has_key?('str')
177
+ end
178
+
179
+ def test_collection_stashes_block_value
180
+ lib.collect("string") {|v| v.to_s }
181
+ assert_equal(['one', 'two', 'one'], lib.collections["string"])
182
+ end
183
+
184
+ def test_collection_stashes_values_index_pairs_if_returned
185
+ map = {'one' => 2, 'two' => 0}
186
+
187
+ lib.collect("pairs") {|v| [v, map[v.to_s]] }
188
+ assert_equal(['two', nil, ['one', :one]], lib.collections["pairs"])
189
+ end
190
+
191
+ def test_collection_excludes_values_which_return_nil
192
+ lib.collect("exclusion") do |v|
193
+ v.kind_of?(String) ? nil : v
194
+ end
195
+ assert_equal([:one], lib.collections["exclusion"])
196
+ end
197
+
198
+ def test_collect_raises_error_for_no_block_given
199
+ assert_raise(ArgumentError) { lib.collect('name') }
200
+ end
201
+
202
+ def test_collect_returns_collection
203
+ result = lib.collect("name") {|v|}
204
+ assert_equal(lib.collections['name'], result)
205
+ end
206
+
207
+ #
208
+ # collect_attribute test
209
+ #
210
+
211
+ def test_collect_attribute_collects_attribute_values
212
+ lib.collect_attribute("to_s")
213
+ assert_equal(['one', 'two', 'one'], lib.collections["to_s"])
214
+ end
215
+
216
+ #
217
+ # [] test
218
+ #
219
+
220
+ def test_get_searches_all_indicies_match
221
+ lib.index_by("string") {|v| v.to_s }
222
+ lib.index_by("strlen") {|v| v.to_s.length }
223
+
224
+ assert_equal ['one', :one], lib['one']
225
+ assert_equal ['one', 'two', :one], lib[3]
226
+ end
227
+
228
+ def test_get_returns_nil_if_no_matches_are_found
229
+ assert_equal nil, lib['three']
230
+ end
231
+
232
+ def test_get_returns_first_match_only
233
+ lib.index_by("str1") {|v| v.to_s }
234
+ lib.index_by("str2") {|v| [v.to_s, v.to_s.upcase] }
235
+
236
+ assert lib.indicies['str1'].has_key?('one')
237
+ assert lib.indicies['str2'].has_key?('one')
238
+
239
+ assert_not_equal lib.indicies['str1']['one'], lib.indicies['str2']['one']
240
+ assert_equal lib.indicies['str1']['one'], lib['one']
241
+ end
242
+
243
+ #
244
+ # clear test
245
+ #
246
+
247
+ def test_clear_clears_all_values_from_lib_indicies_and_collections
248
+ lib.index_by("str") {|v| v.to_s }
249
+ lib.collect("str") {|v| v.to_s }
250
+
251
+ assert !lib.values.empty?
252
+ assert !lib.indicies['str'].empty?
253
+ assert !lib.collections['str'].empty?
254
+
255
+ lib.clear
256
+
257
+ assert lib.values.empty?
258
+ assert lib.indicies['str'].empty?
259
+ assert lib.collections['str'].empty?
260
+ end
261
+
262
+ def test_clear_only_removes_indicies_and_collections_if_specified
263
+ lib.index_by("str") {|v| v.to_s }
264
+ lib.collect("str") {|v| v.to_s }
265
+
266
+ lib.clear
267
+
268
+ assert !lib.indicies.empty?
269
+ assert !lib.collections.empty?
270
+
271
+ lib.clear(true)
272
+
273
+ assert lib.indicies.empty?
274
+ assert lib.collections.empty?
275
+ end
276
+
277
+ #
278
+ # add test
279
+ #
280
+
281
+ def test_add_adds_new_values
282
+ lib.add(:two, 'three')
283
+ assert_equal ['one', 'two', :one, :two, 'three'], lib.values
284
+ end
285
+
286
+ def test_add_does_not_add_duplicate_values
287
+ lib.add(:one, :two, 'two', 'three')
288
+ assert_equal ['one', 'two', :one, :two, 'three'], lib.values
289
+ end
290
+
291
+ def test_add_returns_newly_added_values
292
+ assert_equal [:two, 'three'], lib.add(:one, :two, 'two', 'three')
293
+ end
294
+
295
+ def test_add_incorporates_new_values_into_existing_indicies_and_collections
296
+ lib.index_by("str") {|v| v.to_s }
297
+ lib.collect("length") {|v| [v, v.to_s.length] }
298
+
299
+ lib.add(:one, :two, 'two', 'three')
300
+ assert_equal({'one' => ['one', :one], 'two' => ['two', :two], 'three' => 'three'}, lib.indicies['str'])
301
+ assert_equal([nil,nil,nil,['one', 'two', :one, :two], nil, 'three'], lib.collections['length'])
302
+ end
303
+
304
+ end