duck_puncher 4.1.0 → 4.2.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: 1cb44e09391f847e93e57bcf8440f2c2d15ed047
4
- data.tar.gz: 3ce4e5424e681138720017420ac9acf62fb813d2
3
+ metadata.gz: f03c81056b93d66e3c9799672cc6e20c34d1ac72
4
+ data.tar.gz: 30186f7d1cc1b8ce19d94810b17e24cf7fe0b2ab
5
5
  SHA512:
6
- metadata.gz: 21f328e2955ba0a5d22edb8182ebba32707413aff62e0f788bc87fe5bf889317d601d44cc809cdfb1edb3e8029777080124e26f57b65e0a6b499e0b51c775960
7
- data.tar.gz: 1de4fb69952aec16f29c3bfa6034cf4400b5aea73d64bbfee6f7b735c0149e4fb5cd278cacd2d45dfbd4bd7a27faf29117a2ef55393520f2cbbe95b8820b27e7
6
+ metadata.gz: a27458ff3a2d1e52d381c44d91b6281b0c485870471298413a8144a070574e06a2119d75f9f61feb803167d0cf6e23c05d7a31f967dc9482eb2c46bfc62429b7
7
+ data.tar.gz: 5cb8230d00f0cf0b058b1db9f9043990beacf2c4dcafb1073aae9d74356b7fc8dd0d6a0ec7eb42a1ceb8b525c9ba21b85d9a1c4458f972e675f98799a2fdba70
data/README.md CHANGED
@@ -8,28 +8,36 @@ DuckPuncher provides an interface for administering __duck punches__ (a.k.a "mon
8
8
  Default extensions:
9
9
 
10
10
  ```ruby
11
- Array #m => `[].m(:to_s)` => `[].map(&:to_s)`
11
+ Enumerable
12
+ #m => `[].m(:to_s)` => `[].map(&:to_s)`
12
13
  #m! => `[].m!(:upcase)` => `[].map!(&:upcase)`
13
14
  #mm => `[].mm(:sub, /[aeiou]/, '*')` => `[].map { |x| x.sub(/[aeiou]/, '*') }`
14
15
  #mm! => `[].mm!(:sub, /[aeiou]/, '*')` => `[].map! { |x| x.sub(/[aeiou]/, '*') }`
15
16
  #except => `[].except('foo', 'bar')` => `[] - ['foo', 'bar']`
16
- Hash #dig => `{a: 1, b: {c: 2}}.dig(:b, :c)` => 2 (Part of standard lib in Ruby >= 2.3)
17
+ #map_keys => `[{id: 1, name: 'foo'}, {id: 2}].map_keys(:id)` => `[1, 2]`
18
+ Hash
19
+ #dig => `{a: 1, b: {c: 2}}.dig(:b, :c)` => 2 (Part of standard lib in Ruby >= 2.3)
17
20
  #compact => `{a: 1, b: nil}.compact` => {a: 1}
18
- Numeric #to_currency => `25.245.to_currency` => 25.25
21
+ Numeric
22
+ #to_currency => `25.245.to_currency` => 25.25
19
23
  #to_duration => `10_000.to_duration` => '2 h 46 min'
20
24
  #to_time_ago => `10_000.to_time_ago` => '2 hours ago'
21
25
  #to_rad => `10.15.to_rad` => 0.17715091907742445
22
- String #pluralize => `'hour'.pluralize(2)` => "hours"
26
+ String
27
+ #pluralize => `'hour'.pluralize(2)` => "hours"
23
28
  #underscore => `'DuckPuncher::JSONStorage'.underscore` => 'duck_puncher/json_storage'
24
29
  #to_boolean => `'1'.to_boolean` => true
25
30
  #constantize => `'MiniTest::Test'.constantize` => MiniTest::Test
26
- Module #local_methods => `Kernel.local_methods` returns the methods defined directly in the class + nested constants w/ methods
27
- Object #clone! => `Object.new.clone!` => a deep clone of the object (using Marshal.dump)
31
+ Module
32
+ #local_methods => `Kernel.local_methods` returns the methods defined directly in the class + nested constants w/ methods
33
+ Object
34
+ #clone! => `Object.new.clone!` => a deep clone of the object (using Marshal.dump)
28
35
  #punch => `'duck'.punch` => a copy of 'duck' with String punches mixed in
29
36
  #punch! => `'duck'.punch!` => destructive version applies extensions directly to the base object
30
37
  #echo => `'duck'.echo.upcase` => spits out the caller and value of the object and returns the object
31
38
  #track => `Object.new.track` => Traces methods calls to the object (requires [object_tracker](https://github.com/ridiculous/object_tracker), which it'll try to download)
32
- Method #to_instruct => `Benchmark.method(:measure).to_instruct` returns the Ruby VM instruction sequence for the method
39
+ Method
40
+ #to_instruct => `Benchmark.method(:measure).to_instruct` returns the Ruby VM instruction sequence for the method
33
41
  #to_source => `Benchmark.method(:measure).to_source` returns the method definition as a string
34
42
  ```
35
43
 
@@ -38,19 +46,19 @@ Method #to_instruct => `Benchmark.method(:measure).to_instruct` returns
38
46
  Punch all registered ducks:
39
47
 
40
48
  ```ruby
41
- DuckPuncher.punch_all!
49
+ DuckPuncher.()
42
50
  ```
43
51
 
44
52
  Punch individual ducks by name:
45
53
 
46
54
  ```ruby
47
- DuckPuncher.punch! Hash, Object
55
+ DuckPuncher.(Hash, Object)
48
56
  ```
49
57
 
50
58
  One method to rule them all:
51
59
 
52
60
  ```ruby
53
- DuckPuncher.punch! Object, only: :punch
61
+ DuckPuncher.(Object, only: :punch)
54
62
  ```
55
63
 
56
64
  ### Tactical punches
data/bin/console CHANGED
@@ -7,10 +7,10 @@ require 'irb'
7
7
  require 'byebug'
8
8
  require_relative '../test/fixtures/wut'
9
9
 
10
- DuckPuncher.log.level = Logger::DEBUG
10
+ DuckPuncher.logger.level = Logger::DEBUG
11
11
 
12
12
  if ENV['PUNCH'] != 'no'
13
- DuckPuncher.punch_all!
13
+ DuckPuncher.()
14
14
  end
15
15
 
16
16
  IRB.start
@@ -0,0 +1,8 @@
1
+ module DuckPuncher
2
+ # @note When updating this file please update comment regarding this module in duck_puncher.rb
3
+ module AncestralHash
4
+ def ancestral_hash
5
+ ::Hash.new { |me, klass| me[me.keys.detect { |key| key > klass }] if klass }
6
+ end
7
+ end
8
+ end
@@ -1,4 +1,5 @@
1
1
  module DuckPuncher
2
+ # @note When updating this file please update comment regarding this module in duck_puncher.rb
2
3
  module Decoration
3
4
  def decorators
4
5
  @decorators ||= ancestral_hash
@@ -18,7 +19,7 @@ module DuckPuncher
18
19
 
19
20
  def cached_decorators
20
21
  @cached_decorators ||= Hash.new do |me, target|
21
- me[target] = DuckPuncher.decorators.select { |klass, _| klass >= target }.sort { |a, b| b[0] <=> a[0] }
22
+ me[target] = DuckPuncher.decorators.select { |klass, _| klass >= target }
22
23
  end
23
24
  end
24
25
 
@@ -0,0 +1,20 @@
1
+ DuckPuncher.logger = Logger.new(STDOUT).tap do |config|
2
+ config.formatter = proc { |*args| "[#{args[1]}] #{args[0]}: #{args[-1]}\n" }
3
+ config.level = Logger::ERROR
4
+ end
5
+
6
+ ducks = [
7
+ [String, DuckPuncher::Ducks::String],
8
+ [Enumerable, DuckPuncher::Ducks::Enumerable],
9
+ [Array, DuckPuncher::Ducks::Enumerable],
10
+ [Hash, DuckPuncher::Ducks::Enumerable],
11
+ [Numeric, DuckPuncher::Ducks::Numeric],
12
+ [Hash, DuckPuncher::Ducks::Hash],
13
+ [Object, DuckPuncher::Ducks::Object],
14
+ [Module, DuckPuncher::Ducks::Module],
15
+ [Method, DuckPuncher::Ducks::Method, { before: ->(_target) { DuckPuncher::GemInstaller.initialize! } }],
16
+ ]
17
+ ducks << ['ActiveRecord::Base', DuckPuncher::Ducks::ActiveRecord] if defined? ::ActiveRecord
18
+ ducks.each do |duck|
19
+ DuckPuncher.register *duck
20
+ end
@@ -2,7 +2,6 @@ module DuckPuncher
2
2
  class Duck
3
3
  attr_accessor :target, :mod, :options
4
4
 
5
- # @todo test punching a module
6
5
  # @param target [String,Class] Class or module to punch
7
6
  # @param mod [String,Module] The module that defines the extensions (@name is used by default)
8
7
  # @param [Hash] options to modify the duck #punch method behavior
@@ -20,14 +19,14 @@ module DuckPuncher
20
19
  # @option options [Symbol,String] :method Specifies if the methods should be included or prepended (:include)
21
20
  # @return [Class] The class that was just punched
22
21
  def punch(opts = {})
23
- target = opts.delete(:target) || self.target
24
- Array(target).each do |klass|
25
- options[:before].call(klass) if options[:before]
26
- klass.extend Usable
27
- klass.usable mod, only: opts[:only], method: opts[:method]
28
- options[:after].call(klass) if options[:after]
22
+ targets = Array(opts.delete(:target) || self.target)
23
+ targets.each do |target|
24
+ options[:before].call(target) if options[:before]
25
+ target.extend Usable
26
+ target.usable mod, only: opts[:only], method: opts[:method]
27
+ options[:after].call(target) if options[:after]
29
28
  end
30
- target
29
+ targets
31
30
  end
32
31
 
33
32
  #
@@ -1,6 +1,6 @@
1
1
  module DuckPuncher
2
2
  module Ducks
3
- module Array
3
+ module Enumerable
4
4
  def m(method_name)
5
5
  map(&method_name)
6
6
  end
@@ -20,6 +20,10 @@ module DuckPuncher
20
20
  def except(*args)
21
21
  self - args
22
22
  end
23
+
24
+ def map_keys(key)
25
+ map { |x| x[key] }
26
+ end
23
27
  end
24
28
  end
25
29
  end
@@ -34,7 +34,7 @@ module DuckPuncher
34
34
  begin
35
35
  require 'object_tracker' || raise(LoadError)
36
36
  rescue LoadError
37
- DuckPuncher.punch! Object, only: :require! unless respond_to? :require!
37
+ DuckPuncher.(Object, only: :require!) unless respond_to? :require!
38
38
  require! 'object_tracker'
39
39
  end
40
40
  extend ::ObjectTracker
@@ -1,7 +1,7 @@
1
1
  module DuckPuncher
2
2
  module Ducks
3
3
  autoload :String, 'duck_puncher/ducks/string'
4
- autoload :Array, 'duck_puncher/ducks/array'
4
+ autoload :Enumerable, 'duck_puncher/ducks/enumerable'
5
5
  autoload :Numeric, 'duck_puncher/ducks/numeric'
6
6
  autoload :Hash, 'duck_puncher/ducks/hash'
7
7
  autoload :Object, 'duck_puncher/ducks/object'
@@ -11,7 +11,7 @@ class DuckPuncher::GemInstaller
11
11
  begin
12
12
  require spec[:require_with]
13
13
  rescue LoadError => e
14
- DuckPuncher.log.error "Failed to load #{spec[:require_with]} from .duck_puncher/ #{e.inspect}"
14
+ DuckPuncher.logger.error "Failed to load #{spec[:require_with]} from .duck_puncher/ #{e.inspect}"
15
15
  end
16
16
  end
17
17
  end
@@ -29,6 +29,6 @@ class DuckPuncher::GemInstaller
29
29
  end
30
30
  installer.installed_gems.any?
31
31
  rescue => e
32
- DuckPuncher.log.error "Failed to install #{args.first}. #{e.inspect}"
32
+ DuckPuncher.logger.error "Failed to install #{args.first}. #{e.inspect}"
33
33
  end
34
34
  end
@@ -1,4 +1,5 @@
1
1
  module DuckPuncher
2
+ # @note When updating this file please update comment regarding this module in duck_puncher.rb
2
3
  module Registration
3
4
  def register(target, *mods)
4
5
  options = mods.last.is_a?(Hash) ? mods.pop : {}
@@ -0,0 +1,23 @@
1
+ module DuckPuncher
2
+ # @note When updating this file please update comment regarding this module in duck_puncher.rb
3
+ module Utilities
4
+ def lookup_constant(const)
5
+ return const if ::Module === const
6
+ if const.to_s.respond_to?(:constantize)
7
+ const.to_s.constantize
8
+ else
9
+ const.to_s.split('::').inject(::Object) { |k, part| k.const_get(part) }
10
+ end
11
+ rescue ::NameError => e
12
+ logger.error "#{e.class}: #{e.message}"
13
+ nil
14
+ end
15
+
16
+ def redefine_constant(name, const)
17
+ if const_defined? name
18
+ remove_const name
19
+ end
20
+ const_set name, const
21
+ end
22
+ end
23
+ end
@@ -1,3 +1,3 @@
1
1
  module DuckPuncher
2
- VERSION = '4.1.0'.freeze
2
+ VERSION = '4.2.0'.freeze
3
3
  end
data/lib/duck_puncher.rb CHANGED
@@ -1,4 +1,3 @@
1
- # Standard lib
2
1
  require 'pathname'
3
2
  require 'fileutils'
4
3
  require 'logger'
@@ -12,81 +11,51 @@ require 'usable'
12
11
  require 'duck_puncher/version'
13
12
  require 'duck_puncher/registration'
14
13
  require 'duck_puncher/decoration'
14
+ require 'duck_puncher/utilities'
15
+ require 'duck_puncher/ancestral_hash'
16
+ require 'duck_puncher/duck'
17
+ require 'duck_puncher/ducks'
15
18
 
16
19
  module DuckPuncher
17
- autoload :JSONStorage, 'duck_puncher/json_storage'
18
20
  autoload :GemInstaller, 'duck_puncher/gem_installer'
19
- autoload :Duck, 'duck_puncher/duck'
20
- autoload :Ducks, 'duck_puncher/ducks'
21
+ autoload :JSONStorage, 'duck_puncher/json_storage'
21
22
 
22
23
  class << self
23
- include Registration, Decoration
24
-
25
- attr_accessor :log
26
- alias_method :logger, :log
27
-
28
- def punch!(*classes)
29
- options = classes.last.is_a?(Hash) ? classes.pop : {}
24
+ # @description Include additional functionality
25
+ # Registration[:register, :deregister]
26
+ # Decoration[:decorators, :build_decorator_class, :decorate, :cached_decorators, :undecorate]
27
+ # Utilities[:lookup_constant, :redefine_constant]
28
+ # AncestralHash[:ancestral_hash]
29
+ include Registration, Decoration, Utilities, AncestralHash
30
+
31
+ attr_accessor :logger
32
+
33
+ # Backwards compatibility
34
+ alias_method :log, :logger
35
+ alias_method :log=, :logger=
36
+
37
+ def call(*args)
38
+ options = args.last.is_a?(Hash) ? args.pop : {}
39
+ classes = args.any? ? args : Ducks.list.keys
30
40
  classes.each do |klass|
31
41
  klass = lookup_constant(klass)
32
42
  Ducks[klass].sort.each do |duck|
33
- punches = options[:only] || Ducks::Module.instance_method(:local_methods).bind(duck.mod).call
34
- log.info %Q(#{duck.target}#{" <-- #{punches}" if Array(punches).any?})
43
+ punches = Array(options[:only] || Ducks::Module.instance_method(:local_methods).bind(duck.mod).call)
35
44
  options[:target] = klass
45
+ logger.info %Q(#{klass}#{" <-- #{duck.mod.name}#{punches}" if punches.any?})
36
46
  unless duck.punch(options)
37
- log.error %Q(Failed to punch #{name})
47
+ logger.error %Q(Failed to punch #{name})
38
48
  end
39
49
  end
40
50
  end
41
51
  nil
42
52
  end
43
53
 
44
- def punch_all!
45
- punch! *Ducks.list.keys
46
- end
47
-
48
- def lookup_constant(const)
49
- return const if Module === const
50
- if const.to_s.respond_to?(:constantize)
51
- const.to_s.constantize
52
- else
53
- const.to_s.split('::').inject(Object) { |k, part| k.const_get(part) }
54
- end
55
- rescue NameError => e
56
- log.error "#{e.class}: #{e.message}"
57
- nil
58
- end
59
-
60
- def redefine_constant(name, const)
61
- if const_defined? name
62
- remove_const name
63
- end
64
- const_set name, const
65
- end
66
-
67
- def ancestral_hash
68
- Hash.new { |me, klass| me[klass.superclass] if klass.respond_to?(:superclass) }
69
- end
70
- end
71
-
72
- self.log = Logger.new(STDOUT).tap do |config|
73
- config.level = Logger::INFO
74
- config.formatter = proc { |*args| "#{args.first}: #{args.last.to_s}\n" }
75
- end
76
-
77
- log.level = Logger::ERROR
78
-
79
- ducks = [
80
- [String, Ducks::String],
81
- [Array, Ducks::Array],
82
- [Numeric, Ducks::Numeric],
83
- [Hash, Ducks::Hash],
84
- [Object, Ducks::Object],
85
- [Module, Ducks::Module],
86
- [Method, Ducks::Method, { before: ->(*) { DuckPuncher::GemInstaller.initialize! } }],
87
- ]
88
- ducks << ['ActiveRecord::Base', Ducks::ActiveRecord] if defined? ::ActiveRecord
89
- ducks.each do |duck|
90
- register *duck
54
+ # Backwards compatibility
55
+ alias punch_all! call
56
+ alias punch! call
91
57
  end
92
58
  end
59
+
60
+ # Everyone likes defaults
61
+ require 'duck_puncher/defaults'
@@ -2,7 +2,7 @@ require_relative '../../test_helper'
2
2
 
3
3
  DuckPuncher.punch! Object
4
4
 
5
- class ArrayTest < MiniTest::Test
5
+ class EnumerableTest < MiniTest::Test
6
6
  attr_reader :subject
7
7
 
8
8
  def setup
@@ -36,4 +36,10 @@ class ArrayTest < MiniTest::Test
36
36
  assert_equal subject.except('a', 'b', 'c'), %w[d e f g h i j k l m]
37
37
  assert_equal subject.except, subject
38
38
  end
39
+
40
+ def test_map_keys
41
+ @subject = [{ id: 1, name: 'a' }, { id: 2, name: 'b' }, { name: 'c' }]
42
+ assert_respond_to @subject.punch, :map_keys
43
+ assert_equal %w[a b c], @subject.punch.map_keys(:name)
44
+ end
39
45
  end
@@ -6,7 +6,6 @@ class DuckPuncherTest < MiniTest::Test
6
6
  def setup
7
7
  @subject = Animal.new
8
8
  @kaia = Kaia.new
9
- DuckPuncher.deregister Animal, Kaia, Dog
10
9
  end
11
10
 
12
11
  def teardown
@@ -22,12 +21,22 @@ class DuckPuncherTest < MiniTest::Test
22
21
  assert_respond_to @kaia.punch, :talk
23
22
  end
24
23
 
25
-
26
24
  def test_punch_all!
27
- DuckPuncher.punch_all!
25
+ DuckPuncher.()
26
+ expected_methods = DuckPuncher::Ducks.list.values.m(:to_a).flatten.m(:mod).m(:local_methods).flatten
27
+ assert expected_methods.size > 1
28
+ good_ducks = DuckPuncher::Ducks.list.select { |_, ducks|
29
+ ducks.all? { |duck| (duck.mod.local_methods - duck.target.instance_methods(:false)).size.zero? }
30
+ }
31
+ assert good_ducks.size > 5
32
+ end
33
+
34
+ def test_call
35
+ DuckPuncher.()
28
36
  expected_methods = DuckPuncher::Ducks.list.values.m(:to_a).flatten.m(:mod).m(:local_methods).flatten
29
37
  assert expected_methods.size > 1
30
38
  good_ducks = DuckPuncher::Ducks.list.select { |_, ducks|
39
+ # Assert that all methods were copied over
31
40
  ducks.all? { |duck| (duck.mod.local_methods - duck.target.instance_methods(:false)).size.zero? }
32
41
  }
33
42
  assert good_ducks.size > 5
data/test/test_helper.rb CHANGED
@@ -5,7 +5,7 @@ require 'duck_puncher'
5
5
 
6
6
  Minitest::Reporters.use!
7
7
 
8
- DuckPuncher.log.level = Logger::INFO
8
+ DuckPuncher.logger.level = Logger::INFO
9
9
 
10
10
  module CustomPunch
11
11
  def talk
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: 4.1.0
4
+ version: 4.2.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-08-18 00:00:00.000000000 Z
11
+ date: 2016-08-24 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: usable
@@ -105,11 +105,13 @@ files:
105
105
  - bin/console
106
106
  - duck_puncher.gemspec
107
107
  - lib/duck_puncher.rb
108
+ - lib/duck_puncher/ancestral_hash.rb
108
109
  - lib/duck_puncher/decoration.rb
110
+ - lib/duck_puncher/defaults.rb
109
111
  - lib/duck_puncher/duck.rb
110
112
  - lib/duck_puncher/ducks.rb
111
113
  - lib/duck_puncher/ducks/active_record.rb
112
- - lib/duck_puncher/ducks/array.rb
114
+ - lib/duck_puncher/ducks/enumerable.rb
113
115
  - lib/duck_puncher/ducks/hash.rb
114
116
  - lib/duck_puncher/ducks/method.rb
115
117
  - lib/duck_puncher/ducks/module.rb
@@ -119,9 +121,10 @@ files:
119
121
  - lib/duck_puncher/gem_installer.rb
120
122
  - lib/duck_puncher/json_storage.rb
121
123
  - lib/duck_puncher/registration.rb
124
+ - lib/duck_puncher/utilities.rb
122
125
  - lib/duck_puncher/version.rb
123
126
  - test/fixtures/wut.rb
124
- - test/lib/duck_puncher/array_test.rb
127
+ - test/lib/duck_puncher/enumerable_test.rb
125
128
  - test/lib/duck_puncher/hash_test.rb
126
129
  - test/lib/duck_puncher/method_test.rb
127
130
  - test/lib/duck_puncher/module_test.rb
@@ -156,7 +159,7 @@ specification_version: 4
156
159
  summary: Administer precision extensions (a.k.a "punches") to your favorite Ruby classes
157
160
  test_files:
158
161
  - test/fixtures/wut.rb
159
- - test/lib/duck_puncher/array_test.rb
162
+ - test/lib/duck_puncher/enumerable_test.rb
160
163
  - test/lib/duck_puncher/hash_test.rb
161
164
  - test/lib/duck_puncher/method_test.rb
162
165
  - test/lib/duck_puncher/module_test.rb