pakyow-support 0.11.3 → 1.0.0.rc1
Sign up to get free protection for your applications and to get access to all the features.
- 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
|