diff options
author | matz <matz@b2dd03c8-39d4-4d8f-98ff-823fe69b080e> | 1998-05-25 09:42:47 +0000 |
---|---|---|
committer | matz <matz@b2dd03c8-39d4-4d8f-98ff-823fe69b080e> | 1998-05-25 09:42:47 +0000 |
commit | 8b1e7e34403977058d412931e31577df719fd2e4 (patch) | |
tree | 7743656749f91b601f3d025ea357e8a8043424d8 /lib/shell.rb | |
parent | 45c61f40b23b14e97428206e1b813eb813526e6f (diff) |
*** empty log message ***
git-svn-id: svn+ssh://ci.ruby-lang.org/ruby/branches/v1_1r@224 b2dd03c8-39d4-4d8f-98ff-823fe69b080e
Diffstat (limited to 'lib/shell.rb')
-rw-r--r-- | lib/shell.rb | 666 |
1 files changed, 666 insertions, 0 deletions
diff --git a/lib/shell.rb b/lib/shell.rb new file mode 100644 index 0000000000..a0ade9ab46 --- /dev/null +++ b/lib/shell.rb @@ -0,0 +1,666 @@ +# +# shell.rb - +# $Release Version: 0.1 $ +# $Revision: 1.1 $ +# $Date: 1998/03/29 17:10:09 $ +# by Keiju ISHITSUKA(Nippon Rational Inc.) +# +# -- +# +# +# + +require "e2mmap" +require "ftools" + +class Shell + @RCS_ID='-$Id: shell.rb,v 1.1 1998/03/29 17:10:09 keiju Exp $-' + + module Error + extend Exception2MessageMapper + 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 + include Error + + class << Shell + attr :cascade, TRUE + attr :debug, TRUE + attr :verbose, TRUE + + alias cascade? cascade + alias debug? debug + alias verbose? verbose + end + + def Shell.cd(path) + sh = new + sh.cd path + sh + end + + def Shell.default_system_path + if @default_system_path + @default_system_path + else + ENV["PATH"].split(":") + end + end + + def Shell.default_system_path=(path) + @default_system_path = path + end + + def Shell.default_record_separator + if @default_record_separator + @default_record_separator + else + $/ + end + end + + def Shell.default_record_separator=(rs) + @default_record_separator = rs + end + + @cascade = TRUE + @debug = FALSE + @verbose = TRUE + + def initialize + @cwd = Dir.pwd + @dir_stack = [] + @umask = nil + + @system_commands = {} + + @system_path = Shell.default_system_path + @record_separator = Shell.default_record_separator + + @verbose = Shell.verbose + end + + attr :system_path + + def system_path=(path) + @system_path = path + @system_commands = {} + end + + def rehash + @system_commands = {} + end + + attr :record_separator, TRUE + + attr :umask, TRUE + attr :verbose, TRUE + + alias verbose? verbose + + def expand_path(path) + if /^\// =~ path + File.expand_path(path) + else + File.expand_path(File.join(@cwd, path)) + end + end + + def effect_umask + if @umask + Thread.critical = TRUE + save = File.umask + begin + yield + ensure + File.umask save + Thread.critical = FALSE + end + else + yield + end + end + + # Dir関連メソッド + def [](pattern) + Thread.critical=TRUE + back = Dir.pwd + begin + Dir.chdir @cwd + Dir[pattern] + ensure + Dir.chdir back + Thread.critical = FALSE + end + end + alias glob [] + + def chdir(path = nil) + path = "~" unless path + @cwd = expand_path(path) + @system_commands.clear + end + alias cd chdir + + def pushdir(path = nil) + if iterator? + pushdir(path) + begin + yield + ensure + popdir + end + elsif path + @dir_stack.push @cwd + @cwd = expand_path(path) + else + if pop = @dir_stack.pop + @dir_stack.push @cwd + chdir pop + else + Shell.fail DirStackEmpty + end + end + end + alias pushd pushdir + + def popdir + if pop = @dir_stack.pop + @cwd = pop + else + Shell.fail DirStackEmpty + end + end + alias popd popdir + + attr :cwd + alias dir cwd + alias getwd cwd + alias pwd cwd + + 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 + + def mkdir(path) + Dir.mkdir(expand_path(path)) + end + + # + # 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 + + def rmdir(path) + Dir.rmdir(expand_path(path)) + end + + def unlink(path) + path = expand_path(path) + if File.directory?(path) + Dir.unlink(path) + else + IO.unlink(path) + end + end + + # + # コマンド拡張 + # command_specs = [[command_name, [arguments,...]]] + # FILENAME* -> expand_path(filename*) + # \*FILENAME* -> filename*.collect{|fn| expand_path(fn)}.join(", ") + # + def Shell.def_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 + ] + if debug? + print d + elsif verbose? + print "Define #{meth}(#{arg_str})\n" + end + eval d + end + end + + # + # File関連メソッド + # open/foreach/unlinkは別定義 + # + normal_delegation_file_methods = [ + ["atime", ["FILENAME"]], + ["basename", ["fn", "*opts"]], + ["chmod", ["mode", "*FILENAMES"]], + ["chown", ["owner", "group", "FILENAME"]], + ["ctime", ["group", "*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_commands(File, + normal_delegation_file_methods) + alias rm delete + + # FileTest関連メソッド + def_commands(FileTest, + FileTest.singleton_methods.collect{|m| [m, ["FILENAME"]]}) + + # 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_commands(File, + normal_delegation_ftools_methods) + alias cmp compare + alias mv move + alias cp copy + alias rm_f safe_unlink + alias mkpath makedirs + + # testコマンド + alias top_level_test test + def test(command, file1, file2 = nil) + if file2 + top_level_test command, expand_path(file1), expand_path(file2) + else + top_level_test command, expand_path(file1) + end + end + + # shell拡張 + def echo(*strings) + Echo.new(self, *strings) + end + + def cat(*filenames) + Cat.new(self, *filenames) + end + + def tee(file) + Tee.new(self, file) + end + +# def sort(*filenames) +# Sort.new(self, *filenames) +# end + + def system(command, *opts) + System.new(self, find_system_command(command), *opts) + end + + # + # コマンドを検索する. もし存在しなけば例外を返す. + # + def find_system_command(command) + return command if /^\// =~ command + case path = @system_commands[command] + when String + if sh.exists?(path) + return path + else + Shell.fail CommandNotFound, command + end + when FALSE + Shell.fail CommandNotFound, command + end + + for p in @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 + + # + # コマンドを特異メソッドとして定義する. + # 定義できない時は例外が発生する. + # + def def_system_command(command, path = command) + d = " + def self.#{command}(*opts) + System.new(self, find_system_command('#{path}'), *opts) + end + " + begin + eval d + rescue + print "Can't define self.#{command} path: #{path}\n" if debug? or verbose? + Shell.fail CanNotDefine, comamnd, path + end + if debug? + print d + elsif verbose? + print "Define self.#{command} path: #{path}\n" + end + end + + # + # コマンドをShellのメソッドとして定義する. + # 定義できない時は例外が発生する. + # + def Shell.def_system_command(command, path = command) + d = " + def #{command}(*opts) + System.new(self, '#{path}', *opts) + end + " + begin + eval d + rescue + print "Can't define #{command} path: #{path}\n" if debug? or verbose? + Shell.fail CanNotDefine, comamnd, path + end + if debug? + print d + elsif verbose? + print "Define #{command} path: #{path}\n" + end + end + + # + # default_path上にのるコマンドを定義する. すでに同名のメソッドが存在 + # する時は, 定義を行なわない. + # デフォルトでは, 全てのメソッドには接頭子"sys_"をつける. + # メソッド名として許されないキャラクタ(英数時以外とメソッド名の + # 先頭が数値になる場合)は, 強制的に``_''に変換する. + # 定義エラーは無視する. + # + def Shell.install_system_command(pre = "sys_") + defined_meth = {} + for m in Shell.methods + defined_meth[m] = TRUE + end + sh = Shell.new + for path in Shell.default_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 + printf("Warning: Can't define %s path: %s\n", + comamnd, + cn) unless debug? or verbose? + end + defined_meth[command] = command + end + end + end + end + + # + # Filterクラス + # 必要なメソッド: + # each() + class Filter + include Enumerable + include Error + + def initialize(sh) + @shell = sh + end + + 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 + @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) + case to + when String + dst = @shell.open(to, "a") + 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 | (filter) + filter.input = self + filter + end + + def method_missing(method, *args) + if Shell.cascade? and @shell.respond_to?(method) + self | @shell.send(method, *args) + else + super + end + end + + def to_a + ary = [] + each(){|l| ary.push l} + ary + end + + def to_s + str = "" + each(){|l| str.concat l} + str + end + end + + class Echo < Filter + 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 < Filter + 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 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 Tee < Filter + 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 System < Filter + def initialize(sh, command, *opts) + require "socket" + + super(sh) +# @sock_me, @sock_peer = Socket.pair(Socket::AF_UNIX, Socket::SOCK_STREAM, 0) + @pipe_me_in, @pipe_peer_out = pipe + @pipe_peer_in, @pipe_me_out = pipe + begin + pid = fork { +# @sock_me.close + @pipe_me_in.close + @pipe_me_out.close +# STDIN.reopen(@sock_peer) +# STDOUT.reopen(@sock_peer) + STDIN.reopen(@pipe_peer_in) + STDOUT.reopen(@pipe_peer_out) + fork { + exec(command + " " + opts.join(" ")) + } + exit + } +# print pid; $stdout.flush + ensure +# sock_peer.close + @pipe_peer_in.close + @pipe_peer_out.close + begin + Process.waitpid(pid, nil) + rescue Errno::ECHILD + end + end + end + + def each(rs = nil) + rs = @shell.record_separator unless rs + begin + th_o = Thread.start{ + super{|l| @pipe_me_out.print l} +# @sock_me.shutdown(0) + @pipe_me_out.close + } + begin + @pipe_me_in.each(rs) do + |l| +# print l + yield l + end + ensure + th_o.exit + end + ensure +# @sock_peer.close unless @sock_peer.closed? +# @sock_me.close + @pipe_me_in.close + end + end + end +end |