diff options
author | knu <knu@b2dd03c8-39d4-4d8f-98ff-823fe69b080e> | 2001-05-17 10:02:47 +0000 |
---|---|---|
committer | knu <knu@b2dd03c8-39d4-4d8f-98ff-823fe69b080e> | 2001-05-17 10:02:47 +0000 |
commit | d9350f56002fde6b5640b93ae7b8885cb7811d1a (patch) | |
tree | 911c962685a2c4caf03fc058a9a0afd3a3a0bdeb /lib/shell | |
parent | c2fa753f7db4ea07c50ec8a182f78b1a263e0709 (diff) |
Initial revision
git-svn-id: svn+ssh://ci.ruby-lang.org/ruby/trunk@1418 b2dd03c8-39d4-4d8f-98ff-823fe69b080e
Diffstat (limited to 'lib/shell')
-rw-r--r-- | lib/shell/builtin-command.rb | 154 | ||||
-rw-r--r-- | lib/shell/command-processor.rb | 591 | ||||
-rw-r--r-- | lib/shell/error.rb | 26 | ||||
-rw-r--r-- | lib/shell/filter.rb | 111 | ||||
-rw-r--r-- | lib/shell/process-controller.rb | 258 | ||||
-rw-r--r-- | lib/shell/system-command.rb | 168 | ||||
-rw-r--r-- | lib/shell/version.rb | 16 |
7 files changed, 1324 insertions, 0 deletions
diff --git a/lib/shell/builtin-command.rb b/lib/shell/builtin-command.rb new file mode 100644 index 0000000000..db1adfa48b --- /dev/null +++ b/lib/shell/builtin-command.rb @@ -0,0 +1,154 @@ +# +# shell/builtin-command.rb - +# $Release Version: 0.6.0 $ +# $Revision$ +# $Date$ +# by Keiju ISHITSUKA(Nihon Rational Software Co.,Ltd) +# +# -- +# +# +# + +require "shell/filter" + +class Shell + class BuiltInCommand<Filter + def wait? + false + end + def active? + true + end + end + + class Echo < BuiltInCommand + def initialize(sh, *strings) + super sh + @strings = strings + end + + def each(rs = nil) + rs = @shell.record_separator unless rs + for str in @strings + yield str + rs + end + end + end + + class Cat < BuiltInCommand + def initialize(sh, *filenames) + super sh + @cat_files = filenames + end + + def each(rs = nil) + if @cat_files.empty? + super + else + for src in @cat_files + @shell.foreach(src, rs){|l| yield l} + end + end + end + end + + class Glob < BuiltInCommand + def initialize(sh, pattern) + super sh + + @pattern = pattern + Thread.critical = true + back = Dir.pwd + begin + Dir.chdir @shell.cwd + @files = Dir[pattern] + ensure + Dir.chdir back + Thread.critical = false + end + end + + def each(rs = nil) + rs = @shell.record_separator unless rs + for f in @files + yield f+rs + end + end + end + +# class Sort < Cat +# def initialize(sh, *filenames) +# super +# end +# +# def each(rs = nil) +# ary = [] +# super{|l| ary.push l} +# for l in ary.sort! +# yield l +# end +# end +# end + + class AppendIO < BuiltInCommand + def initialize(sh, io, filter) + super sh + @input = filter + @io = io + end + + def input=(filter) + @input.input=filter + for l in @input + @io << l + end + end + + end + + class AppendFile < AppendIO + def initialize(sh, to_filename, filter) + @file_name = to_filename + io = sh.open(to_filename, "a") + super(sh, io, filter) + end + + def input=(filter) + begin + super + ensure + @io.close + end + end + end + + class Tee < BuiltInCommand + def initialize(sh, filename) + super sh + @to_filename = filename + end + + def each(rs = nil) + to = @shell.open(@to_filename, "w") + begin + super{|l| to << l; yield l} + ensure + to.close + end + end + end + + class Concat < BuiltInCommand + def initialize(sh, *jobs) + super(sh) + @jobs = jobs + end + + def each(rs = nil) + while job = @jobs.shift + job.each{|l| yield l} + end + end + end +end diff --git a/lib/shell/command-processor.rb b/lib/shell/command-processor.rb new file mode 100644 index 0000000000..38e38151fa --- /dev/null +++ b/lib/shell/command-processor.rb @@ -0,0 +1,591 @@ +# +# shell/command-controller.rb - +# $Release Version: 0.6.0 $ +# $Revision$ +# $Date$ +# by Keiju ISHITSUKA(Nippon Rational Inc.) +# +# -- +# +# +# + +require "e2mmap" +require "ftools" +require "thread" + +require "shell/error" +require "shell/filter" +require "shell/system-command" +require "shell/builtin-command" + +class Shell + class CommandProcessor + + # + # initialize of Shell and related classes. + # + NoDelegateMethods = ["initialize", "expand_path"] + def self.initialize + + install_builtin_commands + + # define CommandProccessor#methods to Shell#methods and Filter#methods + for m in CommandProcessor.instance_methods - NoDelegateMethods + add_delegate_command_to_shell(m) + end + + def self.method_added(id) + add_delegate_command_to_shell(id) + end + end + + # + # include run file. + # + def self.run_config + begin + load File.expand_path("~/.rb_shell") if ENV.key?("HOME") + rescue LoadError, Errno::ENOENT + rescue + print "load error: #{rc}\n" + print $!.type, ": ", $!, "\n" + for err in $@[0, [email protected] - 2] + print "\t", err, "\n" + end + end + end + + def initialize(shell) + @shell = shell + @system_commands = {} + end + + # + # CommandProcessor#expand_path(path) + # path: String + # return: String + # pwd����ߤ����Хѥ����֤� + # + def expand_path(path) + @shell.expand_path(path) + end + + # + # File��Ϣ���ޥ�� + # Shell#foreach + # Shell#open + # Shell#unlink + # Shell#test + # + # - + # + # CommandProcessor#foreach(path, rs) + # path: String + # rs: String - record separator + # iterator + # Same as: + # File#foreach (when path is file) + # Dir#foreach (when path is directory) + # path��pwd��������Хѥ��ˤʤ� + # + def foreach(path = nil, *rs) + path = "." unless path + path = expand_path(path) + + if File.directory?(path) + Dir.foreach(path){|fn| yield fn} + else + IO.foreach(path, *rs){|l| yield l} + end + end + + # + # CommandProcessor#open(path, mode) + # path: String + # mode: String + # return: File or Dir + # Same as: + # File#open (when path is file) + # Dir#open (when path is directory) + # mode��path���ե�����λ�����ͭ�� + # + def open(path, mode) + path = expand_path(path) + if File.directory?(path) + Dir.open(path) + else + effect_umask do + File.open(path, mode) + end + end + end + # public :open + + # + # CommandProcessor#unlink(path) + # same as: + # Dir#unlink (when path is directory) + # File#unlink (when path is file) + # + def unlink(path) + path = expand_path(path) + if File.directory?(path) + Dir.unlink(path) + else + IO.unlink(path) + end + end + + # + # CommandProcessor#test(command, file1, file2) + # CommandProcessor#[command, file1, file2] + # command: char or String or Symbol + # file1: String + # file2: String(optional) + # return: Boolean + # same as: + # test() (when command is char or length 1 string or sumbol) + # FileTest.command (others) + # example: + # sh[?e, "foo"] + # sh[:e, "foo"] + # sh["e", "foo"] + # sh[:exists?, "foo"] + # sh["exists?", "foo"] + # + def test(command, file1, file2=nil) + file1 = expand_path(file1) + file2 = expand_path(file2) if file2 + command = command.id2name if command.kind_of?(Symbol) + + case command + when Integer + top_level_test(command, file1, file2) + when String + if command.size == 1 + if file2 + top_level_test(command, file1, file2) + else + top_level_test(command, file1) + end + else + if file2 + FileTest.send(command, file1, file2) + else + FileTest.send(command, file1) + end + end + end + end + alias [] test + + # + # Dir��Ϣ��å� + # + # Shell#mkdir + # Shell#rmdir + # + #-- + # + # CommandProcessor#mkdir(*path) + # path: String + # same as Dir.mkdir() + # + def mkdir(*path) + for dir in path + Dir.mkdir(expand_path(dir)) + end + end + + # + # CommandProcessor#rmdir(*path) + # path: String + # same as Dir.rmdir() + # + def rmdir(*path) + for dir in path + Dir.rmdir(expand_path(path)) + end + end + + # + # CommandProcessor#system(command, *opts) + # command: String + # opts: String + # retuen: SystemCommand + # Same as system() function + # example: + # print sh.system("ls", "-l") + # sh.system("ls", "-l") | sh.head > STDOUT + # + def system(command, *opts) + SystemCommand.new(@shell, find_system_command(command), *opts) + end + + # + # ProcessCommand#rehash + # clear command hash table. + # + def rehash + @system_commands = {} + end + + # + # ProcessCommand#transact + # + def check_point + @shell.process_controller.wait_all_jobs_execution + end + alias finish_all_jobs check_point + + def transact(&block) + begin + @shell.instance_eval &block + ensure + check_point + end + end + + # + # internal commands + # + def out(dev = STDOUT, &block) + dev.print transact &block + end + + def echo(*strings) + Echo.new(@shell, *strings) + end + + def cat(*filenames) + Cat.new(@shell, *filenames) + end + + # def sort(*filenames) + # Sort.new(self, *filenames) + # end + + def glob(pattern) + Glob.new(@shell, pattern) + end + + def append(to, filter) + case to + when String + AppendFile.new(@shell, to, filter) + when IO + AppendIO.new(@shell, to, filter) + else + Shell.Fail CanNotMethodApply, "append", to.type + end + end + + def tee(file) + Tee.new(@shell, file) + end + + def concat(*jobs) + Concat.new(@shell, *jobs) + end + + # %pwd, %cwd -> @pwd + def notify(*opts, &block) + Thread.exclusive do + Shell.notify(*opts) {|mes| + yield mes if iterator? + + mes.gsub!("%pwd", "#{@cwd}") + mes.gsub!("%cwd", "#{@cwd}") + } + end + end + + # + # private functions + # + def effect_umask + if @shell.umask + Thread.critical = true + save = File.umask + begin + yield + ensure + File.umask save + Thread.critical = false + end + else + yield + end + end + private :effect_umask + + def find_system_command(command) + return command if /^\// =~ command + case path = @system_commands[command] + when String + if exists?(path) + return path + else + Shell.Fail CommandNotFound, command + end + when false + Shell.Fail CommandNotFound, command + end + + for p in @shell.system_path + path = join(p, command) + if FileTest.exists?(path) + @system_commands[command] = path + return path + end + end + @system_commands[command] = false + Shell.Fail CommandNotFound, command + end + + # + # CommandProcessor.def_system_command(command, path) + # command: String + # path: String + # define 'command()' method as method. + # + def self.def_system_command(command, path = command) + begin + eval ((d = %Q[def #{command}(*opts) + SystemCommand.new(@shell, '#{path}', *opts) + end]), nil, __FILE__, __LINE__ - 1) + rescue SyntaxError + Shell.notify "warn: Can't define #{command} path: #{path}." + end + Shell.notify "Define #{command} path: #{path}.", Shell.debug? + Shell.notify("Definition of #{command}: ", d, + Shell.debug.kind_of?(Integer) && Shell.debug > 1) + end + + def self.undef_system_command(command) + command = command.id2name if command.kind_of?(Symbol) + remove_method(command) + Shell.module_eval{remove_method(command)} + Filter.module_eval{remove_method(command)} + self + end + + # define command alias + # ex) + # def_alias_command("ls_c", "ls", "-C", "-F") + # def_alias_command("ls_c", "ls"){|*opts| ["-C", "-F", *opts]} + # + @alias_map = {} + def self.alias_map + @alias_map + end + def self.alias_command(ali, command, *opts, &block) + ali = ali.id2name if ali.kind_of?(Symbol) + command = command.id2name if command.kind_of?(Symbol) + begin + if iterator? + @alias_map[ali.intern] = proc + + eval ((d = %Q[def #{ali}(*opts) + @shell.__send__(:#{command}, + *(CommandProcessor.alias_map[:#{ali}].call *opts)) + end]), nil, __FILE__, __LINE__ - 1) + + else + args = opts.collect{|opt| '"' + opt + '"'}.join "," + eval ((d = %Q[def #{ali}(*opts) + @shell.__send__(:#{command}, #{args}, *opts) + end]), nil, __FILE__, __LINE__ - 1) + end + rescue SyntaxError + Shell.notify "warn: Can't alias #{ali} command: #{command}." + Shell.notify("Definition of #{ali}: ", d) + raise + end + Shell.notify "Define #{ali} command: #{command}.", Shell.debug? + Shell.notify("Definition of #{ali}: ", d, + Shell.debug.kind_of?(Integer) && Shell.debug > 1) + self + end + + def self.unalias_command(ali) + ali = ali.id2name if ali.kind_of?(Symbol) + @alias_map.delete ali.intern + undef_system_command(ali) + end + + # + # CommandProcessor.def_builtin_commands(delegation_class, command_specs) + # delegation_class: Class or Module + # command_specs: [[command_name, [argument,...]],...] + # command_name: String + # arguments: String + # FILENAME?? -> expand_path(filename??) + # *FILENAME?? -> filename??.collect{|f|expand_path(f)}.join(", ") + # define command_name(argument,...) as + # delegation_class.command_name(argument,...) + # + def self.def_builtin_commands(delegation_class, command_specs) + for meth, args in command_specs + arg_str = args.collect{|arg| arg.downcase}.join(", ") + call_arg_str = args.collect{ + |arg| + case arg + when /^(FILENAME.*)$/ + format("expand_path(%s)", $1.downcase) + when /^(\*FILENAME.*)$/ + # \*FILENAME* -> filenames.collect{|fn| expand_path(fn)}.join(", ") + $1.downcase + '.collect{|fn| expand_path(fn)}' + else + arg + end + }.join(", ") + d = %Q[def #{meth}(#{arg_str}) + #{delegation_class}.#{meth}(#{call_arg_str}) + end] + Shell.notify "Define #{meth}(#{arg_str})", Shell.debug? + Shell.notify("Definition of #{meth}: ", d, + Shell.debug.kind_of?(Integer) && Shell.debug > 1) + eval d + end + end + + # + # CommandProcessor.install_system_commands(pre) + # pre: String - command name prefix + # define CommandProcessor.command() from all command of + # default_system_path. If a method exists, and names of it and + # the target command are the same, the method is not defined. + # Default action prefix "sys_" to the method name. The character + # which is not forgiven as a method name (when the first char is + # alphabet or char is alpha-numeric) converts into ``_''. A + # definition error is ignored. + # (Meaning same in Japanese: default_system_path��ˤΤ륳�ޥ�ɤ��� + # ������. ���Ǥ�Ʊ̾�Υ�åɤ�¸�ߤ������, �����Ԥʤ�ʤ�. �� + # �ե���ȤǤ�, ���ƤΥ�åɤˤ���Ƭ��"sys_"��Ĥ���. ��å�̾ + # �Ȥ��Ƶ�����ʤ�����饯��(�ѿ����ʳ��ȥ�å�̾����Ƭ�����ͤ� + # �ʤ���)��, ����Ū��``_''���Ѵ�����. ������顼��̵�뤹��.) + # + def self.install_system_commands(pre = "sys_") + defined_meth = {} + for m in Shell.methods + defined_meth[m] = true + end + sh = Shell.new + for path in Shell.default_system_path + next unless sh.directory? path + sh.cd path + sh.foreach do + |cn| + if !defined_meth[pre + cn] && sh.file?(cn) && sh.executable?(cn) + command = (pre + cn).gsub(/\W/, "_").sub(/^([0-9])/, '_\1') + begin + def_system_command(command, sh.expand_path(cn)) + rescue + Shell.notify "warn: Can't define #{command} path: #{cn}" + end + defined_meth[command] = command + end + end + end + end + + #---------------------------------------------------------------------- + # + # class initializing methods - + # + #---------------------------------------------------------------------- + def self.add_delegate_command_to_shell(id) + id = id.intern if id.kind_of?(String) + name = id.id2name + if Shell.method_defined?(id) + Shell.notify "warn: override definnition of Shell##{name}." + Shell.notify "warn: alias Shell##{name} to Shell##{name}_org.\n" + Shell.module_eval "alias #{name}_org #{name}" + end + Shell.notify "method added: Shell##{name}.", Shell.debug? + Shell.module_eval(%Q[def #{name}(*args, &block) + begin + @command_processor.__send__(:#{name}, *args, &block) + rescue Exception + [email protected]_if{|s| /:in `__getobj__'$/ =~ s} #` + [email protected]_if{|s| /^\\(eval\\):/ =~ s} + raise + end + end], __FILE__, __LINE__) + + if Shell::Filter.method_defined?(id) + Shell.notify "warn: override definnition of Shell::Filter##{name}." + Shell.notify "warn: alias Shell##{name} to Shell::Filter##{name}_org." + Filter.module_eval "alias #{name}_org #{name}" + end + Shell.notify "method added: Shell::Filter##{name}.", Shell.debug? + Filter.module_eval(%Q[def #{name}(*args, &block) + begin + self | @shell.__send__(:#{name}, *args, &block) + rescue Exception + [email protected]_if{|s| /:in `__getobj__'$/ =~ s} #` + [email protected]_if{|s| /^\\(eval\\):/ =~ s} + raise + end + end], __FILE__, __LINE__) + end + + # + # define default builtin commands + # + def self.install_builtin_commands + # method related File. + # (exclude open/foreach/unlink) + normal_delegation_file_methods = [ + ["atime", ["FILENAME"]], + ["basename", ["fn", "*opts"]], + ["chmod", ["mode", "*FILENAMES"]], + ["chown", ["owner", "group", "*FILENAME"]], + ["ctime", ["FILENAMES"]], + ["delete", ["*FILENAMES"]], + ["dirname", ["FILENAME"]], + ["ftype", ["FILENAME"]], + ["join", ["*items"]], + ["link", ["FILENAME_O", "FILENAME_N"]], + ["lstat", ["FILENAME"]], + ["mtime", ["FILENAME"]], + ["readlink", ["FILENAME"]], + ["rename", ["FILENAME_FROM", "FILENAME_TO"]], + # ["size", ["FILENAME"]], + ["split", ["pathname"]], + ["stat", ["FILENAME"]], + ["symlink", ["FILENAME_O", "FILENAME_N"]], + ["truncate", ["FILENAME", "length"]], + ["utime", ["atime", "mtime", "*FILENAMES"]]] + + def_builtin_commands(File, normal_delegation_file_methods) + alias_method :rm, :delete + + # method related FileTest + def_builtin_commands(FileTest, + FileTest.singleton_methods.collect{|m| [m, ["FILENAME"]]}) + + # method related ftools + normal_delegation_ftools_methods = [ + ["syscopy", ["FILENAME_FROM", "FILENAME_TO"]], + ["copy", ["FILENAME_FROM", "FILENAME_TO"]], + ["move", ["FILENAME_FROM", "FILENAME_TO"]], + ["compare", ["FILENAME_FROM", "FILENAME_TO"]], + ["safe_unlink", ["*FILENAMES"]], + ["makedirs", ["*FILENAMES"]], + # ["chmod", ["mode", "*FILENAMES"]], + ["install", ["FILENAME_FROM", "FILENAME_TO", "mode"]], + ] + def_builtin_commands(File, + normal_delegation_ftools_methods) + alias_method :cmp, :compare + alias_method :mv, :move + alias_method :cp, :copy + alias_method :rm_f, :safe_unlink + alias_method :mkpath, :makedirs + end + + end +end diff --git a/lib/shell/error.rb b/lib/shell/error.rb new file mode 100644 index 0000000000..df5e669af6 --- /dev/null +++ b/lib/shell/error.rb @@ -0,0 +1,26 @@ +# +# shell/error.rb - +# $Release Version: 0.6.0 $ +# $Revision$ +# $Date$ +# by Keiju ISHITSUKA(Nihon Rational Software Co.,Ltd) +# +# -- +# +# +# + +require "e2mmap" + +class Shell + module Error + extend Exception2MessageMapper + def_e2message TypeError, "wrong argument type %s (expected %s)" + + def_exception :DirStackEmpty, "Directory stack empty." + def_exception :CanNotDefine, "Can't define method(%s, %s)." + def_exception :CanNotMethodApply, "This method(%s) can't apply this type(%s)." + def_exception :CommandNotFound, "Command not found(%s)." + end +end + diff --git a/lib/shell/filter.rb b/lib/shell/filter.rb new file mode 100644 index 0000000000..441cded221 --- /dev/null +++ b/lib/shell/filter.rb @@ -0,0 +1,111 @@ +# +# shell/filter.rb - +# $Release Version: 0.6.0 $ +# $Revision$ +# $Date$ +# by Keiju ISHITSUKA(Nihon Rational Software Co.,Ltd) +# +# -- +# +# +# + +class Shell + # + # Filter + # A method to require + # each() + # + class Filter + include Enumerable + include Error + + def initialize(sh) + @shell = sh # parent shell + @input = nil # input filter + end + + attr_reader :input + + def input=(filter) + @input = filter + end + + def each(rs = nil) + rs = @shell.record_separator unless rs + if @input + @input.each(rs){|l| yield l} + end + end + + def < (src) + case src + when String + cat = Cat.new(@shell, src) + cat | self + when IO + self.input = src + self + else + Filter.Fail CanNotMethodApply, "<", to.type + end + end + + def > (to) + case to + when String + dst = @shell.open(to, "w") + begin + each(){|l| dst << l} + ensure + dst.close + end + when IO + each(){|l| to << l} + else + Filter.Fail CanNotMethodApply, ">", to.type + end + self + end + + def >> (to) + begin + Shell.cd(@shell.pwd).append(to, self) + rescue CanNotMethodApply + Shell.Fail CanNotMethodApply, ">>", to.type + end + end + + def | (filter) + filter.input = self + if active? + @shell.process_controller.start_job filter + end + filter + end + + def + (filter) + Join.new(@shell, self, filter) + end + + def to_a + ary = [] + each(){|l| ary.push l} + ary + end + + def to_s + str = "" + each(){|l| str.concat l} + str + end + + def inspect + if @shell.debug.kind_of?(Integer) && @shell.debug > 2 + super + else + to_s + end + end + end +end diff --git a/lib/shell/process-controller.rb b/lib/shell/process-controller.rb new file mode 100644 index 0000000000..5cbbe0c500 --- /dev/null +++ b/lib/shell/process-controller.rb @@ -0,0 +1,258 @@ +# +# shell/process-controller.rb - +# $Release Version: 0.6.0 $ +# $Revision$ +# $Date$ +# by Keiju ISHITSUKA(Nihon Rational Software Co.,Ltd) +# +# -- +# +# +# + +require "mutex_m" +require "monitor" +require "sync" + +class Shell + class ProcessController + + @ProcessControllers = {} + @ProcessControllers.extend Mutex_m + + class<<self + + def process_controllers_exclusive + begin + @ProcessControllers.lock unless Thread.critical + yield + ensure + @ProcessControllers.unlock unless Thread.critical + end + end + + def activate(pc) + process_controllers_exclusive do + @ProcessControllers[pc] ||= 0 + @ProcessControllers[pc] += 1 + end + end + + def inactivate(pc) + process_controllers_exclusive do + if @ProcessControllers[pc] + if (@ProcessControllers[pc] -= 1) == 0 + @ProcessControllers.delete(pc) + end + end + end + end + + def each_active_object + process_controllers_exclusive do + for ref in @ProcessControllers.keys + yield ref + end + end + end + end + + def initialize(shell) + @shell = shell + @waiting_jobs = [] + @active_jobs = [] + @jobs_sync = Sync.new + + @job_monitor = Mutex.new + @job_condition = ConditionVariable.new + end + + def jobs + jobs = [] + @jobs_sync.synchronize(:SH) do + jobs.concat @waiting_jobs + jobs.concat @active_jobs + end + jobs + end + + def active_jobs + @active_jobs + end + + def waiting_jobs + @waiting_jobs + end + + def jobs_exist? + @jobs_sync.synchronize(:SH) do + @active_jobs.empty? or @waiting_jobs.empty? + end + end + + def active_jobs_exist? + @jobs_sync.synchronize(:SH) do + @active_jobs.empty? + end + end + + def waiting_jobs_exist? + @jobs_sync.synchronize(:SH) do + @waiting_jobs.empty? + end + end + + # job�Υ������塼����ɲ� + def add_schedule(command) + @jobs_sync.synchronize(:EX) do + ProcessController.activate(self) + if @active_jobs.empty? + start_job command + else + @waiting_jobs.push(command) + end + end + end + + # job �Ϥ��� + def start_job(command = nil) + @jobs_sync.synchronize(:EX) do + if command + return if command.active? + @waiting_jobs.delete command + else + command = @waiting_jobs.shift + return unless command + end + @active_jobs.push command + command.start + + # ����job��input�Ȥ���job�Ⳬ�Ϥ��� + for job in @waiting_jobs + start_job(job) if job.input == command + end + end + end + + def waiting_job?(job) + @jobs_sync.synchronize(:SH) do + @waiting_jobs.include?(job) + end + end + + def active_job?(job) + @jobs_sync.synchronize(:SH) do + @active_jobs.include?(job) + end + end + + # job�ν�λ + def terminate_job(command) + @jobs_sync.synchronize(:EX) do + @active_jobs.delete command + ProcessController.inactivate(self) + if @active_jobs.empty? + start_job + end + end + end + + # job�ζ�����λ + def kill_job(sig, command) + @jobs_sync.synchronize(:SH) do + if @waiting_jobs.delete command + ProcessController.inactivate(self) + return + elsif @active_jobs.include?(command) + begin + r = command.kill sig + ProcessController.inactivate(self) + rescue + print "Shell: Warn: $!\n" if @shell.verbose? + return nil + end + @active_jobs.delete command + r + end + end + end + + # ���٤Ƥ�job�μ¹Խ�λ�Ԥ� + def wait_all_jobs_execution + @job_monitor.synchronize do + begin + while !jobs.empty? + @job_condition.wait(@job_monitor) + end + ensure + redo unless jobs.empty? + end + end + end + + # ��ñ��fork + def sfork(command, &block) + pipe_me_in, pipe_peer_out = IO.pipe + pipe_peer_in, pipe_me_out = IO.pipe + Thread.critical = true + + STDOUT.flush + ProcessController.each_active_object do |pc| + for jobs in pc.active_jobs + jobs.flush + end + end + + pid = fork { + Thread.critical = true + + Thread.list.each do |th| + th.kill unless [Thread.main, Thread.current].include?(th) + end + + STDIN.reopen(pipe_peer_in) + STDOUT.reopen(pipe_peer_out) + + ObjectSpace.each_object(IO) do |io| + if ![STDIN, STDOUT, STDERR].include?(io) + io.close unless io.closed? + end + end + yield + } + + pipe_peer_in.close + pipe_peer_out.close + command.notify "job(%name:##{pid}) start", @shell.debug? + Thread.critical = false + + th = Thread.start { + Thread.critical = true + begin + _pid = nil + command.notify("job(%id) start to waiting finish.", @shell.debug?) + Thread.critical = false + _pid = Process.waitpid(pid, nil) + rescue Errno::ECHILD + command.notify "warn: job(%id) was done already waitipd." + _pid = true + ensure + # �ץ�������λ���˥��ޥ�ɼ¹Ԥ������ޤ��Ԥ����뤿��. + if _pid + else + command.notify("notice: Process finishing...", + "wait for Job[%id] to finish.", + "You can use Shell#transact or Shell#check_point for more safe execution.") + redo + end + Thread.exclusive do + terminate_job(command) + @job_condition.signal + command.notify "job(%id) finish.", @shell.debug? + end + end + } + return pid, pipe_me_in, pipe_me_out + end + end +end diff --git a/lib/shell/system-command.rb b/lib/shell/system-command.rb new file mode 100644 index 0000000000..c22b9ac0a4 --- /dev/null +++ b/lib/shell/system-command.rb @@ -0,0 +1,168 @@ +# +# shell/system-command.rb - +# $Release Version: 0.6.0 $ +# $Revision$ +# $Date$ +# by Keiju ISHITSUKA(Nihon Rational Software Co.,Ltd) +# +# -- +# +# +# + +require "shell/filter" + +class Shell + class SystemCommand < Filter + def initialize(sh, command, *opts) + if t = opts.find{|opt| !opt.kind_of?(String) && opt.type} + Shell.Fail TypeError, t.type, "String" + end + super(sh) + @command = command + @opts = opts + + @input_queue = Queue.new + @pid = nil + + sh.process_controller.add_schedule(self) + end + + attr_reader :command + alias name command + + def wait? + @shell.process_controller.waiting_job?(self) + end + + def active? + @shell.process_controller.active_job?(self) + end + + def input=(inp) + super + if active? + start_export + end + end + + def start + @pid, @pipe_in, @pipe_out = @shell.process_controller.sfork(self) { + Dir.chdir @shell.pwd + exec(@command, *@opts) + } + if @input + start_export + end + start_import + end + + def flush + @pipe_out.flush if @pipe_out and !@pipe_out.closed? + end + + def terminate + begin + @pipe_in.close + rescue IOError + end + begin + @pipe_out.close + rescue IOError + end + end + + def kill(sig) + if @pid + Process.kill(sig, @pid) + end + end + + + def start_import +# Thread.critical = true + notify "Job(%id) start imp-pipe.", @shell.debug? + rs = @shell.record_separator unless rs + _eop = true +# Thread.critical = false + th = Thread.start { + Thread.critical = true + begin + Thread.critical = false + while l = @pipe_in.gets + @input_queue.push l + end + _eop = false + rescue Errno::EPIPE + _eop = false + ensure + if _eop + notify("warn: Process finishing...", + "wait for Job[%id] to finish pipe importing.", + "You can use Shell#transact or Shell#check_point for more safe execution.") +# Tracer.on + Thread.current.run + redo + end + Thread.exclusive do + notify "job(%id}) close imp-pipe.", @shell.debug? + @input_queue.push :EOF + @pipe_in.close + end + end + } + end + + def start_export + notify "job(%id) start exp-pipe.", @shell.debug? + _eop = true + th = Thread.start{ + Thread.critical = true + begin + Thread.critical = false + @input.each{|l| @pipe_out.print l} + _eop = false + rescue Errno::EPIPE + _eop = false + ensure + if _eop + notify("shell: warn: Process finishing...", + "wait for Job(%id) to finish pipe exporting.", + "You can use Shell#transact or Shell#check_point for more safe execution.") +# Tracer.on + redo + end + Thread.exclusive do + notify "job(%id) close exp-pipe.", @shell.debug? + @pipe_out.close + end + end + } + end + + alias super_each each + def each(rs = nil) + while (l = @input_queue.pop) != :EOF + yield l + end + end + + # ex) + # if you wish to output: + # "shell: job(#{@command}:#{@pid}) close pipe-out." + # then + # mes: "job(%id) close pipe-out." + # yorn: Boolean(@shell.debug? or @shell.verbose?) + def notify(*opts, &block) + Thread.exclusive do + @shell.notify(*opts) {|mes| + yield mes if iterator? + + mes.gsub!("%id", "#{@command}:##{@pid}") + mes.gsub!("%name", "#{@command}") + mes.gsub!("%pid", "#{@pid}") + } + end + end + end +end diff --git a/lib/shell/version.rb b/lib/shell/version.rb new file mode 100644 index 0000000000..6694c804d8 --- /dev/null +++ b/lib/shell/version.rb @@ -0,0 +1,16 @@ +# +# version.rb - shell version definition file +# $Release Version: 0.6.0$ +# $Revision$ +# $Date$ +# by Keiju ISHITSUKA(Nihon Rational Software Co.,Ltd) +# +# -- +# +# +# + +class Shell + @RELEASE_VERSION = "0.6.0" + @LAST_UPDATE_DATE = "01/03/19" +end |