lib/trailblazer/activity/dsl/linear/strategy.rb in trailblazer-activity-dsl-linear-0.5.0 vs lib/trailblazer/activity/dsl/linear/strategy.rb in trailblazer-activity-dsl-linear-1.0.0.beta1
- old
+ new
@@ -6,110 +6,155 @@
# holds the {@schema}
# provides DSL step/merge!
# provides DSL inheritance
# provides run-time {call}
# maintains the {state} with {seq} and normalizer options
- module Strategy
- def initialize!(state)
- @state = state
+ # This could be a class but we decided to leave it as a module that then gets
+ # extended into {Path} and friends. This won't trigger the inheritance (because)
+ # there is nothing to inherit.
+ class Strategy
+ extend Linear::Helper # import {Subprocess()} and friends as class methods. creates shortcuts to {Strategy.Output} etc.
+ include Linear::Helper::Constants
- recompile_activity!(@state.to_h[:sequence])
- end
+ class << self
+ def initialize!(state)
+ @state = state
+ end
- def inherited(inheriter)
- super
+ def inherited(inheriter)
+ super
- # inherits the {@sequence}, and options.
- inheriter.initialize!(@state.copy)
- end
+ # Inherits the {State:sequencer} and other options without recomputing anything.
+ inheriter.initialize!(@state.copy)
+ end
- # Called from {#step} and friends.
- def self.task_for!(state, type, task, options={}, &block)
- options = options.merge(dsl_track: type)
+ # @public
+ # We forward `step` to the Dsl (State) object.
+ # Recompiling the activity/sequence is a matter specific to Strategy (Railway etc).
+ def step(*args, &block); recompile_activity_for(:step, *args, &block); end
+ def terminus(*args); recompile_activity_for(:terminus, *args); end
- # {#update_sequence} is the only way to mutate the state instance.
- state.update_sequence do |sequence:, normalizers:, normalizer_options:, fields:|
- # Compute the sequence rows.
- options = normalizers.(type, normalizer_options: normalizer_options, options: task, user_options: options.merge(sequence: sequence))
+ private def recompile_activity_for(type, *args, &block)
+ sequence = apply_step_on_sequence_builder(type, *args, &block)
- sequence = Activity::DSL::Linear::DSL.apply_adds_from_dsl(sequence, **options)
+ recompile!(sequence)
end
- end
- # @public
- private def step(*args, &block)
- recompile_activity_for(:step, *args, &block)
- end
+ # TODO: make {rescue} optional, only in dev mode.
+ # @return Sequence
+ private def apply_step_on_sequence_builder(type, arg, options={}, &block)
+ return Sequence::Builder.(type, arg, options,
+ sequence: @state.get(:sequence),
+ normalizers: @state.get(:normalizers),
- private def recompile_activity_for(type, *args, &block)
- args = forward_block(args, block)
+ normalizer_options: @state.get(:normalizer_options),
- seq = @state.send(type, *args)
+ &block
+ )
- recompile_activity!(seq)
- rescue Sequence::IndexError
- # re-raise this exception with activity class prepended
- # to the message this time.
- raise $!, "#{self}:#{$!.message}"
- end
+ rescue Activity::Adds::IndexError
+ # re-raise this exception with activity class prepended
+ # to the message this time.
+ raise $!, "#{self}:#{$!.message}"
+ end
- private def recompile_activity!(seq)
- schema = Compiler.(seq)
+ private def recompile_activity(sequence)
+ schema = Sequence::Compiler.(sequence)
+ Activity.new(schema)
+ end
- @activity = Activity.new(schema)
- end
+ # DISCUSS: this should be the only way to "update" anything on state.
+ def recompile!(sequence)
+ activity = recompile_activity(sequence)
- private def forward_block(args, block)
- options = args[1]
+ @state.update!(:sequence) { |*| sequence }
+ @state.update!(:activity) { |*| activity }
+ end
- return args unless options.is_a?(Hash)
+ # Used only once per strategy class body.
+ def compile_strategy!(strategy, **options)
+ options = strategy.OptionsForSequenceBuilder(**options)
- # FIXME: doesn't account {task: <>} and repeats logic from Normalizer.
+ compile_strategy_for!(**options)
+ end
- # DISCUSS: THIS SHOULD BE DONE IN DSL.Path() which is stateful! the block forwarding should be the only thing happening here!
- evaluated_options =
- options.find_all { |k,v| v.is_a?(BlockProxy) }.collect do |output, proxy|
- 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?
+ def compile_strategy_for!(sequence:, normalizers:, **normalizer_options)
+ @state.update!(:normalizers) { normalizers } # immutable
+ @state.update!(:normalizer_options) { normalizer_options } # immutable
- [output, Linear.Path(**shared_options, **proxy.options, &(proxy.block || block))] # FIXME: the || sucks.
+ recompile!(sequence)
end
- evaluated_options = Hash[evaluated_options]
+ # Mainly used for introspection.
+ def to_h
+ activity = @state.get(:activity)
- return args[0], options.merge(evaluated_options)
- end
+ activity.to_h.to_h.merge(
+ activity: activity,
+ sequence: @state.get(:sequence),
+ )
+ end
- def Path(**options, &block) # syntactically, we can't access the {do ... end} block here.
- BlockProxy.new(options, block)
- end
+ # @Runtime
+ # Injects {:exec_context} so that {:instance_method}s work.
+ def call(args, **circuit_options)
+ activity = @state.get(:activity)
- BlockProxy = Struct.new(:options, :block)
+ activity.(
+ args,
+ **circuit_options.merge(exec_context: new)
+ )
+ end
- private def merge!(activity)
- old_seq = @state.instance_variable_get(:@sequence) # TODO: fixme
- new_seq = activity.instance_variable_get(:@state).instance_variable_get(:@sequence) # TODO: fix the interfaces
+ def invoke(*args)
+ TaskWrap.invoke(self, *args)
+ end
+ end # class << self
+ # FIXME: do we want class << self?!
- seq = Linear.Merge(old_seq, new_seq, end_id: "End.success")
+ module DSL
+ module_function
- @state.instance_variable_set(:@sequence, seq) # FIXME: hate this so much.
- end
+ def start_sequence(wirings: [])
+ start_default = Activity::Start.new(semantic: :default)
+ start_event = Linear::Sequence.create_row(task: start_default, id: "Start.default", magnetic_to: nil, wirings: wirings)
+ _sequence = Linear::Sequence[start_event]
+ end
- def to_h
- @activity.to_h.to_h.merge(activity: @activity)
- end
+ def Build(strategy, **options, &block)
+ Class.new(strategy) do
+ compile_strategy!(strategy::DSL, normalizers: @state.get(:normalizers), **options)
- # Injects {:exec_context} so that {:instance_method}s work.
- def call(args, **circuit_options)
- @activity.(
- args,
- **circuit_options.merge(exec_context: new)
- )
- end
+ class_exec(&block) if block_given?
+ end
+ end
+ end # DSL
- def invoke(*args)
- TaskWrap.invoke(self, *args)
+ # FIXME: move to State#dup
+ def self.copy(value, **) # DISCUSS: should that be here?
+ value.copy
end
+
+ require_relative "feature/merge"
+ extend Merge::DSL # {Strategy.merge!}
+
+ state = Declarative::State(
+ normalizers: [nil, {}], # immutable
+ normalizer_options: [nil, {}], # immutable
+
+ sequence: [nil, {}], # when inherited, call #dup
+ activity: [nil, {}], # when inherited, call #dup
+
+ fields: [Hash.new, {}],
+ )
+
+ initialize!(state) # build an empty State instance that can be copied and recompiled.
+ # override :sequencer, :sequence, :activity
+ # This is done in every subclass.
+ recompile!(DSL.start_sequence)
end # Strategy
end
end
end
end
+
+