attribute_struct 0.1.8 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
data/CHANGELOG.md CHANGED
@@ -1,3 +1,9 @@
1
+ ## v0.2.0
2
+ * Add support for value setting into given context level
3
+ * Add #build helper method
4
+ * Introduce `:value_collapse` option for multi set combination instead of replacement
5
+ * Provide bang suffix aliases
6
+
1
7
  ## v0.1.8
2
8
  * Basic (optional) auto camel detection on import
3
9
  * Add hash helper methods out to class
data/CONTRIBUTING.md ADDED
@@ -0,0 +1,25 @@
1
+ # Contributing
2
+
3
+ ## Branches
4
+
5
+ ### `master` branch
6
+
7
+ The master branch is the current stable released version.
8
+
9
+ ### `develop` branch
10
+
11
+ The develop branch is the current edge of development.
12
+
13
+ ## Pull requests
14
+
15
+ * https://github.com/chrisroberts/attribute_struct/pulls
16
+
17
+ Please base all pull requests off the `develop` branch. Merges to
18
+ `master` only occur through the `develop` branch. Pull requests
19
+ based on `master` will likely be cherry picked.
20
+
21
+ ## Issues
22
+
23
+ Need to report an issue? Use the github issues:
24
+
25
+ * https://github.com/chrisroberts/attribute_struct/issues
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- attribute_struct (0.1.7)
4
+ attribute_struct (0.1.9)
5
5
  hashie (>= 2.0.0)
6
6
 
7
7
  GEM
data/LICENSE ADDED
@@ -0,0 +1,13 @@
1
+ Copyright 2014 Chris Roberts
2
+
3
+ Licensed under the Apache License, Version 2.0 (the "License");
4
+ you may not use this file except in compliance with the License.
5
+ You may obtain a copy of the License at
6
+
7
+ http://www.apache.org/licenses/LICENSE-2.0
8
+
9
+ Unless required by applicable law or agreed to in writing, software
10
+ distributed under the License is distributed on an "AS IS" BASIS,
11
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ See the License for the specific language governing permissions and
13
+ limitations under the License.
@@ -5,6 +5,7 @@ Gem::Specification.new do |s|
5
5
  s.version = AttributeStruct::VERSION.version
6
6
  s.summary = 'Attribute structures'
7
7
  s.author = 'Chris Roberts'
8
+ s.license = 'Apache 2.0'
8
9
  s.email = 'chrisroberts.code@gmail.com'
9
10
  s.homepage = 'http://github.com/chrisroberts/attribute_struct'
10
11
  s.description = 'Attribute structures'
@@ -5,5 +5,13 @@ class AttributeStruct
5
5
  class AttributeHash < ::Hash
6
6
  include ::Hashie::Extensions::DeepMerge
7
7
  include ::Hashie::Extensions::IndifferentAccess
8
+
9
+ def to_hash
10
+ ::Hash[
11
+ self.map do |k,v|
12
+ [k, v.is_a?(::Hash) ? v.to_hash : v]
13
+ end
14
+ ]
15
+ end
8
16
  end
9
17
  end
@@ -2,13 +2,15 @@ class AttributeStruct < BasicObject
2
2
 
3
3
  class << self
4
4
 
5
- # Global flag for camel cased keys
5
+ # @return [Truthy, Falsey] global flag for camel keys
6
6
  attr_reader :camel_keys
7
- # Force tooling from Chef
7
+ # @return [Truthy, Falsey] force chef tooling (Mash)
8
8
  attr_accessor :force_chef
9
9
 
10
- # val:: bool
11
10
  # Automatically converts keys to camel case
11
+ #
12
+ # @param val [TrueClass, FalseClass]
13
+ # @return [TrueClass, FalseClass]
12
14
  def camel_keys=(val)
13
15
  load_the_camels if val
14
16
  @camel_keys = !!val
@@ -22,7 +24,7 @@ class AttributeStruct < BasicObject
22
24
  end
23
25
  end
24
26
 
25
- # Determines what hash library to load based on availability
27
+ # Determine what hash library to load based on availability
26
28
  def load_the_hash
27
29
  unless(@hash_loaded)
28
30
  if(defined?(Chef) || force_chef)
@@ -36,47 +38,97 @@ class AttributeStruct < BasicObject
36
38
  end
37
39
  end
38
40
 
41
+ # @return [AttributeStruct::AttributeHash, Mash]
39
42
  def hashish
40
43
  load_the_hash
41
44
  @hash_loaded == :chef ? ::Mash : ::AttributeStruct::AttributeHash
42
45
  end
43
46
 
47
+ # Create AttributeStruct instance and dump the resulting hash
48
+ def build(&block)
49
+ raise ArgumentError.new 'Block required for build!' unless block
50
+ new(&block)._dump
51
+ end
52
+
44
53
  end
45
54
 
46
- # Flag for camel cased keys
55
+ # @return [Truthy, Falsey] current camelizing setting
47
56
  attr_reader :_camel_keys
48
-
49
- def initialize(*args, &block)
57
+ # @return [AtributeStruct::AttributeHash, Mash] holding space for state
58
+ attr_reader :_arg_state
59
+
60
+ # Create new instance
61
+ #
62
+ # @param init_hash [Hash] hash to initialize struct
63
+ # @yield block to execute within struct context
64
+ def initialize(init_hash=nil, &block)
50
65
  _klass.load_the_hash
51
66
  @_camel_keys = _klass.camel_keys
67
+ @_arg_state = self.class.hashish.new
52
68
  @table = __hashish.new
53
- unless(args.empty?)
54
- if(args.size == 1 && args.first.is_a?(::Hash))
55
- _load(args.first)
56
- end
69
+ if(init_hash)
70
+ _load(init_hash)
57
71
  end
58
72
  if(block)
59
73
  self.instance_exec(&block)
60
74
  end
61
75
  end
62
76
 
63
- # val:: bool
64
- # Turn camel cased keys on/off
77
+ # Execute block within current context
78
+ #
79
+ # @yield block to execute
80
+ # @return [Object]
81
+ def _build(&block)
82
+ self.instance_exec(&block)
83
+ end
84
+
85
+ # Set state into current context
86
+ #
87
+ # @param args [Hashish] hashish type holding data for context
88
+ # @return [Hashish]
89
+ def _set_state(args={})
90
+ _arg_state.merge!(args)
91
+ end
92
+
93
+ # Value of requested state
94
+ #
95
+ # @param key [Symbol, String]
96
+ # @param traverse [TrueClass, FalseClass] traverse towards root for matching key
97
+ # @return [Object, NilClass]
98
+ def _state(key, traverse=true)
99
+ if(_arg_state.keys.include?(key))
100
+ _arg_state[key]
101
+ else
102
+ if(traverse && _parent)
103
+ _parent._state(key)
104
+ end
105
+ end
106
+ end
107
+
108
+ # Enable/disable camel keys
109
+ #
110
+ # @param val [TrueClass, FalseClass]
111
+ # @return [TrueClass, FalseClass]
65
112
  def _camel_keys=(val)
66
113
  _klass.load_the_camels if val
67
114
  @_camel_keys = !!val
68
115
  end
69
116
 
70
- # key:: Object
71
- # Access data directly
117
+ # Direct data access
118
+ #
119
+ # @param key [String, Symbol]
120
+ # @return [Object]
72
121
  def [](key)
73
122
  _data[_process_key(key)]
74
123
  end
75
124
 
76
- # key:: Object
77
- # val:: Object
78
- # Directly set val into struct. Useful when key is not valid syntax
79
- # for a ruby method
125
+ # Directly set value into struct. Useful when the key
126
+ # is not valid ruby syntax for a method
127
+ #
128
+ # @param key [String, Symbol]
129
+ # @param val [Object]
130
+ # @yield block to execute within context
131
+ # @return [Object]
80
132
  def _set(key, val=nil, &block)
81
133
  if(val)
82
134
  self.method_missing(key, val, &block)
@@ -84,42 +136,73 @@ class AttributeStruct < BasicObject
84
136
  self.method_missing(key, &block)
85
137
  end
86
138
  end
87
-
88
- # Dragons and unicorns all over in here
139
+ alias_method :set!, :_set
140
+
141
+ # Provides struct DSL behavior
142
+ #
143
+ # @param sym [Symbol, String] method name
144
+ # @param args [Object] argument list
145
+ # @yield provided block
146
+ # @return [Object] existing value or newly set value
147
+ # @note Dragons and unicorns all over in here
89
148
  def method_missing(sym, *args, &block)
90
149
  if((s = sym.to_s).end_with?('='))
91
150
  s.slice!(-1, s.length)
92
151
  sym = s
93
152
  end
94
153
  sym = _process_key(sym)
95
- @table[sym] ||= _klass_new
96
154
  if(!args.empty? || block)
97
155
  if(args.empty? && block)
98
- base = @table[sym]
156
+ if(_state(:value_collapse))
157
+ orig = @table.fetch(sym, :__unset__)
158
+ end
159
+ base = _klass_new
99
160
  if(block.arity == 0)
100
161
  base.instance_exec(&block)
101
162
  else
102
163
  base.instance_exec(base, &block)
103
164
  end
104
- @table[sym] = base
165
+ if(orig.is_a?(::NilClass))
166
+ @table[sym] = base
167
+ else
168
+ if(orig == :__unset__)
169
+ @table[sym] = base
170
+ else
171
+ orig = [orig] unless orig.is_a?(::Array)
172
+ orig << base
173
+ @table[sym] = orig
174
+ end
175
+ end
105
176
  elsif(!args.empty? && block)
106
177
  base = @table[sym]
107
178
  base = _klass_new unless base.is_a?(_klass)
108
- @table[sym] = base
109
179
  leaf = base
180
+ key = sym
110
181
  args.each do |arg|
111
182
  leaf = base[arg]
183
+ key = arg
112
184
  unless(leaf.is_a?(_klass))
113
185
  leaf = _klass_new
114
186
  base._set(arg, leaf)
115
187
  base = leaf
116
188
  end
117
189
  end
190
+ if(!leaf.nil? && _state(:value_collapse))
191
+ orig = leaf
192
+ leaf = orig.parent._klass_new
193
+ end
118
194
  if(block.arity == 0)
119
195
  leaf.instance_exec(&block)
120
196
  else
121
197
  leaf.instance_exec(leaf, &block)
122
198
  end
199
+ if(orig)
200
+ orig = [orig] unless orig.is_a?(::Array)
201
+ orig << leaf
202
+ else
203
+ orig = leaf
204
+ end
205
+ @table[sym] = orig
123
206
  else
124
207
  if(args.size > 1)
125
208
  @table[sym] = _klass_new unless @table[sym].is_a?(_klass)
@@ -131,42 +214,54 @@ class AttributeStruct < BasicObject
131
214
  end
132
215
  return endpoint # custom break out
133
216
  else
134
- @table[sym] = args.first
217
+ if(_state(:value_collapse) && !(leaf = @table[sym]).nil?)
218
+ leaf = [leaf] unless leaf.is_a?(::Array)
219
+ leaf << args.first
220
+ @table[sym] = leaf
221
+ else
222
+ @table[sym] = args.first
223
+ end
135
224
  end
136
225
  end
137
226
  end
227
+ @table[sym] = _klass_new if @table[sym].nil? && !@table[sym].is_a?(_klass)
138
228
  @table[sym]
139
229
  end
140
230
 
141
- # Returns if this struct is considered nil (empty data)
231
+ # @return [TrueClass, FalseClass] struct is nil (empty data)
142
232
  def nil?
143
233
  _data.empty?
144
234
  end
145
235
 
146
- # klass:: Class
147
- # Returns if this struct is a klass
236
+ # Determine if self is a class
237
+ #
238
+ # @param klass [Class]
239
+ # @return [TrueClass, FalseClass]
148
240
  def is_a?(klass)
149
241
  klass.ancestors.include?(_klass)
150
242
  end
151
243
  alias_method :kind_of?, :is_a?
152
244
 
153
- # Returns current keys within struct
245
+ # @return [Array<String,Symbol>] keys within struct
154
246
  def _keys
155
247
  _data.keys
156
248
  end
157
249
 
158
- # Returns underlying data hash
250
+ # @return [AttributeStruct::AttributeHash, Mash] underlying struct data
159
251
  def _data
160
252
  @table
161
253
  end
162
254
 
163
- # key:: Object
164
- # Delete entry in struct with key
255
+ # Delete entry from struct
256
+ #
257
+ # @param key [String, Symbol]
258
+ # @return [Object] value of entry
165
259
  def _delete(key)
166
260
  _data.delete(_process_key(key))
167
261
  end
262
+ alias_method :delete!, :_delete
168
263
 
169
- # Dumps the current instance to a Hash
264
+ # @return [AttributeStruct::AttributeHash, Mash] dump struct to hashish
170
265
  def _dump
171
266
  processed = @table.map do |key, value|
172
267
  if(value.is_a?(::Enumerable))
@@ -183,9 +278,12 @@ class AttributeStruct < BasicObject
183
278
  end
184
279
  __hashish[*processed.flatten(1)]
185
280
  end
281
+ alias_method :dump!, :_dump
186
282
 
187
- # hashish:: Hash type object
188
- # Clears current instance data and replaces with provided hash
283
+ # Clear current struct data and replace
284
+ #
285
+ # @param hashish [Hash] hashish type instance
286
+ # @return [self]
189
287
  def _load(hashish)
190
288
  @table.clear
191
289
  if(_root._camel_keys_action == :auto_discovery)
@@ -213,11 +311,13 @@ class AttributeStruct < BasicObject
213
311
  self
214
312
  end
215
313
 
216
- # target:: AttributeStruct
217
- # Performs a deep merge and returns the resulting AttributeStruct
218
- def _merge(target)
314
+ # Perform deep merge
315
+ #
316
+ # @param overlay [AttributeStruct]
317
+ # @return [AttributeStruct] newly merged instance
318
+ def _merge(overlay)
219
319
  source = _deep_copy
220
- dest = target._deep_copy
320
+ dest = overlay._deep_copy
221
321
  if(defined?(::Chef))
222
322
  result = ::Chef::Mixin::DeepMerge.merge(source, dest)
223
323
  else
@@ -226,21 +326,26 @@ class AttributeStruct < BasicObject
226
326
  _klass.new(result)
227
327
  end
228
328
 
229
- # target:: AttributeStruct
230
- # Performs a deep merge and updates the current instance with the
231
- # resulting value
232
- def _merge!(target)
233
- result = _merge(target)._dump
329
+ # Perform deep merge in place
330
+ #
331
+ # @param overlay [AttributeStruct]
332
+ # @return [self]
333
+ def _merge!(overlay)
334
+ result = _merge(overlay)._dump
234
335
  _load(result)
235
336
  self
236
337
  end
237
338
 
238
- # Returns a new Hash type instance based on what is available
339
+ # @return [Class] hashish type available
239
340
  def __hashish
240
341
  defined?(::Chef) ? ::Mash : ::AttributeStruct::AttributeHash
241
342
  end
242
343
 
243
- # Returns dup of value. Converts Symbol objects to strings
344
+ # Provide dup of instance
345
+ #
346
+ # @param v [Object]
347
+ # @return [Object] duped instance
348
+ # @note if Symbol provided, String is returned
244
349
  def _do_dup(v)
245
350
  begin
246
351
  v.dup
@@ -249,8 +354,10 @@ class AttributeStruct < BasicObject
249
354
  end
250
355
  end
251
356
 
252
- # thing:: Object
253
- # Returns a proper deep copy
357
+ # Create a "deep" copy
358
+ #
359
+ # @param thing [Object] struct to copy. defaults to self
360
+ # @return [Object] new instance
254
361
  def _deep_copy(thing=nil)
255
362
  thing ||= _dump
256
363
  if(thing.is_a?(::Enumerable))
@@ -264,8 +371,11 @@ class AttributeStruct < BasicObject
264
371
  val
265
372
  end
266
373
 
267
- # key:: String or Symbol
268
- # Processes the key and returns value based on current settings
374
+ # Provide expected key format based on context
375
+ #
376
+ # @param key [String, Symbol]
377
+ # @param args [Object] argument list (:force will force processing)
378
+ # @return [String, Symbol]
269
379
  def _process_key(key, *args)
270
380
  key = key.to_s
271
381
  if(_camel_keys && _camel_keys_action)
@@ -288,14 +398,16 @@ class AttributeStruct < BasicObject
288
398
  key
289
399
  end
290
400
  end
401
+ alias_method :process_key!, :_process_key
291
402
 
292
- # Helper to return class of current instance
403
+ # @return [Class] this class
293
404
  def _klass
294
405
  ::AttributeStruct
295
406
  end
296
407
  alias_method :class, :_klass
297
408
 
298
- # Helper to return new instance of current instance type
409
+ # @return [AttributeStruct] new struct instance
410
+ # @note will set self as parent and propogate camelizing status
299
411
  def _klass_new
300
412
  n = _klass.new
301
413
  unless(_camel_keys_action == :auto_discovery)
@@ -306,22 +418,26 @@ class AttributeStruct < BasicObject
306
418
  n
307
419
  end
308
420
 
309
- # v:: Symbol (:auto_disable, :auto_enable)
310
- # Sets custom rule for processed keys
421
+ # Set custom rule for processed keys at this context level
422
+ #
423
+ # @param v [Symbol] :auto_disable or :auto_enable
424
+ # @return [Symbol]
311
425
  def _camel_keys_set(v)
312
426
  @_camel_keys_set = v
313
427
  end
314
428
 
315
- # Returns value set via #_camel_keys_set
429
+ # @return [Symbol, NilClass] :auto_disable or :auto_enable
316
430
  def _camel_keys_action
317
431
  @_camel_keys_set
318
432
  end
319
433
 
434
+ # @return [AttributeStruct, NilClass] parent of this struct
320
435
  def _parent(obj=nil)
321
436
  @_parent = obj if obj
322
437
  @_parent
323
438
  end
324
439
 
440
+ # @return [AttributeStruct, NilClass] root of the struct or nil if self is root
325
441
  def _root
326
442
  r = self
327
443
  until(r._parent.nil?)
@@ -330,10 +446,10 @@ class AttributeStruct < BasicObject
330
446
  r
331
447
  end
332
448
 
333
- # args:: Objects
334
- # Helper to create Arrays with nested AttributeStructs. Proc
335
- # instances are automatically executed into new AttributeStruct
336
- # instances
449
+ # Create an Array and evaluate discovered AttributeStructs
450
+ #
451
+ # @param args [Object] array contents
452
+ # @return [Array]
337
453
  def _array(*args)
338
454
  args.map do |maybe_block|
339
455
  if(maybe_block.is_a?(::Proc))
@@ -349,5 +465,6 @@ class AttributeStruct < BasicObject
349
465
  end
350
466
  end
351
467
  end
468
+ alias_method :array!, :_array
352
469
 
353
470
  end
@@ -5,7 +5,7 @@ module MonkeyCamels
5
5
  klass.class_eval do
6
6
 
7
7
  include Humps
8
-
8
+
9
9
  alias_method :un_camel_to_s, :to_s
10
10
  alias_method :to_s, :camel_to_s
11
11
  alias_method :un_camel_initialize_copy, :initialize_copy
@@ -14,27 +14,36 @@ module MonkeyCamels
14
14
  end
15
15
  end
16
16
 
17
+ # Create a camel copy based on settings
18
+ #
19
+ # @return [String]
17
20
  def camel_initialize_copy(orig)
18
21
  new_val = un_camel_initialize_copy(orig)
19
22
  orig._camel? ? new_val : new_val._no_hump
20
23
  end
21
-
24
+
25
+ # Provide string formatted based on hump setting
26
+ #
27
+ # @return [String]
22
28
  def camel_to_s
23
29
  val = un_camel_to_s
24
30
  _camel? ? val : val._no_hump
25
31
  end
26
32
 
27
33
  module Humps
28
-
34
+
35
+ # @return [TrueClass, FalseClass] camelized
29
36
  def _camel?
30
37
  !@__not_camel
31
38
  end
32
39
 
40
+ # @return [self] disable camelizing
33
41
  def _no_hump
34
42
  @__not_camel = true
35
43
  self
36
44
  end
37
45
 
46
+ # @return [self] enable camelizing
38
47
  def _hump
39
48
  @__not_camel = false
40
49
  self
@@ -48,7 +57,7 @@ end
48
57
  String.send(:include, MonkeyCamels)
49
58
  Symbol.send(:include, MonkeyCamels)
50
59
 
51
- # Specialized type
60
+ # Specialized String type
52
61
  class CamelString < String
53
62
  def initialize(val=nil)
54
63
  super
@@ -1,8 +1,9 @@
1
1
  require 'attribute_struct/attribute_struct'
2
2
 
3
3
  class AttributeStruct
4
+ # Custom version container
4
5
  class Version < ::Gem::Version
5
6
  end
6
-
7
- VERSION = Version.new('0.1.8')
7
+ # Current library version
8
+ VERSION = Version.new('0.2.0')
8
9
  end
@@ -0,0 +1,80 @@
1
+ require 'minitest/autorun'
2
+
3
+ describe AttributeStruct do
4
+ describe 'Collapse' do
5
+ describe 'direct value set' do
6
+
7
+ describe 'When values are not collapsed' do
8
+ before do
9
+ @struct = AttributeStruct.new
10
+ @struct.direct.assignment 1
11
+ @struct.direct.assignment 2
12
+ @dump = @struct._dump
13
+ end
14
+
15
+ it 'should return last set value' do
16
+ @dump['direct']['assignment'].must_equal 2
17
+ end
18
+ end
19
+
20
+ describe 'When values are collapsed' do
21
+ before do
22
+ @struct = AttributeStruct.new
23
+ @struct._set_state(:value_collapse => true)
24
+ @struct.direct.assignment 1
25
+ @struct.direct.assignment 2
26
+ @dump = @struct._dump
27
+ end
28
+
29
+ it 'should return both assigned values as an array' do
30
+ @dump['direct']['assignment'].must_equal [1,2]
31
+ end
32
+ end
33
+
34
+ end
35
+
36
+ describe 'block value set' do
37
+
38
+ describe 'When values are not collapsed' do
39
+ before do
40
+ @struct = AttributeStruct.new
41
+ @struct.direct do
42
+ assignment true
43
+ end
44
+ @struct.direct do
45
+ assignment false
46
+ end
47
+ @dump = @struct._dump
48
+ end
49
+
50
+ it 'should return last set value' do
51
+ @dump['direct']['assignment'].must_equal false
52
+ end
53
+
54
+ end
55
+
56
+ describe 'When values are collapsed' do
57
+ before do
58
+ @struct = AttributeStruct.new
59
+ @struct._set_state(:value_collapse => true)
60
+ @struct.direct do
61
+ assignment true
62
+ end
63
+ @struct.direct do
64
+ assignment false
65
+ end
66
+ @dump = @struct._dump
67
+ end
68
+
69
+ it 'should return both assigned values as an array' do
70
+ @dump['direct'].must_equal [
71
+ {'assignment' => true},
72
+ {'assignment' => false}
73
+ ]
74
+ end
75
+
76
+ end
77
+
78
+ end
79
+ end
80
+ end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: attribute_struct
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.8
4
+ version: 0.2.0
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2013-10-31 00:00:00.000000000 Z
12
+ date: 2014-05-27 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: hashie
@@ -42,13 +42,17 @@ files:
42
42
  - test/specs/camel.rb
43
43
  - test/specs/merging.rb
44
44
  - test/specs/basic.rb
45
+ - test/specs/collapse.rb
45
46
  - Gemfile
46
47
  - README.md
48
+ - LICENSE
47
49
  - attribute_struct.gemspec
48
50
  - CHANGELOG.md
51
+ - CONTRIBUTING.md
49
52
  - Gemfile.lock
50
53
  homepage: http://github.com/chrisroberts/attribute_struct
51
- licenses: []
54
+ licenses:
55
+ - Apache 2.0
52
56
  post_install_message:
53
57
  rdoc_options: []
54
58
  require_paths: