* transcode_data.h (rb_transcoder_stateful_type_t): defined.
[ruby-svn.git] / lib / shell / command-processor.rb
blobb7b58bb618cc00bfaa5478bd09349cd38cfa3b13
2 #   shell/command-controller.rb - 
3 #       $Release Version: 0.7 $
4 #       $Revision$
5 #       by Keiju ISHITSUKA(keiju@ruby-lang.org)
7 # --
9 #   
12 require "e2mmap"
13 require "thread"
15 require "shell/error"
16 require "shell/filter"
17 require "shell/system-command"
18 require "shell/builtin-command"
20 class Shell
21   class CommandProcessor
22 #    include Error
24     #
25     # initialize of Shell and related classes.
26     #
27     m = [:initialize, :expand_path]
28     if Object.methods.first.kind_of?(String)
29       NoDelegateMethods = m.collect{|m| m.id2name}
30     else
31       NoDelegateMethods = m
32     end
34     def self.initialize
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)
41       end
42       
43       def self.method_added(id)
44         add_delegate_command_to_shell(id)
45       end
46     end
48     #
49     # include run file.
50     #
51     def self.run_config
52       begin
53         load File.expand_path("~/.rb_shell") if ENV.key?("HOME")
54       rescue LoadError, Errno::ENOENT
55       rescue
56         print "load error: #{rc}\n"
57         print $!.class, ": ", $!, "\n"
58         for err in $@[0, $@.size - 2]
59           print "\t", err, "\n"
60         end
61       end
62     end
64     def initialize(shell)
65       @shell = shell
66       @system_commands = {}
67     end
69     #
70     # CommandProcessor#expand_path(path)
71     #     path:   String
72     #     return: String
73     #   returns the absolute path for <path>
74     #
75     def expand_path(path)
76       @shell.expand_path(path)
77     end
79     #
80     # File related commands
81     # Shell#foreach
82     # Shell#open
83     # Shell#unlink
84     # Shell#test
85     #
86     # -
87     #   
88     # CommandProcessor#foreach(path, rs)
89     #     path: String
90     #     rs:   String - record separator
91     #     iterator
92     #   Same as:
93     #     File#foreach (when path is file)
94     #     Dir#foreach (when path is directory)
95     #   path is relative to pwd
96     #
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}
103       else
104         IO.foreach(path, *rs){|l| yield l}
105       end
106     end
108     #
109     # CommandProcessor#open(path, mode)
110     #     path:   String
111     #     mode:   String
112     #     return: File or Dir
113     #   Same as:
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
117     #
118     def open(path, mode = nil, perm = 0666, &b)
119       path = expand_path(path)
120       if File.directory?(path)
121         Dir.open(path, &b)
122       else
123         if @shell.umask
124           f = File.open(path, mode, perm)
125           File.chmod(perm & ~@shell.umask, path)
126           if block_given?
127             f.each &b
128           end
129           f
130         else
131           f = File.open(path, mode, perm, &b)
132         end
133       end
134     end
135     #  public :open
137     #
138     # CommandProcessor#unlink(path)
139     #   same as:
140     #     Dir#unlink  (when path is directory)
141     #     File#unlink (when path is file)
142     #
143     def unlink(path)
144       @shell.check_point
146       path = expand_path(path)
147       if File.directory?(path)
148         Dir.unlink(path)
149       else
150         IO.unlink(path)
151       end
152       Void.new(@shell)
153     end
155     #
156     # CommandProcessor#test(command, file1, file2)
157     # CommandProcessor#[command, file1, file2]
158     #     command: char or String or Symbol
159     #     file1:   String
160     #     file2:   String(optional)
161     #     return: Boolean
162     #   same as:
163     #     test()           (when command is char or length 1 string or symbol)
164     #     FileTest.command (others)
165     #   example:
166     #     sh[?e, "foo"]
167     #     sh[:e, "foo"]
168     #     sh["e", "foo"]
169     #     sh[:exists?, "foo"]
170     #     sh["exists?", "foo"]
171     #     
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)
178       case command
179       when Integer
180         if file2
181           top_level_test(command, file1, file2)
182         else
183           top_level_test(command, file1)
184         end
185       when String
186         if command.size == 1
187           if file2
188             top_level_test(command, file1, file2)
189           else
190             top_level_test(command, file1)
191           end
192         else
193           if file2
194             FileTest.send(command, file1, file2)
195           else
196             FileTest.send(command, file1)
197           end
198         end
199       end
200     end
201     alias [] test
203     #
204     # Dir related methods
205     #
206     # Shell#mkdir
207     # Shell#rmdir
208     #
209     #--
210     #
211     # CommandProcessor#mkdir(*path)
212     #     path: String
213     #   same as Dir.mkdir()
214     #     
215     def mkdir(*path)
216       @shell.check_point
217       notify("mkdir #{path.join(' ')}")
218       
219       perm = nil
220       if path.last.kind_of?(Integer)
221         perm = path.pop
222       end
223       for dir in path
224         d = expand_path(dir)
225         if perm
226           Dir.mkdir(d, perm)
227         else
228           Dir.mkdir(d)
229         end
230         File.chmod(d, 0666 & ~@shell.umask) if @shell.umask
231       end
232       Void.new(@shell)
233     end
235     #
236     # CommandProcessor#rmdir(*path)
237     #     path: String
238     #   same as Dir.rmdir()
239     #     
240     def rmdir(*path)
241       @shell.check_point
242       notify("rmdir #{path.join(' ')}")
244       for dir in path
245         Dir.rmdir(expand_path(dir))
246       end
247       Void.new(@shell)
248     end
250     #
251     # CommandProcessor#system(command, *opts)
252     #     command: String
253     #     opts:    String
254     #     return:  SystemCommand
255     #   Same as system() function
256     #   example:
257     #     print sh.system("ls", "-l")
258     #     sh.system("ls", "-l") | sh.head > STDOUT
259     # 
260     def system(command, *opts)
261       if opts.empty?
262         if command =~ /\*|\?|\{|\}|\[|\]|<|>|\(|\)|~|&|\||\\|\$|;|'|`|"|\n/
263           return SystemCommand.new(@shell, find_system_command("sh"), "-c", command)
264         else
265           command, *opts = command.split(/\s+/)
266         end
267       end
268       SystemCommand.new(@shell, find_system_command(command), *opts)
269     end
271     #
272     # ProcessCommand#rehash
273     #   clear command hash table.
274     #
275     def rehash
276       @system_commands = {}
277     end
279     #
280     # ProcessCommand#transact
281     #
282     def check_point
283       @shell.process_controller.wait_all_jobs_execution
284     end
285     alias finish_all_jobs check_point
287     def transact(&block)
288       begin
289         @shell.instance_eval(&block)
290       ensure
291         check_point
292       end
293     end
295     #
296     # internal commands
297     #
298     def out(dev = STDOUT, &block)
299       dev.print transact(&block)
300     end
302     def echo(*strings)
303       Echo.new(@shell, *strings)
304     end
306     def cat(*filenames)
307       Cat.new(@shell, *filenames)
308     end
310     #   def sort(*filenames)
311     #     Sort.new(self, *filenames)
312     #   end
314     def glob(pattern)
315       Glob.new(@shell, pattern)
316     end
318     def append(to, filter)
319       case to
320       when String
321         AppendFile.new(@shell, to, filter)
322       when IO
323         AppendIO.new(@shell, to, filter)
324       else
325         Shell.Fail Error::CantApplyMethod, "append", to.class
326       end
327     end
329     def tee(file)
330       Tee.new(@shell, file)
331     end
333     def concat(*jobs)
334       Concat.new(@shell, *jobs)
335     end
337     # %pwd, %cwd -> @pwd
338     def notify(*opts, &block)
339       Shell.notify(*opts) {|mes|
340         yield mes if iterator?
341         
342         mes.gsub!("%pwd", "#{@cwd}")
343         mes.gsub!("%cwd", "#{@cwd}")
344       }
345     end
347     #
348     # private functions
349     #
350     def find_system_command(command)
351       return command if /^\// =~ command
352       case path = @system_commands[command]
353       when String
354         if exists?(path)
355           return path
356         else
357           Shell.Fail Error::CommandNotFound, command
358         end
359       when false
360         Shell.Fail Error::CommandNotFound, command
361       end
363       for p in @shell.system_path
364         path = join(p, command)
365         if FileTest.exist?(path)
366           @system_commands[command] = path
367           return path
368         end
369       end
370       @system_commands[command] = false
371       Shell.Fail Error::CommandNotFound, command
372     end
374     #
375     # CommandProcessor.def_system_command(command, path)
376     #     command:  String
377     #     path:     String
378     #   define 'command()' method as method.
379     #
380     def self.def_system_command(command, path = command)
381       begin
382         eval((d = %Q[def #{command}(*opts)
383                   SystemCommand.new(@shell, '#{path}', *opts)
384                end]), nil, __FILE__, __LINE__ - 1)
385       rescue SyntaxError
386         Shell.notify "warn: Can't define #{command} path: #{path}." 
387       end
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)
391     end
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)}
398       self
399     end
401     # define command alias
402     # ex)
403     # def_alias_command("ls_c", "ls", "-C", "-F")
404     # def_alias_command("ls_c", "ls"){|*opts| ["-C", "-F", *opts]}
405     #
406     @alias_map = {}
407     def self.alias_map
408       @alias_map
409     end
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)
413       begin
414         if iterator?
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)
421     
422         else
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)
427         end
428       rescue SyntaxError
429         Shell.notify "warn: Can't alias #{ali} command: #{command}." 
430         Shell.notify("Definition of #{ali}: ", d)
431         raise
432       end
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)
436       self
437     end
438    
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)
443     end
444    
445     #
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
450     #        arguments:    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,...)
455     #
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{
460           |arg|
461           case arg
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)}'
467           else
468             arg
469           end
470         }.join(", ")
471         d = %Q[def #{meth}(#{arg_str})
472                     #{delegation_class}.#{meth}(#{call_arg_str})
473                  end]
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)
477         eval d
478       end
479     end
481     #
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.
489     #
490     def self.install_system_commands(pre = "sys_")
491       defined_meth = {}
492       for m in Shell.methods
493         defined_meth[m] = true
494       end
495       sh = Shell.new
496       for path in Shell.default_system_path
497         next unless sh.directory? path
498         sh.cd path
499         sh.foreach do
500           |cn|
501           if !defined_meth[pre + cn] && sh.file?(cn) && sh.executable?(cn)
502             command = (pre + cn).gsub(/\W/, "_").sub(/^([0-9])/, '_\1')
503             begin
504               def_system_command(command, sh.expand_path(cn))
505             rescue
506               Shell.notify "warn: Can't define #{command} path: #{cn}"
507             end
508             defined_meth[command] = command
509           end
510         end
511       end
512     end
514     #----------------------------------------------------------------------
515     #
516     #  class initializing methods  - 
517     #
518     #----------------------------------------------------------------------
519     def self.add_delegate_command_to_shell(id)
520       id = id.intern if id.kind_of?(String)
521       name = id.id2name
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}"
526       end
527       Shell.notify "method added: Shell##{name}.", Shell.debug?
528       Shell.module_eval(%Q[def #{name}(*args, &block)
529                             begin
530                               @command_processor.__send__(:#{name}, *args, &block)
531                             rescue Exception
532                               $@.delete_if{|s| /:in `__getobj__'$/ =~ s} #`
533                               $@.delete_if{|s| /^\\(eval\\):/ =~ s}
534                             raise
535                             end
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}"
542       end
543       Shell.notify "method added: Shell::Filter##{name}.", Shell.debug?
544       Filter.module_eval(%Q[def #{name}(*args, &block)
545                             begin
546                               self | @shell.__send__(:#{name}, *args, &block)
547                             rescue Exception
548                               $@.delete_if{|s| /:in `__getobj__'$/ =~ s} #`
549                               $@.delete_if{|s| /^\\(eval\\):/ =~ s}
550                             raise
551                             end
552                           end], __FILE__, __LINE__)
553     end
555     #
556     # define default builtin commands
557     #
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"]]})
590     end
592   end