diff --git a/.travis.yml b/.travis.yml index 3f5baf5..ea7b8bb 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,11 +1,7 @@ language: ruby rvm: -- 2.0.0 -- 2.1.3 -- 2.1.5 -- 2.2.0 -- 2.2.1 -- 2.2.2 +- 2.2.4 +- 2.3.0 deploy: provider: rubygems gem: factor diff --git a/Gemfile.lock b/Gemfile.lock index 79f5bcc..1860aba 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,39 +1,35 @@ PATH remote: . specs: - factor (2.9.08) - commander (~> 4.3, >= 4.3.4) - configatron (~> 4.5, >= 4.5.0) - rainbow (~> 2.0, >= 2.0.0) - rest-client (~> 1.8, >= 1.8.0) - rspec (~> 3.3, >= 3.3.0) - varify (~> 0.0.5) - wrong (~> 0.7.1) + factor (3.0.0) + commander (~> 4.4.0) + concurrent-ruby (~> 1.0.1) + configatron (~> 4.5.0) + rainbow (~> 2.1.0) GEM remote: https://rubygems.org/ specs: celluloid (0.16.0) timers (~> 4.0.0) - coderay (1.1.0) - commander (4.3.4) + coderay (1.1.1) + commander (4.4.0) highline (~> 1.7.2) + concurrent-ruby (1.0.1) configatron (4.5.0) - coveralls (0.8.1) + coveralls (0.8.13) json (~> 1.8) - rest-client (>= 1.6.8, < 2) - simplecov (~> 0.10.0) + simplecov (~> 0.11.0) term-ansicolor (~> 1.3) thor (~> 0.19.1) + tins (~> 1.6.0) diff-lcs (1.2.5) docile (1.1.5) - domain_name (0.5.24) - unf (>= 0.0.5, < 1.0.0) ffi (1.9.8) formatador (0.2.5) - guard (2.12.6) + guard (2.13.0) formatador (>= 0.2.4) - listen (~> 2.7) + listen (>= 2.7, <= 4.0) lumberjack (~> 1.0) nenv (~> 0.1) notiffany (~> 0.0) @@ -41,14 +37,12 @@ GEM shellany (~> 0.0) thor (>= 0.18.1) guard-compat (1.2.1) - guard-rspec (4.5.2) + guard-rspec (4.6.5) guard (~> 2.1) guard-compat (~> 1.1) rspec (>= 2.99.0, < 4.0) - highline (1.7.2) - hitimes (1.2.2) - http-cookie (1.0.2) - domain_name (~> 0.5) + highline (1.7.8) + hitimes (1.2.3) json (1.8.3) listen (2.10.0) celluloid (~> 0.16.0) @@ -56,47 +50,34 @@ GEM rb-inotify (>= 0.9) lumberjack (1.0.9) method_source (0.8.2) - mime-types (2.6.1) nenv (0.2.0) - netrc (0.10.3) notiffany (0.0.6) nenv (~> 0.1) shellany (~> 0.0) - predicated (0.2.6) - pry (0.10.1) + pry (0.10.3) coderay (~> 1.1.0) method_source (~> 0.8.1) slop (~> 3.4) - rainbow (2.0.0) - rake (10.4.2) + rainbow (2.1.0) + rake (11.1.2) rb-fsevent (0.9.5) rb-inotify (0.9.5) ffi (>= 0.5.0) - rest-client (1.8.0) - http-cookie (>= 1.0.2, < 2.0) - mime-types (>= 1.16, < 3.0) - netrc (~> 0.7) - rspec (3.3.0) - rspec-core (~> 3.3.0) - rspec-expectations (~> 3.3.0) - rspec-mocks (~> 3.3.0) - rspec-core (3.3.0) - rspec-support (~> 3.3.0) - rspec-expectations (3.3.0) + rspec (3.4.0) + rspec-core (~> 3.4.0) + rspec-expectations (~> 3.4.0) + rspec-mocks (~> 3.4.0) + rspec-core (3.4.4) + rspec-support (~> 3.4.0) + rspec-expectations (3.4.0) diff-lcs (>= 1.2.0, < 2.0) - rspec-support (~> 3.3.0) - rspec-mocks (3.3.0) + rspec-support (~> 3.4.0) + rspec-mocks (3.4.1) diff-lcs (>= 1.2.0, < 2.0) - rspec-support (~> 3.3.0) - rspec-support (3.3.0) - ruby2ruby (2.2.0) - ruby_parser (~> 3.1) - sexp_processor (~> 4.0) - ruby_parser (3.7.0) - sexp_processor (~> 4.1) - sexp_processor (4.6.0) + rspec-support (~> 3.4.0) + rspec-support (3.4.1) shellany (0.0.1) - simplecov (0.10.0) + simplecov (0.11.2) docile (~> 1.1.0) json (~> 1.8) simplecov-html (~> 0.10.0) @@ -107,24 +88,17 @@ GEM thor (0.19.1) timers (4.0.1) hitimes - tins (1.5.2) - unf (0.1.4) - unf_ext - unf_ext (0.0.7.1) - varify (0.0.5) - wrong (0.7.1) - diff-lcs (~> 1.2.5) - predicated (~> 0.2.6) - ruby2ruby (>= 2.0.1) - ruby_parser (>= 3.0.1) - sexp_processor (>= 4.0) + tins (1.6.0) PLATFORMS ruby DEPENDENCIES - coveralls (~> 0.8.1) + coveralls (~> 0.8.13) factor! - guard (~> 2.12.6) - guard-rspec (~> 4.5.2) - rake (~> 10.4.2) + guard (~> 2.13.0) + guard-rspec (~> 4.6.5) + rake (~> 11.1.2) + +BUNDLED WITH + 1.11.2 diff --git a/factor.gemspec b/factor.gemspec index 980b960..72225f3 100644 --- a/factor.gemspec +++ b/factor.gemspec @@ -18,15 +18,12 @@ Gem::Specification.new do |s| s.executables = ['factor'] s.require_paths = ['lib'] - s.add_runtime_dependency 'commander', '~> 4.3', '>= 4.3.4' - s.add_runtime_dependency 'rainbow', '~> 2.0', '>= 2.0.0' - s.add_runtime_dependency 'configatron', '~> 4.5', '>= 4.5.0' - s.add_runtime_dependency 'rest-client', '~> 1.8', '>= 1.8.0' - s.add_runtime_dependency 'wrong', '~> 0.7.1' - s.add_runtime_dependency 'rspec', '~> 3.3', '>= 3.3.0' - s.add_runtime_dependency 'varify', '~> 0.0.5' - s.add_development_dependency 'coveralls', '~> 0.8.1' - s.add_development_dependency 'rake', '~> 10.4.2' - s.add_development_dependency 'guard', '~> 2.12.6' - s.add_development_dependency 'guard-rspec', '~> 4.5.2' + s.add_runtime_dependency 'commander', '~> 4.4.0' + s.add_runtime_dependency 'rainbow', '~> 2.1.0' + s.add_runtime_dependency 'configatron', '~> 4.5.0' + s.add_runtime_dependency 'concurrent-ruby', '~> 1.0.1' + s.add_development_dependency 'coveralls', '~> 0.8.13' + s.add_development_dependency 'rake', '~> 11.1.2' + s.add_development_dependency 'guard', '~> 2.13.0' + s.add_development_dependency 'guard-rspec', '~> 4.6.5' end diff --git a/lib/commands.rb b/lib/commands.rb index aba8106..890ad51 100644 --- a/lib/commands.rb +++ b/lib/commands.rb @@ -10,20 +10,21 @@ program :version, Factor::VERSION program :description, 'Factor.io Server to run workflows' -command 'server' do |c| - c.syntax = 'factor server [options]' +command 'workflow' do |c| + c.syntax = 'factor workflow workflow_file' c.description = 'Start the Factor.io Server in the current local directory' - c.option '--log FILE', String, 'Log file path. Default is stdout.' - c.option '--credentials FILE', String, 'credentials.yml file path.' - c.option '--path FILE', String, 'Path to workflows' - c.when_called Factor::Commands::WorkflowCommand, :server + c.option '--settings FILE', String, 'factor.yml file path.' + c.option '--verbose', 'Verbose logging' + c.when_called Factor::Commands::WorkflowCommand, :run end command 'run' do |c| c.syntax = 'factor run service_address params' c.description = 'Run a specific command.' - c.option '--credentials FILE', String, 'credentials.yml file path.' + c.option '--connector FILE', String, 'file to require for loading method' + c.option '--verbose', 'Verbose logging' c.when_called Factor::Commands::RunCommand, :run end -alias_command 's', 'server' \ No newline at end of file +alias_command 'w', 'workflow' +alias_command 'r', 'run' \ No newline at end of file diff --git a/lib/factor/commands/base.rb b/lib/factor/commands/base.rb index 31908fb..5e12777 100644 --- a/lib/factor/commands/base.rb +++ b/lib/factor/commands/base.rb @@ -3,7 +3,7 @@ require 'configatron' require 'yaml' require 'fileutils' -require 'factor/logger/basic' +require 'factor/logger' module Factor module Commands @@ -11,47 +11,67 @@ module Commands class Command attr_accessor :logger - DEFAULT_FILENAME = { - credentials: File.expand_path('./credentials.yml') - } + DEFAULT_FILENAME = File.expand_path('./settings.yml') def initialize - @logger = Factor::Log::BasicLogger.new + @logger = Factor::Logger.new end - def load_config(options = {}) - load_config_data :credentials, options + def load_settings(options = {}) + relative_path = options.settings || DEFAULT_FILENAME + absolute_path = File.expand_path(relative_path) + content = File.read(absolute_path) + data = YAML.load(content) + configatron[:settings].configure_from_hash(data) + end + + def settings + configatron.settings.to_hash + end + + protected + + def info(message) + log(:info, message) + end + + def warn(message) + log(:warn, message) end - def save_config(options={}) - credentials_relative_path = options[:credentials] || DEFAULT_FILENAME[:credentials] - credentials_absolute_path = File.expand_path(credentials_relative_path) - credentials = Hash[stringify(configatron.credentials.to_h).sort] + def error(message) + log(:error, message) + end + + def success(message) + log(:success, message) + end - File.write(credentials_absolute_path,YAML.dump(credentials)) + def log(type, message) + @logger.log(type, message) if @logger end + private - def stringify(hash) - hash.inject({}) do |options, (key, value)| - options[key.to_s] = value.is_a?(Hash) ? stringify(value) : value - options + def try_json(value) + new_value = value + begin + new_value = JSON.parse(value, symbolize_names: true) + rescue JSON::ParserError end + new_value end - def load_config_data(config_type, options = {}) - relative_path = options[config_type] || DEFAULT_FILENAME[config_type] - absolute_path = File.expand_path(relative_path) - begin - data = YAML.load(File.read(absolute_path)) - rescue - data = {} + + def params(args = []) + request_options = {} + args.each do |arg| + key,value = arg.split(/:/,2) + raise ArgumentError, "Option '#{arg}' is not a valid option" unless key && value + request_options[key.to_sym] = try_json(value) end - configatron[config_type].configure_from_hash(data) - rescue => ex - logger.error message:"Couldn't load #{config_type} from #{absolute_path}", exception:ex - exit + request_options end end end diff --git a/lib/factor/commands/run_command.rb b/lib/factor/commands/run_command.rb index 08b8d27..8403ab6 100644 --- a/lib/factor/commands/run_command.rb +++ b/lib/factor/commands/run_command.rb @@ -2,46 +2,42 @@ require 'json' require 'factor/commands/base' -require 'factor/workflow/runtime' +require 'factor/connector' +# require 'factor/workflow/runtime' module Factor module Commands class RunCommand < Factor::Commands::Command def run(args, options) - config_settings = {} - config_settings[:credentials] = options.credentials - load_config(config_settings) - - credential_settings = configatron.credentials.to_hash - runtime = Factor::Workflow::Runtime.new(credential_settings, logger: logger) - - begin - params = JSON.parse(args[1] || '{}') - rescue => ex - logger.error "'#{args[1]}' can't be parsed as JSON" - exit - end + address = args[0] + parameters = params(args[1..-1]) - done = false - - begin - runtime.run(args[0],params) do |response_info| - data = response_info.is_a?(Array) ? response_info.map {|i| i.marshal_dump} : response_info.marshal_dump - JSON.pretty_generate(data).split("\n").each do |line| - logger.info line - end - done = true - end.on_fail do - done = true - end - rescue => ex - logger.error ex.message - done = true + if options.connector + info "Loading #{options.connector}" if options.verbose + require options.connector end - Factor::Common::Blocker.block_until_interrupt_or { done } + connector_class = Factor::Connector.get(address) + + raise ArgumentError, "Connector '#{address}' not found" unless connector_class + + info "Running '#{address}(#{parameters})'" if options.verbose + connector = connector_class.new(parameters) + connector.add_observer(self, :events) if options.verbose + response = connector.run - logger.info 'Good bye!' + @logger.indent += 1 + info response + @logger.indent -= 1 + success 'Done!' + end + + def events(type, content) + if type==:log + @logger.indent += 1 + @logger.log(content[:type], content[:message]) + @logger.indent -= 1 + end end end end diff --git a/lib/factor/commands/workflow_command.rb b/lib/factor/commands/workflow_command.rb index cd0330d..07d3a72 100644 --- a/lib/factor/commands/workflow_command.rb +++ b/lib/factor/commands/workflow_command.rb @@ -1,87 +1,31 @@ # encoding: UTF-8 -require 'factor/common/blocker' require 'factor/commands/base' require 'factor/workflow/runtime' +require 'factor/logger' module Factor module Commands # Workflow is a Command to start the factor runtime from the CLI class WorkflowCommand < Factor::Commands::Command - def initialize - @runtimes = [] - super - end - - def server(_args, options) - config_settings = {} - config_settings[:credentials] = options.credentials - workflow_filename = File.expand_path(options.path || '.') - @destination_stream = File.new(options.log, 'w+') if options.log - - load_config(config_settings) - load_all_workflows(workflow_filename) + def run(args, options) - if @runtimes.count > 0 - - logger.info 'Ctrl-c to exit' - Factor::Common::Blocker.block_until_interrupt_or do - @runtimes.all?{|r| r.stopped?} - end - - logger.info "Stopping..." - @runtimes.each {|r| r.unload if r.started? } - Factor::Common::Blocker.block_until sleep:0.5 do - @runtimes.all?{|r| r.stopped?} - end + if options.settings + info "Loading settings from #{options.settings}" if options.verbose + load_settings(options) end - logger.info 'Good bye!' - end - private + workflow_filename = File.expand_path(args[0]) + info "Loading workflow from '#{workflow_filename}'" if options.verbose + workflow_definition = File.read(workflow_filename) - def load_all_workflows(workflow_filename) - glob_ending = workflow_filename[-1] == '/' ? '' : '/' - glob = "#{workflow_filename}#{glob_ending}*.rb" - file_list = Dir.glob(glob) - if !file_list.all? { |file| File.file?(file) } - logger.error "#{workflow_filename} is neither a file or directory" - elsif file_list.count == 0 - logger.error 'No workflows in this directory to run' - else - file_list.each { |filename| load_workflow(File.expand_path(filename)) } - end - end + info "Starting workflow runtime..." if options.verbose + @logger.indent += 1 if options.verbose + runtime = Factor::Workflow::Runtime.new(settings: settings, logger:@logger, verbose: options.verbose) + runtime.load workflow_definition, workflow_filename + @logger.indent -= 1 if options.verbose - def load_workflow(workflow_filename) - logger.info "Loading workflow from #{workflow_filename}" - begin - workflow_definition = File.read(workflow_filename) - rescue => ex - logger.error "Couldn't read file #{workflow_filename}", exception:ex - return - end - - load_workflow_from_definition(workflow_definition, File.basename(workflow_filename)) - end - - def load_workflow_from_definition(workflow_definition, filename) - logger.info "Setting up workflow processor" - begin - credential_settings = configatron.credentials.to_hash - runtime = Factor::Workflow::Runtime.new(credential_settings, logger: logger, workflow_filename: filename) - @runtimes << runtime - rescue => ex - message = "Couldn't setup workflow process" - logger.error message:message, exception:ex - end - - begin - logger.info "Starting workflow" - runtime.load(workflow_definition) - rescue => ex - logger.error message: "Couldn't start workflow", exception: ex - end + success "Workflow completed" if options.verbose end end end diff --git a/lib/factor/common/blocker.rb b/lib/factor/common/blocker.rb deleted file mode 100644 index eb4b863..0000000 --- a/lib/factor/common/blocker.rb +++ /dev/null @@ -1,44 +0,0 @@ -module Factor - module Common - module Blocker - - def self.block_until(options = {}, &block) - pause = options[:sleep] || 0.1 - - begin - continue = block.yield - sleep pause - end while !continue - end - - def self.block_until_interrupt_or(options = {}, &block) - pause = options[:sleep] || 0.1 - interrupted = false - - Thread.new do - block_until_interrupt - interrupted = true - end - - begin - begin - until_met = block.yield - sleep pause - rescue Interrupt - interrupted = true - end - stop_looping = until_met || interrupted - end until stop_looping - end - - def self.block_until_interrupt - begin - begin - sleep 0.1 - end while true - rescue Interrupt - end - end - end - end -end \ No newline at end of file diff --git a/lib/factor/common/deep_struct.rb b/lib/factor/common/deep_struct.rb deleted file mode 100644 index 6da0860..0000000 --- a/lib/factor/common/deep_struct.rb +++ /dev/null @@ -1,56 +0,0 @@ -require 'ostruct' - -module Factor - module Common - class DeepStruct < OpenStruct - def initialize(hash=nil) - @table = {} - @hash_table = {} - - if hash - hash.each do |k,v| - @table[k.to_sym] = (v.is_a?(Hash) ? self.class.new(v) : v) - @hash_table[k.to_sym] = v - - new_ostruct_member(k) - end - end - end - - def to_h - @hash_table - end - - def to_s - @hast_table.to_s - end - - def inspect - @hash_table.inspect - end - - def [](idx) - hash = marshal_dump - hash[idx.to_sym] - end - end - - def self.flat_hash(h,f=[],g={}) - return g.update({ f=>h }) unless h.is_a? Hash - h.each { |k,r| flat_hash(r,f+[k],g) } - g - end - - def self.simple_object_convert(item) - if item.is_a?(Hash) - Factor::Common::DeepStruct.new(item) - elsif item.is_a?(Array) - item.map do |i| - simple_object_convert(i) - end - else - item - end - end - end -end \ No newline at end of file diff --git a/lib/factor/connector.rb b/lib/factor/connector.rb new file mode 100644 index 0000000..7b2cf13 --- /dev/null +++ b/lib/factor/connector.rb @@ -0,0 +1,64 @@ +require 'observer' + +module Factor + class Connector + include Observable + def self.register(connector) + if connector.superclass != Factor::Connector + raise ArgumentError, "Connector must be a Factor::Connector" + end + + @@paths ||= {} + @@paths[underscore(connector.name)] = connector + end + + def self.get(path) + @@paths ||= {} + @@paths[path] + end + + def run + end + + protected + + def trigger(data) + changed + notify_observers(:trigger, data) + end + + def info(message) + log(:info, message) + end + + def warn(message) + log(:warn, message) + end + + def success(message) + log(:success, message) + end + + def error(message) + log(:error, message) + end + + def log(type, message) + changed + notify_observers(:log, {type: type, message:message}) + changed + notify_observers(type, message) + end + + private + + def self.underscore(string) + word = string.dup + word.gsub!(/([A-Z]+)([A-Z][a-z])/,'\1_\2') + word.gsub!(/([a-z\d])([A-Z])/,'\1_\2') + word.tr!("-", "_") + word.downcase! + word + end + end +end diff --git a/lib/factor/connector/definition.rb b/lib/factor/connector/definition.rb deleted file mode 100644 index 20cdb6f..0000000 --- a/lib/factor/connector/definition.rb +++ /dev/null @@ -1,124 +0,0 @@ -require 'observer' -require 'varify' - -require 'factor/connector/error' - -module Factor - module Connector - class Definition - include Observable - include Varify - - def initialize - Varify::Base.callback do |failure| - fail failure[:message] - end - end - - def self.id(id) - raise ArgumentError, "ID must be a sym" unless id.is_a?(Symbol) - instance_variable_set('@id',id) - define_method :id do - id - end - end - - def self.resource(resource,&block) - resources = instance_variable_get('@resources') || [] - resources.push resource - instance_variable_set('@resources',resources) - - block.call - - resources.pop - instance_variable_set('@resources',resources) - remove_instance_variable('@resources') if resources.count == 0 - end - - def self.action(action,&block) - resources = instance_variable_get('@resources') || [] - address = resources + [action] - actions = instance_variable_get('@actions') || {} - - actions[address] = block - instance_variable_set('@actions', actions) - end - - def self.listener(listener,&block) - resources = instance_variable_get('@resources') || [] - address = resources + [listener] - start_address = resources + [listener] + [:start] - stop_address = resources + [listener] + [:stop] - - instance_variable_set('@listener', listener) - - block.call - - listeners = instance_variable_get('@listeners') || {} - raise ArgumentError, "Start block must be defined in listener '#{address.join('::')}'" unless listeners[start_address] - raise ArgumentError, "Stop block must be defined in listener '#{address.join('::')}'" unless listeners[stop_address] - - remove_instance_variable('@listener') - end - - def self.start(&block) - listener = instance_variable_get('@listener') - raise ArgumentError, 'Start block must be defined within a Listener' unless listener - resources = instance_variable_get('@resources') || [] - address = resources + [listener] + [:start] - - listeners = instance_variable_get('@listeners') || {} - listeners[address] = block - instance_variable_set('@listeners', listeners) - end - - def self.stop(&block) - listener = instance_variable_get('@listener') - raise ArgumentError, 'Stop block must be defined within a Listener' unless listener - resources = instance_variable_get('@resources') || [] - address = resources + [listener] + [:stop] - - listeners = instance_variable_get('@listeners') || {} - listeners[address] = block - instance_variable_set('@listeners', listeners) - end - - def trigger(data) - changed - notify_observers type:'trigger', data:data - end - - def respond(data) - changed - notify_observers type:'response', data: data - end - - def info(message) - log 'info', message - end - - def error(message) - log 'error', message - end - - def warn(message) - log 'warn', message - end - - def debug(message) - log 'debug', message - end - - def fail(message,params={}) - changed - notify_observers type:'fail', message: message - raise Factor::Connector::Error, exception:params[:exception], message:message if !params[:throw] - end - - def log(status, message) - changed - notify_observers type: 'log', status: status, message: message - end - end - end -end \ No newline at end of file diff --git a/lib/factor/connector/error.rb b/lib/factor/connector/error.rb deleted file mode 100644 index e89cab5..0000000 --- a/lib/factor/connector/error.rb +++ /dev/null @@ -1,14 +0,0 @@ -# encoding: UTF-8 - -module Factor - module Connector - class Error < StandardError - attr_accessor :state, :exception - - def initialize(params = {}) - @exception = params[:exception] - super(params[:message] || '') - end - end - end -end \ No newline at end of file diff --git a/lib/factor/connector/registry.rb b/lib/factor/connector/registry.rb deleted file mode 100644 index 715f05a..0000000 --- a/lib/factor/connector/registry.rb +++ /dev/null @@ -1,22 +0,0 @@ -module Factor - module Connector - module Registry - - def self.get(id) - begin - require "factor-connector-#{id}" - rescue LoadError - end - get_class = self.class.constants.find do |class_name| - begin - class_obj = self.class.const_get(class_name) - class_obj.superclass == Factor::Connector::Definition && class_obj.new.id == id - rescue - end - end - raise "No definition found with id #{id}" unless get_class - self.class.const_get(get_class) - end - end - end -end \ No newline at end of file diff --git a/lib/factor/connector/runtime.rb b/lib/factor/connector/runtime.rb deleted file mode 100644 index b2485b6..0000000 --- a/lib/factor/connector/runtime.rb +++ /dev/null @@ -1,97 +0,0 @@ -require 'observer' - -module Factor - module Connector - class Runtime - include Observable - attr_reader :logs, :state - - def initialize(connector) - @connector = connector.new - @connector.add_observer(self, :log) - @logs = [] - @state = :stopped - end - - def started? - @state == :started - end - - def starting? - @state == :starting - end - - def stopped? - @state == :stopped - end - - def stopping? - @state == :stopping - end - - def callback=(block) - @callback = block if block - end - - def callback(&block) - @callback = block if block - end - - def log(params) - @logs << params - changed - notify_observers params - @callback.call(params) if @callback - end - - def run(address, options={}) - raise ArgumentError, "Address must be an Array" unless address.is_a?(Array) - raise ArgumentError, "Address must be an Array of Symbols" unless address.all?{|a| a.is_a?(Symbol)} - raise ArgumentError, "Address must not be empty" unless address.length > 0 - @address = address - actions = @connector.class.instance_variable_get('@actions') - action = actions[address] - raise ArgumentError, "Action #{address} not found" unless action - Thread.new do - begin - @connector.instance_exec(options,&action) - rescue => ex - log type:'fail', message: ex.message - end - end - end - - def start_listener(address, options={}) - @address = address - listeners = @connector.class.instance_variable_get('@listeners') - listener = listeners[address + [:start]] - raise ArgumentError, "Listener #{address} not found" unless listener - @start_listener_thread = Thread.new do - @state = :starting - begin - @connector.instance_exec(options, &listener) - @state = :started - rescue => ex - @state = :stopped - log type:'fail', message: ex.message - end - end - end - - def stop_listener - listeners = @connector.class.instance_variable_get('@listeners') - listener = listeners[@address + [:stop]] - raise ArgumentError, "Listener #{address} not found" unless listener - - Thread.new do - @state = :stopping - begin - @connector.instance_eval(&listener) - ensure - @state = :stopped - end - end - end - end - end -end \ No newline at end of file diff --git a/lib/factor/connector/test.rb b/lib/factor/connector/test.rb deleted file mode 100644 index c73f93c..0000000 --- a/lib/factor/connector/test.rb +++ /dev/null @@ -1,191 +0,0 @@ -require 'rspec' -require 'rspec/expectations' -require 'rspec/matchers' -require 'wrong' - -module Factor - module Connector - module Test - DEFAULT_DELAY = 5 - DEFAULT_TIMEOUT = 0.25 - - @timeout = DEFAULT_TIMEOUT - @delay = DEFAULT_DELAY - - def self.timeout - @timeout - end - - def self.timeout=(value) - @timeout=value - end - - def self.reset - @timeout = DEFAULT_TIMEOUT - @delay = DEFAULT_DELAY - end - - def self.delay - @delay - end - - def self.delay=(value) - @delay = value - end - - def self.eventually_options - options = {} - options[:timeout] = Factor::Connector::Test.timeout if Factor::Connector::Test.timeout!=DEFAULT_TIMEOUT - options[:delay] = Factor::Connector::Test.delay if Factor::Connector::Test.delay!=DEFAULT_DELAY - options - end - - RSpec::Matchers.define :message do |expected| - match do |actual| - begin - Wrong.eventually(Factor::Connector::Test.eventually_options) do - actual.logs.any? do |log| - case expected.class.name - when 'Hash' - status = expected.keys.first.to_s - message = expected.values.first - log[:type]=='log' && log[:status]==status && log[:message]==message - when 'String' - log[:type]=='log' && log[:message]==expected - when 'Symbol' - log[:type]=='log' && log[:status]==expected.to_s - when 'NilClass' - log[:type]=='log' - else - false - end - end - end - true - rescue => ex - false - end - end - - failure_message do - case expected.class.name - when 'Hash' - status = expected.keys.first.to_s - message = expected.values.first - "expected #{actual.logs} to log '#{status}' message '#{message}'" - when 'Symbol' - "expected #{actual.logs} to log '#{expected}'" - when 'String' - "expected #{actual.logs} to log message '#{expected}'" - when 'NilClass' - "expected #{actual.logs} to log a message" - else - "#{expected.class} is an unrecognizable matcher type" - end - end - end - - RSpec::Matchers.define :respond do |expected| - - match do |actual| - begin - Wrong.eventually(Factor::Connector::Test.eventually_options) do - actual.logs.any? do |log| - case expected.class.name - when 'Hash' - log[:type]=='response' && log[:data]==expected - when 'NilClass' - log[:type]=='response' - else - false - end - end - end - true - rescue => ex - false - end - end - - failure_message do - case expected.class.name - when 'Hash' - "expected #{actual.logs} to respond with data '#{expected}'" - when 'NilClass' - "expected #{actual.logs} to respond" - else - "#{expected.class} is an unrecognizable matcher type" - end - end - end - - RSpec::Matchers.define :trigger do |expected| - - match do |actual| - begin - Wrong.eventually(Factor::Connector::Test.eventually_options) do - actual.logs.any? do |log| - case expected.class.name - when 'Hash' - log[:type]=='trigger' && log[:data]==expected - when 'NilClass' - log[:type]=='trigger' - else - false - end - end - end - true - rescue => ex - false - end - end - - failure_message do - case expected.class.name - when 'Hash' - "expected #{actual.logs} to respond with data '#{expected}'" - when 'NilClass' - "expected #{actual.logs} to respond" - else - "#{expected.class} is an unrecognizable matcher type" - end - end - end - - RSpec::Matchers.define :fail do |expected| - - match do |actual| - begin - Wrong.eventually(Factor::Connector::Test.eventually_options) do - actual.logs.any? do |log| - case expected.class.name - when 'String' - log[:type]=='fail' && log[:message]==expected - when 'NilClass' - log[:type]=='fail' - else - false - end - end - end - true - rescue => ex - false - end - end - - failure_message do - case expected.class.name - when 'String' - "expected #{actual.logs} to fail with message '#{expected}'" - when 'NilClass' - "expected #{actual.logs} to fail" - else - "#{expected.class} is an unrecognizable matcher type" - end - end - end - end - end -end \ No newline at end of file diff --git a/lib/factor/logger.rb b/lib/factor/logger.rb new file mode 100644 index 0000000..0e08158 --- /dev/null +++ b/lib/factor/logger.rb @@ -0,0 +1,49 @@ +require 'rainbow' + +module Factor + class Logger + attr_accessor :indent + + def initialize + @indent = 0 + end + + def log(log_level, message) + log_level_text = format_log_level(log_level) + puts "[ #{log_level_text} ] [#{time}] #{' ' * @indent}#{message}" + end + + def info(message) + log :info, message + end + + def warn(message) + log :warn, message + end + + def error(message) + log :error, message + end + + def success(message) + log :success, message + end + + private + + def format_log_level(log_level) + formated_log_level = log_level.to_s.upcase.center(10) + case log_level.to_sym + when :error then Rainbow(formated_log_level).red + when :info then Rainbow(formated_log_level).white.bright + when :warn then Rainbow(formated_log_level).yellow + when :success then Rainbow(formated_log_level).green + else formated_log_level + end + end + + def time + Time.now.localtime.strftime('%m/%d/%y %T.%L') + end + end +end \ No newline at end of file diff --git a/lib/factor/logger/basic.rb b/lib/factor/logger/basic.rb deleted file mode 100644 index dc666a4..0000000 --- a/lib/factor/logger/basic.rb +++ /dev/null @@ -1,69 +0,0 @@ -require 'rainbow' -require 'factor/logger/logger' - -module Factor - module Log - class BasicLogger < Factor::Log::Logger - - attr_accessor :destination_stream - - def log(section, options={}) - options = { message: options } if options.is_a?(String) - tag = tag(options) - message = options['message'] || options[:message] - section_text = format_section(section) - write "[ #{section_text} ] [#{time}]#{tag} #{message}" if message - exception options[:exception] if options[:exception] - end - - def info(options = {}) - log :info, options - end - - def warn(options = {}) - log :warn, options - end - - def error(options = {}) - log :error, options - end - - def success(options = {}) - log :success, options - end - - private - - def exception(exception) - error message: " #{exception.message}" - exception.backtrace.each do |line| - error message: " #{line}" - end - end - - def format_section(section) - formated_section = section.to_s.upcase.center(10) - case section.to_sym - when :error then Rainbow(formated_section).red - when :info then Rainbow(formated_section).white.bright - when :warn then Rainbow(formated_section).yellow - when :success then Rainbow(formated_section).green - else formated_section - end - end - - def tag(options) - primary = options['service_id'] || options['instance_id'] - secondary = ":#{options['instane_id']}" if options['service_id'] && options['instance_id'] - primary ? "[#{primary}#{secondary || ''}]" : '' - end - - def write(message) - stream = @destination_stream || $stdout - stream.puts(message) - stream.flush - end - - end - end -end \ No newline at end of file diff --git a/lib/factor/logger/logger.rb b/lib/factor/logger/logger.rb deleted file mode 100644 index 5cb1220..0000000 --- a/lib/factor/logger/logger.rb +++ /dev/null @@ -1,28 +0,0 @@ - -module Factor - module Log - - class Logger - - def log - raise NotImplemented - end - - def info - raise NotImplemented - end - - def warn - raise NotImplemented - end - - def error - raise NotImplemented - end - - def time - Time.now.localtime.strftime('%m/%d/%y %T.%L') - end - end - end -end \ No newline at end of file diff --git a/lib/factor/logger/test.rb b/lib/factor/logger/test.rb deleted file mode 100644 index b19f9dd..0000000 --- a/lib/factor/logger/test.rb +++ /dev/null @@ -1,38 +0,0 @@ -require 'factor/logger/logger' - -module Factor - module Log - class TestLogger < Factor::Log::Logger - - attr_reader :history - - def initialize - @history = [] - end - - def log(section, message='') - history << {status: section, message:message} - end - - def info(message = '') - log :info, message - end - - def warn(message = '') - log :warn, message - end - - def error(message = '') - log :error, message - end - - def success(message = '') - log :success, message - end - - def clear - @history=[] - end - end - end -end \ No newline at end of file diff --git a/lib/factor/version.rb b/lib/factor/version.rb index 5584c93..ee06cf7 100644 --- a/lib/factor/version.rb +++ b/lib/factor/version.rb @@ -2,5 +2,5 @@ # Primary Factor.io module module Factor - VERSION = '2.9.08' + VERSION = '3.0.0' end diff --git a/lib/factor/workflow/connector_future.rb b/lib/factor/workflow/connector_future.rb new file mode 100644 index 0000000..882e7f6 --- /dev/null +++ b/lib/factor/workflow/connector_future.rb @@ -0,0 +1,27 @@ +require 'factor/workflow/future' + +module Factor + module Workflow + class ConnectorFuture < Future + def initialize(action) + @subscribers = {} + @action = action + @action.add_observer(self, :trigger) + + super() do + @action.run + end + end + + def trigger(type, data) + @subscribers[type] ||= [] + @subscribers[type].each {|subscriber| subscriber.call(data)} + end + + def on(type, &block) + @subscribers[type] ||= [] + @subscribers[type] << block + end + end + end +end \ No newline at end of file diff --git a/lib/factor/workflow/definition.rb b/lib/factor/workflow/definition.rb deleted file mode 100644 index 9d2b8c1..0000000 --- a/lib/factor/workflow/definition.rb +++ /dev/null @@ -1,174 +0,0 @@ -# encoding: UTF-8 - -require 'factor/commands/base' -require 'factor/common/deep_struct' -require 'factor/common/blocker' -require 'factor/workflow/service_address' -require 'factor/workflow/exec_handler' -require 'factor/connector/runtime' -require 'factor/connector/registry' -require 'factor/connector/definition' - -module Factor - module Workflow - class Definition - attr_reader :state - - def initialize(credentials, options={}) - @logger = options[:logger] if options[:logger] - @credentials = credentials - @workflow_filename = options[:workflow_filename] - @unload = false - @connector_runtimes = [] - end - - def stop - @state = :stopping - @unload = true - end - - def state - empty = @connector_runtimes.count == 0 - all_started = @connector_runtimes.all? {|r| r.started? } - all_stopped = @connector_runtimes.all? {|r| r.stopped? } - any_stopping = @connector_runtimes.any? {|r| r.stopping? } - any_starting = @connector_runtimes.any? {|r| r.starting? } - - if empty || all_stopped - :stopped - elsif all_started - :started - elsif any_stopping - :stopping - elsif any_starting - :starting - else - :stopped - end - end - - def started? - state == :started - end - - def starting? - state == :starting - end - - def stopped? - state == :stopped - end - - def stopping? - state == :stopping - end - - def listen(service_ref, params = {}, &block) - address, connector_runtime, exec, params_and_creds = initialize_connector_runtime(service_ref,params) - line = caller.first.split(":")[1] - @context = @workflow_filename ? "#{service_ref}(#{@workflow_filename}:#{line})" : "#{service_ref}" - @connector_runtimes << connector_runtime - - done = false - - connector_runtime.callback do |response| - message = response[:message] - type = response[:type] - - case type - when 'trigger' - success "Triggered" - block.call(Factor::Common.simple_object_convert(response[:payload])) if block - when 'log' - log_callback(message,response[:status]) - when 'fail' - message = response[:message] || 'unkonwn error' - error "Failed: #{message}" - exec.fail_block.call(message) if exec.fail_block - done = true - end - end - - success "Starting" - connector_runtime.start_listener(address.path, params) - - Thread.new do - Factor::Common::Blocker.block_until { done || @unload } - - success "Stopping" - connector_runtime.stop_listener - success "Stopped" - @connector_runtimes.delete(connector_runtimes) - end - - exec - end - - def run(service_ref, params = {}, &block) - address, connector_runtime, exec, params_and_creds = initialize_connector_runtime(service_ref,params) - line = caller.first.split(":")[1] - @context = @workflow_filename ? "#{service_ref}(#{@workflow_filename}:#{line})" : "#{service_ref}" - - connector_runtime.callback do |response| - message = response[:message] - type = response[:type] - - case type - when 'log' - log_callback(message,response[:status]) - when 'fail' - error_message = response[:message] || "unknown error" - error "Failed: #{error_message}" - exec.fail_block.call(message) if exec.fail_block - when 'response' - success "Completed" - payload = response[:payload] || {} - block.call(Factor::Common.simple_object_convert(payload)) if block - end - end - - success "Starting" - listener_instance = connector_runtime.run(address.path, params_and_creds) - exec - end - - def success(message) - @logger.success @context ? "[#{@context}] #{message}" : message - end - - def info(message) - @logger.info @context ? "[#{@context}] #{message}" : message - end - - def warn(message) - @logger.warn @context ? "[#{@context}] #{message}" : message - end - - def error(message) - @logger.error @context ? "[#{@context}] #{message}" : message - end - - private - - def initialize_connector_runtime(service_ref, params={}) - address = Factor::Workflow::ServiceAddress.new(service_ref) - service_credentials = @credentials[address.service.to_sym] || {} - exec = Factor::Workflow::ExecHandler.new(service_ref, params) - connector_class = Factor::Connector::Registry.get(address.service) - connector_runtime = Factor::Connector::Runtime.new(connector_class) - params_and_creds = Factor::Common::DeepStruct.new(params.merge(service_credentials)).to_h - - [address, connector_runtime, exec, params_and_creds] - end - - def log_callback(message,status) - case status - when 'info' then info message - when 'warn' then warn message - when 'error' then error message - when 'debug' then error message - end - end - end - end -end diff --git a/lib/factor/workflow/dsl.rb b/lib/factor/workflow/dsl.rb new file mode 100644 index 0000000..ebcf6f3 --- /dev/null +++ b/lib/factor/workflow/dsl.rb @@ -0,0 +1,54 @@ +require 'factor/workflow/connector_future' + +module Factor + module Workflow + class DSL + def initialize(options={}) + @logger = options[:logger] + end + + def run(address, options={}) + + connector_class = Factor::Connector.get(address) + connector = connector_class.new(options) + + Factor::Workflow::ConnectorFuture.new(connector) + end + + def all(*events, &block) + Future.all(*events, &block) + end + + def any(*events, &block) + Future.any(*events, &block) + end + + def info(message) + log(:info, message) + end + + def warn(message) + log(:warn, message) + end + + def error(message) + log(:error, message) + end + + def success(message) + log(:success, message) + end + + def log(type, message) + @logger.log(type, message) if @logger + end + + def on(type, *actions, &block) + raise ArgumentError, "All actions must be an ConnectorFuture" unless actions.all? {|a| a.is_a?(ConnectorFuture) } + actions.each {|a| a.on(type, &block) } + end + + end + + end +end \ No newline at end of file diff --git a/lib/factor/workflow/exec_handler.rb b/lib/factor/workflow/exec_handler.rb deleted file mode 100644 index 4bb7c40..0000000 --- a/lib/factor/workflow/exec_handler.rb +++ /dev/null @@ -1,16 +0,0 @@ -module Factor - module Workflow - class ExecHandler - attr_accessor :params, :service, :fail_block - - def initialize(service = nil, params = {}) - @service = service - @params = params - end - - def on_fail(&block) - @fail_block = block - end - end - end -end \ No newline at end of file diff --git a/lib/factor/workflow/future.rb b/lib/factor/workflow/future.rb new file mode 100644 index 0000000..9f4bfdf --- /dev/null +++ b/lib/factor/workflow/future.rb @@ -0,0 +1,79 @@ +require 'concurrent' + +module Factor + module Workflow + class Future + extend Forwardable + + def initialize(promise=nil, &block) + raise ArgumentError, "promise or block required" unless promise || block + raise ArgumentError, "promise and block can't both be provided" if promise && block + raise ArgumentError, "promise must be a Concurrent::Promise" if promise && !promise.is_a?(Concurrent::Promise) + + @promise = promise || Concurrent::Promise.new(&block) + end + + def_delegator :@promise, :state, :state + def_delegator :@promise, :pending?, :pending? + def_delegator :@promise, :rejected?, :rejected? + def_delegator :@promise, :fulfilled?, :fulfilled? + def_delegator :@promise, :unscheduled?, :unscheduled? + def_delegator :@promise, :reason, :reason + def_delegator :@promise, :value, :value + def_delegator :@promise, :fail, :fail + + def completed? + @promise.fulfilled? || @promise.rejected? + end + + def then(&block) + Future.new(@promise.then(&block)) + end + + def execute + Future.new(@promise.execute) + end + + def rescue(&block) + Future.new(@promise.rescue(&block)) + end + + def wait + @promise.execute if @promise.unscheduled? + @promise.wait + end + + def self.all(*handlers, &block) + block ||= lambda {|v| true} + Future.new do + handlers.each {|handler| handler.execute if handler.unscheduled?} + completed = handlers.map do |handler| + handler.wait + handler + end + worked = completed.all? { |handler| handler.fulfilled? && block.call(handler.value) } + + raise StandardError, "At least one event failed" unless worked + + worked + end + end + + def self.any(*handlers, &block) + block ||= lambda {|v| true} + Future.new do + handlers.each {|handler| handler.execute if handler.unscheduled?} + completed = handlers.map do |handler| + handler.wait + handler + end + worked = completed.any? { |handler| handler.fulfilled? && block.call(handler.value) } + + raise StandardError, "There were no successful events" unless worked + + worked + end + end + end + end +end \ No newline at end of file diff --git a/lib/factor/workflow/runtime.rb b/lib/factor/workflow/runtime.rb index 462fa34..8ac6ce3 100644 --- a/lib/factor/workflow/runtime.rb +++ b/lib/factor/workflow/runtime.rb @@ -1,26 +1,17 @@ # encoding: UTF-8 -require 'forwardable' - -require 'factor/workflow/definition' +require 'factor/workflow/dsl' module Factor module Workflow class Runtime extend Forwardable - def initialize(credentials, options={}) - @definition = Factor::Workflow::Definition.new(credentials, options) + def initialize(options={}) + @options = options + @dsl = DSL.new(@options) end - def_delegator :@definition, :instance_eval, :load - def_delegator :@definition, :run, :run - def_delegator :@definition, :stop, :unload - def_delegator :@definition, :state, :state - def_delegator :@definition, :started?, :started? - def_delegator :@definition, :starting?, :starting? - def_delegator :@definition, :stopped?, :stopped? - def_delegator :@definition, :stopping?, :stopping? - + def_delegator :@dsl, :instance_eval, :load end end end diff --git a/lib/factor/workflow/service_address.rb b/lib/factor/workflow/service_address.rb deleted file mode 100644 index c57c952..0000000 --- a/lib/factor/workflow/service_address.rb +++ /dev/null @@ -1,50 +0,0 @@ -module Factor - module Workflow - class ServiceAddress < Array - def initialize(service_ref) - if service_ref.is_a?(String) - service_map = service_ref.split('::').map {|i| i.to_sym} - raise ArgumentError, 'Address must not be empty' if service_ref.empty? - raise ArgumentError, 'Address must contain at least the service name and action' unless service_map.count > 1 - raise ArgumentError, 'Address must not contain empty references' unless service_map.all?{|i| !i.empty?} - super service_map - elsif service_ref.is_a?(ServiceAddress) || service_ref.is_a?(Array) - raise ArgumentError, 'All elements in array must be a string' unless service_ref.all?{|i| i.is_a?(String) || i.is_a?(Symbol)} - super service_ref - else - raise ArgumentError, 'Address must be a String, Array, or ServiceAddress' - end - end - - def workflow? - self.service == :workflow - end - - def service - self.first - end - - def namespace - raise ArgumentError, 'Address must contain at least two parts' unless self.count >= 2 - self[0..-2] - end - - def id - self.last - end - - def to_s - self.join('::') - end - - def resource - raise "No resource path defined, address must contain at least three parts" unless self.length >= 3 - self[1..-2] - end - - def path - self[1..-1] - end - end - end -end diff --git a/lib/factor/workflow/test.rb b/lib/factor/workflow/test.rb deleted file mode 100644 index 3372ec4..0000000 --- a/lib/factor/workflow/test.rb +++ /dev/null @@ -1,54 +0,0 @@ -require 'rspec' -require 'rspec/expectations' -require 'rspec/matchers' -require 'wrong' - -module Factor - module Workflow - module Test - - RSpec::Matchers.define :log do |expected| - match do |actual| - begin - Wrong.eventually do - actual.history.any? do |log| - case expected.class.name - when 'Hash' - status = expected.keys.first.to_s - message = expected.values.first - status_match = log[:status].to_s == status.to_s - message_match = log[:message].end_with? message - status_match && message_match - when 'String' - log[:message].end_with? expected - when 'Symbol' - log[:status].to_s == expected.to_s - else - false - end - end - end - true - rescue => ex - false - end - end - - failure_message do - case expected.class.name - when 'Hash' - status = expected.keys.first.to_s - message = expected.values.first - "expected #{actual.history} to log '#{status}' message '#{message}'" - when 'Symbol' - "expected #{actual.history} to log '#{expected}'" - when 'String' - "expected #{actual.history} to log message '#{expected}'" - else - "#{expected.class} is an unrecognizable matcher type" - end - end - end - end - end -end \ No newline at end of file diff --git a/spec/commands/base_spec.rb b/spec/commands/base_spec.rb index 910decf..55272b3 100644 --- a/spec/commands/base_spec.rb +++ b/spec/commands/base_spec.rb @@ -11,72 +11,6 @@ @command = Factor::Commands::Command.new end - output_methods = %w(info warn error success) - - output_methods.each do |method_name| - describe ".#{method_name}" do - it "logs #{method_name}" do - - test_string = 'Hello World' - output = capture_stdout do - @command.logger.method(method_name.to_sym).call message: test_string - end - - expect(output).to include(test_string) - expect(output).to include(method_name.upcase) - end - end - end - - describe '.exception' do - it 'logs exception' do - - test_string = 'Hello World' - exception_string = 'Something be busted' - output = capture_stdout do - begin - raise ArgumentError, exception_string - rescue => ex - @command.logger.error message: test_string, exception:ex - end - end - - expect(output).to include(test_string) - expect(output).to include(exception_string) - expect(output).to include('ERROR') - - end - end - - describe '.load_config' do - it 'can load credentials' do - credentials_file = Tempfile.new('credentials') - - credentials_content = { - 'github' => { - 'api_key' => 'fake_github_key' - }, - 'heroku' => { - 'api_key' => 'fake_heroku_key' - } - } - - credentials_file.write(YAML.dump(credentials_content)) - credentials_file.rewind - options = Commander::Command::Options.new - options.credentials = credentials_file.path - config_settings = { - credentials: options.credentials, - } - - output = capture_stdout do - @command.load_config config_settings - end - - expect(configatron.credentials.github.api_key).to eq('fake_github_key') - expect(configatron.credentials.heroku.api_key).to eq('fake_heroku_key') - - credentials_file.close - end + it 'can' do end end diff --git a/spec/commands/run_spec.rb b/spec/commands/run_spec.rb new file mode 100644 index 0000000..f42d350 --- /dev/null +++ b/spec/commands/run_spec.rb @@ -0,0 +1,13 @@ +# encoding: UTF-8 + +require 'spec_helper' + +require 'factor/commands/run_command' + +describe Factor::Commands::RunCommand do + describe 'run' do + it 'can run a basic workflow' do + + end + end +end diff --git a/spec/commands/workflow_spec.rb b/spec/commands/workflow_spec.rb index 7597607..28902cb 100644 --- a/spec/commands/workflow_spec.rb +++ b/spec/commands/workflow_spec.rb @@ -5,7 +5,7 @@ require 'factor/commands/workflow_command' describe Factor::Commands::WorkflowCommand do - describe '.server' do + describe 'workflow' do it 'can run a basic workflow' do end diff --git a/spec/common/deep_struct_spec.rb b/spec/common/deep_struct_spec.rb deleted file mode 100644 index 947a91b..0000000 --- a/spec/common/deep_struct_spec.rb +++ /dev/null @@ -1,51 +0,0 @@ -# encoding: UTF-8 - -require 'spec_helper' - -require 'factor/common/deep_struct' - -describe Factor::Common::DeepStruct do - it 'handles depth 1 Hash' do - source = { - a:'b' - } - - result = Factor::Common.simple_object_convert source - - expect(result).to respond_to(:a) - expect(result.a).to eq('b') - end - - it 'handles depth n Hash' do - source = { - a:{ - b: 'c' - } - } - - result = Factor::Common.simple_object_convert source - - expect(result).to respond_to(:a) - expect(result).to respond_to(:to_h) - expect(result.a).to respond_to(:b, :to_h) - expect(result.a.to_h).to eq(b:'c') - expect(result.a.b).to eq('c') - - end - - it 'handles array of Hash' do - source = [ - {a:'b'}, - {c:'d'} - ] - - result = Factor::Common.simple_object_convert source - - expect(result).to be_a(Array) - expect(result).to all(be_a(Factor::Common::DeepStruct)) - expect(result[0]).to respond_to(:a) - expect(result[0].a).to eq('b') - expect(result[1]).to respond_to(:c) - expect(result[1].c).to eq('d') - end -end diff --git a/spec/connector/definition_spec.rb b/spec/connector/definition_spec.rb deleted file mode 100644 index 9e861bb..0000000 --- a/spec/connector/definition_spec.rb +++ /dev/null @@ -1,78 +0,0 @@ -# encoding: UTF-8 - -require 'spec_helper' - -require 'factor/connector/definition' - -describe Factor::Connector::Definition do - before do - class MyDef < Factor::Connector::Definition - id :my_def - action :action_1 do - end - listener :listener_1 do - start do |params| - end - stop do - end - end - resource :nest_1 do - action :action_nested_1 do - end - listener :listener_nested_1 do - start do |params| - end - stop do |params| - end - end - resource :nest_2 do - action :action_nested_2 do - end - listener :listener_nested_2 do - start do |params| - end - stop do |params| - end - end - end - end - end - @definition = MyDef.new - end - - it 'can set the ID' do - expect(@definition).to respond_to(:id) - expect(@definition.id).to eq(:my_def) - end - - it 'can define an action' do - actions = @definition.class.instance_variable_get('@actions') - expect_proc_in(actions,[:action_1]) - end - - it 'can define a listener' do - listeners = @definition.class.instance_variable_get('@listeners') - expect_proc_in(listeners,[:listener_1,:start]) - expect_proc_in(listeners,[:listener_1,:stop]) - end - - it 'can created nested actions and listeners' do - actions = @definition.class.instance_variable_get('@actions') - listeners = @definition.class.instance_variable_get('@listeners') - - expect_proc_in(actions,[:nest_1,:action_nested_1]) - expect_proc_in(actions,[:nest_1,:nest_2,:action_nested_2]) - expect_proc_in(listeners, [:nest_1, :listener_nested_1, :start]) - expect_proc_in(listeners, [:nest_1, :listener_nested_1, :stop]) - expect_proc_in(listeners, [:nest_1, :nest_2, :listener_nested_2, :start]) - expect_proc_in(listeners, [:nest_1, :nest_2, :listener_nested_2, :stop]) - end - - def expect_proc_in(method,key) - expect(method).to_not be(nil) - expect(method).to be_a(Hash) - expect(method.keys).to include(key) - expect(method[key]).to_not be(nil) - expect(method[key]).to be_a(Proc) - end -end diff --git a/spec/connector/registry_spec.rb b/spec/connector/registry_spec.rb deleted file mode 100644 index 2465f2e..0000000 --- a/spec/connector/registry_spec.rb +++ /dev/null @@ -1,28 +0,0 @@ -# encoding: UTF-8 - -require 'spec_helper' - -require 'factor/connector/definition' -require 'factor/connector/registry' - -describe Factor::Connector::Registry do - before do - class MyDef < Factor::Connector::Definition - id :my_def - end - - end - - it 'can load definition by ID' do - definition = Factor::Connector::Registry.get(:my_def) - expect(definition).to eq(MyDef) - expect(definition.superclass).to eq(Factor::Connector::Definition) - end - - it 'fails on bad ID' do - expect { - Factor::Connector::Registry.get(:foo) - }.to raise_error - end - -end diff --git a/spec/connector/runtime_spec.rb b/spec/connector/runtime_spec.rb deleted file mode 100644 index af15090..0000000 --- a/spec/connector/runtime_spec.rb +++ /dev/null @@ -1,83 +0,0 @@ -# encoding: UTF-8 - -require 'spec_helper' - -require 'factor/connector/definition' -require 'factor/connector/runtime' -require 'factor/connector/test' - -describe Factor::Connector::Runtime do - include Factor::Connector::Test - - before do - class MyDef < Factor::Connector::Definition - id :my_def - def initialize - @some_var='some_var' - end - action :action do |data| - info "info" - warn "warn" - error "error" - respond foo: data[:foo], bar:'bar', some_var: @some_var - end - action :action_fail do |data| - fail "Something broke" - respond foo:'test' - end - action :action_varify do |data| - foo = data.varify(:foo,required:true) - end - listener :listener do - t=nil - start do |data| - t = Thread.new do - begin - sleep 0.1 - trigger foo:'bar', a:data[:a] - end while true - end - - respond started:'foo', c:data[:a] - end - stop do - info 'killing' - t.kill - respond done:true - end - end - end - @runtime = Factor::Connector::Runtime.new(MyDef) - end - - describe 'Actions' do - it 'can run and handle parameters' do - @runtime.run([:action], foo:'bar') - - expect(@runtime).to message info:'info' - expect(@runtime).to message warn:'warn' - expect(@runtime).to respond foo:'bar', bar:'bar', some_var:'some_var' - end - - it 'can fail' do - @runtime.run([:action_fail], foo:'bar') - expect(@runtime).to fail 'Something broke' - end - - it 'can fail' do - @runtime.run([:action_varify], nofoo:'bar') - expect(@runtime).to fail 'Foo (:foo) is required' - end - end - - describe 'Listeners' do - it 'can start, stop, trigger and handle parameters' do - @runtime.start_listener([:listener], a:'b') - expect(@runtime).to respond started:'foo', c:'b' - expect(@runtime).to trigger foo:'bar', a:'b' - @runtime.stop_listener - expect(@runtime).to message info:'killing' - expect(@runtime).to respond done:true - end - end -end diff --git a/spec/connector_spec.rb b/spec/connector_spec.rb new file mode 100644 index 0000000..f469816 --- /dev/null +++ b/spec/connector_spec.rb @@ -0,0 +1,11 @@ +# encoding: UTF-8 + +require 'spec_helper' + +require 'factor/commands/run_command' + +describe Factor::Connector do + it 'can' do + + end +end diff --git a/spec/logger_spec.rb b/spec/logger_spec.rb new file mode 100644 index 0000000..fb249a0 --- /dev/null +++ b/spec/logger_spec.rb @@ -0,0 +1,13 @@ +# encoding: UTF-8 + +require 'spec_helper' + +require 'factor/logger' + +describe Factor::Logger do + describe 'run' do + it 'can run a basic workflow' do + + end + end +end diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 3a0f1c0..285170a 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -1,19 +1,7 @@ # encoding: UTF-8 require 'coveralls' -require 'stringio' Coveralls.wear! $LOAD_PATH.unshift File.dirname(__FILE__) + '/../lib' - -def capture_stdout(&_block) - original_stdout = $stdout - $stdout = fake = StringIO.new - begin - yield - ensure - $stdout = original_stdout - end - fake.string -end diff --git a/spec/workflow/runtime_spec.rb b/spec/workflow/runtime_spec.rb deleted file mode 100644 index 57000b4..0000000 --- a/spec/workflow/runtime_spec.rb +++ /dev/null @@ -1,89 +0,0 @@ -# encoding: UTF-8 - -require 'spec_helper' - -require 'factor/workflow/runtime' -require 'factor/workflow/test' -require 'factor/connector/definition' -require 'factor/connector/registry' -require 'factor/logger/test' - -describe Factor::Workflow::Runtime do - before :all do - class MyDef < Factor::Connector::Definition - id :my_def - def initialize - @some_var='some_var' - end - action :action do |data| - info "info" - warn "warn" - error "error" - respond foo: data[:foo], bar:'bar', some_var: @some_var - end - action :action_fail do |data| - fail "Something broke" - respond foo:'test' - end - listener :listener do - t=nil - start do |data| - info 'i am starting' - t = Thread.new do - begin - trigger foo:'bar', a:data[:a] - sleep 5 - end while true - end - end - stop do - info 'killing' - t.kill - respond done:true - end - end - end - @logger = Factor::Log::TestLogger.new - @runtime = Factor::Workflow::Runtime.new({}, logger:@logger) - end - - before :each do - @logger.clear - end - - it 'can run a connector action' do - @runtime.run 'my_def::action', foo:'sweet' - - expect(@logger).to log success:'Starting' - expect(@logger).to log info:'info' - expect(@logger).to log warn:'warn' - expect(@logger).to log error:'error' - expect(@logger).to log success:'Completed' - end - - it 'can fail a connector action' do - @runtime.run 'my_def::action_fail', foo:'sweet' - - expect(@logger).to log error:'Failed: Something broke' - end - - it 'can run a workflow' do - workflow_definition = " - listen 'my_def::listener' do - info 'sweet' - end - " - @runtime.load(workflow_definition) - - expect(@logger).to log success:'Starting' - expect(@logger).to log info:'i am starting' - expect(@logger).to log success: 'Triggered' - expect(@logger).to log info: 'sweet' - - @runtime.unload - - expect(@logger).to log success:'Stopping' - expect(@logger).to log success:'Stopped' - - end -end diff --git a/spec/workflow/service_address_spec.rb b/spec/workflow/service_address_spec.rb deleted file mode 100644 index cb8a626..0000000 --- a/spec/workflow/service_address_spec.rb +++ /dev/null @@ -1,71 +0,0 @@ -# encoding: UTF-8 - -require 'spec_helper' - -require 'factor/workflow/service_address' - -describe Factor::Workflow::ServiceAddress do - - it 'fails on empty' do - expect { - Factor::Workflow::ServiceAddress.new('') - }.to raise_error - end - - it 'fails on no namespace' do - expect { - Factor::Workflow::ServiceAddress.new('foo') - }.to raise_error - end - - it 'fails on empty reference' do - expect{ - Factor::Workflow::ServiceAddress.new('::::') - }.to raise_error - end - - it 'can identify namespace' do - address = Factor::Workflow::ServiceAddress.new('a::b') - expect(address.namespace).to eq([:a]) - end - - it 'can identify deep namespace' do - address = Factor::Workflow::ServiceAddress.new('a::b::c') - expect(address.namespace).to eq([:a,:b]) - end - - it 'can identify service name' do - address = Factor::Workflow::ServiceAddress.new('a::b::c') - expect(address.service).to eq(:a) - end - - it 'can identify action/listener id' do - address = Factor::Workflow::ServiceAddress.new('a::b::c') - expect(address.id).to eq(:c) - end - - it 'can the resource path' do - address = Factor::Workflow::ServiceAddress.new('a::b::c::d') - expect(address.resource).to eq([:b,:c]) - end - - it 'can get the path' do - address = Factor::Workflow::ServiceAddress.new('a::b::c::d') - expect(address.path).to eq([:b,:c, :d]) - end - - it 'fails to get resource path if undefined' do - address = Factor::Workflow::ServiceAddress.new('a::b') - expect{ - address.resource - }.to raise_error - end - - it 'can initialize with array and convert to string' do - address = '' - expect { - address = Factor::Workflow::ServiceAddress.new([:a, :b, :c]) - }.to_not raise_error - expect(address.to_s).to eq('a::b::c') - end -end diff --git a/spec/workflow_spec.rb b/spec/workflow_spec.rb new file mode 100644 index 0000000..7e66fad --- /dev/null +++ b/spec/workflow_spec.rb @@ -0,0 +1,13 @@ +# encoding: UTF-8 + +require 'spec_helper' + +require 'factor/workflow/runtime' + +describe Factor::Workflow::Runtime do + describe 'run' do + it 'can run a basic workflow' do + + end + end +end