handy_hash 0.1.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.
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
@@ -0,0 +1,10 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /Gemfile.lock
4
+ /_yardoc/
5
+ /coverage/
6
+ /doc/
7
+ /pkg/
8
+ /spec/reports/
9
+ /tmp/
10
+ *.gem
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --format documentation
2
+ --color
data/.travis.yml ADDED
@@ -0,0 +1,4 @@
1
+ language: ruby
2
+ rvm:
3
+ - 2.2.2
4
+ before_install: gem install bundler -v 1.11.2
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in handy_hash.gemspec
4
+ gemspec
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
@@ -0,0 +1,6 @@
1
+ require "bundler/gem_tasks"
2
+ require "rspec/core/rake_task"
3
+
4
+ RSpec::Core::RakeTask.new(:spec)
5
+
6
+ task :default => :spec
@@ -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: []