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 + +