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