lib/floe/workflow.rb in floe-0.1.1 vs lib/floe/workflow.rb in floe-0.2.0

- old
+ new

@@ -3,80 +3,76 @@ require "json" module Floe class Workflow class << self - def load(path_or_io, context = {}, credentials = {}) + def load(path_or_io, context = nil, credentials = {}) payload = path_or_io.respond_to?(:read) ? path_or_io.read : File.read(path_or_io) new(payload, context, credentials) end end - attr_reader :context, :credentials, :first_state, :payload, :states, :states_by_name, :start_at + attr_reader :context, :credentials, :payload, :states, :states_by_name, :current_state, :status - def initialize(payload, context = {}, credentials = {}) + def initialize(payload, context = nil, credentials = {}) payload = JSON.parse(payload) if payload.kind_of?(String) context = JSON.parse(context) if context.kind_of?(String) credentials = JSON.parse(credentials) if credentials.kind_of?(String) + context = Context.new(context) unless context.kind_of?(Context) - @payload = payload - @context = context - @credentials = credentials + @payload = payload + @context = context || {"global" => {}} + @credentials = credentials + @states = payload["States"].to_a.map { |name, state| State.build!(self, name, state) } - @states_by_name = states.to_h { |state| [state.name, state] } - @start_at = @payload["StartAt"] - @first_state = @states_by_name[@start_at] + @states_by_name = @states.each_with_object({}) { |state, result| result[state.name] = state } + start_at = @payload["StartAt"] + + current_state_name = @context["State"]["Name"] || start_at + @current_state = @states_by_name[current_state_name] + + @status = current_state_name == start_at ? "pending" : current_state.status rescue JSON::ParserError => err raise Floe::InvalidWorkflowError, err.message end - def run! - state = first_state - input = context.dup + def step + @status = "running" if @status == "pending" + @context["Execution"]["StartTime"] ||= Time.now.utc - until state.nil? - state, output = state.run!(input) - input = output - end + input = @context["State"]["Output"] || @context["Execution"]["Input"].dup - output - end + tick = Process.clock_gettime(Process::CLOCK_MONOTONIC) + next_state, output = current_state.run!(input) + tock = Process.clock_gettime(Process::CLOCK_MONOTONIC) - def to_dot - String.new.tap do |s| - s << "digraph {\n" - states.each do |state| - s << state.to_dot << "\n" - end - s << "\n" - states.each do |state| - Array(state.to_dot_transitions).each do |transition| - s << transition << "\n" - end - end - s << "}\n" - end - end + @context["State"] = { + "EnteredTime" => tick, + "FinishedTime" => tock, + "Duration" => tock - tick, + "Output" => output, + "Name" => next_state&.name, + "Input" => output + } - def to_svg(path: nil) - require "open3" - out, err, _status = Open3.capture3("dot -Tsvg", :stdin_data => to_dot) + @context["States"] << @context["State"] - raise "Error from graphviz:\n#{err}" if err && !err.empty? + @status = current_state.status - File.write(path, out) if path + next_state_name = next_state&.name + @current_state = next_state_name && @states_by_name[next_state_name] - out + self end - def to_ascii(path: nil) - require "open3" - out, err, _status = Open3.capture3("graph-easy", :stdin_data => to_dot) + def run! + until end? + step + end + self + end - raise "Error from graph-easy:\n#{err}" if err && !err.empty? - - File.write(path, out) if path - - out + def end? + current_state.nil? end end end