lab42_nhash 0.1.0.pre

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.
@@ -0,0 +1,48 @@
1
+ require_relative './exceptions'
2
+ module Lab42
3
+ class NHash
4
+ module Afixes
5
+
6
+ def pop_prefix; @prefix_stack.pop; self end
7
+ def pop_suffix; @suffix_stack.pop; self end
8
+ def push_prefix pfx; @prefix_stack.push pfx.to_s; self end
9
+ def push_suffix sfx; @suffix_stack.push sfx.to_s; self end
10
+
11
+ def current_prefix
12
+ @prefix_stack
13
+ .fetch( -1, '' )
14
+ .split( '.' )
15
+ end
16
+
17
+ def current_suffix
18
+ @suffix_stack
19
+ .fetch( -1, '' )
20
+ .split( '.' )
21
+ end
22
+
23
+ def with_affixes pfx, sfx, &blk
24
+ push_prefix pfx
25
+ push_suffix sfx
26
+ _invoke blk, self
27
+ ensure
28
+ pop_prefix
29
+ pop_suffix
30
+ end
31
+
32
+ def with_prefix pfx, &blk
33
+ push_prefix pfx
34
+ _invoke blk, self
35
+ ensure
36
+ pop_prefix
37
+ end
38
+
39
+ def with_suffix sfx, &blk
40
+ push_suffix sfx
41
+ _invoke blk, self
42
+ ensure
43
+ pop_suffix
44
+ end
45
+ end # module Afixes
46
+ include Afixes
47
+ end # class NHash
48
+ end # module Lab42
@@ -0,0 +1,3 @@
1
+ require_relative '../nhash'
2
+
3
+ NHash = Lab42::NHash
@@ -0,0 +1,5 @@
1
+ module Lab42
2
+ class NHash
3
+ IllegalStateError = Class.new RuntimeError
4
+ end # class NHash
5
+ end # module Lab42
@@ -0,0 +1,70 @@
1
+ module Lab42
2
+ class NHash
3
+ module Fallbacks
4
+ def again
5
+ raise IllegalStateError, "must not call again before a failed get/fetch" unless @inside_fallback
6
+ get( *@fallback_params )
7
+ end
8
+
9
+ def clear_fallbacks!
10
+ @fallbacks = []
11
+ end
12
+
13
+ def pop_fallback
14
+ raise IllegalStateError, "must not tamper with the fallback stack inside fallbacks" if @inside_fallback
15
+
16
+ @fallbacks.pop
17
+
18
+ self
19
+ end
20
+
21
+ def push_fallback fb=nil, &blk
22
+ raise ArgumentError, "need to provide a lambda or a block" unless fb || blk
23
+ assure_not_inside_fallback
24
+
25
+ @current_fallback_pointer = @fallbacks.size
26
+ @fallbacks.push( fb || blk )
27
+
28
+ self
29
+ end
30
+
31
+ def with_fallback b=nil, &blk
32
+ assure_not_inside_fallback
33
+ save_fallback_pointer = @current_fallback_pointer
34
+ _invoke (b||blk), self
35
+ ensure
36
+ @current_fallback_pointer = save_fallback_pointer
37
+ end
38
+
39
+ private
40
+ def assure_not_inside_fallback
41
+ raise IllegalStateError, "must not tamper with the fallback stack inside fallbacks" if @inside_fallback
42
+ end
43
+ def current_fallback
44
+ @fallbacks[ @current_fallback_pointer ]
45
+ end
46
+ # Contract: Invoked only after a KeyError raising get/fetch
47
+ def fallback keyexpr, k
48
+ raise k unless current_fallback
49
+ @inside_fallback = true # according to contract, setting the flag allowing the invocation of again
50
+ @fallback_params = keyexpr
51
+ invoke_current_fallback
52
+ ensure
53
+ @inside_fallback = false # forbid @again invocation again (pun intended)
54
+ end
55
+
56
+ def invoke_current_fallback
57
+ fb = current_fallback
58
+ @current_fallback_pointer -= 1
59
+ @current_fallback_pointer = @fallbacks.size if @current_fallback_pointer < 0
60
+ _invoke( fb, self ).tap do
61
+ # Make sure to reset if nothing was raised inside the invocation
62
+ @current_fallback_pointer = @fallbacks.size - 1
63
+ end
64
+ end
65
+
66
+ end # module Fallbacks
67
+
68
+ include Fallbacks
69
+ end # class NHash
70
+ end # module Lab42
@@ -0,0 +1,21 @@
1
+ require 'erb'
2
+
3
+ module Lab42
4
+ class NHash
5
+ module Interpolation
6
+ def get! *args, &blk
7
+ result = get( *args, &blk )
8
+ return result unless String === result
9
+ _expand_result result
10
+ end
11
+ alias_method :fetch!, :get!
12
+
13
+ private
14
+ def _expand_result result
15
+ ERB.new( result ).result( binding )
16
+ end
17
+
18
+ end # module Interpolation
19
+ include Interpolation
20
+ end # class NHash
21
+ end # module Lab42
@@ -0,0 +1,16 @@
1
+ module Lab42
2
+ class NHash
3
+ module Invocation
4
+ private
5
+ def _invoke a_proc, *args
6
+ if a_proc.arity < 0 || a_proc.arity == args.size
7
+ a_proc.( *args )
8
+ else
9
+ args.first.instance_exec(&a_proc)
10
+ end
11
+ end
12
+ end # module Invocation
13
+
14
+ include Invocation
15
+ end # class NHash
16
+ end # module Lab42
@@ -0,0 +1,50 @@
1
+ module Lab42
2
+ class NHash
3
+ module LookupChains
4
+ def push_prefix_lookup *prefixes
5
+ prefixes.reverse.each do | prefix |
6
+ push_fallback do
7
+ with_prefix prefix do
8
+ again
9
+ end
10
+ end
11
+ end
12
+ end
13
+
14
+ def push_suffix_lookup *suffixes
15
+ suffixes.reverse.each do | suffix |
16
+ push_fallback do
17
+ with_suffix suffix do
18
+ again
19
+ end
20
+ end
21
+ end
22
+ end
23
+
24
+ def push_affix_lookup( prefixes: required, suffixes: required, suffixes_first: false )
25
+ if suffixes_first
26
+ suffixes.product( prefixes ).reverse.each do | suff, pref |
27
+ push_fallback do
28
+ with_suffix suff do
29
+ with_prefix pref do
30
+ again
31
+ end
32
+ end
33
+ end
34
+ end
35
+ else
36
+ prefixes.product( suffixes ).reverse.each do | pref, suff |
37
+ push_fallback do
38
+ with_suffix suff do
39
+ with_prefix pref do
40
+ again
41
+ end
42
+ end
43
+ end
44
+ end
45
+ end
46
+ end
47
+ end # module LookupChains
48
+ include LookupChains
49
+ end # class NHash
50
+ end # module Lab42
@@ -0,0 +1,5 @@
1
+ module Lab42
2
+ class NHash
3
+ VERSION = '0.1.0.pre'
4
+ end # class NHash
5
+ end # module Lab42
@@ -0,0 +1,76 @@
1
+ require_relative './nhash/exceptions'
2
+ require_relative './nhash/invocation'
3
+ require_relative './nhash/affixes'
4
+ require_relative './nhash/fallbacks'
5
+ require_relative './nhash/lookup_chains'
6
+ require_relative './nhash/interpolation'
7
+
8
+ module Lab42
9
+ class NHash
10
+
11
+ attr_reader :hashy
12
+
13
+ def get keyexpr, *default, &defblock
14
+ keys = keyexpr.to_s.split( '.' )
15
+ keys = complete_keys keys.reject(&:empty?), use_prefix: keys.first.empty?, use_suffix: keyexpr[-1] == '.'
16
+ found = @indifferent_access ? _get_indiff( keys ) : _get( keys )
17
+ case found
18
+ when Hash
19
+ self.class.new( found ).import_options export_options
20
+ else
21
+ found
22
+ end
23
+ rescue KeyError => k
24
+ return fallback keyexpr, k if default.empty? && defblock.nil?
25
+ default.empty? ? defblock.(keyexpr) : default.first
26
+ end
27
+ alias_method :fetch, :get
28
+
29
+ def import_options options
30
+ @indifferent_access = options[:indifferent_access]
31
+ self
32
+ end
33
+
34
+ def with_indifferent_access
35
+ self.class.new( @hashy ).import_options indifferent_access: true
36
+ end
37
+
38
+ private
39
+ def complete_keys keys, use_prefix: false, use_suffix: false
40
+ keys = current_prefix + keys if use_prefix
41
+ keys += current_suffix if use_suffix
42
+ keys
43
+ end
44
+
45
+ # TODO: This is inefficent, either cache it or split on pushing onto stack
46
+ def _get keys
47
+ keys.inject hashy do | partial_hash, key_element |
48
+ raise KeyError, keys.join('.') unless Hash === partial_hash
49
+ partial_hash.fetch key_element
50
+ end
51
+ end
52
+
53
+ def _get_indiff keys
54
+ keys.inject hashy do | partial_hash, key_element |
55
+ raise KeyError, keys.join('.') unless Hash === partial_hash
56
+ partial_hash.fetch( key_element ){ partial_hash.fetch key_element.to_sym }
57
+ end
58
+ end
59
+ def export_options
60
+ { indifferent_access: @indifferent_access }
61
+ end
62
+
63
+ def initialize hashy
64
+ @hashy = hashy
65
+ init_options
66
+ end
67
+
68
+ def init_options
69
+ @indifferent_access = false
70
+ @suffix_stack = []
71
+ @prefix_stack = []
72
+ @fallbacks = []
73
+ @current_fallback_pointer = 0
74
+ end
75
+ end # class NHash
76
+ end # module Lab42
@@ -0,0 +1,11 @@
1
+ require_relative "../lib/lab42/nhash/auto_import"
2
+
3
+ PROJECT_ROOT = File.expand_path "../..", __FILE__
4
+ Dir[File.join(PROJECT_ROOT,"spec/support/**/*.rb")].each {|f| require f}
5
+
6
+ RSpec.configure do |c|
7
+ c.treat_symbols_as_metadata_keys_with_true_values = true
8
+ c.filter_run wip: true
9
+ c.run_all_when_everything_filtered = true
10
+ end
11
+
@@ -0,0 +1,17 @@
1
+ require 'spec_helper'
2
+
3
+ describe NHash do
4
+ subject do
5
+ described_class.new( "a" => {
6
+ "b" =>
7
+ { "c" => 'abc' } })
8
+ end
9
+
10
+ it "can access with affixes" do
11
+ expect( subject.with_affixes( 'a', 'c') do
12
+ get '.b.'
13
+ end
14
+ ).to eq( 'abc' )
15
+ end
16
+
17
+ end # describe NHash
@@ -0,0 +1,22 @@
1
+ require 'spec_helper'
2
+
3
+ describe NHash do
4
+ subject do
5
+ described_class.new({ "a" => {
6
+ "b" =>
7
+ { "c" => 'abc',
8
+ "d" => 'abd' } },
9
+ "x" => {
10
+ "b" =>
11
+ { "c" => 'xbc' } } })
12
+ .push_prefix 'a.b'
13
+ end
14
+
15
+ it "can access normally" do
16
+ expect( subject.get('a.b.c') ).to eq( 'abc' )
17
+ end
18
+
19
+ it "or with a prefix" do
20
+ expect( subject.get( '.c' ) ).to eq( 'abc' )
21
+ end
22
+ end # describe NHash
@@ -0,0 +1,23 @@
1
+ require 'spec_helper'
2
+
3
+ describe NHash do
4
+ subject do
5
+ described_class.new({ "a" => {
6
+ "b" =>
7
+ { "c" => 'abc',
8
+ "d" => 'abd' } },
9
+ "x" => {
10
+ "b" =>
11
+ { "c" => 'xbc' } } })
12
+ .push_suffix 'b.c'
13
+ end
14
+
15
+ it "can access normally" do
16
+ expect( subject.get('a.b.c') ).to eq( 'abc' )
17
+ end
18
+
19
+ it "or with a suffix" do
20
+ expect( subject.get( 'a.' ) ).to eq( 'abc' )
21
+ expect( subject.get( 'x.' ) ).to eq( 'xbc' )
22
+ end
23
+ end # describe NHash
@@ -0,0 +1,91 @@
1
+ require 'spec_helper'
2
+
3
+ describe NHash do
4
+ subject do
5
+ NHash.new(
6
+ en: { '1' => 'one', '2' => 'two', '3' => 'three' },
7
+ fr: { '1' => 'un', '2' => 'deux' },
8
+ it: { '1' => 'uno' } )
9
+ .with_indifferent_access
10
+ end
11
+
12
+ context 'non regression if no fallbacks are declared' do
13
+ it 'accesses correctly' do
14
+ expect( subject.get('it.1' ) ).to eq 'uno'
15
+ end
16
+ it 'raises KeyError correctly' do
17
+ expect(
18
+ ->{
19
+ subject.get 'it.2'
20
+ }
21
+ ).to raise_error KeyError
22
+ end
23
+
24
+ end # context 'non regression if no fallbacks are declared'
25
+
26
+ context :fallbacks do
27
+ before do
28
+ subject.push_fallback do
29
+ # let us use English as the last ressort
30
+ with_prefix(:en){again}
31
+ end
32
+ end
33
+
34
+ context :one_level_fallback do
35
+ it 'still works' do
36
+ subject.with_prefix( :it ) do | n |
37
+ expect( n.get( '.1' ) ).to eq 'uno'
38
+ end
39
+ end
40
+
41
+ it 'finds the english value else' do
42
+ subject.with_prefix( :it ) do | n |
43
+ expect( n.get( '.2' )).to eq 'two'
44
+ end
45
+ end
46
+
47
+ context :second_level_fallback do
48
+ before do
49
+ subject.push_fallback do |s|
50
+ s.with_prefix(:fr){
51
+ again
52
+ }
53
+ end
54
+ end
55
+ it 'still works' do
56
+ subject.with_prefix( :it ) do | n |
57
+ expect( n.get( '.1' ) ).to eq 'uno'
58
+ end
59
+ end
60
+
61
+ it 'finds the french value if present' do
62
+ subject.with_prefix( :it ) do | n |
63
+ expect( n.get( '.2' )).to eq 'deux'
64
+ end
65
+ end
66
+
67
+ it 'otherwise finds the english value' do
68
+ subject.with_prefix( :it ) do | n |
69
+ expect( n.get( '.3' )).to eq 'three'
70
+ end
71
+ end
72
+ end # context :second_level_fallback
73
+
74
+ context :clear_fallbacks do
75
+ before do
76
+ subject.clear_fallbacks!
77
+ end
78
+
79
+ it 'raises a KeyError now' do
80
+ subject.with_prefix( :it ) do | n |
81
+ expect(
82
+ ->{
83
+ n.get( '.2' )
84
+ }
85
+ ).to raise_error KeyError
86
+ end
87
+ end
88
+ end # context :clear_fallbacks
89
+ end # context :normal_access
90
+ end # context :fallbacks
91
+ end # describe NHash
metadata ADDED
@@ -0,0 +1,199 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: lab42_nhash
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0.pre
5
+ platform: ruby
6
+ authors:
7
+ - Robert Dober
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2014-05-12 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: forwarder2
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ~>
18
+ - !ruby/object:Gem::Version
19
+ version: '0.2'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ~>
25
+ - !ruby/object:Gem::Version
26
+ version: '0.2'
27
+ - !ruby/object:Gem::Dependency
28
+ name: ruby_parser
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ~>
32
+ - !ruby/object:Gem::Version
33
+ version: '3.6'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ~>
39
+ - !ruby/object:Gem::Version
40
+ version: '3.6'
41
+ - !ruby/object:Gem::Dependency
42
+ name: bundler
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ~>
46
+ - !ruby/object:Gem::Version
47
+ version: '1.6'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ~>
53
+ - !ruby/object:Gem::Version
54
+ version: '1.6'
55
+ - !ruby/object:Gem::Dependency
56
+ name: rspec
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ~>
60
+ - !ruby/object:Gem::Version
61
+ version: '2.14'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ~>
67
+ - !ruby/object:Gem::Version
68
+ version: '2.14'
69
+ - !ruby/object:Gem::Dependency
70
+ name: pry
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - ~>
74
+ - !ruby/object:Gem::Version
75
+ version: '0.9'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - ~>
81
+ - !ruby/object:Gem::Version
82
+ version: '0.9'
83
+ - !ruby/object:Gem::Dependency
84
+ name: pry-debugger
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - ~>
88
+ - !ruby/object:Gem::Version
89
+ version: '0.2'
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - ~>
95
+ - !ruby/object:Gem::Version
96
+ version: '0.2'
97
+ - !ruby/object:Gem::Dependency
98
+ name: ae
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - ~>
102
+ - !ruby/object:Gem::Version
103
+ version: '1.8'
104
+ type: :development
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - ~>
109
+ - !ruby/object:Gem::Version
110
+ version: '1.8'
111
+ - !ruby/object:Gem::Dependency
112
+ name: qed
113
+ requirement: !ruby/object:Gem::Requirement
114
+ requirements:
115
+ - - ~>
116
+ - !ruby/object:Gem::Version
117
+ version: '2.9'
118
+ type: :development
119
+ prerelease: false
120
+ version_requirements: !ruby/object:Gem::Requirement
121
+ requirements:
122
+ - - ~>
123
+ - !ruby/object:Gem::Version
124
+ version: '2.9'
125
+ description: A Nested Hash with dotted access à la I18n in Rails
126
+ email:
127
+ - robert.dober@gmail.com
128
+ executables: []
129
+ extensions: []
130
+ extra_rdoc_files: []
131
+ files:
132
+ - .gitignore
133
+ - .rspec
134
+ - Gemfile
135
+ - Gemfile.lock
136
+ - LICENSE
137
+ - README.md
138
+ - demo/000-basic-examples.md
139
+ - demo/010-indifferent-access.md
140
+ - demo/020-afixes.md
141
+ - demo/030-lookup-chains.md
142
+ - demo/060-fallback.md
143
+ - demo/080-interpolation.md
144
+ - demo/applique/require_ae.rb
145
+ - demo/applique/require_nhash.rb
146
+ - lab42_nhash.gemspec
147
+ - lib/lab42/nhash.rb
148
+ - lib/lab42/nhash/affixes.rb
149
+ - lib/lab42/nhash/auto_import.rb
150
+ - lib/lab42/nhash/exceptions.rb
151
+ - lib/lab42/nhash/fallbacks.rb
152
+ - lib/lab42/nhash/interpolation.rb
153
+ - lib/lab42/nhash/invocation.rb
154
+ - lib/lab42/nhash/lookup_chains.rb
155
+ - lib/lab42/nhash/version.rb
156
+ - spec/spec_helper.rb
157
+ - spec/unit/afixes/affix_spec.rb
158
+ - spec/unit/afixes/prefix_spec.rb
159
+ - spec/unit/afixes/suffix_spec.rb
160
+ - spec/unit/fallback/fallback_spec.rb
161
+ homepage: https://github.com/RobertDober/lab42_nested_hash
162
+ licenses:
163
+ - MIT
164
+ metadata: {}
165
+ post_install_message:
166
+ rdoc_options: []
167
+ require_paths:
168
+ - lib
169
+ required_ruby_version: !ruby/object:Gem::Requirement
170
+ requirements:
171
+ - - '>='
172
+ - !ruby/object:Gem::Version
173
+ version: 2.0.0
174
+ required_rubygems_version: !ruby/object:Gem::Requirement
175
+ requirements:
176
+ - - '>='
177
+ - !ruby/object:Gem::Version
178
+ version: 2.2.2
179
+ requirements: []
180
+ rubyforge_project:
181
+ rubygems_version: 2.2.2
182
+ signing_key:
183
+ specification_version: 4
184
+ summary: A nested hash view with dotted deep access à la I18n.t of Rails and with
185
+ optional string interpolation. Typically YML loaded Hashes are used.
186
+ test_files:
187
+ - demo/000-basic-examples.md
188
+ - demo/010-indifferent-access.md
189
+ - demo/020-afixes.md
190
+ - demo/030-lookup-chains.md
191
+ - demo/060-fallback.md
192
+ - demo/080-interpolation.md
193
+ - demo/applique/require_ae.rb
194
+ - demo/applique/require_nhash.rb
195
+ - spec/spec_helper.rb
196
+ - spec/unit/afixes/affix_spec.rb
197
+ - spec/unit/afixes/prefix_spec.rb
198
+ - spec/unit/afixes/suffix_spec.rb
199
+ - spec/unit/fallback/fallback_spec.rb