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,227 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "pakyow/support/class_state"
4
+
5
+ module Pakyow
6
+ module Support
7
+ # Makes it possible to define and call hooks on an object.
8
+ #
9
+ # Hooks can be defined at the class or instance level. When calling hooks
10
+ # on an instance, hooks defined on the class will be called first.
11
+ #
12
+ # By default, hooks are called in the order they are defined. Each hook
13
+ # can be assigned a relative priority to influence when it is to be called
14
+ # (relative to other hooks of the same type). Default hook priority is `0`,
15
+ # and can instead be set to `1` (high) or `-1` (low).
16
+ #
17
+ # @example
18
+ # class Fish
19
+ # include Pakyow::Support::Hookable
20
+ # events :swim
21
+ #
22
+ # def swim
23
+ # performing "swim" do
24
+ # puts "swimming"
25
+ # end
26
+ # end
27
+ # end
28
+ #
29
+ # Fish.before "swim" do
30
+ # puts "prepping"
31
+ # end
32
+ #
33
+ # fish = Fish.new
34
+ #
35
+ # fish.after "swim" do
36
+ # puts "resting"
37
+ # end
38
+ #
39
+ # fish.swim
40
+ # => prepping
41
+ # swimming
42
+ # resting
43
+ #
44
+ module Hookable
45
+ # Known hook priorities.
46
+ #
47
+ PRIORITIES = { default: 0, high: 1, low: -1 }.freeze
48
+
49
+ def self.included(base)
50
+ base.include CommonMethods
51
+ base.include InstanceMethods
52
+
53
+ base.extend ClassMethods
54
+
55
+ base.extend ClassState
56
+ base.class_state :__events, default: [], inheritable: true, getter: false
57
+ base.class_state :__hooks, default: [], inheritable: true, getter: false
58
+ base.class_state :__hook_hash, default: { after: {}, before: {} }, inheritable: true
59
+ base.class_state :__hook_pipeline, default: { after: {}, before: {} }, inheritable: true
60
+ end
61
+
62
+ # @api private
63
+ def known_event?(event)
64
+ self.class.known_event?(event.to_sym)
65
+ end
66
+
67
+ # Class-level api methods.
68
+ #
69
+ module ClassMethods
70
+ attr_reader :__hook_pipeline
71
+
72
+ def self.extended(base)
73
+ base.extend(CommonMethods)
74
+ end
75
+
76
+ # Sets the known events for the hookable object. Hooks registered for
77
+ # an event that doesn't exist will raise an ArgumentError.
78
+ #
79
+ # @param events [Array<Symbol>] The list of known events.
80
+ #
81
+ def events(*events)
82
+ @__events.concat(events.map(&:to_sym)).uniq!; @__events
83
+ end
84
+
85
+ # Defines a hook to call before event occurs.
86
+ #
87
+ # @param event [Symbol] The name of the event.
88
+ # @param priority [Symbol, Integer] The priority of the hook.
89
+ # Other priorities include:
90
+ # high (1)
91
+ # low (-1)
92
+ #
93
+ def before(event, name = nil, priority: PRIORITIES[:default], exec: true, &block)
94
+ add_hook(:before, event, name, priority, exec, block)
95
+ end
96
+ alias on before
97
+
98
+ # Defines a hook to call after event occurs.
99
+ #
100
+ # @see #before
101
+ #
102
+ def after(event, name = nil, priority: PRIORITIES[:default], exec: true, &block)
103
+ add_hook(:after, event, name, priority, exec, block)
104
+ end
105
+
106
+ # Defines a hook to call before and after event occurs.
107
+ #
108
+ # @see #before
109
+ #
110
+ def around(event, name = nil, priority: PRIORITIES[:default], exec: true, &block)
111
+ add_hook(:before, event, name, priority, exec, block)
112
+ add_hook(:after, event, name, priority, exec, block)
113
+ end
114
+
115
+ # @api private
116
+ def known_event?(event)
117
+ @__events.include?(event.to_sym)
118
+ end
119
+
120
+ def known_hook?(event)
121
+ @__hooks.any? { |hook|
122
+ hook[:name] == event.to_sym
123
+ }
124
+ end
125
+ end
126
+
127
+ # Methods included at the class and instance level.
128
+ #
129
+ module CommonMethods
130
+ # Calls all registered hooks for `event`, yielding between them.
131
+ #
132
+ # @param event [Symbol] The name of the event.
133
+ #
134
+ def performing(event, *args)
135
+ call_hooks(:before, event, *args)
136
+ value = yield
137
+ call_hooks(:after, event, *args)
138
+ value
139
+ end
140
+
141
+ # Calls all registered hooks of type, for event.
142
+ #
143
+ # @param type [Symbol] The type of event (e.g. before / after).
144
+ # @param event [Symbol] The name of the event.
145
+ #
146
+ def call_hooks(type, event, *args)
147
+ hooks(type, event).each do |hook|
148
+ if hook[:exec]
149
+ instance_exec(*args, &hook[:block])
150
+ else
151
+ hook[:block].call(*args)
152
+ end
153
+ end
154
+ end
155
+
156
+ # @api private
157
+ def hooks(type, event)
158
+ __hook_pipeline[type][event] || []
159
+ end
160
+
161
+ # @api private
162
+ def add_hook(type, event, name, priority, exec, hook)
163
+ if priority.is_a?(Symbol)
164
+ priority = PRIORITIES[priority]
165
+ end
166
+
167
+ if known_event?(event) || known_hook?(event)
168
+ hook = {
169
+ type: type,
170
+ event: event.to_sym,
171
+ name: name ? name.to_sym : nil,
172
+ priority: priority,
173
+ block: hook,
174
+ exec: exec
175
+ }
176
+
177
+ (@__hook_hash[type.to_sym][event.to_sym] ||= []) << hook
178
+ @__hooks << hook
179
+ else
180
+ raise ArgumentError, "#{event} is not a known hook event"
181
+ end
182
+
183
+ reprioritize!(type, event)
184
+ pipeline!(type, event)
185
+
186
+ if known_hook?(event)
187
+ traverse_events_for_hook(event) do |hook_event|
188
+ pipeline!(:before, hook_event); pipeline!(:after, hook_event)
189
+ end
190
+ end
191
+ end
192
+
193
+ # @api private
194
+ def traverse_events_for_hook(name, &block)
195
+ if hook = @__hooks.find { |h| h[:name] == name.to_sym }
196
+ yield hook[:event]
197
+ traverse_events_for_hook(hook[:event], &block)
198
+ end
199
+ end
200
+
201
+ # @api private
202
+ def reprioritize!(type, event)
203
+ @__hook_hash[type.to_sym][event.to_sym] = @__hook_hash[type.to_sym][event.to_sym].group_by { |hook|
204
+ hook[:priority]
205
+ }.sort { |a, b|
206
+ b[0] <=> a[0]
207
+ }.flat_map { |group|
208
+ group[1]
209
+ }
210
+ end
211
+
212
+ # @api private
213
+ def pipeline!(type, event)
214
+ __hook_pipeline[type.to_sym][event.to_sym] = @__hook_hash.dig(type.to_sym, event.to_sym).to_a.flat_map { |hook|
215
+ [@__hook_pipeline[:before][hook[:name]].to_a, hook, @__hook_pipeline[:after][hook[:name]].to_a].flatten
216
+ }
217
+ end
218
+ end
219
+
220
+ module InstanceMethods
221
+ def __hook_pipeline
222
+ self.class.__hook_pipeline
223
+ end
224
+ end
225
+ end
226
+ end
227
+ end
@@ -0,0 +1,183 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "delegate"
4
+
5
+ module Pakyow
6
+ module Support
7
+ # Creates a Hash-like object can access stored data with symbol or
8
+ # string keys.
9
+ #
10
+ # The original hash is converted to symbol keys, which means
11
+ # that a hash that originally contains a symbol and string key
12
+ # with the same symbold value will conflict. It is not guaranteed
13
+ # which value will be saved.
14
+ #
15
+ # IndifferentHash instances have the same api as Hash, but any method
16
+ # that would return a Hash, will return an IndifferentHash (with
17
+ # the exception of to_h/to_hash).
18
+ #
19
+ # NOTE: Please lookup Ruby's documentation for Hash to learn what
20
+ # methods are available.
21
+ #
22
+ # @example
23
+ # { test: "test1", "test" => "test2" } => { test: "test2" }
24
+ #
25
+ class IndifferentHash < SimpleDelegator
26
+ class << self
27
+ def deep(object)
28
+ hash = object.to_h
29
+ unless hash.empty?
30
+ hash = hash.each_with_object({}) { |(key, value), new_hash|
31
+ new_hash[key] = case value
32
+ when Hash
33
+ deep(value)
34
+ when Array
35
+ value.map { |value_item|
36
+ case value_item
37
+ when Hash
38
+ deep(value_item)
39
+ else
40
+ value_item
41
+ end
42
+ }
43
+ else
44
+ value
45
+ end
46
+ }
47
+ end
48
+
49
+ self.new(hash)
50
+ end
51
+
52
+ private
53
+
54
+ def indifferent_key_method(*methods)
55
+ methods.each do |name|
56
+ define_method(name) do |key = nil, *args, &block|
57
+ key = convert_key(key)
58
+ internal_hash.public_send(name, key, *args, &block)
59
+ end
60
+ end
61
+ end
62
+
63
+ def indifferent_multi_key_method(*methods)
64
+ methods.each do |name|
65
+ define_method(name) do |*keys, &block|
66
+ keys = keys.map { |key|
67
+ convert_key(key)
68
+ }
69
+ internal_hash.public_send(name, *keys, &block)
70
+ end
71
+ end
72
+ end
73
+
74
+ def indifferentize_return_method(*methods)
75
+ methods.each do |name|
76
+ define_method(name) do |*args, &block|
77
+ hash = internal_hash.public_send(name, *args, &block)
78
+ self.class.new(hash) if hash
79
+ end
80
+ end
81
+ end
82
+
83
+ def indifferentize_update_method(*methods)
84
+ methods.each do |name|
85
+ define_method(name) do |*args, &block|
86
+ args = args.map { |arg| stringify_keys(arg) }
87
+ hash = internal_hash.public_send(name, *args, &block)
88
+ self if hash
89
+ end
90
+ end
91
+ end
92
+
93
+ def indifferentize_argument_method(*methods)
94
+ methods.each do |name|
95
+ define_method(name) do |*args, &block|
96
+ args = args.map { |arg| stringify_keys(arg) }
97
+ internal_hash.public_send(name, *args, &block)
98
+ end
99
+ end
100
+ end
101
+ end
102
+
103
+ def initialize(hash = {})
104
+ self.internal_hash = hash
105
+ end
106
+
107
+ indifferent_key_method :[], :[]=, :default, :delete, :fetch, :has_key?, :key?, :include?, :member?, :store
108
+ indifferent_multi_key_method :fetch_values, :values_at, :dig
109
+ indifferentize_return_method :merge, :invert, :compact, :reject, :select, :transform_values
110
+ indifferentize_update_method :merge!, :update, :replace, :clear, :keep_if, :delete_if, :compact!, :reject!, :select!
111
+ indifferentize_argument_method :>, :>=, :<=>, :<, :<=, :==
112
+
113
+ def internal_hash
114
+ __getobj__
115
+ end
116
+
117
+ def to_h
118
+ internal_hash.each_with_object({}) { |(key, value), new_hash|
119
+ key = case key
120
+ when String
121
+ key.to_sym
122
+ else
123
+ key
124
+ end
125
+
126
+ value = case value
127
+ when IndifferentHash
128
+ value.to_h
129
+ else
130
+ value
131
+ end
132
+
133
+ new_hash[key] = value
134
+ }
135
+ end
136
+ alias to_hash to_h
137
+
138
+ # Fixes an issue using pp inside a delegator.
139
+ #
140
+ def pp(*args)
141
+ Kernel.pp(*args)
142
+ end
143
+
144
+ private
145
+
146
+ def internal_hash=(other)
147
+ __setobj__(stringify_keys(other))
148
+ end
149
+
150
+ def stringify_keys(object)
151
+ return object unless object.respond_to?(:to_h)
152
+
153
+ converted = {}
154
+ object.to_h.each do |key, value|
155
+ converted[convert_key(key)] = value
156
+ end
157
+
158
+ converted
159
+ end
160
+
161
+ def convert_key(key)
162
+ case key
163
+ when Symbol
164
+ key.to_s
165
+ else
166
+ key
167
+ end
168
+ end
169
+ end
170
+
171
+ module Indifferentize
172
+ refine Hash do
173
+ def indifferentize
174
+ Pakyow::Support::IndifferentHash.new(self)
175
+ end
176
+
177
+ def deep_indifferentize
178
+ Pakyow::Support::IndifferentHash.deep(self)
179
+ end
180
+ end
181
+ end
182
+ end
183
+ end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "dry/inflector"
4
+
5
+ module Pakyow
6
+ module Support
7
+ def self.inflector
8
+ @inflector ||= Dry::Inflector.new do |inflections|
9
+ inflections.uncountable "children"
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,88 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "pakyow/support/class_state"
4
+
5
+ module Pakyow
6
+ module Support
7
+ # Customized inspectors for your objects.
8
+ #
9
+ # @example
10
+ # class FooBar
11
+ # include Pakyow::Support::Inspectable
12
+ # inspectable :@foo, :baz
13
+ #
14
+ # def initialize
15
+ # @foo = :foo
16
+ # @bar = :bar
17
+ # end
18
+ #
19
+ # def baz
20
+ # :baz
21
+ # end
22
+ # end
23
+ #
24
+ # FooBar.instance.inspect
25
+ # => #<FooBar:0x007fd3330248c0 @foo=:foo baz=:baz>
26
+ #
27
+ module Inspectable
28
+ def self.included(base)
29
+ base.extend ClassMethods
30
+ base.extend ClassState unless base.ancestors.include?(ClassState)
31
+ base.class_state :__inspectables, inheritable: true, default: []
32
+ end
33
+
34
+ module ClassMethods
35
+ # Sets the instance vars and public methods that should be part of the inspection.
36
+ #
37
+ # @param inspectables [Array<Symbol>] The list of instance variables and public methods.
38
+ #
39
+ def inspectable(*inspectables)
40
+ @__inspectables = inspectables.map(&:to_sym)
41
+ end
42
+ end
43
+
44
+ # Recursion protection based on:
45
+ # https://stackoverflow.com/a/5772445
46
+ #
47
+ def inspect
48
+ inspection = String.new("#<#{self.class}:#{self.object_id}")
49
+
50
+ if recursive_inspect?
51
+ "#{inspection} ...>"
52
+ else
53
+ prevent_inspect_recursion do
54
+ if self.class.__inspectables.any?
55
+ inspection << " " + self.class.__inspectables.map { |inspectable|
56
+ value = if inspectable.to_s.start_with?("@")
57
+ instance_variable_get(inspectable)
58
+ else
59
+ send(inspectable)
60
+ end
61
+
62
+ "#{inspectable}=#{value.inspect}"
63
+ }.join(", ")
64
+ end
65
+
66
+ inspection.strip << ">"
67
+ end
68
+ end
69
+ end
70
+
71
+ private
72
+
73
+ def inspected_objects
74
+ Thread.current[:inspected_objects] ||= {}
75
+ end
76
+
77
+ def prevent_inspect_recursion
78
+ inspected_objects[object_id] = true; yield
79
+ ensure
80
+ inspected_objects.delete(object_id)
81
+ end
82
+
83
+ def recursive_inspect?
84
+ inspected_objects[object_id]
85
+ end
86
+ end
87
+ end
88
+ end
@@ -0,0 +1,31 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "logger"
4
+
5
+ module Pakyow
6
+ module Support
7
+ module Logging
8
+ # Yields Pakyow.logger if defined, otherwise raises `error`.
9
+ #
10
+ def self.yield_or_raise(error)
11
+ if defined?(Pakyow.logger)
12
+ yield(Pakyow.logger)
13
+ else
14
+ raise error
15
+ end
16
+ end
17
+
18
+ # Yields Pakyow.logger if defined, or a default logger.
19
+ #
20
+ def self.safe
21
+ logger = if defined?(Pakyow.logger) && Pakyow.logger
22
+ Pakyow.logger
23
+ else
24
+ ::Logger.new($stdout)
25
+ end
26
+
27
+ yield logger
28
+ end
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "pakyow/support/inflector"
4
+
5
+ module Pakyow
6
+ module Support
7
+ # @api private
8
+ module ObjectMaker
9
+ def self.define_const_for_object_with_name(object_to_define, object_name)
10
+ return if object_name.nil?
11
+
12
+ target = object_name.namespace.parts.inject(Object) { |target_for_part, object_name_part|
13
+ ObjectMaker.define_object_on_target_with_constant_name(Module.new, target_for_part, object_name_part)
14
+ }
15
+
16
+ ObjectMaker.define_object_on_target_with_constant_name(object_to_define, target, object_name.name)
17
+ end
18
+
19
+ def self.define_object_on_target_with_constant_name(object, target, constant_name)
20
+ constant_name = Support.inflector.camelize(constant_name)
21
+
22
+ unless target.const_defined?(constant_name, false)
23
+ target.const_set(constant_name, object)
24
+ end
25
+
26
+ target.const_get(constant_name)
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,45 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "pakyow/support/inflector"
4
+ require "pakyow/support/makeable/object_namespace"
5
+
6
+ module Pakyow
7
+ module Support
8
+ # @api private
9
+ class ObjectName
10
+ class << self
11
+ def namespace(*namespaces, object_name)
12
+ ObjectName.new(
13
+ ObjectNamespace.new(*namespaces),
14
+ object_name
15
+ )
16
+ end
17
+ end
18
+
19
+ attr_reader :namespace, :name
20
+
21
+ def initialize(namespace, name)
22
+ @namespace, @name = namespace, name.to_sym
23
+ end
24
+
25
+ def isolated(subobject_name)
26
+ ObjectName.new(
27
+ ObjectNamespace.new(*parts),
28
+ subobject_name
29
+ )
30
+ end
31
+
32
+ def parts
33
+ namespace.parts + [@name]
34
+ end
35
+
36
+ def to_s
37
+ [@namespace, @name].join("/")
38
+ end
39
+
40
+ def constant
41
+ [@namespace.constant, Support.inflector.camelize(@name)].join("::")
42
+ end
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "pakyow/support/inflector"
4
+
5
+ module Pakyow
6
+ module Support
7
+ # @api private
8
+ class ObjectNamespace
9
+ def initialize(*namespaces)
10
+ @namespaces = namespaces.map(&:to_sym)
11
+ end
12
+
13
+ def parts
14
+ @namespaces
15
+ end
16
+
17
+ def to_s
18
+ @namespaces.join("/")
19
+ end
20
+
21
+ def constant
22
+ @namespaces.map { |namespace|
23
+ Support.inflector.camelize(namespace)
24
+ }.join("::")
25
+ end
26
+ end
27
+ end
28
+ end