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.
Files changed (161) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +12 -0
  3. data/.travis.yml +6 -0
  4. data/CHANGES.md +4 -0
  5. data/LICENSE.txt +9 -0
  6. data/README.md +48 -0
  7. data/Rakefile +10 -0
  8. data/bin/console +14 -0
  9. data/bin/setup +8 -0
  10. data/gems.rb +3 -0
  11. data/lib/trailblazer/future.rb +9 -0
  12. data/lib/trailblazer/future/version.rb +5 -0
  13. data/lib/trailblazer/v2_1/activity.rb +123 -0
  14. data/lib/trailblazer/v2_1/activity/config.rb +37 -0
  15. data/lib/trailblazer/v2_1/activity/dsl/add_task.rb +22 -0
  16. data/lib/trailblazer/v2_1/activity/dsl/helper.rb +68 -0
  17. data/lib/trailblazer/v2_1/activity/dsl/magnetic.rb +36 -0
  18. data/lib/trailblazer/v2_1/activity/dsl/magnetic/builder.rb +101 -0
  19. data/lib/trailblazer/v2_1/activity/dsl/magnetic/builder/default_normalizer.rb +26 -0
  20. data/lib/trailblazer/v2_1/activity/dsl/magnetic/builder/fast_track.rb +118 -0
  21. data/lib/trailblazer/v2_1/activity/dsl/magnetic/builder/normalizer.rb +113 -0
  22. data/lib/trailblazer/v2_1/activity/dsl/magnetic/builder/path.rb +105 -0
  23. data/lib/trailblazer/v2_1/activity/dsl/magnetic/builder/railway.rb +97 -0
  24. data/lib/trailblazer/v2_1/activity/dsl/magnetic/builder/state.rb +58 -0
  25. data/lib/trailblazer/v2_1/activity/dsl/magnetic/finalizer.rb +51 -0
  26. data/lib/trailblazer/v2_1/activity/dsl/magnetic/generate.rb +62 -0
  27. data/lib/trailblazer/v2_1/activity/dsl/magnetic/merge.rb +16 -0
  28. data/lib/trailblazer/v2_1/activity/dsl/magnetic/process_options.rb +76 -0
  29. data/lib/trailblazer/v2_1/activity/dsl/magnetic/structure/alterations.rb +44 -0
  30. data/lib/trailblazer/v2_1/activity/dsl/magnetic/structure/plus_poles.rb +85 -0
  31. data/lib/trailblazer/v2_1/activity/dsl/magnetic/structure/polarization.rb +23 -0
  32. data/lib/trailblazer/v2_1/activity/dsl/record.rb +11 -0
  33. data/lib/trailblazer/v2_1/activity/dsl/schema/dependencies.rb +46 -0
  34. data/lib/trailblazer/v2_1/activity/dsl/schema/sequence.rb +46 -0
  35. data/lib/trailblazer/v2_1/activity/dsl/strategy/build_state.rb +32 -0
  36. data/lib/trailblazer/v2_1/activity/dsl/strategy/fast_track.rb +24 -0
  37. data/lib/trailblazer/v2_1/activity/dsl/strategy/path.rb +26 -0
  38. data/lib/trailblazer/v2_1/activity/dsl/strategy/plan.rb +36 -0
  39. data/lib/trailblazer/v2_1/activity/dsl/strategy/railway.rb +23 -0
  40. data/lib/trailblazer/v2_1/activity/interface.rb +16 -0
  41. data/lib/trailblazer/v2_1/activity/introspect.rb +167 -0
  42. data/lib/trailblazer/v2_1/activity/present.rb +95 -0
  43. data/lib/trailblazer/v2_1/activity/structures.rb +57 -0
  44. data/lib/trailblazer/v2_1/activity/task_builder.rb +41 -0
  45. data/lib/trailblazer/v2_1/activity/task_wrap.rb +40 -0
  46. data/lib/trailblazer/v2_1/activity/task_wrap/call_task.rb +19 -0
  47. data/lib/trailblazer/v2_1/activity/task_wrap/merge.rb +23 -0
  48. data/lib/trailblazer/v2_1/activity/task_wrap/runner.rb +62 -0
  49. data/lib/trailblazer/v2_1/activity/task_wrap/trace.rb +44 -0
  50. data/lib/trailblazer/v2_1/activity/task_wrap/variable_mapping.rb +162 -0
  51. data/lib/trailblazer/v2_1/activity/testing.rb +32 -0
  52. data/lib/trailblazer/v2_1/activity/trace.rb +97 -0
  53. data/lib/trailblazer/v2_1/circuit.rb +71 -0
  54. data/lib/trailblazer/v2_1/container_chain.rb +45 -0
  55. data/lib/trailblazer/v2_1/context.rb +79 -0
  56. data/lib/trailblazer/v2_1/deprecation/call.rb +46 -0
  57. data/lib/trailblazer/v2_1/deprecation/context.rb +43 -0
  58. data/lib/trailblazer/v2_1/dsl.rb +47 -0
  59. data/lib/trailblazer/v2_1/macro.rb +11 -0
  60. data/lib/trailblazer/v2_1/macro/contract.rb +5 -0
  61. data/lib/trailblazer/v2_1/operation.rb +91 -0
  62. data/lib/trailblazer/v2_1/operation/auto_inject.rb +47 -0
  63. data/lib/trailblazer/v2_1/operation/callable.rb +42 -0
  64. data/lib/trailblazer/v2_1/operation/class_dependencies.rb +25 -0
  65. data/lib/trailblazer/v2_1/operation/contract.rb +61 -0
  66. data/lib/trailblazer/v2_1/operation/deprecated_macro.rb +19 -0
  67. data/lib/trailblazer/v2_1/operation/deprecations.rb +21 -0
  68. data/lib/trailblazer/v2_1/operation/guard.rb +18 -0
  69. data/lib/trailblazer/v2_1/operation/heritage.rb +30 -0
  70. data/lib/trailblazer/v2_1/operation/inject.rb +36 -0
  71. data/lib/trailblazer/v2_1/operation/inspect.rb +79 -0
  72. data/lib/trailblazer/v2_1/operation/model.rb +50 -0
  73. data/lib/trailblazer/v2_1/operation/module.rb +29 -0
  74. data/lib/trailblazer/v2_1/operation/nested.rb +98 -0
  75. data/lib/trailblazer/v2_1/operation/persist.rb +14 -0
  76. data/lib/trailblazer/v2_1/operation/policy.rb +44 -0
  77. data/lib/trailblazer/v2_1/operation/public_call.rb +55 -0
  78. data/lib/trailblazer/v2_1/operation/pundit.rb +38 -0
  79. data/lib/trailblazer/v2_1/operation/railway.rb +32 -0
  80. data/lib/trailblazer/v2_1/operation/railway/fast_track.rb +13 -0
  81. data/lib/trailblazer/v2_1/operation/railway/macaroni.rb +23 -0
  82. data/lib/trailblazer/v2_1/operation/railway/normalizer.rb +58 -0
  83. data/lib/trailblazer/v2_1/operation/railway/task_builder.rb +37 -0
  84. data/lib/trailblazer/v2_1/operation/rescue.rb +42 -0
  85. data/lib/trailblazer/v2_1/operation/responder.rb +18 -0
  86. data/lib/trailblazer/v2_1/operation/result.rb +30 -0
  87. data/lib/trailblazer/v2_1/operation/test.rb +17 -0
  88. data/lib/trailblazer/v2_1/operation/trace.rb +46 -0
  89. data/lib/trailblazer/v2_1/operation/validate.rb +76 -0
  90. data/lib/trailblazer/v2_1/operation/wrap.rb +83 -0
  91. data/lib/trailblazer/v2_1/option.rb +78 -0
  92. data/lib/trailblazer/v2_1/rails.rb +12 -0
  93. data/lib/trailblazer/v2_1/rails/cell.rb +22 -0
  94. data/lib/trailblazer/v2_1/rails/controller.rb +66 -0
  95. data/lib/trailblazer/v2_1/rails/form.rb +21 -0
  96. data/lib/trailblazer/v2_1/rails/railtie.rb +31 -0
  97. data/lib/trailblazer/v2_1/rails/railtie/extend_application_controller.rb +28 -0
  98. data/lib/trailblazer/v2_1/rails/railtie/loader.rb +58 -0
  99. data/lib/trailblazer/v2_1/rails/test/integration.rb +6 -0
  100. data/lib/trailblazer/v2_1/versions.txt +7 -0
  101. data/test/rails5.0/.gitignore +17 -0
  102. data/test/rails5.0/Gemfile +21 -0
  103. data/test/rails5.0/Rakefile +3 -0
  104. data/test/rails5.0/app/concepts/artist/cell/dashboard.rb +7 -0
  105. data/test/rails5.0/app/concepts/artist/cell/show.rb +4 -0
  106. data/test/rails5.0/app/concepts/artist/view/dashboard.erb +4 -0
  107. data/test/rails5.0/app/concepts/artist/view/show.erb +1 -0
  108. data/test/rails5.0/app/concepts/params/operation/with_args.rb +5 -0
  109. data/test/rails5.0/app/concepts/song/contract/form.rb +6 -0
  110. data/test/rails5.0/app/concepts/song/operation/create.rb +15 -0
  111. data/test/rails5.0/app/concepts/song/operation/show.rb +3 -0
  112. data/test/rails5.0/app/concepts/song/operation/update.rb +15 -0
  113. data/test/rails5.0/app/controllers/application_controller.rb +46 -0
  114. data/test/rails5.0/app/controllers/args_controller.rb +5 -0
  115. data/test/rails5.0/app/controllers/artists_controller.rb +24 -0
  116. data/test/rails5.0/app/controllers/params_controller.rb +11 -0
  117. data/test/rails5.0/app/controllers/songs_controller.rb +35 -0
  118. data/test/rails5.0/app/models/artist.rb +2 -0
  119. data/test/rails5.0/app/models/song.rb +2 -0
  120. data/test/rails5.0/app/views/args/with_args.html.erb +1 -0
  121. data/test/rails5.0/app/views/layouts/application.html.erb +2 -0
  122. data/test/rails5.0/app/views/songs/edit.html.erb +4 -0
  123. data/test/rails5.0/app/views/songs/new.html.erb +5 -0
  124. data/test/rails5.0/app/views/songs/new_with_result.html.erb +3 -0
  125. data/test/rails5.0/app/views/songs/show.html.erb +3 -0
  126. data/test/rails5.0/bin/bundle +3 -0
  127. data/test/rails5.0/bin/rails +4 -0
  128. data/test/rails5.0/bin/rake +4 -0
  129. data/test/rails5.0/bin/setup +29 -0
  130. data/test/rails5.0/config.ru +4 -0
  131. data/test/rails5.0/config/application.rb +25 -0
  132. data/test/rails5.0/config/boot.rb +3 -0
  133. data/test/rails5.0/config/database.yml +21 -0
  134. data/test/rails5.0/config/environment.rb +5 -0
  135. data/test/rails5.0/config/environments/development.rb +41 -0
  136. data/test/rails5.0/config/environments/test.rb +42 -0
  137. data/test/rails5.0/config/initializers/assets.rb +11 -0
  138. data/test/rails5.0/config/initializers/backtrace_silencers.rb +7 -0
  139. data/test/rails5.0/config/initializers/cookies_serializer.rb +3 -0
  140. data/test/rails5.0/config/initializers/filter_parameter_logging.rb +4 -0
  141. data/test/rails5.0/config/initializers/inflections.rb +16 -0
  142. data/test/rails5.0/config/initializers/mime_types.rb +4 -0
  143. data/test/rails5.0/config/initializers/session_store.rb +3 -0
  144. data/test/rails5.0/config/initializers/wrap_parameters.rb +14 -0
  145. data/test/rails5.0/config/locales/en.yml +23 -0
  146. data/test/rails5.0/config/routes.rb +15 -0
  147. data/test/rails5.0/config/secrets.yml +17 -0
  148. data/test/rails5.0/db/migrate/20161122145124_create_songs.rb +8 -0
  149. data/test/rails5.0/db/schema.rb +19 -0
  150. data/test/rails5.0/log/.keep +0 -0
  151. data/test/rails5.0/test/concepts/song/operation/create_test.rb +11 -0
  152. data/test/rails5.0/test/concepts/song/operation/update_test.rb +34 -0
  153. data/test/rails5.0/test/integration/.keep +0 -0
  154. data/test/rails5.0/test/integration/args_controller_test.rb +8 -0
  155. data/test/rails5.0/test/integration/artists_controller_test.rb +28 -0
  156. data/test/rails5.0/test/integration/params_controller_test.rb +8 -0
  157. data/test/rails5.0/test/integration/songs_controller_test.rb +40 -0
  158. data/test/rails5.0/test/test_helper.rb +7 -0
  159. data/test/test_helper.rb +5 -0
  160. data/trailblazer-future.gemspec +25 -0
  161. 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