lib/yuuki/caller.rb in yuuki-0.1.0 vs lib/yuuki/caller.rb in yuuki-0.1.1

- old
+ new

@@ -1,135 +1,149 @@ +# frozen_string_literal: true + +require 'set' require 'yuuki/runner' module Yuuki - class Caller - Runner = Struct.new(:method, :tags, :exclude, :threading, :priority) + class Caller + def self.require_dir(_dir, recursive: false) + Dir.glob(recursive ? "#{require_dir}/**/*.rb" : "#{require_dir}/*.rb"){|file| require file} + end - def self.require_dir(dir, recursive = false) - Dir.glob(recursive ? "#{require_dir}/**/*.rb" : "#{require_dir}/*.rb"){|file| require file} - end + def initialize(*instances) + @instances = Set.new + @threads = [] + add(*instances) + end - def initialize(*klass) - @runners = [] - @threads = [] - add(*klass) + def add(*instances) + instances.each do |instance| + # create instance if class is given + if instance.is_a?(Class) + klass = instance + instance = instance.new + else + klass = instance.class end - def add(*klass) - klass.each do |klass| - instance = klass.new - instance.instance_variable_set(:@caller, self) - info = klass.instance_variable_get(:@runner_info) || {} - methods = klass.public_instance_methods(false) - methods |= klass.instance_variable_get(:@runner_adds) || [] - methods -= klass.instance_variable_get(:@runner_excepts) || [] - @runners += methods.map do |method| - tags = info.dig(method, :tags) || [] - exclude = !!info.dig(method, :exclude) - threading = !!info.dig(method, :threading) - priority = info.dig(method, :priority) || 0 - Runner.new(instance.method(method), tags, exclude, threading, priority) - end - end - @runners.sort_by!{|e| -e.priority} - end + # check the klass is extended + raise Yuuki::Error, 'Runner instance must be extend Yuuki::Runner' unless klass.singleton_class.include?(Yuuki::Runner) - def run(**args) - run_internal(@runners.reject(&:exclude), args) - end + # add @yuuki to the instance + instance.instance_variable_set(:@yuuki, self) - def run_all(**args) - run_internal(@runners, args) - end + # regist + @instances << instance + end + end - def run_class(*klass, **args) - run_internal(@runners.select{|e| !e.exclude && klass.any?{|k| e.method.receiver.instance_of?(k)}}, args) - end + def runners + list = @instances.flat_map do |instance| + methods = instance.class.instance_variable_get(:@yuuki_methods) + methods.select{|_sig, meta| meta[:enabled]}.map{|sig, meta| [instance.method(sig), meta]} + end + list.sort_by{|_method, meta| -(meta[:priority] || 0)} + end - def run_class_all(*klass, **args) - run_internal(@runners.select{|e| klass.any?{|k| e.method.receiver.instance_of?(k)}}, args) - end + def run(**args, &block) + run_internal(runners, args, &block) + end - def run_tag(*tags, **args) - run_internal(@runners.select{|e| tags.any?{|t| e.tags.any?(t)}}, args) - end + def run_select(proc_select, **args, &block) + run_internal(runners.select(&proc_select), args, &block) + end - def run_select(**args) - run_internal(@runners.select{|e| yield(e)}, args) - end + def run_tag(*tags, **args, &block) + run_select(proc{|_method, meta| meta[:tags]&.intersect?(tags)}, **args, &block) + end - def run_method(klass, method, **args) - runners = klass ? @runners.select{|e| e.method.receiver.class == klass} : @runners - runners.select!{|e| e.method.name == method} if method - run_internal(runners, args) - end + def run_method(klass, method_sig, **args, &block) + select_proc = proc do |method, _meta| + flag_klass = klass ? method.receiver.instance_of?(klass) : true + flag_method = method_sig ? method.name == method_sig : true + flag_klass && flag_method + end + run_select(select_proc, **args, &block) + end - def join - @threads.each(&:join) - end + def join + @threads.each(&:join) + @threads.select!(&:alive?) + end - def alive? - @threads.any?(&:alive?) - end - alias running? alive? + def alive? + @threads.select!(&:alive?) + !@threads.empty? + end + alias running? alive? - private + private - def run_internal(runners, args) - runners.each do |runner| - if runner.threading - @threads << Thread.new(runner.method, args) do |method, args| - run_method_internal(method, args) - end - else - run_method_internal(runner.method, args) - end - end - @threads.select!(&:alive?) + def run_internal(runners, args, &block) + @threads.select!(&:alive?) + runners.each do |method, meta| + if meta[:thread] + thread = Thread.new(method, args, block) do |thread_method, thread_args, thread_block| + run_method_internal(thread_method, thread_args, &thread_block) + end + thread.priority = meta[:priority] || 0 + @threads << thread + else + run_method_internal(method, args, &block) end + end + end - def run_method_internal(method, args) - params = method.parameters - return method[] if params.size == 0 - params_array = [] - params_hash = {} - params_block = nil - nonspecified_last_opt = nil - params.each do |type, name| - case type - when :req - raise Yuuki::Error.new("A required argument '#{name}' was not found on running #{method.owner}::#{method.name}") unless args.key?(name) - params_array << args[name] - when :opt - next nonspecified_last_opt = name unless args.key?(name) - raise Yuuki::Error.new("A required argument '#{nonspecified_last_opt}' was not found"\ - " on running #{method.owner}::#{method.name}"" with optional argument '#{name}'") if nonspecified_last_opt - params_array << args[name] - when :rest - next unless args.key?(name) - raise Yuuki::Error.new("A required argument '#{nonspecified_last_opt}' not found"\ - " on running #{method.owner}::#{method.name}"" with rest argument '#{name}'") if nonspecified_last_opt - if args[name].respond_to?(:to_ary) - params_array += args[name] - else - params_array << args[name] - end - when :keyreq - raise Yuuki::Error.new("A required key argument '#{name}' was not found on running #{method.owner}::#{method.name}") unless args.key?(name) - params_hash[name] = args[name] - when :key - params_hash[name] = args[name] if args.key?(name) - when :keyrest - next unless args.key?(name) - if args[name].respond_to?(:to_hash) - params_hash.merge!(args[name]) - else - params_hash[name] = args[name] - end - when :block - params_block = args[name] - end - end - params_hash.empty? ? method[*params_array, &params_block] : method[*params_array, **params_hash, &params_block] + def run_method_internal(method, args, &block) + params = method.parameters + return method[] if params.empty? + params_array = [] + params_hash = {} + params_block = nil + nonspecified_last_opt = nil + params.each do |type, name| + case type + when :req + raise Yuuki::Error, "A required argument '#{name}' was not found on running #{method.owner}::#{method.name}" unless args.key?(name) + params_array << args[name] + when :opt + # if parameters do not contain the :opt argument, treat it as not specified + next nonspecified_last_opt = name unless args.key?(name) + if nonspecified_last_opt + # if there already exist non-specified :opt arguments, no more specified :opt argument is allowed + raise Yuuki::Error, "A required argument '#{nonspecified_last_opt}' was not found"\ + " on running #{method.owner}::#{method.name}"" with optional argument '#{name}'" + end + params_array << args[name] + when :rest + next unless args.key?(name) + if nonspecified_last_opt + # if there already exist non-specified :opt arguments, the :rest argument cannot be handled + raise Yuuki::Error, "A required argument '#{nonspecified_last_opt}' not found"\ + " on running #{method.owner}::#{method.name}"" with rest argument '#{name}'" + end + if args[name].respond_to?(:to_ary) + params_array += args[name] + else + params_array << args[name] + end + when :keyreq + raise Yuuki::Error, "A required key argument '#{name}' was not found on running #{method.owner}::#{method.name}" unless args.key?(name) + params_hash[name] = args[name] + when :key + params_hash[name] = args[name] if args.key?(name) + when :keyrest + next unless args.key?(name) + if args[name].respond_to?(:to_hash) + params_hash.merge!(args[name]) + else + params_hash[name] = args[name] + end + when :block + params_block = args[name] end + end + params_block = block unless params.any?{|type, _| type == :block} + params_hash.empty? ? method[*params_array, &params_block] : method[*params_array, **params_hash, &params_block] end + end end