summaryrefslogtreecommitdiff
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
parentc2fa753f7db4ea07c50ec8a182f78b1a263e0709 (diff)
Initial revision
git-svn-id: svn+ssh://ci.ruby-lang.org/ruby/trunk@1418 b2dd03c8-39d4-4d8f-98ff-823fe69b080e
-rw-r--r--doc/shell.rd283
-rw-r--r--doc/shell.rd.jp283
-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
9 files changed, 1890 insertions, 0 deletions
diff --git a/doc/shell.rd b/doc/shell.rd
new file mode 100644
index 0000000000..437f035dec
--- /dev/null
+++ b/doc/shell.rd
@@ -0,0 +1,283 @@
+shell.rbユーザガイド
+ $Release Version: 0.6.0 $
+ $Revision$
+ $Date$
+ by Keiju ISHITSUKA(keiju@ishitsuka.com)
+
+* What's shell.rb?
+
+It realizes a wish to do execution of command and filtering like
+sh/csh. However, Control statement which include sh/csh just uses
+facility of ruby.
+
+* Main classes
+** Shell
+
+All shell objects have a each unique current directory. Any shell object
+execute a command on relative path from current directory.
+
++ Shell#cwd/dir/getwd/pwd current directory
++ Shell#system_path command path
++ Shell#umask umask
+
+** Filter
+
+Any result of command exection is a Filter. Filter include Enumerable,
+therefore a Filter object can use all Enumerable facility.
+
+* Main methods
+** Command definition
+
+For executing a command on OS, you need to define it as a Shell
+method.
+
+notice) Also, there are a Shell#system alternatively to execute the
+command even if it is not defined.
+
++ Shell.def_system_command(command, path = command)
+Register command as a Shell method
+
+++ Shell.def_system_command "ls"
+ define ls
+++ Shell.def_system_command "sys_sort", "sort"
+ define sys_sort as sort
+
++ Shell.install_system_commands(pre = "sys_")
+
+Define all command of default_system_path. Default action prefix
+"sys_" to the method name.
+
+** 生成
+
++ Shell.new
+Shell creates a Shell object of which current directory is the process
+current directory.
+
++ Shell.cd(path)
+Shell creates a Shell object of which current directory is <path>.
+
+** Process management
+
++ jobs
+The shell returns jobs list of scheduling.
+
++ kill sig, job
+The shell kill <job>.
+
+** Current directory operation
+
++ Shell#cd(path, &block)/chdir
+The current directory of the shell change to <path>. If it is called
+with an block, it changes current directory to the <path> while its
+block executes.
+
++ Shell#pushd(path = nil, &block)/pushdir
+
+The shell push current directory to directory stack. it changes
+current directory to <path>. If the path is omitted, it exchange its
+current directory and the top of its directory stack. If it is called
+with an block, it do `pushd' the <path> while its block executes.
+
++ Shell#popd/popdir
+The shell pop a directory from directory stack, and its directory is
+changed to current directory.
+
+** ファイル/ディレクトリ操作
+
++ Shell#foreach(path = nil, &block)
+Same as:
+ File#foreach (when path is a file)
+ Dir#foreach (when path is a directory)
+
++ Shell#open(path, mode)
+Same as:
+ File#open(when path is a file)
+ Dir#open(when path is a directory)
+
++ Shell#unlink(path)
+Same as:
+ Dir#open(when path is a file)
+ Dir#unlink(when path is a directory)
+
++ Shell#test(command, file1, file2)/Shell#[command, file1, file]
+Same as file testing function test().
+ex)
+ sh[?e, "foo"]
+ sh[:e, "foo"]
+ sh["e", "foo"]
+ sh[:exists?, "foo"]
+ sh["exists?", "foo"]
+
++ Shell#mkdir(*path)
+Same as Dir.mkdir(its parameters is one or more)
+
++ Shell#rmdir(*path)
+Same as Dir.rmdir(its parameters is one or more)
+
+** Command execution
++ System#system(command, *opts)
+The shell execure <command>.
+ex)
+ print sh.system("ls", "-l")
+ sh.system("ls", "-l") | sh.head > STDOUT
+
++ System#rehash
+The shell do rehash.
+
++ Shell#transact &block
+The shell execute block as self.
+ex)
+ sh.transact{system("ls", "-l") | head > STDOUT}
+
++ Shell#out(dev = STDOUT, &block)
+The shell do transact, and its result output to dev.
+
+** Internal Command
++ Shell#echo(*strings)
++ Shell#cat(*files)
++ Shell#glob(patten)
++ Shell#tee(file)
+
+When these are executed, they return a filter object, which is a
+result of their execution.
+
++ Filter#each &block
+The shell iterate with each line of it.
+
++ Filter#<(src)
+The shell inputs from src. If src is a string, it inputs from a file
+of which name is the string. If src is a IO, it inputs its IO.
+
++ Filter#>(to)
+The shell outputs to <to>. If <to> is a string, it outputs to a file
+of which name is the string. If <to>c is a IO, it outoputs to its IO.
+
++ Filter#>>(to)
+The shell appends to <to>. If <to> is a string, it is append to a file
+of which name is the string. If <to>c is a IO, it is append to its IO.
+
++ Filter#|(filter)
+pipe combination
+
++ Filter#+(filter)
+filter1 + filter2 output filter1, and next output filter2.
+
++ Filter#to_a
++ Filter#to_s
+
+** Built-in command
+
++ Shell#atime(file)
++ Shell#basename(file, *opt)
++ Shell#chmod(mode, *files)
++ Shell#chown(owner, group, *file)
++ Shell#ctime(file)
++ Shell#delete(*file)
++ Shell#dirname(file)
++ Shell#ftype(file)
++ Shell#join(*file)
++ Shell#link(file_from, file_to)
++ Shell#lstat(file)
++ Shell#mtime(file)
++ Shell#readlink(file)
++ Shell#rename(file_from, file_to)
++ Shell#split(file)
++ Shell#stat(file)
++ Shell#symlink(file_from, file_to)
++ Shell#truncate(file, length)
++ Shell#utime(atime, mtime, *file)
+
+These have a same function as a class method which is in File with same name.
+
++ Shell#blockdev?(file)
++ Shell#chardev?(file)
++ Shell#directory?(file)
++ Shell#executable?(file)
++ Shell#executable_real?(file)
++ Shell#exist?(file)/Shell#exists?(file)
++ Shell#file?(file)
++ Shell#grpowned?(file)
++ Shell#owned?(file)
++ Shell#pipe?(file)
++ Shell#readable?(file)
++ Shell#readable_real?(file)
++ Shell#setgid?(file)
++ Shell#setuid?(file)
++ Shell#size(file)/Shell#size?(file)
++ Shell#socket?(file)
++ Shell#sticky?(file)
++ Shell#symlink?(file)
++ Shell#writable?(file)
++ Shell#writable_real?(file)
++ Shell#zero?(file)
+
+These have a same function as a class method which is in FileTest with
+same name.
+
++ Shell#syscopy(filename_from, filename_to)
++ Shell#copy(filename_from, filename_to)
++ Shell#move(filename_from, filename_to)
++ Shell#compare(filename_from, filename_to)
++ Shell#safe_unlink(*filenames)
++ Shell#makedirs(*filenames)
++ Shell#install(filename_from, filename_to, mode)
+
+These have a same function as a class method which is in FileTools
+with same name.
+
+And also, alias:
+
++ Shell#cmp <- Shell#compare
++ Shell#mv <- Shell#move
++ Shell#cp <- Shell#copy
++ Shell#rm_f <- Shell#safe_unlink
++ Shell#mkpath <- Shell#makedirs
+
+* Samples
+** ex1
+
+ sh = Shell.cd("/tmp")
+ sh.mkdir "shell-test-1" unless sh.exists?("shell-test-1")
+ sh.cd("shell-test-1")
+ for dir in ["dir1", "dir3", "dir5"]
+ if !sh.exists?(dir)
+ sh.mkdir dir
+ sh.cd(dir) do
+ f = sh.open("tmpFile", "w")
+ f.print "TEST\n"
+ f.close
+ end
+ print sh.pwd
+ end
+ end
+
+** ex2
+
+ sh = Shell.cd("/tmp")
+ sh.transact do
+ mkdir "shell-test-1" unless exists?("shell-test-1")
+ cd("shell-test-1")
+ for dir in ["dir1", "dir3", "dir5"]
+ if !exists?(dir)
+ mkdir dir
+ cd(dir) do
+ f = open("tmpFile", "w")
+ f.print "TEST\n"
+ f.close
+ end
+ print pwd
+ end
+ end
+ end
+
+** ex3
+
+ sh.cat("/etc/printcap") | sh.tee("tee1") > "tee2"
+ (sh.cat < "/etc/printcap") | sh.tee("tee11") > "tee12"
+ sh.cat("/etc/printcap") | sh.tee("tee1") >> "tee2"
+ (sh.cat < "/etc/printcap") | sh.tee("tee11") >> "tee12"
+
+** ex5
+
+ print sh.cat("/etc/passwd").head.collect{|l| l =~ /keiju/}
+
diff --git a/doc/shell.rd.jp b/doc/shell.rd.jp
new file mode 100644
index 0000000000..0e8c2ee69b
--- /dev/null
+++ b/doc/shell.rd.jp
@@ -0,0 +1,283 @@
+shell.rbユーザガイド
+ $Release Version: 0.6.0 $
+ $Revision$
+ $Date$
+ by Keiju ISHITSUKA(keiju@ishitsuka.com)
+
+ruby上でshellっぽいコマンドを使えるようにする.
+
+* 目的
+
+sh/cshのようにコマンドの実行及びフィルタリングを気軽に行いたい. ただし,
+sh/cshには制御文があるがそれはrubyの機能をそのまま用いる.
+
+* 主なクラス一覧
+** Shell
+
+Shellオブジェクトはカレントディレクトリを持ち, コマンド実行はそこからの
+相対パスになります.
+
++ Shell#cwd/dir/getwd/pwd カレントディレクトリ
++ Shell#system_path コマンドのパス
++ Shell#umask umask
+
+** Filter
+コマンドの実行結果はFilterとしてかえります. Enumerableをincludeしていま
+す.
+
+* 主なメソッド一覧
+** コマンド定義
+
+OS上のコマンドを実行するにはまず, Shellのメソッドとして定義します.
+注) コマンドを定義しなくともすむShell#systemコマンドもあります.
+
++ Shell.def_system_command(command, path = command)
+Shellのメソッドとしてcommandを登録します.
+
+++ Shell.def_system_command "ls"
+ ls を定義
+++ Shell.def_system_command "sys_sort", "sort"
+ sortコマンドをsys_sortとして定義
+
++ Shell.undef_system_command(command)
+commandを削除します.
+
++ Shell.alias_command(ali, command, *opts) {...}
+commandのaliasをします.
+例)
+ Shell.alias_command "lsC", "ls", "-CBF", "--show-control-chars"
+ Shell.alias_command("lsC", "ls"){|*opts| ["-CBF", "--show-control-chars", *opts]}
+
++ Shell.unalias_command(ali)
+commandのaliasを削除します.
+
++ Shell.install_system_commands(pre = "sys_")
+system_path上にある全ての実行可能ファイルをShellに定義する. メソッド名は
+元のファイル名の頭にpreをつけたものとなる.
+
+** 生成
+
++ Shell.new
+プロセスのカレントディレクトリをカレントディレクトリとするShellオブジェ
+クトを生成します.
+
++ Shell.cd(path)
+pathをカレントディレクトリとするShellオブジェクトを生成します.
+
+** プロセス管理
+
++ jobs
+スケジューリングされているjobの一覧を返す.
+
++ kill sig, job
+jobをkillする
+
+** カレントディレクトリ操作
+
++ Shell#cd(path, &block)/chdir
+カレントディレクトリをpathにする. イテレータとして呼ばれたときには, ブロッ
+ク実行中のみカレントディレクトリを変更する.
+
++ Shell#pushd(path = nil, &block)/pushdir
+
+カレントディレクトリをディレクトリスタックにつみ, カレントディレクトリを
+pathにする. pathが省略されたときには, カレントディレクトリとディレクトリ
+スタックのトップを交換する. イテレータとして呼ばれたときには, ブロック実
+行中のみpushdする.
+
++ Shell#popd/popdir
+ディレクトリスタックからポップし, それをカレントディレクトリにする.
+
+** ファイル/ディレクトリ操作
+
++ Shell#foreach(path = nil, &block)
+pathがファイルなら, File#foreach
+pathがディレクトリなら, Dir#foreach
+
++ Shell#open(path, mode)
+pathがファイルなら, File#open
+pathがディレクトリなら, Dir#open
+
++ Shell#unlink(path)
+pathがファイルなら, File#unlink
+pathがディレクトリなら, Dir#unlink
+
++ Shell#test(command, file1, file2)/Shell#[command, file1, file]
+ファイルテスト関数testと同じ.
+例)
+ sh[?e, "foo"]
+ sh[:e, "foo"]
+ sh["e", "foo"]
+ sh[:exists?, "foo"]
+ sh["exists?", "foo"]
+
++ Shell#mkdir(*path)
+Dir.mkdirと同じ(複数可)
+
++ Shell#rmdir(*path)
+Dir.rmdirと同じ(複数可)
+
+** コマンド実行
++ System#system(command, *opts)
+commandを実行する.
+例)
+ print sh.system("ls", "-l")
+ sh.system("ls", "-l") | sh.head > STDOUT
+
++ System#rehash
+リハッシュする
+
++ Shell#transact &block
+ブロック中ではshellをselfとして実行する.
+例)
+ sh.transact{system("ls", "-l") | head > STDOUT}
+
++ Shell#out(dev = STDOUT, &block)
+transactを呼び出しその結果をdevに出力する.
+
+** 内部コマンド
++ Shell#echo(*strings)
++ Shell#cat(*files)
++ Shell#glob(patten)
++ Shell#tee(file)
+
+これらは実行すると, それらを内容とするFilterオブジェクトを返します.
+
++ Filter#each &block
+フィルタの一行ずつをblockに渡す.
+
++ Filter#<(src)
+srcをフィルタの入力とする. srcが, 文字列ならばファイルを, IOであればそれ
+をそのまま入力とする.
+
++ Filter#>(to)
+srcをフィルタの出力とする. toが, 文字列ならばファイルに, IOであればそれ
+をそのまま出力とする.
+
++ Filter#>>(to)
+srcをフィルタに追加する. toが, 文字列ならばファイルに, IOであればそれを
+そのまま出力とする.
+
++ Filter#|(filter)
+パイプ結合
+
++ Filter#+(filter)
+filter1 + filter2 は filter1の出力の後, filter2の出力を行う.
+
++ Filter#to_a
++ Filter#to_s
+
+** 組込みコマンド
+
++ Shell#atime(file)
++ Shell#basename(file, *opt)
++ Shell#chmod(mode, *files)
++ Shell#chown(owner, group, *file)
++ Shell#ctime(file)
++ Shell#delete(*file)
++ Shell#dirname(file)
++ Shell#ftype(file)
++ Shell#join(*file)
++ Shell#link(file_from, file_to)
++ Shell#lstat(file)
++ Shell#mtime(file)
++ Shell#readlink(file)
++ Shell#rename(file_from, file_to)
++ Shell#split(file)
++ Shell#stat(file)
++ Shell#symlink(file_from, file_to)
++ Shell#truncate(file, length)
++ Shell#utime(atime, mtime, *file)
+
+これらはFileクラスにある同名のクラスメソッドと同じです.
+
++ Shell#blockdev?(file)
++ Shell#chardev?(file)
++ Shell#directory?(file)
++ Shell#executable?(file)
++ Shell#executable_real?(file)
++ Shell#exist?(file)/Shell#exists?(file)
++ Shell#file?(file)
++ Shell#grpowned?(file)
++ Shell#owned?(file)
++ Shell#pipe?(file)
++ Shell#readable?(file)
++ Shell#readable_real?(file)
++ Shell#setgid?(file)
++ Shell#setuid?(file)
++ Shell#size(file)/Shell#size?(file)
++ Shell#socket?(file)
++ Shell#sticky?(file)
++ Shell#symlink?(file)
++ Shell#writable?(file)
++ Shell#writable_real?(file)
++ Shell#zero?(file)
+
+これらはFileTestクラスにある同名のクラスメソッドと同じです.
+
++ Shell#syscopy(filename_from, filename_to)
++ Shell#copy(filename_from, filename_to)
++ Shell#move(filename_from, filename_to)
++ Shell#compare(filename_from, filename_to)
++ Shell#safe_unlink(*filenames)
++ Shell#makedirs(*filenames)
++ Shell#install(filename_from, filename_to, mode)
+
+これらはFileToolsクラスにある同名のクラスメソッドと同じです.
+
+その他, 以下のものがエイリアスされています.
+
++ Shell#cmp <- Shell#compare
++ Shell#mv <- Shell#move
++ Shell#cp <- Shell#copy
++ Shell#rm_f <- Shell#safe_unlink
++ Shell#mkpath <- Shell#makedirs
+
+* サンプル
+** ex1
+
+ sh = Shell.cd("/tmp")
+ sh.mkdir "shell-test-1" unless sh.exists?("shell-test-1")
+ sh.cd("shell-test-1")
+ for dir in ["dir1", "dir3", "dir5"]
+ if !sh.exists?(dir)
+ sh.mkdir dir
+ sh.cd(dir) do
+ f = sh.open("tmpFile", "w")
+ f.print "TEST\n"
+ f.close
+ end
+ print sh.pwd
+ end
+ end
+
+** ex2
+
+ sh = Shell.cd("/tmp")
+ sh.transact do
+ mkdir "shell-test-1" unless exists?("shell-test-1")
+ cd("shell-test-1")
+ for dir in ["dir1", "dir3", "dir5"]
+ if !exists?(dir)
+ mkdir dir
+ cd(dir) do
+ f = open("tmpFile", "w")
+ f.print "TEST\n"
+ f.close
+ end
+ print pwd
+ end
+ end
+ end
+
+** ex3
+
+ sh.cat("/etc/printcap") | sh.tee("tee1") > "tee2"
+ (sh.cat < "/etc/printcap") | sh.tee("tee11") > "tee12"
+ sh.cat("/etc/printcap") | sh.tee("tee1") >> "tee2"
+ (sh.cat < "/etc/printcap") | sh.tee("tee11") >> "tee12"
+
+** ex5
+
+ print sh.cat("/etc/passwd").head.collect{|l| l =~ /keiju/}
+
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