handy_hash 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +10 -0
- data/.rspec +2 -0
- data/.travis.yml +4 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +21 -0
- data/README.md +150 -0
- data/Rakefile +6 -0
- data/handy_hash.gemspec +27 -0
- data/lib/handy_hash.rb +128 -0
- metadata +110 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 2a1d894869671094bd915172b9938eadb13ef9db
|
4
|
+
data.tar.gz: 8579d3708287239cee019e5920b4b19242a47153
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 26c57a7b27463c18c15c94e919be0a4e7108f2f98ef7a939194b684090ab785996764d492c27141d69797e7a7ae7cd39facf7c78ea4c92136979091408dfbbd2
|
7
|
+
data.tar.gz: 355f5a1cf674d69089ce9ac64b8a1e7304ce596b213aa22d9f624d02bbe1df8ae8879296ee06f1e9b9d231b9f84700283def68ecfe9af20ff015c77e7171f7b2
|
data/.gitignore
ADDED
data/.rspec
ADDED
data/.travis.yml
ADDED
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
The MIT License (MIT)
|
2
|
+
|
3
|
+
Copyright (c) 2018 schebannyj
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in
|
13
|
+
all copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
21
|
+
THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,150 @@
|
|
1
|
+
# HandyHash
|
2
|
+
|
3
|
+
`HandyHash` - это небольшая обертка для `Hash`, которая может быть весьма удобна в некоторых случаях.
|
4
|
+
|
5
|
+
Возможности:
|
6
|
+
|
7
|
+
- доступ к данным через методы-геттеры (см. Использование)
|
8
|
+
- рекурсивный `freeze`
|
9
|
+
- рекурсивное слияние + специальный DSL для описания слияния (см. Использование)
|
10
|
+
|
11
|
+
## Установка
|
12
|
+
|
13
|
+
Добавить в Gemfile слудющую строку:
|
14
|
+
|
15
|
+
```ruby
|
16
|
+
gem 'handy_hash'
|
17
|
+
```
|
18
|
+
|
19
|
+
Запустить:
|
20
|
+
|
21
|
+
$ bundle
|
22
|
+
|
23
|
+
Или установить гем вручную:
|
24
|
+
|
25
|
+
$ gem install handy_hash
|
26
|
+
|
27
|
+
## Использование
|
28
|
+
|
29
|
+
Инициализация:
|
30
|
+
|
31
|
+
```ruby
|
32
|
+
data = HandyHash.new(
|
33
|
+
host_name: 'www.myapp.com',
|
34
|
+
some_lib: {
|
35
|
+
path: '/lib/some_lib',
|
36
|
+
init_opts: {
|
37
|
+
flags: 3465873,
|
38
|
+
secret: "78396nxry837d"
|
39
|
+
},
|
40
|
+
methods: %w(one two)
|
41
|
+
}
|
42
|
+
)
|
43
|
+
```
|
44
|
+
|
45
|
+
Получать значения можно разными способами.
|
46
|
+
|
47
|
+
Как из обычного хэша (работает как `HashWithIndifferentAccess`):
|
48
|
+
|
49
|
+
```ruby
|
50
|
+
data[:host_name] # => "www.myapp.com"
|
51
|
+
data["some_lib"][:init_opts] # => {"flags" => 3465873, "secret" => "78396nxry837d"}
|
52
|
+
data["some_lib"]["init_opts"][:flags] # => 3465873
|
53
|
+
```
|
54
|
+
|
55
|
+
В этом случае никакой проверки на присутствие элемента не производится:
|
56
|
+
|
57
|
+
```ruby
|
58
|
+
data[:foo] # => nil
|
59
|
+
data[:foo][:bar] # => NoMethodError: undefined method `[]' for nil:NilClass
|
60
|
+
```
|
61
|
+
|
62
|
+
Можно получать значения, используя геттеры:
|
63
|
+
|
64
|
+
```ruby
|
65
|
+
data.host_name # => "www.myapp.com"
|
66
|
+
data.some_lib.init_opts # => {"flags" => 3465873, "secret" => "78396nxry837d"}
|
67
|
+
data.some_lib.init_opts[:flags] # => 3465873
|
68
|
+
data.some_lib.init_opts.secret # => "78396nxry837d"
|
69
|
+
```
|
70
|
+
|
71
|
+
В этом случае можно "безопасно" спускаться на любую глубину, не опасаясь возникновения исключения:
|
72
|
+
|
73
|
+
```ruby
|
74
|
+
data.foo.present? # => false
|
75
|
+
data.foo.bar.baz.present? # => false
|
76
|
+
data.foo.bar[:baz] # => nil
|
77
|
+
```
|
78
|
+
|
79
|
+
Есть возможность принудительно вызвать исключение приотсутствии определенного элемента:
|
80
|
+
|
81
|
+
```ruby
|
82
|
+
data.some_lib.path! # => "/lib/some_lib"
|
83
|
+
data.foo!.bar # => HandyHash::ValueMissingError: value missing: "foo"
|
84
|
+
```
|
85
|
+
|
86
|
+
В качестве аргумента можно передать значение по умолчанию:
|
87
|
+
|
88
|
+
```ruby
|
89
|
+
data.some_lib.init_opts.flags(0) # => 3465873
|
90
|
+
data.foo.bar.baz(666) # => 666
|
91
|
+
```
|
92
|
+
|
93
|
+
Если ключ хэша соответствует названию какого-то существующего метода объекта `HandyHash` (например, `methods`, `hash`, `patch` и т.д.), то необходимо оборачивать метод знаком `_`:
|
94
|
+
|
95
|
+
```ruby
|
96
|
+
data.some_lib._methods_ # => ["one", "two"]
|
97
|
+
```
|
98
|
+
|
99
|
+
### Слияние (переопределение значений)
|
100
|
+
|
101
|
+
Слияние производится с помощью метода `#patch`, который возвращает новый объект `HandyHash` с новыми значениями. Значения в исходном объекте не меняются.
|
102
|
+
|
103
|
+
Слияние производится рекурсивно во всех нижележащих `Hash`-объектах.
|
104
|
+
|
105
|
+
Выполнить слияние можно, передав методу `#patch` объект `Hash`:
|
106
|
+
|
107
|
+
```ruby
|
108
|
+
new_data = data.patch(
|
109
|
+
some_lib: {
|
110
|
+
path: '/lib/other_path'
|
111
|
+
},
|
112
|
+
foo: :bar
|
113
|
+
)
|
114
|
+
new_data.some_lib.path # => '/lib/other_path'
|
115
|
+
new_data.foo # => :bar
|
116
|
+
```
|
117
|
+
|
118
|
+
Можно с помощью DSL:
|
119
|
+
|
120
|
+
```ruby
|
121
|
+
new_data = data.patch{
|
122
|
+
some_lib {
|
123
|
+
path '/lib/other_path'
|
124
|
+
}
|
125
|
+
foo :bar
|
126
|
+
}
|
127
|
+
new_data.some_lib.path # => '/lib/other_path'
|
128
|
+
new_data.foo # => :bar
|
129
|
+
```
|
130
|
+
|
131
|
+
Для экономии строк при изменениях значений на глубоких уровнях иерархии можно использовать цепочки методов:
|
132
|
+
|
133
|
+
```ruby
|
134
|
+
new_data = data.patch{
|
135
|
+
some_lib.init_opts.secret 'abc'
|
136
|
+
foo.bar.baz 123
|
137
|
+
}
|
138
|
+
new_data.some_lib.init_opts.secret # => "abc"
|
139
|
+
new_data.foo.bar.baz # => 123
|
140
|
+
```
|
141
|
+
|
142
|
+
## Contributing
|
143
|
+
|
144
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/stp-che/handy_hash.
|
145
|
+
|
146
|
+
|
147
|
+
## License
|
148
|
+
|
149
|
+
The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).
|
150
|
+
|
data/Rakefile
ADDED
data/handy_hash.gemspec
ADDED
@@ -0,0 +1,27 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'handy_hash'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "handy_hash"
|
8
|
+
spec.version = HandyHash::VERSION
|
9
|
+
spec.authors = ["schebannyj"]
|
10
|
+
spec.email = ["stp.eternal@gmail.com"]
|
11
|
+
|
12
|
+
spec.summary = %q{HandyHash is a Hash that supply handy ways for accessing data and merging.}
|
13
|
+
spec.description = %q{HandyHash is a Hash object that supply handy ways for accessing data and merging.}
|
14
|
+
spec.homepage = "https://github.com/stp-che/handy_hash"
|
15
|
+
spec.license = "MIT"
|
16
|
+
|
17
|
+
spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
|
18
|
+
spec.bindir = "exe"
|
19
|
+
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
20
|
+
spec.require_paths = ["lib"]
|
21
|
+
|
22
|
+
spec.add_runtime_dependency "activesupport", "~> 5.0"
|
23
|
+
|
24
|
+
spec.add_development_dependency "bundler", "~> 1.11"
|
25
|
+
spec.add_development_dependency "rake", "~> 10.0"
|
26
|
+
spec.add_development_dependency "rspec", "~> 3.0"
|
27
|
+
end
|
data/lib/handy_hash.rb
ADDED
@@ -0,0 +1,128 @@
|
|
1
|
+
require 'active_support/all'
|
2
|
+
|
3
|
+
class HandyHash < HashWithIndifferentAccess
|
4
|
+
VERSION = '0.1.0'
|
5
|
+
|
6
|
+
class ValueMissingError < StandardError; end
|
7
|
+
|
8
|
+
def initialize(value=nil)
|
9
|
+
if value
|
10
|
+
value.each do |k, v|
|
11
|
+
self[k] = __wrap v
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
def freeze
|
17
|
+
super
|
18
|
+
each{|_, v| v.freeze unless v.frozen?}
|
19
|
+
end
|
20
|
+
|
21
|
+
def []=(k,v)
|
22
|
+
super k, __wrap(v)
|
23
|
+
end
|
24
|
+
|
25
|
+
def patch(hash=nil, &block)
|
26
|
+
change_set = [].tap do |ch|
|
27
|
+
ch << __wrap(hash) if hash
|
28
|
+
ch << Builder.new(&block).data if block_given?
|
29
|
+
end
|
30
|
+
Patch.(self, *change_set)
|
31
|
+
end
|
32
|
+
|
33
|
+
def method_missing(m, *args, &block)
|
34
|
+
if m =~ /\Ato_ary\Z/ || m =~ /\=$/ || args.size > 1 || block_given?
|
35
|
+
super
|
36
|
+
else
|
37
|
+
m = m.to_s
|
38
|
+
if m[-1] == '!'
|
39
|
+
m = m.to_s.sub!(/!$/, '')
|
40
|
+
required = true
|
41
|
+
end
|
42
|
+
if m =~ /\A_(.+)_\Z/
|
43
|
+
m = $1
|
44
|
+
end
|
45
|
+
__fetch_value m, required: required, default: args.first
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
private
|
50
|
+
|
51
|
+
def __fetch_value(key, required: false, default: nil)
|
52
|
+
self[key] || default || (required ? raise(ValueMissingError, "value missing: \"#{key}\"") : HandyHash.Nil)
|
53
|
+
end
|
54
|
+
|
55
|
+
def __wrap(v)
|
56
|
+
v.kind_of?(Hash) && !v.kind_of?(HandyHash) ? HandyHash.new(v) : v
|
57
|
+
end
|
58
|
+
|
59
|
+
class << self
|
60
|
+
def Nil
|
61
|
+
@Nil ||= new.tap(&:freeze)
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
# @api private
|
66
|
+
class Patch
|
67
|
+
class << self
|
68
|
+
def call(orig_data, *change_set)
|
69
|
+
change_set.inject(orig_data){|data, changes| deep_merge(data, changes) }
|
70
|
+
end
|
71
|
+
|
72
|
+
private
|
73
|
+
|
74
|
+
def deep_merge(h1, h2)
|
75
|
+
HandyHash.new({}).tap do |d|
|
76
|
+
h1.each{|k,v|
|
77
|
+
unless h2.key?(k)
|
78
|
+
d[k] = v
|
79
|
+
else
|
80
|
+
if v.kind_of?(Hash) && h2[k].kind_of?(Hash)
|
81
|
+
d[k] = call(v, h2[k])
|
82
|
+
else
|
83
|
+
d[k] = h2[k]
|
84
|
+
end
|
85
|
+
end
|
86
|
+
}
|
87
|
+
h2.each{|k,v|
|
88
|
+
next if h1.key?(k)
|
89
|
+
d[k] = v
|
90
|
+
}
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
# @api private
|
97
|
+
class Builder
|
98
|
+
def initialize(&block)
|
99
|
+
@content = {}
|
100
|
+
instance_eval &block if block_given?
|
101
|
+
end
|
102
|
+
|
103
|
+
def method_missing(m, *args, &block)
|
104
|
+
if m =~ /\Ato_ary\Z/ || m =~ /\=$/ || args.size > 1
|
105
|
+
super
|
106
|
+
else
|
107
|
+
m = m.to_s
|
108
|
+
if m =~ /\A_(.+)_\Z/
|
109
|
+
m = $1
|
110
|
+
end
|
111
|
+
if args.empty?
|
112
|
+
@content[m] = Builder.new(&block)
|
113
|
+
else
|
114
|
+
@content[m] = args.first
|
115
|
+
end
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
def data
|
120
|
+
HandyHash.new.tap do |d|
|
121
|
+
@content.each do |k, v|
|
122
|
+
d[k] = v.kind_of?(Builder) ? v.data : v
|
123
|
+
end
|
124
|
+
end
|
125
|
+
end
|
126
|
+
end
|
127
|
+
|
128
|
+
end
|
metadata
ADDED
@@ -0,0 +1,110 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: handy_hash
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- schebannyj
|
8
|
+
autorequire:
|
9
|
+
bindir: exe
|
10
|
+
cert_chain: []
|
11
|
+
date: 2018-05-23 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: activesupport
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '5.0'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '5.0'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: bundler
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '1.11'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '1.11'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: rake
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '10.0'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '10.0'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: rspec
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - "~>"
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '3.0'
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - "~>"
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '3.0'
|
69
|
+
description: HandyHash is a Hash object that supply handy ways for accessing data
|
70
|
+
and merging.
|
71
|
+
email:
|
72
|
+
- stp.eternal@gmail.com
|
73
|
+
executables: []
|
74
|
+
extensions: []
|
75
|
+
extra_rdoc_files: []
|
76
|
+
files:
|
77
|
+
- ".gitignore"
|
78
|
+
- ".rspec"
|
79
|
+
- ".travis.yml"
|
80
|
+
- Gemfile
|
81
|
+
- LICENSE.txt
|
82
|
+
- README.md
|
83
|
+
- Rakefile
|
84
|
+
- handy_hash.gemspec
|
85
|
+
- lib/handy_hash.rb
|
86
|
+
homepage: https://github.com/stp-che/handy_hash
|
87
|
+
licenses:
|
88
|
+
- MIT
|
89
|
+
metadata: {}
|
90
|
+
post_install_message:
|
91
|
+
rdoc_options: []
|
92
|
+
require_paths:
|
93
|
+
- lib
|
94
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
95
|
+
requirements:
|
96
|
+
- - ">="
|
97
|
+
- !ruby/object:Gem::Version
|
98
|
+
version: '0'
|
99
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
100
|
+
requirements:
|
101
|
+
- - ">="
|
102
|
+
- !ruby/object:Gem::Version
|
103
|
+
version: '0'
|
104
|
+
requirements: []
|
105
|
+
rubyforge_project:
|
106
|
+
rubygems_version: 2.4.8
|
107
|
+
signing_key:
|
108
|
+
specification_version: 4
|
109
|
+
summary: HandyHash is a Hash that supply handy ways for accessing data and merging.
|
110
|
+
test_files: []
|