duck_puncher 4.1.0 → 4.2.0

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