trailblazer-developer 0.0.26 → 0.0.28

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.
@@ -1,16 +1,12 @@
1
- require 'trailblazer/activity'
2
-
3
1
  module Trailblazer::Developer
4
2
  module Trace
5
3
 
6
- Activity = Trailblazer::Activity
7
-
8
4
  class << self
9
5
  # Public entry point to activate tracing when running {activity}.
10
6
  def call(activity, (ctx, flow_options), **circuit_options)
11
7
  activity, (ctx, flow_options), circuit_options = Trace.arguments_for_call( activity, [ctx, flow_options], **circuit_options ) # only run once for the entire circuit!
12
8
 
13
- signal, (ctx, flow_options) = Activity::TaskWrap.invoke(activity, [ctx, flow_options], **circuit_options)
9
+ signal, (ctx, flow_options) = Trailblazer::Activity::TaskWrap.invoke(activity, [ctx, flow_options], **circuit_options)
14
10
 
15
11
  return flow_options[:stack], signal, [ctx, flow_options]
16
12
  end
@@ -19,21 +15,20 @@ module Trailblazer::Developer
19
15
 
20
16
  def arguments_for_call(activity, (options, original_flow_options), **original_circuit_options)
21
17
  default_flow_options = {
22
- stack: Trace::Stack.new,
23
-
24
- input_data_collector: Trace.method(:default_input_data_collector),
25
- output_data_collector: Trace.method(:default_output_data_collector),
18
+ stack: Trace::Stack.new,
19
+ input_data_collector: Trace.method(:default_input_data_collector),
20
+ output_data_collector: Trace.method(:default_output_data_collector),
26
21
  }
27
22
 
28
- flow_options = { **default_flow_options, **Hash( original_flow_options ) }
23
+ flow_options = {**default_flow_options, **Hash(original_flow_options)}
29
24
 
30
25
  default_circuit_options = {
31
- wrap_runtime: ::Hash.new(Trace.merge_plan), # DISCUSS: this overrides existing {:wrap_runtime}.
26
+ wrap_runtime: ::Hash.new(Trace.task_wrap_extensions), # DISCUSS: this overrides existing {:wrap_runtime}.
32
27
  }
33
28
 
34
- circuit_options = { **original_circuit_options, **default_circuit_options }
29
+ circuit_options = {**original_circuit_options, **default_circuit_options}
35
30
 
36
- return activity, [ options, flow_options ], circuit_options
31
+ return activity, [options, flow_options], circuit_options
37
32
  end
38
33
  end
39
34
 
@@ -42,16 +37,10 @@ module Trailblazer::Developer
42
37
  # and before the TaskWrap is finished.
43
38
  #
44
39
  # @private
45
- def merge_plan
46
- Activity::TaskWrap::Extension.new(
47
- {
48
- insert: [Activity::Adds::Insert.method(:Prepend), "task_wrap.call_task"],
49
- row: Activity::TaskWrap::Pipeline.Row("task_wrap.capture_args", Trace.method(:capture_args))
50
- },
51
- {
52
- insert: [Activity::Adds::Insert.method(:Append)], # append to the very end of tW.
53
- row: Activity::TaskWrap::Pipeline.Row("task_wrap.capture_return", Trace.method(:capture_return))
54
- },
40
+ def task_wrap_extensions
41
+ Trailblazer::Activity::TaskWrap.Extension(
42
+ [Trace.method(:capture_args), id: "task_wrap.capture_args", prepend: "task_wrap.call_task"],
43
+ [Trace.method(:capture_return), id: "task_wrap.capture_return", append: nil], # append to the very end of tW.
55
44
  )
56
45
  end
57
46
 
@@ -60,112 +49,62 @@ module Trailblazer::Developer
60
49
  # us a better way.
61
50
  # taskWrap step to capture incoming arguments of a step.
62
51
  def capture_args(wrap_config, ((ctx, flow), circuit_options))
63
- flow[:stack].indent!
52
+ original_args = [[ctx, flow], circuit_options]
64
53
 
65
- flow[:stack] << Entity::Input.new(
66
- wrap_config[:task],
67
- circuit_options[:activity],
68
- flow[:input_data_collector].call(wrap_config, [ctx, flow], circuit_options)
69
- ).freeze
54
+ captured_input = Captured(Captured::Input, flow[:input_data_collector], wrap_config, original_args)
55
+
56
+ flow[:stack] << captured_input
70
57
 
71
- return wrap_config, [[ctx, flow], circuit_options]
58
+ return wrap_config, original_args
72
59
  end
73
60
 
74
61
  # taskWrap step to capture outgoing arguments from a step.
75
62
  def capture_return(wrap_config, ((ctx, flow), circuit_options))
76
- flow[:stack] << Entity::Output.new(
77
- wrap_config[:task],
78
- {},
79
- flow[:output_data_collector].call(wrap_config, [ctx, flow], circuit_options)
80
- ).freeze
63
+ original_args = [[ctx, flow], circuit_options]
81
64
 
82
- flow[:stack].unindent!
65
+ captured_output = Captured(Captured::Output, flow[:output_data_collector], wrap_config, original_args)
83
66
 
84
- return wrap_config, [[ctx, flow], circuit_options]
85
- end
86
-
87
- def default_input_data_collector(wrap_config, (ctx, _), circuit_options)
88
- graph = Trailblazer::Activity::Introspect::Graph(circuit_options[:activity])
89
- task = wrap_config[:task]
90
- name = (node = graph.find { |node| node[:task] == task }) ? node[:id] : task
91
-
92
- { ctx: ctx, task_name: name }
93
- end
67
+ flow[:stack] << captured_output
94
68
 
95
- def default_output_data_collector(wrap_config, (ctx, _), _)
96
- { ctx: ctx, signal: wrap_config[:return_signal] }
69
+ return wrap_config, original_args
97
70
  end
98
71
 
99
- # Structures used in {capture_args} and {capture_return}.
100
- # These get pushed onto one {Level} in a {Stack}.
101
- #
102
- # Level[
103
- # Level[ ==> this is a scalar task
104
- # Entity::Input
105
- # Entity::Output
106
- # ]
107
- # Level[ ==> nested task
108
- # Entity::Input
109
- # Level[
110
- # Entity::Input
111
- # Entity::Output
112
- # ]
113
- # Entity::Output
114
- # ]
115
- # ]
116
- Entity = Struct.new(:task, :activity, :data)
117
- Entity::Input = Class.new(Entity)
118
- Entity::Output = Class.new(Entity)
119
-
120
- class Level < Array
121
- def inspect
122
- %{<Level>#{super}}
123
- end
124
-
125
- # @param level {Trace::Level}
126
- def self.input_output_nested_for_level(level)
127
- input = level[0]
128
- output = level[-1]
129
-
130
- output, nested = output.is_a?(Entity::Output) ? [output, level-[input, output]] : [nil, level[1..-1]]
72
+ def Captured(captured_class, data_collector, wrap_config, ((ctx, flow), circuit_options))
73
+ collected_data = data_collector.call(wrap_config, [[ctx, flow], circuit_options])
131
74
 
132
- return input, output, nested
133
- end
75
+ captured_class.new( # either Input or Output
76
+ wrap_config[:task],
77
+ circuit_options[:activity],
78
+ collected_data
79
+ ).freeze
134
80
  end
135
81
 
136
- # Mutable/stateful per design. We want a (global) stack!
137
- class Stack
138
- attr_reader :top
139
-
140
- def initialize
141
- @nested = Level.new
142
- @stack = [ @nested ]
143
- end
144
-
145
- def indent!
146
- current << indented = Level.new
147
- @stack << indented
148
- end
149
-
150
- def unindent!
151
- @stack.pop
152
- end
153
-
154
- def <<(entity)
155
- @top = entity
82
+ # Called in {#Captured}.
83
+ # DISCUSS: this is where to start for a new {Inspector} implementation.
84
+ def default_input_data_collector(wrap_config, ((ctx, _), _)) # DISCUSS: would it be faster to access ctx via {original_args[0][0]}?
85
+ # mutable, old_ctx = ctx.decompose
86
+ # mutable, old_ctx = ctx, nil
156
87
 
157
- current << entity
158
- end
88
+ {
89
+ # ctx: ctx.to_h.freeze,
90
+ ctx_snapshot: ctx.to_h.collect { |k,v| [k, v.inspect] }.to_h,
91
+ } # TODO: proper snapshot!
92
+ end
159
93
 
160
- def to_a
161
- @nested
162
- end
94
+ # Called in {#Captured}.
95
+ def default_output_data_collector(wrap_config, ((ctx, _), _))
96
+ returned_ctx, _ = wrap_config[:return_args]
163
97
 
164
- private
98
+ # FIXME: snapshot!
99
+ {
100
+ # ctx: ctx.to_h.freeze,
101
+ ctx_snapshot: returned_ctx.to_h.collect { |k,v| [k, v.inspect] }.to_h,
102
+ signal: wrap_config[:return_signal]
103
+ }
104
+ end
165
105
 
166
- def current
167
- @stack.last
168
- end
169
- end # Stack
106
+ Captured = Struct.new(:task, :activity, :data)
107
+ Captured::Input = Class.new(Captured)
108
+ Captured::Output = Class.new(Captured)
170
109
  end
171
110
  end
@@ -1,7 +1,7 @@
1
1
  module Trailblazer
2
2
  module Version
3
3
  module Developer
4
- VERSION = "0.0.26"
4
+ VERSION = "0.0.28"
5
5
  end
6
6
  end
7
7
  end
@@ -1,4 +1,3 @@
1
-
2
1
  module Trailblazer::Developer
3
2
  module Wtf
4
3
 
@@ -15,24 +14,24 @@ module Trailblazer::Developer
15
14
 
16
15
  module_function
17
16
 
18
- # tree: Array of Trace::TreeNodes::Node
19
- # task_node - current Trace::TreeNodes::Node to render
20
- # position - task_node's position in tree
21
- def call(tree:, task_node:, position:)
22
- value = value_for(tree, task_node, position)
23
- [task_node.level, value]
17
+ # {options} can be {style: {#<Captured::Input> => [:red, :bold]}}
18
+ def call(tree:, debugger_node:, style: {}, **options)
19
+ label = styled_label(tree, debugger_node, style: style, **options)
20
+
21
+ [debugger_node.level, label]
24
22
  end
25
23
 
26
- def value_for(tree, task_node, position)
27
- if task_node.output.nil? && tree[position.next].nil? # i.e. when exception raised
28
- return %{#{fmt(fmt(task_node.value, :red), :bold)}}
29
- end
24
+ def styled_label(tree, debugger_node, color_map:, **options)
25
+ label = apply_style(debugger_node.label, debugger_node, **options)
30
26
 
31
- if task_node.output.nil? # i.e. on entry/exit point of activity
32
- return %{#{task_node.value}}
33
- end
27
+ %{#{fmt(label, color_map[ signal_of(debugger_node) ])}}
28
+ end
29
+
30
+ def apply_style(label, debugger_node, style:, **)
31
+ return label unless styles = style[debugger_node.captured_input]
34
32
 
35
- %{#{fmt(task_node.value, task_node.color_map[ signal_of(task_node) ])}}
33
+ styles.each { |s| label = fmt(label, s) }
34
+ label
36
35
  end
37
36
 
38
37
  def fmt(line, style)
@@ -44,7 +43,7 @@ module Trailblazer::Developer
44
43
  end
45
44
 
46
45
  def signal_of(task_node)
47
- entity_signal = task_node.output.data[:signal]
46
+ entity_signal = task_node.captured_output.data[:signal]
48
47
  entity_klass = entity_signal.is_a?(Class) ? entity_signal : entity_signal.class
49
48
 
50
49
  SIGNALS_MAP[entity_klass.name.to_sym]
@@ -14,77 +14,64 @@ module Trailblazer::Developer
14
14
 
15
15
  # Run {activity} with tracing enabled and inject a mutable {Stack} instance.
16
16
  # This allows to display the trace even when an exception happened
17
- def invoke(activity, (ctx, flow_options), **circuit_options)
18
- activity, (ctx, flow_options), circuit_options = Wtf.arguments_for_trace(
19
- activity, [ctx, flow_options], **circuit_options
20
- )
17
+ def invoke(activity, (ctx, flow_options), present_options: {}, **circuit_options)
18
+ flow_options ||= {}
21
19
 
22
- _returned_stack, signal, (ctx, flow_options) = Trace.invoke(
23
- activity, [ctx, flow_options], **circuit_options
24
- )
20
+ stack = Trace::Stack.new # unfortunately, we need this mutable object before things break.
25
21
 
26
- return signal, [ctx, flow_options], circuit_options
27
- ensure
28
- puts Trace::Present.(
29
- flow_options[:stack],
30
- renderer: Wtf::Renderer,
31
- color_map: Wtf::Renderer::DEFAULT_COLOR_MAP.merge( flow_options[:color_map] || {} ),
22
+ complete_stack, signal, (ctx, flow_options) = Trace.invoke(
23
+ activity,
24
+ [ctx, flow_options.merge(stack: stack)],
25
+ **circuit_options
32
26
  )
33
- end
34
-
35
- def arguments_for_trace(activity, (ctx, original_flow_options), **circuit_options)
36
- default_flow_options = {
37
- # this instance gets mutated with every step. unfortunately, there is
38
- # no other way in Ruby to keep the trace even when an exception was thrown.
39
- stack: Trace::Stack.new,
40
-
41
- input_data_collector: method(:trace_input_data_collector),
42
- output_data_collector: method(:trace_output_data_collector),
43
- }
44
27
 
45
- # Merge default options with flow_options as an order of precedence
46
- flow_options = { **default_flow_options, **Hash( original_flow_options ) }
47
-
48
- # Normalize `focus_on` param to
49
- # 1. Wrap step and variable names into an array if not already
50
- flow_options[:focus_on] = {
51
- steps: Array( flow_options.dig(:focus_on, :steps) ),
52
- variables: Array( flow_options.dig(:focus_on, :variables) ),
53
- }
28
+ return signal, [ctx, flow_options], circuit_options
54
29
 
55
- [activity, [ ctx, flow_options ], circuit_options]
56
- end
30
+ ensure
31
+ # incomplete_stack = flow_options[:stack]
32
+ incomplete_stack = stack
57
33
 
58
- # Overring default input and output data collectors to collect/capture
59
- # 1. inspect of focusable variables for given focusable step
60
- # 2. Or inspect of focused variables for all steps
61
- def trace_input_data_collector(wrap_config, (ctx, flow_options), circuit_options)
62
- data = Trace.default_input_data_collector(wrap_config, [ctx, flow_options], circuit_options)
34
+ # in 99%, exception_source is a {Captured::Input}.
35
+ exception_source = incomplete_stack.to_a.last # DISCUSS: in most cases, this is where the problem has happened.
36
+ # However, what if an error happens in, say, an input filter? TODO: test this
63
37
 
64
- if Wtf.capture_variables?(step_name: data[:task_name], **flow_options)
65
- data[:focused_variables] = Trace::Focusable.capture_variables_from(ctx, **flow_options)
66
- end
38
+ complete_stack = Exception::Stack.complete(incomplete_stack) # TODO: only in case of exception!
67
39
 
68
- data
40
+ puts Trace::Present.(
41
+ complete_stack,
42
+ # we can hand in options per node, identified by their captured_input part.
43
+ node_options: {
44
+ exception_source => {data: {exception_source: true}}, # goes to {Debugger::Node.build}
45
+ },
46
+
47
+ renderer: Wtf::Renderer,
48
+ color_map: Wtf::Renderer::DEFAULT_COLOR_MAP.merge( flow_options[:color_map] || {} ),
49
+ style: {exception_source => [:red, :bold]},
50
+ **present_options, # TODO: test.
51
+ )
69
52
  end
70
53
 
71
- def trace_output_data_collector(wrap_config, (ctx, flow_options), circuit_options)
72
- data = Trace.default_output_data_collector(wrap_config, [ctx, flow_options], circuit_options)
73
- input = flow_options[:stack].top
54
+ module Exception
55
+ # When an exception occurs the Stack instance is incomplete - it is missing Captured::Output instances
56
+ # for Inputs still open. This method adds the missing elements so the Trace::Tree algorithm doesn't crash.
57
+ module Stack
58
+ def self.complete(incomplete_stack)
59
+ processed = []
74
60
 
75
- if Wtf.capture_variables?(step_name: input.data[:task_name], **flow_options)
76
- data[:focused_variables] = Trace::Focusable.capture_variables_from(ctx, **flow_options)
77
- end
61
+ incomplete_stack.to_a.each do |captured|
62
+ if captured.is_a?(Trace::Captured::Input)
63
+ processed << captured
64
+ else
65
+ processed.pop
66
+ end
67
+ end
78
68
 
79
- data
80
- end
69
+ missing_captured = processed.reverse.collect { |captured| Trace::Captured::Output.new(captured.task, captured.activity, {}) }
81
70
 
82
- # private
83
- def capture_variables?(step_name:, focus_on:, **)
84
- return true if focus_on[:steps].include?(step_name) # For given step
85
- return true if focus_on[:steps].empty? && focus_on[:variables].any? # For selected vars but all steps
71
+ Trace::Stack.new(incomplete_stack.to_a + missing_captured)
72
+ end
73
+ end # Stack
86
74
 
87
- false
88
75
  end
89
76
  end
90
77
  end
@@ -6,11 +6,17 @@ module Trailblazer
6
6
  end
7
7
  end
8
8
 
9
+ require "trailblazer/activity"
9
10
  require "trailblazer/developer/wtf"
10
11
  require "trailblazer/developer/wtf/renderer"
11
12
  require "trailblazer/developer/trace"
13
+ require "trailblazer/developer/trace/stack"
14
+ require "trailblazer/developer/trace/tree"
12
15
  require "trailblazer/developer/trace/present"
13
- require "trailblazer/developer/trace/focusable"
14
- require "trailblazer/developer/trace/inspector"
16
+ require "trailblazer/developer/trace/debugger"
15
17
  require "trailblazer/developer/render/circuit"
16
18
  require "trailblazer/developer/render/linear"
19
+ require "trailblazer/developer/render/task_wrap"
20
+ require "trailblazer/developer/introspect" # TODO: might get removed, again.
21
+ require "trailblazer/developer/trace/debugger/normalizer"
22
+ require "trailblazer/developer/introspect/graph"
@@ -10,7 +10,7 @@ Gem::Specification.new do |spec|
10
10
 
11
11
  spec.summary = "Developer tools for Trailblazer."
12
12
  spec.description = "Developer tools for Trailblazer: debugger, activity visualizer and tracing."
13
- spec.homepage = "http://trailblazer.to"
13
+ spec.homepage = "http://trailblazer.to/2.1/docs/trailblazer.html#trailblazer-developer"
14
14
  spec.license = "LGPL-3.0"
15
15
 
16
16
  spec.files = `git ls-files -z`.split("\x0").reject do |f|
@@ -22,7 +22,8 @@ Gem::Specification.new do |spec|
22
22
  spec.add_development_dependency "minitest"
23
23
  spec.add_development_dependency "minitest-line"
24
24
  spec.add_development_dependency "rake"
25
+ spec.add_development_dependency "trailblazer-operation", ">= 0.10.0"
25
26
 
26
- spec.add_dependency "trailblazer-activity-dsl-linear", ">= 1.0.0.beta1", "< 1.1.0"
27
+ spec.add_dependency "trailblazer-activity-dsl-linear", ">= 1.2.0", "< 1.3.0"
27
28
  spec.add_dependency "hirb"
28
29
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: trailblazer-developer
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.26
4
+ version: 0.0.28
5
5
  platform: ruby
6
6
  authors:
7
7
  - Nick Sutterer
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2022-07-19 00:00:00.000000000 Z
11
+ date: 2023-03-02 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -66,26 +66,40 @@ dependencies:
66
66
  - - ">="
67
67
  - !ruby/object:Gem::Version
68
68
  version: '0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: trailblazer-operation
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - ">="
74
+ - !ruby/object:Gem::Version
75
+ version: 0.10.0
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - ">="
81
+ - !ruby/object:Gem::Version
82
+ version: 0.10.0
69
83
  - !ruby/object:Gem::Dependency
70
84
  name: trailblazer-activity-dsl-linear
71
85
  requirement: !ruby/object:Gem::Requirement
72
86
  requirements:
73
87
  - - ">="
74
88
  - !ruby/object:Gem::Version
75
- version: 1.0.0.beta1
89
+ version: 1.2.0
76
90
  - - "<"
77
91
  - !ruby/object:Gem::Version
78
- version: 1.1.0
92
+ version: 1.3.0
79
93
  type: :runtime
80
94
  prerelease: false
81
95
  version_requirements: !ruby/object:Gem::Requirement
82
96
  requirements:
83
97
  - - ">="
84
98
  - !ruby/object:Gem::Version
85
- version: 1.0.0.beta1
99
+ version: 1.2.0
86
100
  - - "<"
87
101
  - !ruby/object:Gem::Version
88
- version: 1.1.0
102
+ version: 1.3.0
89
103
  - !ruby/object:Gem::Dependency
90
104
  name: hirb
91
105
  requirement: !ruby/object:Gem::Requirement
@@ -116,19 +130,23 @@ files:
116
130
  - Rakefile
117
131
  - bin/console
118
132
  - bin/setup
119
- - gems.local.rb
120
133
  - lib/trailblazer/developer.rb
134
+ - lib/trailblazer/developer/introspect.rb
135
+ - lib/trailblazer/developer/introspect/graph.rb
121
136
  - lib/trailblazer/developer/render/circuit.rb
122
137
  - lib/trailblazer/developer/render/linear.rb
138
+ - lib/trailblazer/developer/render/task_wrap.rb
123
139
  - lib/trailblazer/developer/trace.rb
124
- - lib/trailblazer/developer/trace/focusable.rb
125
- - lib/trailblazer/developer/trace/inspector.rb
140
+ - lib/trailblazer/developer/trace/debugger.rb
141
+ - lib/trailblazer/developer/trace/debugger/normalizer.rb
126
142
  - lib/trailblazer/developer/trace/present.rb
143
+ - lib/trailblazer/developer/trace/stack.rb
144
+ - lib/trailblazer/developer/trace/tree.rb
127
145
  - lib/trailblazer/developer/version.rb
128
146
  - lib/trailblazer/developer/wtf.rb
129
147
  - lib/trailblazer/developer/wtf/renderer.rb
130
148
  - trailblazer-developer.gemspec
131
- homepage: http://trailblazer.to
149
+ homepage: http://trailblazer.to/2.1/docs/trailblazer.html#trailblazer-developer
132
150
  licenses:
133
151
  - LGPL-3.0
134
152
  metadata: {}
data/gems.local.rb DELETED
@@ -1,11 +0,0 @@
1
- source "https://rubygems.org"
2
-
3
- # Specify your gem's dependencies in trailblazer-developer.gemspec
4
- gemspec
5
-
6
- gem "trailblazer-activity", path: "../trailblazer-activity"
7
- gem "trailblazer-activity-dsl-linear", path: "../trailblazer-activity-dsl-linear"
8
- gem "representable"
9
- gem 'pry-byebug'
10
- gem "faraday"
11
- gem "multi_json"
@@ -1,78 +0,0 @@
1
- module Trailblazer
2
- module Developer
3
- module Trace
4
-
5
- module Focusable
6
- module_function
7
-
8
- # Get inspect of {focus_on.variables} or current {ctx}
9
- def capture_variables_from(ctx, focus_on:, inspector: Trace::Inspector, **flow_options)
10
- # ctx keys to be captured, for example [:current_user, :model, ....]
11
- variables = (selected = focus_on[:variables]).any? ? selected : ctx.keys
12
-
13
- variables.each_with_object({}) do |variable, result|
14
- if variable.is_a?(Proc) # To allow deep key access from ctx
15
- result[:Custom] = inspector.(variable.call(ctx), **flow_options)
16
- else
17
- result[variable] = inspector.(ctx[variable], **flow_options)
18
- end
19
- end
20
- end
21
-
22
- # Generate Hirb's vertical table nodes from captured ctx of each step
23
- # |-- some step name
24
- # | |-- ********** Input **********
25
- # message: "WTF!"
26
- # seq: []
27
- # | `-- ********** Output **********
28
- # message: "WTF!"
29
- # seq: [:a]
30
- def tree_nodes_for(level, input:, output:, **options)
31
- # TODO: Reverting `Hash#compact` usage as it is not supported in Ruby <= 2.4
32
- # Once the support is droped, revert actual code with below and remove entity check.
33
- # input_output_nodes = { Input: input, Output: output }.compact.collect do |table_header, entity|
34
-
35
- input_output_nodes = { Input: input, Output: output }.collect do |table_header, entity|
36
- next unless entity
37
- next unless Array( entity.data[:focused_variables] ).any?
38
-
39
- table = vertical_table_for(entity.data[:focused_variables], table_header: table_header)
40
- Present::TreeNodes::Node.new(level + 1, table, input, output, options).freeze
41
- end
42
-
43
- input_output_nodes.compact
44
- end
45
-
46
- # @private
47
- def vertical_table_for(focused_variables, table_header:)
48
- patched_vertical_table.render(
49
- Array[ focused_variables ],
50
- description: nil,
51
- table_header: table_header, # Custom option, not from Hirb
52
- )
53
- end
54
-
55
- # Overrding `Hirb::Helpers::VerticalTable#render_rows` because there is no option
56
- # to customize vertical table's row header :(
57
- # We need it to print if given entity is Input/Output
58
- #
59
- # @private
60
- def patched_vertical_table
61
- table = Class.new(Hirb::Helpers::VerticalTable)
62
-
63
- table.send(:define_method, :render_rows) do
64
- longest_header = Hirb::String.size (@headers.values.sort_by {|e| Hirb::String.size(e) }.last || '')
65
- stars = "*" * [(longest_header + (longest_header / 2)), 3].max
66
-
67
- @rows.map do |row|
68
- "#{stars} #{@options[:table_header]} #{stars}\n" +
69
- @fields.map{ |f| "#{Hirb::String.rjust(@headers[f], longest_header)}: #{row[f]}" }.join("\n")
70
- end
71
- end
72
-
73
- table
74
- end
75
- end
76
- end
77
- end
78
- end