module Eco module API class Session class Config class Workflow extend Eco::API::Common::ClassHelpers @workflow = { options: nil, load: {input: nil, people: {get: nil, filter: nil}, filter: nil}, usecases: nil, launch_jobs: nil, post_launch: {usecases: nil, launch_jobs: nil}, end: nil } class << self def stages @stages ||= (@workflow || {}).keys end def validate_stage(stage) "Unknown Workflow stage '#{stage}'. Should be any of #{stages}" unless stages.include?(stage) end def titleize(key) str_name = key.to_s.strip.split(/[\-\_ ]/i).compact.map do |str| str.slice(0).upcase + str.slice(1..-1).downcase end.join("") end def get_model(key) raise "Expected Symbol. Given: #{key.class}" unless key.is_a?(Symbol) workflow = @workflow[key] raise "Expected Array. Given #{model.class}" unless !workflow || workflow.is_a?(Hash) class_name = titleize(key.to_s) full_class_name = "#{self}::#{class_name}" if target_class = resolve_class(full_class_name, exception: false) else target_class = Class.new(Eco::API::Session::Config::Workflow) do @workflow = workflow end self.const_set class_name, target_class end target_class end end attr_reader :config attr_reader :name def initialize(name = nil, _parent: self, config:) @config = config @name = name @stages = {} @_parent = _parent @pending = true # moments @on = nil @before = [] @after = [] end def pending? @pending end def skip! @skip = true @pending = false end def skip? return @skip if instance_variable_defined?(:@skip) return false if root? @_parent.skip? end def for(key = nil) raise "A block should be given." unless block_given? if !key yield(self) else stage(key).for(&Proc.new) end self end def on(key = nil, &block) raise "A block should be given." unless block if !key @on = block else stage(key).on(&block) end self end def before(key = nil, &block) raise "A block should be given." unless block if !key @before.push(block) else stage(key).before(&block) end self end def after(key = nil, &block) raise "A block should be given." unless block if !key @after.push(block) else stage(key).after(&block) end self end def run(key = nil, io:, &block) if key io = stage(key).run(io: io, &block) elsif pending? @before.each {|c| io = c.call(self, io)} unless skip? io.session.logger.debug("(Workflow: #{path}) running now") if block io = block.call(self, io) else existing_stages.each {|stg| io = stg.run(io: io)} unless ready? msg = "(Workflow: #{path}) 'on' callback is not defined, nor block given" io.session.logger.debug(msg) end io = @on.call(self, io) if ready? end @pending = false end @after.each {|c| io = c.call(self, io)} end io end protected 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?} end def stages_ready? existing_stages.all? {|s| s.ready?} end def stage(key) self.class.validate_stage(key) @stages[key] ||= self.class.get_model(key).new(key, _parent: self, config: config) end end end end end end