pakyow-support 0.11.3 → 1.0.0.rc1
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 +5 -5
- data/{pakyow-support/CHANGELOG.md → CHANGELOG.md} +4 -0
- data/LICENSE +4 -0
- data/{pakyow-support/README.md → README.md} +1 -2
- data/lib/pakyow/support/aargv.rb +25 -0
- data/lib/pakyow/support/bindable.rb +19 -0
- data/lib/pakyow/support/class_state.rb +49 -0
- data/lib/pakyow/support/cli/runner.rb +106 -0
- data/lib/pakyow/support/cli/style.rb +13 -0
- data/lib/pakyow/support/configurable/config.rb +153 -0
- data/lib/pakyow/support/configurable/setting.rb +52 -0
- data/lib/pakyow/support/configurable.rb +103 -0
- data/lib/pakyow/support/core_refinements/array/ensurable.rb +25 -0
- data/lib/pakyow/support/core_refinements/method/introspection.rb +21 -0
- data/lib/pakyow/support/core_refinements/proc/introspection.rb +21 -0
- data/lib/pakyow/support/core_refinements/string/normalization.rb +50 -0
- data/lib/pakyow/support/deep_dup.rb +61 -0
- data/lib/pakyow/support/deep_freeze.rb +82 -0
- data/lib/pakyow/support/definable.rb +242 -0
- data/lib/pakyow/support/dependencies.rb +61 -0
- data/lib/pakyow/support/extension.rb +82 -0
- data/lib/pakyow/support/hookable.rb +227 -0
- data/lib/pakyow/support/indifferentize.rb +183 -0
- data/lib/pakyow/support/inflector.rb +13 -0
- data/lib/pakyow/support/inspectable.rb +88 -0
- data/lib/pakyow/support/logging.rb +31 -0
- data/lib/pakyow/support/makeable/object_maker.rb +30 -0
- data/lib/pakyow/support/makeable/object_name.rb +45 -0
- data/lib/pakyow/support/makeable/object_namespace.rb +28 -0
- data/lib/pakyow/support/makeable.rb +117 -0
- data/lib/pakyow/support/message_verifier.rb +74 -0
- data/lib/pakyow/support/path_version.rb +21 -0
- data/lib/pakyow/support/pipeline/object.rb +41 -0
- data/lib/pakyow/support/pipeline.rb +335 -0
- data/lib/pakyow/support/safe_string.rb +60 -0
- data/lib/pakyow/support/serializer.rb +49 -0
- data/lib/pakyow/support/silenceable.rb +21 -0
- data/lib/pakyow/support/string_builder.rb +62 -0
- data/lib/pakyow/support.rb +1 -0
- metadata +107 -26
- data/pakyow-support/LICENSE +0 -20
- data/pakyow-support/lib/pakyow/support/aargv.rb +0 -15
- data/pakyow-support/lib/pakyow/support/array.rb +0 -9
- data/pakyow-support/lib/pakyow/support/dir.rb +0 -32
- data/pakyow-support/lib/pakyow/support/dup.rb +0 -23
- data/pakyow-support/lib/pakyow/support/file.rb +0 -5
- data/pakyow-support/lib/pakyow/support/hash.rb +0 -47
- data/pakyow-support/lib/pakyow/support/kernel.rb +0 -9
- data/pakyow-support/lib/pakyow/support/string.rb +0 -36
- data/pakyow-support/lib/pakyow/support.rb +0 -8
- data/pakyow-support/lib/pakyow-support.rb +0 -1
@@ -0,0 +1,61 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "delegate"
|
4
|
+
|
5
|
+
module Pakyow
|
6
|
+
module Support
|
7
|
+
# Refines Object, Array, and Hash with support for deep_dup.
|
8
|
+
#
|
9
|
+
# @example
|
10
|
+
# using DeepDup
|
11
|
+
# state = { "foo" => ["bar"] }
|
12
|
+
# duped = state.deep_dup
|
13
|
+
#
|
14
|
+
# state.keys[0] === duped.keys[0]
|
15
|
+
# => false
|
16
|
+
#
|
17
|
+
# state.values[0][0] === duped.values[0][0]
|
18
|
+
# => false
|
19
|
+
#
|
20
|
+
module DeepDup
|
21
|
+
# Objects that can't be copied.
|
22
|
+
UNDUPABLE = [Symbol, Integer, NilClass, TrueClass, FalseClass, Class, Module].freeze
|
23
|
+
|
24
|
+
[Object, Delegator].each do |klass|
|
25
|
+
refine klass do
|
26
|
+
# Returns a copy of the object.
|
27
|
+
#
|
28
|
+
def deep_dup
|
29
|
+
if UNDUPABLE.include?(self.class)
|
30
|
+
self
|
31
|
+
else
|
32
|
+
dup
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
refine Array do
|
39
|
+
# Returns a deep copy of the array.
|
40
|
+
#
|
41
|
+
def deep_dup
|
42
|
+
map(&:deep_dup)
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
refine Hash do
|
47
|
+
# Returns a deep copy of the hash.
|
48
|
+
#
|
49
|
+
def deep_dup
|
50
|
+
hash = dup
|
51
|
+
each_pair do |key, value|
|
52
|
+
hash.delete(key)
|
53
|
+
hash[key.deep_dup] = value.deep_dup
|
54
|
+
end
|
55
|
+
|
56
|
+
hash
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
@@ -0,0 +1,82 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "delegate"
|
4
|
+
|
5
|
+
module Pakyow
|
6
|
+
module Support
|
7
|
+
module DeepFreeze
|
8
|
+
def self.extended(subclass)
|
9
|
+
subclass.instance_variable_set(:@unfreezable_variables, [])
|
10
|
+
|
11
|
+
super
|
12
|
+
end
|
13
|
+
|
14
|
+
def inherited(subclass)
|
15
|
+
subclass.instance_variable_set(:@unfreezable_variables, @unfreezable_variables)
|
16
|
+
|
17
|
+
super
|
18
|
+
end
|
19
|
+
|
20
|
+
def unfreezable(*ivars)
|
21
|
+
@unfreezable_variables.concat(ivars.map { |ivar| :"@#{ivar}" })
|
22
|
+
@unfreezable_variables.uniq!
|
23
|
+
end
|
24
|
+
|
25
|
+
[Object, Delegator].each do |klass|
|
26
|
+
refine klass do
|
27
|
+
def deep_freeze
|
28
|
+
unless frozen? || !respond_to?(:freeze)
|
29
|
+
self.freeze
|
30
|
+
freezable_variables.each do |name|
|
31
|
+
instance_variable_get(name).deep_freeze
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
self
|
36
|
+
end
|
37
|
+
|
38
|
+
private def freezable_variables
|
39
|
+
object = if self.is_a?(Class) || self.is_a?(Module)
|
40
|
+
self
|
41
|
+
else
|
42
|
+
self.class
|
43
|
+
end
|
44
|
+
|
45
|
+
if object.instance_variable_defined?(:@unfreezable_variables)
|
46
|
+
instance_variables - object.instance_variable_get(:@unfreezable_variables)
|
47
|
+
else
|
48
|
+
instance_variables
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
refine Array do
|
55
|
+
def deep_freeze
|
56
|
+
unless frozen?
|
57
|
+
self.freeze
|
58
|
+
each(&:deep_freeze)
|
59
|
+
end
|
60
|
+
|
61
|
+
self
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
refine Hash do
|
66
|
+
def deep_freeze
|
67
|
+
unless frozen?
|
68
|
+
frozen_hash = {}
|
69
|
+
each_pair do |key, value|
|
70
|
+
frozen_hash[key.deep_freeze] = value.deep_freeze
|
71
|
+
end
|
72
|
+
|
73
|
+
self.replace(frozen_hash)
|
74
|
+
self.freeze
|
75
|
+
end
|
76
|
+
|
77
|
+
self
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
@@ -0,0 +1,242 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "pakyow/support/deep_dup"
|
4
|
+
require "pakyow/support/inflector"
|
5
|
+
require "pakyow/support/makeable"
|
6
|
+
|
7
|
+
module Pakyow
|
8
|
+
module Support
|
9
|
+
# Provides control over how state is defined on an object, and how state is
|
10
|
+
# shared across object instances and subclasses.
|
11
|
+
#
|
12
|
+
# You define the type of state provided by an object, along with any global
|
13
|
+
# state for that object type. When an instance is created or the definable
|
14
|
+
# object is subclassed, the new object inherits the global state and can be
|
15
|
+
# extended with its own state. Definable objects' `initialize` method should
|
16
|
+
# always call super with the block to ensure that state is inherited correctly.
|
17
|
+
#
|
18
|
+
# Once `defined!` is called on an instance, consider freezing the object so
|
19
|
+
# that it cannot be extended later.
|
20
|
+
#
|
21
|
+
#
|
22
|
+
# @example
|
23
|
+
# class SomeDefinableObject
|
24
|
+
# include Support::Definable
|
25
|
+
#
|
26
|
+
# def initialize(some_arg, &block)
|
27
|
+
# super()
|
28
|
+
|
29
|
+
# # Do something with some_arg, etc.
|
30
|
+
#
|
31
|
+
# defined!(&block)
|
32
|
+
# end
|
33
|
+
# end
|
34
|
+
#
|
35
|
+
# @api private
|
36
|
+
#
|
37
|
+
module Definable
|
38
|
+
using DeepDup
|
39
|
+
|
40
|
+
def self.included(base)
|
41
|
+
base.include CommonMethods
|
42
|
+
base.extend ClassMethods, CommonMethods
|
43
|
+
base.prepend Initializer
|
44
|
+
base.extend Support::Makeable
|
45
|
+
base.instance_variable_set(:@__state, {})
|
46
|
+
end
|
47
|
+
|
48
|
+
# @api private
|
49
|
+
def defined!(&block)
|
50
|
+
# set instance level state
|
51
|
+
self.instance_eval(&block) if block_given?
|
52
|
+
|
53
|
+
# merge global state
|
54
|
+
@__state.each do |name, state|
|
55
|
+
state.instances.concat(self.class.__state[name].instances)
|
56
|
+
end
|
57
|
+
|
58
|
+
# merge inherited state
|
59
|
+
if inherited = self.class.__inherited_state
|
60
|
+
@__state.each do |name, state|
|
61
|
+
instances = state.instances
|
62
|
+
instances.concat(inherited[name].instances) if inherited[name]
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
module ClassMethods
|
68
|
+
attr_reader :__state, :__inherited_state
|
69
|
+
|
70
|
+
def inherited(subclass)
|
71
|
+
super
|
72
|
+
|
73
|
+
subclass.instance_variable_set(:@__inherited_state, @__state.deep_dup)
|
74
|
+
subclass.instance_variable_set(:@__state, @__state.each_with_object({}) { |(name, state_instance), state|
|
75
|
+
state[name] = State.new(name, state_instance.object)
|
76
|
+
})
|
77
|
+
end
|
78
|
+
|
79
|
+
# Register a type of state that can be defined.
|
80
|
+
#
|
81
|
+
# @param object
|
82
|
+
# Can be a class or instance, but must respond to :make. The `make`
|
83
|
+
# method should return the object to be "made" and accept a block
|
84
|
+
# that should be evaluated in the context of the object.
|
85
|
+
#
|
86
|
+
# class Person
|
87
|
+
# # ...
|
88
|
+
#
|
89
|
+
# def make(name, dob, &block)
|
90
|
+
# person = self.class.new(name, dob)
|
91
|
+
#
|
92
|
+
# person.instance_eval(&block)
|
93
|
+
# person
|
94
|
+
# end
|
95
|
+
#
|
96
|
+
# def befriend(person)
|
97
|
+
# friends << person
|
98
|
+
# end
|
99
|
+
# end
|
100
|
+
#
|
101
|
+
# class App
|
102
|
+
# include Pakyow::Support::Definable
|
103
|
+
#
|
104
|
+
# stateful :person, Person
|
105
|
+
# end
|
106
|
+
#
|
107
|
+
# john = App.person 'John', Date.new(1988, 8, 13) do
|
108
|
+
# end
|
109
|
+
#
|
110
|
+
# App.person 'Sofie', Date.new(2015, 9, 6) do
|
111
|
+
# befriend(john)
|
112
|
+
# end
|
113
|
+
#
|
114
|
+
def stateful(name, object, &stateful_block)
|
115
|
+
name = name.to_sym
|
116
|
+
@__state[name] = State.new(name, object)
|
117
|
+
plural_name = Support.inflector.pluralize(name.to_s).to_sym
|
118
|
+
|
119
|
+
within = if __object_name
|
120
|
+
ObjectNamespace.new(*__object_name.namespace.parts.dup.concat([plural_name]))
|
121
|
+
else
|
122
|
+
self
|
123
|
+
end
|
124
|
+
|
125
|
+
method_body = Proc.new do |*args, priority: :default, **opts, &block|
|
126
|
+
return @__state[name] if block.nil?
|
127
|
+
|
128
|
+
stateful_block.call(args, opts) if stateful_block
|
129
|
+
object.make(*args, within: within, **opts, &block).tap do |state|
|
130
|
+
@__state[name].register(state, priority: priority)
|
131
|
+
end
|
132
|
+
end
|
133
|
+
|
134
|
+
define_method name, &method_body
|
135
|
+
define_singleton_method name, &method_body
|
136
|
+
end
|
137
|
+
|
138
|
+
# Define state for the object.
|
139
|
+
#
|
140
|
+
def define(&block)
|
141
|
+
instance_eval(&block)
|
142
|
+
end
|
143
|
+
end
|
144
|
+
|
145
|
+
module CommonMethods
|
146
|
+
# Returns registered state instances. If +type+ is passed, returns state of that type.
|
147
|
+
#
|
148
|
+
def state(type = nil)
|
149
|
+
if instance_variable_defined?(:@__state)
|
150
|
+
return @__state if type.nil?
|
151
|
+
|
152
|
+
if @__state && @__state.key?(type)
|
153
|
+
@__state[type].instances
|
154
|
+
else
|
155
|
+
[]
|
156
|
+
end
|
157
|
+
else
|
158
|
+
{}
|
159
|
+
end
|
160
|
+
end
|
161
|
+
end
|
162
|
+
|
163
|
+
module Initializer
|
164
|
+
def initialize(*)
|
165
|
+
# Create mutable state for this instance based on global.
|
166
|
+
#
|
167
|
+
@__state = self.class.__state.each_with_object({}) { |(name, global_state), state|
|
168
|
+
state[name] = State.new(name, global_state.object)
|
169
|
+
}
|
170
|
+
|
171
|
+
super
|
172
|
+
end
|
173
|
+
end
|
174
|
+
end
|
175
|
+
|
176
|
+
# Contains state for a definable class or instance.
|
177
|
+
#
|
178
|
+
# @api private
|
179
|
+
class State
|
180
|
+
using DeepDup
|
181
|
+
|
182
|
+
attr_reader :name, :object, :instances, :priorities
|
183
|
+
|
184
|
+
PRIORITIES = { default: 0, high: 1, low: -1 }.freeze
|
185
|
+
|
186
|
+
def initialize(name, object)
|
187
|
+
@name = name.to_sym
|
188
|
+
@object = object
|
189
|
+
@instances = []
|
190
|
+
@priorities = {}
|
191
|
+
end
|
192
|
+
|
193
|
+
def initialize_copy(original)
|
194
|
+
super
|
195
|
+
@instances = original.instances.deep_dup
|
196
|
+
end
|
197
|
+
|
198
|
+
# TODO: we handle both instances and classes, so reconsider the variable naming
|
199
|
+
def <<(instance)
|
200
|
+
register(instance)
|
201
|
+
end
|
202
|
+
|
203
|
+
# TODO: we handle both instances and classes, so reconsider the variable naming
|
204
|
+
def register(instance, priority: :default)
|
205
|
+
unless priority.is_a?(Integer)
|
206
|
+
priority = PRIORITIES.fetch(priority) {
|
207
|
+
raise ArgumentError, "unknown priority `#{priority}'"
|
208
|
+
}
|
209
|
+
end
|
210
|
+
|
211
|
+
unless instance.is_a?(Module)
|
212
|
+
enforce_registration!(instance)
|
213
|
+
end
|
214
|
+
|
215
|
+
unless instances.include?(instance)
|
216
|
+
instances << instance
|
217
|
+
end
|
218
|
+
|
219
|
+
priorities[instance] = priority
|
220
|
+
reprioritize!
|
221
|
+
end
|
222
|
+
|
223
|
+
def enforce_registration!(instance)
|
224
|
+
ancestors = if instance.respond_to?(:new)
|
225
|
+
instance.ancestors
|
226
|
+
else
|
227
|
+
instance.class.ancestors
|
228
|
+
end
|
229
|
+
|
230
|
+
unless ancestors.include?(@object)
|
231
|
+
raise ArgumentError, "expected instance of '#{@object}'"
|
232
|
+
end
|
233
|
+
end
|
234
|
+
|
235
|
+
def reprioritize!
|
236
|
+
@instances.sort! { |a, b|
|
237
|
+
(@priorities[b] || 0) <=> (@priorities[a] || 0)
|
238
|
+
}
|
239
|
+
end
|
240
|
+
end
|
241
|
+
end
|
242
|
+
end
|
@@ -0,0 +1,61 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Pakyow
|
4
|
+
module Support
|
5
|
+
# @api private
|
6
|
+
module Dependencies
|
7
|
+
extend self
|
8
|
+
|
9
|
+
LOCAL_FRAMEWORK_PATH = File.expand_path("../../../../../", __FILE__)
|
10
|
+
|
11
|
+
def strip_path_prefix(line)
|
12
|
+
if line.start_with?(Pakyow.config.root)
|
13
|
+
line.gsub(/^#{Pakyow.config.root}\//, "")
|
14
|
+
elsif line.start_with?(Pakyow.config.lib)
|
15
|
+
line.gsub(/^#{Pakyow.config.lib}\//, "")
|
16
|
+
elsif line.start_with?(Gem.default_dir)
|
17
|
+
line.gsub(/^#{Gem.default_dir}\/gems\//, "")
|
18
|
+
elsif line.start_with?(Bundler.bundle_path.to_s)
|
19
|
+
line.gsub(/^#{Bundler.bundle_path.to_s}\/gems\//, "")
|
20
|
+
elsif line.start_with?(RbConfig::CONFIG["libdir"])
|
21
|
+
line.gsub(/^#{RbConfig::CONFIG["libdir"]}\//, "")
|
22
|
+
elsif line.start_with?(LOCAL_FRAMEWORK_PATH)
|
23
|
+
line.gsub(/^#{LOCAL_FRAMEWORK_PATH}\//, "")
|
24
|
+
else
|
25
|
+
line
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def library_name(line)
|
30
|
+
case library_type(line)
|
31
|
+
when :gem, :bundler
|
32
|
+
strip_path_prefix(line).split("/")[0].split("-")[0..-2].join("-")
|
33
|
+
when :ruby
|
34
|
+
"ruby"
|
35
|
+
when :pakyow
|
36
|
+
strip_path_prefix(line).split("/")[0]
|
37
|
+
when :lib
|
38
|
+
strip_path_prefix(line).split("/")[1]
|
39
|
+
else
|
40
|
+
nil
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
def library_type(line)
|
45
|
+
if line.start_with?(Gem.default_dir)
|
46
|
+
:gem
|
47
|
+
elsif line.start_with?(Bundler.bundle_path.to_s)
|
48
|
+
:bundler
|
49
|
+
elsif line.start_with?(RbConfig::CONFIG["libdir"])
|
50
|
+
:ruby
|
51
|
+
elsif line.start_with?(LOCAL_FRAMEWORK_PATH)
|
52
|
+
:pakyow
|
53
|
+
elsif line.start_with?(Pakyow.config.lib)
|
54
|
+
:lib
|
55
|
+
else
|
56
|
+
nil
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
@@ -0,0 +1,82 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Pakyow
|
4
|
+
module Support
|
5
|
+
# Makes it easier to define extensions.
|
6
|
+
#
|
7
|
+
# @example
|
8
|
+
#
|
9
|
+
# module SomeExtension
|
10
|
+
# extend Pakyow::Support::Extension
|
11
|
+
#
|
12
|
+
# # only allows the extension to be used on descendants of `SomeBaseClass`
|
13
|
+
# restrict_extension SomeBaseClass
|
14
|
+
#
|
15
|
+
# apply_extension do
|
16
|
+
# # anything here is evaluated on the object including the extension
|
17
|
+
# end
|
18
|
+
#
|
19
|
+
# class_methods do
|
20
|
+
# # class methods can be defined here
|
21
|
+
# end
|
22
|
+
#
|
23
|
+
# prepend_methods do
|
24
|
+
# # instance methods you wish to prepend can be defined here
|
25
|
+
# end
|
26
|
+
#
|
27
|
+
# # define instance-level methods as usual
|
28
|
+
# end
|
29
|
+
#
|
30
|
+
# class SomeClass < SomeBaseClass
|
31
|
+
# include SomeExtension
|
32
|
+
# end
|
33
|
+
#
|
34
|
+
module Extension
|
35
|
+
def restrict_extension(type)
|
36
|
+
@__extension_restriction = type
|
37
|
+
end
|
38
|
+
|
39
|
+
def apply_extension(&block)
|
40
|
+
@__extension_block = block
|
41
|
+
end
|
42
|
+
|
43
|
+
def class_methods(&block)
|
44
|
+
@__extension_extend_module = Module.new(&block)
|
45
|
+
end
|
46
|
+
|
47
|
+
def prepend_methods(&block)
|
48
|
+
@__extension_prepend_module = Module.new(&block)
|
49
|
+
end
|
50
|
+
|
51
|
+
def included(base)
|
52
|
+
enforce_restrictions(base)
|
53
|
+
mixin_extension_modules(base)
|
54
|
+
include_extensions(base)
|
55
|
+
end
|
56
|
+
|
57
|
+
private
|
58
|
+
|
59
|
+
def enforce_restrictions(base)
|
60
|
+
if instance_variable_defined?(:@__extension_restriction) && !base.ancestors.include?(@__extension_restriction)
|
61
|
+
raise StandardError, "expected `#{base}' to be `#{@__extension_restriction}'"
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
def mixin_extension_modules(base)
|
66
|
+
if instance_variable_defined?(:@__extension_extend_module)
|
67
|
+
base.extend @__extension_extend_module
|
68
|
+
end
|
69
|
+
|
70
|
+
if instance_variable_defined?(:@__extension_prepend_module)
|
71
|
+
base.prepend @__extension_prepend_module
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
def include_extensions(base)
|
76
|
+
if instance_variable_defined?(:@__extension_block)
|
77
|
+
base.instance_exec(&@__extension_block)
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|