core-extension 0.0.0 → 0.4.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
  SHA256:
3
- metadata.gz: 8a71a7e994976d0d1541932017b638a74dc3403686c8c49f517e3c019d7ad656
4
- data.tar.gz: 6edfca3b01dbaa1c52df86e9a1cd024395772abf1438e3d6b0845785972a70b4
3
+ metadata.gz: 36cc50171a9abdf4bcc66e06d50de8b882ca63c4e0abcde6e8a0a3c788e18d1b
4
+ data.tar.gz: 1ba5eb71c9c127531de40821d2b6f3edaf4e9f3a3324769cdc0053eb2f3c5c6c
5
5
  SHA512:
6
- metadata.gz: 13c7cfc9dce149e23b940830cce4e9e45d8e8b03cfd43e56030638267514f614271d4ba9979a58800df6dec8a70a33a92bdb46cd783513b3f0c8f4df9174cd51
7
- data.tar.gz: '04704682e6f059cd22b6f4c7f68590ed59b2c4a0be43bbc003cac5f77fd27ee54a481a1251f51c1004e3c1c10078ec0bb47b9e5891e079d9d432b5d4b81269af'
6
+ metadata.gz: 60133541a193b46b53e2e727219f4c23ba778bca20e0a7c3d2c3b650b3eca8c4eeaa27510b9b3fbb2e55260543002d63a220b0597308706752eed1994eaa61b6
7
+ data.tar.gz: f64b2074f69a38d694a7de22e6ea42c3cae2cc454f31b5146146031f4b43fcc3626e247c182acbb80b2e7c4bc7fa503eafd9b6928a5231783200d7bedb0fe810
data/CHANGELOG.md CHANGED
@@ -1,3 +1,31 @@
1
+ ## [v0.4.0](https://github.com/metabahn/corerb/releases/tag/2021-10-24)
2
+
3
+ *released on 2021-10-24*
4
+
5
+ * `add` [#86](https://github.com/metabahn/corerb/pull/86) Copy instance variables when cloning an extension ([bryanp](https://github.com/bryanp))
6
+
7
+ ## [v0.3.0](https://github.com/metabahn/corerb/releases/tag/2021-07-15)
8
+
9
+ *released on 2021-07-15*
10
+
11
+ * `chg` [#72](https://github.com/metabahn/corerb/pull/72) Rename extension dependency flags ([bryanp](https://github.com/bryanp))
12
+ * `chg` [#71](https://github.com/metabahn/corerb/pull/71) Rename Is::Extension::applied to applies ([bryanp](https://github.com/bryanp))
13
+ * `chg` [#68](https://github.com/metabahn/corerb/pull/68) Pass the extension scope to apply blocks ([bryanp](https://github.com/bryanp))
14
+ * `chg` [#67](https://github.com/metabahn/corerb/pull/67) Refactor extend support for extensions ([bryanp](https://github.com/bryanp))
15
+ * `chg` [#66](https://github.com/metabahn/corerb/pull/66) Support extending classes with extensions ([bryanp](https://github.com/bryanp))
16
+
17
+ ## [v0.2.0](https://github.com/metabahn/corerb/releases/tag/2021-07-07)
18
+
19
+ *released on 2021-07-07*
20
+
21
+ * `chg` [#40](https://github.com/metabahn/corerb/pull/40) Drop Ruby 2.6 support from core-extension ([bryanp](https://github.com/bryanp))
22
+
23
+ ## [v0.1.0](https://github.com/metabahn/corerb/releases/tag/2021-02-10)
24
+
25
+ *released on 2021-02-10*
26
+
27
+ * `chg` [#3](https://github.com/metabahn/corerb/pull/3) Apply extension dependencies before behavior ([bryanp](https://github.com/bryanp))
28
+
1
29
  ## [v0.0.0](https://github.com/metabahn/corerb/releases/tag/2020-12-29)
2
30
 
3
31
  *released on 2020-12-29*
@@ -2,7 +2,7 @@
2
2
 
3
3
  module Core
4
4
  module Extension
5
- # [public] Defines behavior that can be applied to objects. Behavior will only be applied to each object once.
5
+ # [public] Defines behavior that can be applied to objects. Behavior will only be applied to each object once by default.
6
6
  #
7
7
  # force - If true, behavior will be applied even if it has already been applied to the including object.
8
8
  #
@@ -12,9 +12,17 @@ module Core
12
12
  @module = build_module(&block)
13
13
  end
14
14
 
15
- # [public] Applies this behavior to an object.
15
+ # [public] Applies this behavior to an object via extend.
16
16
  #
17
- def apply(object)
17
+ def apply_extend(object)
18
+ if force? || unapplied?(object)
19
+ object.extend(@module)
20
+ end
21
+ end
22
+
23
+ # [public] Applies this behavior to an object via include.
24
+ #
25
+ def apply_include(object)
18
26
  if force? || unapplied?(object)
19
27
  object.include(@module)
20
28
  end
@@ -34,11 +42,35 @@ module Core
34
42
 
35
43
  private def build_module(&block)
36
44
  Module.new {
45
+ define_singleton_method(:extended) do |base|
46
+ arguments = {
47
+ extended: true,
48
+ included: false
49
+ }
50
+
51
+ base.class_exec(**Behavior.conditional_keyword_arguments(arguments, block), &block)
52
+ end
53
+
37
54
  define_singleton_method(:included) do |base|
38
- base.class_eval(&block)
55
+ arguments = {
56
+ extended: false,
57
+ included: true
58
+ }
59
+
60
+ base.class_exec(**Behavior.conditional_keyword_arguments(arguments, block), &block)
39
61
  end
40
62
  }
41
63
  end
64
+
65
+ class << self
66
+ def conditional_keyword_arguments(arguments, callable)
67
+ arguments.keep_if { |key, _value|
68
+ callable.parameters.any? { |type, name|
69
+ (type == :key || type == :keyreq || type == :keyrest) && name == key
70
+ }
71
+ }
72
+ end
73
+ end
42
74
  end
43
75
  end
44
76
  end
@@ -6,69 +6,71 @@ module Core
6
6
  #
7
7
  # flags - Changes how the dependencies are applied. Possible values include:
8
8
  #
9
- # * `:class` - Extends the including object, making methods available at the class level.
9
+ # * `:definition` - Extends the definition of the including object.
10
10
  #
11
- # * `:instance` - Includes the methods into the object, making them available at the instance level.
11
+ # * `:implementation` - Extends the implementation of the including object.
12
12
  #
13
- # Dependencies are applied with the `:instance` flag by default.
13
+ # Dependencies are applied with the `:implementation` flag by default.
14
14
  #
15
- # modules - Modules to be applied to objects, following the rules defined by flags.
15
+ # dependency - The dependency to be applied to objects, following the rules defined by flags.
16
16
  #
17
- # prepend - If `true`, methods will be prepended rather than appended.
18
- #
19
- # block - If passed, defines an anonymous module as a dependency.
17
+ # prepend - If `true`, methods will be prepended.
20
18
  #
21
19
  class Dependency
22
- ALLOWED_FLAGS = %i[class instance].freeze
20
+ ALLOWED_FLAGS = %i[definition implementation].freeze
21
+ ALLOWED_FLAGS_STRING = ALLOWED_FLAGS.map { |allowed_flag|
22
+ "`#{allowed_flag.inspect}'"
23
+ }.join(", ").freeze
23
24
 
24
- def initialize(*flags, modules: [], prepend: false, &block)
25
+ def initialize(*flags, dependency:, prepend: false)
26
+ flags = flags.map(&:to_sym)
25
27
  enforce_allowed_flags(flags)
26
28
 
27
- @flags = flags.map(&:to_sym)
28
- @modules = modules.to_a
29
+ @flags = flags
30
+ @dependency = dependency
29
31
  @prepend = prepend
30
-
31
- @modules << build_module(&block) if block
32
+ @definition = @flags.include?(:definition)
33
+ @implementation = @flags.include?(:implementation) || (!definition? && !prepend?)
32
34
  end
33
35
 
34
- # [public] Apply the defined dependencies to an object.
36
+ # [public] Apply the defined dependencies to an object via extend.
35
37
  #
36
- def apply(object)
37
- including = include?
38
- extending = extend?
39
- prepending = prepend?
38
+ def apply_extend(object)
39
+ if prepend?
40
+ object.singleton_class.prepend(@dependency) if implementation? || definition?
41
+ elsif definition? || implementation?
42
+ object.extend(@dependency)
43
+ end
44
+ end
40
45
 
41
- @modules.each do |each_module|
42
- if prepending
43
- object.prepend(each_module) if including
44
- object.singleton_class.prepend(each_module) if extending
45
- else
46
- object.include(each_module) if including
47
- object.extend(each_module) if extending
48
- end
46
+ # [public] Apply the defined dependencies to an object via include.
47
+ #
48
+ def apply_include(object)
49
+ if prepend?
50
+ object.prepend(@dependency) if implementation?
51
+ object.singleton_class.prepend(@dependency) if definition?
52
+ else
53
+ object.include(@dependency) if implementation?
54
+ object.extend(@dependency) if definition?
49
55
  end
50
56
  end
51
57
 
52
- private def include?
53
- @flags.include?(:instance) || (!extend? && !prepend?)
58
+ private def implementation?
59
+ @implementation == true
54
60
  end
55
61
 
56
- private def extend?
57
- @flags.include?(:class)
62
+ private def definition?
63
+ @definition == true
58
64
  end
59
65
 
60
66
  private def prepend?
61
67
  @prepend == true
62
68
  end
63
69
 
64
- private def build_module(&block)
65
- Module.new(&block)
66
- end
67
-
68
70
  private def enforce_allowed_flags(flags)
69
71
  flags.each do |flag|
70
72
  unless allowed_flag?(flag)
71
- raise ArgumentError, "Expected flag `#{flag.inspect}' to be one of: #{allowed_flags_string}"
73
+ raise ArgumentError, "Expected flag `#{flag.inspect}' to be one of: #{ALLOWED_FLAGS_STRING}"
72
74
  end
73
75
  end
74
76
  end
@@ -76,12 +78,6 @@ module Core
76
78
  private def allowed_flag?(flag)
77
79
  ALLOWED_FLAGS.include?(flag)
78
80
  end
79
-
80
- private def allowed_flags_string
81
- ALLOWED_FLAGS.map { |allowed_flag|
82
- "`#{allowed_flag.inspect}'"
83
- }.join(", ")
84
- end
85
81
  end
86
82
  end
87
83
  end
@@ -2,7 +2,7 @@
2
2
 
3
3
  module Core
4
4
  module Extension
5
- VERSION = "0.0.0"
5
+ VERSION = "0.4.0"
6
6
 
7
7
  def self.version
8
8
  VERSION
@@ -6,3 +6,5 @@ module Core
6
6
  require_relative "extension/version"
7
7
  end
8
8
  end
9
+
10
+ require_relative "../is/extension"
data/lib/is/extension.rb CHANGED
@@ -7,6 +7,101 @@ module Is
7
7
  # [public] Turn a module into a mixin with superpowers.
8
8
  #
9
9
  module Extension
10
+ # Adding core-copy as a dependency creates a recursive dependency, so just bundle it.
11
+ #
12
+ module Copy
13
+ DEFAULT = ::Object.new
14
+
15
+ refine ::Object do
16
+ if RbConfig::CONFIG["RUBY_PROGRAM_VERSION"] < "3"
17
+ def copy(freeze: DEFAULT)
18
+ should_freeze = resolve_freeze_argument(freeze)
19
+
20
+ value = clone(freeze: should_freeze)
21
+ value.freeze if should_freeze
22
+ value
23
+ end
24
+ else
25
+ def copy(freeze: DEFAULT)
26
+ clone(freeze: resolve_freeze_argument(freeze))
27
+ end
28
+ end
29
+
30
+ private def resolve_freeze_argument(value)
31
+ case value
32
+ when DEFAULT
33
+ frozen?
34
+ else
35
+ !!value
36
+ end
37
+ end
38
+ end
39
+
40
+ refine Array do
41
+ def copy(freeze: DEFAULT)
42
+ unless Extension.copying?(self)
43
+ Extension.prevent_recursion(self) do
44
+ array = map { |value|
45
+ value.copy(freeze: freeze)
46
+ }
47
+
48
+ array.freeze if resolve_freeze_argument(freeze)
49
+
50
+ array
51
+ end
52
+ end
53
+ end
54
+ end
55
+
56
+ refine Hash do
57
+ def copy(freeze: DEFAULT)
58
+ unless Extension.copying?(self)
59
+ Extension.prevent_recursion(self) do
60
+ hash = {}
61
+
62
+ each_pair do |key, value|
63
+ hash[key.copy(freeze: freeze)] = value.copy(freeze: freeze)
64
+ end
65
+
66
+ hash.freeze if resolve_freeze_argument(freeze)
67
+
68
+ hash
69
+ end
70
+ end
71
+ end
72
+ end
73
+ end
74
+
75
+ class << self
76
+ def prevent_recursion(object)
77
+ object_id = object.object_id
78
+ copied_objects[object_id] = true
79
+ yield
80
+ ensure
81
+ copied_objects.delete(object_id)
82
+ end
83
+
84
+ def copying?(object)
85
+ copied_objects[object.object_id]
86
+ end
87
+
88
+ def copied_objects
89
+ # We can't use core-local because it would create a recursive dependency.
90
+ #
91
+ Thread.current["__corerb_localized_#{object_id}__corerb_copied_objects"] ||= {}
92
+ end
93
+ end
94
+
95
+ using Copy
96
+
97
+ def initialize_copy(...)
98
+ super
99
+
100
+ instance_variables.each do |instance_variable|
101
+ instance_variable_set(instance_variable, instance_variable_get(instance_variable).copy)
102
+ end
103
+ end
104
+
10
105
  # [public] Restrict the extension to one or more object types.
11
106
  #
12
107
  def restrict(*types)
@@ -14,24 +109,35 @@ module Is
14
109
  end
15
110
 
16
111
  # [public] Define a module to be extended or included into objects that include this extension.
17
- # See [Core::Extension::Dependency] for argument details.
18
112
  #
19
113
  def extends(*flags, dependencies: [], prepend: false, &block)
20
- defined_methods << Core::Extension::Dependency.new(*flags, modules: dependencies, prepend: prepend, &block)
114
+ dependencies.each do |dependency|
115
+ defined_dependencies << Core::Extension::Dependency.new(*flags, dependency: dependency, prepend: prepend)
116
+ end
117
+
118
+ if block
119
+ defined_dependencies << Core::Extension::Dependency.new(*flags, dependency: Module.new(&block), prepend: prepend)
120
+ end
21
121
  end
22
122
 
23
123
  # [public] Define behavior to be evaled on objects that include this extension.
24
- # See [Core::Extension::Behavior] for argument details.
25
124
  #
26
- def applied(force: false, &block)
125
+ def applies(force: false, &block)
27
126
  defined_behaviors << Core::Extension::Behavior.new(force: force, &block)
28
127
  end
29
128
 
30
- # @api private
129
+ def extended(base)
130
+ enforce_allowed_types(base)
131
+ extend_defined_dependencies(base)
132
+ extend_defined_behaviors(base)
133
+
134
+ super
135
+ end
136
+
31
137
  def included(base)
32
138
  enforce_allowed_types(base)
33
- apply_defined_behaviors(base)
34
- apply_defined_methods(base)
139
+ include_defined_dependencies(base)
140
+ include_defined_behaviors(base)
35
141
 
36
142
  super
37
143
  end
@@ -54,15 +160,27 @@ module Is
54
160
  }
55
161
  end
56
162
 
57
- private def apply_defined_behaviors(object)
163
+ private def extend_defined_behaviors(object)
164
+ defined_behaviors.each do |behavior|
165
+ behavior.apply_extend(object)
166
+ end
167
+ end
168
+
169
+ private def extend_defined_dependencies(object)
170
+ defined_dependencies.each do |dependency|
171
+ dependency.apply_extend(object)
172
+ end
173
+ end
174
+
175
+ private def include_defined_behaviors(object)
58
176
  defined_behaviors.each do |behavior|
59
- behavior.apply(object)
177
+ behavior.apply_include(object)
60
178
  end
61
179
  end
62
180
 
63
- private def apply_defined_methods(object)
64
- defined_methods.each do |methods|
65
- methods.apply(object)
181
+ private def include_defined_dependencies(object)
182
+ defined_dependencies.each do |dependency|
183
+ dependency.apply_include(object)
66
184
  end
67
185
  end
68
186
 
@@ -74,8 +192,8 @@ module Is
74
192
  @__defined_behaviors ||= []
75
193
  end
76
194
 
77
- private def defined_methods
78
- @__defined_methods ||= []
195
+ private def defined_dependencies
196
+ @__defined_dependencies ||= []
79
197
  end
80
198
  end
81
199
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: core-extension
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.0
4
+ version: 0.4.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Bryan Powell
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2020-12-29 00:00:00.000000000 Z
11
+ date: 2021-10-24 00:00:00.000000000 Z
12
12
  dependencies: []
13
13
  description: Create mixins with superpowers.
14
14
  email: bryan@metabahn.com
@@ -35,14 +35,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
35
35
  requirements:
36
36
  - - ">="
37
37
  - !ruby/object:Gem::Version
38
- version: 2.5.0
38
+ version: '2.7'
39
39
  required_rubygems_version: !ruby/object:Gem::Requirement
40
40
  requirements:
41
41
  - - ">="
42
42
  - !ruby/object:Gem::Version
43
43
  version: '0'
44
44
  requirements: []
45
- rubygems_version: 3.1.2
45
+ rubygems_version: 3.2.22
46
46
  signing_key:
47
47
  specification_version: 4
48
48
  summary: Create mixins with superpowers.