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.
Files changed (51) hide show
  1. checksums.yaml +4 -4
  2. data/.mdlrc +1 -1
  3. data/.reek.yml +12 -0
  4. data/CHANGELOG.md +16 -0
  5. data/Gemfile.lock +1 -1
  6. data/README.md +12 -2
  7. data/Rakefile +1 -1
  8. data/bin/all_the_errors +8 -0
  9. data/bin/errors +12 -0
  10. data/bin/errors_cli/flow_error_demo.rb +22 -0
  11. data/docs/README.md +1 -1
  12. data/lib/flows/contract/case_eq.rb +3 -1
  13. data/lib/flows/flow/errors.rb +29 -0
  14. data/lib/flows/flow/node.rb +1 -0
  15. data/lib/flows/flow/router/custom.rb +5 -0
  16. data/lib/flows/flow/router/simple.rb +5 -0
  17. data/lib/flows/flow/router.rb +4 -0
  18. data/lib/flows/flow.rb +21 -0
  19. data/lib/flows/plugin/dependency_injector.rb +5 -5
  20. data/lib/flows/plugin/output_contract/dsl.rb +15 -3
  21. data/lib/flows/plugin/output_contract/wrapper.rb +14 -12
  22. data/lib/flows/plugin/output_contract.rb +1 -0
  23. data/lib/flows/plugin/profiler/injector.rb +35 -0
  24. data/lib/flows/plugin/profiler/report/events.rb +43 -0
  25. data/lib/flows/plugin/profiler/report/flat/method_report.rb +81 -0
  26. data/lib/flows/plugin/profiler/report/flat.rb +41 -0
  27. data/lib/flows/plugin/profiler/report/raw.rb +15 -0
  28. data/lib/flows/plugin/profiler/report/tree/calculated_node.rb +116 -0
  29. data/lib/flows/plugin/profiler/report/tree/node.rb +35 -0
  30. data/lib/flows/plugin/profiler/report/tree.rb +98 -0
  31. data/lib/flows/plugin/profiler/report.rb +48 -0
  32. data/lib/flows/plugin/profiler/wrapper.rb +53 -0
  33. data/lib/flows/plugin/profiler.rb +114 -0
  34. data/lib/flows/plugin.rb +1 -0
  35. data/lib/flows/railway/dsl.rb +3 -2
  36. data/lib/flows/result/do.rb +6 -8
  37. data/lib/flows/shared_context_pipeline/dsl/callbacks.rb +38 -0
  38. data/lib/flows/shared_context_pipeline/dsl/tracks.rb +52 -0
  39. data/lib/flows/shared_context_pipeline/dsl.rb +5 -56
  40. data/lib/flows/shared_context_pipeline/mutation_step.rb +6 -8
  41. data/lib/flows/shared_context_pipeline/step.rb +6 -8
  42. data/lib/flows/shared_context_pipeline/track.rb +2 -15
  43. data/lib/flows/shared_context_pipeline/track_list.rb +11 -6
  44. data/lib/flows/shared_context_pipeline/wrap.rb +64 -0
  45. data/lib/flows/shared_context_pipeline.rb +109 -26
  46. data/lib/flows/util/inheritable_singleton_vars/dup_strategy.rb +40 -51
  47. data/lib/flows/util/inheritable_singleton_vars/isolation_strategy.rb +39 -52
  48. data/lib/flows/util/inheritable_singleton_vars.rb +22 -15
  49. data/lib/flows/util/prepend_to_class.rb +43 -9
  50. data/lib/flows/version.rb +1 -1
  51. 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
@@ -11,3 +11,4 @@ end
11
11
  require_relative 'plugin/dependency_injector'
12
12
  require_relative 'plugin/implicit_init'
13
13
  require_relative 'plugin/output_contract'
14
+ require_relative 'plugin/profiler'
@@ -4,11 +4,12 @@ module Flows
4
4
  module DSL
5
5
  attr_reader :steps
6
6
 
7
- Flows::Util::InheritableSingletonVars::DupStrategy.call(
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
@@ -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
- attr_reader :tracks
6
- attr_reader :before_all_callbacks
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(nil).freeze
5
- EMPTY_ERR = Flows::Result::Err.new(nil).freeze
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, meta|
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], meta[:name], context[:data])
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, meta|
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], meta[:name], context[:data], result)
23
+ callback.call(context[:class], node_meta[:name], result, context[:data], context[:meta])
26
24
  end
27
25
  end
28
26
  end