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.
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