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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: b2ebd6df2befa524cfa62d7e94bd86c95b6e05f4
4
- data.tar.gz: efc4ed3226ffb5ed36fafd67735d294a48f0f8e2
3
+ metadata.gz: e0a5aa2de7709f8b53e6ab183581335b3493eb5e
4
+ data.tar.gz: 9838712103fbf49a3614ee930b53c6c7171457b8
5
5
  SHA512:
6
- metadata.gz: e13d18b83abee84c985762eb05639c63a8a9e32f2a936d709c4a26b100cb5e95008ee561640d16f2ccf5fca4aedf6745347cf2d26e2026637ddae5dc2ed9d693
7
- data.tar.gz: 4d777965f7a99d0cec674d290d9290b512394068e9cca996b59565e27e640a29bb3308d0079aa0132a672cc73a6223fc2421acb8de064ed52e1f6ea1310c598b
6
+ metadata.gz: d3d4032fcfd695eaec9baeaaa6d4c4c6f9d6e8144cbfaa5cd01edda887320818fac8d55b5b49391f1edc129db8d703ad275449b191850236606bcfe16530a122
7
+ data.tar.gz: f3d6bb17dd490336d578d7e13747393b14233fc1c1da36857fc1966ea8384ae8a20ca2a716004391192662681e25674f9ef841b1fd70b12e4c079c0de518bfc0
data/README.md CHANGED
@@ -1,26 +1,26 @@
1
- # DuckPuncher
1
+ # DuckPuncher [![Gem Version](https://badge.fury.io/rb/duck_puncher.svg)](http://badge.fury.io/rb/duck_puncher) [![Build Status](https://travis-ci.org/ridiculous/duck_puncher.svg)](https://travis-ci.org/ridiculous/duck_puncher) [![Code Climate](https://codeclimate.com/github/ridiculous/duck_puncher/badges/gpa.svg)](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. You know, lil love punches! :punch: :heart:
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 => `[].m(:to_s)` => `[].map(&:to_s)`
10
- Array#mm => `[].mm(:sub, /[aeiou]/, '*')` => `[].map { |x| x.sub(/[aeiou]/, '*') }`
11
- Array#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
- Numeric#to_duration => `10_000.to_duration` => '2 h 46 min'
15
- Numeric#to_time_ago => `10_000.to_time_ago` => '2 hours ago'
16
- Numeric#to_rad => `10.15.to_rad` => 0.17715091907742445
17
- String#pluralize => `'hour'.pluralize(2)` => "hours"
18
- String#underscore => `'DuckPuncher::JSONStorage'.underscore` => 'duck_puncher/json_storage'
19
- Object#clone! => `Object.new.clone!` => a deep clone of the object (using Marshal.dump)
20
- Object#punch => `'duck'.punch` => a copy of 'duck' with the mixed String punches
21
- Object#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
- Method#to_source => `Benchmark.method(:measure).to_source` returns the method definition as a string
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
- INFO: Already punched Numeric
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! #=> punches all the ducks forever
76
- DuckPuncher.punch! :Hash, :Object #=> only punches the Hash and Object ducks
77
- DuckPuncher.punch! :Object, only: :punch #=> only opens a can of whoop ass! Define one method to rule them all
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
- Create a new class of your favorite duck pre-punched:
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 #=> returns an anonymous punched duck that inherits from String
84
- DuckString = DuckPuncher.punch :String #=> give the anonymous duck a name, so that you can use it!
85
- DuckString.new.respond_to? :underscore #=> true
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) #=> [true, false, true]
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
@@ -7,7 +7,7 @@ Rake::TestTask.new(:soft_punch_test) do |t|
7
7
  end
8
8
 
9
9
  Rake::TestTask.new(:hard_punch_test) do |t|
10
- t.pattern = 'test/duck_puncher/*_test.rb'
10
+ t.pattern = 'test/lib/**/*_test.rb'
11
11
  end
12
12
 
13
13
  task default: [:soft_punch_test, :hard_punch_test]
@@ -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
- def load_path
11
- "duck_puncher/ducks/#{name.to_s.gsub(/\B([A-Z])/, '_\1').downcase}"
12
- end
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
- def delegated
32
- DelegateClass(klass).tap { |k| punch target: k }
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
@@ -11,8 +11,8 @@ module DuckPuncher
11
11
  end
12
12
  end
13
13
 
14
- def punch
15
- DuckPuncher.delegate_class(self.class.name.to_sym).new(self)
14
+ def punch(duck_name = self.class.name)
15
+ DuckPuncher.delegate_class(duck_name.to_sym, self).new(self)
16
16
  end
17
17
 
18
18
  def track
@@ -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.load_path
31
+ autoload duck.name, load_path_for(duck)
28
32
  end
29
33
  end
30
34
  end
@@ -1,3 +1,3 @@
1
1
  module DuckPuncher
2
- VERSION = '2.9.3'.freeze
2
+ VERSION = '2.10.0'.freeze
3
3
  end
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 delegate_class(name)
17
+ def delegations
18
18
  @delegations ||= {}
19
- @delegations[name] ||= Ducks[name].dup.delegated
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 { |name| Ducks[name].dup.classify }.compact
26
- punched_ducks = punched_ducks.first if singular
27
- punched_ducks
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
@@ -1,4 +1,5 @@
1
- require_relative '../test_helper'
1
+ require_relative '../../test_helper'
2
+
2
3
  DuckPuncher.punch! :Object
3
4
 
4
5
  class ArrayTest < MiniTest::Test
@@ -1,4 +1,4 @@
1
- require_relative '../test_helper'
1
+ require_relative '../../test_helper'
2
2
  DuckPuncher.punch! :Object
3
3
 
4
4
  class HashTest < MiniTest::Test
@@ -1,5 +1,5 @@
1
- require_relative '../test_helper'
2
- require_relative '../fixtures/wut'
1
+ require_relative '../../test_helper'
2
+ require_relative '../../fixtures/wut'
3
3
  DuckPuncher.punch! :Method
4
4
 
5
5
  class MethodTest < MiniTest::Test
@@ -1,4 +1,4 @@
1
- require_relative '../test_helper'
1
+ require_relative '../../test_helper'
2
2
  DuckPuncher.punch! :Numeric
3
3
 
4
4
  class NumericTest < MiniTest::Test
@@ -1,4 +1,4 @@
1
- require_relative '../test_helper'
1
+ require_relative '../../test_helper'
2
2
  DuckPuncher.punch! :Object
3
3
 
4
4
  class ObjectTest < MiniTest::Test
@@ -1,4 +1,4 @@
1
- require_relative '../test_helper'
1
+ require_relative '../../test_helper'
2
2
 
3
3
  DuckPuncher.punch! :String
4
4
 
@@ -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.9.3
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-06 00:00:00.000000000 Z
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