lab42_nhash 0.1.0 → 0.1.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 083d1e07c143e83a9743bc203ab0386c0ca3d13d
4
- data.tar.gz: 0c1017bc1a32a9ed99c26d2e83047e76b3a1dd11
3
+ metadata.gz: dfa964b499cd8f9c9fb458f0082eacf0f990cfac
4
+ data.tar.gz: f5c06457ecdd6652fd05a33e768791192993a37d
5
5
  SHA512:
6
- metadata.gz: 01108baa232347e7688bdc2c163f3e7c1e9870bd3156f5e8ead4497b594ba5803b1995c35d2e64847619c9a73578f5d6f414ca1ff478216cd85670077b6ae96c
7
- data.tar.gz: acb2707844115bc986d0682a4aad72a181c8a2ffd001d0e84bf0797f9bbb6956c4def3bd8cd209b14c750adc616947f2deeb940f685beeaf66a2192f5a8f09af
6
+ metadata.gz: aacab0eb78da5bbeb5b945624e0c2fa058634ba87731dee6803cae4caf27c9e9d21f8910b18a75579528a167d556417b1caeb25bdcea512ca31fd3e614d3f779
7
+ data.tar.gz: 7aa8904790917d875576b024692427ea49fe3b252096aca527b3c25f101d8afb7a035822262f9a41533cc6d4c402ef68eeefa9afecd893d92f3a86f8c9e8d52d
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- lab42_nhash (0.1.0)
4
+ lab42_nhash (0.1.1)
5
5
  forwarder2 (~> 0.2)
6
6
  ruby_parser (~> 3.6)
7
7
 
data/README.md CHANGED
@@ -2,10 +2,18 @@
2
2
 
3
3
  A nested hash view with dotted deep access à la I18n.t of Rails and with optional string interpolation. Typically YML loaded Hashes are used.
4
4
 
5
+ ## What is it good for?
5
6
 
6
- ## Get Access (combined keys with dot notation)
7
+ I developped `NHash` to scratch an itch that developped when creating presentations of hierarchical data with different aspects. E.g. different languages
8
+ and different versions.
9
+
10
+ For that I wanted to combine the _data_ and define strategies what to do when compound keys were not found. Typically _data_ will be quite a big `Hash`
11
+ instance coming from _Yaml_ files, a _Json_ webservice, or a document oriented database like _MongoDB_.
12
+
13
+ But let us start slowly:
14
+
15
+ ## Most Basic Example
7
16
 
8
- Please see the [QED](http://rubyworks.github.io/qed/) demos [here](https://github.com/RobertDober/lab42_nested_hash/blob/master/demo/000-basic-examples.md) for detailed explainations and specifications:
9
17
 
10
18
  ```ruby
11
19
  h = { "a" => 1,
@@ -29,8 +37,48 @@ or _subhashes_:
29
37
  ```
30
38
 
31
39
 
40
+ Please see the [QED](http://rubyworks.github.io/qed/) demos [here](https://github.com/RobertDober/lab42_nested_hash/blob/master/demo) for detailed explainations and specifications of the implemented features:
41
+
42
+
32
43
  ### Indifferent Access
33
44
 
34
- ### Prefix and Suffix Stacks
45
+ Symbol and String Keys are treated the same.
46
+
47
+ ### Affixes
48
+
49
+ Prefixes and Suffixes can be predefined and used when a compound key starts and/or ends with a `.`.
50
+
51
+ ### Lookup Chains
52
+
53
+ Taking advantage of the Affixes features an affixed access can look in more than one affix. E.g. to fall back
54
+ to English as the default translation language.
55
+
56
+ ### Fallbacks
57
+
58
+ A more general concept of what to do if an access fails (raises a `KeyError`).
59
+
60
+ ### Interpolation
61
+
62
+ If values have `ERB` syntax templates as values, the special `get!` access can be used to interpolate them
63
+ in various contexts, the `NHash` instance's binding being the default.
64
+
65
+ ### Compound Values
66
+
67
+ Subhashes and subarrays maintain important context information, e.g. for _Interpolation_.
68
+
69
+ ### Hierarchies
70
+
71
+ If all else fails...
72
+ We can check in other `NHash` instances. They are organised as a _Depth First Search Tree_.
73
+
74
+ This feature is particularly interesting for complex document composition out of a hierarchical data
75
+ source tree (e.g. a set of yaml files, different for each new version/variation of the result).
76
+
77
+ ### Factories
78
+
79
+ The `NHash` constructor takes a `Hash` instance (defaulting to `{}` ) as param. This implies that
80
+ much boiler blate code has to be written in order to bring in other data sources, as e.g. Yaml files.
81
+
82
+ The factories help to avoid this.
35
83
 
36
- ## Fallbacks
84
+ There are even helpers to create an `NHash` hierarchy from data sources.
@@ -20,7 +20,7 @@ Here is an example:
20
20
  root.add_hierarchies one, two, three
21
21
  ```
22
22
 
23
- This constructed a tree like the following (denoting `:never_found` with a `*`)
23
+ This constructed a tree like the following (denoting `:never_found` by a `*`)
24
24
 
25
25
 
26
26
  ```
@@ -125,4 +125,5 @@ from its root, e.g. affix chains or fallbacks.
125
125
  end
126
126
  ```
127
127
 
128
+
128
129
  ### Interpolation Context
@@ -0,0 +1,70 @@
1
+ # Lab42::NHash QED
2
+
3
+ ## Factories
4
+
5
+ The purpose of factories is to bring in common data sources to create a, potentially hierarchic, `NHash` instance.
6
+
7
+ Let us see what the generic factory method `from_sources` does with `Hash` or `NHash` instances first.
8
+
9
+ ### Creating Hierarchies from Hash alikes
10
+
11
+ ```ruby
12
+ nh = NHash.new( 'a' => 1, b: 1 )
13
+ h = { 'a' => 2, 'b' => 2 }
14
+
15
+ hier = NHash.from_sources nh, h
16
+
17
+ ```
18
+
19
+ The hierarchy's root node is on the left, and all hierarchies created by `from_sources` are lists, not trees.
20
+
21
+ Demonstration:
22
+
23
+ ```ruby
24
+ hier.get( 'a' ).assert == 1
25
+ hier.get( 'b' ).assert == 2
26
+ ```
27
+
28
+
29
+ Maybe you noticed that symbolic keys were not found. That is beacause, this time, we did not specify `with_indifferent_access`
30
+
31
+ This can be done too, of course (note again that all keys are transformed to strings as usual:
32
+
33
+ ```ruby
34
+ hier = NHash.from_sources_with_indifferent_access nh, h
35
+ hier.get( :a ).assert == 1
36
+ hier.get( :b ).assert == 1
37
+ ```
38
+
39
+
40
+ ### Creating Hierarchies from YAML Files
41
+
42
+ The same works if the data is inside a Yaml file
43
+
44
+ ```ruby
45
+ given_a_file named: "one.yml", with: <<-EOF
46
+ a: 1
47
+ EOF
48
+ and_given_a_file named: "two.yml", with: <<-EOF
49
+ a: 2
50
+ b: 2
51
+ EOF
52
+ ```
53
+
54
+ Now if we invoke `from_sources` with strings, denoting these files, these strings will be replaced by an `NHash` instance
55
+ initialized with the `Hash` resulting from YAML loading the content of the file
56
+ Now if we invoke icodefrom_sourceswith strings, denoting these files, these strings will be replaced by an icodeNHashinstance
57
+ initialized with the icodeHashresulting from YAML loading the content of the file
58
+
59
+ Or in short ;) :
60
+
61
+ ```ruby
62
+ yaml_hierarchy = NHash.from_sources 'one.yml', 'two.yml'
63
+
64
+ yaml_hierarchy.get( :a ).assert == 1
65
+ yaml_hierarchy.get( :b ).assert == 2
66
+ KeyError.assert.raised? do
67
+ yaml_hierarchy.get :c
68
+ end
69
+ ```
70
+
@@ -0,0 +1,26 @@
1
+
2
+ def given_a_file named: required, with: required
3
+ @files ||= {}
4
+ stub_file unless @orig_readable
5
+ @files.update named => with
6
+ end
7
+ alias_method :and_given_a_file, :given_a_file
8
+
9
+ def erase_all_files
10
+ @files = {}
11
+ # Is it necessary to remove methods first?
12
+ File.define_singleton_method :readable?, &@orig_readable
13
+ File.define_singleton_method :read, &@orig_read
14
+ end
15
+
16
+ def stub_file
17
+ @orig_readable = File.method :readable?
18
+ @orig_read = File.method :read
19
+ files = @files
20
+ File.define_singleton_method :readable? do | fn |
21
+ !!files[ fn ]
22
+ end
23
+ File.define_singleton_method :read do | fn |
24
+ files[ fn ]
25
+ end
26
+ end
@@ -1,18 +1,56 @@
1
1
  require_relative 'enum'
2
+
3
+ require 'yaml'
4
+
2
5
  module Lab42
3
6
  class NHash
4
7
  module ClassMethods
5
8
 
9
+ def from_sources *sources
10
+ __from_sources__(sources)
11
+ end
12
+
13
+ def from_sources_with_indifferent_access *sources
14
+ __from_sources__(sources, indifferent_access: true )
15
+ end
16
+
6
17
  def from_value value, options={}
7
18
  case value
8
19
  when Hash
9
- Lab42::NHash.new( value ).import_options options
20
+ new( value ).import_options options
10
21
  when Enumerable
11
22
  Lab42::NHash::Enum.new value, options
12
23
  else
13
24
  value
14
25
  end
15
26
  end
27
+
28
+ private
29
+ def __from_sources__ sources, indifferent_access: false
30
+ sources = sources.map{ |source|
31
+ make_nhash_from source, indifferent_access: indifferent_access
32
+ }
33
+ result = sources.shift
34
+ sources.inject result do | r, s |
35
+ r.add_hierarchy s
36
+ s
37
+ end
38
+ result
39
+ end
40
+
41
+ def make_nhash_from source, indifferent_access: false
42
+ case source
43
+ when Hash
44
+ new source, indifferent_access: indifferent_access
45
+ when self
46
+ indifferent_access ? source.dup.with_indifferent_access : source.dup
47
+ when String
48
+ raise ArgumentError, "#{source.inspect} needs to designate a readable yaml file" unless File.readable? source
49
+ new YAML.load( File.read source ), indifferent_access: indifferent_access
50
+ else
51
+ raise ArgumentError, "type of value #{source.inspect} is not implemented (yet)"
52
+ end
53
+ end
16
54
  end # module ClassMethods
17
55
  extend ClassMethods
18
56
  end # class NHash
@@ -14,7 +14,7 @@ module Lab42
14
14
  self
15
15
  end
16
16
 
17
- def get_form_hierarchies keyexpr, keyexc
17
+ def get_from_hierarchies keyexpr, keyexc
18
18
  @hierarchies.each do | h |
19
19
  begin
20
20
  return h.get keyexpr
@@ -1,5 +1,5 @@
1
1
  module Lab42
2
2
  class NHash
3
- VERSION = '0.1.0'
3
+ VERSION = '0.1.1'
4
4
  end # class NHash
5
5
  end # module Lab42
data/lib/lab42/nhash.rb CHANGED
@@ -18,7 +18,7 @@ module Lab42
18
18
  forward :pop_binding, to: :@binding_stack, as: :pop
19
19
  forward :push_binding, to: :@binding_stack, as: :push
20
20
 
21
- attr_reader :hashy, :parent
21
+ attr_reader :hashy, :hierarchies, :parent
22
22
 
23
23
  def export_options
24
24
  { indifferent_access: @indifferent_access,
@@ -41,7 +41,7 @@ module Lab42
41
41
  def fallback_or_hierarchy keyexpr, keyexc
42
42
  fallback keyexpr, keyexc
43
43
  rescue KeyError => k
44
- get_form_hierarchies keyexpr, k
44
+ get_from_hierarchies keyexpr, k
45
45
  end
46
46
 
47
47
  def import_options options
@@ -57,7 +57,22 @@ module Lab42
57
57
  end
58
58
  end
59
59
 
60
+
61
+ def with_indifferent_access! cache={}
62
+ __recursive_indifferent_access__ self, {}
63
+ end
60
64
  private
65
+
66
+ def __recursive_indifferent_access__ for_nhash, cache
67
+ return if cache[ for_nhash.object_id ]
68
+
69
+ for_nhash.with_indifferent_access
70
+ cache.update for_nhash.object_id => true
71
+
72
+ for_nhash.hierarchies.each do | hier |
73
+ __recursive_indifferent_access__ hier, cache
74
+ end
75
+ end
61
76
  def complete_keys keys, use_prefix: false, use_suffix: false
62
77
  keys = current_prefix + keys if use_prefix
63
78
  keys += current_suffix if use_suffix
@@ -79,18 +94,18 @@ module Lab42
79
94
  end
80
95
  end
81
96
 
82
- def initialize hashy={}
97
+ def initialize hashy={}, options={}
83
98
  @hashy = hashy
84
- init_options
99
+ init_options options
85
100
  end
86
101
 
87
- def init_options
102
+ def init_options options
88
103
  @parent = self
89
104
 
90
- @indifferent_access = false
91
- @suffix_stack = []
92
- @prefix_stack = []
93
- @binding_stack = []
105
+ @indifferent_access = options.fetch(:indifferent_access, false)
106
+ @suffix_stack = options.fetch(:suffix_stack, [])
107
+ @prefix_stack = options.fetch(:prefix_stack, [])
108
+ @binding_stack = options.fetch(:binding_stack, [])
94
109
 
95
110
  @fallbacks = []
96
111
  @current_fallback_pointer = 0
@@ -0,0 +1,15 @@
1
+
2
+ module FileMocks
3
+
4
+ def given_a_file named: required, with: required
5
+ before do
6
+ expect( File ).to receive( :readable? ).with( named ).and_return( true ).ordered
7
+ expect( File ).to receive( :read ).with( named ).and_return( with ).ordered
8
+ end
9
+ end
10
+
11
+ end # module FileMocks
12
+
13
+ RSpec.configure do | c |
14
+ c.extend FileMocks
15
+ end
@@ -0,0 +1,50 @@
1
+ require 'spec_helper'
2
+
3
+ describe NHash do
4
+ context :factories do
5
+ context :with_hashes do
6
+ let(:h){{ 'a' => 1, 'c' => 1 }}
7
+ let(:nh){described_class.new( 'a' => 2, 'b' => 2 )}
8
+ subject do
9
+ described_class.from_sources( h, nh )
10
+ end
11
+
12
+ it 'is a NHash instance' do
13
+ expect( subject ).to be_kind_of described_class
14
+ end
15
+
16
+ it 'finds elements in root' do
17
+ expect( subject.get( 'a' ) ).to eq 1
18
+ end
19
+
20
+ it 'finds element at 2nd level now' do
21
+ expect( subject.get( 'b' ) ).to eq 2
22
+ end
23
+
24
+ context 'longer chains' do
25
+ let(:first){ described_class.new( 'a' => 0) }
26
+ let(:root){
27
+ described_class.from_sources first, h, nh
28
+ }
29
+
30
+ it 'finds a at root level' do
31
+ expect( root.get 'a' ).to eq 0
32
+ end
33
+
34
+ it 'finds c at second level' do
35
+ expect( root.get 'c' ).to eq 1
36
+ end
37
+
38
+ it 'finds b at last level' do
39
+ expect( root.get 'b' ).to eq 2
40
+ end
41
+
42
+ it 'does not find d at all' do
43
+ expect( ->{root.get 'd'} ).to raise_error KeyError
44
+ end
45
+
46
+ end # context 'longer chains'
47
+ end # context :with_hashes
48
+
49
+ end # context :factories
50
+ end # describe NHash
@@ -0,0 +1,31 @@
1
+ require 'spec_helper'
2
+
3
+ describe NHash do
4
+ context :factories do
5
+ context 'from yaml files' do
6
+
7
+ given_a_file named: 'one.yml', with: <<-EOF
8
+ a: 1
9
+ EOF
10
+ given_a_file named: 'two.yml', with: <<-EOF
11
+ a: 2
12
+ b: 2
13
+ EOF
14
+ subject do
15
+ described_class.from_sources 'one.yml', 'two.yml'
16
+ end
17
+
18
+ it "finds a's value in the first file" do
19
+ expect( subject.get :a ).to eq 1
20
+ end
21
+
22
+ it "finds b's value in the first file" do
23
+ expect( subject.get :b ).to eq 2
24
+ end
25
+
26
+ it "does not find c's value anywhere" do
27
+ expect( ->{ subject.get :c } ).to raise_error KeyError
28
+ end
29
+ end # context 'from yaml files'
30
+ end # context :factories
31
+ end # describe NHash
@@ -0,0 +1,32 @@
1
+ require 'spec_helper'
2
+
3
+ describe NHash do
4
+
5
+ context :factories do
6
+ context 'without indifferent access' do
7
+ subject do
8
+ described_class.from_sources( {a: 1}, {'a' => 2, b: 2} )
9
+ end
10
+ it 'finds only the stringy "a"' do
11
+ expect( subject.get( :a ) ).to eq 2
12
+ end
13
+ it 'and string "b" is not found at all' do
14
+ expect( ->{ subject.get :b } ).to raise_error KeyError
15
+ end
16
+ end # context :indifferent_access
17
+
18
+ context 'however, with indifferent access' do
19
+ subject do
20
+ described_class.from_sources_with_indifferent_access( {a: 1}, {'a' => 2, b: 2} )
21
+ end
22
+ it 'finds also the symbol :a' do
23
+ expect( subject.get( :a ) ).to eq 1
24
+ end
25
+ it 'as well as the symbol :b' do
26
+ expect( subject.get( :b ) ).to eq 2
27
+ end
28
+
29
+ end # context 'however, with indifferent access'
30
+ end # context :factories
31
+
32
+ end # describe NHash
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: lab42_nhash
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.1.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Robert Dober
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2014-05-23 00:00:00.000000000 Z
11
+ date: 2014-05-27 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: forwarder2
@@ -143,6 +143,8 @@ files:
143
143
  - demo/080-interpolation.md
144
144
  - demo/090-compound-values.md
145
145
  - demo/100-hierarchies.md
146
+ - demo/120-factories.md
147
+ - demo/applique/file_mockers.rb
146
148
  - demo/applique/require_ae.rb
147
149
  - demo/applique/require_nhash.rb
148
150
  - lab42_nhash.gemspec
@@ -161,11 +163,15 @@ files:
161
163
  - spec/integration/hierarchie_and_lookup_spec.rb
162
164
  - spec/regression_spec.rb
163
165
  - spec/spec_helper.rb
166
+ - spec/support/file_mocks.rb
164
167
  - spec/unit/afixes/affix_spec.rb
165
168
  - spec/unit/afixes/prefix_spec.rb
166
169
  - spec/unit/afixes/suffix_spec.rb
167
170
  - spec/unit/compound/enum_spec.rb
168
171
  - spec/unit/compound/nhash_spec.rb
172
+ - spec/unit/factories/from_sources_using_hashes_spec.rb
173
+ - spec/unit/factories/from_sources_using_yaml_files_spec.rb
174
+ - spec/unit/factories/inidfferent_access_spec.rb
169
175
  - spec/unit/fallback/fallback_spec.rb
170
176
  - spec/unit/hierarchies/base_spec.rb
171
177
  - spec/unit/hierarchies/tree_form_spec.rb
@@ -206,16 +212,22 @@ test_files:
206
212
  - demo/080-interpolation.md
207
213
  - demo/090-compound-values.md
208
214
  - demo/100-hierarchies.md
215
+ - demo/120-factories.md
216
+ - demo/applique/file_mockers.rb
209
217
  - demo/applique/require_ae.rb
210
218
  - demo/applique/require_nhash.rb
211
219
  - spec/integration/hierarchie_and_lookup_spec.rb
212
220
  - spec/regression_spec.rb
213
221
  - spec/spec_helper.rb
222
+ - spec/support/file_mocks.rb
214
223
  - spec/unit/afixes/affix_spec.rb
215
224
  - spec/unit/afixes/prefix_spec.rb
216
225
  - spec/unit/afixes/suffix_spec.rb
217
226
  - spec/unit/compound/enum_spec.rb
218
227
  - spec/unit/compound/nhash_spec.rb
228
+ - spec/unit/factories/from_sources_using_hashes_spec.rb
229
+ - spec/unit/factories/from_sources_using_yaml_files_spec.rb
230
+ - spec/unit/factories/inidfferent_access_spec.rb
219
231
  - spec/unit/fallback/fallback_spec.rb
220
232
  - spec/unit/hierarchies/base_spec.rb
221
233
  - spec/unit/hierarchies/tree_form_spec.rb