summaryrefslogtreecommitdiff
path: root/lib
diff options
context:
space:
mode:
authorknu <knu@b2dd03c8-39d4-4d8f-98ff-823fe69b080e>2001-05-17 10:02:47 +0000
committerknu <knu@b2dd03c8-39d4-4d8f-98ff-823fe69b080e>2001-05-17 10:02:47 +0000
commitd9350f56002fde6b5640b93ae7b8885cb7811d1a (patch)
tree911c962685a2c4caf03fc058a9a0afd3a3a0bdeb /lib
parentc2fa753f7db4ea07c50ec8a182f78b1a263e0709 (diff)
Initial revision
git-svn-id: svn+ssh://ci.ruby-lang.org/ruby/trunk@1418 b2dd03c8-39d4-4d8f-98ff-823fe69b080e
Diffstat (limited to 'lib')
-rw-r--r--lib/shell/builtin-command.rb154
-rw-r--r--lib/shell/command-processor.rb591
-rw-r--r--lib/shell/error.rb26
-rw-r--r--lib/shell/filter.rb111
-rw-r--r--lib/shell/process-controller.rb258
-rw-r--r--lib/shell/system-command.rb168
-rw-r--r--lib/shell/version.rb16
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, $@.size - 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
+ $@.delete_if{|s| /:in `__getobj__'$/ =~ s} #`
+ $@.delete_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
+ $@.delete_if{|s| /:in `__getobj__'$/ =~ s} #`
+ $@.delete_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