lib/shell/command-processor.rb
DEFINITIONS
This source file includes following functions.
1 #
2 # shell/command-controller.rb -
3 # $Release Version: 0.6.0 $
4 # $Revision: 1.3 $
5 # $Date: 2001/06/27 15:35:04 $
6 # by Keiju ISHITSUKA(Nippon Rational Inc.)
7 #
8 # --
9 #
10 #
11 #
12
13 require "e2mmap"
14 require "ftools"
15 require "thread"
16
17 require "shell/error"
18 require "shell/filter"
19 require "shell/system-command"
20 require "shell/builtin-command"
21
22 class Shell
23 class CommandProcessor
24
25 #
26 # initialize of Shell and related classes.
27 #
28 NoDelegateMethods = ["initialize", "expand_path"]
29 def self.initialize
30
31 install_builtin_commands
32
33 # define CommandProccessor#methods to Shell#methods and Filter#methods
34 for m in CommandProcessor.instance_methods - NoDelegateMethods
35 add_delegate_command_to_shell(m)
36 end
37
38 def self.method_added(id)
39 add_delegate_command_to_shell(id)
40 end
41 end
42
43 #
44 # include run file.
45 #
46 def self.run_config
47 begin
48 load File.expand_path("~/.rb_shell") if ENV.key?("HOME")
49 rescue LoadError, Errno::ENOENT
50 rescue
51 print "load error: #{rc}\n"
52 print $!.type, ": ", $!, "\n"
53 for err in $@[0, $@.size - 2]
54 print "\t", err, "\n"
55 end
56 end
57 end
58
59 def initialize(shell)
60 @shell = shell
61 @system_commands = {}
62 end
63
64 #
65 # CommandProcessor#expand_path(path)
66 # path: String
67 # return: String
68 # returns the absolute path for <path>
69 #
70 def expand_path(path)
71 @shell.expand_path(path)
72 end
73
74 #
75 # File related commands
76 # Shell#foreach
77 # Shell#open
78 # Shell#unlink
79 # Shell#test
80 #
81 # -
82 #
83 # CommandProcessor#foreach(path, rs)
84 # path: String
85 # rs: String - record separator
86 # iterator
87 # Same as:
88 # File#foreach (when path is file)
89 # Dir#foreach (when path is directory)
90 # path is relative to pwd
91 #
92 def foreach(path = nil, *rs)
93 path = "." unless path
94 path = expand_path(path)
95
96 if File.directory?(path)
97 Dir.foreach(path){|fn| yield fn}
98 else
99 IO.foreach(path, *rs){|l| yield l}
100 end
101 end
102
103 #
104 # CommandProcessor#open(path, mode)
105 # path: String
106 # mode: String
107 # return: File or Dir
108 # Same as:
109 # File#open (when path is file)
110 # Dir#open (when path is directory)
111 # mode has an effect only when path is a file
112 #
113 def open(path, mode)
114 path = expand_path(path)
115 if File.directory?(path)
116 Dir.open(path)
117 else
118 effect_umask do
119 File.open(path, mode)
120 end
121 end
122 end
123 # public :open
124
125 #
126 # CommandProcessor#unlink(path)
127 # same as:
128 # Dir#unlink (when path is directory)
129 # File#unlink (when path is file)
130 #
131 def unlink(path)
132 path = expand_path(path)
133 if File.directory?(path)
134 Dir.unlink(path)
135 else
136 IO.unlink(path)
137 end
138 end
139
140 #
141 # CommandProcessor#test(command, file1, file2)
142 # CommandProcessor#[command, file1, file2]
143 # command: char or String or Symbol
144 # file1: String
145 # file2: String(optional)
146 # return: Boolean
147 # same as:
148 # test() (when command is char or length 1 string or sumbol)
149 # FileTest.command (others)
150 # example:
151 # sh[?e, "foo"]
152 # sh[:e, "foo"]
153 # sh["e", "foo"]
154 # sh[:exists?, "foo"]
155 # sh["exists?", "foo"]
156 #
157 def test(command, file1, file2=nil)
158 file1 = expand_path(file1)
159 file2 = expand_path(file2) if file2
160 command = command.id2name if command.kind_of?(Symbol)
161
162 case command
163 when Integer
164 top_level_test(command, file1, file2)
165 when String
166 if command.size == 1
167 if file2
168 top_level_test(command, file1, file2)
169 else
170 top_level_test(command, file1)
171 end
172 else
173 if file2
174 FileTest.send(command, file1, file2)
175 else
176 FileTest.send(command, file1)
177 end
178 end
179 end
180 end
181 alias [] test
182
183 #
184 # Dir related methods
185 #
186 # Shell#mkdir
187 # Shell#rmdir
188 #
189 #--
190 #
191 # CommandProcessor#mkdir(*path)
192 # path: String
193 # same as Dir.mkdir()
194 #
195 def mkdir(*path)
196 for dir in path
197 Dir.mkdir(expand_path(dir))
198 end
199 end
200
201 #
202 # CommandProcessor#rmdir(*path)
203 # path: String
204 # same as Dir.rmdir()
205 #
206 def rmdir(*path)
207 for dir in path
208 Dir.rmdir(expand_path(path))
209 end
210 end
211
212 #
213 # CommandProcessor#system(command, *opts)
214 # command: String
215 # opts: String
216 # retuen: SystemCommand
217 # Same as system() function
218 # example:
219 # print sh.system("ls", "-l")
220 # sh.system("ls", "-l") | sh.head > STDOUT
221 #
222 def system(command, *opts)
223 SystemCommand.new(@shell, find_system_command(command), *opts)
224 end
225
226 #
227 # ProcessCommand#rehash
228 # clear command hash table.
229 #
230 def rehash
231 @system_commands = {}
232 end
233
234 #
235 # ProcessCommand#transact
236 #
237 def check_point
238 @shell.process_controller.wait_all_jobs_execution
239 end
240 alias finish_all_jobs check_point
241
242 def transact(&block)
243 begin
244 @shell.instance_eval(&block)
245 ensure
246 check_point
247 end
248 end
249
250 #
251 # internal commands
252 #
253 def out(dev = STDOUT, &block)
254 dev.print transact(&block)
255 end
256
257 def echo(*strings)
258 Echo.new(@shell, *strings)
259 end
260
261 def cat(*filenames)
262 Cat.new(@shell, *filenames)
263 end
264
265 # def sort(*filenames)
266 # Sort.new(self, *filenames)
267 # end
268
269 def glob(pattern)
270 Glob.new(@shell, pattern)
271 end
272
273 def append(to, filter)
274 case to
275 when String
276 AppendFile.new(@shell, to, filter)
277 when IO
278 AppendIO.new(@shell, to, filter)
279 else
280 Shell.Fail CanNotMethodApply, "append", to.type
281 end
282 end
283
284 def tee(file)
285 Tee.new(@shell, file)
286 end
287
288 def concat(*jobs)
289 Concat.new(@shell, *jobs)
290 end
291
292 # %pwd, %cwd -> @pwd
293 def notify(*opts, &block)
294 Thread.exclusive do
295 Shell.notify(*opts) {|mes|
296 yield mes if iterator?
297
298 mes.gsub!("%pwd", "#{@cwd}")
299 mes.gsub!("%cwd", "#{@cwd}")
300 }
301 end
302 end
303
304 #
305 # private functions
306 #
307 def effect_umask
308 if @shell.umask
309 Thread.critical = true
310 save = File.umask
311 begin
312 yield
313 ensure
314 File.umask save
315 Thread.critical = false
316 end
317 else
318 yield
319 end
320 end
321 private :effect_umask
322
323 def find_system_command(command)
324 return command if /^\// =~ command
325 case path = @system_commands[command]
326 when String
327 if exists?(path)
328 return path
329 else
330 Shell.Fail CommandNotFound, command
331 end
332 when false
333 Shell.Fail CommandNotFound, command
334 end
335
336 for p in @shell.system_path
337 path = join(p, command)
338 if FileTest.exists?(path)
339 @system_commands[command] = path
340 return path
341 end
342 end
343 @system_commands[command] = false
344 Shell.Fail CommandNotFound, command
345 end
346
347 #
348 # CommandProcessor.def_system_command(command, path)
349 # command: String
350 # path: String
351 # define 'command()' method as method.
352 #
353 def self.def_system_command(command, path = command)
354 begin
355 eval((d = %Q[def #{command}(*opts)
356 SystemCommand.new(@shell, '#{path}', *opts)
357 end]), nil, __FILE__, __LINE__ - 1)
358 rescue SyntaxError
359 Shell.notify "warn: Can't define #{command} path: #{path}."
360 end
361 Shell.notify "Define #{command} path: #{path}.", Shell.debug?
362 Shell.notify("Definition of #{command}: ", d,
363 Shell.debug.kind_of?(Integer) && Shell.debug > 1)
364 end
365
366 def self.undef_system_command(command)
367 command = command.id2name if command.kind_of?(Symbol)
368 remove_method(command)
369 Shell.module_eval{remove_method(command)}
370 Filter.module_eval{remove_method(command)}
371 self
372 end
373
374 # define command alias
375 # ex)
376 # def_alias_command("ls_c", "ls", "-C", "-F")
377 # def_alias_command("ls_c", "ls"){|*opts| ["-C", "-F", *opts]}
378 #
379 @alias_map = {}
380 def self.alias_map
381 @alias_map
382 end
383 def self.alias_command(ali, command, *opts, &block)
384 ali = ali.id2name if ali.kind_of?(Symbol)
385 command = command.id2name if command.kind_of?(Symbol)
386 begin
387 if iterator?
388 @alias_map[ali.intern] = proc
389
390 eval((d = %Q[def #{ali}(*opts)
391 @shell.__send__(:#{command},
392 *(CommandProcessor.alias_map[:#{ali}].call *opts))
393 end]), nil, __FILE__, __LINE__ - 1)
394
395 else
396 args = opts.collect{|opt| '"' + opt + '"'}.join ","
397 eval((d = %Q[def #{ali}(*opts)
398 @shell.__send__(:#{command}, #{args}, *opts)
399 end]), nil, __FILE__, __LINE__ - 1)
400 end
401 rescue SyntaxError
402 Shell.notify "warn: Can't alias #{ali} command: #{command}."
403 Shell.notify("Definition of #{ali}: ", d)
404 raise
405 end
406 Shell.notify "Define #{ali} command: #{command}.", Shell.debug?
407 Shell.notify("Definition of #{ali}: ", d,
408 Shell.debug.kind_of?(Integer) && Shell.debug > 1)
409 self
410 end
411
412 def self.unalias_command(ali)
413 ali = ali.id2name if ali.kind_of?(Symbol)
414 @alias_map.delete ali.intern
415 undef_system_command(ali)
416 end
417
418 #
419 # CommandProcessor.def_builtin_commands(delegation_class, command_specs)
420 # delegation_class: Class or Module
421 # command_specs: [[command_name, [argument,...]],...]
422 # command_name: String
423 # arguments: String
424 # FILENAME?? -> expand_path(filename??)
425 # *FILENAME?? -> filename??.collect{|f|expand_path(f)}.join(", ")
426 # define command_name(argument,...) as
427 # delegation_class.command_name(argument,...)
428 #
429 def self.def_builtin_commands(delegation_class, command_specs)
430 for meth, args in command_specs
431 arg_str = args.collect{|arg| arg.downcase}.join(", ")
432 call_arg_str = args.collect{
433 |arg|
434 case arg
435 when /^(FILENAME.*)$/
436 format("expand_path(%s)", $1.downcase)
437 when /^(\*FILENAME.*)$/
438 # \*FILENAME* -> filenames.collect{|fn| expand_path(fn)}.join(", ")
439 $1.downcase + '.collect{|fn| expand_path(fn)}'
440 else
441 arg
442 end
443 }.join(", ")
444 d = %Q[def #{meth}(#{arg_str})
445 #{delegation_class}.#{meth}(#{call_arg_str})
446 end]
447 Shell.notify "Define #{meth}(#{arg_str})", Shell.debug?
448 Shell.notify("Definition of #{meth}: ", d,
449 Shell.debug.kind_of?(Integer) && Shell.debug > 1)
450 eval d
451 end
452 end
453
454 #
455 # CommandProcessor.install_system_commands(pre)
456 # pre: String - command name prefix
457 # defines every command which belongs in default_system_path via
458 # CommandProcessor.command(). It doesn't define already defined
459 # methods twice. By default, "pre_" is prefixes to each method
460 # name. Characters that may not be used in a method name are
461 # all converted to '_'. Definition errors are just ignored.
462 #
463 def self.install_system_commands(pre = "sys_")
464 defined_meth = {}
465 for m in Shell.methods
466 defined_meth[m] = true
467 end
468 sh = Shell.new
469 for path in Shell.default_system_path
470 next unless sh.directory? path
471 sh.cd path
472 sh.foreach do
473 |cn|
474 if !defined_meth[pre + cn] && sh.file?(cn) && sh.executable?(cn)
475 command = (pre + cn).gsub(/\W/, "_").sub(/^([0-9])/, '_\1')
476 begin
477 def_system_command(command, sh.expand_path(cn))
478 rescue
479 Shell.notify "warn: Can't define #{command} path: #{cn}"
480 end
481 defined_meth[command] = command
482 end
483 end
484 end
485 end
486
487 #----------------------------------------------------------------------
488 #
489 # class initializing methods -
490 #
491 #----------------------------------------------------------------------
492 def self.add_delegate_command_to_shell(id)
493 id = id.intern if id.kind_of?(String)
494 name = id.id2name
495 if Shell.method_defined?(id)
496 Shell.notify "warn: override definnition of Shell##{name}."
497 Shell.notify "warn: alias Shell##{name} to Shell##{name}_org.\n"
498 Shell.module_eval "alias #{name}_org #{name}"
499 end
500 Shell.notify "method added: Shell##{name}.", Shell.debug?
501 Shell.module_eval(%Q[def #{name}(*args, &block)
502 begin
503 @command_processor.__send__(:#{name}, *args, &block)
504 rescue Exception
505 $@.delete_if{|s| /:in `__getobj__'$/ =~ s} #`
506 $@.delete_if{|s| /^\\(eval\\):/ =~ s}
507 raise
508 end
509 end], __FILE__, __LINE__)
510
511 if Shell::Filter.method_defined?(id)
512 Shell.notify "warn: override definnition of Shell::Filter##{name}."
513 Shell.notify "warn: alias Shell##{name} to Shell::Filter##{name}_org."
514 Filter.module_eval "alias #{name}_org #{name}"
515 end
516 Shell.notify "method added: Shell::Filter##{name}.", Shell.debug?
517 Filter.module_eval(%Q[def #{name}(*args, &block)
518 begin
519 self | @shell.__send__(:#{name}, *args, &block)
520 rescue Exception
521 $@.delete_if{|s| /:in `__getobj__'$/ =~ s} #`
522 $@.delete_if{|s| /^\\(eval\\):/ =~ s}
523 raise
524 end
525 end], __FILE__, __LINE__)
526 end
527
528 #
529 # define default builtin commands
530 #
531 def self.install_builtin_commands
532 # method related File.
533 # (exclude open/foreach/unlink)
534 normal_delegation_file_methods = [
535 ["atime", ["FILENAME"]],
536 ["basename", ["fn", "*opts"]],
537 ["chmod", ["mode", "*FILENAMES"]],
538 ["chown", ["owner", "group", "*FILENAME"]],
539 ["ctime", ["FILENAMES"]],
540 ["delete", ["*FILENAMES"]],
541 ["dirname", ["FILENAME"]],
542 ["ftype", ["FILENAME"]],
543 ["join", ["*items"]],
544 ["link", ["FILENAME_O", "FILENAME_N"]],
545 ["lstat", ["FILENAME"]],
546 ["mtime", ["FILENAME"]],
547 ["readlink", ["FILENAME"]],
548 ["rename", ["FILENAME_FROM", "FILENAME_TO"]],
549 # ["size", ["FILENAME"]],
550 ["split", ["pathname"]],
551 ["stat", ["FILENAME"]],
552 ["symlink", ["FILENAME_O", "FILENAME_N"]],
553 ["truncate", ["FILENAME", "length"]],
554 ["utime", ["atime", "mtime", "*FILENAMES"]]]
555
556 def_builtin_commands(File, normal_delegation_file_methods)
557 alias_method :rm, :delete
558
559 # method related FileTest
560 def_builtin_commands(FileTest,
561 FileTest.singleton_methods.collect{|m| [m, ["FILENAME"]]})
562
563 # method related ftools
564 normal_delegation_ftools_methods = [
565 ["syscopy", ["FILENAME_FROM", "FILENAME_TO"]],
566 ["copy", ["FILENAME_FROM", "FILENAME_TO"]],
567 ["move", ["FILENAME_FROM", "FILENAME_TO"]],
568 ["compare", ["FILENAME_FROM", "FILENAME_TO"]],
569 ["safe_unlink", ["*FILENAMES"]],
570 ["makedirs", ["*FILENAMES"]],
571 # ["chmod", ["mode", "*FILENAMES"]],
572 ["install", ["FILENAME_FROM", "FILENAME_TO", "mode"]],
573 ]
574 def_builtin_commands(File,
575 normal_delegation_ftools_methods)
576 alias_method :cmp, :compare
577 alias_method :mv, :move
578 alias_method :cp, :copy
579 alias_method :rm_f, :safe_unlink
580 alias_method :mkpath, :makedirs
581 end
582
583 end
584 end