trailblazer-future 2.1.0.rc1
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 +7 -0
- data/.gitignore +12 -0
- data/.travis.yml +6 -0
- data/CHANGES.md +4 -0
- data/LICENSE.txt +9 -0
- data/README.md +48 -0
- data/Rakefile +10 -0
- data/bin/console +14 -0
- data/bin/setup +8 -0
- data/gems.rb +3 -0
- data/lib/trailblazer/future.rb +9 -0
- data/lib/trailblazer/future/version.rb +5 -0
- data/lib/trailblazer/v2_1/activity.rb +123 -0
- data/lib/trailblazer/v2_1/activity/config.rb +37 -0
- data/lib/trailblazer/v2_1/activity/dsl/add_task.rb +22 -0
- data/lib/trailblazer/v2_1/activity/dsl/helper.rb +68 -0
- data/lib/trailblazer/v2_1/activity/dsl/magnetic.rb +36 -0
- data/lib/trailblazer/v2_1/activity/dsl/magnetic/builder.rb +101 -0
- data/lib/trailblazer/v2_1/activity/dsl/magnetic/builder/default_normalizer.rb +26 -0
- data/lib/trailblazer/v2_1/activity/dsl/magnetic/builder/fast_track.rb +118 -0
- data/lib/trailblazer/v2_1/activity/dsl/magnetic/builder/normalizer.rb +113 -0
- data/lib/trailblazer/v2_1/activity/dsl/magnetic/builder/path.rb +105 -0
- data/lib/trailblazer/v2_1/activity/dsl/magnetic/builder/railway.rb +97 -0
- data/lib/trailblazer/v2_1/activity/dsl/magnetic/builder/state.rb +58 -0
- data/lib/trailblazer/v2_1/activity/dsl/magnetic/finalizer.rb +51 -0
- data/lib/trailblazer/v2_1/activity/dsl/magnetic/generate.rb +62 -0
- data/lib/trailblazer/v2_1/activity/dsl/magnetic/merge.rb +16 -0
- data/lib/trailblazer/v2_1/activity/dsl/magnetic/process_options.rb +76 -0
- data/lib/trailblazer/v2_1/activity/dsl/magnetic/structure/alterations.rb +44 -0
- data/lib/trailblazer/v2_1/activity/dsl/magnetic/structure/plus_poles.rb +85 -0
- data/lib/trailblazer/v2_1/activity/dsl/magnetic/structure/polarization.rb +23 -0
- data/lib/trailblazer/v2_1/activity/dsl/record.rb +11 -0
- data/lib/trailblazer/v2_1/activity/dsl/schema/dependencies.rb +46 -0
- data/lib/trailblazer/v2_1/activity/dsl/schema/sequence.rb +46 -0
- data/lib/trailblazer/v2_1/activity/dsl/strategy/build_state.rb +32 -0
- data/lib/trailblazer/v2_1/activity/dsl/strategy/fast_track.rb +24 -0
- data/lib/trailblazer/v2_1/activity/dsl/strategy/path.rb +26 -0
- data/lib/trailblazer/v2_1/activity/dsl/strategy/plan.rb +36 -0
- data/lib/trailblazer/v2_1/activity/dsl/strategy/railway.rb +23 -0
- data/lib/trailblazer/v2_1/activity/interface.rb +16 -0
- data/lib/trailblazer/v2_1/activity/introspect.rb +167 -0
- data/lib/trailblazer/v2_1/activity/present.rb +95 -0
- data/lib/trailblazer/v2_1/activity/structures.rb +57 -0
- data/lib/trailblazer/v2_1/activity/task_builder.rb +41 -0
- data/lib/trailblazer/v2_1/activity/task_wrap.rb +40 -0
- data/lib/trailblazer/v2_1/activity/task_wrap/call_task.rb +19 -0
- data/lib/trailblazer/v2_1/activity/task_wrap/merge.rb +23 -0
- data/lib/trailblazer/v2_1/activity/task_wrap/runner.rb +62 -0
- data/lib/trailblazer/v2_1/activity/task_wrap/trace.rb +44 -0
- data/lib/trailblazer/v2_1/activity/task_wrap/variable_mapping.rb +162 -0
- data/lib/trailblazer/v2_1/activity/testing.rb +32 -0
- data/lib/trailblazer/v2_1/activity/trace.rb +97 -0
- data/lib/trailblazer/v2_1/circuit.rb +71 -0
- data/lib/trailblazer/v2_1/container_chain.rb +45 -0
- data/lib/trailblazer/v2_1/context.rb +79 -0
- data/lib/trailblazer/v2_1/deprecation/call.rb +46 -0
- data/lib/trailblazer/v2_1/deprecation/context.rb +43 -0
- data/lib/trailblazer/v2_1/dsl.rb +47 -0
- data/lib/trailblazer/v2_1/macro.rb +11 -0
- data/lib/trailblazer/v2_1/macro/contract.rb +5 -0
- data/lib/trailblazer/v2_1/operation.rb +91 -0
- data/lib/trailblazer/v2_1/operation/auto_inject.rb +47 -0
- data/lib/trailblazer/v2_1/operation/callable.rb +42 -0
- data/lib/trailblazer/v2_1/operation/class_dependencies.rb +25 -0
- data/lib/trailblazer/v2_1/operation/contract.rb +61 -0
- data/lib/trailblazer/v2_1/operation/deprecated_macro.rb +19 -0
- data/lib/trailblazer/v2_1/operation/deprecations.rb +21 -0
- data/lib/trailblazer/v2_1/operation/guard.rb +18 -0
- data/lib/trailblazer/v2_1/operation/heritage.rb +30 -0
- data/lib/trailblazer/v2_1/operation/inject.rb +36 -0
- data/lib/trailblazer/v2_1/operation/inspect.rb +79 -0
- data/lib/trailblazer/v2_1/operation/model.rb +50 -0
- data/lib/trailblazer/v2_1/operation/module.rb +29 -0
- data/lib/trailblazer/v2_1/operation/nested.rb +98 -0
- data/lib/trailblazer/v2_1/operation/persist.rb +14 -0
- data/lib/trailblazer/v2_1/operation/policy.rb +44 -0
- data/lib/trailblazer/v2_1/operation/public_call.rb +55 -0
- data/lib/trailblazer/v2_1/operation/pundit.rb +38 -0
- data/lib/trailblazer/v2_1/operation/railway.rb +32 -0
- data/lib/trailblazer/v2_1/operation/railway/fast_track.rb +13 -0
- data/lib/trailblazer/v2_1/operation/railway/macaroni.rb +23 -0
- data/lib/trailblazer/v2_1/operation/railway/normalizer.rb +58 -0
- data/lib/trailblazer/v2_1/operation/railway/task_builder.rb +37 -0
- data/lib/trailblazer/v2_1/operation/rescue.rb +42 -0
- data/lib/trailblazer/v2_1/operation/responder.rb +18 -0
- data/lib/trailblazer/v2_1/operation/result.rb +30 -0
- data/lib/trailblazer/v2_1/operation/test.rb +17 -0
- data/lib/trailblazer/v2_1/operation/trace.rb +46 -0
- data/lib/trailblazer/v2_1/operation/validate.rb +76 -0
- data/lib/trailblazer/v2_1/operation/wrap.rb +83 -0
- data/lib/trailblazer/v2_1/option.rb +78 -0
- data/lib/trailblazer/v2_1/rails.rb +12 -0
- data/lib/trailblazer/v2_1/rails/cell.rb +22 -0
- data/lib/trailblazer/v2_1/rails/controller.rb +66 -0
- data/lib/trailblazer/v2_1/rails/form.rb +21 -0
- data/lib/trailblazer/v2_1/rails/railtie.rb +31 -0
- data/lib/trailblazer/v2_1/rails/railtie/extend_application_controller.rb +28 -0
- data/lib/trailblazer/v2_1/rails/railtie/loader.rb +58 -0
- data/lib/trailblazer/v2_1/rails/test/integration.rb +6 -0
- data/lib/trailblazer/v2_1/versions.txt +7 -0
- data/test/rails5.0/.gitignore +17 -0
- data/test/rails5.0/Gemfile +21 -0
- data/test/rails5.0/Rakefile +3 -0
- data/test/rails5.0/app/concepts/artist/cell/dashboard.rb +7 -0
- data/test/rails5.0/app/concepts/artist/cell/show.rb +4 -0
- data/test/rails5.0/app/concepts/artist/view/dashboard.erb +4 -0
- data/test/rails5.0/app/concepts/artist/view/show.erb +1 -0
- data/test/rails5.0/app/concepts/params/operation/with_args.rb +5 -0
- data/test/rails5.0/app/concepts/song/contract/form.rb +6 -0
- data/test/rails5.0/app/concepts/song/operation/create.rb +15 -0
- data/test/rails5.0/app/concepts/song/operation/show.rb +3 -0
- data/test/rails5.0/app/concepts/song/operation/update.rb +15 -0
- data/test/rails5.0/app/controllers/application_controller.rb +46 -0
- data/test/rails5.0/app/controllers/args_controller.rb +5 -0
- data/test/rails5.0/app/controllers/artists_controller.rb +24 -0
- data/test/rails5.0/app/controllers/params_controller.rb +11 -0
- data/test/rails5.0/app/controllers/songs_controller.rb +35 -0
- data/test/rails5.0/app/models/artist.rb +2 -0
- data/test/rails5.0/app/models/song.rb +2 -0
- data/test/rails5.0/app/views/args/with_args.html.erb +1 -0
- data/test/rails5.0/app/views/layouts/application.html.erb +2 -0
- data/test/rails5.0/app/views/songs/edit.html.erb +4 -0
- data/test/rails5.0/app/views/songs/new.html.erb +5 -0
- data/test/rails5.0/app/views/songs/new_with_result.html.erb +3 -0
- data/test/rails5.0/app/views/songs/show.html.erb +3 -0
- data/test/rails5.0/bin/bundle +3 -0
- data/test/rails5.0/bin/rails +4 -0
- data/test/rails5.0/bin/rake +4 -0
- data/test/rails5.0/bin/setup +29 -0
- data/test/rails5.0/config.ru +4 -0
- data/test/rails5.0/config/application.rb +25 -0
- data/test/rails5.0/config/boot.rb +3 -0
- data/test/rails5.0/config/database.yml +21 -0
- data/test/rails5.0/config/environment.rb +5 -0
- data/test/rails5.0/config/environments/development.rb +41 -0
- data/test/rails5.0/config/environments/test.rb +42 -0
- data/test/rails5.0/config/initializers/assets.rb +11 -0
- data/test/rails5.0/config/initializers/backtrace_silencers.rb +7 -0
- data/test/rails5.0/config/initializers/cookies_serializer.rb +3 -0
- data/test/rails5.0/config/initializers/filter_parameter_logging.rb +4 -0
- data/test/rails5.0/config/initializers/inflections.rb +16 -0
- data/test/rails5.0/config/initializers/mime_types.rb +4 -0
- data/test/rails5.0/config/initializers/session_store.rb +3 -0
- data/test/rails5.0/config/initializers/wrap_parameters.rb +14 -0
- data/test/rails5.0/config/locales/en.yml +23 -0
- data/test/rails5.0/config/routes.rb +15 -0
- data/test/rails5.0/config/secrets.yml +17 -0
- data/test/rails5.0/db/migrate/20161122145124_create_songs.rb +8 -0
- data/test/rails5.0/db/schema.rb +19 -0
- data/test/rails5.0/log/.keep +0 -0
- data/test/rails5.0/test/concepts/song/operation/create_test.rb +11 -0
- data/test/rails5.0/test/concepts/song/operation/update_test.rb +34 -0
- data/test/rails5.0/test/integration/.keep +0 -0
- data/test/rails5.0/test/integration/args_controller_test.rb +8 -0
- data/test/rails5.0/test/integration/artists_controller_test.rb +28 -0
- data/test/rails5.0/test/integration/params_controller_test.rb +8 -0
- data/test/rails5.0/test/integration/songs_controller_test.rb +40 -0
- data/test/rails5.0/test/test_helper.rb +7 -0
- data/test/test_helper.rb +5 -0
- data/trailblazer-future.gemspec +25 -0
- metadata +246 -0
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
require "hirb"
|
|
2
|
+
|
|
3
|
+
module Trailblazer::V2_1
|
|
4
|
+
class Activity < Module
|
|
5
|
+
|
|
6
|
+
# Task < Array
|
|
7
|
+
# [ input, ..., output ]
|
|
8
|
+
|
|
9
|
+
module Trace
|
|
10
|
+
# TODO: make this simpler.
|
|
11
|
+
module Present
|
|
12
|
+
module_function
|
|
13
|
+
|
|
14
|
+
def call(stack, level=1, tree=[])
|
|
15
|
+
tree(stack.to_a, level, tree)
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def tree(stack, level, tree)
|
|
19
|
+
tree_for(stack, level, tree)
|
|
20
|
+
|
|
21
|
+
Hirb::Console.format_output(tree, class: :tree, type: :directory)
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def tree_for(stack, level, tree)
|
|
25
|
+
stack.each do |task| # always a Stack::Task[input, ..., output]
|
|
26
|
+
input, output, nested = input_output_nested_for_task(task)
|
|
27
|
+
|
|
28
|
+
task = input.task
|
|
29
|
+
|
|
30
|
+
graph = Introspect::Graph(input.activity)
|
|
31
|
+
|
|
32
|
+
name = (node = graph.find { |node| node[:task] == task }) ? node[:id] : task
|
|
33
|
+
name ||= task # FIXME: bullshit
|
|
34
|
+
|
|
35
|
+
tree << [ level, name ]
|
|
36
|
+
|
|
37
|
+
if nested.any? # nesting
|
|
38
|
+
tree_for(nested, level + 1, tree)
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
tree
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
# DISCUSS: alternatively, we can have Task<input: output: data: >
|
|
46
|
+
def input_output_nested_for_task(task)
|
|
47
|
+
input = task[0]
|
|
48
|
+
output = task[-1]
|
|
49
|
+
|
|
50
|
+
output, nested = output.is_a?(Entity::Output) ? [output, task-[input, output]] : [nil, task[1..-1]]
|
|
51
|
+
|
|
52
|
+
return input, output, nested
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def to_name(debug_item)
|
|
56
|
+
track = debug_item[2]
|
|
57
|
+
klass = track.class == Class ? track : track.class
|
|
58
|
+
color = color_map[klass]
|
|
59
|
+
|
|
60
|
+
return debug_item[0].to_s unless color
|
|
61
|
+
colorify(debug_item[0], color)
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def to_options(debug_item)
|
|
65
|
+
debug_item[4]
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def colorify(string, color)
|
|
71
|
+
"\e[#{color_table[color]}m#{string}\e[0m"
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
def color_map
|
|
75
|
+
{
|
|
76
|
+
Trailblazer::V2_1::Activity::Start => :blue,
|
|
77
|
+
Trailblazer::V2_1::Activity::End => :pink,
|
|
78
|
+
Trailblazer::V2_1::Activity::Right => :green,
|
|
79
|
+
Trailblazer::V2_1::Activity::Left => :red
|
|
80
|
+
}
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
def color_table
|
|
84
|
+
{
|
|
85
|
+
red: 31,
|
|
86
|
+
green: 32,
|
|
87
|
+
yellow: 33,
|
|
88
|
+
blue: 34,
|
|
89
|
+
pink: 35
|
|
90
|
+
}
|
|
91
|
+
end
|
|
92
|
+
end
|
|
93
|
+
end
|
|
94
|
+
end
|
|
95
|
+
end
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
module Trailblazer::V2_1
|
|
2
|
+
class Activity < Module
|
|
3
|
+
# Generic run-time structures that are built via the DSL.
|
|
4
|
+
|
|
5
|
+
# Builds an {Activity::End} instance.
|
|
6
|
+
def self.End(semantic)
|
|
7
|
+
End.new(semantic: semantic)
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
# Any instance of subclass of End will halt the circuit's execution when hit.
|
|
11
|
+
|
|
12
|
+
# An End event is a simple structure typically found as the last task invoked
|
|
13
|
+
# in an activity. The special behavior is that it
|
|
14
|
+
# a) maintains a semantic that is used to further connect that very event
|
|
15
|
+
# b) its `End#call` method returns the end instance itself as the signal.
|
|
16
|
+
class End
|
|
17
|
+
def initialize(semantic:, **options)
|
|
18
|
+
@options = options.merge(semantic: semantic)
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def call(args, circuit_options)
|
|
22
|
+
return self, args, circuit_options
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def to_h
|
|
26
|
+
@options
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def to_s
|
|
30
|
+
%{#<#{self.class.name} #{@options.collect{ |k,v| "#{k}=#{v.inspect}" }.join(" ")}>}
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
alias_method :inspect, :to_s
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
class Start < End
|
|
37
|
+
def call(args, circuit_options)
|
|
38
|
+
return Activity::Right, args, circuit_options
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
class Signal; end
|
|
43
|
+
class Right < Signal; end
|
|
44
|
+
class Left < Signal; end
|
|
45
|
+
|
|
46
|
+
# signal: actual signal emitted by the task
|
|
47
|
+
# color: the mapping, where this signal will travel to. This can be e.g. Left=>:success. The polarization when building the graph.
|
|
48
|
+
# "i am traveling towards :success because ::step said so!"
|
|
49
|
+
# semantic: the original "semantic" or role of the signal, such as :success. This usually comes from the activity hosting this output.
|
|
50
|
+
Output = Struct.new(:signal, :semantic)
|
|
51
|
+
|
|
52
|
+
# Builds an {Activity::Output} instance.
|
|
53
|
+
def self.Output(signal, color)
|
|
54
|
+
Output.new(signal, color).freeze
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
end
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
module Trailblazer::V2_1
|
|
2
|
+
module Activity::TaskBuilder
|
|
3
|
+
# every step is wrapped by this proc/decider. this is executed in the circuit as the actual task.
|
|
4
|
+
# Step calls step.(options, **options, flow_options)
|
|
5
|
+
# Output signal binary: true=>Right, false=>Left.
|
|
6
|
+
# Passes through all subclasses of Direction.~~~~~~~~~~~~~~~~~
|
|
7
|
+
def self.Binary(user_proc)
|
|
8
|
+
Task.new(Trailblazer::V2_1::Option::KW( user_proc ), user_proc)
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
# Translates the return value of the user step into a valid signal.
|
|
12
|
+
# Note that it passes through subclasses of {Signal}.
|
|
13
|
+
def self.binary_signal_for(result, on_true, on_false)
|
|
14
|
+
result.is_a?(Class) && result < Activity::Signal ? result : (result ? on_true : on_false)
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
class Task
|
|
18
|
+
def initialize(task, user_proc)
|
|
19
|
+
@task = task
|
|
20
|
+
@user_proc = user_proc
|
|
21
|
+
|
|
22
|
+
freeze
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def call( (ctx, flow_options), **circuit_options )
|
|
26
|
+
# Execute the user step with TRB's kw args.
|
|
27
|
+
result = @task.( ctx, **circuit_options ) # circuit_options contains :exec_context.
|
|
28
|
+
|
|
29
|
+
# Return an appropriate signal which direction to go next.
|
|
30
|
+
signal = Activity::TaskBuilder.binary_signal_for( result, Activity::Right, Activity::Left )
|
|
31
|
+
|
|
32
|
+
return signal, [ ctx, flow_options ]
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def inspect
|
|
36
|
+
%{#<Trailblazer::V2_1::Activity::TaskBuilder::Task user_proc=#{@user_proc}>}
|
|
37
|
+
end
|
|
38
|
+
alias_method :to_s, :inspect
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
module Trailblazer::V2_1
|
|
2
|
+
class Activity < Module
|
|
3
|
+
#
|
|
4
|
+
# Example with tracing:
|
|
5
|
+
#
|
|
6
|
+
# Call the task_wrap circuit:
|
|
7
|
+
# |-- Start
|
|
8
|
+
# |-- Trace.capture_args [optional]
|
|
9
|
+
# |-- Call (call actual task) id: "task_wrap.call_task"
|
|
10
|
+
# |-- Trace.capture_return [optional]
|
|
11
|
+
# |-- Wrap::End
|
|
12
|
+
module TaskWrap
|
|
13
|
+
# The actual activity that implements the taskWrap.
|
|
14
|
+
def self.initial_activity
|
|
15
|
+
Module.new do
|
|
16
|
+
extend Trailblazer::V2_1::Activity::Path(
|
|
17
|
+
name: "taskWrap",
|
|
18
|
+
normalizer_class: Magnetic::DefaultNormalizer,
|
|
19
|
+
plus_poles: Magnetic::PlusPoles.initial( :success => Magnetic::Builder::Path.default_outputs[:success] ) # DefaultNormalizer doesn't give us default PlusPoles.
|
|
20
|
+
)
|
|
21
|
+
|
|
22
|
+
task TaskWrap.method(:call_task), id: "task_wrap.call_task" # ::call_task is defined in task_wrap/call_task.
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
# Compute runtime arguments necessary to execute a taskWrap per task of the activity.
|
|
27
|
+
def self.invoke(activity, args, wrap_runtime: {}, **circuit_options)
|
|
28
|
+
circuit_options = circuit_options.merge(
|
|
29
|
+
runner: TaskWrap::Runner,
|
|
30
|
+
wrap_runtime: wrap_runtime,
|
|
31
|
+
|
|
32
|
+
activity: { adds: [], circuit: {} }, # for Runner
|
|
33
|
+
)
|
|
34
|
+
|
|
35
|
+
# signal, (ctx, flow), circuit_options =
|
|
36
|
+
Runner.(activity, args, circuit_options)
|
|
37
|
+
end
|
|
38
|
+
end # TaskWrap
|
|
39
|
+
end
|
|
40
|
+
end
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
class Trailblazer::V2_1::Activity < Module
|
|
2
|
+
module TaskWrap
|
|
3
|
+
# TaskWrap step that calls the actual wrapped task and passes all `original_args` to it.
|
|
4
|
+
#
|
|
5
|
+
# It writes to wrap_ctx[:return_signal], wrap_ctx[:return_args]
|
|
6
|
+
def self.call_task((wrap_ctx, original_args), **circuit_options)
|
|
7
|
+
task = wrap_ctx[:task]
|
|
8
|
+
|
|
9
|
+
# Call the actual task we're wrapping here.
|
|
10
|
+
# puts "~~~~wrap.call: #{task}"
|
|
11
|
+
return_signal, return_args = task.(*original_args)
|
|
12
|
+
|
|
13
|
+
# DISCUSS: do we want original_args here to be passed on, or the "effective" return_args which are different to original_args now?
|
|
14
|
+
wrap_ctx = wrap_ctx.merge( return_signal: return_signal, return_args: return_args )
|
|
15
|
+
|
|
16
|
+
return Right, [ wrap_ctx, original_args ]
|
|
17
|
+
end
|
|
18
|
+
end # Wrap
|
|
19
|
+
end
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
module Trailblazer::V2_1
|
|
2
|
+
module Activity::TaskWrap
|
|
3
|
+
# This is instantiated via the DSL, and passed to the :extension API,
|
|
4
|
+
# allowing to add steps to the Activity's static_wrap.
|
|
5
|
+
# Compile-time function
|
|
6
|
+
class Merge
|
|
7
|
+
def initialize(extension_plan)
|
|
8
|
+
@extension_plan = extension_plan
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
# {:extension API}
|
|
12
|
+
def call(activity, task, local_options, *returned_options)
|
|
13
|
+
# we could make the default initial_activity injectable via the DSL, the value would sit in returned_options or local_options.
|
|
14
|
+
static_wrap = Activity::TaskWrap.wrap_static_for(task, activity: activity)
|
|
15
|
+
|
|
16
|
+
# # macro might want to apply changes to the static task_wrap (e.g. Inject)
|
|
17
|
+
new_wrap = Activity::Path::Plan.merge( static_wrap, @extension_plan )
|
|
18
|
+
|
|
19
|
+
activity[:wrap_static, task] = new_wrap
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
class Trailblazer::V2_1::Activity < Module
|
|
2
|
+
module TaskWrap
|
|
3
|
+
# The runner is passed into Activity#call( runner: Runner ) and is called for every task in the circuit.
|
|
4
|
+
# It runs the TaskWrap per task.
|
|
5
|
+
#
|
|
6
|
+
# (wrap_ctx, original_args), **wrap_circuit_options
|
|
7
|
+
module Runner
|
|
8
|
+
# Runner signature: call( task, direction, options, static_wraps )
|
|
9
|
+
#
|
|
10
|
+
# @api private
|
|
11
|
+
# @interface Runner
|
|
12
|
+
def self.call(task, args, circuit_options)
|
|
13
|
+
wrap_ctx = { task: task }
|
|
14
|
+
|
|
15
|
+
# this activity is "wrapped around" the actual `task`.
|
|
16
|
+
task_wrap_activity = merge_static_with_runtime(task, circuit_options)
|
|
17
|
+
|
|
18
|
+
# We save all original args passed into this Runner.call, because we want to return them later after this wrap
|
|
19
|
+
# is finished.
|
|
20
|
+
original_args = [ args, circuit_options ]
|
|
21
|
+
|
|
22
|
+
# call the wrap {Activity} around the task.
|
|
23
|
+
wrap_end_signal, ( wrap_ctx, _ ) = task_wrap_activity.(
|
|
24
|
+
[ wrap_ctx, original_args ], # we omit circuit_options here on purpose, so the wrapping activity uses the default, plain Runner.
|
|
25
|
+
{}
|
|
26
|
+
)
|
|
27
|
+
|
|
28
|
+
# don't return the wrap's end signal, but the one from call_task.
|
|
29
|
+
# return all original_args for the next "real" task in the circuit (this includes circuit_options).
|
|
30
|
+
|
|
31
|
+
return wrap_ctx[:return_signal], wrap_ctx[:return_args]
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
private
|
|
35
|
+
|
|
36
|
+
# Compute the task's wrap by applying alterations both static and from runtime.
|
|
37
|
+
#
|
|
38
|
+
# NOTE: this is for performance reasons: we could have only one hash containing everything but that'd mean
|
|
39
|
+
# unnecessary computations at `call`-time since steps might not even be executed.
|
|
40
|
+
# TODO: make this faster.
|
|
41
|
+
def self.merge_static_with_runtime(task, wrap_runtime:, **circuit_options)
|
|
42
|
+
wrap_activity = TaskWrap.wrap_static_for(
|
|
43
|
+
task,
|
|
44
|
+
circuit_options
|
|
45
|
+
) # find static wrap for this specific task, or default wrap activity.
|
|
46
|
+
|
|
47
|
+
# Apply runtime alterations.
|
|
48
|
+
# Grab the additional wirings for the particular `task` from `wrap_runtime` (returns default otherwise).
|
|
49
|
+
wrap_runtime[task] ? Trailblazer::V2_1::Activity::Path::Plan.merge(wrap_activity, wrap_runtime[task]) : wrap_activity
|
|
50
|
+
end
|
|
51
|
+
end # Runner
|
|
52
|
+
|
|
53
|
+
# Retrieve the static wrap config from {activity}.
|
|
54
|
+
# I do not like this part too much, I'd prefer computing the {:wrap_static} at compile-time for the entire
|
|
55
|
+
# object graph (including nesteds) and simply pass it through to all Runner calls.
|
|
56
|
+
# TODO: simplify that. See above
|
|
57
|
+
def self.wrap_static_for(task, activity:, default_activity: TaskWrap.initial_activity, **)
|
|
58
|
+
wrap_static = activity[:wrap_static] || {}
|
|
59
|
+
wrap_static[task] || default_activity
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
end
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
class Trailblazer::V2_1::Activity < Module
|
|
2
|
+
module TaskWrap
|
|
3
|
+
# TaskWrap tasks for tracing.
|
|
4
|
+
module Trace
|
|
5
|
+
module_function
|
|
6
|
+
|
|
7
|
+
# taskWrap step to capture incoming arguments of a step.
|
|
8
|
+
# def self.capture_args(direction, options, flow_options, wrap_config, original_flow_options)
|
|
9
|
+
def capture_args((wrap_config, original_args), **circuit_options)
|
|
10
|
+
|
|
11
|
+
original_args = capture_for(wrap_config[:task], *original_args)
|
|
12
|
+
|
|
13
|
+
return Trailblazer::V2_1::Activity::Right, [wrap_config, original_args], circuit_options
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
# taskWrap step to capture outgoing arguments from a step.
|
|
17
|
+
def capture_return((wrap_config, original_args), **circuit_options)
|
|
18
|
+
(original_options, original_flow_options, _) = original_args[0]
|
|
19
|
+
|
|
20
|
+
original_flow_options[:stack] << Trailblazer::V2_1::Activity::Trace::Entity::Output.new(
|
|
21
|
+
wrap_config[:task], {}, wrap_config[:return_signal]
|
|
22
|
+
).freeze
|
|
23
|
+
|
|
24
|
+
original_flow_options[:stack].unindent!
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
return Trailblazer::V2_1::Activity::Right, [wrap_config, original_args], circuit_options
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
# It's important to understand that {flow[:stack]} is mutated by design. This is needed so
|
|
31
|
+
# in case of exceptions we still have a "global" trace - unfortunately Ruby doesn't allow
|
|
32
|
+
# us a better way.
|
|
33
|
+
def capture_for(task, (ctx, flow), activity:, **circuit_options)
|
|
34
|
+
flow[:stack].indent!
|
|
35
|
+
|
|
36
|
+
flow[:stack] << Trailblazer::V2_1::Activity::Trace::Entity::Input.new(
|
|
37
|
+
task, activity
|
|
38
|
+
).freeze
|
|
39
|
+
|
|
40
|
+
return [ctx, flow], circuit_options.merge(activity: activity)
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
end
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
require "trailblazer/v2_1/context"
|
|
2
|
+
|
|
3
|
+
class Trailblazer::V2_1::Activity < Module
|
|
4
|
+
module TaskWrap
|
|
5
|
+
# Creates taskWrap steps to map variables before and after the actual step.
|
|
6
|
+
# We hook into the Normalizer, process `:input` and `:output` directives and
|
|
7
|
+
# translate them into a {DSL::Extension}.
|
|
8
|
+
#
|
|
9
|
+
# Note that the two options are not the only way to create filters, you can use the
|
|
10
|
+
# more low-level {Scoped()} etc., too, and write your own filter logic.
|
|
11
|
+
module VariableMapping
|
|
12
|
+
# DSL step for Magnetic::Normalizer.
|
|
13
|
+
# Translates `:input` and `:output` into VariableMapping taskWrap extensions.
|
|
14
|
+
def self.normalizer_step_for_input_output(ctx, *)
|
|
15
|
+
options, io_config = Magnetic::Options.normalize( ctx[:options], [:input, :output] )
|
|
16
|
+
|
|
17
|
+
return if io_config.empty?
|
|
18
|
+
|
|
19
|
+
ctx[:options] = options # without :input and :output
|
|
20
|
+
ctx[:options] = options.merge(Trailblazer::V2_1::Activity::TaskWrap::VariableMapping(io_config) => true)
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
# The taskWrap extension that's included into the static taskWrap for a task.
|
|
24
|
+
def self.extension_for(input, output)
|
|
25
|
+
Trailblazer::V2_1::Activity::DSL::Extension.new(
|
|
26
|
+
Merge.new(
|
|
27
|
+
Module.new do
|
|
28
|
+
extend Path::Plan()
|
|
29
|
+
|
|
30
|
+
task input, id: "task_wrap.input", before: "task_wrap.call_task"
|
|
31
|
+
task output, id: "task_wrap.output", before: "End.success", group: :end
|
|
32
|
+
end
|
|
33
|
+
)
|
|
34
|
+
)
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
# @private
|
|
39
|
+
def self.filter_for(filter)
|
|
40
|
+
if filter.is_a?(::Array) || filter.is_a?(::Hash)
|
|
41
|
+
TaskWrap::DSL.filter_from_dsl(filter)
|
|
42
|
+
else
|
|
43
|
+
filter
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
# Returns an Extension instance to be thrown into the `step` DSL arguments.
|
|
49
|
+
def self.VariableMapping(input:, output:)
|
|
50
|
+
input = Input.new(
|
|
51
|
+
Input::Scoped.new(
|
|
52
|
+
Trailblazer::V2_1::Option::KW( filter_for(input) ) ) )
|
|
53
|
+
|
|
54
|
+
output = Output.new(
|
|
55
|
+
Output::Unscoped.new(
|
|
56
|
+
Trailblazer::V2_1::Option::KW( filter_for(output) ) ) )
|
|
57
|
+
|
|
58
|
+
VariableMapping.extension_for(input, output)
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
# TaskWrap step to compute the incoming {Context} for the wrapped task.
|
|
62
|
+
# This allows renaming, filtering, hiding, of the options passed into the wrapped task.
|
|
63
|
+
#
|
|
64
|
+
# Both Input and Output are typically to be added before and after task_wrap.call_task.
|
|
65
|
+
#
|
|
66
|
+
# @note Assumption: we always have :input _and_ :output, where :input produces a Context and :output decomposes it.
|
|
67
|
+
|
|
68
|
+
# Calls your {@filter} and replaces the original ctx with your returned one.
|
|
69
|
+
class Input
|
|
70
|
+
def initialize(filter)
|
|
71
|
+
@filter = filter
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
# `original_args` are the actual args passed to the wrapped task: [ [options, ..], circuit_options ]
|
|
75
|
+
#
|
|
76
|
+
def call( (wrap_ctx, original_args), circuit_options )
|
|
77
|
+
# let user compute new ctx for the wrapped task.
|
|
78
|
+
input_ctx = apply_filter(*original_args)
|
|
79
|
+
|
|
80
|
+
wrap_ctx = wrap_ctx.merge( vm_original_ctx: original_args[0][0] ) # remember the original ctx
|
|
81
|
+
|
|
82
|
+
# decompose the original_args since we want to modify them.
|
|
83
|
+
(original_ctx, original_flow_options), original_circuit_options = original_args
|
|
84
|
+
|
|
85
|
+
# instead of the original Context, pass on the filtered `input_ctx` in the wrap.
|
|
86
|
+
return Trailblazer::V2_1::Activity::Right, [ wrap_ctx, [[input_ctx, original_flow_options], original_circuit_options] ]
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
private
|
|
90
|
+
|
|
91
|
+
def apply_filter((ctx, original_flow_options), original_circuit_options)
|
|
92
|
+
@filter.( ctx, original_circuit_options ) # returns {new_ctx}.
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
class Scoped
|
|
96
|
+
def initialize(filter)
|
|
97
|
+
@filter = filter
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
def call(original_ctx, circuit_options)
|
|
101
|
+
Trailblazer::V2_1::Context( # TODO: make this interchangeable so we can work on faster contexts?
|
|
102
|
+
@filter.(original_ctx, **circuit_options)
|
|
103
|
+
)
|
|
104
|
+
end
|
|
105
|
+
end
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
module DSL
|
|
109
|
+
# The returned filter compiles a new hash for Scoped/Unscoped that only contains
|
|
110
|
+
# the desired i/o variables.
|
|
111
|
+
def self.filter_from_dsl(map)
|
|
112
|
+
hsh = DSL.hash_for(map)
|
|
113
|
+
|
|
114
|
+
->(incoming_ctx, kwargs) { Hash[hsh.collect { |from_name, to_name| [to_name, incoming_ctx[from_name]] }] }
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
def self.hash_for(ary)
|
|
118
|
+
return ary if ary.instance_of?(::Hash)
|
|
119
|
+
Hash[ary.collect { |name| [name, name] }]
|
|
120
|
+
end
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
# TaskWrap step to compute the outgoing {Context} from the wrapped task.
|
|
124
|
+
# This allows renaming, filtering, hiding, of the options returned from the wrapped task.
|
|
125
|
+
class Output
|
|
126
|
+
def initialize(filter)
|
|
127
|
+
@filter = filter
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
# Runs your filter and replaces the ctx in `wrap_ctx[:return_args]` with the filtered one.
|
|
131
|
+
def call( (wrap_ctx, original_args), **circuit_options )
|
|
132
|
+
(original_ctx, original_flow_options), original_circuit_options = original_args
|
|
133
|
+
|
|
134
|
+
returned_ctx, _ = wrap_ctx[:return_args] # this is the Context returned from `call`ing the task.
|
|
135
|
+
original_ctx = wrap_ctx[:vm_original_ctx]
|
|
136
|
+
# let user compute the output.
|
|
137
|
+
output_ctx = @filter.(original_ctx, returned_ctx, **original_circuit_options)
|
|
138
|
+
|
|
139
|
+
wrap_ctx = wrap_ctx.merge( return_args: [output_ctx, original_flow_options] )
|
|
140
|
+
|
|
141
|
+
# and then pass on the "new" context.
|
|
142
|
+
return Trailblazer::V2_1::Activity::Right, [ wrap_ctx, original_args ]
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
private
|
|
146
|
+
|
|
147
|
+
# Merge the resulting {@filter.()} hash back into the original ctx.
|
|
148
|
+
# DISCUSS: do we need the original_ctx as a filter argument?
|
|
149
|
+
class Unscoped
|
|
150
|
+
def initialize(filter)
|
|
151
|
+
@filter = filter
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
def call(original_ctx, new_ctx, **circuit_options)
|
|
155
|
+
original_ctx.merge(
|
|
156
|
+
@filter.(new_ctx, **circuit_options)
|
|
157
|
+
)
|
|
158
|
+
end
|
|
159
|
+
end
|
|
160
|
+
end
|
|
161
|
+
end # Wrap
|
|
162
|
+
end
|