corefines 1.0.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 +7 -0
- data/LICENSE +21 -0
- data/README.adoc +198 -0
- data/Rakefile +23 -0
- data/lib/corefines/array.rb +30 -0
- data/lib/corefines/enumerable.rb +64 -0
- data/lib/corefines/hash.rb +164 -0
- data/lib/corefines/module.rb +95 -0
- data/lib/corefines/object.rb +389 -0
- data/lib/corefines/string.rb +211 -0
- data/lib/corefines/support/alias_submodules.rb +103 -0
- data/lib/corefines/support/classes_including_module.rb +27 -0
- data/lib/corefines/support/fake_refinements.rb +86 -0
- data/lib/corefines/symbol.rb +32 -0
- data/lib/corefines/version.rb +3 -0
- data/lib/corefines.rb +14 -0
- data/spec/array/second_spec.rb +10 -0
- data/spec/array/third_spec.rb +10 -0
- data/spec/enumerable/index_by_spec.rb +13 -0
- data/spec/enumerable/map_send_spec.rb +24 -0
- data/spec/hash/compact_spec.rb +48 -0
- data/spec/hash/op_plus_spec.rb +11 -0
- data/spec/hash/rekey_spec.rb +100 -0
- data/spec/hash/symbolize_keys_spec.rb +21 -0
- data/spec/module/alias_class_method_spec.rb +21 -0
- data/spec/module/alias_method_chain_spec.rb +76 -0
- data/spec/object/blank_spec.rb +128 -0
- data/spec/object/deep_dup_spec.rb +61 -0
- data/spec/object/else_spec.rb +24 -0
- data/spec/object/in_spec.rb +21 -0
- data/spec/object/instance_values_spec.rb +22 -0
- data/spec/object/then_if_spec.rb +64 -0
- data/spec/object/then_spec.rb +26 -0
- data/spec/object/try_spec.rb +47 -0
- data/spec/spec_helper.rb +30 -0
- data/spec/string/color_spec.rb +82 -0
- data/spec/string/concat_spec.rb +36 -0
- data/spec/string/decolor_spec.rb +27 -0
- data/spec/string/remove_spec.rb +57 -0
- data/spec/string/unindent_spec.rb +66 -0
- data/spec/support/alias_submodules_spec.rb +83 -0
- data/spec/support/classes_including_module_spec.rb +35 -0
- data/spec/support/fake_refinements_spec.rb +118 -0
- data/spec/symbol/call_spec.rb +16 -0
- metadata +175 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: a692c4abce603d86050acd275daec0cdd2c303e6
|
4
|
+
data.tar.gz: 7d8a10a41f2eff14a549d7f192ed1485ecf2348e
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 8507ca0ade2ee56e6e430a552fc5edfd32a28dbe9e434b61f8b884143a9cf25c604b3ce71b5f76dbec7663a77cda404cde7689569cbca89e7c0f568dfb046a7d
|
7
|
+
data.tar.gz: e3136911b789c35cdc559c0280fcd31bae9a64f6465b7a8eaa3428e1a368da021e65c69c406a2c3e2bf7320897739f6571d6d586054e43945e00f01826a8ac62
|
data/LICENSE
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
The MIT License
|
2
|
+
|
3
|
+
Copyright 2015 Jakub Jirutka <jakub@jirutka.cz>.
|
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.adoc
ADDED
@@ -0,0 +1,198 @@
|
|
1
|
+
= Corefines
|
2
|
+
Jakub Jirutka <https://github.com/jirutka[@jirutka]>
|
3
|
+
:page-layout: base
|
4
|
+
:idprefix:
|
5
|
+
ifdef::env-github[:idprefix: user-content-]
|
6
|
+
:idseparator: -
|
7
|
+
:source-language: ruby
|
8
|
+
:language: {source-language}
|
9
|
+
// custom
|
10
|
+
:gem-name: corefines
|
11
|
+
:gh-name: jirutka/{gem-name}
|
12
|
+
:gh-branch: master
|
13
|
+
:badge-style: flat
|
14
|
+
:doc-base-url: http://www.rubydoc.info/github/jirutka/corefines/Corefines
|
15
|
+
|
16
|
+
ifdef::env-github[]
|
17
|
+
image:https://img.shields.io/travis/{gh-name}/{gh-branch}.svg?style={badge-style}[Build Status, link="https://travis-ci.org/{gh-name}"]
|
18
|
+
image:https://img.shields.io/codeclimate/coverage/github/{gh-name}.svg?style={badge-style}[Test Coverage, link="https://codeclimate.com/github/{gh-name}"]
|
19
|
+
image:https://img.shields.io/codeclimate/github/{gh-name}.svg?style={badge-style}[Code Climate, link="https://codeclimate.com/github/{gh-name}"]
|
20
|
+
image:https://img.shields.io/gem/v/{gem-name}.svg?style={badge-style}[Gem Version, link="https://rubygems.org/gems/{gem-name}"]
|
21
|
+
image:https://img.shields.io/badge/yard-docs-blue.svg?style={badge-style}[Yard Docs, link="http://www.rubydoc.info/github/{gh-name}/frames"]
|
22
|
+
endif::env-github[]
|
23
|
+
|
24
|
+
Corefines is a collection of general purpose _refinements_ for extending the core capabilities of Ruby’s built-in classes.
|
25
|
+
It also provides a <<compatibility-mode>> for older Ruby versions and alternative Ruby implementations that don’t support refinements (yet).
|
26
|
+
|
27
|
+
|
28
|
+
== Why refinements?
|
29
|
+
|
30
|
+
Extending core classes with so called monkey-paching pollutes the _global_ scope, so it affects all files on the `$LOAD_PATH`, i.e. whole application including used gems.
|
31
|
+
It’s not usually so big deal when you’re doing it in your application, but it’s very dangerous when used in a gem (library).
|
32
|
+
This can result in strange and hard to debug behaviour if another gem overrides a core class with the same method as your gem, but different implementation, and both gems are used together.
|
33
|
+
|
34
|
+
Refinements basically allows you to put monkey patches in an isolated namespace, so that your changes to core classes don’t affect other code.
|
35
|
+
|
36
|
+
TODO
|
37
|
+
|
38
|
+
|
39
|
+
== Installation
|
40
|
+
|
41
|
+
Add this line to your application’s Gemfile:
|
42
|
+
|
43
|
+
[source]
|
44
|
+
gem 'corefines', '~> 1.0'
|
45
|
+
|
46
|
+
or to your gemspec:
|
47
|
+
|
48
|
+
[source]
|
49
|
+
s.add_runtime_dependency 'corefines', '~> 1.0'
|
50
|
+
|
51
|
+
and then execute:
|
52
|
+
|
53
|
+
[source, sh]
|
54
|
+
$ bundle install
|
55
|
+
|
56
|
+
|
57
|
+
== Using
|
58
|
+
|
59
|
+
First, you must require `corefines` prior using:
|
60
|
+
|
61
|
+
[source]
|
62
|
+
require 'corefines'
|
63
|
+
|
64
|
+
This will _not_ activate any extensions (just register them), even when running in compatibility mode.
|
65
|
+
Extensions (refinements) are activated selectively with the method http://ruby-doc.org/core-2.2.0/Module.html#method-i-using[`using`].
|
66
|
+
|
67
|
+
Refinements are organized into modules by class which they refine, and further into submodules for individual methods.
|
68
|
+
When an extension refines multiple classes, then it’s included in a module named after their nearest common ancestor (superclass).
|
69
|
+
|
70
|
+
[source, plain]
|
71
|
+
Corefines::<CLASS>::<METHOD>
|
72
|
+
|
73
|
+
A single extension can be imported into the current scope classically, e.g.:
|
74
|
+
|
75
|
+
[source]
|
76
|
+
using Corefines::Object::ThenIf
|
77
|
+
|
78
|
+
or preferably using its “alias”:
|
79
|
+
|
80
|
+
[source]
|
81
|
+
using Corefines::Object::then_if
|
82
|
+
|
83
|
+
If you want to include all extensions for the class, then you can just import the parent module, e.g.:
|
84
|
+
|
85
|
+
[source]
|
86
|
+
using Corefines::Object
|
87
|
+
|
88
|
+
But more often you want to include multiple extensions for the class, but not all of them, e.g.:
|
89
|
+
|
90
|
+
[source]
|
91
|
+
using Corefines::Object::then_if
|
92
|
+
using Corefines::Object::in?
|
93
|
+
|
94
|
+
this can be abbreviated to:
|
95
|
+
|
96
|
+
[source]
|
97
|
+
using Corefines::Object[:then_if, :in?]
|
98
|
+
|
99
|
+
If you feel that _Corefines_ is too long, then you can also use abbreviation _CF_ instead:
|
100
|
+
|
101
|
+
[source]
|
102
|
+
using CF::Object::then_if
|
103
|
+
|
104
|
+
Refinements can be activated (with `using`) at top-level (per file), inside a class, module or a method.
|
105
|
+
|
106
|
+
|
107
|
+
== Compatibility mode
|
108
|
+
|
109
|
+
Refinements are still a young feature, so there’s a possibility that your gem or application will have to work on a Ruby platform that doesn’t fully support refinements yet.
|
110
|
+
|
111
|
+
The main Ruby implementation, https://en.wikipedia.org/wiki/Ruby_MRI[MRI] (aka CRuby), supports refinements since version 2.1.0 (https://www.ruby-lang.org/en/news/2013/12/25/ruby-2-1-0-is-released/[released in 25 Dec 2013]).
|
112
|
+
footnote:[Actually, refinements has been introduced to MRI in 2.0.0, as an experimental feature. However, its design and implementation has been changed then, so refinements in 2.0.x and 2.1+ behaves quite differently.]
|
113
|
+
Version 2.0.0 (https://www.ruby-lang.org/en/news/2013/02/24/ruby-2-0-0-p0-is-released/[released in 24 Feb 2013]) is still supported though.
|
114
|
+
http://www.jruby.org/[JRuby] doesn’t support refinements yet, it’s planned in the upcoming version 9.0.0.0 (https://github.com/jruby/jruby/issues/1062[#1062]).
|
115
|
+
http://rubini.us/[Rubinius] also doesn’t support refinements yet.
|
116
|
+
|
117
|
+
This gem is a collection of pure refinements, and yet, it works even on older Rubies that don’t support refinements.
|
118
|
+
Wait… how?
|
119
|
+
Well, when you use the gem with an older Ruby, it’s actually cheating.
|
120
|
+
Instead of locally scoped changes, it falls back to global monkey-patching.
|
121
|
+
|
122
|
+
The Corefines gem adds `refine` and `using` methods to the core classes, so you can define and use refinements just like in newer Rubies.
|
123
|
+
But internally it works very differently.
|
124
|
+
The `refine` method adds a given block to a collection of pending “refinements” inside its module.
|
125
|
+
When `using` is called _first time_ for the module, it _evaluates_ module’s “refinements” in context of the target classes (i.e. do a monkey-patch).
|
126
|
+
|
127
|
+
Not ideal indeed, but probably the best of what we can achieve.
|
128
|
+
|
129
|
+
|
130
|
+
== List of refinements
|
131
|
+
|
132
|
+
* {doc-base-url}/Array[Array]
|
133
|
+
** {doc-base-url}/Array/Second[#second]
|
134
|
+
** {doc-base-url}/Array/Third[#third]
|
135
|
+
* {doc-base-url}/Enumerable[Enumerable]
|
136
|
+
** {doc-base-url}/Enumerable/IndexBy[#index_by]
|
137
|
+
** {doc-base-url}/Enumerable/MapSend[#map_send]
|
138
|
+
* {doc-base-url}/Hash[Hash]
|
139
|
+
** {doc-base-url}/Hash/OpPlus[#+]
|
140
|
+
** {doc-base-url}/Hash/Compact[#compact]
|
141
|
+
** {doc-base-url}/Hash/Compact[#compact!]
|
142
|
+
** {doc-base-url}/Hash/Rekey[#rekey]
|
143
|
+
** {doc-base-url}/Hash/Rekey[#rekey!]
|
144
|
+
** {doc-base-url}/Hash/SymbolizeKeys[#symbolize_keys]
|
145
|
+
** {doc-base-url}/Hash/SymbolizeKeys[#symbolize_keys!]
|
146
|
+
* {doc-base-url}/Module[Module]
|
147
|
+
** {doc-base-url}/Module/AliasClassMethod[#alias_class_method]
|
148
|
+
** {doc-base-url}/Module/AliasMethodChain[#alias_method_chain]
|
149
|
+
* {doc-base-url}/Object[Object]
|
150
|
+
** {doc-base-url}/Object/Blank[#blank?]
|
151
|
+
** {doc-base-url}/Object/DeepDup[#deep_dup]
|
152
|
+
** {doc-base-url}/Object/Else[#else]
|
153
|
+
** {doc-base-url}/Object/In[#in?]
|
154
|
+
** {doc-base-url}/Object/InstanceValues[#instance_values]
|
155
|
+
** {doc-base-url}/Object/Blank[#presence]
|
156
|
+
** {doc-base-url}/Object/Then[#then]
|
157
|
+
** {doc-base-url}/Object/ThenIf[#then_if]
|
158
|
+
** {doc-base-url}/Object/Try[#try]
|
159
|
+
** {doc-base-url}/Object/Try[#try!]
|
160
|
+
* {doc-base-url}/String[String]
|
161
|
+
** {doc-base-url}/String/Color[#color]
|
162
|
+
** {doc-base-url}/String/Concat[#concat]
|
163
|
+
** {doc-base-url}/String/Decolor[#decolor]
|
164
|
+
** {doc-base-url}/String/Remove[#remove]
|
165
|
+
** {doc-base-url}/String/Unindent[#unindent] (alias `#strip_heredoc`)
|
166
|
+
* {doc-base-url}/Symbol[Symbol]
|
167
|
+
** {doc-base-url}/Symbol/Call[#call]
|
168
|
+
|
169
|
+
|
170
|
+
== Acknowledgement
|
171
|
+
|
172
|
+
Most of the extension methods are based on, or highly inspired from:
|
173
|
+
|
174
|
+
* https://github.com/rails/rails/tree/master/activesupport[Active Support (Ruby extensions)]
|
175
|
+
* https://github.com/rubyworks/facets[Ruby Facets]
|
176
|
+
* https://github.com/gregwebs/methodchain[methodchain]
|
177
|
+
* https://github.com/fazibear/colorize[colorize]
|
178
|
+
|
179
|
+
Very useful articles about refinements and how to “trick” them:
|
180
|
+
|
181
|
+
* https://www.new-bamboo.co.uk/blog/2014/02/05/refinements-under-the-knife/[
|
182
|
+
Refinements under the knife] by https://github.com/leemachin[@leemachin]
|
183
|
+
* http://qiita.com/joker1007/items/68d066a12bc763bd2cb4[Refinement関係の小技とできない事をまとめてみた] by https://github.com/joker1007[@joker1007]
|
184
|
+
|
185
|
+
|
186
|
+
== Contributing
|
187
|
+
|
188
|
+
. Fork it.
|
189
|
+
. Create your feature branch (`git checkout -b my-new-feature`).
|
190
|
+
. Commit your changes (`git commit -am 'Add some feature'`).
|
191
|
+
. Push to the branch (`git push origin my-new-feature`).
|
192
|
+
. Create a new Pull Request.
|
193
|
+
|
194
|
+
|
195
|
+
== License
|
196
|
+
|
197
|
+
This project is licensed under http://opensource.org/licenses/MIT/[MIT License].
|
198
|
+
For the full text of the license, see the link:LICENSE[LICENSE] file.
|
data/Rakefile
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
require 'bundler/gem_tasks'
|
2
|
+
|
3
|
+
begin
|
4
|
+
require 'rspec/core/rake_task'
|
5
|
+
|
6
|
+
RSpec::Core::RakeTask.new(:spec)
|
7
|
+
|
8
|
+
task :test => :spec
|
9
|
+
task :default => :spec
|
10
|
+
|
11
|
+
rescue LoadError => e
|
12
|
+
warn "#{e.path} is not available"
|
13
|
+
end
|
14
|
+
|
15
|
+
begin
|
16
|
+
require 'yard'
|
17
|
+
|
18
|
+
# options are defined in .yardopts
|
19
|
+
YARD::Rake::YardocTask.new(:yard)
|
20
|
+
|
21
|
+
rescue LoadError => e
|
22
|
+
warn "#{e.path} is not available"
|
23
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
require 'corefines/support/alias_submodules'
|
2
|
+
|
3
|
+
module Corefines
|
4
|
+
module Array
|
5
|
+
|
6
|
+
##
|
7
|
+
# @!method second
|
8
|
+
# @return the second element, or +nil+ if doesn't exist.
|
9
|
+
module Second
|
10
|
+
refine ::Array do
|
11
|
+
def second
|
12
|
+
self[1]
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
##
|
18
|
+
# @!method third
|
19
|
+
# @return the third element, or +nil+ if doesn't exist.
|
20
|
+
module Third
|
21
|
+
refine ::Array do
|
22
|
+
def third
|
23
|
+
self[2]
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
include Support::AliasSubmodules
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,64 @@
|
|
1
|
+
require 'corefines/support/alias_submodules'
|
2
|
+
require 'corefines/support/classes_including_module'
|
3
|
+
|
4
|
+
module Corefines
|
5
|
+
module Enumerable
|
6
|
+
|
7
|
+
##
|
8
|
+
# @!method index_by
|
9
|
+
# Convert enumerable into a Hash, iterating over each element where the
|
10
|
+
# provided block must return the key to be used to map to the value.
|
11
|
+
#
|
12
|
+
# It's similar to {::Enumerable#group_by}, but when two elements
|
13
|
+
# corresponds to the same key, then only the last one is preserved in the
|
14
|
+
# resulting Hash.
|
15
|
+
#
|
16
|
+
# @example
|
17
|
+
# people.index_by(&:login)
|
18
|
+
# => { "flynn" => <Person @login="flynn">, "bradley" => <Person @login="bradley">, ...}
|
19
|
+
#
|
20
|
+
# @example
|
21
|
+
# people.index_by.each(&:login)
|
22
|
+
# => { "flynn" => <Person @login="flynn">, "bradley" => <Person @login="bradley">, ...}
|
23
|
+
#
|
24
|
+
# @yield [obj] gives each element to the block.
|
25
|
+
# @yieldreturn the key to be used to map to the value.
|
26
|
+
# @return [Hash]
|
27
|
+
#
|
28
|
+
module IndexBy
|
29
|
+
Support.classes_including_module(::Enumerable) do |klass|
|
30
|
+
|
31
|
+
refine klass do
|
32
|
+
def index_by
|
33
|
+
::Hash[map { |elem| [ yield(elem), elem ] }]
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
##
|
40
|
+
# @!method map_send(method_name, *args, &block)
|
41
|
+
# Sends a message to each element and collects the result.
|
42
|
+
#
|
43
|
+
# @example
|
44
|
+
# [1, 2, 3].map_send(:+, 3) #=> [4, 5, 6]
|
45
|
+
#
|
46
|
+
# @param method_name [Symbol] name of the method to call.
|
47
|
+
# @param args arguments to pass to the method.
|
48
|
+
# @param block [Proc] block to pass to the method.
|
49
|
+
# @return [Enumerable]
|
50
|
+
#
|
51
|
+
module MapSend
|
52
|
+
Support.classes_including_module(::Enumerable) do |klass|
|
53
|
+
|
54
|
+
refine klass do
|
55
|
+
def map_send(method_name, *args, &block)
|
56
|
+
map { |e| e.__send__(method_name, *args, &block) }
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
include Support::AliasSubmodules
|
63
|
+
end
|
64
|
+
end
|
@@ -0,0 +1,164 @@
|
|
1
|
+
require 'corefines/support/alias_submodules'
|
2
|
+
|
3
|
+
module Corefines
|
4
|
+
module Hash
|
5
|
+
|
6
|
+
##
|
7
|
+
# @!method compact
|
8
|
+
# @example
|
9
|
+
# hash = { a: true, b: false, c: nil }
|
10
|
+
# hash.compact # => { a: true, b: false }
|
11
|
+
# hash # => { a: true, b: false, c: nil }
|
12
|
+
# { c: nil }.compact # => {}
|
13
|
+
#
|
14
|
+
# @return [Hash] a new hash with no key-value pairs which value is +nil+.
|
15
|
+
#
|
16
|
+
# @!method compact!
|
17
|
+
# Removes all key-value pairs from the hash which value is +nil+.
|
18
|
+
# Same as {#compact}, but modifies +self+.
|
19
|
+
#
|
20
|
+
# @return [Hash] self
|
21
|
+
#
|
22
|
+
module Compact
|
23
|
+
refine ::Hash do
|
24
|
+
def compact
|
25
|
+
reject { |_, value| value.nil? }
|
26
|
+
end
|
27
|
+
|
28
|
+
def compact!
|
29
|
+
delete_if { |_, value| value.nil? }
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
##
|
35
|
+
# @!method +(other_hash)
|
36
|
+
# Alias for +#merge+.
|
37
|
+
#
|
38
|
+
# @example
|
39
|
+
# {a: 1, b: 2} + {b: 3, c: 4}
|
40
|
+
# => {a: 1, b: 3, c: 4}
|
41
|
+
#
|
42
|
+
# @param other_hash [Hash]
|
43
|
+
# @return [Hash] a new hash containing the contents of _other_hash_ and
|
44
|
+
# this hash. The value for entries with duplicate keys will be that of
|
45
|
+
# _other_hash_.
|
46
|
+
#
|
47
|
+
module OpPlus
|
48
|
+
refine ::Hash do
|
49
|
+
def +(other_hash)
|
50
|
+
merge other_hash
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
##
|
56
|
+
# @!method rekey(key_map = nil, &block)
|
57
|
+
# Returns a new hash with keys transformed according to the given
|
58
|
+
# _key_map_ or the _block_.
|
59
|
+
#
|
60
|
+
# If no _key_map_ or _block_ is given, then all keys are converted
|
61
|
+
# to +Symbols+, as long as they respond to +to_sym+.
|
62
|
+
#
|
63
|
+
# @example
|
64
|
+
# hash = { :a => 1, 'b' => 2 }
|
65
|
+
# hash.rekey(:a => :x, :c => :y) # => { :x => 1, 'b' => 2 }
|
66
|
+
# hash.rekey(&:to_s) # => { 'a' => 1, 'b' => 2 }
|
67
|
+
# hash.rekey { |k| k.to_s.upcase } # => { 'A' => 1, 'B' => 2 }
|
68
|
+
# hash.rekey # => { :a => 1, :b => 2 }
|
69
|
+
#
|
70
|
+
# @overload rekey(key_map)
|
71
|
+
# @param key_map [Hash] a translation map from the old keys to the new
|
72
|
+
# keys; <tt>{from_key => to_key, ...}</tt>.
|
73
|
+
#
|
74
|
+
# @overload rekey
|
75
|
+
# @yield [key, value] gives every key-value pair to the block.
|
76
|
+
# The return value becomes a new key.
|
77
|
+
#
|
78
|
+
# @return [Hash] a new hash.
|
79
|
+
# @raise ArgumentError if both _key_map_ and the _block_ are given.
|
80
|
+
#
|
81
|
+
# @!method rekey!(key_map = nil, &block)
|
82
|
+
# Transforms keys according to the given _key_map_ or the _block_.
|
83
|
+
# Same as {#rekey}, but modifies +self+.
|
84
|
+
#
|
85
|
+
# @overload rekey!(key_map)
|
86
|
+
# @param key_map [Hash] a translation map from the old keys to the new
|
87
|
+
# keys; <tt>{from_key => to_key, ...}</tt>.
|
88
|
+
#
|
89
|
+
# @overload rekey!
|
90
|
+
# @yield [key, value] gives every key-value pair to the block.
|
91
|
+
# The return value becomes a new key.
|
92
|
+
#
|
93
|
+
# @return [Hash] self
|
94
|
+
# @raise (see #rekey)
|
95
|
+
#
|
96
|
+
module Rekey
|
97
|
+
refine ::Hash do
|
98
|
+
def rekey(key_map = nil, &block)
|
99
|
+
fail ArgumentError, "provide key_map, or block, not both" if key_map && block
|
100
|
+
block = ->(k, _) { k.to_sym rescue k } if !key_map && !block
|
101
|
+
|
102
|
+
# Note: self.dup is used to preserve the default_proc.
|
103
|
+
if block
|
104
|
+
# This is needed for "symbol procs" (e.g. &:to_s).
|
105
|
+
transform_key = block.arity.abs == 1 ? ->(k, _) { block[k] } : block
|
106
|
+
|
107
|
+
each_with_object(dup.clear) do |(key, value), hash|
|
108
|
+
hash[ transform_key[key, value] ] = value
|
109
|
+
end
|
110
|
+
else
|
111
|
+
key_map.each_with_object(dup) do |(from, to), hash|
|
112
|
+
hash[to] = hash.delete(from) if hash.key? from
|
113
|
+
end
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
def rekey!(key_map = nil, &block)
|
118
|
+
replace rekey(key_map, &block)
|
119
|
+
end
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
123
|
+
##
|
124
|
+
# @!method symbolize_keys
|
125
|
+
# @example
|
126
|
+
# hash = { 'name' => 'Lindsey', :born => 1986 }
|
127
|
+
# hash.symbolize_keys # => { :name => 'Lindsey', :born => 1986 }
|
128
|
+
# hash # => { 'name' => 'Lindsey', :born => 1986 }
|
129
|
+
#
|
130
|
+
# @return [Hash] a new hash with all keys converted to symbols, as long
|
131
|
+
# as they respond to +to_sym+.
|
132
|
+
#
|
133
|
+
# @!method symbolize_keys!
|
134
|
+
# Converts all keys to symbols, as long as they respond to +to_sym+.
|
135
|
+
# Same as {#symbolize_keys}, but modifies +self+.
|
136
|
+
#
|
137
|
+
# @return [Hash] self
|
138
|
+
#
|
139
|
+
module SymbolizeKeys
|
140
|
+
refine ::Hash do
|
141
|
+
def symbolize_keys
|
142
|
+
each_with_object(dup.clear) do |(key, value), hash|
|
143
|
+
hash[(key.to_sym rescue key)] = value
|
144
|
+
end
|
145
|
+
end
|
146
|
+
|
147
|
+
def symbolize_keys!
|
148
|
+
keys.each do |key|
|
149
|
+
self[(key.to_sym rescue key)] = delete(key)
|
150
|
+
end
|
151
|
+
self
|
152
|
+
end
|
153
|
+
end
|
154
|
+
end
|
155
|
+
|
156
|
+
include Support::AliasSubmodules
|
157
|
+
|
158
|
+
class << self
|
159
|
+
alias_method :compact!, :compact
|
160
|
+
alias_method :rekey!, :rekey
|
161
|
+
alias_method :symbolize_keys!, :symbolize_keys
|
162
|
+
end
|
163
|
+
end
|
164
|
+
end
|
@@ -0,0 +1,95 @@
|
|
1
|
+
require 'corefines/support/alias_submodules'
|
2
|
+
|
3
|
+
module Corefines
|
4
|
+
module Module
|
5
|
+
##
|
6
|
+
# @!method alias_class_method(new_name, old_name)
|
7
|
+
# Makes _new_name_ a new copy of the class method _old_name_.
|
8
|
+
#
|
9
|
+
# @param new_name [Symbol] name of the new class method to create.
|
10
|
+
# @param old_name [Symbol] name of the existing class method to alias.
|
11
|
+
# @return [self]
|
12
|
+
#
|
13
|
+
module AliasClassMethod
|
14
|
+
refine ::Module do
|
15
|
+
def alias_class_method(new_name, old_name)
|
16
|
+
singleton_class.__send__(:alias_method, new_name, old_name)
|
17
|
+
self
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
##
|
23
|
+
# @!method alias_method_chain(target, feature)
|
24
|
+
# Encapsulates the common pattern of:
|
25
|
+
#
|
26
|
+
# alias_method :meth_without_feature, :meth
|
27
|
+
# alias_method :meth, :meth_with_feature
|
28
|
+
#
|
29
|
+
# With this, you simply do:
|
30
|
+
#
|
31
|
+
# alias_method_chain :meth, :feature
|
32
|
+
#
|
33
|
+
# for example:
|
34
|
+
#
|
35
|
+
# class ChainExample
|
36
|
+
# def say
|
37
|
+
# "hello"
|
38
|
+
# end
|
39
|
+
# def say_with_accent
|
40
|
+
# "helloo!"
|
41
|
+
# end
|
42
|
+
# alias_method_chain :say, :accent
|
43
|
+
# end
|
44
|
+
#
|
45
|
+
# and both aliases are set up for you:
|
46
|
+
#
|
47
|
+
# example = ChainExample.new
|
48
|
+
# example.say #=> "helloo!"
|
49
|
+
# example.say_without_accent #=> "hello"
|
50
|
+
#
|
51
|
+
# Query and bang methods (+foo?+, +foo!+) keep the same punctuation:
|
52
|
+
#
|
53
|
+
# alias_method_chain :say!, :accent
|
54
|
+
#
|
55
|
+
# is equivalent to:
|
56
|
+
#
|
57
|
+
# alias_method :say_without_accent!, :say!
|
58
|
+
# alias_method :say!, :say_with_accent!
|
59
|
+
#
|
60
|
+
# so you can safely chain +foo+, +foo?+, and +foo!+ with the same
|
61
|
+
# feature.
|
62
|
+
#
|
63
|
+
# @param target [Symbol] name of the method to alias.
|
64
|
+
# @param feature [Symbol] suffix for the aliases.
|
65
|
+
# @return [self]
|
66
|
+
#
|
67
|
+
module AliasMethodChain
|
68
|
+
refine ::Module do
|
69
|
+
def alias_method_chain(target, feature)
|
70
|
+
# Strip out punctuation on predicates, bang or writer methods since
|
71
|
+
# e.g. target?_without_feature is not a valid method name.
|
72
|
+
aliased_target, punctuation = target.to_s.sub(/([?!=])$/, ''), $1
|
73
|
+
|
74
|
+
with_method = "#{aliased_target}_with_#{feature}#{punctuation}"
|
75
|
+
without_method = "#{aliased_target}_without_#{feature}#{punctuation}"
|
76
|
+
|
77
|
+
alias_method without_method, target
|
78
|
+
alias_method target, with_method
|
79
|
+
|
80
|
+
if public_method_defined? without_method
|
81
|
+
public target
|
82
|
+
elsif protected_method_defined? without_method
|
83
|
+
protected target
|
84
|
+
elsif private_method_defined? without_method
|
85
|
+
private target
|
86
|
+
end
|
87
|
+
|
88
|
+
self
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
include Support::AliasSubmodules
|
94
|
+
end
|
95
|
+
end
|