trailblazer-developer 0.0.25 → 0.0.27

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 0e213c664a285173ad64b7236f17a026dda9159c7e53c4e595f5c9b36a3bcd15
4
- data.tar.gz: 2beca5ea9fb78c56263302c7dee6a5566e798b5a40db737283a63dcb5a35295c
3
+ metadata.gz: 45cd9fac3c0701cd0e98573e27c59c80de31d5d91b306980c961d58f64161d71
4
+ data.tar.gz: 745d4d28834261571fdbccc9949b597bb1f17f669bc238a6bbc09925001d4594
5
5
  SHA512:
6
- metadata.gz: ff33e213922bb017b4b9b6ba4b9a26cd7fb359437a03ed20902c9667cd593a026763253880fa43f9327858f0f871b95e3919e1247f49d04446d6c0fd462a490d
7
- data.tar.gz: 9386d74ddd3f3fcfaa30f74b8a3fb7ed1aad1d692e6f19b28f8555e115dbae6e2c4a765f30d887bae8e5ab30763179aa5488ecb0b8a0833e255cd21c7bb22175
6
+ metadata.gz: 57bdf494ed795a80edadba06bcc011d9ee5832eb6a4f87f0c02a5a1b0a81b82be05507eb4eb593cd6476b71efb7f9a1a82eced117dcc5e41175d028f5c2c8f0e
7
+ data.tar.gz: 6735f1eea27c3fa7c87d394d3d0a9baf9c2218f632f8bd2d2b5b9325366a3d5ff47a5e89668bd8146429e8ef00ad814b9593fa0e8fbea7519dbe3dd345ae14bd
@@ -1,3 +1,6 @@
1
+ ## This file is managed by Terraform.
2
+ ## Do not modify this file directly, as it may be overwritten.
3
+ ## Please open an issue instead.
1
4
  name: CI
2
5
  on: [push, pull_request]
3
6
  jobs:
@@ -5,13 +8,12 @@ jobs:
5
8
  strategy:
6
9
  fail-fast: false
7
10
  matrix:
8
- # Due to https://github.com/actions/runner/issues/849, we have to use quotes for '3.0'
9
- ruby: [2.5, 2.6, 2.7, '3.0', head, jruby]
11
+ ruby: [2.7, '3.0', '3.1']
10
12
  runs-on: ubuntu-latest
11
13
  steps:
12
- - uses: actions/checkout@v2
14
+ - uses: actions/checkout@v3
13
15
  - uses: ruby/setup-ruby@v1
14
16
  with:
15
17
  ruby-version: ${{ matrix.ruby }}
16
- bundler-cache: true # runs 'bundle install' and caches installed gems automatically
18
+ bundler-cache: true
17
19
  - run: bundle exec rake
@@ -0,0 +1,19 @@
1
+ ## This file is managed by Terraform.
2
+ ## Do not modify this file directly, as it may be overwritten.
3
+ ## Please open an issue instead.
4
+ name: CI JRuby
5
+ on: [push, pull_request]
6
+ jobs:
7
+ test:
8
+ strategy:
9
+ fail-fast: false
10
+ matrix:
11
+ ruby: [jruby, jruby-head]
12
+ runs-on: ubuntu-latest
13
+ steps:
14
+ - uses: actions/checkout@v3
15
+ - uses: ruby/setup-ruby@v1
16
+ with:
17
+ ruby-version: ${{ matrix.ruby }}
18
+ bundler-cache: true
19
+ - run: bundle exec rake
@@ -0,0 +1,19 @@
1
+ ## This file is managed by Terraform.
2
+ ## Do not modify this file directly, as it may be overwritten.
3
+ ## Please open an issue instead.
4
+ name: CI with EOL ruby versions
5
+ on: [push, pull_request]
6
+ jobs:
7
+ test:
8
+ strategy:
9
+ fail-fast: false
10
+ matrix:
11
+ ruby: [2.5, 2.6]
12
+ runs-on: ubuntu-latest
13
+ steps:
14
+ - uses: actions/checkout@v3
15
+ - uses: ruby/setup-ruby@v1
16
+ with:
17
+ ruby-version: ${{ matrix.ruby }}
18
+ bundler-cache: true
19
+ - run: bundle exec rake
@@ -0,0 +1,19 @@
1
+ ## This file is managed by Terraform.
2
+ ## Do not modify this file directly, as it may be overwritten.
3
+ ## Please open an issue instead.
4
+ name: CI TruffleRuby
5
+ on: [push, pull_request]
6
+ jobs:
7
+ test:
8
+ strategy:
9
+ fail-fast: false
10
+ matrix:
11
+ ruby: [truffleruby, truffleruby-head]
12
+ runs-on: ubuntu-latest
13
+ steps:
14
+ - uses: actions/checkout@v3
15
+ - uses: ruby/setup-ruby@v1
16
+ with:
17
+ ruby-version: ${{ matrix.ruby }}
18
+ bundler-cache: true
19
+ - run: bundle exec rake
data/CHANGES.md CHANGED
@@ -1,3 +1,33 @@
1
+ # 0.0.27
2
+
3
+ ## Trace
4
+
5
+ * `Trace.capture_args` and `capture_return` no longer use any `Graph` logic. This
6
+ is moved to `Trace::Present` instead to reduce complexity at crunch time.
7
+ * Removed the `:focus_on` option for `#wtf?`. This is now solved through the debugger.
8
+ * `Trace.default_input_collector` and `default_output_collector` now receive the original taskWrap/pipeline-args
9
+ `[wrap_ctx, original_args]`.
10
+ * In `Trace::Present.call` it's now possible to provide labels to the `default_renderer` using the `:label` option.
11
+ * `Circuit.render` now accepts segments/path.
12
+ * Fixed a bug in `Trace::Present` where the traced top activity would show up as a node, which was wrong.
13
+ * Rename `Trace::Entity` to `Trace::Captured`.
14
+ * Removed `:position` keyword in `Trace::Renderer` and friends.
15
+ * Changed `Trace::Stack` from a nested datastructure to a linear `Captured` stack.
16
+
17
+ ## Debugger
18
+
19
+ * Add `Debugger::Node` as an interface and datastructure between tracing (`Stack` and `Captured`) and `Present`,
20
+ the latter now having access to `Debugger::Node`, only.
21
+ * Add `Debugger::Normalizer`, which computes default values for `Debugger::Node` at present-time.
22
+
23
+ ## Internals
24
+
25
+ * Use `trailblazer-activity-dsl-linear-1.0.0`.
26
+
27
+ # 0.0.26
28
+
29
+ * Fixing release, and allow using beta versions.
30
+
1
31
  # 0.0.25
2
32
 
3
33
  * Use `trailblazer-activity-dsl-linear` >= 1.0.0.beta1. This allows using this gem for testing beta versions.
data/Gemfile CHANGED
@@ -6,5 +6,5 @@ gemspec
6
6
  # gem "trailblazer-activity", ">= 0.7.1"
7
7
  # gem "trailblazer-activity", path: "../trailblazer-activity"
8
8
  # gem "trailblazer-activity-dsl-linear", path: "../trailblazer-activity-dsl-linear"
9
- # gem "trailblazer-activity-dsl-linear", github: "trailblazer/trailblazer-activity-dsl-linear"
10
- # gem "trailblazer-activity", github: "trailblazer/trailblazer-activity"
9
+ gem "trailblazer-activity-dsl-linear", github: "trailblazer/trailblazer-activity-dsl-linear"
10
+ gem "trailblazer-activity", github: "trailblazer/trailblazer-activity"
@@ -0,0 +1,17 @@
1
+ module Trailblazer
2
+ module Developer
3
+ module Introspect
4
+ # find the path for Strategy subclasses.
5
+ # FIXME: will be removed
6
+ def self.find_path(activity_class, segments)
7
+ activity = activity_class.is_a?(Class) ? activity_class.to_h[:activity] : activity_class # FIXME: not a real fan of this, maybe always do {to_h[:activity]}
8
+
9
+ node, activity, graph = Activity::Introspect.find_path(activity, segments)
10
+
11
+ activity = activity.is_a?(Class) ? activity.to_h[:activity] : activity # FIXME: not a real fan of this, maybe always do {to_h[:activity]}
12
+
13
+ return node, activity, graph
14
+ end
15
+ end
16
+ end
17
+ end
@@ -11,7 +11,12 @@ module Trailblazer
11
11
  module_function
12
12
 
13
13
  # Render an {Activity}'s circuit as a simple hash.
14
- def call(activity, **options)
14
+ def call(activity, path: nil, **options)
15
+ if path # TODO: move to place where all renderers can use this logic!
16
+ node, _, graph = Developer::Introspect.find_path(activity, path)
17
+ activity = node.task
18
+ end
19
+
15
20
  graph = Activity::Introspect::Graph(activity)
16
21
 
17
22
  circuit_hash(graph, **options)
@@ -0,0 +1,79 @@
1
+ module Trailblazer
2
+ module Developer
3
+ module Render
4
+ module TaskWrap
5
+ # @param activity Trailblazer::Activity
6
+ def self.render_for(activity, node)
7
+ task_wrap = task_wrap_for_activity(activity) # TODO: MERGE WITH BELOW
8
+ task = node.task
9
+ step_wrap = task_wrap[task] # the taskWrap for the actual step, e.g. {input,call_task,output}.
10
+
11
+ level = 2
12
+ nodes = render_pipeline(step_wrap, level)
13
+
14
+ nodes = [[0, activity], [1, node.id], *nodes]
15
+
16
+ Hirb::Console.format_output(nodes, class: :tree, type: :directory, multi_line_nodes: true)
17
+ end
18
+
19
+ # @param activity Activity
20
+ def self.task_wrap_for_activity(activity, **)
21
+ activity[:wrap_static]
22
+ end
23
+
24
+ def self.render_pipeline(pipeline, level)
25
+ renderers = Hash.new(method(:render_task_wrap_step))
26
+ renderers.merge!(
27
+ Trailblazer::Activity::DSL::Linear::VariableMapping::Pipe::Input => method(:render_input),
28
+ Trailblazer::Activity::DSL::Linear::VariableMapping::Pipe::Output => method(:render_input),
29
+ )
30
+ # TODO: use collect
31
+ nodes=[]
32
+
33
+ pipeline.to_a.collect do |row|
34
+ renderer = renderers[row[1].class]
35
+
36
+ nodes = nodes + renderer.(row, level) # call the rendering component.
37
+ end
38
+
39
+ nodes
40
+ end
41
+
42
+ def self.render_task_wrap_step(row, level)
43
+ text = row.id.to_s.ljust(33, ".") + row[1].class.to_s
44
+
45
+ [[level, text]]
46
+ end
47
+
48
+ def self.render_input(row, level)
49
+ variable_mapping = Activity::DSL::Linear::VariableMapping
50
+
51
+ input_pipe = row[1].instance_variable_get(:@pipe) # this is again a {TaskWrap::Pipeline}.
52
+
53
+ filters = input_pipe.to_a.collect do |id, filter|
54
+ id, class_name, info =
55
+ if filter.is_a?(variable_mapping::AddVariables) || filter.is_a?(variable_mapping::SetVariable)
56
+ # TODO: grab user_filter here if needed for understanding
57
+ # _info = filter.instance_variable_get(:@user_filter).inspect # we could even grab the source code for callables here!
58
+ _info = ""
59
+
60
+ [id, filter.class.to_s.match(/VariableMapping::.+/), _info]
61
+ else # generic VariableMapping::DSL step such as {VariableMapping.scope}
62
+ _name = filter.inspect.match(/VariableMapping\.\w+/)
63
+
64
+ [id.to_s, _name, ""]
65
+ end
66
+
67
+ text = "#{id.ljust(45, ".")} #{info.ljust(45, ".")} #{class_name}"
68
+
69
+ [level+1, text]
70
+ end
71
+
72
+ # pp filters
73
+ render_task_wrap_step(row, level) + filters
74
+ # render_task_wrap_step(row, level)
75
+ end
76
+ end
77
+ end
78
+ end
79
+ end
@@ -0,0 +1,68 @@
1
+ module Trailblazer
2
+ module Developer
3
+ module Trace
4
+ module Debugger
5
+ # @private
6
+ # Public entry point to add Debugger::Node normalizer steps.
7
+ def self.add_normalizer_step!(step, id:, normalizer: Normalizer::PIPELINES.last, **options)
8
+ task = Normalizer.Task(step)
9
+
10
+ # We have a TaskWrap::Pipeline (a very simple style of "activity" used for normalizers) and
11
+ # add another step using the "friendly interface" from {Activity::Adds}.
12
+ options = {append: nil} unless options.any?
13
+
14
+ pipeline_extension = Activity::TaskWrap::Extension.build([task, id: id, **options])
15
+
16
+ Normalizer::PIPELINES << pipeline_extension.(normalizer)
17
+ end
18
+
19
+ module Normalizer
20
+ def self.Task(user_step) # TODO: we could keep this in the {activity} gem.
21
+ Activity::TaskWrap::Pipeline::TaskAdapter.for_step(user_step, option: false) # we don't need Option as we don't have ciruit_options here, and no {:exec_context}
22
+ end
23
+
24
+ # Default steps for the Debugger::Node options pipeline, following the step-interface.
25
+ module Default
26
+ def self.compile_id(ctx, task_map_for_activity:, task:, **)
27
+ ctx[:compile_id] = task_map_for_activity.fetch(task)[:id]
28
+ end
29
+
30
+ def self.compile_path(ctx, parent_map:, captured_node:, **)
31
+ ctx[:compile_path] = Trace::Tree::ParentMap.path_for(parent_map, captured_node)
32
+ end
33
+
34
+ def self.runtime_id(ctx, compile_id:, **)
35
+ ctx[:runtime_id] = compile_id
36
+ end
37
+
38
+ def self.runtime_path(ctx, runtime_id:, compile_path:, **)
39
+ return ctx[:runtime_path] = compile_path if compile_path.empty? # FIXME: this currently only applies to root.
40
+
41
+ ctx[:runtime_path] = compile_path[0..-2] + [runtime_id]
42
+ end
43
+
44
+ def self.label(ctx, label: nil, runtime_id:, **)
45
+ ctx[:label] = label || runtime_id
46
+ end
47
+
48
+ def self.data(ctx, data: {}, **)
49
+ ctx[:data] = data
50
+ end
51
+ end
52
+
53
+ default_steps = {
54
+ compile_id: Normalizer.Task(Default.method(:compile_id)),
55
+ compile_path: Normalizer.Task(Default.method(:compile_path)),
56
+ runtime_id: Normalizer.Task(Default.method(:runtime_id)),
57
+ runtime_path: Normalizer.Task(Default.method(:runtime_path)),
58
+ label: Normalizer.Task(Default.method(:label)),
59
+ data: Normalizer.Task(Default.method(:data)),
60
+ }.
61
+ collect { |id, task| Activity::TaskWrap::Pipeline.Row(id, task) }
62
+
63
+ PIPELINES = [Activity::TaskWrap::Pipeline.new(default_steps)] # we do mutate this constant at compile-time. Maybe # DISCUSS and find a better way.
64
+ end
65
+ end
66
+ end
67
+ end
68
+ end
@@ -0,0 +1,81 @@
1
+ module Trailblazer
2
+ module Developer
3
+ module Trace
4
+ module Debugger
5
+ class Node < Struct.new(:captured_node, :task, :activity, :compile_id, :compile_path, :runtime_id, :runtime_path, :label, :data, :captured_input, :captured_output, :level, keyword_init: true)
6
+ # The idea is to only work with {Activity} instances on this level, as that's the runtime concept.
7
+
8
+ # TODO: class, "type",
9
+ # which track, return signal, etc
10
+
11
+
12
+ # we always key options for specific nodes by Stack::Captured::Input, so we don't confuse activities if they were called multiple times.
13
+ def self.build(tree, enumerable_tree, node_options: {}, normalizer: Debugger::Normalizer::PIPELINES.last, **options_for_nodes)
14
+ parent_map = Trace::Tree::ParentMap.build(tree).to_h # DISCUSS: can we use {enumerable_tree} for {ParentMap}?
15
+
16
+
17
+ container_activity = enumerable_tree[0].captured_input.activity # TODO: any other way to grab the container_activity? Maybe via {activity.container_activity}?
18
+
19
+ # TODO: cache activity graph
20
+ top_activity = enumerable_tree[0].captured_input.task
21
+
22
+ task_maps_per_activity = {
23
+ container_activity => {top_activity => {id: nil}} # exposes {Introspect::TaskMap}-compatible interface.
24
+ }
25
+
26
+ # DISCUSS: this might change if we introduce a new Node type for Trace.
27
+ debugger_nodes = enumerable_tree[0..-1].collect do |node|
28
+ activity = node.captured_input.activity
29
+ task = node.captured_input.task
30
+ # it's possible to pass per-node options, like {label: "Yo!"} via {:node_options[<captured_input>]}
31
+ options = node_options[node.captured_input] || {}
32
+
33
+
34
+
35
+ task_map_for_activity = task_maps_per_activity[activity] || Activity::Introspect.TaskMap(activity)
36
+
37
+ options_for_debugger_node, _ = normalizer.(
38
+ {
39
+ captured_node: node,
40
+ task: task,
41
+ activity: activity,
42
+ parent_map: parent_map,
43
+ task_map_for_activity: task_map_for_activity,
44
+ **options
45
+ },
46
+ []
47
+ )
48
+
49
+ options_for_debugger_node = options_for_debugger_node.slice(*(options_for_debugger_node.keys - [:parent_map, :task_map_for_activity]))
50
+
51
+ # these attributes are not changing with the presentation
52
+ Debugger::Node.new(
53
+ captured_node: node,
54
+ activity: activity,
55
+ task: task,
56
+
57
+ level: node.level,
58
+ captured_input: node.captured_input,
59
+ captured_output: node.captured_output,
60
+
61
+ **options_for_debugger_node,
62
+ ).freeze
63
+ end
64
+ end
65
+
66
+ def self.build_for_stack(stack, **options_for_debugger_nodes)
67
+ tree, processed = Trace.Tree(stack.to_a)
68
+
69
+ enumerable_tree = Trace::Tree.Enumerable(tree)
70
+
71
+ Debugger::Node.build(
72
+ tree,
73
+ enumerable_tree,
74
+ **options_for_debugger_nodes,
75
+ )
76
+ end
77
+ end
78
+ end # Debugger
79
+ end # Trace
80
+ end
81
+ end
@@ -1,63 +1,45 @@
1
- require 'hirb'
1
+ require "hirb"
2
2
 
3
3
  module Trailblazer::Developer
4
4
  module Trace
5
5
  module Present
6
6
  module_function
7
7
 
8
- def default_renderer(task_node:, **)
9
- [ task_node.level, %{#{task_node.value}} ]
8
+ # @private
9
+ def default_renderer(debugger_node:, **) # DISCUSS: for compatibility, should we pass {:task_node} here, too?
10
+ [debugger_node.level, debugger_node.label]
10
11
  end
11
12
 
12
- def call(stack, level: 1, tree: [], renderer: method(:default_renderer), **options)
13
- tree(stack.to_a, level, tree: tree, renderer: renderer, **options)
14
- end
15
-
16
- def tree(stack, level, tree:, renderer:, **options)
17
- opts = options.merge(tree: tree)
18
- tree_for(stack, level, **opts)
19
-
20
- nodes = tree.each_with_index.map do |task_node, position|
21
- renderer.(task_node: task_node, position: position, tree: tree)
13
+ # Returns the console output string.
14
+ # @private
15
+ def render(debugger_nodes, renderer: method(:default_renderer), **options_for_renderer)
16
+ nodes = debugger_nodes.collect do |debugger_node|
17
+ renderer.(debugger_node: debugger_node, tree: debugger_nodes, **options_for_renderer)
22
18
  end
23
19
 
24
20
  Hirb::Console.format_output(nodes, class: :tree, type: :directory, multi_line_nodes: true)
25
21
  end
26
22
 
27
- def tree_for(stack, level, tree:, **options)
28
- stack.each do |lvl| # always a Stack::Task[input, ..., output]
29
- input, output, nested = Trace::Level.input_output_nested_for_level(lvl)
23
+ # Entry point for rendering a Stack as a "tree branch" the way we do it in {#wtf?}.
24
+ def call(stack, render_method: method(:render), node_options: {}, **options)
25
+ # The top activity doesn't have an ID, hence we need to compute a default label.
26
+ # TODO: maybe we should deep-merge here.
27
+ captured_input_for_top_activity = stack.to_a[0]
30
28
 
31
- tree.push(*TreeNodes.for(level, **options.merge(input: input, output: output)))
29
+ top_activity_options = {
30
+ # we can pass particular label "hints".
31
+ captured_input_for_top_activity => {
32
+ # label: %{#{captured_input_for_top_activity.task.superclass} (anonymous)},
33
+ label: captured_input_for_top_activity.task.inspect,
34
+ },
35
+ }
32
36
 
33
- if nested.any? # nesting
34
- opts = options.merge(tree: tree)
35
- tree_for(nested, level + 1, **opts)
36
- end
37
+ node_options = top_activity_options.merge(node_options)
37
38
 
38
- tree
39
- end
40
- end
39
+ debugger_nodes = Debugger::Node.build_for_stack(stack, node_options: node_options, **options) # currently, we agree on using a Debugger::Node list as the presentation data structure.
41
40
 
42
- module TreeNodes
43
- Node = Struct.new(:level, :value, :input, :output, :options) do
44
- # Allow access to any custom key from options, eg. color_map
45
- def method_missing(name, *)
46
- options[name]
47
- end
48
- end
49
-
50
- module_function
51
-
52
- def for(level, input:, output:, **options)
53
- nodes = Array[ Node.new(level, input.data[:task_name], input, output, options).freeze ]
54
-
55
- focused_nodes = Trace::Focusable.tree_nodes_for(level, input: input, output: output, **options)
56
- nodes += focused_nodes if focused_nodes.length > 0
57
-
58
- nodes
59
- end
41
+ return render_method.(debugger_nodes, **options)
60
42
  end
61
- end
43
+ end # Present
62
44
  end
63
45
  end
@@ -0,0 +1,21 @@
1
+ module Trailblazer
2
+ module Developer
3
+ module Trace
4
+ # The stack is a linear one-dimensional array. Per traced task two {Trace::Captured}
5
+ # elements get pushed onto it (unless there's an Exception).
6
+ class Stack
7
+ def initialize(captureds=[])
8
+ @stack = captureds
9
+ end
10
+
11
+ def <<(captured)
12
+ @stack << captured
13
+ end
14
+
15
+ def to_a
16
+ @stack
17
+ end
18
+ end # Stack
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,86 @@
1
+ module Trailblazer
2
+ module Developer
3
+ module Trace
4
+ # Datastructure representing a trace.
5
+ class Tree
6
+ # This could also be seen as {tree.to_a}.
7
+ def self.Enumerable(node)
8
+ Enumerable.nodes_for(node)
9
+ end
10
+
11
+ module Enumerable
12
+ # @private
13
+ def self.nodes_for(node)
14
+ [node, *node.nodes.collect { |n| nodes_for(n) } ].flatten
15
+ end
16
+ end # Enumerable
17
+
18
+ # Map each {Node} instance to its parent {Node}.
19
+ module ParentMap
20
+ def self.build(node)
21
+ children_map = []
22
+ node.nodes.each { |n| children_map += ParentMap.build(n) }#.flatten(1)
23
+
24
+ node.nodes.collect { |n| [n, node] } + children_map
25
+ end
26
+
27
+ # @public
28
+ def self.path_for(parent_map, node)
29
+ path = []
30
+
31
+ while parent = parent_map[node] # DISCUSS: what if the graphs are cached and present, already?
32
+ node_id = Activity::Introspect::TaskMap(node.captured_input.activity)[node.captured_input.task][:id]
33
+ path << node_id
34
+
35
+ node = parent
36
+ end
37
+
38
+ path.reverse
39
+ end
40
+ end
41
+
42
+ class Node < Struct.new(:level, :captured_input, :captured_output, :nodes)
43
+ end
44
+ end # Tree
45
+
46
+
47
+ # Builds a tree graph from a linear stack.
48
+ # Consists of {Tree::Node} structures.
49
+ def self.Tree(stack_end, level: 0, parent: nil)
50
+ processed = []
51
+ nodes = []
52
+
53
+ # for {captured_input} we're gonna build a {Node}!
54
+ captured_input, remaining = stack_end[0], stack_end[1..-1]
55
+
56
+ raise unless captured_input.is_a?(Captured::Input)
57
+
58
+ while next_captured = remaining[0]
59
+ if next_captured.is_a?(Captured::Input)
60
+
61
+ bla, _processed = Tree(remaining, level: level+1)
62
+ nodes += [bla]
63
+ processed += _processed
64
+
65
+
66
+ remaining = remaining - processed
67
+
68
+ else # Captured::Output
69
+
70
+ raise unless next_captured.is_a?(Captured::Output)
71
+ raise if next_captured.activity != captured_input.activity
72
+
73
+ node = Tree::Node.new(level, captured_input, next_captured, nodes)
74
+
75
+ return node,
76
+ [captured_input, *processed, next_captured] # what nodes did we process here?
77
+
78
+ end
79
+
80
+ end
81
+
82
+
83
+ end # Tree
84
+ end
85
+ end # Developer
86
+ end
@@ -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.25"
4
+ VERSION = "0.0.27"
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,12 +6,16 @@ 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"
17
19
  require "trailblazer/developer/render/task_wrap"
20
+ require "trailblazer/developer/introspect" # TODO: might get removed, again.
21
+ require "trailblazer/developer/trace/debugger/normalizer"
@@ -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,9 @@ 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"
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.1.0", "< 1.2.0"
28
+ # FIXME: Activity 0.14.1
27
29
  spec.add_dependency "hirb"
28
30
  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.25
4
+ version: 0.0.27
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: 2022-12-14 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'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - ">="
81
+ - !ruby/object:Gem::Version
82
+ version: '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.1.0
76
90
  - - "<"
77
91
  - !ruby/object:Gem::Version
78
- version: 1.1.0
92
+ version: 1.2.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.1.0
86
100
  - - "<"
87
101
  - !ruby/object:Gem::Version
88
- version: 1.1.0
102
+ version: 1.2.0
89
103
  - !ruby/object:Gem::Dependency
90
104
  name: hirb
91
105
  requirement: !ruby/object:Gem::Requirement
@@ -108,6 +122,9 @@ extensions: []
108
122
  extra_rdoc_files: []
109
123
  files:
110
124
  - ".github/workflows/ci.yml"
125
+ - ".github/workflows/ci_jruby.yml"
126
+ - ".github/workflows/ci_legacy.yml"
127
+ - ".github/workflows/ci_truffleruby.yml"
111
128
  - ".gitignore"
112
129
  - CHANGES.md
113
130
  - Gemfile
@@ -116,19 +133,22 @@ files:
116
133
  - Rakefile
117
134
  - bin/console
118
135
  - bin/setup
119
- - gems.local.rb
120
136
  - lib/trailblazer/developer.rb
137
+ - lib/trailblazer/developer/introspect.rb
121
138
  - lib/trailblazer/developer/render/circuit.rb
122
139
  - lib/trailblazer/developer/render/linear.rb
140
+ - lib/trailblazer/developer/render/task_wrap.rb
123
141
  - lib/trailblazer/developer/trace.rb
124
- - lib/trailblazer/developer/trace/focusable.rb
125
- - lib/trailblazer/developer/trace/inspector.rb
142
+ - lib/trailblazer/developer/trace/debugger.rb
143
+ - lib/trailblazer/developer/trace/debugger/normalizer.rb
126
144
  - lib/trailblazer/developer/trace/present.rb
145
+ - lib/trailblazer/developer/trace/stack.rb
146
+ - lib/trailblazer/developer/trace/tree.rb
127
147
  - lib/trailblazer/developer/version.rb
128
148
  - lib/trailblazer/developer/wtf.rb
129
149
  - lib/trailblazer/developer/wtf/renderer.rb
130
150
  - trailblazer-developer.gemspec
131
- homepage: http://trailblazer.to
151
+ homepage: http://trailblazer.to/2.1/docs/trailblazer.html#trailblazer-developer
132
152
  licenses:
133
153
  - LGPL-3.0
134
154
  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
@@ -1,48 +0,0 @@
1
- module Trailblazer
2
- module Developer
3
- module Trace
4
-
5
- # This module does the inspection of given `ctx` with deep traversal.
6
- # It only gets called when focusing is going on (i.e. focus_on API).
7
- module Inspector
8
- module_function
9
-
10
- def call(value, default_inspector: method(:default_inspector), **)
11
- return hash_inspector(value, default_inspector: default_inspector) if value.is_a?(Hash)
12
- return array_inspector(value, default_inspector: default_inspector) if value.is_a?(Array)
13
-
14
- default_inspector.(value)
15
- end
16
-
17
- def hash_inspector(value, default_inspector:)
18
- Hash[
19
- value.collect do |key, nested_value|
20
- [key, call(nested_value, default_inspector: default_inspector)]
21
- end
22
- ]
23
- end
24
-
25
- def array_inspector(value, default_inspector:)
26
- value.collect do |nested_value|
27
- call(nested_value, default_inspector: default_inspector)
28
- end
29
- end
30
-
31
- # To avoid additional query that AR::Relation#inspect makes,
32
- # we're calling AR::Relation#to_sql to get plain SQL string instead.
33
- def activerecord_relation_inspector(value)
34
- { query: value.to_sql }
35
- end
36
-
37
- def default_inspector(value)
38
- if defined?(ActiveRecord) && value.is_a?(ActiveRecord::Relation)
39
- return activerecord_relation_inspector(value)
40
- end
41
-
42
- value.inspect
43
- end
44
- end
45
-
46
- end
47
- end
48
- end