trailblazer-activity-dsl-linear 0.1.0

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.
@@ -0,0 +1,11 @@
1
+ module Trailblazer
2
+ module Version
3
+ module Activity
4
+ module DSL
5
+ module Linear
6
+ VERSION = "0.1.0"
7
+ end
8
+ end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,163 @@
1
+ module Trailblazer
2
+ class Activity
3
+ def self.FastTrack(options)
4
+ Class.new(FastTrack) do
5
+ initialize!(Railway::DSL::State.new(FastTrack::DSL.OptionsForState(options)))
6
+ end
7
+ end
8
+
9
+ # Implementation module that can be passed to `Activity[]`.
10
+ class FastTrack
11
+ Linear = Activity::DSL::Linear
12
+
13
+ # Signals
14
+ FailFast = Class.new(Signal)
15
+ PassFast = Class.new(Signal)
16
+
17
+ module DSL
18
+ module_function
19
+
20
+ def normalizer
21
+ step_options(Trailblazer::Activity::Railway::DSL.normalizer)
22
+ end
23
+
24
+ def normalizer_for_fail
25
+ sequence = step_options(Trailblazer::Activity::Railway::DSL.normalizer_for_fail)
26
+
27
+ Path::DSL.prepend_to_path(
28
+ sequence,
29
+
30
+ {
31
+ "fast_track.fail_fast_option_for_fail" => method(:fail_fast_option_for_fail),
32
+ },
33
+ Linear::Insert.method(:Prepend), "path.wirings"
34
+ )
35
+ end
36
+
37
+ def normalizer_for_pass
38
+ sequence = step_options(Trailblazer::Activity::Railway::DSL.normalizer_for_pass)
39
+
40
+ Path::DSL.prepend_to_path(
41
+ sequence,
42
+
43
+ {
44
+ "fast_track.pass_fast_option_for_pass" => method(:pass_fast_option_for_pass),
45
+ },
46
+ Linear::Insert.method(:Prepend), "path.wirings"
47
+ )
48
+ end
49
+
50
+ def step_options(sequence)
51
+ Path::DSL.prepend_to_path( # this doesn't particularly put the steps after the Path steps.
52
+ sequence,
53
+
54
+ {
55
+ "fast_track.pass_fast_option" => method(:pass_fast_option),
56
+ "fast_track.fail_fast_option" => method(:fail_fast_option),
57
+ "fast_track.fast_track_option" => method(:fast_track_option),
58
+ },
59
+ Linear::Insert.method(:Prepend), "path.wirings"
60
+ )
61
+ end
62
+
63
+ def pass_fast_option((ctx, flow_options), *)
64
+ ctx = merge_connections_for(ctx, ctx, :pass_fast, :success)
65
+
66
+ return Right, [ctx, flow_options]
67
+ end
68
+
69
+ def pass_fast_option_for_pass((ctx, flow_options), *)
70
+ ctx = merge_connections_for(ctx, ctx, :pass_fast, :failure)
71
+ ctx = merge_connections_for(ctx, ctx, :pass_fast, :success)
72
+
73
+ return Right, [ctx, flow_options]
74
+ end
75
+
76
+ def fail_fast_option((ctx, flow_options), *)
77
+ ctx = merge_connections_for(ctx, ctx, :fail_fast, :failure)
78
+
79
+ return Right, [ctx, flow_options]
80
+ end
81
+
82
+ def fail_fast_option_for_fail((ctx, flow_options), *)
83
+ ctx = merge_connections_for(ctx, ctx, :fail_fast, :failure)
84
+ ctx = merge_connections_for(ctx, ctx, :fail_fast, :success)
85
+
86
+ return Right, [ctx, flow_options]
87
+ end
88
+
89
+ def fast_track_option((ctx, flow_options), *)
90
+ return Right, [ctx, flow_options] unless ctx[:fast_track]
91
+
92
+ ctx = merge_connections_for(ctx, ctx, :fast_track, :fail_fast, :fail_fast)
93
+ ctx = merge_connections_for(ctx, ctx, :fast_track, :pass_fast, :pass_fast)
94
+
95
+ ctx = merge_outputs_for(ctx,
96
+ pass_fast: Activity.Output(Activity::FastTrack::PassFast, :pass_fast),
97
+ fail_fast: Activity.Output(Activity::FastTrack::FailFast, :fail_fast),
98
+ )
99
+
100
+ return Right, [ctx, flow_options]
101
+ end
102
+
103
+ def merge_connections_for(ctx, options, option_name, semantic, magnetic_to=option_name)
104
+ return ctx unless options[option_name]
105
+
106
+ connections = ctx[:connections].merge(semantic => [Linear::Search.method(:Forward), magnetic_to])
107
+ ctx = ctx.merge(connections: connections)
108
+ end
109
+
110
+ def merge_outputs_for(ctx, outputs)
111
+ ctx = ctx.merge(
112
+ outputs: outputs.merge(ctx[:outputs])
113
+ )
114
+ end
115
+
116
+ def initial_sequence(initial_sequence:, fail_fast_end: Activity::End.new(semantic: :fail_fast), pass_fast_end: Activity::End.new(semantic: :pass_fast), **)
117
+ sequence = initial_sequence
118
+
119
+ sequence = Path::DSL.append_end(sequence, task: fail_fast_end, magnetic_to: :fail_fast, id: "End.fail_fast")
120
+ sequence = Path::DSL.append_end(sequence, task: pass_fast_end, magnetic_to: :pass_fast, id: "End.pass_fast")
121
+ end
122
+
123
+
124
+
125
+ # This is slow and should be done only once at compile-time,
126
+ # DISCUSS: maybe make this a function?
127
+ # These are the normalizers for an {Activity}, to be injected into a State.
128
+ Normalizers = Linear::State::Normalizer.new(
129
+ step: Linear::Normalizer.activity_normalizer( FastTrack::DSL.normalizer ), # here, we extend the generic FastTrack::step_normalizer with the Activity-specific DSL
130
+ fail: Linear::Normalizer.activity_normalizer( FastTrack::DSL.normalizer_for_fail ),
131
+ pass: Linear::Normalizer.activity_normalizer( FastTrack::DSL.normalizer_for_pass ),
132
+ )
133
+
134
+ def self.OptionsForState(normalizers: Normalizers, **options)
135
+ options = Railway::DSL.OptionsForState(options).
136
+ merge(normalizers: normalizers)
137
+
138
+ initial_sequence = FastTrack::DSL.initial_sequence(**options)
139
+
140
+ {
141
+ **options,
142
+ initial_sequence: initial_sequence,
143
+ }
144
+ end
145
+ end # DSL
146
+
147
+ class << self
148
+ private def fail(*args, &block)
149
+ recompile_activity_for(:fail, *args, &block) # from Path::Strategy
150
+ end
151
+
152
+ private def pass(*args, &block)
153
+ recompile_activity_for(:pass, *args, &block) # from Path::Strategy
154
+ end
155
+ end
156
+
157
+ extend Activity::DSL::Linear::Strategy
158
+
159
+ initialize!(Railway::DSL::State.new(DSL.OptionsForState()))
160
+
161
+ end # FastTrack
162
+ end
163
+ end
@@ -0,0 +1,179 @@
1
+ module Trailblazer
2
+ class Activity
3
+ # {Strategy} that helps building simple linear activities.
4
+ class Path
5
+ # Functions that help creating a path-specific sequence.
6
+ module DSL
7
+ Linear = Activity::DSL::Linear
8
+
9
+ module_function
10
+
11
+ def normalizer
12
+ prepend_step_options(
13
+ initial_sequence(track_name: :success, end_task: Activity::End.new(semantic: :success), end_id: "End.success")
14
+ )
15
+ end
16
+
17
+ def start_sequence(track_name:)
18
+ start_default = Activity::Start.new(semantic: :default)
19
+ start_event = Linear::Sequence.create_row(task: start_default, id: "Start.default", magnetic_to: nil, wirings: [Linear::Search::Forward(unary_outputs[:success], track_name)])
20
+ sequence = Linear::Sequence[start_event]
21
+ end
22
+
23
+ # DISCUSS: still not sure this should sit here.
24
+ # Pseudo-DSL that prepends {steps} to {sequence}.
25
+ def prepend_to_path(sequence, steps, insertion_method=Linear::Insert.method(:Prepend), insert_id="End.success")
26
+ new_rows = steps.collect do |id, task|
27
+ Linear::Sequence.create_row(
28
+ task: task,
29
+ magnetic_to: :success,
30
+ wirings: [Linear::Search::Forward(unary_outputs[:success], :success)],
31
+ id: id,
32
+ )
33
+ end
34
+
35
+ insertion_method.(sequence, new_rows, insert_id)
36
+ end
37
+
38
+ def unary_outputs
39
+ {success: Activity::Output(Activity::Right, :success)}
40
+ end
41
+
42
+ def unary_connections(track_name: :success)
43
+ {success: [Linear::Search.method(:Forward), track_name]}
44
+ end
45
+
46
+ def merge_path_outputs((ctx, flow_options), *)
47
+ ctx = {outputs: unary_outputs}.merge(ctx)
48
+
49
+ return Right, [ctx, flow_options]
50
+ end
51
+
52
+ def merge_path_connections((ctx, flow_options), *)
53
+ raise unless track_name = ctx[:track_name]# TODO: make track_name required kw.
54
+ ctx = {connections: unary_connections(track_name: track_name)}.merge(ctx)
55
+
56
+ return Right, [ctx, flow_options]
57
+ end
58
+
59
+ # Processes {:before,:after,:replace,:delete} options and
60
+ # defaults to {before: "End.success"} which, yeah.
61
+ def normalize_sequence_insert((ctx, flow_options), *)
62
+ insertion = ctx.keys & sequence_insert_options.keys
63
+ insertion = insertion[0] || :before
64
+ raise if ctx[:end_id].nil? # FIXME
65
+ target = ctx[insertion] || ctx[:end_id]
66
+
67
+ insertion_method = sequence_insert_options[insertion]
68
+
69
+ ctx = ctx.merge(sequence_insert: [Linear::Insert.method(insertion_method), target])
70
+
71
+ return Right, [ctx, flow_options]
72
+ end
73
+
74
+ # @private
75
+ def sequence_insert_options
76
+ {
77
+ :before => :Prepend,
78
+ :after => :Append,
79
+ :replace => :Replace,
80
+ :delete => :Delete,
81
+ }
82
+ end
83
+
84
+ def normalize_magnetic_to((ctx, flow_options), *) # TODO: merge with Railway.merge_magnetic_to
85
+ raise unless track_name = ctx[:track_name]# TODO: make track_name required kw.
86
+
87
+ ctx = {magnetic_to: track_name}.merge(ctx)
88
+
89
+ return Right, [ctx, flow_options]
90
+ end
91
+
92
+ # Return {Path::Normalizer} sequence.
93
+ def prepend_step_options(sequence)
94
+ prepend_to_path(
95
+ sequence,
96
+
97
+ "path.outputs" => method(:merge_path_outputs),
98
+ "path.connections" => method(:merge_path_connections),
99
+ "path.sequence_insert" => method(:normalize_sequence_insert),
100
+ "path.magnetic_to" => method(:normalize_magnetic_to),
101
+ "path.wirings" => Linear::Normalizer.method(:compile_wirings),
102
+ )
103
+ end
104
+
105
+ # Returns an initial two-step sequence with {Start.default > End.success}.
106
+ def initial_sequence(track_name:, end_task:, end_id:)
107
+ # TODO: this could be an Activity itself but maybe a bit too much for now.
108
+ sequence = start_sequence(track_name: track_name)
109
+ sequence = append_end(sequence, task: end_task, magnetic_to: track_name, id: end_id, append_to: "Start.default")
110
+ end
111
+
112
+ def append_end(sequence, **options)
113
+ sequence = Linear::DSL.insert_task(sequence, **append_end_options(options))
114
+ end
115
+
116
+ def append_end_options(task:, magnetic_to:, id:, append_to: "End.success")
117
+ end_args = {sequence_insert: [Linear::Insert.method(:Append), append_to], stop_event: true}
118
+
119
+ {
120
+ task: task,
121
+ magnetic_to: magnetic_to,
122
+ id: id,
123
+ wirings: [
124
+ Linear::Search::Noop(
125
+ Activity::Output.new(task, task.to_h[:semantic]), # DISCUSS: do we really want to transport the semantic "in" the object?
126
+ # magnetic_to
127
+ )],
128
+ # outputs: {magnetic_to => },
129
+ # connections: {magnetic_to => [Linear::Search.method(:Noop)]},
130
+ **end_args
131
+ }
132
+ end
133
+
134
+ # This is slow and should be done only once at compile-time,
135
+ # DISCUSS: maybe make this a function?
136
+ # These are the normalizers for an {Activity}, to be injected into a State.
137
+ Normalizers = Linear::State::Normalizer.new(
138
+ step: Linear::Normalizer.activity_normalizer( Path::DSL.normalizer ), # here, we extend the generic FastTrack::step_normalizer with the Activity-specific DSL
139
+ )
140
+
141
+ def self.OptionsForState(normalizers: Normalizers, track_name: :success, end_task: Activity::End.new(semantic: :success), end_id: "End.success", **options)
142
+ initial_sequence = Path::DSL.initial_sequence(track_name: track_name, end_task: end_task, end_id: end_id) # DISCUSS: the standard initial_seq could be cached.
143
+
144
+ {
145
+ normalizers: normalizers,
146
+ initial_sequence: initial_sequence,
147
+
148
+ track_name: track_name,
149
+ end_id: end_id,
150
+ step_interface_builder: Activity::TaskBuilder.method(:Binary), # DISCUSS: this is currently the only option we want to pass on in Path() ?
151
+ adds: [],
152
+ **options
153
+ }
154
+ end
155
+
156
+ # Implements the actual API ({#step} and friends).
157
+ # This can be used later to create faster DSLs where the activity is compiled only once, a la
158
+ # Path() do ... end
159
+ class State < Linear::State
160
+ def step(*args)
161
+ seq = Linear::Strategy.task_for!(self, :step, *args) # mutate @state
162
+ end
163
+ end
164
+
165
+ end # DSL
166
+
167
+ extend DSL::Linear::Strategy
168
+
169
+ initialize!(Path::DSL::State.new(DSL.OptionsForState()))
170
+ end # Path
171
+
172
+ def self.Path(options)
173
+ Class.new(Path) do
174
+ initialize!(Path::DSL::State.new(Path::DSL.OptionsForState(options)))
175
+ end
176
+ end
177
+ end
178
+ end
179
+
@@ -0,0 +1,172 @@
1
+ module Trailblazer
2
+ # Implementation module that can be passed to `Activity[]`.
3
+ class Activity
4
+ class Railway
5
+ module DSL
6
+ Linear = Activity::DSL::Linear
7
+
8
+ module_function
9
+
10
+ def normalizer
11
+ step_options(Activity::Path::DSL.normalizer)
12
+ end
13
+
14
+ # Change some parts of the "normal" {normalizer} pipeline.
15
+ # TODO: make this easier, even at this step.
16
+
17
+ def normalizer_for_fail
18
+ sequence = normalizer
19
+
20
+ id = "railway.magnetic_to.fail"
21
+ task = Fail.method(:merge_magnetic_to)
22
+
23
+ # TODO: use prepend_to_path
24
+ sequence = Linear::DSL.insert_task(sequence,
25
+ task: task,
26
+ magnetic_to: :success, id: id,
27
+ wirings: [Linear::Search.Forward(Path::DSL.unary_outputs[:success], :success)],
28
+ sequence_insert: [Linear::Insert.method(:Prepend), "path.wirings"])
29
+
30
+ id = "railway.connections.fail.success_to_failure"
31
+ task = Fail.method(:connect_success_to_failure)
32
+
33
+ sequence = Linear::DSL.insert_task(sequence,
34
+ task: task,
35
+ magnetic_to: :success, id: id,
36
+ wirings: [Linear::Search.Forward(Path::DSL.unary_outputs[:success], :success)],
37
+ sequence_insert: [Linear::Insert.method(:Replace), "path.connections"])
38
+ end
39
+
40
+ def normalizer_for_pass
41
+ sequence = normalizer
42
+
43
+ id = "railway.connections.pass.failure_to_success"
44
+ task = Pass.method(:connect_failure_to_success)
45
+
46
+ sequence = Linear::DSL.insert_task(sequence,
47
+ task: task,
48
+ magnetic_to: :success, id: id,
49
+ wirings: [Linear::Search.Forward(Path::DSL.unary_outputs[:success], :success)],
50
+ sequence_insert: [Linear::Insert.method(:Append), "path.connections"])
51
+ end
52
+
53
+ module Fail
54
+ module_function
55
+
56
+ def merge_magnetic_to((ctx, flow_options), *)
57
+ ctx = ctx.merge(magnetic_to: :failure)
58
+
59
+ return Right, [ctx, flow_options]
60
+ end
61
+
62
+ def connect_success_to_failure((ctx, flow_options), *)
63
+ ctx = {connections: {success: [Linear::Search.method(:Forward), :failure]}}.merge(ctx)
64
+
65
+ return Right, [ctx, flow_options]
66
+ end
67
+ end
68
+
69
+ module Pass
70
+ module_function
71
+
72
+ def connect_failure_to_success((ctx, flow_options), *)
73
+ connections = ctx[:connections].merge({failure: [Linear::Search.method(:Forward), :success]})
74
+
75
+ return Right, [ctx.merge(connections: connections), flow_options]
76
+ end
77
+ end
78
+
79
+ # Add {Railway} steps to normalizer path.
80
+ def step_options(sequence)
81
+ Path::DSL.prepend_to_path( # this doesn't particularly put the steps after the Path steps.
82
+ sequence,
83
+
84
+ {
85
+ "railway.outputs" => method(:normalize_path_outputs),
86
+ "railway.connections" => method(:normalize_path_connections),
87
+ },
88
+
89
+ Linear::Insert.method(:Prepend), "path.wirings" # override where it's added.
90
+ )
91
+ end
92
+
93
+ def normalize_path_outputs((ctx, flow_options), *)
94
+ outputs = failure_outputs.merge(ctx[:outputs])
95
+ ctx = ctx.merge(outputs: outputs)
96
+
97
+ return Right, [ctx, flow_options]
98
+ end
99
+
100
+ def normalize_path_connections((ctx, flow_options), *)
101
+ connections = failure_connections.merge(ctx[:connections])
102
+ ctx = ctx.merge(connections: connections)
103
+
104
+ return Right, [ctx, flow_options]
105
+ end
106
+
107
+ def failure_outputs
108
+ {failure: Activity::Output(Activity::Left, :failure)}
109
+ end
110
+ def failure_connections
111
+ {failure: [Linear::Search.method(:Forward), :failure]}
112
+ end
113
+
114
+ def initial_sequence(failure_end:, initial_sequence:, **path_options)
115
+ # TODO: this could be an Activity itself but maybe a bit too much for now.
116
+ sequence = Path::DSL.append_end(initial_sequence, task: failure_end, magnetic_to: :failure, id: "End.failure")
117
+ end
118
+
119
+ class State < Path::DSL::State
120
+ def fail(*args)
121
+ seq = Linear::Strategy.task_for!(self, :fail, *args) # mutate @state
122
+ end
123
+
124
+ def pass(*args)
125
+ seq = Linear::Strategy.task_for!(self, :pass, *args) # mutate @state
126
+ end
127
+ end # Instance
128
+
129
+ Normalizers = Linear::State::Normalizer.new(
130
+ step: Linear::Normalizer.activity_normalizer( Railway::DSL.normalizer ), # here, we extend the generic FastTrack::step_normalizer with the Activity-specific DSL
131
+ fail: Linear::Normalizer.activity_normalizer( Railway::DSL.normalizer_for_fail ),
132
+ pass: Linear::Normalizer.activity_normalizer( Railway::DSL.normalizer_for_pass ),
133
+ )
134
+
135
+
136
+ def self.OptionsForState(normalizers: Normalizers, failure_end: Activity::End.new(semantic: :failure), **options)
137
+ options = Path::DSL.OptionsForState(options).
138
+ merge(normalizers: normalizers, failure_end: failure_end)
139
+
140
+ initial_sequence = Railway::DSL.initial_sequence(failure_end: failure_end, **options)
141
+
142
+ {
143
+ **options,
144
+ initial_sequence: initial_sequence,
145
+ }
146
+ end
147
+
148
+ end # DSL
149
+
150
+ class << self
151
+ private def fail(*args, &block)
152
+ recompile_activity_for(:fail, *args, &block)
153
+ end
154
+
155
+ private def pass(*args, &block)
156
+ recompile_activity_for(:pass, *args, &block)
157
+ end
158
+ end
159
+
160
+ extend DSL::Linear::Strategy
161
+
162
+ initialize!(Railway::DSL::State.new(DSL.OptionsForState()))
163
+
164
+ end # Railway
165
+
166
+ def self.Railway(options)
167
+ Class.new(Railway) do
168
+ initialize!(Railway::DSL::State.new(Railway::DSL.OptionsForState(options)))
169
+ end
170
+ end
171
+ end
172
+ end