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