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 +4 -4
- data/.github/workflows/ci.yml +6 -4
- data/.github/workflows/ci_jruby.yml +19 -0
- data/.github/workflows/ci_legacy.yml +19 -0
- data/.github/workflows/ci_truffleruby.yml +19 -0
- data/CHANGES.md +30 -0
- data/Gemfile +2 -2
- data/lib/trailblazer/developer/introspect.rb +17 -0
- data/lib/trailblazer/developer/render/circuit.rb +6 -1
- data/lib/trailblazer/developer/render/task_wrap.rb +79 -0
- data/lib/trailblazer/developer/trace/debugger/normalizer.rb +68 -0
- data/lib/trailblazer/developer/trace/debugger.rb +81 -0
- data/lib/trailblazer/developer/trace/present.rb +25 -43
- data/lib/trailblazer/developer/trace/stack.rb +21 -0
- data/lib/trailblazer/developer/trace/tree.rb +86 -0
- data/lib/trailblazer/developer/trace.rb +51 -112
- data/lib/trailblazer/developer/version.rb +1 -1
- data/lib/trailblazer/developer/wtf/renderer.rb +15 -16
- data/lib/trailblazer/developer/wtf.rb +44 -57
- data/lib/trailblazer/developer.rb +6 -2
- data/trailblazer-developer.gemspec +4 -2
- metadata +30 -10
- data/gems.local.rb +0 -11
- data/lib/trailblazer/developer/trace/focusable.rb +0 -78
- data/lib/trailblazer/developer/trace/inspector.rb +0 -48
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 45cd9fac3c0701cd0e98573e27c59c80de31d5d91b306980c961d58f64161d71
|
4
|
+
data.tar.gz: 745d4d28834261571fdbccc9949b597bb1f17f669bc238a6bbc09925001d4594
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 57bdf494ed795a80edadba06bcc011d9ee5832eb6a4f87f0c02a5a1b0a81b82be05507eb4eb593cd6476b71efb7f9a1a82eced117dcc5e41175d028f5c2c8f0e
|
7
|
+
data.tar.gz: 6735f1eea27c3fa7c87d394d3d0a9baf9c2218f632f8bd2d2b5b9325366a3d5ff47a5e89668bd8146429e8ef00ad814b9593fa0e8fbea7519dbe3dd345ae14bd
|
data/.github/workflows/ci.yml
CHANGED
@@ -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
|
-
|
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@
|
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
|
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
|
-
|
10
|
-
|
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
|
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
|
-
|
9
|
-
|
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
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
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
|
-
|
28
|
-
|
29
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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:
|
23
|
-
|
24
|
-
|
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 = {
|
23
|
+
flow_options = {**default_flow_options, **Hash(original_flow_options)}
|
29
24
|
|
30
25
|
default_circuit_options = {
|
31
|
-
wrap_runtime: ::Hash.new(Trace.
|
26
|
+
wrap_runtime: ::Hash.new(Trace.task_wrap_extensions), # DISCUSS: this overrides existing {:wrap_runtime}.
|
32
27
|
}
|
33
28
|
|
34
|
-
circuit_options = {
|
29
|
+
circuit_options = {**original_circuit_options, **default_circuit_options}
|
35
30
|
|
36
|
-
return activity, [
|
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
|
46
|
-
Activity::TaskWrap
|
47
|
-
|
48
|
-
|
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
|
52
|
+
original_args = [[ctx, flow], circuit_options]
|
64
53
|
|
65
|
-
flow[:
|
66
|
-
|
67
|
-
|
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,
|
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
|
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[:
|
65
|
+
captured_output = Captured(Captured::Output, flow[:output_data_collector], wrap_config, original_args)
|
83
66
|
|
84
|
-
|
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
|
-
|
96
|
-
{ ctx: ctx, signal: wrap_config[:return_signal] }
|
69
|
+
return wrap_config, original_args
|
97
70
|
end
|
98
71
|
|
99
|
-
|
100
|
-
|
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
|
-
|
133
|
-
|
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
|
-
#
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
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
|
-
|
158
|
-
|
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
|
-
|
161
|
-
|
162
|
-
|
94
|
+
# Called in {#Captured}.
|
95
|
+
def default_output_data_collector(wrap_config, ((ctx, _), _))
|
96
|
+
returned_ctx, _ = wrap_config[:return_args]
|
163
97
|
|
164
|
-
|
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
|
-
|
167
|
-
|
168
|
-
|
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,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
|
-
#
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
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
|
27
|
-
|
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
|
-
|
32
|
-
|
33
|
-
|
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
|
-
|
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.
|
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
|
-
|
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
|
-
|
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
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
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
|
-
|
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
|
-
|
56
|
-
|
30
|
+
ensure
|
31
|
+
# incomplete_stack = flow_options[:stack]
|
32
|
+
incomplete_stack = stack
|
57
33
|
|
58
|
-
|
59
|
-
|
60
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
72
|
-
|
73
|
-
|
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
|
-
|
76
|
-
|
77
|
-
|
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
|
-
|
80
|
-
end
|
69
|
+
missing_captured = processed.reverse.collect { |captured| Trace::Captured::Output.new(captured.task, captured.activity, {}) }
|
81
70
|
|
82
|
-
|
83
|
-
|
84
|
-
|
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/
|
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.
|
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.
|
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-
|
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.
|
89
|
+
version: 1.1.0
|
76
90
|
- - "<"
|
77
91
|
- !ruby/object:Gem::Version
|
78
|
-
version: 1.
|
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.
|
99
|
+
version: 1.1.0
|
86
100
|
- - "<"
|
87
101
|
- !ruby/object:Gem::Version
|
88
|
-
version: 1.
|
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/
|
125
|
-
- lib/trailblazer/developer/trace/
|
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
|