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