2 # shell/command-controller.rb -
3 # $Release Version: 0.7 $
5 # by Keiju ISHITSUKA(keiju@ruby-lang.org)
16 require "shell/filter"
17 require "shell/system-command"
18 require "shell/builtin-command"
21 class CommandProcessor
25 # initialize of Shell and related classes.
27 m = [:initialize, :expand_path]
28 if Object.methods.first.kind_of?(String)
29 NoDelegateMethods = m.collect{|m| m.id2name}
36 install_builtin_commands
38 # define CommandProccessor#methods to Shell#methods and Filter#methods
39 for m in CommandProcessor.instance_methods(false) - NoDelegateMethods
40 add_delegate_command_to_shell(m)
43 def self.method_added(id)
44 add_delegate_command_to_shell(id)
53 load File.expand_path("~/.rb_shell") if ENV.key?("HOME")
54 rescue LoadError, Errno::ENOENT
56 print "load error: #{rc}\n"
57 print $!.class, ": ", $!, "\n"
58 for err in $@[0, $@.size - 2]
70 # CommandProcessor#expand_path(path)
73 # returns the absolute path for <path>
76 @shell.expand_path(path)
80 # File related commands
88 # CommandProcessor#foreach(path, rs)
90 # rs: String - record separator
93 # File#foreach (when path is file)
94 # Dir#foreach (when path is directory)
95 # path is relative to pwd
97 def foreach(path = nil, *rs)
98 path = "." unless path
99 path = expand_path(path)
101 if File.directory?(path)
102 Dir.foreach(path){|fn| yield fn}
104 IO.foreach(path, *rs){|l| yield l}
109 # CommandProcessor#open(path, mode)
112 # return: File or Dir
114 # File#open (when path is file)
115 # Dir#open (when path is directory)
116 # mode has an effect only when path is a file
118 def open(path, mode = nil, perm = 0666, &b)
119 path = expand_path(path)
120 if File.directory?(path)
124 f = File.open(path, mode, perm)
125 File.chmod(perm & ~@shell.umask, path)
131 f = File.open(path, mode, perm, &b)
138 # CommandProcessor#unlink(path)
140 # Dir#unlink (when path is directory)
141 # File#unlink (when path is file)
146 path = expand_path(path)
147 if File.directory?(path)
156 # CommandProcessor#test(command, file1, file2)
157 # CommandProcessor#[command, file1, file2]
158 # command: char or String or Symbol
160 # file2: String(optional)
163 # test() (when command is char or length 1 string or symbol)
164 # FileTest.command (others)
169 # sh[:exists?, "foo"]
170 # sh["exists?", "foo"]
172 alias top_level_test test
173 def test(command, file1, file2=nil)
174 file1 = expand_path(file1)
175 file2 = expand_path(file2) if file2
176 command = command.id2name if command.kind_of?(Symbol)
181 top_level_test(command, file1, file2)
183 top_level_test(command, file1)
188 top_level_test(command, file1, file2)
190 top_level_test(command, file1)
194 FileTest.send(command, file1, file2)
196 FileTest.send(command, file1)
204 # Dir related methods
211 # CommandProcessor#mkdir(*path)
213 # same as Dir.mkdir()
217 notify("mkdir #{path.join(' ')}")
220 if path.last.kind_of?(Integer)
230 File.chmod(d, 0666 & ~@shell.umask) if @shell.umask
236 # CommandProcessor#rmdir(*path)
238 # same as Dir.rmdir()
242 notify("rmdir #{path.join(' ')}")
245 Dir.rmdir(expand_path(dir))
251 # CommandProcessor#system(command, *opts)
254 # return: SystemCommand
255 # Same as system() function
257 # print sh.system("ls", "-l")
258 # sh.system("ls", "-l") | sh.head > STDOUT
260 def system(command, *opts)
262 if command =~ /\*|\?|\{|\}|\[|\]|<|>|\(|\)|~|&|\||\\|\$|;|'|`|"|\n/
263 return SystemCommand.new(@shell, find_system_command("sh"), "-c", command)
265 command, *opts = command.split(/\s+/)
268 SystemCommand.new(@shell, find_system_command(command), *opts)
272 # ProcessCommand#rehash
273 # clear command hash table.
276 @system_commands = {}
280 # ProcessCommand#transact
283 @shell.process_controller.wait_all_jobs_execution
285 alias finish_all_jobs check_point
289 @shell.instance_eval(&block)
298 def out(dev = STDOUT, &block)
299 dev.print transact(&block)
303 Echo.new(@shell, *strings)
307 Cat.new(@shell, *filenames)
310 # def sort(*filenames)
311 # Sort.new(self, *filenames)
315 Glob.new(@shell, pattern)
318 def append(to, filter)
321 AppendFile.new(@shell, to, filter)
323 AppendIO.new(@shell, to, filter)
325 Shell.Fail Error::CantApplyMethod, "append", to.class
330 Tee.new(@shell, file)
334 Concat.new(@shell, *jobs)
338 def notify(*opts, &block)
339 Shell.notify(*opts) {|mes|
340 yield mes if iterator?
342 mes.gsub!("%pwd", "#{@cwd}")
343 mes.gsub!("%cwd", "#{@cwd}")
350 def find_system_command(command)
351 return command if /^\// =~ command
352 case path = @system_commands[command]
357 Shell.Fail Error::CommandNotFound, command
360 Shell.Fail Error::CommandNotFound, command
363 for p in @shell.system_path
364 path = join(p, command)
365 if FileTest.exist?(path)
366 @system_commands[command] = path
370 @system_commands[command] = false
371 Shell.Fail Error::CommandNotFound, command
375 # CommandProcessor.def_system_command(command, path)
378 # define 'command()' method as method.
380 def self.def_system_command(command, path = command)
382 eval((d = %Q[def #{command}(*opts)
383 SystemCommand.new(@shell, '#{path}', *opts)
384 end]), nil, __FILE__, __LINE__ - 1)
386 Shell.notify "warn: Can't define #{command} path: #{path}."
388 Shell.notify "Define #{command} path: #{path}.", Shell.debug?
389 Shell.notify("Definition of #{command}: ", d,
390 Shell.debug.kind_of?(Integer) && Shell.debug > 1)
393 def self.undef_system_command(command)
394 command = command.id2name if command.kind_of?(Symbol)
395 remove_method(command)
396 Shell.module_eval{remove_method(command)}
397 Filter.module_eval{remove_method(command)}
401 # define command alias
403 # def_alias_command("ls_c", "ls", "-C", "-F")
404 # def_alias_command("ls_c", "ls"){|*opts| ["-C", "-F", *opts]}
410 def self.alias_command(ali, command, *opts, &block)
411 ali = ali.id2name if ali.kind_of?(Symbol)
412 command = command.id2name if command.kind_of?(Symbol)
415 @alias_map[ali.intern] = proc
417 eval((d = %Q[def #{ali}(*opts)
418 @shell.__send__(:#{command},
419 *(CommandProcessor.alias_map[:#{ali}].call *opts))
420 end]), nil, __FILE__, __LINE__ - 1)
423 args = opts.collect{|opt| '"' + opt + '"'}.join(",")
424 eval((d = %Q[def #{ali}(*opts)
425 @shell.__send__(:#{command}, #{args}, *opts)
426 end]), nil, __FILE__, __LINE__ - 1)
429 Shell.notify "warn: Can't alias #{ali} command: #{command}."
430 Shell.notify("Definition of #{ali}: ", d)
433 Shell.notify "Define #{ali} command: #{command}.", Shell.debug?
434 Shell.notify("Definition of #{ali}: ", d,
435 Shell.debug.kind_of?(Integer) && Shell.debug > 1)
439 def self.unalias_command(ali)
440 ali = ali.id2name if ali.kind_of?(Symbol)
441 @alias_map.delete ali.intern
442 undef_system_command(ali)
446 # CommandProcessor.def_builtin_commands(delegation_class, command_specs)
447 # delegation_class: Class or Module
448 # command_specs: [[command_name, [argument,...]],...]
449 # command_name: String
451 # FILENAME?? -> expand_path(filename??)
452 # *FILENAME?? -> filename??.collect{|f|expand_path(f)}.join(", ")
453 # define command_name(argument,...) as
454 # delegation_class.command_name(argument,...)
456 def self.def_builtin_commands(delegation_class, command_specs)
457 for meth, args in command_specs
458 arg_str = args.collect{|arg| arg.downcase}.join(", ")
459 call_arg_str = args.collect{
462 when /^(FILENAME.*)$/
463 format("expand_path(%s)", $1.downcase)
464 when /^(\*FILENAME.*)$/
465 # \*FILENAME* -> filenames.collect{|fn| expand_path(fn)}.join(", ")
466 $1.downcase + '.collect{|fn| expand_path(fn)}'
471 d = %Q[def #{meth}(#{arg_str})
472 #{delegation_class}.#{meth}(#{call_arg_str})
474 Shell.notify "Define #{meth}(#{arg_str})", Shell.debug?
475 Shell.notify("Definition of #{meth}: ", d,
476 Shell.debug.kind_of?(Integer) && Shell.debug > 1)
482 # CommandProcessor.install_system_commands(pre)
483 # pre: String - command name prefix
484 # defines every command which belongs in default_system_path via
485 # CommandProcessor.command(). It doesn't define already defined
486 # methods twice. By default, "pre_" is prefixes to each method
487 # name. Characters that may not be used in a method name are
488 # all converted to '_'. Definition errors are just ignored.
490 def self.install_system_commands(pre = "sys_")
492 for m in Shell.methods
493 defined_meth[m] = true
496 for path in Shell.default_system_path
497 next unless sh.directory? path
501 if !defined_meth[pre + cn] && sh.file?(cn) && sh.executable?(cn)
502 command = (pre + cn).gsub(/\W/, "_").sub(/^([0-9])/, '_\1')
504 def_system_command(command, sh.expand_path(cn))
506 Shell.notify "warn: Can't define #{command} path: #{cn}"
508 defined_meth[command] = command
514 #----------------------------------------------------------------------
516 # class initializing methods -
518 #----------------------------------------------------------------------
519 def self.add_delegate_command_to_shell(id)
520 id = id.intern if id.kind_of?(String)
522 if Shell.method_defined?(id)
523 Shell.notify "warn: override definnition of Shell##{name}."
524 Shell.notify "warn: alias Shell##{name} to Shell##{name}_org.\n"
525 Shell.module_eval "alias #{name}_org #{name}"
527 Shell.notify "method added: Shell##{name}.", Shell.debug?
528 Shell.module_eval(%Q[def #{name}(*args, &block)
530 @command_processor.__send__(:#{name}, *args, &block)
532 $@.delete_if{|s| /:in `__getobj__'$/ =~ s} #`
533 $@.delete_if{|s| /^\\(eval\\):/ =~ s}
536 end], __FILE__, __LINE__)
538 if Shell::Filter.method_defined?(id)
539 Shell.notify "warn: override definnition of Shell::Filter##{name}."
540 Shell.notify "warn: alias Shell##{name} to Shell::Filter##{name}_org."
541 Filter.module_eval "alias #{name}_org #{name}"
543 Shell.notify "method added: Shell::Filter##{name}.", Shell.debug?
544 Filter.module_eval(%Q[def #{name}(*args, &block)
546 self | @shell.__send__(:#{name}, *args, &block)
548 $@.delete_if{|s| /:in `__getobj__'$/ =~ s} #`
549 $@.delete_if{|s| /^\\(eval\\):/ =~ s}
552 end], __FILE__, __LINE__)
556 # define default builtin commands
558 def self.install_builtin_commands
559 # method related File.
560 # (exclude open/foreach/unlink)
561 normal_delegation_file_methods = [
562 ["atime", ["FILENAME"]],
563 ["basename", ["fn", "*opts"]],
564 ["chmod", ["mode", "*FILENAMES"]],
565 ["chown", ["owner", "group", "*FILENAME"]],
566 ["ctime", ["FILENAMES"]],
567 ["delete", ["*FILENAMES"]],
568 ["dirname", ["FILENAME"]],
569 ["ftype", ["FILENAME"]],
570 ["join", ["*items"]],
571 ["link", ["FILENAME_O", "FILENAME_N"]],
572 ["lstat", ["FILENAME"]],
573 ["mtime", ["FILENAME"]],
574 ["readlink", ["FILENAME"]],
575 ["rename", ["FILENAME_FROM", "FILENAME_TO"]],
576 # ["size", ["FILENAME"]],
577 ["split", ["pathname"]],
578 ["stat", ["FILENAME"]],
579 ["symlink", ["FILENAME_O", "FILENAME_N"]],
580 ["truncate", ["FILENAME", "length"]],
581 ["utime", ["atime", "mtime", "*FILENAMES"]]]
583 def_builtin_commands(File, normal_delegation_file_methods)
584 alias_method :rm, :delete
586 # method related FileTest
587 def_builtin_commands(FileTest,
588 FileTest.singleton_methods(false).collect{|m| [m, ["FILENAME"]]})