handy_hash 0.1.0

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