dry-core 0.5.0 → 0.9.1

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
  SHA256:
3
- metadata.gz: 455db6bceb946acab5dbe431c4ba7edf9a7344f09c2b2af9e38c0b8788e3d600
4
- data.tar.gz: c460402f0b99fbe8ad488463cebd828a6a814d0a45f5200fbf1c90302fa66649
3
+ metadata.gz: 81d1266061bbae6eb6d4c08dba39d2787f29f033ec6b8ed8555830e170cae6fc
4
+ data.tar.gz: bb44a8177749013af0a3245e5e7e9c168818700c6d93b5dbbcf4d46d05e9e4fd
5
5
  SHA512:
6
- metadata.gz: c07482895edfb498acc414cc6672c2fa271cbd892f59393b4baa6b3b7ec4e115b29474be85fe42e40660fe06f926442608d693816594bdfcd910411185acad9c
7
- data.tar.gz: c3d0966d660f09a26007e226b57e1cc2c124a420606b4de5d104730c6f5d9c54492a89788fb601722eab10f42dac4cda5d6689c79fe6c53a3df9c6510569fa7e
6
+ metadata.gz: 48c5e7b008c7f1b3ec9d23801f020ee67f34199aec9e9690066cd7a4316e7d79e48c77056df180679e22767b367b4988c0f617948c49d931e5d62f3be54a1c4f
7
+ data.tar.gz: a2eb13da61361ef1d7c2ebb52390df5406f1598d1a9f64c2b11de9fc574d51f28920cc34342dfa3027bb9eb73b13802114b0d0aaafd51e0688cda7b66fd647bc
data/CHANGELOG.md CHANGED
@@ -1,6 +1,94 @@
1
1
  <!--- DO NOT EDIT THIS FILE - IT'S AUTOMATICALLY GENERATED VIA DEVTOOLS --->
2
2
 
3
- ## unreleased
3
+ ## 0.9.1 2022-10-18
4
+
5
+
6
+ ### Changed
7
+
8
+ - Correct missing constant for IDENTITY (issue #75 fixed via #76) (@poloka)
9
+
10
+ [Compare v0.9.0...v0.9.1](https://github.com/dry-rb/dry-core/compare/v0.9.0...v0.9.1)
11
+
12
+ ## 0.9.0 2022-10-15
13
+
14
+
15
+ ### Changed
16
+
17
+ - dry-core now uses zeitwerk for autoloading (@solnic)
18
+
19
+ [Compare v0.8.1...v0.9.0](https://github.com/dry-rb/dry-core/compare/v0.8.1...v0.9.0)
20
+
21
+ ## 0.8.1 2022-07-27
22
+
23
+
24
+ ### Fixed
25
+
26
+ - [memoizable] plays better with inheritance.
27
+ There were cases when cached values from base claesses were used, see #70 (@flash-gordon)
28
+
29
+
30
+
31
+ [Compare v0.8.0...v0.8.1](https://github.com/dry-rb/dry-core/compare/v0.8.0...v0.8.1)
32
+
33
+ ## 0.8.0 2022-07-15
34
+
35
+
36
+ ### Added
37
+
38
+ - `Dry::Core::BasicObject` ported from hanami-utils (@jodosha)
39
+
40
+ ### Changed
41
+
42
+ - [BREAKING] [descendants tracker] switch to using `Class#subclasses` on Ruby 3.1+.
43
+ This changes the order of returned subclasses (immediate subclasses now go first) (@flash-gordon)
44
+
45
+
46
+ [Compare v0.7.1...v0.8.0](https://github.com/dry-rb/dry-core/compare/v0.7.1...v0.8.0)
47
+
48
+ ## 0.7.1 2021-07-10
49
+
50
+
51
+ ### Fixed
52
+
53
+ - [memoizable] memoizable correctly handles cases where a method
54
+ has unnamed params (e.g. happens when the new `...` syntax is used) (@flash-gordon)
55
+
56
+
57
+
58
+ [Compare v0.7.0...v0.7.1](https://github.com/dry-rb/dry-core/compare/v0.7.0...v0.7.1)
59
+
60
+ ## 0.7.0 2021-07-08
61
+
62
+
63
+ ### Fixed
64
+
65
+ - [memoizable] warnings when using keyword arguments (@flash-gordon)
66
+ - [deprecations] warnings show more relevant information about caller by default (@timriley)
67
+
68
+ ### Changed
69
+
70
+ - Minimal Ruby version is 2.6
71
+ - [memoizable] memoization of block-accepting methods is deprecated (@flash-gordon)
72
+
73
+ [Compare v0.6.0...v0.7.0](https://github.com/dry-rb/dry-core/compare/v0.6.0...v0.7.0)
74
+
75
+ ## 0.6.0 2021-06-03
76
+
77
+
78
+ ### Added
79
+
80
+ - [memoizable] support for `BasicObject` (@oleander)
81
+ - [memoizable] support for methods that accept blocks (@oleander)
82
+ - [deprecations] allow printing frame info on warn when setting up Deprecation module (via #52) (@waiting-for-dev)
83
+
84
+ ### Fixed
85
+
86
+ - [memoizable] works with MRI 2.7+ keyword arguments now (@oleander)
87
+
88
+
89
+ [Compare v0.5.0...v0.6.0](https://github.com/dry-rb/dry-core/compare/v0.5.0...v0.6.0)
90
+
91
+ ## 0.5.0 2020-12-12
4
92
 
5
93
 
6
94
  ### Added
@@ -8,7 +96,7 @@
8
96
  - dry-equalizer has been imported into dry-core as `Dry::Core::Equalizer` but the interface remains the same, which is `include Dry.Equalizer(...)` - we'll be porting all other gems that depend on dry-equalizer to the latest dry-core with equalizer included *gradually*. Eventually dry-equalizer usage will be gone completely in rom-rb/dry-rb/hanami projects (@solnic)
9
97
 
10
98
 
11
- [Compare v0.4.10...master](https://github.com/dry-rb/dry-core/compare/v0.4.10...master)
99
+ [Compare v0.4.10...v0.5.0](https://github.com/dry-rb/dry-core/compare/v0.4.10...v0.5.0)
12
100
 
13
101
  ## 0.4.10 2020-11-19
14
102
 
data/LICENSE CHANGED
@@ -1,6 +1,6 @@
1
1
  The MIT License (MIT)
2
2
 
3
- Copyright (c) 2015-2020 dry-rb team
3
+ Copyright (c) 2015-2022 dry-rb team
4
4
 
5
5
  Permission is hereby granted, free of charge, to any person obtaining a copy of
6
6
  this software and associated documentation files (the "Software"), to deal in
data/README.md CHANGED
@@ -1,3 +1,4 @@
1
+ <!--- this file is synced from dry-rb/template-gem project -->
1
2
  [gem]: https://rubygems.org/gems/dry-core
2
3
  [actions]: https://github.com/dry-rb/dry-core/actions
3
4
  [codacy]: https://www.codacy.com/gh/dry-rb/dry-core
@@ -10,19 +11,19 @@
10
11
  [![CI Status](https://github.com/dry-rb/dry-core/workflows/ci/badge.svg)][actions]
11
12
  [![Codacy Badge](https://api.codacy.com/project/badge/Grade/40946292b9094624beec604a149a6023)][codacy]
12
13
  [![Codacy Badge](https://api.codacy.com/project/badge/Coverage/40946292b9094624beec604a149a6023)][codacy]
13
- [![Inline docs](http://inch-ci.org/github/dry-rb/dry-core.svg?branch=master)][inchpages]
14
+ [![Inline docs](http://inch-ci.org/github/dry-rb/dry-core.svg?branch=main)][inchpages]
14
15
 
15
16
  ## Links
16
17
 
17
- * [User documentation](http://dry-rb.org/gems/dry-core)
18
+ * [User documentation](https://dry-rb.org/gems/dry-core)
18
19
  * [API documentation](http://rubydoc.info/gems/dry-core)
19
20
 
20
21
  ## Supported Ruby versions
21
22
 
22
23
  This library officially supports the following Ruby versions:
23
24
 
24
- * MRI >= `2.5`
25
- * jruby >= `9.2`
25
+ * MRI `>= 2.7.0`
26
+ * jruby `>= 9.3` (postponed until 2.7 is supported)
26
27
 
27
28
  ## License
28
29
 
data/dry-core.gemspec CHANGED
@@ -1,34 +1,36 @@
1
1
  # frozen_string_literal: true
2
- # this file is managed by dry-rb/devtools project
3
2
 
4
- lib = File.expand_path('lib', __dir__)
3
+ # this file is synced from dry-rb/template-gem project
4
+
5
+ lib = File.expand_path("lib", __dir__)
5
6
  $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
6
- require 'dry/core/version'
7
+ require "dry/core/version"
7
8
 
8
9
  Gem::Specification.new do |spec|
9
- spec.name = 'dry-core'
10
+ spec.name = "dry-core"
10
11
  spec.authors = ["Nikita Shilnikov"]
11
12
  spec.email = ["fg@flashgordon.ru"]
12
- spec.license = 'MIT'
13
+ spec.license = "MIT"
13
14
  spec.version = Dry::Core::VERSION.dup
14
15
 
15
16
  spec.summary = "A toolset of small support modules used throughout the dry-rb ecosystem"
16
17
  spec.description = spec.summary
17
- spec.homepage = 'https://dry-rb.org/gems/dry-core'
18
+ spec.homepage = "https://dry-rb.org/gems/dry-core"
18
19
  spec.files = Dir["CHANGELOG.md", "LICENSE", "README.md", "dry-core.gemspec", "lib/**/*"]
19
- spec.bindir = 'bin'
20
+ spec.bindir = "bin"
20
21
  spec.executables = []
21
- spec.require_paths = ['lib']
22
+ spec.require_paths = ["lib"]
22
23
 
23
- spec.metadata['allowed_push_host'] = 'https://rubygems.org'
24
- spec.metadata['changelog_uri'] = 'https://github.com/dry-rb/dry-core/blob/master/CHANGELOG.md'
25
- spec.metadata['source_code_uri'] = 'https://github.com/dry-rb/dry-core'
26
- spec.metadata['bug_tracker_uri'] = 'https://github.com/dry-rb/dry-core/issues'
24
+ spec.metadata["allowed_push_host"] = "https://rubygems.org"
25
+ spec.metadata["changelog_uri"] = "https://github.com/dry-rb/dry-core/blob/main/CHANGELOG.md"
26
+ spec.metadata["source_code_uri"] = "https://github.com/dry-rb/dry-core"
27
+ spec.metadata["bug_tracker_uri"] = "https://github.com/dry-rb/dry-core/issues"
27
28
 
28
- spec.required_ruby_version = ">= 2.5.0"
29
+ spec.required_ruby_version = ">= 2.7.0"
29
30
 
30
31
  # to update dependencies edit project.yml
31
32
  spec.add_runtime_dependency "concurrent-ruby", "~> 1.0"
33
+ spec.add_runtime_dependency "zeitwerk", "~> 2.6"
32
34
 
33
35
  spec.add_development_dependency "bundler"
34
36
  spec.add_development_dependency "rake"
@@ -0,0 +1,144 @@
1
+ # frozen_string_literal: true
2
+
3
+ # This implementation was imported from `hanami-utils` gem.
4
+ module Dry
5
+ module Core
6
+ # BasicObject
7
+ #
8
+ # @since 0.8.0
9
+ class BasicObject < ::BasicObject
10
+ # Lookups constants at the top-level namespace, if they are missing in the
11
+ # current context.
12
+ #
13
+ # @param name [Symbol] the constant name
14
+ #
15
+ # @return [Object, Module] the constant
16
+ #
17
+ # @raise [NameError] if the constant cannot be found
18
+ #
19
+ # @since 0.8.0
20
+ # @api private
21
+ #
22
+ # @see https://ruby-doc.org/core/Module.html#method-i-const_missing
23
+ def self.const_missing(name)
24
+ ::Object.const_get(name)
25
+ end
26
+
27
+ # Returns the class for debugging purposes.
28
+ #
29
+ # @since 0.8.0
30
+ #
31
+ # @see http://ruby-doc.org/core/Object.html#method-i-class
32
+ def class
33
+ (class << self; self; end).superclass
34
+ end
35
+
36
+ # Bare minimum inspect for debugging purposes.
37
+ #
38
+ # @return [String] the inspect string
39
+ #
40
+ # @since 0.8.0
41
+ #
42
+ # @see http://ruby-doc.org/core/Object.html#method-i-inspect
43
+ inspect_method = ::Kernel.instance_method(:inspect)
44
+ define_method(:inspect) do
45
+ original = inspect_method.bind_call(self)
46
+ "#{original[0...-1]}#{__inspect}>"
47
+ end
48
+
49
+ # @!macro [attach] instance_of?(class)
50
+ #
51
+ # Determines if self is an instance of given class or module
52
+ #
53
+ # @param class [Class,Module] the class of module to verify
54
+ #
55
+ # @return [TrueClass,FalseClass] the result of the check
56
+ #
57
+ # @raise [TypeError] if the given argument is not of the expected types
58
+ #
59
+ # @since 0.8.0
60
+ #
61
+ # @see http://ruby-doc.org/core/Object.html#method-i-instance_of-3F
62
+ define_method :instance_of?, ::Object.instance_method(:instance_of?)
63
+
64
+ # @!macro [attach] is_a?(class)
65
+ #
66
+ # Determines if self is of the type of the object class or module
67
+ #
68
+ # @param class [Class,Module] the class of module to verify
69
+ #
70
+ # @return [TrueClass,FalseClass] the result of the check
71
+ #
72
+ # @raise [TypeError] if the given argument is not of the expected types
73
+ #
74
+ # @since 0.8.0
75
+ #
76
+ # @see http://ruby-doc.org/core/Object.html#method-i-is_a-3F
77
+ define_method :is_a?, ::Object.instance_method(:is_a?)
78
+
79
+ # @!macro [attach] kind_of?(class)
80
+ #
81
+ # Determines if self is of the kind of the object class or module
82
+ #
83
+ # @param class [Class,Module] the class of module to verify
84
+ #
85
+ # @return [TrueClass,FalseClass] the result of the check
86
+ #
87
+ # @raise [TypeError] if the given argument is not of the expected types
88
+ #
89
+ # @since 0.8.0
90
+ #
91
+ # @see http://ruby-doc.org/core/Object.html#method-i-kind_of-3F
92
+ define_method :kind_of?, ::Object.instance_method(:kind_of?)
93
+
94
+ # Alias for __id__
95
+ #
96
+ # @return [Fixnum] the object id
97
+ #
98
+ # @since 0.8.0
99
+ #
100
+ # @see http://ruby-doc.org/core/Object.html#method-i-object_id
101
+ def object_id
102
+ __id__
103
+ end
104
+
105
+ # Interface for pp
106
+ #
107
+ # @param printer [PP] the Pretty Printable printer
108
+ # @return [String] the pretty-printable inspection of the object
109
+ #
110
+ # @since 0.8.0
111
+ #
112
+ # @see https://ruby-doc.org/stdlib/libdoc/pp/rdoc/PP.html
113
+ def pretty_print(printer)
114
+ printer.text(inspect)
115
+ end
116
+
117
+ # Returns true if responds to the given method.
118
+ #
119
+ # @return [TrueClass,FalseClass] the result of the check
120
+ #
121
+ # @since 0.8.0
122
+ #
123
+ # @see http://ruby-doc.org/core/Object.html#method-i-respond_to-3F
124
+ def respond_to?(method_name, include_all = false) # rubocop:disable Style/OptionalBooleanParameter
125
+ respond_to_missing?(method_name, include_all)
126
+ end
127
+
128
+ private
129
+
130
+ # Must be overridden by descendants
131
+ #
132
+ # @since 0.8.0
133
+ # @api private
134
+ def respond_to_missing?(_method_name, _include_all)
135
+ ::Kernel.raise ::NotImplementedError
136
+ end
137
+
138
+ # @since 0.8.0
139
+ # @api private
140
+ def __inspect # rubocop:disable Style/EmptyMethod
141
+ end
142
+ end
143
+ end
144
+ end
@@ -46,7 +46,8 @@ module Dry
46
46
  # this means you shouldn't pass Procs in args unless you're sure
47
47
  # they are always the same instances, otherwise you introduce a memory leak
48
48
  #
49
- # @return [Object] block's return value (cached for subsequent calls with the same argument values)
49
+ # @return [Object] block's return value (cached for subsequent calls with
50
+ # the same argument values)
50
51
  def fetch_or_store(*args, &block)
51
52
  cache.fetch_or_store(args.hash, &block)
52
53
  end
@@ -59,8 +60,8 @@ module Dry
59
60
  # @yield An arbitrary block
60
61
  #
61
62
  # @return [Object] block's return value
62
- def fetch_or_store(*args, &block)
63
- self.class.fetch_or_store(*args, &block)
63
+ def fetch_or_store(...)
64
+ self.class.fetch_or_store(...)
64
65
  end
65
66
  end
66
67
  end
@@ -1,7 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "dry/core/constants"
4
- require "dry/core/errors"
5
4
 
6
5
  module Dry
7
6
  module Core
@@ -10,7 +9,6 @@ module Dry
10
9
  # @api public
11
10
  module ClassAttributes
12
11
  include Constants
13
-
14
12
  # Specify what attributes a class will use
15
13
  #
16
14
  # @example
@@ -67,7 +65,7 @@ module Dry
67
65
  # defines :one, coerce: Dry::Types['coercible.string']
68
66
  # end
69
67
  #
70
- def defines(*args, type: ::Object, coerce: IDENTITY)
68
+ def defines(*args, type: ::Object, coerce: IDENTITY) # rubocop:disable Metrics/PerceivedComplexity
71
69
  unless coerce.respond_to?(:call)
72
70
  raise ::ArgumentError, "Non-callable coerce option: #{coerce.inspect}"
73
71
  end
@@ -83,10 +81,10 @@ module Dry
83
81
  else
84
82
  nil
85
83
  end
86
- elsif type === value
84
+ elsif type === value # rubocop:disable Style/CaseEquality
87
85
  instance_variable_set(ivar, coerce.call(value))
88
86
  else
89
- raise InvalidClassAttributeValue.new(name, value)
87
+ raise InvalidClassAttributeValueError.new(name, value)
90
88
  end
91
89
  end
92
90
  end
@@ -6,9 +6,7 @@ module Dry
6
6
  class ClassBuilder
7
7
  ParentClassMismatch = Class.new(TypeError)
8
8
 
9
- attr_reader :name
10
- attr_reader :parent
11
- attr_reader :namespace
9
+ attr_reader :name, :parent, :namespace
12
10
 
13
11
  def initialize(name:, parent: nil, namespace: nil)
14
12
  @name = name
@@ -83,7 +81,7 @@ module Dry
83
81
  def create_base(namespace, name, parent)
84
82
  begin
85
83
  namespace.const_get(name)
86
- rescue NameError
84
+ rescue NameError # rubocop:disable Lint/SuppressedException
87
85
  end
88
86
 
89
87
  if namespace.const_defined?(name, false)
@@ -25,7 +25,7 @@ module Dry
25
25
  # An empty set
26
26
  EMPTY_SET = ::Set.new.freeze
27
27
  # An empty string
28
- EMPTY_STRING = "".freeze
28
+ EMPTY_STRING = ""
29
29
  # Identity function
30
30
  IDENTITY = (-> x { x }).freeze
31
31
 
@@ -42,7 +42,7 @@ module Dry
42
42
  # end
43
43
  Undefined = Object.new.tap do |undefined|
44
44
  # @api private
45
- Self = -> { Undefined }
45
+ Self = -> { Undefined } # rubocop:disable Lint/ConstantDefinitionInBlock
46
46
 
47
47
  # @api public
48
48
  def undefined.to_s
@@ -4,7 +4,7 @@ require "logger"
4
4
 
5
5
  module Dry
6
6
  module Core
7
- # An extension for issueing warnings on using deprecated methods.
7
+ # An extension for issuing warnings on using deprecated methods.
8
8
  #
9
9
  # @example
10
10
  #
@@ -33,17 +33,27 @@ module Dry
33
33
  # Prints a warning
34
34
  #
35
35
  # @param [String] msg Warning string
36
- def warn(msg, tag: nil)
37
- tagged = "[#{tag || 'deprecated'}] #{msg.gsub(/^\s+/, '')}"
38
- logger.warn(tagged)
36
+ # @param [String] tag Tag to help identify the source of the warning.
37
+ # Defaults to "deprecated"
38
+ # @param [Integer] Caller frame to add to the message
39
+ def warn(msg, tag: nil, uplevel: nil)
40
+ caller_info = uplevel.nil? ? nil : "#{caller_locations(uplevel + 2, 1)[0]} "
41
+ tag = "[#{tag || "deprecated"}] "
42
+ hint = msg.gsub(/^\s+/, "")
43
+
44
+ logger.warn("#{caller_info}#{tag}#{hint}")
39
45
  end
40
46
 
41
47
  # Wraps arguments with a standard message format and prints a warning
42
48
  #
43
49
  # @param [Object] name what is deprecated
44
50
  # @param [String] msg additional message usually containing upgrade instructions
45
- def announce(name, msg, tag: nil)
46
- warn(deprecation_message(name, msg), tag: tag)
51
+ def announce(name, msg, tag: nil, uplevel: nil)
52
+ # Bump the uplevel (if provided) by one to account for the uplevel calculation
53
+ # taking place one frame deeper in `.warn`
54
+ uplevel += 1 if uplevel
55
+
56
+ warn(deprecation_message(name, msg), tag: tag, uplevel: uplevel)
47
57
  end
48
58
 
49
59
  # @api private
@@ -92,7 +102,7 @@ module Dry
92
102
  # @param [#warn] logger
93
103
  #
94
104
  # @api public
95
- def set_logger!(output = $stderr)
105
+ def set_logger!(output = $stderr) # rubocop:disable Naming/AccessorMethodName
96
106
  if output.respond_to?(:warn)
97
107
  @logger = output
98
108
  else
@@ -108,8 +118,9 @@ module Dry
108
118
  end
109
119
 
110
120
  # @api private
111
- class Tagged < Module
121
+ class Tagged < ::Module
112
122
  def initialize(tag)
123
+ super()
113
124
  @tag = tag
114
125
  end
115
126
 
@@ -145,8 +156,8 @@ module Dry
145
156
  # @option [String] message optional deprecation message
146
157
  def deprecate(old_name, new_name = nil, message: nil)
147
158
  full_msg = Deprecations.deprecated_name_message(
148
- "#{self.name}##{old_name}",
149
- new_name ? "#{self.name}##{new_name}" : nil,
159
+ "#{name}##{old_name}",
160
+ new_name ? "#{name}##{new_name}" : nil,
150
161
  message
151
162
  )
152
163
  mod = self
@@ -178,8 +189,8 @@ module Dry
178
189
  # @option [String] message optional deprecation message
179
190
  def deprecate_class_method(old_name, new_name = nil, message: nil)
180
191
  full_msg = Deprecations.deprecated_name_message(
181
- "#{self.name}.#{old_name}",
182
- new_name ? "#{self.name}.#{new_name}" : nil,
192
+ "#{name}.#{old_name}",
193
+ new_name ? "#{name}.#{new_name}" : nil,
183
194
  message
184
195
  )
185
196
 
@@ -203,7 +214,7 @@ module Dry
203
214
  remove_const(constant_name)
204
215
 
205
216
  full_msg = Deprecations.deprecated_name_message(
206
- "#{self.name}::#{constant_name}",
217
+ "#{name}::#{constant_name}",
207
218
  message
208
219
  )
209
220
 
@@ -1,20 +1,9 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Dry
4
- # Build an equalizer module for the inclusion in other class
5
- #
6
- # ## Credits
7
- #
8
- # Equalizer has been originally imported from the equalizer gem created by Dan Kubb
9
- #
10
- # @api public
11
- def self.Equalizer(*keys, **options)
12
- Dry::Core::Equalizer.new(*keys, **options)
13
- end
14
-
15
4
  module Core
16
5
  # Define equality, equivalence and inspection methods
17
- class Equalizer < Module
6
+ class Equalizer < ::Module
18
7
  # Initialize an Equalizer with the given keys
19
8
  #
20
9
  # Will use the keys with which it is initialized to define #cmp?,
@@ -29,6 +18,7 @@ module Dry
29
18
  #
30
19
  # @api private
31
20
  def initialize(*keys, **options)
21
+ super()
32
22
  @keys = keys.uniq
33
23
  define_methods(**options)
34
24
  freeze
@@ -148,4 +138,19 @@ module Dry
148
138
  end
149
139
  end
150
140
  end
141
+
142
+ # Old modules that depend on dry/core/equalizer may miss
143
+ # this method if dry/core is not required explicitly
144
+ unless singleton_class.method_defined?(:Equalizer)
145
+ # Build an equalizer module for the inclusion in other class
146
+ #
147
+ # ## Credits
148
+ #
149
+ # Equalizer has been originally imported from the equalizer gem created by Dan Kubb
150
+ #
151
+ # @api public
152
+ def self.Equalizer(*keys, **options)
153
+ Dry::Core::Equalizer.new(*keys, **options)
154
+ end
155
+ end
151
156
  end
@@ -2,7 +2,7 @@
2
2
 
3
3
  module Dry
4
4
  module Core
5
- class InvalidClassAttributeValue < StandardError
5
+ class InvalidClassAttributeValueError < StandardError
6
6
  def initialize(name, value)
7
7
  super(
8
8
  "Value #{value.inspect} is invalid for class attribute #{name.inspect}"
@@ -4,64 +4,240 @@ module Dry
4
4
  module Core
5
5
  module Memoizable
6
6
  MEMOIZED_HASH = {}.freeze
7
+ PARAM_PLACEHOLDERS = %i[* ** &].freeze
7
8
 
8
9
  module ClassInterface
9
- def memoize(*names)
10
- prepend(Memoizer.new(self, names))
10
+ module Base
11
+ def memoize(*names)
12
+ prepend(Memoizer.new(self, names))
13
+ end
14
+
15
+ def inherited(base)
16
+ super
17
+
18
+ memoizer = base.ancestors.find { _1.is_a?(Memoizer) }
19
+ base.prepend(memoizer.dup)
20
+ end
11
21
  end
12
22
 
13
- def new(*)
14
- obj = super
15
- obj.instance_variable_set(:'@__memoized__', MEMOIZED_HASH.dup)
16
- obj
23
+ module BasicObject
24
+ include Base
25
+
26
+ def new(*)
27
+ obj = super
28
+ obj.instance_eval { @__memoized__ = MEMOIZED_HASH.dup }
29
+ obj
30
+ end
31
+ end
32
+
33
+ module Object
34
+ include Base
35
+
36
+ def new(*)
37
+ obj = super
38
+ obj.instance_variable_set(:@__memoized__, MEMOIZED_HASH.dup)
39
+ obj
40
+ end
41
+
42
+ if respond_to?(:ruby2_keywords, true)
43
+ ruby2_keywords(:new)
44
+ end
17
45
  end
18
46
  end
19
47
 
20
48
  def self.included(klass)
21
49
  super
22
- klass.extend(ClassInterface)
23
- end
24
50
 
25
- attr_reader :__memoized__
51
+ if klass <= Object
52
+ klass.extend(ClassInterface::Object)
53
+ else
54
+ klass.extend(ClassInterface::BasicObject)
55
+ end
56
+ end
26
57
 
27
58
  # @api private
28
- class Memoizer < Module
29
- attr_reader :klass
30
- attr_reader :names
59
+ class Memoizer < ::Module
60
+ KERNEL = {
61
+ singleton: ::Kernel.instance_method(:singleton_class),
62
+ ivar_set: ::Kernel.instance_method(:instance_variable_set),
63
+ frozen: ::Kernel.instance_method(:frozen?)
64
+ }.freeze
31
65
 
32
66
  # @api private
33
67
  def initialize(klass, names)
34
- @names = names
35
- @klass = klass
36
- define_memoizable_names!
68
+ super()
69
+ names.each do |name|
70
+ define_memoizable(
71
+ method: klass.instance_method(name)
72
+ )
73
+ end
37
74
  end
38
75
 
39
76
  private
40
77
 
41
78
  # @api private
42
- def define_memoizable_names!
43
- names.each do |name|
44
- meth = klass.instance_method(name)
79
+ # rubocop:disable Metrics/AbcSize
80
+ # rubocop:disable Metrics/PerceivedComplexity
81
+ def define_memoizable(method:)
82
+ parameters = method.parameters
83
+ mod = self
84
+ kernel = KERNEL
85
+
86
+ if parameters.empty?
87
+ key = "#{object_id}:#{method.name}".hash.abs
45
88
 
46
- if meth.parameters.size > 0
47
- define_method(name) do |*args|
48
- name_with_args = :"#{name}_#{args.hash}"
89
+ define_method(method.name) do
90
+ value = super()
49
91
 
50
- if __memoized__.key?(name_with_args)
51
- __memoized__[name_with_args]
52
- else
53
- __memoized__[name_with_args] = super(*args)
54
- end
92
+ if kernel[:frozen].bind_call(self)
93
+ # It's not possible to modify singleton classes
94
+ # of frozen objects
95
+ mod.remove_method(method.name)
96
+ mod.module_eval(<<~RUBY, __FILE__, __LINE__ + 1)
97
+ def #{method.name} # def slow_calc
98
+ cached = @__memoized__[#{key}] # cached = @__memoized__[12345678]
99
+ #
100
+ if cached || @__memoized__.key?(#{key}) # if cached || @__memoized__.key?(12345678)
101
+ cached # cached
102
+ else # else
103
+ @__memoized__[#{key}] = super # @__memoized__[12345678] = super
104
+ end # end
105
+ end # end
106
+ RUBY
107
+ else
108
+ # We make an attr_reader for computed value.
109
+ # Readers are "special-cased" in ruby so such
110
+ # access will be the fastest way, faster than you'd
111
+ # expect :)
112
+ attr_name = :"__memozed_#{key}__"
113
+ ivar_name = :"@#{attr_name}"
114
+ kernel[:ivar_set].bind_call(self, ivar_name, value)
115
+ eigenclass = kernel[:singleton].bind_call(self)
116
+ eigenclass.attr_reader(attr_name)
117
+ eigenclass.alias_method(method.name, attr_name)
118
+ eigenclass.remove_method(attr_name)
55
119
  end
120
+
121
+ value
122
+ end
123
+ else
124
+ mapping = parameters.to_h { |k, v = nil| [k, v] }
125
+ params, binds = declaration(parameters, mapping)
126
+ last_param = parameters.last
127
+
128
+ if last_param[0].eql?(:block) && !last_param[1].eql?(:&)
129
+ Deprecations.warn(<<~WARN)
130
+ Memoization for block-accepting methods isn't safe.
131
+ Every call creates a new block instance bloating cached results.
132
+ In the future, blocks will still be allowed but won't participate in
133
+ cache key calculation.
134
+ WARN
135
+ end
136
+
137
+ m = module_eval(<<~RUBY, __FILE__, __LINE__ + 1)
138
+ def #{method.name}(#{params.join(", ")}) # def slow_calc(arg1, arg2, arg3)
139
+ key = [:"#{method.name}", #{binds.join(", ")}].hash # key = [:slow_calc, arg1, arg2, arg3].hash
140
+ #
141
+ if @__memoized__.key?(key) # if @__memoized__.key?(key)
142
+ @__memoized__[key] # @__memoized__[key]
143
+ else # else
144
+ @__memoized__[key] = super # @__memoized__[key] = super
145
+ end # end
146
+ end # end
147
+ RUBY
148
+
149
+ if respond_to?(:ruby2_keywords, true) && mapping.key?(:reyrest)
150
+ ruby2_keywords(method.name)
151
+ end
152
+
153
+ m
154
+ end
155
+ end
156
+ # rubocop:enable Metrics/AbcSize
157
+ # rubocop:enable Metrics/PerceivedComplexity
158
+
159
+ # @api private
160
+ def declaration(definition, lookup)
161
+ params = []
162
+ binds = []
163
+ defined = {}
164
+
165
+ definition.each do |type, name|
166
+ mapped_type = map_bind_type(type, name, lookup, defined) do
167
+ raise ::NotImplementedError, "type: #{type}, name: #{name}"
168
+ end
169
+
170
+ if mapped_type
171
+ defined[mapped_type] = true
172
+ bind = name_from_param(name) || make_bind_name(binds.size)
173
+
174
+ binds << bind
175
+ params << param(bind, mapped_type)
176
+ end
177
+ end
178
+
179
+ [params, binds]
180
+ end
181
+
182
+ # @api private
183
+ def name_from_param(name)
184
+ if PARAM_PLACEHOLDERS.include?(name)
185
+ nil
186
+ else
187
+ name
188
+ end
189
+ end
190
+
191
+ # @api private
192
+ def make_bind_name(idx)
193
+ :"__lv_#{idx}__"
194
+ end
195
+
196
+ # @api private
197
+ def map_bind_type(type, name, original_params, defined_types) # rubocop:disable Metrics/PerceivedComplexity
198
+ case type
199
+ when :req
200
+ :reqular
201
+ when :rest, :keyreq, :keyrest
202
+ type
203
+ when :block
204
+ if name.eql?(:&)
205
+ # most likely this is a case of delegation
206
+ # rather than actual block
207
+ nil
56
208
  else
57
- define_method(name) do
58
- if __memoized__.key?(name)
59
- __memoized__[name]
60
- else
61
- __memoized__[name] = super()
62
- end
63
- end
209
+ type
210
+ end
211
+ when :opt
212
+ if original_params.key?(:rest) || defined_types[:rest]
213
+ nil
214
+ else
215
+ :rest
216
+ end
217
+ when :key
218
+ if original_params.key?(:keyrest) || defined_types[:keyrest]
219
+ nil
220
+ else
221
+ :keyrest
64
222
  end
223
+ else
224
+ yield
225
+ end
226
+ end
227
+
228
+ # @api private
229
+ def param(name, type)
230
+ case type
231
+ when :reqular
232
+ name
233
+ when :rest
234
+ "*#{name}"
235
+ when :keyreq
236
+ "#{name}:"
237
+ when :keyrest
238
+ "**#{name}"
239
+ when :block
240
+ "&#{name}"
65
241
  end
66
242
  end
67
243
  end
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Dry
4
4
  module Core
5
- VERSION = "0.5.0".freeze
5
+ VERSION = "0.9.1"
6
6
  end
7
7
  end
data/lib/dry/core.rb CHANGED
@@ -1,10 +1,44 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "zeitwerk"
4
+
5
+ require "dry/core/constants"
6
+ require "dry/core/errors"
3
7
  require "dry/core/version"
4
8
 
5
9
  # :nodoc:
6
10
  module Dry
7
11
  # :nodoc:
8
12
  module Core
13
+ include Constants
14
+
15
+ def self.loader
16
+ @loader ||= Zeitwerk::Loader.new.tap do |loader|
17
+ root = File.expand_path("..", __dir__)
18
+ loader.tag = "dry-core"
19
+ loader.inflector = Zeitwerk::GemInflector.new("#{root}/dry-core.rb")
20
+ loader.push_dir(root)
21
+ loader.ignore(
22
+ "#{root}/dry-core.rb",
23
+ "#{root}/dry/core/{constants,errors,version}.rb"
24
+ )
25
+ end
26
+ end
27
+
28
+ loader.setup
29
+ end
30
+
31
+ # See dry/core/equalizer.rb
32
+ unless singleton_class.method_defined?(:Equalizer)
33
+ # Build an equalizer module for the inclusion in other class
34
+ #
35
+ # ## Credits
36
+ #
37
+ # Equalizer has been originally imported from the equalizer gem created by Dan Kubb
38
+ #
39
+ # @api public
40
+ def self.Equalizer(*keys, **options)
41
+ Dry::Core::Equalizer.new(*keys, **options)
42
+ end
9
43
  end
10
44
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: dry-core
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.5.0
4
+ version: 0.9.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Nikita Shilnikov
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2020-12-12 00:00:00.000000000 Z
11
+ date: 2022-10-18 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: concurrent-ruby
@@ -24,6 +24,20 @@ dependencies:
24
24
  - - "~>"
25
25
  - !ruby/object:Gem::Version
26
26
  version: '1.0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: zeitwerk
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '2.6'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '2.6'
27
41
  - !ruby/object:Gem::Dependency
28
42
  name: bundler
29
43
  requirement: !ruby/object:Gem::Requirement
@@ -79,6 +93,7 @@ files:
79
93
  - dry-core.gemspec
80
94
  - lib/dry-core.rb
81
95
  - lib/dry/core.rb
96
+ - lib/dry/core/basic_object.rb
82
97
  - lib/dry/core/cache.rb
83
98
  - lib/dry/core/class_attributes.rb
84
99
  - lib/dry/core/class_builder.rb
@@ -96,7 +111,7 @@ licenses:
96
111
  - MIT
97
112
  metadata:
98
113
  allowed_push_host: https://rubygems.org
99
- changelog_uri: https://github.com/dry-rb/dry-core/blob/master/CHANGELOG.md
114
+ changelog_uri: https://github.com/dry-rb/dry-core/blob/main/CHANGELOG.md
100
115
  source_code_uri: https://github.com/dry-rb/dry-core
101
116
  bug_tracker_uri: https://github.com/dry-rb/dry-core/issues
102
117
  post_install_message:
@@ -107,14 +122,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
107
122
  requirements:
108
123
  - - ">="
109
124
  - !ruby/object:Gem::Version
110
- version: 2.5.0
125
+ version: 2.7.0
111
126
  required_rubygems_version: !ruby/object:Gem::Requirement
112
127
  requirements:
113
128
  - - ">="
114
129
  - !ruby/object:Gem::Version
115
130
  version: '0'
116
131
  requirements: []
117
- rubygems_version: 3.1.4
132
+ rubygems_version: 3.1.6
118
133
  signing_key:
119
134
  specification_version: 4
120
135
  summary: A toolset of small support modules used throughout the dry-rb ecosystem