lib/eco/api/session/config/workflow.rb in eco-helpers-2.6.4 vs lib/eco/api/session/config/workflow.rb in eco-helpers-2.7.0

- old
+ new

@@ -5,17 +5,17 @@ class Workflow extend Eco::API::Common::ClassHierarchy WORKFLOW_MODEL = [ :options, - {load: [{input: [:get, :filter]}, {people: [:get, :filter]}, :filter]}, + {load: [{input: %i[get filter]}, {people: %i[get filter]}, :filter]}, :usecases, :launch_jobs, - {post_launch: [:usecases, :launch_jobs]}, + {post_launch: %i[usecases launch_jobs]}, :report, :end, :close - ] + ].freeze class << self def stages model_attrs end @@ -38,11 +38,11 @@ end self.model = WORKFLOW_MODEL attr_reader :config - def initialize(name = nil, _parent: self, config:) + def initialize(name = nil, config:, _parent: self) # rubocop:disable Lint/UnderscorePrefixedVariableName @config = config @name = name @stages = {} @_parent = _parent @@ -95,40 +95,43 @@ # @return [Eco::API::Session::Config::Workflow] # 1. if block is provided provided, it returns the **current stage** object (to ease chainig). # 2. if block is not provided, it returns the **stage** referred by `key` def for(key = nil, &block) raise ArgumentError, "With no 'key', a block should be given." unless key || block_given? - self.tap do + tap do next yield(self) unless key next stage(key).for(&block) if block_given? return stage(key) end end alias_method :with, :for # Used in **configuration** time **to define** the **behaviour** the target (sub)stage `key` - # @note if a `block` is provided it will **not** `yield` the target stage immediately, but when the _workflow_ reaches the stage + # @note if a `block` is provided it will **not** `yield` the target stage immediately, + # but when the _workflow_ reaches the stage # @param key [Symbol, nil] cases: # - if `key` is not provided, it targets the _current stage_ # - if `key` is provided, it targets the specific _sub-stage_ # @yield [stage_workflow, io] the behaviour of the target stage `key` when the _workflow_ reaches it # @yieldparam stage_workflow [Eco::API::Session::Config::Workflow] the _target stage_ referred by `key` # @yieldparam io [Eco::API::UseCases::BaseIO] the input/output object carried througout all the _workflow_ # @yieldreturn [Eco::API::UseCases::BaseIO] the `io` input/output object carried througout all the _workflow_ # @return [Eco::API::Session::Config::Workflow] the current stage object (to ease chainig). def on(key = nil, &block) raise ArgumentError, "A block should be given." unless block_given? - if !key - @on = block - else + if key stage(key).on(&block) + else + @on = block end self end - # When there is an `Exception`, you might have defined some `callback` to do something with it (i.e. register, email) - # @yield [exception, io] the `callback` to do something with an `Exception` raised within this _workflow_ stage + # When there is an `Exception`, you might have defined some `callback` + # to do something with it (i.e. register, email) + # @yield [exception, io] the `callback` to do something with + # an `Exception` raised within this _workflow_ stage # @yieldparam exception [Exception] the exception object that was raised # @yieldparam io [Eco::API::UseCases::BaseIO] the input/output object carried througout all the _workflow_ # @yieldreturn [Eco::API::UseCases::BaseIO] the `io` input/output object carried througout all the _workflow_ # @return [Eco::API::Session::Config::Workflow] the current stage object (to ease chainig). def rescue(&block) @@ -144,50 +147,54 @@ return @exit_handle unless block @exit_handle = block self end - # Used in **configuration** time **add previous** `callbacks` **before** the `on` _callback_ of the (sub)stage `key` is actually `run` + # Used in **configuration** time **add previous** `callbacks` + # **before** the `on` _callback_ of the (sub)stage `key` is actually `run` # @note # - it will **not** `yield` it immediately, but when the _workflow_ reaches the target stage # - in this case, you can define multiple `callbacks` # @param key [Symbol, nil] cases: # - if `key` is not provided, it targets the _current stage_ # - if `key` is provided, it targets the specific _sub-stage_ - # @yield [stage_workflow, io] one of the things to do **before** the `on` _callback_ of the (sub)stage `key` is actually `run` + # @yield [stage_workflow, io] one of the things to do **before** the + # `on` _callback_ of the (sub)stage `key` is actually `run` # @yieldparam stage_workflow [Eco::API::Session::Config::Workflow] the _target stage_ referred by `key` # @yieldparam io [Eco::API::UseCases::BaseIO] the input/output object carried througout all the _workflow_ # @yieldreturn [Eco::API::UseCases::BaseIO] `io` the input/output object carried througout all the _workflow_ # @return [Eco::API::Session::Config::Workflow] the current stage object (to ease chainig). def before(key = nil, &block) raise ArgumentError, "A block should be given." unless block_given? - if !key - @before.push(block) - else + if key stage(key).before(&block) + else + @before.push(block) end self end - # Used in **configuration** time **add previous** `callbacks` **after** the `on` _callback_ of the (sub)stage `key` is actually `run` + # Used in **configuration** time **add previous** `callbacks` **after** the `on` _callback_ + # of the (sub)stage `key` is actually `run` # @note # - it will **not** `yield` it immediately, but when the _workflow_ reaches the target stage # - in this case, you can define multiple `callbacks` # @param key [Symbol, nil] cases: # - if `key` is not provided, it targets the _current stage_ # - if `key` is provided, it targets the specific _sub-stage_ - # @yield [stage_workflow, io] one of the things to do **after** the `on` _callback_ of the (sub)stage `key` is actually `run` + # @yield [stage_workflow, io] one of the things to do **after** the + # `on` _callback_ of the (sub)stage `key` is actually `run` # @yieldparam stage_workflow [Eco::API::Session::Config::Workflow] the _target stage_ referred by `key` # @yieldparam io [Eco::API::UseCases::BaseIO] the input/output object carried througout all the _workflow_ # @yieldreturn [Eco::API::UseCases::BaseIO] `io` the input/output object carried througout all the _workflow_ # @return [Eco::API::Session::Config::Workflow] the current stage object (to ease chainig). def after(key = nil, &block) raise ArgumentError, "A block should be given." unless block_given? - if !key - @after.push(block) - else + if key stage(key).after(&block) + else + @after.push(block) end self end # Used in run time to **execute the workflow** of the (sub)stage `key` @@ -200,12 +207,12 @@ # @note if a `block` is provided: # - it will **not** run the workflow of the substages to `key` stage # - it will **not** run the `callback` for `on` defined during the configuration time # - it will rather `yield` the target stage after all the `before` _callbacks_ have been run # - aside of this, the rest will be the same as when the _block_ is provided (see previous note) - # @note [if the object returned by `before`, `after` and `run` callbacks is not an `Eco::API::UseCases::BaseIO`, - # the original `io` object will be returned instead. + # @note [if the object returned by `before`, `after` and `run` callbacks + # is not an `Eco::API::UseCases::BaseIO`, the original `io` object will be returned instead. # @param key [Symbol, nil] cases: # - if `key` is not provided, it targets the _current stage_ # - if `key` is provided, it targets the specific _sub-stage_ # @param io [Eco::API::UseCases::BaseIO] the input/output object # @yield [stage_workflow, io] if a `block` is provided, see `note` @@ -214,75 +221,85 @@ # @yieldreturn [Eco::API::UseCases::BaseIO] the `io` input/output object carried througout all the _workflow_ # @return [Eco::API::Session::Config::Workflow] the current stage object (to ease chainig). def run(key = nil, io:, &block) raise "Missing BaseIO object" unless io.is_a?(Eco::API::UseCases::BaseIO) - if key - io = io_result(io: io) do - stage(key).run(io: io, &block) - end - elsif pending? - @before.each do |c| + rescuable(io) do + if key io = io_result(io: io) do - io.evaluate(self, io, &c) + stage(key).run(io: io, &block) end + elsif pending? + io = run_before(io) + io = run_it(io, &block) unless skip? + io = run_after(io) end + io + ensure + @pending = false + end + end - unless skip? - io.session.logger.debug("(Workflow: #{path}) running now") - if block - io = io_result(io: io) do - io.evaluate(self, io, &block) - end - else - existing_stages.each do |stg| - io = io_result(io: io) do - stg.run(io: io) - end - end + protected - unless ready? - msg = "(Workflow: #{path}) 'on' callback is not defined, nor block given" - io.session.logger.debug(msg) - end - io = io_result(io: io) do - io.evaluate(self, io, &@on) - end - end - @pending = false + def root? + @_parent == self + end + + def path + return name if root? + "#{@_parent.path}:#{name}" + end + + def ready? + !!@on + end + + def run_before(io) + @before.each do |c| + io = io_result(io: io) do + io.evaluate(self, io, &c) end + end + io + end - @after.each do |c| + def run_after(io) + @after.each do |c| + io = io_result(io: io) do + io.evaluate(self, io, &c) + end + end + io + end + + def run_it(io, &block) + io.session.logger.debug("(Workflow: #{path}) running now") + if block + io = io_result(io: io) do + io.evaluate(self, io, &block) + end + else + existing_stages.each do |stg| io = io_result(io: io) do - io.evaluate(self, io, &c) + stg.run(io: io) end end + + unless ready? + msg = "(Workflow: #{path}) 'on' callback is not defined, nor block given" + io.session.logger.debug(msg) + end + io = io_result(io: io) do + io.evaluate(self, io, &@on) + end end io - rescue SystemExit => e - io = io_result(io: io) do - io.evaluate(e, io, &self.exit_handle) - end - exit e.status - rescue Interrupt => i - raise - rescue Exception => e - # raise unless self.rescue - io = io_result(io: io) do - io.evaluate(e, io, &self.rescue) - end - raise ensure @pending = false end - protected - - def root? - @_parent == self - end - # It ensures an `Eco::API::UseCases::BaseIO` is returned # @note it always brings the IO to be a `BaseIO`. # As the `output` and the type is captured, it just cleans usecase related # logic from the object. # @raise ArgumentError if `klass` isn't child of `Eco::API::UseCases::BaseIO` @@ -294,39 +311,47 @@ result = yield io = result if result.is_a?(klass) io.instance_of?(klass) ? io : io.base end - def path - return name if root? - "#{@_parent.path}:#{name}" - end - - def root? - @_parent == self - end - - def ready? - !!@on - end - def existing_stages # sort defined stages by stage order sorted_keys = self.class.stages & @stages.keys sorted_keys.map {|k| stage(k)} end def ready_stages - exiting_stages.select {|s| s.ready?} + exiting_stages.select(&:ready?) end def stages_ready? - existing_stages.all? {|s| s.ready?} + existing_stages.all?(&:ready?) end def stage(key) self.class.validate_stage(key) @stages[key] ||= self.class.workflow_class(key).new(key, _parent: self, config: config) + end + + # helper to treat trigger the exit and rescue handlers + def rescuable(io) + yield + # rescue SystemStackError => err + # puts err.patch_full_message(trace_count: 100) + # exit 1 + rescue SystemExit => err + io = io_result(io: io) do + io.evaluate(err, io, &exit_handle) + end + exit err.status + rescue Interrupt => _int + raise + rescue StandardError => err + # raise unless self.rescue + io = io_result(io: io) do + io.evaluate(err, io, &self.rescue) + end + raise end end end end end