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.
Files changed (51) hide show
  1. checksums.yaml +5 -5
  2. data/{pakyow-support/CHANGELOG.md → CHANGELOG.md} +4 -0
  3. data/LICENSE +4 -0
  4. data/{pakyow-support/README.md → README.md} +1 -2
  5. data/lib/pakyow/support/aargv.rb +25 -0
  6. data/lib/pakyow/support/bindable.rb +19 -0
  7. data/lib/pakyow/support/class_state.rb +49 -0
  8. data/lib/pakyow/support/cli/runner.rb +106 -0
  9. data/lib/pakyow/support/cli/style.rb +13 -0
  10. data/lib/pakyow/support/configurable/config.rb +153 -0
  11. data/lib/pakyow/support/configurable/setting.rb +52 -0
  12. data/lib/pakyow/support/configurable.rb +103 -0
  13. data/lib/pakyow/support/core_refinements/array/ensurable.rb +25 -0
  14. data/lib/pakyow/support/core_refinements/method/introspection.rb +21 -0
  15. data/lib/pakyow/support/core_refinements/proc/introspection.rb +21 -0
  16. data/lib/pakyow/support/core_refinements/string/normalization.rb +50 -0
  17. data/lib/pakyow/support/deep_dup.rb +61 -0
  18. data/lib/pakyow/support/deep_freeze.rb +82 -0
  19. data/lib/pakyow/support/definable.rb +242 -0
  20. data/lib/pakyow/support/dependencies.rb +61 -0
  21. data/lib/pakyow/support/extension.rb +82 -0
  22. data/lib/pakyow/support/hookable.rb +227 -0
  23. data/lib/pakyow/support/indifferentize.rb +183 -0
  24. data/lib/pakyow/support/inflector.rb +13 -0
  25. data/lib/pakyow/support/inspectable.rb +88 -0
  26. data/lib/pakyow/support/logging.rb +31 -0
  27. data/lib/pakyow/support/makeable/object_maker.rb +30 -0
  28. data/lib/pakyow/support/makeable/object_name.rb +45 -0
  29. data/lib/pakyow/support/makeable/object_namespace.rb +28 -0
  30. data/lib/pakyow/support/makeable.rb +117 -0
  31. data/lib/pakyow/support/message_verifier.rb +74 -0
  32. data/lib/pakyow/support/path_version.rb +21 -0
  33. data/lib/pakyow/support/pipeline/object.rb +41 -0
  34. data/lib/pakyow/support/pipeline.rb +335 -0
  35. data/lib/pakyow/support/safe_string.rb +60 -0
  36. data/lib/pakyow/support/serializer.rb +49 -0
  37. data/lib/pakyow/support/silenceable.rb +21 -0
  38. data/lib/pakyow/support/string_builder.rb +62 -0
  39. data/lib/pakyow/support.rb +1 -0
  40. metadata +107 -26
  41. data/pakyow-support/LICENSE +0 -20
  42. data/pakyow-support/lib/pakyow/support/aargv.rb +0 -15
  43. data/pakyow-support/lib/pakyow/support/array.rb +0 -9
  44. data/pakyow-support/lib/pakyow/support/dir.rb +0 -32
  45. data/pakyow-support/lib/pakyow/support/dup.rb +0 -23
  46. data/pakyow-support/lib/pakyow/support/file.rb +0 -5
  47. data/pakyow-support/lib/pakyow/support/hash.rb +0 -47
  48. data/pakyow-support/lib/pakyow/support/kernel.rb +0 -9
  49. data/pakyow-support/lib/pakyow/support/string.rb +0 -36
  50. data/pakyow-support/lib/pakyow/support.rb +0 -8
  51. 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