lib/wukong/runner.rb in wukong-3.0.0.pre3 vs lib/wukong/runner.rb in wukong-3.0.0

- old
+ new

@@ -1,144 +1,167 @@ +require_relative("runner/code_loader") +require_relative("runner/deploy_pack_loader") +require_relative("runner/boot_sequence") + module Wukong - module CommandlineRunner - - def exit_with_status(status, options = {}) - warn options[:msg] if options[:msg] - @env.dump_help if options[:show_help] - exit(status) - end - def env= settings - @env = settings - end + # A base class which handles + # + # * requiring any necessary code like deploy packs or code from command-line arguments + # * having all plugins configure settings as necessary + # * resolving settings + # * having all plugins boot from now resolved settings + # * parsing command-line arguments + # * instantiating and handing over control to a driver which runs the actual code + class Runner + + include Logging + include CodeLoader + include DeployPackLoader + include BootSequence - def self.included(base) - base.extend(ClassMethods) + # The settings object that will be configured and booted from. + # All plugins will configure this object. + attr_accessor :settings + + # Create a new Runner with the given +settings+. + # + # Uses an empty Configliere::Param object if no +settings+ are + # given. + # + # @param [Configliere::Param] settings + def initialize settings=Configliere::Param.new + self.settings = settings end - module ClassMethods - - def usage(usg = nil) - return @usage if usg.nil? - @usage = usg + # Instantiates a new Runner and boot it up. + # + # Will rescue any Wukong::Error with a logged error message and + # exit. + def self.run(settings=Configliere::Param.new) + begin + new(settings).boot! + rescue Wukong::Error => e + die(e.message, 127) end + end - def desc(dsc = nil) - return @description if dsc.nil? - @decription = desc - end + # The parsed command-line arguments. + # + # Will raise an error if +boot+ hasn't been called yet. + # + # @return [Array<String>] + def args + settings.rest + end - def add_param(*args) - defined_params << args - end - - def defined_params - @defined_params ||= [] - end + # The root directory we should consider ourselves to be running + # in. + # + # Defaults to the root directory of a deploy pack if we're running + # inside one, else just returns `Dir.pwd`. + # + # @return [String] + def root + in_deploy_pack? ? deploy_pack_dir : Dir.pwd + end - def base_config(conf = nil) - return @base_configuration if conf.nil? - @base_configuration = conf - end + # Convenience method for setting the usage message of a Runner. + # + # @param [String, nil] msg set the usage message + # @return [String] the usage message + def self.usage msg=nil + return @usage unless msg + @usage = msg + end + + # Convenience method for setting the description message of a Runner. + # + # @param [String, nil] msg set the description message + # @return [String] the description message + def self.description msg=nil + return @description unless msg + @description = msg + end - def decorate_environment! env - usg = self.usage - env.define_singleton_method(:usage){ usg } - env.description = self.desc - defined_params.each{ |params| env.send(:define, *params) } - end + # Kill this process with the given error `message` and exit + # `code`. + # + # @param [String] message + # @param [Integer] code. + def self.die(message=nil, code=127) + log.error(message) if message + exit(code) + end - def in_deploy_pack? - return @in_deploy_pack unless @in_deploy_pack.nil? - @in_deploy_pack = (find_deploy_pack_dir != '/') - end + # Return the name of the program this Runner is running. + # + # This is passed to plugins which can configure settings + # appropriately. Defaults to the name of the currently running + # process. + # + # @return [String] + def program_name + @program_name || File.basename($0) + end - def find_deploy_pack_dir - return @deploy_pack_dir if @deploy_pack_dir - wd = Dir.pwd - parent = File.dirname(wd) - until wd == parent - return wd if File.exist?(File.join(wd, 'Gemfile')) && File.exist?(File.join(wd, 'config', 'environment.rb')) - wd = parent - parent = File.dirname(wd) - end - @deploy_pack_dir = wd - end + # Explicitly set the name of the program this Runner is running. + # + # This is useful for unit tests in which the name of the currently + # running process may be different from the runner command being + # tested (`rspec` vs. `wu-local`). + # + # @param [String] name + def program_name= name + @program_name = name + end - def run!(*run_params) - settings = base_configuration || Configliere::Param.use(:commandline) - boot_environment(settings) if in_deploy_pack? - runner = new(*run_params) - runner.env = settings.resolve! - runner.run(*settings.rest) - end - - end - end - - class LocalRunner - include CommandlineRunner - base_configuration + # Return the usage message for this runner. + # + # @return [String] the usage message + def usage + ["usage: #{program_name} [ --param=val | --param | -p val | -p ]", self.class.usage].compact.join(' ') + end - usage 'usage: wu-local PROCESSOR|FLOW [ --param=value | -p value | --param | -p]' - desc <<EOF - wu-local is a tool for running Wukong processors and flows locally on - the command-line. Use wu-local by passing it a processor and feeding - in some data: + # Return the description text for this runner. + # + # @return [String] the description text + def description + self.class.description + end - $ echo 'UNIX is Clever and Fun...' | wu-local tokenizer.rb - UNIX - is - Clever - and - Fun + # Is there a processor or dataflow registered with the given + # `name`? + # + # @param [String] name + # @return [true, false] + def registered? name + name && Wukong.registry.registered?(name.to_sym) + end - If your processors have named fields you can pass them in as - arguments: + # Retrieve the dataflow registered under a given `name`. + # + # @param [String,Symbol] name + # @return [Wukong::Processor, Wukong::Dataflow, nil] + def dataflow_class_for(name) + builder = (Wukong.registry.retrieve(name.to_sym) or return) + builder.for_class + end - $ echo 'UNIX is clever and fun...' | wu-local tokenizer.rb --min_length=4 - UNIX - Clever + # Is the given `name` a registered as a processor? + # + # @param [String,Symbol] name + # @return [true, false] + def processor?(name) + registered?(name) && dataflow_class_for(name).ancestors.include?(Wukong::Processor) + end - You can chain processors and calls to wu-local together: - - $ echo 'UNIX is clever and fun...' | wu-local tokenizer.rb --min_length=4 | wu-local downcaser.rb - unix - clever - - Which is a good way to develop a combined data flow which you can - again test locally: - - $ echo 'UNIX is clever and fun...' | wu-local tokenize_and_downcase_big_words.rb - unix - clever -EOF - - add_param :run, description: "Name of the processor or dataflow to use. Defaults to basename of the given path.", flag: 'r' - add_param :tcp_server, description: "Run locally as a server using provided TCP port", default: false, flag: 't' - - def run *args - arg = args.first - case - when arg.nil? - exit_with_status(1, show_help: true, msg: "Must pass a processor name or path to a processor file. Got <#{arg}>") - when Wukong.registry.registered?(arg.to_sym) - processor = arg.to_sym - when File.exist?(arg) - load arg - processor = @env.run || File.basename(arg, '.rb') - else - exit_with_status(2, show_help: true, msg: "Must pass a processor name or path to a processor file. Got <#{arg}>") - end - run_em_server(processor, @env) + # Is the given `name` a registered as a dataflow? + # + # @param [String,Symbol] name + # @return [true, false] + def dataflow?(name) + registered?(name) && dataflow_class_for(name).ancestors.include?(Wukong::Dataflow) end - - def run_em_server(processor, env) - EM.run do - env.tcp_server ? Wu::TCPServer.start(processor, env) : Wu::StdioServer.start(processor, env) - end - rescue Wu::Error => e - exit_with_status(3, msg: e.backtrace.join("\n")) - end - end + end end