lab42_nhash 0.1.0.pre

Sign up to get free protection for your applications and to get access to all the features.
@@ -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