duck_puncher 2.9.3 → 2.10.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 +4 -4
- data/README.md +90 -50
- data/Rakefile +1 -1
- data/lib/duck_puncher/duck.rb +13 -6
- data/lib/duck_puncher/ducks/object.rb +2 -2
- data/lib/duck_puncher/ducks.rb +5 -1
- data/lib/duck_puncher/version.rb +1 -1
- data/lib/duck_puncher.rb +25 -5
- data/test/{duck_puncher → lib/duck_puncher}/array_test.rb +2 -1
- data/test/{duck_puncher → lib/duck_puncher}/hash_test.rb +1 -1
- data/test/{duck_puncher → lib/duck_puncher}/method_test.rb +2 -2
- data/test/{duck_puncher → lib/duck_puncher}/numeric_test.rb +1 -1
- data/test/{duck_puncher → lib/duck_puncher}/object_test.rb +1 -1
- data/test/{duck_puncher → lib/duck_puncher}/string_test.rb +1 -1
- data/test/lib/duck_puncher_test.rb +23 -0
- metadata +16 -14
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: e0a5aa2de7709f8b53e6ab183581335b3493eb5e
|
4
|
+
data.tar.gz: 9838712103fbf49a3614ee930b53c6c7171457b8
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: d3d4032fcfd695eaec9baeaaa6d4c4c6f9d6e8144cbfaa5cd01edda887320818fac8d55b5b49391f1edc129db8d703ad275449b191850236606bcfe16530a122
|
7
|
+
data.tar.gz: f3d6bb17dd490336d578d7e13747393b14233fc1c1da36857fc1966ea8384ae8a20ca2a716004391192662681e25674f9ef841b1fd70b12e4c079c0de518bfc0
|
data/README.md
CHANGED
@@ -1,26 +1,26 @@
|
|
1
|
-
# DuckPuncher
|
1
|
+
# DuckPuncher [](http://badge.fury.io/rb/duck_puncher) [](https://travis-ci.org/ridiculous/duck_puncher) [](https://codeclimate.com/github/ridiculous/duck_puncher)
|
2
2
|
|
3
3
|
Since Ruby objects walk and talk like ducks, they must therefore _be_ ducks. But ducks don't always behave, and some times they need
|
4
|
-
tough love
|
4
|
+
tough love! :punch: :heart:
|
5
5
|
|
6
6
|
These are the ducks I love the most:
|
7
7
|
|
8
8
|
```ruby
|
9
|
-
Array#m
|
10
|
-
|
11
|
-
|
12
|
-
Hash#dig
|
13
|
-
Numeric#to_currency
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
String#pluralize => `'hour'.pluralize(2)` => "hours"
|
18
|
-
|
19
|
-
Object#clone! => `Object.new.clone!` => a deep clone of the object (using Marshal.dump)
|
20
|
-
|
21
|
-
|
22
|
-
Method#to_instruct => `Benchmark.method(:measure).to_instruct` returns the Ruby VM instruction sequence for the method
|
23
|
-
|
9
|
+
Array #m => `[].m(:to_s)` => `[].map(&:to_s)`
|
10
|
+
#mm => `[].mm(:sub, /[aeiou]/, '*')` => `[].map { |x| x.sub(/[aeiou]/, '*') }`
|
11
|
+
#get => `[].methods.get('ty?')` => [:empty?]
|
12
|
+
Hash #dig => `{a: 1, b: {c: 2}}.dig(:b, :c)` => 2 (Part of standard lib in Ruby >= 2.3)
|
13
|
+
Numeric #to_currency => `25.245.to_currency` => 25.25
|
14
|
+
#to_duration => `10_000.to_duration` => '2 h 46 min'
|
15
|
+
#to_time_ago => `10_000.to_time_ago` => '2 hours ago'
|
16
|
+
#to_rad => `10.15.to_rad` => 0.17715091907742445
|
17
|
+
String #pluralize => `'hour'.pluralize(2)` => "hours"
|
18
|
+
#underscore => `'DuckPuncher::JSONStorage'.underscore` => 'duck_puncher/json_storage'
|
19
|
+
Object #clone! => `Object.new.clone!` => a deep clone of the object (using Marshal.dump)
|
20
|
+
#punch => `'duck'.punch` => a copy of 'duck' with the mixed String punches
|
21
|
+
#track => `'duck'.punch.track` => downloads the [ObjectTracker](https://github.com/ridiculous/object_tracker) gem if it's not available and starts tracking this object
|
22
|
+
Method #to_instruct => `Benchmark.method(:measure).to_instruct` returns the Ruby VM instruction sequence for the method
|
23
|
+
#to_source => `Benchmark.method(:measure).to_source` returns the method definition as a string
|
24
24
|
```
|
25
25
|
|
26
26
|
## Tactical punches
|
@@ -29,7 +29,7 @@ Sometimes you don't want to punch all the ducks. That's why you can punch only c
|
|
29
29
|
|
30
30
|
```ruby
|
31
31
|
>> DuckPuncher.punch! :Numeric, only: [:to_currency, :to_duration]
|
32
|
-
|
32
|
+
WARN: Punching [:to_currency, :to_duration] onto Numeric
|
33
33
|
=> nil
|
34
34
|
>> 100.to_currency '$'
|
35
35
|
=> "$100.00"
|
@@ -39,29 +39,6 @@ INFO: Already punched Numeric
|
|
39
39
|
NoMethodError: undefined method `to_time_ago' for 100:Fixnum
|
40
40
|
```
|
41
41
|
|
42
|
-
There is also an experimental punch that tries to download the required gem if it doesn't exist on your computer. The
|
43
|
-
method is called `require!` and works like this:
|
44
|
-
|
45
|
-
Downloads and activates a gem for the current and subsequent consoles. For example:
|
46
|
-
|
47
|
-
```bash
|
48
|
-
>> `require 'pry'`
|
49
|
-
LoadError: cannot load such file -- pry
|
50
|
-
from (irb):1:in `require'
|
51
|
-
from (irb):1
|
52
|
-
from bin/console:10:in `<main>'
|
53
|
-
>> require! 'pry'
|
54
|
-
Fetching: method_source-0.8.2.gem (100%)
|
55
|
-
Fetching: slop-3.6.0.gem (100%)
|
56
|
-
Fetching: coderay-1.1.0.gem (100%)
|
57
|
-
Fetching: pry-0.10.3.gem (100%)
|
58
|
-
=> true
|
59
|
-
>> Pry.start
|
60
|
-
[1] pry(main)>
|
61
|
-
```
|
62
|
-
|
63
|
-
Pretty cool, right? Although, it doesn't work well with bigger gems or those with native extensions.
|
64
|
-
|
65
42
|
## Install
|
66
43
|
|
67
44
|
gem 'duck_puncher'
|
@@ -72,29 +49,92 @@ Ducks need to be _loaded_ before they can be punched! Maybe put this in an initi
|
|
72
49
|
|
73
50
|
```ruby
|
74
51
|
# config/initializers/duck_puncher.rb
|
75
|
-
DuckPuncher.punch_all!
|
76
|
-
DuckPuncher.punch! :Hash, :Object
|
77
|
-
DuckPuncher.punch! :Object, only: :punch
|
52
|
+
DuckPuncher.punch_all! # => punches all the ducks forever
|
53
|
+
DuckPuncher.punch! :Hash, :Object # => only punches the Hash and Object ducks
|
54
|
+
DuckPuncher.punch! :Object, only: :punch # => only opens a can of whoop ass! Define one method to rule them all
|
78
55
|
```
|
79
56
|
|
80
|
-
|
57
|
+
The `.punch` method creates and caches a new punched class that inherits from the original. This avoids altering built-in
|
58
|
+
classes. For example:
|
81
59
|
|
82
60
|
```ruby
|
83
|
-
DuckPuncher.punch :String
|
84
|
-
|
85
|
-
|
61
|
+
>> DuckPuncher.punch :String
|
62
|
+
=> DuckPuncher::StringDuck
|
63
|
+
>> DuckPuncher::StringDuck.new('Yes').to_boolean
|
64
|
+
=> true
|
65
|
+
>> String.new('Yes').respond_to? :to_boolean
|
66
|
+
=> false
|
86
67
|
```
|
87
68
|
|
88
69
|
If you punch `Object` then you can use `punch` on any object to get a new decorated copy of the class with the desired
|
89
70
|
functionality mixed in:
|
90
71
|
|
91
72
|
```ruby
|
92
|
-
DuckPuncher.punch! :Object, only: :punch
|
93
|
-
%w[yes no 1].punch.m(:punch).punch.m(:to_boolean)
|
73
|
+
>> DuckPuncher.punch! :Object, only: :punch
|
74
|
+
>> %w[yes no 1].punch.m(:punch).punch.m(:to_boolean)
|
75
|
+
=> [true, false, true]
|
94
76
|
```
|
95
77
|
|
96
78
|
Because `DuckPuncher` extends the amazing [Usable](https://github.com/ridiculous/usable) gem, you can configure only the punches you want!
|
97
79
|
|
80
|
+
## Registering custom punches
|
81
|
+
|
82
|
+
DuckPuncher allows you to utilize the `punch` interface to decorate any kind of object with your own punches. Simply call
|
83
|
+
`.register` with the name of your module:
|
84
|
+
|
85
|
+
```ruby
|
86
|
+
module Donald
|
87
|
+
def tap_tap
|
88
|
+
p self
|
89
|
+
self
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
DuckPuncher.register :Donald, class: 'Array', if: -> { !defined?(::Rails) || Rails.env.development? }
|
94
|
+
```
|
95
|
+
|
96
|
+
The register method takes the same options as [Duck#initialize](https://github.com/ridiculous/duck_puncher/blob/master/lib/duck_puncher/duck.rb#L11)
|
97
|
+
and will be used to configure punches.
|
98
|
+
|
99
|
+
Activate a custom punch:
|
100
|
+
|
101
|
+
```ruby
|
102
|
+
DuckPuncher.punch! :Donald
|
103
|
+
[].tap_tap
|
104
|
+
# or
|
105
|
+
DuckPuncher.punch! :Object, only: :punch
|
106
|
+
[].punch(:Donald).tap_tap
|
107
|
+
```
|
108
|
+
|
109
|
+
## Experimental
|
110
|
+
|
111
|
+
__Object#require__ will try to require a gem, or, if it's not found, then _download_ it! It will also keep track of any
|
112
|
+
downloaded gems and load them for subsequent IRB/rails console sessions. Gems are _not_
|
113
|
+
saved to the Gemfile.
|
114
|
+
|
115
|
+
In the wild:
|
116
|
+
|
117
|
+
```bash
|
118
|
+
>> `require 'pry'`
|
119
|
+
LoadError: cannot load such file -- pry
|
120
|
+
from (irb):1:in `require'
|
121
|
+
from (irb):1
|
122
|
+
from bin/console:10:in `<main>'
|
123
|
+
>> DuckPuncher.punch! :Object, only: :require!
|
124
|
+
WARN: Punching require! onto Object
|
125
|
+
=> nil
|
126
|
+
>> require! 'pry'
|
127
|
+
Fetching: method_source-0.8.2.gem (100%)
|
128
|
+
Fetching: slop-3.6.0.gem (100%)
|
129
|
+
Fetching: coderay-1.1.0.gem (100%)
|
130
|
+
Fetching: pry-0.10.3.gem (100%)
|
131
|
+
=> true
|
132
|
+
>> Pry.start
|
133
|
+
[1] pry(main)>
|
134
|
+
```
|
135
|
+
|
136
|
+
Perfect! Mostly ... although, it doesn't work well with bigger gems or those with native extensions ¯\\\_(ツ)_/¯
|
137
|
+
|
98
138
|
## Contributing
|
99
139
|
|
100
140
|
* Fork it
|
data/Rakefile
CHANGED
data/lib/duck_puncher/duck.rb
CHANGED
@@ -2,15 +2,20 @@ module DuckPuncher
|
|
2
2
|
class Duck
|
3
3
|
attr_accessor :name, :options
|
4
4
|
|
5
|
+
# @param [Symbol] name of the duck
|
6
|
+
# @param [Hash] options to modify the duck #punch method behavior
|
7
|
+
# @option options [String] :class (name) to punch
|
8
|
+
# @option options [Proc] :if Stops +punch+ if it returns false
|
9
|
+
# @option options [Proc] :before A hook that is called with the target @klass before +punch+
|
10
|
+
# @option options [Proc] :after A hook that is called with the target @klass after +punch+
|
5
11
|
def initialize(name, options = {})
|
6
12
|
@name = name
|
7
13
|
@options = options
|
8
14
|
end
|
9
15
|
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
16
|
+
# @param [Hash] opts to modify punch
|
17
|
+
# @option options [Class] :target Overrides the @klass to be punched
|
18
|
+
# @option options [Array,Symbol] :only Specifies the methods to extend onto the current object
|
14
19
|
def punch(opts = {})
|
15
20
|
if options[:if] && !options[:if].call
|
16
21
|
DuckPuncher.log.warn %Q(Failed to punch #{name}!)
|
@@ -28,8 +33,10 @@ module DuckPuncher
|
|
28
33
|
@klass ||= (options[:class] || name).to_s.split('::').inject(Kernel) { |k, part| k.const_get part }
|
29
34
|
end
|
30
35
|
|
31
|
-
|
32
|
-
|
36
|
+
# @param [Class] obj The object being punched
|
37
|
+
def delegated(obj = nil)
|
38
|
+
obj_class = obj ? obj.class : klass
|
39
|
+
DelegateClass(obj_class).tap { |k| punch target: k }
|
33
40
|
end
|
34
41
|
|
35
42
|
def classify
|
data/lib/duck_puncher/ducks.rb
CHANGED
@@ -17,6 +17,10 @@ module DuckPuncher
|
|
17
17
|
list.find { |duck| duck.name == name.to_sym } ||
|
18
18
|
DuckPuncher.log.info(%Q(Couldn't find "#{name}" in my list of Ducks! I know about: #{list.map(&:name).map(&:to_s)}))
|
19
19
|
end
|
20
|
+
|
21
|
+
def load_path_for(duck)
|
22
|
+
"duck_puncher/ducks/#{duck.name.to_s.gsub(/\B([A-Z])/, '_\1').downcase}"
|
23
|
+
end
|
20
24
|
end
|
21
25
|
|
22
26
|
#
|
@@ -24,7 +28,7 @@ module DuckPuncher
|
|
24
28
|
#
|
25
29
|
|
26
30
|
list.each do |duck|
|
27
|
-
autoload duck.name, duck
|
31
|
+
autoload duck.name, load_path_for(duck)
|
28
32
|
end
|
29
33
|
end
|
30
34
|
end
|
data/lib/duck_puncher/version.rb
CHANGED
data/lib/duck_puncher.rb
CHANGED
@@ -14,17 +14,33 @@ module DuckPuncher
|
|
14
14
|
class << self
|
15
15
|
attr_accessor :log
|
16
16
|
|
17
|
-
def
|
17
|
+
def delegations
|
18
18
|
@delegations ||= {}
|
19
|
-
|
19
|
+
end
|
20
|
+
|
21
|
+
def classes
|
22
|
+
@classes ||= {}
|
23
|
+
end
|
24
|
+
|
25
|
+
# @param [Symbol] duck_name
|
26
|
+
# @param [Class] obj The object being punched
|
27
|
+
def delegate_class(duck_name, obj = nil)
|
28
|
+
delegations[duck_name] ||= const_set "#{duck_name}DuckDelegated", Ducks[duck_name].dup.delegated(obj)
|
29
|
+
end
|
30
|
+
|
31
|
+
def duck_class(name)
|
32
|
+
classes[name] ||= const_set "#{name}Duck", Ducks[name].dup.classify
|
20
33
|
end
|
21
34
|
|
22
35
|
# @description Extends functionality to a copy of the specified class
|
23
36
|
def punch(*names)
|
24
37
|
singular = names.size == 1
|
25
|
-
punched_ducks = names.map
|
26
|
-
|
27
|
-
|
38
|
+
punched_ducks = names.map(&method(:duck_class)).compact
|
39
|
+
if singular
|
40
|
+
punched_ducks.first
|
41
|
+
else
|
42
|
+
punched_ducks
|
43
|
+
end
|
28
44
|
end
|
29
45
|
|
30
46
|
def punch!(*names)
|
@@ -43,6 +59,10 @@ module DuckPuncher
|
|
43
59
|
log.warn 'Punching all ducks! Watch out!'
|
44
60
|
Ducks.list.each &:punch
|
45
61
|
end
|
62
|
+
|
63
|
+
def register(*args)
|
64
|
+
Ducks.list << Duck.new(*args)
|
65
|
+
end
|
46
66
|
end
|
47
67
|
|
48
68
|
# @description Default logger
|
@@ -0,0 +1,23 @@
|
|
1
|
+
require_relative '../test_helper'
|
2
|
+
|
3
|
+
module CustomPunch
|
4
|
+
def tap_tap
|
5
|
+
p self
|
6
|
+
self
|
7
|
+
end
|
8
|
+
end
|
9
|
+
|
10
|
+
class DuckPuncherTest < MiniTest::Test
|
11
|
+
def teardown
|
12
|
+
Object.send :remove_const, :CustomPunch
|
13
|
+
end
|
14
|
+
|
15
|
+
def test_register
|
16
|
+
refute_respond_to [], :tap_tap
|
17
|
+
DuckPuncher.register :CustomPunch, class: 'Array'
|
18
|
+
DuckPuncher.punch! :CustomPunch
|
19
|
+
assert_respond_to [], :tap_tap
|
20
|
+
DuckPuncher.punch! :Object, only: :punch
|
21
|
+
assert_respond_to [].punch(:CustomPunch), :tap_tap
|
22
|
+
end
|
23
|
+
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: duck_puncher
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 2.
|
4
|
+
version: 2.10.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Ryan Buckley
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2016-02-
|
11
|
+
date: 2016-02-16 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: usable
|
@@ -117,13 +117,14 @@ files:
|
|
117
117
|
- lib/duck_puncher/gem_installer.rb
|
118
118
|
- lib/duck_puncher/json_storage.rb
|
119
119
|
- lib/duck_puncher/version.rb
|
120
|
-
- test/duck_puncher/array_test.rb
|
121
|
-
- test/duck_puncher/hash_test.rb
|
122
|
-
- test/duck_puncher/method_test.rb
|
123
|
-
- test/duck_puncher/numeric_test.rb
|
124
|
-
- test/duck_puncher/object_test.rb
|
125
|
-
- test/duck_puncher/string_test.rb
|
126
120
|
- test/fixtures/wut.rb
|
121
|
+
- test/lib/duck_puncher/array_test.rb
|
122
|
+
- test/lib/duck_puncher/hash_test.rb
|
123
|
+
- test/lib/duck_puncher/method_test.rb
|
124
|
+
- test/lib/duck_puncher/numeric_test.rb
|
125
|
+
- test/lib/duck_puncher/object_test.rb
|
126
|
+
- test/lib/duck_puncher/string_test.rb
|
127
|
+
- test/lib/duck_puncher_test.rb
|
127
128
|
- test/soft_punch/duck_puncher_test.rb
|
128
129
|
- test/test_helper.rb
|
129
130
|
homepage: https://github.com/ridiculous/duck_puncher
|
@@ -151,12 +152,13 @@ signing_key:
|
|
151
152
|
specification_version: 4
|
152
153
|
summary: Administer precision punches to your favorite Ruby classes
|
153
154
|
test_files:
|
154
|
-
- test/duck_puncher/array_test.rb
|
155
|
-
- test/duck_puncher/hash_test.rb
|
156
|
-
- test/duck_puncher/method_test.rb
|
157
|
-
- test/duck_puncher/numeric_test.rb
|
158
|
-
- test/duck_puncher/object_test.rb
|
159
|
-
- test/duck_puncher/string_test.rb
|
160
155
|
- test/fixtures/wut.rb
|
156
|
+
- test/lib/duck_puncher/array_test.rb
|
157
|
+
- test/lib/duck_puncher/hash_test.rb
|
158
|
+
- test/lib/duck_puncher/method_test.rb
|
159
|
+
- test/lib/duck_puncher/numeric_test.rb
|
160
|
+
- test/lib/duck_puncher/object_test.rb
|
161
|
+
- test/lib/duck_puncher/string_test.rb
|
162
|
+
- test/lib/duck_puncher_test.rb
|
161
163
|
- test/soft_punch/duck_puncher_test.rb
|
162
164
|
- test/test_helper.rb
|