trailblazer-activity-dsl-linear 0.2.5 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: d7b19b59ebf1ee26717edb3a9d5e797eec4a4796bc567bdc21ee92ba13fd4a04
4
- data.tar.gz: 782a283ddf31bdebc6575323c7fcdae0958f301802793c5d193b2c42494f1076
3
+ metadata.gz: fb92b49935156ee66dcf347cfe7b803cc3be0222ebf84ee676923e48bfb20ea2
4
+ data.tar.gz: 802be96cb11290483f4b326d9251933b7783f230dbf8cf6f6bf6781b64bb6a4e
5
5
  SHA512:
6
- metadata.gz: bc097663260f945545c68f4f7f9c8e6023631b5ff67552ec0baf0ee39acdf3b6c8517c31b31aecadcaf8d61b09e04ee31c033d0ae80b5019f5cc0b3392da64b1
7
- data.tar.gz: cafc5c1e5dd50768986066988efac3b4fcbc37a5da13e10b2ca542fec63eb7193dee59a6379437bdd3ac69ea3a2e492065c4c076755e58432b11461e215fb01b
6
+ metadata.gz: 3b408a672b9ad2ccbd09352ede7f47938f3506c687a7204ac762da44abd949a9788f61fd7dce096e41c222dafcbc6ee67c4ee304aede850f7f7ffcae90b42137
7
+ data.tar.gz: 4f13684ec5f770e321f713747460b095033ff39e2c1e9b67f9afc82ac69bec057d4f5e32f4342d88fb763344be9f710aa42e04026fcc44dd824d66a7517e4c69
data/CHANGES.md CHANGED
@@ -1,3 +1,30 @@
1
+ # 0.3.0
2
+
3
+ * Fix circuit interface callable to make `step task: :instance_method` use circuit signature.
4
+
5
+ # 0.2.9
6
+
7
+ * The `Path()` helper, when used with `:end_task` will now automatically _append_ the end task (or terminus) to `End.success`.
8
+ It used to be placed straight after the last path's element, which made it hard to later insert more steps into that very path.
9
+
10
+ # 0.2.8
11
+
12
+ * Add `:inherit` option so `step` can override an existing step while inheriting the original `:extensions` and `:connections` (which are the `Outputs`). This is great to customize "template" activities.
13
+ * Add `Track(:color, wrap_around: true)` option and `Search::WrapAround` so you can find a certain track color (or the beginning of a Path) even when the path was positioned before the actual step in the `Sequence`.
14
+ Note that this feature is still experimental and might get removed.
15
+
16
+ # 0.2.7
17
+
18
+ * `Did you mean ?` suggestions on Linear::Sequence::IndexError.
19
+ * Introduce `Linear::Helper` module for strategy extensions in third-party gems.
20
+ * Convenient way to patch Subprocess itself using `patch` option.
21
+ * Allow multiple `Path()` macro per step.
22
+ * Small fix for defining instance methods as steps using circuit interface.
23
+
24
+ # 0.2.6
25
+
26
+ * Added `@fields` to `Linear::State` to save arbitrary data on the activity/strategy level.
27
+
1
28
  # 0.2.5
2
29
 
3
30
  * Patching now requires the [`:patch` option for `Subprocess`](http://2019.trailblazer.to/2.1/docs/activity.html#activity-dsl-options-patching
data/Gemfile CHANGED
@@ -10,3 +10,4 @@ gem "rubocop", require: false
10
10
  # gem "trailblazer-context", path: "../trailblazer-context"
11
11
  # gem "trailblazer-developer", path: "../trailblazer-developer"
12
12
  # gem "trailblazer-activity", path: "../trailblazer-activity"
13
+ # gem "trailblazer-activity", path: "../circuit"
data/README.md CHANGED
@@ -1,29 +1,93 @@
1
- # Trailblazer-Activity-DSL-Linear
1
+ # Activity-DSL-Linear
2
2
 
3
- _The popular Railway/Fasttrack DSL for building activities._
3
+ The `activity-dsl-linear` gem brings:
4
+ - [Path](https://2019.trailblazer.to/2.1/docs/activity.html#activity-strategy-path)
5
+ - [Railway](https://2019.trailblazer.to/2.1/docs/activity.html#activity-strategy-railway)
6
+ - [Fasttrack](https://2019.trailblazer.to/2.1/docs/activity.html#activity-strategy-fasttrack)
7
+
8
+ DSLs strategies for buildig activities. It is build around [`activity`](https://github.com/trailblazer/trailblazer-activity) gem.
4
9
 
10
+ Please find the [full documentation on the Trailblazer website](https://2019.trailblazer.to/2.1/docs/activity.html#activity-strategy). [Note that the docs are WIP.]
5
11
 
6
- # Overview
12
+ ## Example
7
13
 
8
- This gem allows creating activities by leveraging a handy DSL. Built-in are the strategies `Path`, the popular `Railway` and `FastTrack`. The latter is used for `Trailblazer::Operation`.
14
+ The `activity-dsl-linear` gem provides three default patterns to model processes: `Path`, `Railway` and `FastTrack`. Here's an example of what a railway activity could look like, along with some more complex connections (you can read more about Railway strategy in the [docs](https://2019.trailblazer.to/2.1/docs/activity.html#activity-strategy-railway)).
9
15
 
10
- Note that you don't need to use the DSL. You can simply create a InIm structure yourself, or use our online editor.
16
+ ```ruby
17
+ require "trailblazer-activity"
18
+ require "trailblazer-activity-dsl-linear"
11
19
 
12
- Full documentation can be found here: trailblazer.to/2.1/#dsl-linear
20
+ class Memo::Update < Trailblazer::Activity::Railway
21
+ # here goes your business logic
22
+ #
23
+ def find_model(ctx, id:, **)
24
+ ctx[:model] = Memo.find_by(id: id)
25
+ end
13
26
 
14
- ## Normalizer
27
+ def validate(ctx, params:, **)
28
+ return true if params[:body].is_a?(String) && params[:body].size > 10
29
+ ctx[:errors] = "body not long enough"
30
+ false
31
+ end
15
32
 
16
- Normalizers are itself linear activities (or "pipelines") that compute all options necessary for `DSL.insert_task`.
17
- For example, `FailFast.normalizer` will process your options such as `fast_track: true` and add necessary connections and outputs.
33
+ def save(ctx, model:, params:, **)
34
+ model.update_attributes(params)
35
+ end
18
36
 
19
- The different "step types" (think of `step`, `fail`, and `pass`) are again implemented as different normalizers that "inherit" generic steps.
37
+ def log_error(ctx, params:, **)
38
+ ctx[:log] = "Some idiot wrote #{params.inspect}"
39
+ end
20
40
 
41
+ # here comes the DSL describing the layout of the activity
42
+ #
43
+ step :find_model
44
+ step :validate, Output(:failure) => End(:validation_error)
45
+ step :save
46
+ fail :log_error
47
+ end
48
+ ```
21
49
 
22
- `:sequence_insert`
23
- `:connections` are callables to find the connecting tasks
50
+ Visually, this would translate to the following circuit.
51
+
52
+ <img src="http://trailblazer.to/images/2.1/activity-readme-example.png">
53
+
54
+ You can run the activity by invoking its `call` method.
55
+
56
+ ```ruby
57
+ ctx = { id: 1, params: { body: "Awesome!" } }
58
+
59
+ signal, (ctx, *) = Update.( [ctx, {}] )
60
+
61
+ pp ctx #=>
62
+ {:id=>1,
63
+ :params=>{:body=>"Awesome!"},
64
+ :model=>#<Memo body=nil>,
65
+ :errors=>"body not long enough"}
66
+
67
+ pp signal #=> #<struct Trailblazer::Activity::End semantic=:validation_error>
68
+ ```
69
+
70
+ With Activity, modeling business processes turns out to be ridiculously simple: You define what should happen and when, and Trailblazer makes sure _that_ it happens.
71
+
72
+ ## Features
73
+
74
+ * Activities can model any process with arbitrary flow and connections.
75
+ * Nesting and compositions are allowed and encouraged (via Trailblazer's [`dsl-linear`](https://github.com/trailblazer/trailblazer-activity-dsl-linear) gem).
76
+ * Different step interfaces, manual processing of DSL options, etc is all possible.
77
+ * Steps can be any kind of callable objects.
78
+ * Tracing! (via Trailblazer's [`developer`](https://github.com/trailblazer/trailblazer-developer) gem)
79
+
80
+ ## Operation
81
+
82
+ Trailblazer's [`Operation`](https://2019.trailblazer.to/2.1/docs/operation.html#operation-overview) internally uses an activity to model the processes.
83
+
84
+ ## Workflow
85
+ Activities can be formed into bigger compounds and using workflow, you can build long-running processes such as a moderated blog post or a parcel delivery. Also, you don't have to use the DSL but can use the [`editor`](https://2019.trailblazer.to/2.1/docs/pro.html#pro-editor)instead(cool for more complex, long-running flows). Here comes a sample screenshot.
86
+
87
+ <img src="http://2019.trailblazer.to/2.1/dist/img/flow.png">
24
88
 
25
89
  ## License
26
90
 
27
91
  © Copyright 2018, Trailblazer GmbH
28
92
 
29
- Licensed under the LGPLv3 license. We also offer a [commercial-friendly license](http://trailblazer.to/pro).
93
+ Licensed under the LGPLv3 license. We also offer a commercial-friendly [license](https://2019.trailblazer.to/2.1/docs/pro.html#pro-license).
@@ -37,7 +37,17 @@ class Trailblazer::Activity
37
37
  sequence
38
38
  end
39
39
 
40
- class IndexError < IndexError; end
40
+ class IndexError < IndexError
41
+ attr_reader :step_id
42
+
43
+ def initialize(sequence, step_id)
44
+ @step_id = step_id
45
+ valid_ids = sequence.collect{ |row| row[3][:id].inspect }
46
+
47
+ message = %{#{@step_id.inspect} is not a valid step ID. Did you mean any of these ?\n#{valid_ids.join("\n")}}
48
+ super(message)
49
+ end
50
+ end
41
51
  end
42
52
 
43
53
  # Sequence
@@ -48,7 +58,21 @@ class Trailblazer::Activity
48
58
  # Note that we only go forward, no back-references are done here.
49
59
  def Forward(output, target_color)
50
60
  ->(sequence, me) do
51
- target_seq_row = sequence[sequence.index(me)+1..-1].find { |seq_row| seq_row[0] == target_color }
61
+ target_seq_row = find_in_range(sequence[sequence.index(me)+1..-1], target_color)
62
+
63
+ return output, target_seq_row
64
+ end
65
+ end
66
+
67
+ # Tries to find a track colored step by doing a Forward-search, first, then wraps around going
68
+ # through all steps from sequence start to self.
69
+ def WrapAround(output, target_color)
70
+ ->(sequence, me) do
71
+ my_index = sequence.index(me)
72
+ # First, try all elements after me, then go through the elements preceding myself.
73
+ wrapped_range = sequence[my_index+1..-1] + sequence[0..my_index-1]
74
+
75
+ target_seq_row = find_in_range(wrapped_range, target_color)
52
76
 
53
77
  return output, target_seq_row
54
78
  end
@@ -69,6 +93,11 @@ class Trailblazer::Activity
69
93
  return output, target_seq_row
70
94
  end
71
95
  end
96
+
97
+ # @private
98
+ def find_in_range(range, target_color)
99
+ target_seq_row = range.find { |seq_row| seq_row[0] == target_color }
100
+ end
72
101
  end # Search
73
102
 
74
103
  # Sequence
@@ -111,7 +140,7 @@ class Trailblazer::Activity
111
140
  end
112
141
 
113
142
  def find(sequence, insert_id)
114
- index = find_index(sequence, insert_id) or raise Sequence::IndexError.new(insert_id.inspect)
143
+ index = find_index(sequence, insert_id) or raise Sequence::IndexError.new(sequence, insert_id)
115
144
 
116
145
  return index, sequence.clone # Ruby doesn't have an easy way to avoid mutating arrays :(
117
146
  end
@@ -157,10 +186,10 @@ end
157
186
 
158
187
  require "trailblazer/activity/dsl/linear/normalizer"
159
188
  require "trailblazer/activity/dsl/linear/state"
189
+ require "trailblazer/activity/dsl/linear/helper"
160
190
  require "trailblazer/activity/dsl/linear/strategy"
161
191
  require "trailblazer/activity/dsl/linear/compiler"
162
192
  require "trailblazer/activity/path"
163
193
  require "trailblazer/activity/railway"
164
194
  require "trailblazer/activity/fast_track"
165
- require "trailblazer/activity/dsl/linear/helper" # FIXME
166
195
  require "trailblazer/activity/dsl/linear/variable_mapping"
@@ -1,125 +1,150 @@
1
1
  module Trailblazer
2
2
  class Activity
3
- module DSL::Linear # TODO: rename!
4
- # @api private
5
- OutputSemantic = Struct.new(:value)
6
- Id = Struct.new(:value)
7
- Track = Struct.new(:color, :adds)
8
- Extension = Struct.new(:callable) do
9
- def call(*args, &block)
10
- callable.(*args, &block)
11
- end
12
- end
13
-
14
- # Shortcut functions for the DSL.
15
- module_function
16
-
17
- # Output( Left, :failure )
18
- # Output( :failure ) #=> Output::Semantic
19
- def Output(signal, semantic=nil)
20
- return OutputSemantic.new(signal) if semantic.nil?
21
-
22
- Activity.Output(signal, semantic)
23
- end
24
-
25
- def End(semantic)
26
- Activity.End(semantic)
27
- end
28
-
29
- def end_id(_end)
30
- "End.#{_end.to_h[:semantic]}" # TODO: use everywhere
31
- end
32
-
33
- def Track(color)
34
- Track.new(color, []).freeze
35
- end
36
-
37
- def Id(id)
38
- Id.new(id).freeze
39
- end
40
-
41
- def Path(track_color: "track_#{rand}", end_id:"path_end_#{rand}", connect_to:nil, **options, &block)
42
- # DISCUSS: here, we use the global normalizer and don't allow injection.
43
- state = Activity::Path::DSL::State.new(Activity::Path::DSL.OptionsForState(track_name: track_color, end_id: end_id, **options)) # TODO: test injecting {:normalizers}.
44
-
45
- # seq = block.call(state) # state changes.
46
- state.instance_exec(&block)
47
-
48
- seq = state.to_h[:sequence]
49
-
50
- _end_id =
51
- if connect_to
52
- end_id
53
- else
54
- nil
3
+ module DSL
4
+ module Linear
5
+ module Helper
6
+ # @api private
7
+ OutputSemantic = Struct.new(:value)
8
+ Id = Struct.new(:value)
9
+ Track = Struct.new(:color, :adds, :options)
10
+ Extension = Struct.new(:callable) do
11
+ def call(*args, &block)
12
+ callable.(*args, &block)
13
+ end
14
+ end
15
+
16
+ def self.included(base)
17
+ base.extend ClassMethods
55
18
  end
56
19
 
57
- seq = strip_start_and_ends(seq, end_id: _end_id) # don't cut off end unless {:connect_to} is set.
20
+ # Shortcut functions for the DSL.
21
+ module ClassMethods
22
+ # Output( Left, :failure )
23
+ # Output( :failure ) #=> Output::Semantic
24
+ def Output(signal, semantic=nil)
25
+ return OutputSemantic.new(signal) if semantic.nil?
26
+
27
+ Activity.Output(signal, semantic)
28
+ end
29
+
30
+ def End(semantic)
31
+ Activity.End(semantic)
32
+ end
33
+
34
+ def end_id(_end)
35
+ "End.#{_end.to_h[:semantic]}" # TODO: use everywhere
36
+ end
37
+
38
+ def Track(color, wrap_around: false)
39
+ Track.new(color, [], wrap_around: wrap_around).freeze
40
+ end
41
+
42
+ def Id(id)
43
+ Id.new(id).freeze
44
+ end
45
+
46
+ def Path(track_color: "track_#{rand}", end_id:"path_end_#{rand}", connect_to:nil, **options, &block)
47
+ # DISCUSS: here, we use the global normalizer and don't allow injection.
48
+ state = Activity::Path::DSL::State.new(Activity::Path::DSL.OptionsForState(track_name: track_color, end_id: end_id, **options)) # TODO: test injecting {:normalizers}.
58
49
 
59
- if connect_to
60
- output, _ = seq[-1][2][0].(seq, seq[-1]) # FIXME: the Forward() proc contains the row's Output, and the only current way to retrieve it is calling the search strategy. It should be Forward#to_h
50
+ # seq = block.call(state) # state changes.
51
+ state.instance_exec(&block)
61
52
 
62
- searches = [Search.ById(output, connect_to.value)]
53
+ seq = state.to_h[:sequence]
63
54
 
64
- row = seq[-1]
65
- row = row[0..1] + [searches] + [row[3]] # FIXME: not mutating an array is so hard: we only want to replace the "searches" element, index 2
55
+ _end_id =
56
+ if connect_to
57
+ end_id
58
+ else
59
+ nil
60
+ end
66
61
 
67
- seq = seq[0..-2] + [row]
68
- end
62
+ seq = strip_start_and_ends(seq, end_id: _end_id) # don't cut off end unless {:connect_to} is set.
69
63
 
70
- # Add the path before End.success - not sure this is bullet-proof.
71
- adds = seq.collect do |row|
72
- {
73
- row: row,
74
- insert: [Insert.method(:Prepend), "End.success"]
75
- }
76
- end
64
+ if connect_to
65
+ output, _ = seq[-1][2][0].(seq, seq[-1]) # FIXME: the Forward() proc contains the row's Output, and the only current way to retrieve it is calling the search strategy. It should be Forward#to_h
77
66
 
78
- # Connect the Output() => Track(path_track)
79
- return Track.new(track_color, adds)
80
- end
67
+ searches = [Search.ById(output, connect_to.value)]
81
68
 
82
- # Computes the {:outputs} options for {activity}.
83
- def Subprocess(activity, patch: {})
84
- patch.each do |path, patch|
85
- activity = Patch.(activity, path, patch) # TODO: test if multiple patches works!
86
- end
69
+ row = seq[-1]
70
+ row = row[0..1] + [searches] + [row[3]] # FIXME: not mutating an array is so hard: we only want to replace the "searches" element, index 2
87
71
 
88
- {
89
- task: activity,
90
- outputs: Hash[activity.to_h[:outputs].collect { |output| [output.semantic, output] }]
91
- }
92
- end
72
+ seq = seq[0..-2] + [row]
73
+ end
93
74
 
94
- module Patch
95
- module_function
75
+ # Add the path elements before {End.success}.
76
+ # Termini (or :stop_event) are to be placed after {End.success}.
77
+ adds = seq.collect do |row|
78
+ options = row[3]
96
79
 
97
- def call(activity, path, customization)
98
- task_id, *path = path
80
+ # the terminus of the path goes _after_ {End.success} into the "end group".
81
+ insert_method = options[:stop_event] ? Insert.method(:Append) : Insert.method(:Prepend)
99
82
 
83
+ {
84
+ row: row,
85
+ insert: [insert_method, "End.success"]
86
+ }
87
+ end
100
88
 
101
- patch =
102
- if task_id
103
- segment_activity = Introspect::Graph(activity).find(task_id).task
104
- patched_segment_activity = call(segment_activity, path, customization)
89
+ # Connect the Output() => Track(path_track)
90
+ return Track.new(track_color, adds, {})
91
+ end
92
+
93
+ # Computes the {:outputs} options for {activity}.
94
+ def Subprocess(activity, patch: {})
95
+ activity = Patch.customize(activity, options: patch)
96
+
97
+ {
98
+ task: activity,
99
+ outputs: Hash[activity.to_h[:outputs].collect { |output| [output.semantic, output] }]
100
+ }
101
+ end
102
+
103
+ module Patch
104
+ module_function
105
+
106
+ def customize(activity, options:)
107
+ options = options.is_a?(Proc) ?
108
+ { [] => options } : # hash-wrapping with empty path, for patching given activity itself
109
+ options
110
+
111
+ options.each do |path, patch|
112
+ activity = call(activity, path, patch) # TODO: test if multiple patches works!
113
+ end
105
114
 
106
- # Replace the patched subprocess.
107
- -> { step Subprocess(patched_segment_activity), replace: task_id, id: task_id }
108
- else
109
- customization # apply the *actual* patch from the Subprocess() call.
115
+ activity
116
+ end
117
+
118
+ def call(activity, path, customization)
119
+ task_id, *path = path
120
+
121
+ patch =
122
+ if task_id
123
+ segment_activity = Introspect::Graph(activity).find(task_id).task
124
+ patched_segment_activity = call(segment_activity, path, customization)
125
+
126
+ # Replace the patched subprocess.
127
+ -> { step Subprocess(patched_segment_activity), replace: task_id, id: task_id }
128
+ else
129
+ customization # apply the *actual* patch from the Subprocess() call.
130
+ end
131
+
132
+ patched_activity = Class.new(activity)
133
+ patched_activity.class_exec(&patch)
134
+ patched_activity
135
+ end
110
136
  end
111
137
 
112
- patched_activity = Class.new(activity)
113
- patched_activity.class_exec(&patch)
114
- patched_activity
115
- end
116
- end
117
-
118
- def normalize(options, local_keys) # TODO: test me.
119
- locals = options.reject { |key, value| ! local_keys.include?(key) }
120
- foreign = options.reject { |key, value| local_keys.include?(key) }
121
- return foreign, locals
122
- end
123
- end
124
- end
138
+ def normalize(options, local_keys) # TODO: test me.
139
+ locals = options.reject { |key, value| ! local_keys.include?(key) }
140
+ foreign = options.reject { |key, value| local_keys.include?(key) }
141
+ return foreign, locals
142
+ end
143
+ end
144
+ end # Helper
145
+
146
+ include Helper # Introduce Helper constants in DSL::Linear scope
147
+ end # Linear
148
+ end # DSL
149
+ end # Activity
125
150
  end
@@ -20,6 +20,7 @@ module Trailblazer
20
20
  "activity.normalize_id" => method(:normalize_id),
21
21
  "activity.normalize_override" => method(:normalize_override),
22
22
  "activity.wrap_task_with_step_interface" => method(:wrap_task_with_step_interface), # last
23
+ "activity.inherit_option" => method(:inherit_option),
23
24
  },
24
25
 
25
26
  Linear::Insert.method(:Append), "Start.default"
@@ -53,16 +54,26 @@ module Trailblazer
53
54
  # Specific to the "step DSL": if the first argument is a callable, wrap it in a {step_interface_builder}
54
55
  # since its interface expects the step interface, but the circuit will call it with circuit interface.
55
56
  def normalize_step_interface((ctx, flow_options), *)
56
- options = ctx[:options] # either a <#task> or {} from macro
57
-
58
- unless options.is_a?(::Hash)
59
- # task = wrap_with_step_interface(task: options, step_interface_builder: ctx[:user_options][:step_interface_builder]) # TODO: make this optional with appropriate wiring.
60
- task = options
61
-
62
- ctx = ctx.merge(options: {task: task, wrap_task: true})
63
- end
64
-
65
- return Trailblazer::Activity::Right, [ctx, flow_options]
57
+ options = case ctx[:options]
58
+ when Hash
59
+ # Circuit Interface
60
+ task = ctx[:options].fetch(:task)
61
+
62
+ if task.is_a?(Symbol)
63
+ # step task: :find
64
+ { options: { task: Trailblazer::Option( task ) } }
65
+ else
66
+ # step task: Callable, ... (Subprocess, Proc, macros etc)
67
+ { task: task }
68
+ end
69
+ else
70
+ # Step Interface
71
+ # step :find, ...
72
+ # step Callable, ... (Method, Proc etc)
73
+ { options: { task: ctx[:options], wrap_task: true } }
74
+ end
75
+
76
+ return Trailblazer::Activity::Right, [ctx.merge(options), flow_options]
66
77
  end
67
78
 
68
79
  def wrap_task_with_step_interface((ctx, flow_options), **)
@@ -122,7 +133,7 @@ module Trailblazer
122
133
 
123
134
  ctx[:wirings] =
124
135
  connections.collect do |semantic, (search_strategy_builder, *search_args)|
125
- output = outputs[semantic] || raise("No `#{semantic}` output found for #{outputs.inspect}")
136
+ output = outputs[semantic] || raise("No `#{semantic}` output found for #{ctx[:id].inspect} and outputs #{outputs.inspect}")
126
137
 
127
138
  search_strategy_builder.( # return proc to be called when compiling Seq, e.g. {ById(output, :id)}
128
139
  output,
@@ -157,6 +168,8 @@ module Trailblazer
157
168
  _adds = [add_end(cfg, magnetic_to: end_id, id: end_id)] unless end_exists
158
169
 
159
170
  [output_to_id(ctx, output, end_id), _adds]
171
+ else
172
+ raise cfg.inspect
160
173
  end
161
174
 
162
175
  connections = connections.merge(new_connections)
@@ -168,8 +181,10 @@ module Trailblazer
168
181
  return Trailblazer::Activity::Right, [new_ctx, flow_options]
169
182
  end
170
183
 
171
- def output_to_track(ctx, output, target)
172
- {output.value => [Linear::Search.method(:Forward), target.color]}
184
+ def output_to_track(ctx, output, track)
185
+ search_strategy = track.options[:wrap_around] ? :WrapAround : :Forward
186
+
187
+ {output.value => [Linear::Search.method(search_strategy), track.color]}
173
188
  end
174
189
 
175
190
  def output_to_id(ctx, output, target)
@@ -219,9 +234,28 @@ module Trailblazer
219
234
  return Trailblazer::Activity::Right, [ctx.merge(new_ctx), flow_options]
220
235
  end
221
236
 
237
+ # Currently, the {:inherit} option copies over {:connections} from the original step
238
+ # and merges them with the (prolly) connections passed from the user.
239
+ def inherit_option((ctx, flow_options), *)
240
+ return Trailblazer::Activity::Right, [ctx, flow_options] unless ctx[:inherit]
241
+
242
+ sequence = ctx[:sequence]
243
+ id = ctx[:id]
244
+
245
+ index = Linear::Insert.find_index(sequence, id)
246
+ row = sequence[index] # from this row we're inheriting options.
247
+
248
+ extensions = (row[3][:extensions]||[]) + (ctx[:extensions]||[]) # FIXME: DEFAULTING, FOR FUCK'S SAKE
249
+
250
+ ctx = ctx.merge(connections: row[3][:connections], extensions: extensions) # "inherit"
251
+
252
+ return Trailblazer::Activity::Right, [ctx, flow_options]
253
+ end
254
+
222
255
  # TODO: make this extendable!
223
256
  def cleanup_options((ctx, flow_options), *)
224
- new_ctx = ctx.reject { |k, v| [:connections, :outputs, :end_id, :step_interface_builder, :failure_end, :track_name, :sequence].include?(k) }
257
+ # new_ctx = ctx.reject { |k, v| [:connections, :outputs, :end_id, :step_interface_builder, :failure_end, :track_name, :sequence].include?(k) }
258
+ new_ctx = ctx.reject { |k, v| [:outputs, :end_id, :step_interface_builder, :failure_end, :track_name, :sequence].include?(k) }
225
259
 
226
260
  return Trailblazer::Activity::Right, [new_ctx, flow_options]
227
261
  end
@@ -3,28 +3,40 @@ module Trailblazer
3
3
  module DSL
4
4
  module Linear
5
5
  # A {State} instance is kept per DSL client, which usually is a subclass of {Path}, {Railway}, etc.
6
+ # State doesn't have any immutable features - all write operations to it must guarantee they only replace
7
+ # instance variables.
8
+ #
9
+ # @private
10
+ #
11
+ # DISCUSS: why do we have this structure? It doesn't cover "immutable copying", that has to be done by its clients.
12
+ # also, copy with to_h
6
13
  class State
7
14
  # remembers how to call normalizers (e.g. track_color), TaskBuilder
8
15
  # remembers sequence
9
- def initialize(normalizers:, initial_sequence:, **normalizer_options)
16
+ def initialize(normalizers:, initial_sequence:, fields: {}.freeze, **normalizer_options)
10
17
  @normalizer = normalizers # compiled normalizers.
11
18
  @sequence = initial_sequence
12
19
  @normalizer_options = normalizer_options
20
+ @fields = fields
13
21
  end
14
22
 
15
23
  # Called to "inherit" a state.
16
24
  def copy
17
- self.class.new(normalizers: @normalizer, initial_sequence: @sequence, **@normalizer_options)
25
+ self.class.new(normalizers: @normalizer, initial_sequence: @sequence, fields: @fields, **@normalizer_options)
18
26
  end
19
27
 
20
28
  def to_h
21
- {sequence: @sequence, normalizers: @normalizer, normalizer_options: @normalizer_options} # FIXME.
29
+ {sequence: @sequence, normalizers: @normalizer, normalizer_options: @normalizer_options, fields: @fields} # FIXME.
22
30
  end
23
31
 
24
32
  def update_sequence(&block)
25
33
  @sequence = yield(to_h)
26
34
  end
27
35
 
36
+ def update_options(fields)
37
+ @fields = fields
38
+ end
39
+
28
40
  # Compiles and maintains all final normalizers for a specific DSL.
29
41
  class Normalizer
30
42
  def compile_normalizer(normalizer_sequence)
@@ -1,5 +1,3 @@
1
- require "forwardable"
2
-
3
1
  module Trailblazer
4
2
  class Activity
5
3
  module DSL
@@ -29,7 +27,7 @@ module Trailblazer
29
27
  options = options.merge(dsl_track: type)
30
28
 
31
29
  # {#update_sequence} is the only way to mutate the state instance.
32
- state.update_sequence do |sequence:, normalizers:, normalizer_options:|
30
+ state.update_sequence do |sequence:, normalizers:, normalizer_options:, fields:|
33
31
  # Compute the sequence rows.
34
32
  options = normalizers.(type, normalizer_options: normalizer_options, options: task, user_options: options.merge(sequence: sequence))
35
33
 
@@ -58,23 +56,29 @@ module Trailblazer
58
56
 
59
57
  private def forward_block(args, block)
60
58
  options = args[1]
61
- if options.is_a?(Hash) # FIXME: doesn't account {task: <>} and repeats logic from Normalizer.
62
- output, proxy = (options.find { |k,v| v.is_a?(BlockProxy) } or return args)
59
+
60
+ return args unless options.is_a?(Hash)
61
+
62
+ # FIXME: doesn't account {task: <>} and repeats logic from Normalizer.
63
+
64
+ # DISCUSS: THIS SHOULD BE DONE IN DSL.Path() which is stateful! the block forwarding should be the only thing happening here!
65
+ evaluated_options =
66
+ options.find_all { |k,v| v.is_a?(BlockProxy) }.collect do |output, proxy|
63
67
  shared_options = {step_interface_builder: @state.instance_variable_get(:@normalizer_options)[:step_interface_builder]} # FIXME: how do we know what to pass on and what not?
64
- return args[0], options.merge(output => Linear.Path(**shared_options, **proxy.options, &block))
68
+
69
+ [output, Linear.Path(**shared_options, **proxy.options, &(proxy.block || block))] # FIXME: the || sucks.
65
70
  end
66
71
 
67
- args
68
- end
72
+ evaluated_options = Hash[evaluated_options]
69
73
 
70
- extend Forwardable
71
- def_delegators Linear, :Output, :End, :Track, :Id, :Subprocess
74
+ return args[0], options.merge(evaluated_options)
75
+ end
72
76
 
73
- def Path(options) # we can't access {block} here, syntactically.
74
- BlockProxy.new(options)
77
+ def Path(options, &block) # syntactically, we can't access the {do ... end} block here.
78
+ BlockProxy.new(options, block)
75
79
  end
76
80
 
77
- BlockProxy = Struct.new(:options)
81
+ BlockProxy = Struct.new(:options, :block)
78
82
 
79
83
  private def merge!(activity)
80
84
  old_seq = @state.instance_variable_get(:@sequence) # TODO: fixme
@@ -3,7 +3,7 @@ module Trailblazer
3
3
  module Activity
4
4
  module DSL
5
5
  module Linear
6
- VERSION = "0.2.5"
6
+ VERSION = "0.3.0"
7
7
  end
8
8
  end
9
9
  end
@@ -154,6 +154,7 @@ module Trailblazer
154
154
  end
155
155
  end
156
156
 
157
+ include Activity::DSL::Linear::Helper
157
158
  extend Activity::DSL::Linear::Strategy
158
159
 
159
160
  initialize!(Railway::DSL::State.new(DSL.OptionsForState()))
@@ -177,6 +177,7 @@ module Trailblazer
177
177
 
178
178
  end # DSL
179
179
 
180
+ include DSL::Linear::Helper
180
181
  extend DSL::Linear::Strategy
181
182
 
182
183
  initialize!(Path::DSL::State.new(DSL.OptionsForState()))
@@ -90,6 +90,8 @@ module Trailblazer
90
90
  )
91
91
  end
92
92
 
93
+ # Add {:failure} output to {:outputs}.
94
+ # TODO: assert that failure_outputs doesn't override existing {:outputs}
93
95
  def normalize_path_outputs((ctx, flow_options), *)
94
96
  outputs = failure_outputs.merge(ctx[:outputs])
95
97
  ctx = ctx.merge(outputs: outputs)
@@ -132,7 +134,6 @@ module Trailblazer
132
134
  pass: Linear::Normalizer.activity_normalizer( Railway::DSL.normalizer_for_pass ),
133
135
  )
134
136
 
135
-
136
137
  def self.OptionsForState(normalizers: Normalizers, failure_end: Activity::End.new(semantic: :failure), **options)
137
138
  options = Path::DSL.OptionsForState(options).
138
139
  merge(normalizers: normalizers, failure_end: failure_end)
@@ -157,6 +158,7 @@ module Trailblazer
157
158
  end
158
159
  end
159
160
 
161
+ include DSL::Linear::Helper
160
162
  extend DSL::Linear::Strategy
161
163
 
162
164
  initialize!(Railway::DSL::State.new(DSL.OptionsForState()))
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: trailblazer-activity-dsl-linear
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.5
4
+ version: 0.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Nick Sutterer
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2020-01-08 00:00:00.000000000 Z
11
+ date: 2020-08-10 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: trailblazer-activity
@@ -135,8 +135,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
135
135
  - !ruby/object:Gem::Version
136
136
  version: '0'
137
137
  requirements: []
138
- rubyforge_project:
139
- rubygems_version: 2.7.3
138
+ rubygems_version: 3.0.8
140
139
  signing_key:
141
140
  specification_version: 4
142
141
  summary: Simple DSL to define Trailblazer activities.