trailblazer-developer 0.0.26 → 0.0.28
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.github/workflows/ci.yml +3 -4
- data/CHANGES.md +35 -0
- data/Gemfile +7 -4
- data/lib/trailblazer/developer/introspect/graph.rb +83 -0
- data/lib/trailblazer/developer/introspect.rb +17 -0
- data/lib/trailblazer/developer/render/circuit.rb +6 -49
- data/lib/trailblazer/developer/render/linear.rb +1 -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 +72 -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 +8 -2
- data/trailblazer-developer.gemspec +3 -2
- metadata +28 -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: 2bc71f425eee589bda27a8b681b796650443e688c3dc46ddb7d12e6daf092f08
|
4
|
+
data.tar.gz: 2f128bcf1675ba3147db4737f348b52d9a1f96562e317feeddda79846c1ca0e8
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 3b10040b636e96bc396ec89b41db298030552b0460ad7fa88b94ebf5a767e881cdf2c219ab573522f4226cb84d6e6d5baac786d384b7ef93d2788576dea4183f
|
7
|
+
data.tar.gz: 2eb0f50c4af74ab44ea0353e61b45a4645adb0f70132be92d329dd8123d0fd20561b58047be1bd9b2a25cb9e6bd5ccd2a6ac67089c7546e0041c78c24c455c62
|
data/.github/workflows/ci.yml
CHANGED
@@ -5,13 +5,12 @@ jobs:
|
|
5
5
|
strategy:
|
6
6
|
fail-fast: false
|
7
7
|
matrix:
|
8
|
-
|
9
|
-
ruby: [2.5, 2.6, 2.7, '3.0', head, jruby]
|
8
|
+
ruby: [2.6, 2.7, '3.0', '3.1', '3.2', 'jruby']
|
10
9
|
runs-on: ubuntu-latest
|
11
10
|
steps:
|
12
|
-
- uses: actions/checkout@
|
11
|
+
- uses: actions/checkout@v3
|
13
12
|
- uses: ruby/setup-ruby@v1
|
14
13
|
with:
|
15
14
|
ruby-version: ${{ matrix.ruby }}
|
16
|
-
bundler-cache: true
|
15
|
+
bundler-cache: true
|
17
16
|
- run: bundle exec rake
|
data/CHANGES.md
CHANGED
@@ -1,3 +1,38 @@
|
|
1
|
+
# 0.0.28
|
2
|
+
|
3
|
+
* Move `Introspect::Graph` over from `trailblazer-activity`. It's a data structure very specific
|
4
|
+
to rendering, which is not a part of pure runtime behavior.
|
5
|
+
* Use `Introspect.Nodes` API instead of `TaskMap`.
|
6
|
+
* Remove `:task_maps_per_activity` in `Debugger` as we don't need to cache anymore.
|
7
|
+
* Require `trailblazer-activity-dsl-linear-1.2.0`.
|
8
|
+
* Remove `Render::Circuit` as this is part of `trailblazer-activity` now.
|
9
|
+
|
10
|
+
# 0.0.27
|
11
|
+
|
12
|
+
## Trace
|
13
|
+
|
14
|
+
* `Trace.capture_args` and `capture_return` no longer use any `Graph` logic. This
|
15
|
+
is moved to `Trace::Present` instead to reduce complexity at crunch time.
|
16
|
+
* Removed the `:focus_on` option for `#wtf?`. This is now solved through the debugger.
|
17
|
+
* `Trace.default_input_collector` and `default_output_collector` now receive the original taskWrap/pipeline-args
|
18
|
+
`[wrap_ctx, original_args]`.
|
19
|
+
* In `Trace::Present.call` it's now possible to provide labels to the `default_renderer` using the `:label` option.
|
20
|
+
* `Circuit.render` now accepts segments/path.
|
21
|
+
* Fixed a bug in `Trace::Present` where the traced top activity would show up as a node, which was wrong.
|
22
|
+
* Rename `Trace::Entity` to `Trace::Captured`.
|
23
|
+
* Removed `:position` keyword in `Trace::Renderer` and friends.
|
24
|
+
* Changed `Trace::Stack` from a nested datastructure to a linear `Captured` stack.
|
25
|
+
|
26
|
+
## Debugger
|
27
|
+
|
28
|
+
* Add `Debugger::Node` as an interface and datastructure between tracing (`Stack` and `Captured`) and `Present`,
|
29
|
+
the latter now having access to `Debugger::Node`, only.
|
30
|
+
* Add `Debugger::Normalizer`, which computes default values for `Debugger::Node` at present-time.
|
31
|
+
|
32
|
+
## Internals
|
33
|
+
|
34
|
+
* Use `trailblazer-activity-dsl-linear-1.0.0`.
|
35
|
+
|
1
36
|
# 0.0.26
|
2
37
|
|
3
38
|
* Fixing release, and allow using beta versions.
|
data/Gemfile
CHANGED
@@ -3,8 +3,11 @@ source "https://rubygems.org"
|
|
3
3
|
# Specify your gem's dependencies in trailblazer-developer.gemspec
|
4
4
|
gemspec
|
5
5
|
|
6
|
-
# gem "trailblazer-activity", "
|
7
|
-
# gem "trailblazer-activity",
|
8
|
-
# gem "trailblazer-
|
6
|
+
# gem "trailblazer-activity", path: "../trailblazer-activity"
|
7
|
+
# gem "trailblazer-activity-dsl-linear", path: "../trailblazer-activity-dsl-linear"
|
8
|
+
# gem "trailblazer-operation", path: "../trailblazer-operation"
|
9
|
+
# gem "trailblazer-pro", path: "../trailblazer-pro"
|
10
|
+
|
11
|
+
# gem "trailblazer-activity", github: "trailblazer/trailblazer-activity"
|
9
12
|
# gem "trailblazer-activity-dsl-linear", github: "trailblazer/trailblazer-activity-dsl-linear"
|
10
|
-
|
13
|
+
|
@@ -0,0 +1,83 @@
|
|
1
|
+
# NOTE: The Graph API might get deprecated and replaced.
|
2
|
+
module Trailblazer
|
3
|
+
module Developer
|
4
|
+
module Introspect
|
5
|
+
# TODO: order of step/fail/pass in Node would be cool to have
|
6
|
+
|
7
|
+
# TODO: Remove Graph. This is only useful to render the full circuit
|
8
|
+
# Some thoughts here:
|
9
|
+
# * where do we need Schema.outputs? and where task.outputs?
|
10
|
+
#
|
11
|
+
#
|
12
|
+
# @private This API is still under construction.
|
13
|
+
class Graph
|
14
|
+
def initialize(activity)
|
15
|
+
@schema = activity.to_h or raise
|
16
|
+
@circuit = @schema[:circuit]
|
17
|
+
@map = @circuit.to_h[:map]
|
18
|
+
@configs = @schema[:nodes]
|
19
|
+
end
|
20
|
+
|
21
|
+
def find(id = nil, &block)
|
22
|
+
return find_by_id(id) unless block_given?
|
23
|
+
|
24
|
+
find_with_block(&block)
|
25
|
+
end
|
26
|
+
|
27
|
+
# TODO: convert to {#to_a}.
|
28
|
+
def collect(strategy: :circuit)
|
29
|
+
@map.keys.each_with_index.collect { |task, i| yield find_with_block { |node| node.task == task }, i }
|
30
|
+
end
|
31
|
+
|
32
|
+
def stop_events
|
33
|
+
@circuit.to_h[:end_events]
|
34
|
+
end
|
35
|
+
|
36
|
+
private
|
37
|
+
|
38
|
+
def find_by_id(id)
|
39
|
+
node = @configs.find { |_, _node| _node.id == id } or return
|
40
|
+
node_for(node[1])
|
41
|
+
end
|
42
|
+
|
43
|
+
def find_with_block
|
44
|
+
existing = @configs.find { |_, node| yield Node(node.task, node.id, node.outputs, node.data) } or return
|
45
|
+
|
46
|
+
node_for(existing[1])
|
47
|
+
end
|
48
|
+
|
49
|
+
# Build a {Graph::Node} with outputs etc.
|
50
|
+
def node_for(node_attributes)
|
51
|
+
Node(
|
52
|
+
node_attributes.task,
|
53
|
+
node_attributes.id,
|
54
|
+
node_attributes.outputs, # [#<struct Trailblazer::Activity::Output signal=Trailblazer::Activity::Right, semantic=:success>]
|
55
|
+
outgoings_for(node_attributes),
|
56
|
+
node_attributes.data,
|
57
|
+
)
|
58
|
+
end
|
59
|
+
|
60
|
+
def Node(*args)
|
61
|
+
Node.new(*args).freeze
|
62
|
+
end
|
63
|
+
|
64
|
+
Node = Struct.new(:task, :id, :outputs, :outgoings, :data)
|
65
|
+
Outgoing = Struct.new(:output, :task)
|
66
|
+
|
67
|
+
def outgoings_for(node)
|
68
|
+
outputs = node.outputs
|
69
|
+
connections = @map[node.task]
|
70
|
+
|
71
|
+
connections.collect do |signal, target|
|
72
|
+
output = outputs.find { |out| out.signal == signal }
|
73
|
+
Outgoing.new(output, target)
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
def self.Graph(*args)
|
79
|
+
Graph.new(*args)
|
80
|
+
end
|
81
|
+
end # Graph
|
82
|
+
end
|
83
|
+
end
|
@@ -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
|
@@ -2,56 +2,13 @@ module Trailblazer
|
|
2
2
|
module Developer
|
3
3
|
module_function
|
4
4
|
|
5
|
-
def render(activity, **options)
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
module Render
|
10
|
-
module Circuit
|
11
|
-
module_function
|
12
|
-
|
13
|
-
# Render an {Activity}'s circuit as a simple hash.
|
14
|
-
def call(activity, **options)
|
15
|
-
graph = Activity::Introspect::Graph(activity)
|
16
|
-
|
17
|
-
circuit_hash(graph, **options)
|
18
|
-
end
|
19
|
-
|
20
|
-
def circuit_hash(graph, **options)
|
21
|
-
content = graph.collect do |node|
|
22
|
-
conns = node.outgoings.collect do |outgoing|
|
23
|
-
" {#{outgoing.output.signal}} => #{inspect_with_matcher(outgoing.task, **options)}"
|
24
|
-
end
|
25
|
-
|
26
|
-
[ inspect_with_matcher(node.task, **options), conns.join("\n") ]
|
27
|
-
end
|
28
|
-
|
29
|
-
content = content.join("\n")
|
30
|
-
|
31
|
-
"\n#{content}".gsub(/0x\w+/, "0x")
|
32
|
-
end
|
33
|
-
|
34
|
-
# If Ruby had pattern matching, this function wasn't necessary.
|
35
|
-
def inspect_with_matcher(task, inspect_task: method(:inspect_task), inspect_end: method(:inspect_end))
|
36
|
-
return inspect_task.(task) unless task.kind_of?(Trailblazer::Activity::End)
|
37
|
-
inspect_end.(task)
|
38
|
-
end
|
39
|
-
|
40
|
-
def inspect_task(task)
|
41
|
-
task.inspect
|
42
|
-
end
|
43
|
-
|
44
|
-
def inspect_end(task)
|
45
|
-
class_name = Render::Circuit.strip(task.class)
|
46
|
-
options = task.to_h
|
47
|
-
|
48
|
-
"#<#{class_name}/#{options[:semantic].inspect}>"
|
49
|
-
end
|
50
|
-
|
51
|
-
def self.strip(string)
|
52
|
-
string.to_s.sub("Trailblazer::Activity::", "")
|
53
|
-
end
|
5
|
+
def render(activity, path: nil, **options)
|
6
|
+
if path # TODO: move to place where all renderers can use this logic!
|
7
|
+
node, _, graph = Developer::Introspect.find_path(activity, path)
|
8
|
+
activity = node.task
|
54
9
|
end
|
10
|
+
|
11
|
+
Activity::Introspect::Render.(activity, **options)
|
55
12
|
end
|
56
13
|
end
|
57
14
|
end
|
@@ -15,7 +15,7 @@ module Trailblazer
|
|
15
15
|
module_function
|
16
16
|
|
17
17
|
def call(operation, style: :line)
|
18
|
-
graph =
|
18
|
+
graph = Introspect::Graph(operation)
|
19
19
|
|
20
20
|
rows = graph.collect do |node, i|
|
21
21
|
next if node[:data][:stop_event] # DISCUSS: show this?
|
@@ -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.to_h[:config][: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, activity:, task:, **)
|
27
|
+
ctx[:compile_id] = Activity::Introspect.Nodes(activity, task: 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,72 @@
|
|
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
|
+
top_activity = enumerable_tree[0].captured_input.task
|
20
|
+
|
21
|
+
# DISCUSS: this might change if we introduce a new Node type for Trace.
|
22
|
+
debugger_nodes = enumerable_tree[0..-1].collect do |node|
|
23
|
+
activity = node.captured_input.activity
|
24
|
+
task = node.captured_input.task
|
25
|
+
# it's possible to pass per-node options, like {label: "Yo!"} via {:node_options[<captured_input>]}
|
26
|
+
options = node_options[node.captured_input] || {}
|
27
|
+
|
28
|
+
|
29
|
+
options_for_debugger_node, _ = normalizer.(
|
30
|
+
{
|
31
|
+
captured_node: node,
|
32
|
+
task: task,
|
33
|
+
activity: activity,
|
34
|
+
parent_map: parent_map,
|
35
|
+
**options
|
36
|
+
},
|
37
|
+
[]
|
38
|
+
)
|
39
|
+
|
40
|
+
options_for_debugger_node = options_for_debugger_node.slice(*(options_for_debugger_node.keys - [:parent_map]))
|
41
|
+
|
42
|
+
# these attributes are not changing with the presentation
|
43
|
+
Debugger::Node.new(
|
44
|
+
captured_node: node,
|
45
|
+
activity: activity,
|
46
|
+
task: task,
|
47
|
+
|
48
|
+
level: node.level,
|
49
|
+
captured_input: node.captured_input,
|
50
|
+
captured_output: node.captured_output,
|
51
|
+
|
52
|
+
**options_for_debugger_node,
|
53
|
+
).freeze
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
def self.build_for_stack(stack, **options_for_debugger_nodes)
|
58
|
+
tree, processed = Trace.Tree(stack.to_a)
|
59
|
+
|
60
|
+
enumerable_tree = Trace::Tree.Enumerable(tree)
|
61
|
+
|
62
|
+
Debugger::Node.build(
|
63
|
+
tree,
|
64
|
+
enumerable_tree,
|
65
|
+
**options_for_debugger_nodes,
|
66
|
+
)
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end # Debugger
|
70
|
+
end # Trace
|
71
|
+
end
|
72
|
+
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.Nodes(node.captured_input.activity, task: 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
|