attribute_struct 0.1.8 → 0.2.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.
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: