nand 0.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.gitignore +17 -0
- data/.rspec +2 -0
- data/.travis.yml +3 -0
- data/CODE_OF_CONDUCT.md +13 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +21 -0
- data/README.ja.md +206 -0
- data/README.md +203 -0
- data/Rakefile +6 -0
- data/bin/console +12 -0
- data/bin/setup +7 -0
- data/exe/nand +19 -0
- data/lib/nand.rb +47 -0
- data/lib/nand/cli.rb +126 -0
- data/lib/nand/daemon.rb +127 -0
- data/lib/nand/launcher.rb +55 -0
- data/lib/nand/launcher/executable_file_launcher.rb +35 -0
- data/lib/nand/launcher/plugin_launcher.rb +29 -0
- data/lib/nand/launcher/rb_file_launcher.rb +63 -0
- data/lib/nand/launcher/shell_launcher.rb +25 -0
- data/lib/nand/logger.rb +42 -0
- data/lib/nand/plugin.rb +22 -0
- data/lib/nand/proc_operation.rb +47 -0
- data/lib/nand/util/object.rb +25 -0
- data/lib/nand/version.rb +5 -0
- data/nand.gemspec +31 -0
- data/samples/spec_mon.rb +67 -0
- data/spec/nand/cli_spec.rb +8 -0
- data/spec/nand/daemon_spec.rb +8 -0
- data/spec/nand/launcher/executable_file_launcher_spec.rb +8 -0
- data/spec/nand/launcher/plugin_launcher_spec.rb +8 -0
- data/spec/nand/launcher/rb_file_launcher_spec.rb +8 -0
- data/spec/nand/launcher/shell_launcher_spec.rb +8 -0
- data/spec/nand/launcher_spec.rb +8 -0
- data/spec/nand/logger_spec.rb +8 -0
- data/spec/nand/plugin_spec.rb +22 -0
- data/spec/nand/proc_operation_spec.rb +44 -0
- data/spec/nand/util/object_spec.rb +8 -0
- data/spec/nand/version_spec.rb +10 -0
- data/spec/nand_spec.rb +28 -0
- data/spec/spec_helper.rb +2 -0
- metadata +198 -0
data/Rakefile
ADDED
data/bin/console
ADDED
@@ -0,0 +1,12 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require "bundler/setup"
|
4
|
+
require "nand"
|
5
|
+
|
6
|
+
# You can add fixtures and/or initialization code here to make experimenting
|
7
|
+
# with your gem easier. You can also use a different console, if you like.
|
8
|
+
|
9
|
+
# (If you use this, don't forget to add pry to your Gemfile!)
|
10
|
+
require "pry"
|
11
|
+
Pry.start
|
12
|
+
|
data/bin/setup
ADDED
data/exe/nand
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'nand'
|
4
|
+
require 'nand/cli'
|
5
|
+
|
6
|
+
def split_options( argv )
|
7
|
+
if argv.include? "--"
|
8
|
+
idx = argv.index("--")
|
9
|
+
[argv[0...idx], argv[idx+1..-1] ]
|
10
|
+
else
|
11
|
+
[argv, []]
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
wrap_exception{
|
16
|
+
argv, additional = split_options(ARGV)
|
17
|
+
Nand.additional_argv = additional
|
18
|
+
Cli.start(argv)
|
19
|
+
}
|
data/lib/nand.rb
ADDED
@@ -0,0 +1,47 @@
|
|
1
|
+
require "nand/version"
|
2
|
+
|
3
|
+
module Nand
|
4
|
+
def wrap_exception
|
5
|
+
yield
|
6
|
+
rescue => e
|
7
|
+
STDERR.puts e.message
|
8
|
+
#STDERR.puts "\n\t" + e.backtrace.join("\n\t")
|
9
|
+
exit -1
|
10
|
+
end
|
11
|
+
def self.additional_argv=(argv)
|
12
|
+
@@additional_argv ||= argv.freeze
|
13
|
+
end
|
14
|
+
def self.additional_argv
|
15
|
+
@@additional_argv
|
16
|
+
end
|
17
|
+
def string_to_stdio( str )
|
18
|
+
if str.to_s.upcase =~/^STD(OUT|ERR|IN)$/
|
19
|
+
eval(str)
|
20
|
+
else
|
21
|
+
str
|
22
|
+
end
|
23
|
+
end
|
24
|
+
def restore_options( options )
|
25
|
+
opts = options.dup
|
26
|
+
unless opts[:debug]
|
27
|
+
opts[:out] &&= string_to_stdio(opts[:out])
|
28
|
+
opts[:err] &&= string_to_stdio(opts[:err])
|
29
|
+
opts[:in] &&= string_to_stdio(opts[:in] )
|
30
|
+
|
31
|
+
opts[:daemon_log] &&= string_to_stdio(opts[:daemon_log])
|
32
|
+
opts[:daemon_out] &&= string_to_stdio(opts[:daemon_out])
|
33
|
+
opts[:daemon_err] &&= string_to_stdio(opts[:daemon_err])
|
34
|
+
|
35
|
+
else
|
36
|
+
opts[:out] = STDOUT
|
37
|
+
opts[:err] = STDERR
|
38
|
+
|
39
|
+
opts[:daemon_out] = STDOUT
|
40
|
+
opts[:daemon_err] = STDERR
|
41
|
+
opts[:daemon_log] = STDOUT
|
42
|
+
end
|
43
|
+
opts
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
include Nand
|
data/lib/nand/cli.rb
ADDED
@@ -0,0 +1,126 @@
|
|
1
|
+
# -*-mode: ruby; coding: utf-8 -*-
|
2
|
+
|
3
|
+
require 'pathname'
|
4
|
+
require 'thor'
|
5
|
+
|
6
|
+
require 'nand/daemon'
|
7
|
+
require 'nand/logger'
|
8
|
+
require 'nand/launcher'
|
9
|
+
|
10
|
+
module Nand
|
11
|
+
class Cli < Thor
|
12
|
+
include Logging
|
13
|
+
|
14
|
+
class_option :run_dir, :desc => "Specify running dir, Default is a Current Directory"
|
15
|
+
class_option :debug, :type => :boolean, :default => false, :hide => true, :desc => "This is debug flag, Daemon process stdout/stderr pipes to this terminal"
|
16
|
+
|
17
|
+
desc "start EXEC_TARGET [OPTIONS] [-- EXEC OPTIONS]", "Start Daemon for EXEC with options"
|
18
|
+
option :out, :aliases => '-o', :default => "/dev/null", :desc => "Daemon process STDOUT pipes to output file"
|
19
|
+
option :err, :aliases => '-e', :default => "/dev/null", :desc => "Daemon process STDERR pipes to output error file"
|
20
|
+
option :in, :aliases => '-i', :default => "/dev/null", :desc => "Daemon process STDIN pipes to input file"
|
21
|
+
option :name, :aliases => '-n', :desc => "Spcify Alias Name of Daemon process, default is first argument(EXEC_TARGET)"
|
22
|
+
option :plugin, :aliases => '-p', :desc => "Nand Plugin File Name or Gem Package Name"
|
23
|
+
option :sec, :aliases => '-s', :type => :numeric, :desc => "Running time limit [seconds] of Daemon process"
|
24
|
+
option :demon_out, :default => "/dev/null", :hide => true, :desc => "Daemon Process STDOUT pipes DEAMON_OUT"
|
25
|
+
option :demon_err, :default => "/dev/null", :hide => true, :desc => "Daemon Process STDERR pipes DEAMON_ERR"
|
26
|
+
option :demon_log, :default => "/dev/null", :desc => "Output Daemon Process Log file"
|
27
|
+
option :recovery, :aliases => '-r', :type => :boolean, :desc => "Auto Recovery"
|
28
|
+
option :recovery_sec, :type => :numeric, :desc => "Auto Recovery time span[seconds]"
|
29
|
+
|
30
|
+
def start(target, *argv)
|
31
|
+
opts = restore_options(options)
|
32
|
+
log_debug! if opts[:debug]
|
33
|
+
dir = run_dir(opts)
|
34
|
+
dir.mkpath unless dir.exist?
|
35
|
+
launcher = find_launcher(target, opts, *argv)
|
36
|
+
opts[:launcher] = launcher
|
37
|
+
daemon = Daemon.new(dir, launcher.execname, opts)
|
38
|
+
print "#{daemon.execname} is Start "
|
39
|
+
begin
|
40
|
+
daemon.run
|
41
|
+
sleep 0
|
42
|
+
if daemon.running?
|
43
|
+
puts "Success [#{daemon.pid}]"
|
44
|
+
end
|
45
|
+
rescue => e
|
46
|
+
puts "Failed [#{e.message}]"
|
47
|
+
log.debug "\t" + e.backtrace.join("\n\t")
|
48
|
+
exit -1
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
desc "status [EXEC_TARGET] [OPTIONS]", "Show Running Daemon Process Status w/wo EXEC_TARGET"
|
53
|
+
option :all, :aliases => '-a', :type => :boolean, :desc => "All running Daemons Status in this Server"
|
54
|
+
def status(target = nil)
|
55
|
+
ds = if options[:all]
|
56
|
+
Daemon.all_runnings
|
57
|
+
else
|
58
|
+
Daemon.running_in(run_dir(options), target)
|
59
|
+
end
|
60
|
+
ds.each do |d|
|
61
|
+
puts status_message(d.run_dir, d.execname, d.running?, d.pid, d.user)
|
62
|
+
end
|
63
|
+
puts status_message(run_dir(options), target, false) if ds.empty? and !target.nil?
|
64
|
+
end
|
65
|
+
|
66
|
+
desc "stop [EXEC_TARGET] [OPTIONS]", "Stop Daemon Process w/wo EXEC_TARGET"
|
67
|
+
option :all, :aliases => '-a', :type => :boolean, :desc => "Show All Running Daemon Status in this Server"
|
68
|
+
def stop(target = nil)
|
69
|
+
dir = run_dir(options)
|
70
|
+
log_debug! if options[:debug]
|
71
|
+
ds = if options[:all]
|
72
|
+
Daemon.all_runnings
|
73
|
+
else
|
74
|
+
Daemon.running_in(dir, target)
|
75
|
+
end
|
76
|
+
ds.each do |d|
|
77
|
+
pid = d.pid
|
78
|
+
if d.running?
|
79
|
+
begin
|
80
|
+
print "#{d.execname} is "
|
81
|
+
d.stop and puts "Stopped [#{pid}]"
|
82
|
+
rescue => e
|
83
|
+
puts "Failed [#{e.message}]"
|
84
|
+
log.debug "\t" + e.backtrace.join("\n\t")
|
85
|
+
end
|
86
|
+
else
|
87
|
+
puts "#{d.execname} is Not Running"
|
88
|
+
end
|
89
|
+
end
|
90
|
+
puts status_message(dir, target, false) if !target.nil? and ds.empty?
|
91
|
+
end
|
92
|
+
|
93
|
+
desc "version", ""
|
94
|
+
def version
|
95
|
+
puts File.basename($PROGRAM_NAME) + " " + VERSION
|
96
|
+
end
|
97
|
+
|
98
|
+
private
|
99
|
+
def find_launcher(target, opts, *argv, &block)
|
100
|
+
launcher = Nand::Launcher.find( target, opts, *(argv + Nand.additional_argv) )
|
101
|
+
if block_given?
|
102
|
+
block.call(launcher)
|
103
|
+
else
|
104
|
+
launcher
|
105
|
+
end
|
106
|
+
end
|
107
|
+
def run_dir(opts)
|
108
|
+
Pathname.new(opts[:run_dir] || Dir.pwd)
|
109
|
+
end
|
110
|
+
def status_message(dir, target, running, pid = nil, user = nil)
|
111
|
+
"#{target} is " + (running ? "Running [#{pid}] by #{user}" : "Not Running") + " in #{dir}"
|
112
|
+
end
|
113
|
+
no_tasks do
|
114
|
+
# デフォルトメソッドを上書きして -h をヘルプ
|
115
|
+
def invoke_task(task, *args)
|
116
|
+
if options[:help]
|
117
|
+
help(task.name)
|
118
|
+
elsif options[:version] or args.flatten.first == "-v"
|
119
|
+
version
|
120
|
+
else
|
121
|
+
super
|
122
|
+
end
|
123
|
+
end
|
124
|
+
end
|
125
|
+
end
|
126
|
+
end
|
data/lib/nand/daemon.rb
ADDED
@@ -0,0 +1,127 @@
|
|
1
|
+
# -*-mode: ruby; coding: utf-8 -*-
|
2
|
+
|
3
|
+
require 'pathname'
|
4
|
+
require 'nand/proc_operation'
|
5
|
+
require 'nand/logger'
|
6
|
+
|
7
|
+
module Nand
|
8
|
+
class Daemon
|
9
|
+
extend ProcOperation
|
10
|
+
include Logging
|
11
|
+
def logger_output_params ; [@daemon_log] end
|
12
|
+
def logger_progname ; @execname end
|
13
|
+
def logger_formatter ; TimeFormatter.new end
|
14
|
+
|
15
|
+
attr_reader :execname, :run_dir
|
16
|
+
def initialize(run_dir, execname, opts = {} )
|
17
|
+
@run_dir = Pathname.new(run_dir)
|
18
|
+
@execname = execname.to_s
|
19
|
+
@launcher = opts[:launcher]
|
20
|
+
@arg_uid = opts[:uid]
|
21
|
+
@pid_file = @run_dir.join(self.class.pid_filename(@execname))
|
22
|
+
@daemon_log = opts[:daemon_log] || "/dev/null"
|
23
|
+
@daemon_out = opts[:daemon_out] || "/dev/null"
|
24
|
+
@daemon_err = opts[:daemon_err] || "/dev/null"
|
25
|
+
@recovery = opts[:recovery] || false
|
26
|
+
@recovery_sec = opts[:recovery_sec] || 1
|
27
|
+
@limit = opts[:sec]
|
28
|
+
|
29
|
+
log.level = LOG_DEBUG if opts[:debug]
|
30
|
+
end
|
31
|
+
def user
|
32
|
+
Etc.getpwuid(uid).name
|
33
|
+
rescue => e
|
34
|
+
nil
|
35
|
+
end
|
36
|
+
def uid; @arg_uid || Process.uid end
|
37
|
+
def running?; @pid_file.exist? and self.class.running_with?(pid, @execname) end
|
38
|
+
|
39
|
+
def run
|
40
|
+
raise "Launcher is Not Specified for #{@execname}" if @launcher.nil?
|
41
|
+
raise "PID file exist #{@pid_file}" if @pid_file.exist?
|
42
|
+
daemonize
|
43
|
+
end
|
44
|
+
def stop; stop_with_signal(:TERM) end
|
45
|
+
def kill; stop_with_signal(:KILL) end
|
46
|
+
def pid; @pid ||=self.class.pid_from_pidfile(@pid_file) end
|
47
|
+
|
48
|
+
private
|
49
|
+
def daemonize( &block )
|
50
|
+
if child = Process.fork
|
51
|
+
Process.waitpid(child) # wait until exit child process
|
52
|
+
return child
|
53
|
+
end
|
54
|
+
Process.setsid
|
55
|
+
exit(0) if Process.fork # double fork and exit child process
|
56
|
+
Process.setsid
|
57
|
+
Dir.chdir(@run_dir)
|
58
|
+
File.umask(0)
|
59
|
+
STDIN.reopen("/dev/null")
|
60
|
+
STDOUT.reopen(@daemon_out)
|
61
|
+
STDERR.reopen(@daemon_err)
|
62
|
+
|
63
|
+
begin
|
64
|
+
File.open(@pid_file, "w"){|fs| fs.puts Process.pid }
|
65
|
+
|
66
|
+
log.info "Daemonize [#{Process.pid}]"
|
67
|
+
|
68
|
+
Signal.trap(:INT) {Thread.new{log.warn("RECEVIED Signal INT") ; send_signal_and_exit(:INT)}}
|
69
|
+
Signal.trap(:TERM) {Thread.new{log.warn("RECEVIED Signal TERM") ; send_signal_and_exit(:TERM)}}
|
70
|
+
begin
|
71
|
+
sleep 0.1
|
72
|
+
|
73
|
+
@child = @launcher.launch
|
74
|
+
raise "Failed Launch for #{@execname}" if @child.nil?
|
75
|
+
log.info "Launched Child Process [#{@child}]"
|
76
|
+
|
77
|
+
wait_untill_limit if !@limit.nil? and 0 < @limit
|
78
|
+
|
79
|
+
Process.waitpid2(@child) unless @child.nil?
|
80
|
+
log.warn "PID #{@child} down"
|
81
|
+
sleep @recovery_sec if @recovery
|
82
|
+
end while @recovery
|
83
|
+
rescue => e
|
84
|
+
log.fatal e.message
|
85
|
+
log.debug "\n\t" + e.backtrace.join("\n\t")
|
86
|
+
ensure
|
87
|
+
terminate
|
88
|
+
end
|
89
|
+
end
|
90
|
+
def terminate( code = 0 )
|
91
|
+
unless @child.nil?
|
92
|
+
log.info "terminate for #{@child} with exit code #{code}"
|
93
|
+
@child = nil
|
94
|
+
end
|
95
|
+
@pid_file.delete if @pid_file.exist?
|
96
|
+
exit code
|
97
|
+
end
|
98
|
+
def send_signal_and_exit(type)
|
99
|
+
Process.kill(type, -@child) unless @child.nil?
|
100
|
+
log.warn "Sent Signal #{type} to #{@child}"
|
101
|
+
terminate
|
102
|
+
rescue => e
|
103
|
+
log.fatal "Failed Send Signal to -#{@child}, since #{e.message}"
|
104
|
+
terminate -1
|
105
|
+
end
|
106
|
+
private
|
107
|
+
def stop_with_signal(signal)
|
108
|
+
raise "Can Not Send #{signal.to_s.capitalize} Signal to #{@execname}" unless uid == Process.uid or uid == 0
|
109
|
+
unless running?
|
110
|
+
@pid_file.delete if @pid_file.exist?
|
111
|
+
raise "#{@execname} is Not Running"
|
112
|
+
end
|
113
|
+
Process.kill(signal, pid)
|
114
|
+
end
|
115
|
+
def wait_untill_limit
|
116
|
+
Signal.trap(:SIGCHLD) {terminate}
|
117
|
+
pid, status = Process.waitpid2(@child, Process::WNOHANG)
|
118
|
+
if pid.nil?
|
119
|
+
log.info "Child[#{@child}] will be Stopped after #{@limit} sec"
|
120
|
+
sleep @limit
|
121
|
+
Signal.trap(:SIGCHLD, "IGNORE")
|
122
|
+
log.info "Send Signal TERM to Child[#{@child}] #{@limit} sec past"
|
123
|
+
Process.kill(:TERM, @child)
|
124
|
+
end
|
125
|
+
end
|
126
|
+
end
|
127
|
+
end
|
@@ -0,0 +1,55 @@
|
|
1
|
+
# -*-mode: ruby; coding: utf-8 -*-
|
2
|
+
|
3
|
+
require 'pathname'
|
4
|
+
require 'stringio'
|
5
|
+
|
6
|
+
module Nand
|
7
|
+
module Launcher
|
8
|
+
def self.find(target, opts = {}, *argv)
|
9
|
+
require 'nand/launcher/executable_file_launcher'
|
10
|
+
require 'nand/launcher/shell_launcher'
|
11
|
+
require 'nand/launcher/rb_file_launcher'
|
12
|
+
require 'nand/launcher/plugin_launcher'
|
13
|
+
|
14
|
+
err = StringIO.new("", "w")
|
15
|
+
launcher_klass = [ExecutableFileLauncher, RbFileLauncher, PluginLauncher, ShellLauncher].find do |klass|
|
16
|
+
klass.launchable? target, err, opts
|
17
|
+
end
|
18
|
+
raise "Not Found Executable #{target}:\n#{io.string}" if launcher_klass.nil?
|
19
|
+
launcher = launcher_klass.load(target, opts, *argv)
|
20
|
+
raise "Not be Ready for #{target} Launcher" unless launcher.ready?
|
21
|
+
launcher
|
22
|
+
end
|
23
|
+
|
24
|
+
class Base
|
25
|
+
def self.launchable?(target, io, *argv)
|
26
|
+
raise "Not Implemented #{__method__} in #{self.name}"
|
27
|
+
end
|
28
|
+
def self.load( target, opts, *argv)
|
29
|
+
raise "Not Implemented #{__method__} in #{self.name}"
|
30
|
+
end
|
31
|
+
attr_reader :execname
|
32
|
+
def initialize(target, opts, *argv)
|
33
|
+
@progname = target
|
34
|
+
@execname = opts[:name] || File.basename(target)
|
35
|
+
@exec_stdout = opts[:out] || "/dev/null"
|
36
|
+
@exec_stderr = opts[:err] || "/dev/null"
|
37
|
+
@exec_stdin = opts[:in] || "/dev/null"
|
38
|
+
@argv = argv
|
39
|
+
end
|
40
|
+
def launch; end
|
41
|
+
def ready?
|
42
|
+
[@exec_stdout, @exec_stderr].each do |out|
|
43
|
+
next if out.is_a? IO
|
44
|
+
path = Pathname.new(out)
|
45
|
+
raise "Illegal Output File #{path.to_s}" unless (path.exist? and path.writable?) or
|
46
|
+
(!path.exist? and path.dirname.writable?)
|
47
|
+
end
|
48
|
+
return true if @exec_stdin.is_a? IO
|
49
|
+
path = Pathname.new(@exec_stdin)
|
50
|
+
raise "Illegal Input File #{@exec_stdin.to_s}" unless path.exist? and path.readable?
|
51
|
+
true
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
# -*-mode: ruby; coding: utf-8 -*-
|
2
|
+
|
3
|
+
require 'nand/launcher/shell_launcher'
|
4
|
+
|
5
|
+
module Nand
|
6
|
+
module Launcher
|
7
|
+
class ExecutableFileLauncher < ShellLauncher
|
8
|
+
def self.launchable?(target, io, opts)
|
9
|
+
file = exec_file(target)
|
10
|
+
raise "Not Found #{file.to_s}" unless file.exist?
|
11
|
+
raise "Not Executable #{file.to_s}" unless file.executable?
|
12
|
+
true
|
13
|
+
rescue => e
|
14
|
+
io.puts "\t- " + e.message
|
15
|
+
false
|
16
|
+
end
|
17
|
+
def self.load(target, opts = {}, *argv)
|
18
|
+
file = exec_file(target)
|
19
|
+
new(target, opts, *argv)
|
20
|
+
end
|
21
|
+
def self.exec_file(target)
|
22
|
+
Pathname.new(target).expand_path
|
23
|
+
end
|
24
|
+
attr_reader :name
|
25
|
+
def initialize(target, opts, *argv)
|
26
|
+
super(target, opts, *argv)
|
27
|
+
@file = self.class.exec_file(target)
|
28
|
+
@name = opts[:name] || @file.basename.to_s
|
29
|
+
end
|
30
|
+
def cmd; "#{@file} #{@argv.join(" ")}" end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
|
@@ -0,0 +1,29 @@
|
|
1
|
+
# -*-mode: ruby; coding: utf-8 -*-
|
2
|
+
|
3
|
+
require 'nand/plugin'
|
4
|
+
require 'nand/launcher/rb_file_launcher'
|
5
|
+
|
6
|
+
module Nand
|
7
|
+
module Launcher
|
8
|
+
class PluginLauncher < RbFileLauncher
|
9
|
+
def self.require_rb(target)
|
10
|
+
require "#{target}/nand/plugin"
|
11
|
+
end
|
12
|
+
def self.launchable?( target, io, opts )
|
13
|
+
specs = Gem::Specification.find_all do |s|
|
14
|
+
s.name =~/^(nand-)*#{target}$/ and s.dependencies.find{ |d| d.name == "nand" }
|
15
|
+
end
|
16
|
+
raise "Not Found #{target} in Installed gems" if specs.empty?
|
17
|
+
raise "Target name #{target} is Not Uniq for installed gem" if 1 < specs.size
|
18
|
+
require_rb(specs.first.name)
|
19
|
+
true
|
20
|
+
rescue LoadError => e
|
21
|
+
io.puts "\t- " + e.message
|
22
|
+
false
|
23
|
+
rescue => e
|
24
|
+
io.puts "\t- " + e.message
|
25
|
+
false
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|