core-extension 0.1.0 → 0.4.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: 95231fea0fd981dbd609b053f8a334003490a1a90805c9168665b5d7574a66b1
4
- data.tar.gz: 510b7bb6e4efbf527431cc178a5229b0482ab3280ebc1d4d2c9c9a0290051278
3
+ metadata.gz: 4ed9558c9300ae7945b4d23f9082dfdde7a531e4fa70a95c0b43086d945d51ad
4
+ data.tar.gz: 48ce184ff00a9b7c9432c348a63b273be732271aeee476376a7592500cef53a0
5
5
  SHA512:
6
- metadata.gz: a543f05bbebd33a68b9ad4fdf9d0c91d213d9423790edaefb59a60be4b39496f8b3a83174f9f7592bcd638f2a875cf1830128c139515076ff90955213f4f28c4
7
- data.tar.gz: 2cc2758b3c0589e5130c873623de64d8777e5bd3d7959ee2eb7d7446fd9bf3ca8061847d01b91030f374a90a0d45ff077090130d19db9976aedc54d539c75bd0
6
+ metadata.gz: 83d18619a6e0e458aa03955b852d6930de8612812f9ea07eeeab96005a31d2befe8220c5ed0ca4a50e38e8f7ebcb620f285d74bd42842d1eb40bc9a29771e2c6
7
+ data.tar.gz: c39ca3469c55b68fafe12fc4461ffb7493a4955a3b75482bc972f184ad0c0ebae0f95d02539d4c3d84e1fdc3138c7816eaaed96dd1a4fcfcbf26a688346b8d87
data/CHANGELOG.md CHANGED
@@ -1,3 +1,31 @@
1
+ ## [v0.4.1](https://github.com/metabahn/corerb/releases/tag/2021-11-02)
2
+
3
+ *released on 2021-11-02*
4
+
5
+ * `chg` [#97](https://github.com/metabahn/corerb/pull/97) Designate internal state with leading and trailing double underscores ([bryanp](https://github.com/bryanp))
6
+
7
+ ## [v0.4.0](https://github.com/metabahn/corerb/releases/tag/2021-10-24)
8
+
9
+ *released on 2021-10-24*
10
+
11
+ * `add` [#86](https://github.com/metabahn/corerb/pull/86) Copy instance variables when cloning an extension ([bryanp](https://github.com/bryanp))
12
+
13
+ ## [v0.3.0](https://github.com/metabahn/corerb/releases/tag/2021-07-15)
14
+
15
+ *released on 2021-07-15*
16
+
17
+ * `chg` [#72](https://github.com/metabahn/corerb/pull/72) Rename extension dependency flags ([bryanp](https://github.com/bryanp))
18
+ * `chg` [#71](https://github.com/metabahn/corerb/pull/71) Rename Is::Extension::applied to applies ([bryanp](https://github.com/bryanp))
19
+ * `chg` [#68](https://github.com/metabahn/corerb/pull/68) Pass the extension scope to apply blocks ([bryanp](https://github.com/bryanp))
20
+ * `chg` [#67](https://github.com/metabahn/corerb/pull/67) Refactor extend support for extensions ([bryanp](https://github.com/bryanp))
21
+ * `chg` [#66](https://github.com/metabahn/corerb/pull/66) Support extending classes with extensions ([bryanp](https://github.com/bryanp))
22
+
23
+ ## [v0.2.0](https://github.com/metabahn/corerb/releases/tag/2021-07-07)
24
+
25
+ *released on 2021-07-07*
26
+
27
+ * `chg` [#40](https://github.com/metabahn/corerb/pull/40) Drop Ruby 2.6 support from core-extension ([bryanp](https://github.com/bryanp))
28
+
1
29
  ## [v0.1.0](https://github.com/metabahn/corerb/releases/tag/2021-02-10)
2
30
 
3
31
  *released on 2021-02-10*
@@ -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.1.0"
5
+ VERSION = "0.4.1"
6
6
 
7
7
  def self.version
8
8
  VERSION
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)
@@ -16,19 +111,33 @@ module Is
16
111
  # [public] Define a module to be extended or included into objects that include this extension.
17
112
  #
18
113
  def extends(*flags, dependencies: [], prepend: false, &block)
19
- 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
20
121
  end
21
122
 
22
123
  # [public] Define behavior to be evaled on objects that include this extension.
23
124
  #
24
- def applied(force: false, &block)
125
+ def applies(force: false, &block)
25
126
  defined_behaviors << Core::Extension::Behavior.new(force: force, &block)
26
127
  end
27
128
 
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
+
28
137
  def included(base)
29
138
  enforce_allowed_types(base)
30
- apply_defined_methods(base)
31
- apply_defined_behaviors(base)
139
+ include_defined_dependencies(base)
140
+ include_defined_behaviors(base)
32
141
 
33
142
  super
34
143
  end
@@ -51,28 +160,40 @@ module Is
51
160
  }
52
161
  end
53
162
 
54
- 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)
55
176
  defined_behaviors.each do |behavior|
56
- behavior.apply(object)
177
+ behavior.apply_include(object)
57
178
  end
58
179
  end
59
180
 
60
- private def apply_defined_methods(object)
61
- defined_methods.each do |methods|
62
- methods.apply(object)
181
+ private def include_defined_dependencies(object)
182
+ defined_dependencies.each do |dependency|
183
+ dependency.apply_include(object)
63
184
  end
64
185
  end
65
186
 
66
187
  private def allowed_types
67
- @__allowed_types ||= []
188
+ @__allowed_types__ ||= []
68
189
  end
69
190
 
70
191
  private def defined_behaviors
71
- @__defined_behaviors ||= []
192
+ @__defined_behaviors__ ||= []
72
193
  end
73
194
 
74
- private def defined_methods
75
- @__defined_methods ||= []
195
+ private def defined_dependencies
196
+ @__defined_dependencies__ ||= []
76
197
  end
77
198
  end
78
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.1.0
4
+ version: 0.4.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Bryan Powell
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2021-02-10 00:00:00.000000000 Z
11
+ date: 2021-11-02 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.2.4
45
+ rubygems_version: 3.2.22
46
46
  signing_key:
47
47
  specification_version: 4
48
48
  summary: Create mixins with superpowers.