flows 0.4.0 → 0.5.0
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.
- checksums.yaml +4 -4
- data/.mdlrc +1 -1
- data/.reek.yml +12 -0
- data/CHANGELOG.md +16 -0
- data/Gemfile.lock +1 -1
- data/README.md +12 -2
- data/Rakefile +1 -1
- data/bin/all_the_errors +8 -0
- data/bin/errors +12 -0
- data/bin/errors_cli/flow_error_demo.rb +22 -0
- data/docs/README.md +1 -1
- data/lib/flows/contract/case_eq.rb +3 -1
- data/lib/flows/flow/errors.rb +29 -0
- data/lib/flows/flow/node.rb +1 -0
- data/lib/flows/flow/router/custom.rb +5 -0
- data/lib/flows/flow/router/simple.rb +5 -0
- data/lib/flows/flow/router.rb +4 -0
- data/lib/flows/flow.rb +21 -0
- data/lib/flows/plugin/dependency_injector.rb +5 -5
- data/lib/flows/plugin/output_contract/dsl.rb +15 -3
- data/lib/flows/plugin/output_contract/wrapper.rb +14 -12
- data/lib/flows/plugin/output_contract.rb +1 -0
- data/lib/flows/plugin/profiler/injector.rb +35 -0
- data/lib/flows/plugin/profiler/report/events.rb +43 -0
- data/lib/flows/plugin/profiler/report/flat/method_report.rb +81 -0
- data/lib/flows/plugin/profiler/report/flat.rb +41 -0
- data/lib/flows/plugin/profiler/report/raw.rb +15 -0
- data/lib/flows/plugin/profiler/report/tree/calculated_node.rb +116 -0
- data/lib/flows/plugin/profiler/report/tree/node.rb +35 -0
- data/lib/flows/plugin/profiler/report/tree.rb +98 -0
- data/lib/flows/plugin/profiler/report.rb +48 -0
- data/lib/flows/plugin/profiler/wrapper.rb +53 -0
- data/lib/flows/plugin/profiler.rb +114 -0
- data/lib/flows/plugin.rb +1 -0
- data/lib/flows/railway/dsl.rb +3 -2
- data/lib/flows/result/do.rb +6 -8
- data/lib/flows/shared_context_pipeline/dsl/callbacks.rb +38 -0
- data/lib/flows/shared_context_pipeline/dsl/tracks.rb +52 -0
- data/lib/flows/shared_context_pipeline/dsl.rb +5 -56
- data/lib/flows/shared_context_pipeline/mutation_step.rb +6 -8
- data/lib/flows/shared_context_pipeline/step.rb +6 -8
- data/lib/flows/shared_context_pipeline/track.rb +2 -15
- data/lib/flows/shared_context_pipeline/track_list.rb +11 -6
- data/lib/flows/shared_context_pipeline/wrap.rb +64 -0
- data/lib/flows/shared_context_pipeline.rb +109 -26
- data/lib/flows/util/inheritable_singleton_vars/dup_strategy.rb +40 -51
- data/lib/flows/util/inheritable_singleton_vars/isolation_strategy.rb +39 -52
- data/lib/flows/util/inheritable_singleton_vars.rb +22 -15
- data/lib/flows/util/prepend_to_class.rb +43 -9
- data/lib/flows/version.rb +1 -1
- metadata +18 -2
@@ -0,0 +1,48 @@
|
|
1
|
+
require_relative 'report/events'
|
2
|
+
require_relative 'report/raw'
|
3
|
+
require_relative 'report/tree'
|
4
|
+
require_relative 'report/flat'
|
5
|
+
|
6
|
+
module Flows
|
7
|
+
module Plugin
|
8
|
+
module Profiler
|
9
|
+
# Base class for {Profiler} reports.
|
10
|
+
#
|
11
|
+
# @!method to_s
|
12
|
+
# @abstract
|
13
|
+
# @return [String] human-readable representation.
|
14
|
+
class Report
|
15
|
+
# @return [Array<Array>] raw profiler events
|
16
|
+
attr_reader :raw_data
|
17
|
+
|
18
|
+
def initialize
|
19
|
+
@raw_data = []
|
20
|
+
end
|
21
|
+
|
22
|
+
# Add event to profile report.
|
23
|
+
#
|
24
|
+
# @param event_type [:started, :finished] event type
|
25
|
+
# @param klass [Class] class where called method is placed
|
26
|
+
# @param method_type [:instance, :singleton] method type
|
27
|
+
# @param method_name [Symbol] name of the called method
|
28
|
+
# @param data [nil, Float] event data, time represented as
|
29
|
+
# a Float microseconds value.
|
30
|
+
def add(*args)
|
31
|
+
raw_data << args
|
32
|
+
end
|
33
|
+
|
34
|
+
# @return [Array<Event>] array of events
|
35
|
+
def events
|
36
|
+
raw_data.map do |raw_event|
|
37
|
+
klass = case raw_event.first
|
38
|
+
when :started then StartEvent
|
39
|
+
when :finished then FinishEvent
|
40
|
+
end
|
41
|
+
|
42
|
+
klass.new(*raw_event[1..-1])
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
@@ -0,0 +1,53 @@
|
|
1
|
+
module Flows
|
2
|
+
module Plugin
|
3
|
+
module Profiler
|
4
|
+
# @api private
|
5
|
+
module Wrapper
|
6
|
+
class << self
|
7
|
+
def make_instance_wrapper(method_name) # rubocop:disable Metrics/MethodLength
|
8
|
+
Module.new.tap do |mod|
|
9
|
+
mod.define_method(method_name) do |*args, &block| # rubocop:disable Metrics/MethodLength
|
10
|
+
thread = Thread.current
|
11
|
+
klass = self.class
|
12
|
+
|
13
|
+
return super(*args, &block) unless thread[THREAD_VAR_FLAG]
|
14
|
+
|
15
|
+
report = thread[THREAD_VAR_REPORT]
|
16
|
+
report.add(:started, klass, :instance, method_name, nil)
|
17
|
+
|
18
|
+
before = Process.clock_gettime(Process::CLOCK_MONOTONIC, :float_microsecond)
|
19
|
+
super(*args, &block)
|
20
|
+
ensure
|
21
|
+
if before
|
22
|
+
after = Process.clock_gettime(Process::CLOCK_MONOTONIC, :float_microsecond)
|
23
|
+
report.add(:finished, klass, :instance, method_name, after - before)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def make_singleton_wrapper(method_name) # rubocop:disable Metrics/MethodLength
|
30
|
+
Module.new.tap do |mod|
|
31
|
+
mod.define_method(method_name) do |*args, &block| # rubocop:disable Metrics/MethodLength
|
32
|
+
thread = Thread.current
|
33
|
+
|
34
|
+
return super(*args, &block) unless thread[THREAD_VAR_FLAG]
|
35
|
+
|
36
|
+
report = thread[THREAD_VAR_REPORT]
|
37
|
+
report.add(:started, self, :singleton, method_name, nil)
|
38
|
+
|
39
|
+
before = Process.clock_gettime(Process::CLOCK_MONOTONIC, :float_microsecond)
|
40
|
+
super(*args, &block)
|
41
|
+
ensure
|
42
|
+
if before
|
43
|
+
after = Process.clock_gettime(Process::CLOCK_MONOTONIC, :float_microsecond)
|
44
|
+
report.add(:finished, self, :singleton, method_name, after - before)
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
@@ -0,0 +1,114 @@
|
|
1
|
+
require_relative 'profiler/report'
|
2
|
+
require_relative 'profiler/injector'
|
3
|
+
require_relative 'profiler/wrapper'
|
4
|
+
|
5
|
+
module Flows
|
6
|
+
module Plugin
|
7
|
+
# Allows to record execution count and time of particular method on class or singleton class.
|
8
|
+
#
|
9
|
+
# Recorded data can be displayed in a different ways.
|
10
|
+
# See {Profiler::Report} implementations for possible options.
|
11
|
+
#
|
12
|
+
# To do a measurement you have call your classes inside {.profile} block.
|
13
|
+
#
|
14
|
+
# @note even without calling {.profile} using this module has some performance
|
15
|
+
# impact. Don't left this module used in production environments.
|
16
|
+
#
|
17
|
+
# @example
|
18
|
+
# class MyClass
|
19
|
+
# CallProfiler = Flows::Plugin::Profiler.for_method(:call)
|
20
|
+
# include CallProfiler
|
21
|
+
#
|
22
|
+
# def call(a, b)
|
23
|
+
# # some work here
|
24
|
+
# end
|
25
|
+
# end
|
26
|
+
#
|
27
|
+
# class AnotherClass
|
28
|
+
# CallProfiler = Flows::Plugin::Profiler.for_method(:perform)
|
29
|
+
# extend CallProfiler
|
30
|
+
#
|
31
|
+
# def self.perform(x)
|
32
|
+
# MyClass.new.call(x, x)
|
33
|
+
# end
|
34
|
+
# end
|
35
|
+
#
|
36
|
+
# last_result = Flows::Plugin::Profiler.profile do
|
37
|
+
# AnotherClass.perform(2)
|
38
|
+
# AnotherClass.perform(6)
|
39
|
+
# end
|
40
|
+
#
|
41
|
+
# Profiler.last_report.to_a
|
42
|
+
# # => [
|
43
|
+
# # [:started, AnotherClass, :singleton, :perform, nil],
|
44
|
+
# # [:started, MyClass, :instance, :call, nil],
|
45
|
+
# # [:finished, MyClass, :instance, :call, 7.3],
|
46
|
+
# # [:finished, AnotherClass, :singleton, :perform, 10.5],
|
47
|
+
# # [:started, AnotherClass, :singleton, :perform, nil],
|
48
|
+
# # [:started, MyClass, :instance, :call, nil],
|
49
|
+
# # [:finished, MyClass, :instance, :call, 8.8],
|
50
|
+
# # [:finished, AnotherClass, :singleton, :perform, 14.2]
|
51
|
+
# # ]
|
52
|
+
module Profiler
|
53
|
+
THREAD_VAR_FLAG = :flows_profiler_flag
|
54
|
+
THREAD_VAR_REPORT = :flows_profiler_report
|
55
|
+
|
56
|
+
class << self
|
57
|
+
# Generates profiler module for a particular method.
|
58
|
+
#
|
59
|
+
# Use `include` for instance methods and `extend` for singleton ones.
|
60
|
+
#
|
61
|
+
# @param method_name [Symbol] method to wrap with profiling.
|
62
|
+
# @return [Module] module to include or extend.
|
63
|
+
def for_method(method_name)
|
64
|
+
Module.new.tap do |mod|
|
65
|
+
injector_mod = Injector.make_module(method_name)
|
66
|
+
mod.const_set(:Injector, injector_mod)
|
67
|
+
mod.extend injector_mod
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
# Profiles a block execution.
|
72
|
+
#
|
73
|
+
# @param report [Report, Symbol]
|
74
|
+
# desired {Report} to be used.
|
75
|
+
# In case of symbol `:some_name` the `Flows::Plugin::Profiler::Report::SomeName.new` will be used.
|
76
|
+
# @yield code to profile
|
77
|
+
# @return block result
|
78
|
+
def profile(report = :raw)
|
79
|
+
thread = Thread.current
|
80
|
+
|
81
|
+
thread[THREAD_VAR_FLAG] = true
|
82
|
+
thread[THREAD_VAR_REPORT] = make_report(report)
|
83
|
+
|
84
|
+
yield
|
85
|
+
ensure
|
86
|
+
thread[THREAD_VAR_FLAG] = false
|
87
|
+
end
|
88
|
+
|
89
|
+
# Resets thread-local variables used for reporting.
|
90
|
+
def reset
|
91
|
+
thread = Thread.current
|
92
|
+
thread[THREAD_VAR_FLAG] = false
|
93
|
+
thread[THREAD_VAR_REPORT] = nil
|
94
|
+
end
|
95
|
+
|
96
|
+
# @return [Report, nil] last generated report if some.
|
97
|
+
def last_report
|
98
|
+
Thread.current[THREAD_VAR_REPORT]
|
99
|
+
end
|
100
|
+
|
101
|
+
private
|
102
|
+
|
103
|
+
def make_report(report_or_sym)
|
104
|
+
case report_or_sym
|
105
|
+
when Report then report_or_sym
|
106
|
+
when Symbol
|
107
|
+
const_name = report_or_sym.to_s.split('_').map(&:capitalize).join
|
108
|
+
Report.const_get(const_name).new
|
109
|
+
end
|
110
|
+
end
|
111
|
+
end
|
112
|
+
end
|
113
|
+
end
|
114
|
+
end
|
data/lib/flows/plugin.rb
CHANGED
data/lib/flows/railway/dsl.rb
CHANGED
@@ -4,11 +4,12 @@ module Flows
|
|
4
4
|
module DSL
|
5
5
|
attr_reader :steps
|
6
6
|
|
7
|
-
Flows::Util::InheritableSingletonVars::DupStrategy.
|
8
|
-
self,
|
7
|
+
SingletonVarsSetup = Flows::Util::InheritableSingletonVars::DupStrategy.make_module(
|
9
8
|
'@steps' => StepList.new
|
10
9
|
)
|
11
10
|
|
11
|
+
include SingletonVarsSetup
|
12
|
+
|
12
13
|
def step(name, lambda = nil)
|
13
14
|
steps.add(name: name, lambda: lambda)
|
14
15
|
end
|
data/lib/flows/result/do.rb
CHANGED
@@ -124,6 +124,12 @@ module Flows
|
|
124
124
|
module Do
|
125
125
|
MOD_VAR_NAME = :@flows_result_module_for_do
|
126
126
|
|
127
|
+
SingletonVarsSetup = ::Flows::Util::InheritableSingletonVars::IsolationStrategy.make_module(
|
128
|
+
MOD_VAR_NAME => -> { Module.new }
|
129
|
+
)
|
130
|
+
|
131
|
+
include SingletonVarsSetup
|
132
|
+
|
127
133
|
# Utility functions for Flows::Result::Do.
|
128
134
|
#
|
129
135
|
# Isolated location prevents polluting user classes with unnecessary methods.
|
@@ -156,14 +162,6 @@ module Flows
|
|
156
162
|
end
|
157
163
|
end
|
158
164
|
|
159
|
-
# @api private
|
160
|
-
def self.extended(mod)
|
161
|
-
::Flows::Util::InheritableSingletonVars::IsolationStrategy.call(
|
162
|
-
mod,
|
163
|
-
MOD_VAR_NAME => -> { Module.new }
|
164
|
-
)
|
165
|
-
end
|
166
|
-
|
167
165
|
def do_notation(method_name)
|
168
166
|
prepended_mod = Util.fetch_and_prepend_module(self)
|
169
167
|
|
@@ -0,0 +1,38 @@
|
|
1
|
+
module Flows
|
2
|
+
class SharedContextPipeline
|
3
|
+
module DSL
|
4
|
+
# @api private
|
5
|
+
module Callbacks
|
6
|
+
SingletonVarsSetup = Flows::Util::InheritableSingletonVars::DupStrategy.make_module(
|
7
|
+
'@before_all_callbacks' => [],
|
8
|
+
'@after_all_callbacks' => [],
|
9
|
+
'@before_each_callbacks' => [],
|
10
|
+
'@after_each_callbacks' => []
|
11
|
+
)
|
12
|
+
|
13
|
+
include SingletonVarsSetup
|
14
|
+
|
15
|
+
attr_reader :before_all_callbacks
|
16
|
+
attr_reader :after_all_callbacks
|
17
|
+
attr_reader :before_each_callbacks
|
18
|
+
attr_reader :after_each_callbacks
|
19
|
+
|
20
|
+
def before_all(&callback)
|
21
|
+
before_all_callbacks << callback
|
22
|
+
end
|
23
|
+
|
24
|
+
def after_all(&callback)
|
25
|
+
after_all_callbacks << callback
|
26
|
+
end
|
27
|
+
|
28
|
+
def before_each(&callback)
|
29
|
+
before_each_callbacks << callback
|
30
|
+
end
|
31
|
+
|
32
|
+
def after_each(&callback)
|
33
|
+
after_each_callbacks << callback
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,52 @@
|
|
1
|
+
module Flows
|
2
|
+
class SharedContextPipeline
|
3
|
+
module DSL
|
4
|
+
# @api private
|
5
|
+
module Tracks
|
6
|
+
DEFAULT_ROUTER_DEF = RouterDefinition.new(
|
7
|
+
Flows::Result::Ok => :next,
|
8
|
+
Flows::Result::Err => :end
|
9
|
+
)
|
10
|
+
|
11
|
+
SingletonVarsSetup = Flows::Util::InheritableSingletonVars::DupStrategy.make_module(
|
12
|
+
'@tracks' => TrackList.new
|
13
|
+
)
|
14
|
+
|
15
|
+
include SingletonVarsSetup
|
16
|
+
|
17
|
+
attr_reader :tracks
|
18
|
+
|
19
|
+
def step(name, router_def = DEFAULT_ROUTER_DEF, &lambda)
|
20
|
+
tracks.add_step(
|
21
|
+
Step.new(name: name, lambda: lambda, router_def: router_def)
|
22
|
+
)
|
23
|
+
end
|
24
|
+
|
25
|
+
def mut_step(name, router_def = DEFAULT_ROUTER_DEF, &lambda)
|
26
|
+
tracks.add_step(
|
27
|
+
MutationStep.new(name: name, lambda: lambda, router_def: router_def)
|
28
|
+
)
|
29
|
+
end
|
30
|
+
|
31
|
+
def wrap(name, router_def = DEFAULT_ROUTER_DEF, &tracks_definitions)
|
32
|
+
tracks.add_step(
|
33
|
+
Wrap.new(method_name: name, router_def: router_def, &tracks_definitions)
|
34
|
+
)
|
35
|
+
end
|
36
|
+
|
37
|
+
def track(name, &block)
|
38
|
+
track_before = tracks.current_track
|
39
|
+
|
40
|
+
tracks.switch_track(name)
|
41
|
+
instance_exec(&block)
|
42
|
+
tracks.switch_track(track_before)
|
43
|
+
end
|
44
|
+
|
45
|
+
# :reek:UtilityFunction is allowed here
|
46
|
+
def routes(routes_def)
|
47
|
+
RouterDefinition.new(routes_def)
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
@@ -1,63 +1,12 @@
|
|
1
|
+
require_relative 'dsl/tracks'
|
2
|
+
require_relative 'dsl/callbacks'
|
3
|
+
|
1
4
|
module Flows
|
2
5
|
class SharedContextPipeline
|
3
6
|
# @api private
|
4
7
|
module DSL
|
5
|
-
|
6
|
-
|
7
|
-
attr_reader :after_all_callbacks
|
8
|
-
attr_reader :before_each_callbacks
|
9
|
-
attr_reader :after_each_callbacks
|
10
|
-
|
11
|
-
DEFAULT_ROUTER_DEF = RouterDefinition.new(
|
12
|
-
Flows::Result::Ok => :next,
|
13
|
-
Flows::Result::Err => :end
|
14
|
-
)
|
15
|
-
|
16
|
-
Flows::Util::InheritableSingletonVars::DupStrategy.call(
|
17
|
-
self,
|
18
|
-
'@tracks' => TrackList.new,
|
19
|
-
'@before_all_callbacks' => [],
|
20
|
-
'@after_all_callbacks' => [],
|
21
|
-
'@before_each_callbacks' => [],
|
22
|
-
'@after_each_callbacks' => []
|
23
|
-
)
|
24
|
-
|
25
|
-
def step(name, router_def = DEFAULT_ROUTER_DEF, &lambda)
|
26
|
-
tracks.add_step(name: name, lambda: lambda, router_def: router_def)
|
27
|
-
end
|
28
|
-
|
29
|
-
def mut_step(name, router_def = DEFAULT_ROUTER_DEF, &lambda)
|
30
|
-
tracks.add_mutation_step(name: name, lambda: lambda, router_def: router_def)
|
31
|
-
end
|
32
|
-
|
33
|
-
def track(name, &block)
|
34
|
-
track_before = tracks.current_track
|
35
|
-
|
36
|
-
tracks.switch_track(name)
|
37
|
-
instance_exec(&block)
|
38
|
-
tracks.switch_track(track_before)
|
39
|
-
end
|
40
|
-
|
41
|
-
# :reek:UtilityFunction is allowed here
|
42
|
-
def routes(routes_def)
|
43
|
-
RouterDefinition.new(routes_def)
|
44
|
-
end
|
45
|
-
|
46
|
-
def before_all(&callback)
|
47
|
-
before_all_callbacks << callback
|
48
|
-
end
|
49
|
-
|
50
|
-
def after_all(&callback)
|
51
|
-
after_all_callbacks << callback
|
52
|
-
end
|
53
|
-
|
54
|
-
def before_each(&callback)
|
55
|
-
before_each_callbacks << callback
|
56
|
-
end
|
57
|
-
|
58
|
-
def after_each(&callback)
|
59
|
-
after_each_callbacks << callback
|
60
|
-
end
|
8
|
+
include Tracks
|
9
|
+
include Callbacks
|
61
10
|
end
|
62
11
|
end
|
63
12
|
end
|
@@ -1,28 +1,26 @@
|
|
1
1
|
module Flows
|
2
2
|
class SharedContextPipeline
|
3
3
|
EMPTY_HASH = {}.freeze
|
4
|
-
EMPTY_OK = Flows::Result::Ok.new(
|
5
|
-
EMPTY_ERR = Flows::Result::Err.new(
|
4
|
+
EMPTY_OK = Flows::Result::Ok.new({}.freeze).freeze
|
5
|
+
EMPTY_ERR = Flows::Result::Err.new({}.freeze).freeze
|
6
6
|
|
7
7
|
# @api private
|
8
8
|
class MutationStep < Step
|
9
|
-
NODE_PREPROCESSOR = lambda do |_input, context,
|
10
|
-
context[:last_step] = meta[:name]
|
11
|
-
|
9
|
+
NODE_PREPROCESSOR = lambda do |_input, context, node_meta|
|
12
10
|
context[:class].before_each_callbacks.each do |callback|
|
13
|
-
callback.call(context[:class],
|
11
|
+
callback.call(context[:class], node_meta[:name], context[:data], context[:meta])
|
14
12
|
end
|
15
13
|
|
16
14
|
[[context[:data]], EMPTY_HASH]
|
17
15
|
end
|
18
16
|
|
19
|
-
NODE_POSTPROCESSOR = lambda do |output, context,
|
17
|
+
NODE_POSTPROCESSOR = lambda do |output, context, node_meta|
|
20
18
|
case output
|
21
19
|
when Flows::Result then output
|
22
20
|
else output ? EMPTY_OK : EMPTY_ERR
|
23
21
|
end.tap do |result|
|
24
22
|
context[:class].after_each_callbacks.each do |callback|
|
25
|
-
callback.call(context[:class],
|
23
|
+
callback.call(context[:class], node_meta[:name], result, context[:data], context[:meta])
|
26
24
|
end
|
27
25
|
end
|
28
26
|
end
|