rubysh 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/.gitignore +17 -0
 - data/Gemfile +4 -0
 - data/LICENSE +22 -0
 - data/README.md +88 -0
 - data/Rakefile +10 -0
 - data/lib/rubysh/base_command.rb +65 -0
 - data/lib/rubysh/base_directive.rb +24 -0
 - data/lib/rubysh/command.rb +102 -0
 - data/lib/rubysh/error.rb +20 -0
 - data/lib/rubysh/fd.rb +43 -0
 - data/lib/rubysh/pipe.rb +4 -0
 - data/lib/rubysh/pipeline.rb +70 -0
 - data/lib/rubysh/redirect.rb +181 -0
 - data/lib/rubysh/runner.rb +156 -0
 - data/lib/rubysh/subprocess/parallel_io.rb +184 -0
 - data/lib/rubysh/subprocess/pipe_wrapper.rb +61 -0
 - data/lib/rubysh/subprocess.rb +154 -0
 - data/lib/rubysh/triple_less_than.rb +65 -0
 - data/lib/rubysh/util.rb +55 -0
 - data/lib/rubysh/version.rb +3 -0
 - data/lib/rubysh.rb +149 -0
 - data/rubysh.gemspec +26 -0
 - data/test/_lib.rb +25 -0
 - data/test/functional/_lib.rb +7 -0
 - data/test/functional/lib/fd-lister +2 -0
 - data/test/functional/lib/leaked_fds.rb +83 -0
 - data/test/functional/lib/redirect_ordering.rb +15 -0
 - data/test/functional/lib/triple_less_than.rb +16 -0
 - data/test/integration/_lib.rb +7 -0
 - data/test/integration/lib/rubysh.rb +6 -0
 - data/test/rubysh +47 -0
 - data/test/unit/_lib.rb +7 -0
 - data/test/unit/lib/rubysh/command.rb +20 -0
 - data/test/unit/lib/rubysh/pipeline.rb +108 -0
 - data/test/unit/lib/rubysh/redirect.rb +44 -0
 - data/test/unit/lib/rubysh/runner.rb +16 -0
 - data/test/unit/lib/rubysh/subprocess/parallel_io.rb +233 -0
 - data/test/unit/lib/rubysh/subprocess.rb +37 -0
 - data/test/unit/lib/rubysh.rb +74 -0
 - metadata +149 -0
 
    
        data/.gitignore
    ADDED
    
    
    
        data/Gemfile
    ADDED
    
    
    
        data/LICENSE
    ADDED
    
    | 
         @@ -0,0 +1,22 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            Copyright (c) 2012 Greg Brockman
         
     | 
| 
      
 2 
     | 
    
         
            +
             
     | 
| 
      
 3 
     | 
    
         
            +
            MIT License
         
     | 
| 
      
 4 
     | 
    
         
            +
             
     | 
| 
      
 5 
     | 
    
         
            +
            Permission is hereby granted, free of charge, to any person obtaining
         
     | 
| 
      
 6 
     | 
    
         
            +
            a copy of this software and associated documentation files (the
         
     | 
| 
      
 7 
     | 
    
         
            +
            "Software"), to deal in the Software without restriction, including
         
     | 
| 
      
 8 
     | 
    
         
            +
            without limitation the rights to use, copy, modify, merge, publish,
         
     | 
| 
      
 9 
     | 
    
         
            +
            distribute, sublicense, and/or sell copies of the Software, and to
         
     | 
| 
      
 10 
     | 
    
         
            +
            permit persons to whom the Software is furnished to do so, subject to
         
     | 
| 
      
 11 
     | 
    
         
            +
            the following conditions:
         
     | 
| 
      
 12 
     | 
    
         
            +
             
     | 
| 
      
 13 
     | 
    
         
            +
            The above copyright notice and this permission notice shall be
         
     | 
| 
      
 14 
     | 
    
         
            +
            included in all copies or substantial portions of the Software.
         
     | 
| 
      
 15 
     | 
    
         
            +
             
     | 
| 
      
 16 
     | 
    
         
            +
            THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
         
     | 
| 
      
 17 
     | 
    
         
            +
            EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
         
     | 
| 
      
 18 
     | 
    
         
            +
            MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
         
     | 
| 
      
 19 
     | 
    
         
            +
            NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
         
     | 
| 
      
 20 
     | 
    
         
            +
            LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
         
     | 
| 
      
 21 
     | 
    
         
            +
            OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
         
     | 
| 
      
 22 
     | 
    
         
            +
            WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
         
     | 
    
        data/README.md
    ADDED
    
    | 
         @@ -0,0 +1,88 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            # Rubysh
         
     | 
| 
      
 2 
     | 
    
         
            +
             
     | 
| 
      
 3 
     | 
    
         
            +
            Rubysh: Ruby subprocesses made easy
         
     | 
| 
      
 4 
     | 
    
         
            +
             
     | 
| 
      
 5 
     | 
    
         
            +
            Rubysh makes shelling out easy with a __sh__-like syntax layer for Ruby:
         
     | 
| 
      
 6 
     | 
    
         
            +
             
     | 
| 
      
 7 
     | 
    
         
            +
                irb -r rubysh
         
     | 
| 
      
 8 
     | 
    
         
            +
                >> command = Rubysh('echo', 'hello-from-Rubysh') | Rubysh('grep', '--color', 'Rubysh')
         
     | 
| 
      
 9 
     | 
    
         
            +
                >> command.run
         
     | 
| 
      
 10 
     | 
    
         
            +
                hello-from-Rubysh
         
     | 
| 
      
 11 
     | 
    
         
            +
                => Rubysh::Runner: echo hello-from-Rubysh | grep --color Rubysh (exitstatus: 0)
         
     | 
| 
      
 12 
     | 
    
         
            +
             
     | 
| 
      
 13 
     | 
    
         
            +
            Rubysh philosophy is to make simple tasks simple and complex tasks
         
     | 
| 
      
 14 
     | 
    
         
            +
            possible.
         
     | 
| 
      
 15 
     | 
    
         
            +
             
     | 
| 
      
 16 
     | 
    
         
            +
            ## Motivation
         
     | 
| 
      
 17 
     | 
    
         
            +
             
     | 
| 
      
 18 
     | 
    
         
            +
            Existing Ruby shell libaries make it very difficult to do tasks that
         
     | 
| 
      
 19 
     | 
    
         
            +
            are simple in __sh__, such as:
         
     | 
| 
      
 20 
     | 
    
         
            +
             
     | 
| 
      
 21 
     | 
    
         
            +
              - piping the output from one program to another
         
     | 
| 
      
 22 
     | 
    
         
            +
              - redirecting a program's output to a file
         
     | 
| 
      
 23 
     | 
    
         
            +
              - use a pre-tokenized array of arguments
         
     | 
| 
      
 24 
     | 
    
         
            +
             
     | 
| 
      
 25 
     | 
    
         
            +
            Rubysh tries to emulate __sh__'s interface and semantics as closely as
         
     | 
| 
      
 26 
     | 
    
         
            +
            possible.
         
     | 
| 
      
 27 
     | 
    
         
            +
             
     | 
| 
      
 28 
     | 
    
         
            +
            ## Features
         
     | 
| 
      
 29 
     | 
    
         
            +
             
     | 
| 
      
 30 
     | 
    
         
            +
            Redirecting a file descriptor to a file:
         
     | 
| 
      
 31 
     | 
    
         
            +
             
     | 
| 
      
 32 
     | 
    
         
            +
                # echo hello-from-Rubysh >/tmp/file.txt
         
     | 
| 
      
 33 
     | 
    
         
            +
                Rubysh('echo', 'hello-from-Rubysh', Rubysh.stdout > '/tmp/file.txt')
         
     | 
| 
      
 34 
     | 
    
         
            +
                Rubysh('echo', 'hello-from-Rubysh', Rubysh::FD(1) > '/tmp/file.txt')
         
     | 
| 
      
 35 
     | 
    
         
            +
             
     | 
| 
      
 36 
     | 
    
         
            +
            Redirecting a file descriptor to another file descriptor:
         
     | 
| 
      
 37 
     | 
    
         
            +
             
     | 
| 
      
 38 
     | 
    
         
            +
                # echo hello-from-Rubysh 2>&1
         
     | 
| 
      
 39 
     | 
    
         
            +
                Rubysh('echo', 'hello-from-Rubysh', Rubysh.stderr > Rubysh.stdout)
         
     | 
| 
      
 40 
     | 
    
         
            +
             
     | 
| 
      
 41 
     | 
    
         
            +
            Feeding standard input with a string literal:
         
     | 
| 
      
 42 
     | 
    
         
            +
             
     | 
| 
      
 43 
     | 
    
         
            +
                # cat <<< "hello there"
         
     | 
| 
      
 44 
     | 
    
         
            +
                Rubysh('cat', Rubysh.<<< 'hello there')
         
     | 
| 
      
 45 
     | 
    
         
            +
             
     | 
| 
      
 46 
     | 
    
         
            +
            Rubysh has been written to work with arbitrary file descriptors, so
         
     | 
| 
      
 47 
     | 
    
         
            +
            you can do the same advanced FD redirection magic you can in __sh__:
         
     | 
| 
      
 48 
     | 
    
         
            +
             
     | 
| 
      
 49 
     | 
    
         
            +
                # cat 3<<< "hello there" <&3
         
     | 
| 
      
 50 
     | 
    
         
            +
                Rubysh('cat', Rubysh::FD(3).<<< 'hello there', Rubysh.stdin < Rubysh::FD(3))
         
     | 
| 
      
 51 
     | 
    
         
            +
             
     | 
| 
      
 52 
     | 
    
         
            +
            You can also capture output to a named target (here :stdout, :stderr
         
     | 
| 
      
 53 
     | 
    
         
            +
            are arbitrary symbols):
         
     | 
| 
      
 54 
     | 
    
         
            +
             
     | 
| 
      
 55 
     | 
    
         
            +
                command = Rubysh('echo', 'hi', Rubysh.stdout > :stdout, Rubysh.stderr > :stderr)
         
     | 
| 
      
 56 
     | 
    
         
            +
                runner = command.run
         
     | 
| 
      
 57 
     | 
    
         
            +
                runner.data(:stdout) # "hi\n"
         
     | 
| 
      
 58 
     | 
    
         
            +
                runner.data(:stderr) # ""
         
     | 
| 
      
 59 
     | 
    
         
            +
             
     | 
| 
      
 60 
     | 
    
         
            +
            Support for controlled input isn't quite ready, but the syntax will be
         
     | 
| 
      
 61 
     | 
    
         
            +
            similar to the above. I want to support interactivity (so being able
         
     | 
| 
      
 62 
     | 
    
         
            +
            to write data, read some data, and then write more data), and haven't
         
     | 
| 
      
 63 
     | 
    
         
            +
            quite decided on the right API for this yet.
         
     | 
| 
      
 64 
     | 
    
         
            +
             
     | 
| 
      
 65 
     | 
    
         
            +
            ## API
         
     | 
| 
      
 66 
     | 
    
         
            +
             
     | 
| 
      
 67 
     | 
    
         
            +
            The Rubysh helper function produces instances of __BaseCommand__. You
         
     | 
| 
      
 68 
     | 
    
         
            +
            can run __run__ on these to spawn a subprocess and then __wait__ for
         
     | 
| 
      
 69 
     | 
    
         
            +
            it to complete. Alternatively, you can do:
         
     | 
| 
      
 70 
     | 
    
         
            +
             
     | 
| 
      
 71 
     | 
    
         
            +
                command = Rubysh('ls')
         
     | 
| 
      
 72 
     | 
    
         
            +
                runner = command.run_async
         
     | 
| 
      
 73 
     | 
    
         
            +
                runner.wait
         
     | 
| 
      
 74 
     | 
    
         
            +
             
     | 
| 
      
 75 
     | 
    
         
            +
            ## Installation
         
     | 
| 
      
 76 
     | 
    
         
            +
             
     | 
| 
      
 77 
     | 
    
         
            +
            Rubysh is hosted on Rubygems. You can install by adding this line to
         
     | 
| 
      
 78 
     | 
    
         
            +
            your application's Gemfile:
         
     | 
| 
      
 79 
     | 
    
         
            +
             
     | 
| 
      
 80 
     | 
    
         
            +
                gem 'rubysh'
         
     | 
| 
      
 81 
     | 
    
         
            +
             
     | 
| 
      
 82 
     | 
    
         
            +
            Or by installing directly via
         
     | 
| 
      
 83 
     | 
    
         
            +
             
     | 
| 
      
 84 
     | 
    
         
            +
                $ gem install rubysh
         
     | 
| 
      
 85 
     | 
    
         
            +
             
     | 
| 
      
 86 
     | 
    
         
            +
            ## Contributing
         
     | 
| 
      
 87 
     | 
    
         
            +
             
     | 
| 
      
 88 
     | 
    
         
            +
            Patches welcome! I'm happy to merge pull requests.
         
     | 
    
        data/Rakefile
    ADDED
    
    
| 
         @@ -0,0 +1,65 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            require 'shellwords'
         
     | 
| 
      
 2 
     | 
    
         
            +
             
     | 
| 
      
 3 
     | 
    
         
            +
            module Rubysh
         
     | 
| 
      
 4 
     | 
    
         
            +
              # TODO:
         
     | 
| 
      
 5 
     | 
    
         
            +
              #
         
     | 
| 
      
 6 
     | 
    
         
            +
              # - freeze after initialize?
         
     | 
| 
      
 7 
     | 
    
         
            +
              class BaseCommand
         
     | 
| 
      
 8 
     | 
    
         
            +
                def stringify_arg(arg)
         
     | 
| 
      
 9 
     | 
    
         
            +
                  case arg
         
     | 
| 
      
 10 
     | 
    
         
            +
                  when BaseCommand, BaseDirective
         
     | 
| 
      
 11 
     | 
    
         
            +
                    arg.stringify
         
     | 
| 
      
 12 
     | 
    
         
            +
                  else
         
     | 
| 
      
 13 
     | 
    
         
            +
                    Shellwords.shellescape(arg.to_s)
         
     | 
| 
      
 14 
     | 
    
         
            +
                  end
         
     | 
| 
      
 15 
     | 
    
         
            +
                end
         
     | 
| 
      
 16 
     | 
    
         
            +
             
     | 
| 
      
 17 
     | 
    
         
            +
                def to_s
         
     | 
| 
      
 18 
     | 
    
         
            +
                  "Command: #{stringify}"
         
     | 
| 
      
 19 
     | 
    
         
            +
                end
         
     | 
| 
      
 20 
     | 
    
         
            +
             
     | 
| 
      
 21 
     | 
    
         
            +
                def inspect
         
     | 
| 
      
 22 
     | 
    
         
            +
                  to_s
         
     | 
| 
      
 23 
     | 
    
         
            +
                end
         
     | 
| 
      
 24 
     | 
    
         
            +
             
     | 
| 
      
 25 
     | 
    
         
            +
                def create_runner
         
     | 
| 
      
 26 
     | 
    
         
            +
                  Runner.new(self)
         
     | 
| 
      
 27 
     | 
    
         
            +
                end
         
     | 
| 
      
 28 
     | 
    
         
            +
             
     | 
| 
      
 29 
     | 
    
         
            +
                def run
         
     | 
| 
      
 30 
     | 
    
         
            +
                  create_runner.run
         
     | 
| 
      
 31 
     | 
    
         
            +
                end
         
     | 
| 
      
 32 
     | 
    
         
            +
             
     | 
| 
      
 33 
     | 
    
         
            +
                def run_async
         
     | 
| 
      
 34 
     | 
    
         
            +
                  create_runner.run_async
         
     | 
| 
      
 35 
     | 
    
         
            +
                end
         
     | 
| 
      
 36 
     | 
    
         
            +
             
     | 
| 
      
 37 
     | 
    
         
            +
                def |(other)
         
     | 
| 
      
 38 
     | 
    
         
            +
                  raise NotImplementedError.new("Override in subclass")
         
     | 
| 
      
 39 
     | 
    
         
            +
                end
         
     | 
| 
      
 40 
     | 
    
         
            +
             
     | 
| 
      
 41 
     | 
    
         
            +
                def initialize(args)
         
     | 
| 
      
 42 
     | 
    
         
            +
                  raise NotImplementedError.new("Override in subclass")
         
     | 
| 
      
 43 
     | 
    
         
            +
                end
         
     | 
| 
      
 44 
     | 
    
         
            +
             
     | 
| 
      
 45 
     | 
    
         
            +
                def start_async(runner)
         
     | 
| 
      
 46 
     | 
    
         
            +
                  raise NotImplementedError.new("Override in subclass")
         
     | 
| 
      
 47 
     | 
    
         
            +
                end
         
     | 
| 
      
 48 
     | 
    
         
            +
             
     | 
| 
      
 49 
     | 
    
         
            +
                def wait(runner)
         
     | 
| 
      
 50 
     | 
    
         
            +
                  raise NotImplementedError.new("Override in subclass")
         
     | 
| 
      
 51 
     | 
    
         
            +
                end
         
     | 
| 
      
 52 
     | 
    
         
            +
             
     | 
| 
      
 53 
     | 
    
         
            +
                def stringify
         
     | 
| 
      
 54 
     | 
    
         
            +
                  raise NotImplementedError.new("Override in subclass")
         
     | 
| 
      
 55 
     | 
    
         
            +
                end
         
     | 
| 
      
 56 
     | 
    
         
            +
             
     | 
| 
      
 57 
     | 
    
         
            +
                def stdout=(value)
         
     | 
| 
      
 58 
     | 
    
         
            +
                  raise NotImplementedError.new("Override in subclass")
         
     | 
| 
      
 59 
     | 
    
         
            +
                end
         
     | 
| 
      
 60 
     | 
    
         
            +
             
     | 
| 
      
 61 
     | 
    
         
            +
                def stdin=(value)
         
     | 
| 
      
 62 
     | 
    
         
            +
                  raise NotImplementedError.new("Override in subclass")
         
     | 
| 
      
 63 
     | 
    
         
            +
                end
         
     | 
| 
      
 64 
     | 
    
         
            +
              end
         
     | 
| 
      
 65 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -0,0 +1,24 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            module Rubysh
         
     | 
| 
      
 2 
     | 
    
         
            +
              class BaseDirective
         
     | 
| 
      
 3 
     | 
    
         
            +
                def stringify
         
     | 
| 
      
 4 
     | 
    
         
            +
                  raise NotImplementedError.new("Override in subclass")
         
     | 
| 
      
 5 
     | 
    
         
            +
                end
         
     | 
| 
      
 6 
     | 
    
         
            +
             
     | 
| 
      
 7 
     | 
    
         
            +
                def prepare!(runner)
         
     | 
| 
      
 8 
     | 
    
         
            +
                  raise NotImplementedError.new("Override in subclass")
         
     | 
| 
      
 9 
     | 
    
         
            +
                end
         
     | 
| 
      
 10 
     | 
    
         
            +
             
     | 
| 
      
 11 
     | 
    
         
            +
                def apply_parent!(runner)
         
     | 
| 
      
 12 
     | 
    
         
            +
                  raise NotImplementedError.new("Override in subclass")
         
     | 
| 
      
 13 
     | 
    
         
            +
                end
         
     | 
| 
      
 14 
     | 
    
         
            +
             
     | 
| 
      
 15 
     | 
    
         
            +
                def apply!(runner)
         
     | 
| 
      
 16 
     | 
    
         
            +
                  raise NotImplementedError.new("Override in subclass")
         
     | 
| 
      
 17 
     | 
    
         
            +
                end
         
     | 
| 
      
 18 
     | 
    
         
            +
             
     | 
| 
      
 19 
     | 
    
         
            +
                # TODO: DRY up?
         
     | 
| 
      
 20 
     | 
    
         
            +
                def state(runner)
         
     | 
| 
      
 21 
     | 
    
         
            +
                  runner.state(self)
         
     | 
| 
      
 22 
     | 
    
         
            +
                end
         
     | 
| 
      
 23 
     | 
    
         
            +
              end
         
     | 
| 
      
 24 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -0,0 +1,102 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            module Rubysh
         
     | 
| 
      
 2 
     | 
    
         
            +
              class Command < BaseCommand
         
     | 
| 
      
 3 
     | 
    
         
            +
                attr_accessor :raw_args, :directives, :args
         
     | 
| 
      
 4 
     | 
    
         
            +
             
     | 
| 
      
 5 
     | 
    
         
            +
                def initialize(args)
         
     | 
| 
      
 6 
     | 
    
         
            +
                  @raw_args = args
         
     | 
| 
      
 7 
     | 
    
         
            +
                  @directives = []
         
     | 
| 
      
 8 
     | 
    
         
            +
                  @args = nil
         
     | 
| 
      
 9 
     | 
    
         
            +
             
     | 
| 
      
 10 
     | 
    
         
            +
                  process_args
         
     | 
| 
      
 11 
     | 
    
         
            +
                end
         
     | 
| 
      
 12 
     | 
    
         
            +
             
     | 
| 
      
 13 
     | 
    
         
            +
                def process_args
         
     | 
| 
      
 14 
     | 
    
         
            +
                  @args = @raw_args.map do |arg|
         
     | 
| 
      
 15 
     | 
    
         
            +
                    case arg
         
     | 
| 
      
 16 
     | 
    
         
            +
                    when BaseCommand
         
     | 
| 
      
 17 
     | 
    
         
            +
                      raise NotImplementedError.new('Not ready for subshells yet')
         
     | 
| 
      
 18 
     | 
    
         
            +
                    when BaseDirective
         
     | 
| 
      
 19 
     | 
    
         
            +
                      @directives << arg
         
     | 
| 
      
 20 
     | 
    
         
            +
                      nil
         
     | 
| 
      
 21 
     | 
    
         
            +
                    else
         
     | 
| 
      
 22 
     | 
    
         
            +
                      arg.to_s
         
     | 
| 
      
 23 
     | 
    
         
            +
                    end
         
     | 
| 
      
 24 
     | 
    
         
            +
                  end.compact
         
     | 
| 
      
 25 
     | 
    
         
            +
                end
         
     | 
| 
      
 26 
     | 
    
         
            +
             
     | 
| 
      
 27 
     | 
    
         
            +
                def stringify
         
     | 
| 
      
 28 
     | 
    
         
            +
                  @raw_args.map do |arg|
         
     | 
| 
      
 29 
     | 
    
         
            +
                    stringify_arg(arg)
         
     | 
| 
      
 30 
     | 
    
         
            +
                  end.join(' ')
         
     | 
| 
      
 31 
     | 
    
         
            +
                end
         
     | 
| 
      
 32 
     | 
    
         
            +
             
     | 
| 
      
 33 
     | 
    
         
            +
                def |(other)
         
     | 
| 
      
 34 
     | 
    
         
            +
                  Pipeline.new([self, other])
         
     | 
| 
      
 35 
     | 
    
         
            +
                end
         
     | 
| 
      
 36 
     | 
    
         
            +
             
     | 
| 
      
 37 
     | 
    
         
            +
                def post_fork(runner, &blk)
         
     | 
| 
      
 38 
     | 
    
         
            +
                  extra_post_forks(runner) << blk
         
     | 
| 
      
 39 
     | 
    
         
            +
                end
         
     | 
| 
      
 40 
     | 
    
         
            +
             
     | 
| 
      
 41 
     | 
    
         
            +
                def set_stdout(runner, value)
         
     | 
| 
      
 42 
     | 
    
         
            +
                  directive = FD.new(:stdout) > value
         
     | 
| 
      
 43 
     | 
    
         
            +
                  add_directive(runner, directive)
         
     | 
| 
      
 44 
     | 
    
         
            +
                end
         
     | 
| 
      
 45 
     | 
    
         
            +
             
     | 
| 
      
 46 
     | 
    
         
            +
                def set_stdin(runner, value)
         
     | 
| 
      
 47 
     | 
    
         
            +
                  directive = FD.new(:stdin) < value
         
     | 
| 
      
 48 
     | 
    
         
            +
                  add_directive(runner, directive)
         
     | 
| 
      
 49 
     | 
    
         
            +
                end
         
     | 
| 
      
 50 
     | 
    
         
            +
             
     | 
| 
      
 51 
     | 
    
         
            +
                def status(runner)
         
     | 
| 
      
 52 
     | 
    
         
            +
                  state(runner)[:subprocess].status
         
     | 
| 
      
 53 
     | 
    
         
            +
                end
         
     | 
| 
      
 54 
     | 
    
         
            +
             
     | 
| 
      
 55 
     | 
    
         
            +
                def pid(runner)
         
     | 
| 
      
 56 
     | 
    
         
            +
                  state(runner)[:subprocess].pid
         
     | 
| 
      
 57 
     | 
    
         
            +
                end
         
     | 
| 
      
 58 
     | 
    
         
            +
             
     | 
| 
      
 59 
     | 
    
         
            +
                def prepare!(runner)
         
     | 
| 
      
 60 
     | 
    
         
            +
                  @directives.each {|directive| directive.prepare!(runner)}
         
     | 
| 
      
 61 
     | 
    
         
            +
                end
         
     | 
| 
      
 62 
     | 
    
         
            +
             
     | 
| 
      
 63 
     | 
    
         
            +
                def start_async(runner)
         
     | 
| 
      
 64 
     | 
    
         
            +
                  # Need to call this *after* we've set up pipeline
         
     | 
| 
      
 65 
     | 
    
         
            +
                  # PipeWrappers. Would prefer to call it in prepare!, but then
         
     | 
| 
      
 66 
     | 
    
         
            +
                  # we'd have to take care of closing the FDs in the parent
         
     | 
| 
      
 67 
     | 
    
         
            +
                  # process here anyway.
         
     | 
| 
      
 68 
     | 
    
         
            +
                  prepare_subprocess(runner)
         
     | 
| 
      
 69 
     | 
    
         
            +
                  state(runner)[:subprocess].run
         
     | 
| 
      
 70 
     | 
    
         
            +
                end
         
     | 
| 
      
 71 
     | 
    
         
            +
             
     | 
| 
      
 72 
     | 
    
         
            +
                def wait(runner)
         
     | 
| 
      
 73 
     | 
    
         
            +
                  state(runner)[:subprocess].wait
         
     | 
| 
      
 74 
     | 
    
         
            +
                end
         
     | 
| 
      
 75 
     | 
    
         
            +
             
     | 
| 
      
 76 
     | 
    
         
            +
                private
         
     | 
| 
      
 77 
     | 
    
         
            +
             
     | 
| 
      
 78 
     | 
    
         
            +
                def add_directive(runner, directive)
         
     | 
| 
      
 79 
     | 
    
         
            +
                  extra_directives(runner) << directive
         
     | 
| 
      
 80 
     | 
    
         
            +
                end
         
     | 
| 
      
 81 
     | 
    
         
            +
             
     | 
| 
      
 82 
     | 
    
         
            +
                def state(runner)
         
     | 
| 
      
 83 
     | 
    
         
            +
                  runner.state(self)
         
     | 
| 
      
 84 
     | 
    
         
            +
                end
         
     | 
| 
      
 85 
     | 
    
         
            +
             
     | 
| 
      
 86 
     | 
    
         
            +
                def extra_directives(runner)
         
     | 
| 
      
 87 
     | 
    
         
            +
                  state(runner)[:extra_directives] ||= []
         
     | 
| 
      
 88 
     | 
    
         
            +
                end
         
     | 
| 
      
 89 
     | 
    
         
            +
             
     | 
| 
      
 90 
     | 
    
         
            +
                def extra_post_forks(runner)
         
     | 
| 
      
 91 
     | 
    
         
            +
                  state(runner)[:extra_post_forks] ||= []
         
     | 
| 
      
 92 
     | 
    
         
            +
                end
         
     | 
| 
      
 93 
     | 
    
         
            +
             
     | 
| 
      
 94 
     | 
    
         
            +
                def prepare_subprocess(runner)
         
     | 
| 
      
 95 
     | 
    
         
            +
                  # extras first because they are currently only used for
         
     | 
| 
      
 96 
     | 
    
         
            +
                  # pipeline, which should not win out over internal redirects.
         
     | 
| 
      
 97 
     | 
    
         
            +
                  directives = extra_directives(runner) + @directives
         
     | 
| 
      
 98 
     | 
    
         
            +
                  post_forks = extra_post_forks(runner)
         
     | 
| 
      
 99 
     | 
    
         
            +
                  state(runner)[:subprocess] = Subprocess.new(args, directives, post_forks, runner)
         
     | 
| 
      
 100 
     | 
    
         
            +
                end
         
     | 
| 
      
 101 
     | 
    
         
            +
              end
         
     | 
| 
      
 102 
     | 
    
         
            +
            end
         
     | 
    
        data/lib/rubysh/error.rb
    ADDED
    
    | 
         @@ -0,0 +1,20 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            module Rubysh
         
     | 
| 
      
 2 
     | 
    
         
            +
              module Error
         
     | 
| 
      
 3 
     | 
    
         
            +
                class BaseError < Exception; end
         
     | 
| 
      
 4 
     | 
    
         
            +
             
     | 
| 
      
 5 
     | 
    
         
            +
                class ExecError < BaseError
         
     | 
| 
      
 6 
     | 
    
         
            +
                  # Exception klass and caller from the child process
         
     | 
| 
      
 7 
     | 
    
         
            +
                  attr_accessor :klass, :caller
         
     | 
| 
      
 8 
     | 
    
         
            +
             
     | 
| 
      
 9 
     | 
    
         
            +
                  def initialize(message, klass, caller)
         
     | 
| 
      
 10 
     | 
    
         
            +
                    super(message)
         
     | 
| 
      
 11 
     | 
    
         
            +
                    @klass = klass
         
     | 
| 
      
 12 
     | 
    
         
            +
                    @caller = caller
         
     | 
| 
      
 13 
     | 
    
         
            +
                  end
         
     | 
| 
      
 14 
     | 
    
         
            +
                end
         
     | 
| 
      
 15 
     | 
    
         
            +
             
     | 
| 
      
 16 
     | 
    
         
            +
                class UnreachableError < BaseError; end
         
     | 
| 
      
 17 
     | 
    
         
            +
                class AlreadyClosedError < BaseError; end
         
     | 
| 
      
 18 
     | 
    
         
            +
                class AlreadyRunError < BaseError; end
         
     | 
| 
      
 19 
     | 
    
         
            +
              end
         
     | 
| 
      
 20 
     | 
    
         
            +
            end
         
     | 
    
        data/lib/rubysh/fd.rb
    ADDED
    
    | 
         @@ -0,0 +1,43 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            module Rubysh
         
     | 
| 
      
 2 
     | 
    
         
            +
              class FD
         
     | 
| 
      
 3 
     | 
    
         
            +
                attr_accessor :fileno
         
     | 
| 
      
 4 
     | 
    
         
            +
             
     | 
| 
      
 5 
     | 
    
         
            +
                def initialize(fileno)
         
     | 
| 
      
 6 
     | 
    
         
            +
                  case fileno
         
     | 
| 
      
 7 
     | 
    
         
            +
                  when Integer
         
     | 
| 
      
 8 
     | 
    
         
            +
                    # pass
         
     | 
| 
      
 9 
     | 
    
         
            +
                  when :stdin
         
     | 
| 
      
 10 
     | 
    
         
            +
                    fileno = 0
         
     | 
| 
      
 11 
     | 
    
         
            +
                  when :stdout
         
     | 
| 
      
 12 
     | 
    
         
            +
                    fileno = 1
         
     | 
| 
      
 13 
     | 
    
         
            +
                  when :stderr
         
     | 
| 
      
 14 
     | 
    
         
            +
                    fileno = 2
         
     | 
| 
      
 15 
     | 
    
         
            +
                  else
         
     | 
| 
      
 16 
     | 
    
         
            +
                    raise Rubysh::Error::BaseError.new("Fileno must be an integer or one of :stdin, :stdout, :stderr, not #{fileno.inspect}")
         
     | 
| 
      
 17 
     | 
    
         
            +
                  end
         
     | 
| 
      
 18 
     | 
    
         
            +
             
     | 
| 
      
 19 
     | 
    
         
            +
                  @fileno = fileno
         
     | 
| 
      
 20 
     | 
    
         
            +
                end
         
     | 
| 
      
 21 
     | 
    
         
            +
             
     | 
| 
      
 22 
     | 
    
         
            +
                def >(target)
         
     | 
| 
      
 23 
     | 
    
         
            +
                  Redirect.new(self, '>', target)
         
     | 
| 
      
 24 
     | 
    
         
            +
                end
         
     | 
| 
      
 25 
     | 
    
         
            +
             
     | 
| 
      
 26 
     | 
    
         
            +
                def <(target)
         
     | 
| 
      
 27 
     | 
    
         
            +
                  Redirect.new(self, '<', target)
         
     | 
| 
      
 28 
     | 
    
         
            +
                end
         
     | 
| 
      
 29 
     | 
    
         
            +
             
     | 
| 
      
 30 
     | 
    
         
            +
                def <<
         
     | 
| 
      
 31 
     | 
    
         
            +
                  Rubysh.<<(self)
         
     | 
| 
      
 32 
     | 
    
         
            +
                end
         
     | 
| 
      
 33 
     | 
    
         
            +
             
     | 
| 
      
 34 
     | 
    
         
            +
                def to_s
         
     | 
| 
      
 35 
     | 
    
         
            +
                  "FD: #{@fileno}"
         
     | 
| 
      
 36 
     | 
    
         
            +
                end
         
     | 
| 
      
 37 
     | 
    
         
            +
             
     | 
| 
      
 38 
     | 
    
         
            +
                def ==(other)
         
     | 
| 
      
 39 
     | 
    
         
            +
                  self.class == other.class &&
         
     | 
| 
      
 40 
     | 
    
         
            +
                    self.fileno == other.fileno
         
     | 
| 
      
 41 
     | 
    
         
            +
                end
         
     | 
| 
      
 42 
     | 
    
         
            +
              end
         
     | 
| 
      
 43 
     | 
    
         
            +
            end
         
     | 
    
        data/lib/rubysh/pipe.rb
    ADDED
    
    
| 
         @@ -0,0 +1,70 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            module Rubysh
         
     | 
| 
      
 2 
     | 
    
         
            +
              class Pipeline < BaseCommand
         
     | 
| 
      
 3 
     | 
    
         
            +
                attr_accessor :pipeline
         
     | 
| 
      
 4 
     | 
    
         
            +
             
     | 
| 
      
 5 
     | 
    
         
            +
                def initialize(pipeline)
         
     | 
| 
      
 6 
     | 
    
         
            +
                  raise Rubysh::Error::BaseError.new("Cannot create an empty pipeline") if pipeline.length == 0
         
     | 
| 
      
 7 
     | 
    
         
            +
                  @pipeline = pipeline
         
     | 
| 
      
 8 
     | 
    
         
            +
                end
         
     | 
| 
      
 9 
     | 
    
         
            +
             
     | 
| 
      
 10 
     | 
    
         
            +
                # sh semantics are that your exitstatus is that of the last in the
         
     | 
| 
      
 11 
     | 
    
         
            +
                # pipeline
         
     | 
| 
      
 12 
     | 
    
         
            +
                def status(runner)
         
     | 
| 
      
 13 
     | 
    
         
            +
                  @pipeline[-1].status(runner)
         
     | 
| 
      
 14 
     | 
    
         
            +
                end
         
     | 
| 
      
 15 
     | 
    
         
            +
             
     | 
| 
      
 16 
     | 
    
         
            +
                def pid(runner)
         
     | 
| 
      
 17 
     | 
    
         
            +
                  @pipeline[-1].pid(runner)
         
     | 
| 
      
 18 
     | 
    
         
            +
                end
         
     | 
| 
      
 19 
     | 
    
         
            +
             
     | 
| 
      
 20 
     | 
    
         
            +
                def prepare!(runner)
         
     | 
| 
      
 21 
     | 
    
         
            +
                  @pipeline.each {|command| command.prepare!(runner)}
         
     | 
| 
      
 22 
     | 
    
         
            +
                end
         
     | 
| 
      
 23 
     | 
    
         
            +
             
     | 
| 
      
 24 
     | 
    
         
            +
                def pipeline_pairs
         
     | 
| 
      
 25 
     | 
    
         
            +
                  @pipeline[0...-1].zip(@pipeline[1..-1])
         
     | 
| 
      
 26 
     | 
    
         
            +
                end
         
     | 
| 
      
 27 
     | 
    
         
            +
             
     | 
| 
      
 28 
     | 
    
         
            +
                def stringify
         
     | 
| 
      
 29 
     | 
    
         
            +
                  @pipeline.map {|cmd| cmd.stringify}.join(' | ')
         
     | 
| 
      
 30 
     | 
    
         
            +
                end
         
     | 
| 
      
 31 
     | 
    
         
            +
             
     | 
| 
      
 32 
     | 
    
         
            +
                def |(other)
         
     | 
| 
      
 33 
     | 
    
         
            +
                  self.class.new(pipeline + [other])
         
     | 
| 
      
 34 
     | 
    
         
            +
                end
         
     | 
| 
      
 35 
     | 
    
         
            +
             
     | 
| 
      
 36 
     | 
    
         
            +
                def start_async(runner)
         
     | 
| 
      
 37 
     | 
    
         
            +
                  last_pipe = nil
         
     | 
| 
      
 38 
     | 
    
         
            +
             
     | 
| 
      
 39 
     | 
    
         
            +
                  pipeline_pairs.each do |left, right|
         
     | 
| 
      
 40 
     | 
    
         
            +
                    # TODO: maybe create an object to represent the pipe
         
     | 
| 
      
 41 
     | 
    
         
            +
                    # relationship, instead of manually assembling here.
         
     | 
| 
      
 42 
     | 
    
         
            +
                    #
         
     | 
| 
      
 43 
     | 
    
         
            +
                    # Don't want to have more than 2 pipes open at a time, so need
         
     | 
| 
      
 44 
     | 
    
         
            +
                    # to #run_async and #close here.
         
     | 
| 
      
 45 
     | 
    
         
            +
                    pipe = Subprocess::PipeWrapper.new
         
     | 
| 
      
 46 
     | 
    
         
            +
                    setup_pipe(runner, pipe, left, right)
         
     | 
| 
      
 47 
     | 
    
         
            +
             
     | 
| 
      
 48 
     | 
    
         
            +
                    left.start_async(runner)
         
     | 
| 
      
 49 
     | 
    
         
            +
                    last_pipe.close if last_pipe
         
     | 
| 
      
 50 
     | 
    
         
            +
                    last_pipe = pipe
         
     | 
| 
      
 51 
     | 
    
         
            +
                  end
         
     | 
| 
      
 52 
     | 
    
         
            +
             
     | 
| 
      
 53 
     | 
    
         
            +
                  @pipeline[-1].start_async(runner)
         
     | 
| 
      
 54 
     | 
    
         
            +
                  last_pipe.close if last_pipe
         
     | 
| 
      
 55 
     | 
    
         
            +
                end
         
     | 
| 
      
 56 
     | 
    
         
            +
             
     | 
| 
      
 57 
     | 
    
         
            +
                def setup_pipe(runner, pipe, left, right)
         
     | 
| 
      
 58 
     | 
    
         
            +
                  left.set_stdout(runner, pipe.writer)
         
     | 
| 
      
 59 
     | 
    
         
            +
                  right.set_stdin(runner, pipe.reader)
         
     | 
| 
      
 60 
     | 
    
         
            +
                end
         
     | 
| 
      
 61 
     | 
    
         
            +
             
     | 
| 
      
 62 
     | 
    
         
            +
                def wait(runner)
         
     | 
| 
      
 63 
     | 
    
         
            +
                  # It's likely we should actually wait for these in parallel; I'm
         
     | 
| 
      
 64 
     | 
    
         
            +
                  # not really sure right now. Might be tricky to avoid waiting
         
     | 
| 
      
 65 
     | 
    
         
            +
                  # for other processes run by this program (could probably use
         
     | 
| 
      
 66 
     | 
    
         
            +
                  # process groups for that?)
         
     | 
| 
      
 67 
     | 
    
         
            +
                  @pipeline.each {|cmd| cmd.wait(runner)}
         
     | 
| 
      
 68 
     | 
    
         
            +
                end
         
     | 
| 
      
 69 
     | 
    
         
            +
              end
         
     | 
| 
      
 70 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -0,0 +1,181 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            module Rubysh
         
     | 
| 
      
 2 
     | 
    
         
            +
              # Note that in bash, the semantics of redirection appear to be
         
     | 
| 
      
 3 
     | 
    
         
            +
              # following (tested empirically, rather than reading a spec):
         
     | 
| 
      
 4 
     | 
    
         
            +
              #
         
     | 
| 
      
 5 
     | 
    
         
            +
              # - [a<&b] and [a>&b] mean the same thing: copy FD b to a
         
     | 
| 
      
 6 
     | 
    
         
            +
              #   (try 'echo test 3>/tmp/testing.txt 1<&3')
         
     | 
| 
      
 7 
     | 
    
         
            +
              # - [a<&a] appears to be a no-op: ls /dev/fd 9<&9
         
     | 
| 
      
 8 
     | 
    
         
            +
              # - If b != a is an invalid file descriptor, then [a>&b] throws an
         
     | 
| 
      
 9 
     | 
    
         
            +
              #   error.
         
     | 
| 
      
 10 
     | 
    
         
            +
              # - Pathnames can only be on the right-hand side of a redirect.
         
     | 
| 
      
 11 
     | 
    
         
            +
              class Redirect < BaseDirective
         
     | 
| 
      
 12 
     | 
    
         
            +
                VALID_DIRECTIONS = ['<', '>', '>>']
         
     | 
| 
      
 13 
     | 
    
         
            +
             
     | 
| 
      
 14 
     | 
    
         
            +
                attr_accessor :source, :direction, :target
         
     | 
| 
      
 15 
     | 
    
         
            +
             
     | 
| 
      
 16 
     | 
    
         
            +
                def initialize(source, direction, target)
         
     | 
| 
      
 17 
     | 
    
         
            +
                  unless VALID_DIRECTIONS.include?(direction)
         
     | 
| 
      
 18 
     | 
    
         
            +
                    raise Rubysh::Error::BaseError.new("Direction must be one of #{VALID_DIRECTIONS.join(', ')}, not #{direction.inspect}")
         
     | 
| 
      
 19 
     | 
    
         
            +
                  end
         
     | 
| 
      
 20 
     | 
    
         
            +
             
     | 
| 
      
 21 
     | 
    
         
            +
                  unless source.kind_of?(IO) || source.kind_of?(FD) || source.kind_of?(Integer)
         
     | 
| 
      
 22 
     | 
    
         
            +
                    raise Rubysh::Error::BaseError.new("Invalid source: #{source.inspect}. Source must be an IO, a Rubysh::FD, or an Integer.")
         
     | 
| 
      
 23 
     | 
    
         
            +
                  end
         
     | 
| 
      
 24 
     | 
    
         
            +
             
     | 
| 
      
 25 
     | 
    
         
            +
                  unless target.respond_to?(:fileno) || target.kind_of?(Integer) || target.kind_of?(String) || target.kind_of?(Symbol)
         
     | 
| 
      
 26 
     | 
    
         
            +
                    raise Rubysh::Error::BaseError.new("Invalid target: #{target.inspect}. Target must respond to :fileno or be an Integer, a String, or a Symbol.")
         
     | 
| 
      
 27 
     | 
    
         
            +
                  end
         
     | 
| 
      
 28 
     | 
    
         
            +
             
     | 
| 
      
 29 
     | 
    
         
            +
                  @source = source
         
     | 
| 
      
 30 
     | 
    
         
            +
                  @target = target
         
     | 
| 
      
 31 
     | 
    
         
            +
                  @direction = direction
         
     | 
| 
      
 32 
     | 
    
         
            +
                end
         
     | 
| 
      
 33 
     | 
    
         
            +
             
     | 
| 
      
 34 
     | 
    
         
            +
                def printable_source
         
     | 
| 
      
 35 
     | 
    
         
            +
                  Util.to_fileno(source)
         
     | 
| 
      
 36 
     | 
    
         
            +
                end
         
     | 
| 
      
 37 
     | 
    
         
            +
             
     | 
| 
      
 38 
     | 
    
         
            +
                def printable_target
         
     | 
| 
      
 39 
     | 
    
         
            +
                  case target
         
     | 
| 
      
 40 
     | 
    
         
            +
                  when Symbol
         
     | 
| 
      
 41 
     | 
    
         
            +
                    target.inspect
         
     | 
| 
      
 42 
     | 
    
         
            +
                  else
         
     | 
| 
      
 43 
     | 
    
         
            +
                    Util.to_fileno(target)
         
     | 
| 
      
 44 
     | 
    
         
            +
                  end
         
     | 
| 
      
 45 
     | 
    
         
            +
                end
         
     | 
| 
      
 46 
     | 
    
         
            +
             
     | 
| 
      
 47 
     | 
    
         
            +
                # TODO: support files
         
     | 
| 
      
 48 
     | 
    
         
            +
                def stringify
         
     | 
| 
      
 49 
     | 
    
         
            +
                  source_file = printable_source
         
     | 
| 
      
 50 
     | 
    
         
            +
                  target_file = printable_target
         
     | 
| 
      
 51 
     | 
    
         
            +
             
     | 
| 
      
 52 
     | 
    
         
            +
                  case direction
         
     | 
| 
      
 53 
     | 
    
         
            +
                  when '<', '>>'
         
     | 
| 
      
 54 
     | 
    
         
            +
                    source_file = nil if source_file == 0
         
     | 
| 
      
 55 
     | 
    
         
            +
                  when '>'
         
     | 
| 
      
 56 
     | 
    
         
            +
                    source_file = nil if source_file == 1
         
     | 
| 
      
 57 
     | 
    
         
            +
                  else
         
     | 
| 
      
 58 
     | 
    
         
            +
                    raise Rubysh::Error::BaseError.new("Unrecognized direction: #{direction.inspect}")
         
     | 
| 
      
 59 
     | 
    
         
            +
                  end
         
     | 
| 
      
 60 
     | 
    
         
            +
             
     | 
| 
      
 61 
     | 
    
         
            +
                  ampersand = target_file.kind_of?(Integer) ? '&' : nil
         
     | 
| 
      
 62 
     | 
    
         
            +
             
     | 
| 
      
 63 
     | 
    
         
            +
                  "#{source_file}#{direction}#{ampersand}#{target_file}"
         
     | 
| 
      
 64 
     | 
    
         
            +
                end
         
     | 
| 
      
 65 
     | 
    
         
            +
             
     | 
| 
      
 66 
     | 
    
         
            +
                def to_s
         
     | 
| 
      
 67 
     | 
    
         
            +
                  "Redirect: #{stringify}"
         
     | 
| 
      
 68 
     | 
    
         
            +
                end
         
     | 
| 
      
 69 
     | 
    
         
            +
             
     | 
| 
      
 70 
     | 
    
         
            +
                def ==(other)
         
     | 
| 
      
 71 
     | 
    
         
            +
                  self.class == other.class &&
         
     | 
| 
      
 72 
     | 
    
         
            +
                    self.printable_source == other.printable_source &&
         
     | 
| 
      
 73 
     | 
    
         
            +
                    self.printable_target == other.printable_target
         
     | 
| 
      
 74 
     | 
    
         
            +
                end
         
     | 
| 
      
 75 
     | 
    
         
            +
             
     | 
| 
      
 76 
     | 
    
         
            +
                def reading?
         
     | 
| 
      
 77 
     | 
    
         
            +
                  direction == '<'
         
     | 
| 
      
 78 
     | 
    
         
            +
                end
         
     | 
| 
      
 79 
     | 
    
         
            +
             
     | 
| 
      
 80 
     | 
    
         
            +
                def target_reading?
         
     | 
| 
      
 81 
     | 
    
         
            +
                  !reading?
         
     | 
| 
      
 82 
     | 
    
         
            +
                end
         
     | 
| 
      
 83 
     | 
    
         
            +
             
     | 
| 
      
 84 
     | 
    
         
            +
                def writing?
         
     | 
| 
      
 85 
     | 
    
         
            +
                  !reading?
         
     | 
| 
      
 86 
     | 
    
         
            +
                end
         
     | 
| 
      
 87 
     | 
    
         
            +
             
     | 
| 
      
 88 
     | 
    
         
            +
                def target_writing?
         
     | 
| 
      
 89 
     | 
    
         
            +
                  !writing?
         
     | 
| 
      
 90 
     | 
    
         
            +
                end
         
     | 
| 
      
 91 
     | 
    
         
            +
             
     | 
| 
      
 92 
     | 
    
         
            +
                def truncate?
         
     | 
| 
      
 93 
     | 
    
         
            +
                  direction == '>'
         
     | 
| 
      
 94 
     | 
    
         
            +
                end
         
     | 
| 
      
 95 
     | 
    
         
            +
             
     | 
| 
      
 96 
     | 
    
         
            +
                def named_target?
         
     | 
| 
      
 97 
     | 
    
         
            +
                  target.kind_of?(Symbol)
         
     | 
| 
      
 98 
     | 
    
         
            +
                end
         
     | 
| 
      
 99 
     | 
    
         
            +
             
     | 
| 
      
 100 
     | 
    
         
            +
                def target_name
         
     | 
| 
      
 101 
     | 
    
         
            +
                  raise Rubysh::Error::BaseError.new("Not a named target") unless named_target?
         
     | 
| 
      
 102 
     | 
    
         
            +
                  target
         
     | 
| 
      
 103 
     | 
    
         
            +
                end
         
     | 
| 
      
 104 
     | 
    
         
            +
             
     | 
| 
      
 105 
     | 
    
         
            +
                def prepare!(runner)
         
     | 
| 
      
 106 
     | 
    
         
            +
                  prepare_target(runner)
         
     | 
| 
      
 107 
     | 
    
         
            +
                end
         
     | 
| 
      
 108 
     | 
    
         
            +
             
     | 
| 
      
 109 
     | 
    
         
            +
                def prepare_target(runner)
         
     | 
| 
      
 110 
     | 
    
         
            +
                  return unless named_target?
         
     | 
| 
      
 111 
     | 
    
         
            +
                  targets = runner.targets
         
     | 
| 
      
 112 
     | 
    
         
            +
                  if targets.include?(target_name)
         
     | 
| 
      
 113 
     | 
    
         
            +
                    raise Rubysh::Error::BaseError.new("#{runner} already has a named target: #{target_name.inspect}")
         
     | 
| 
      
 114 
     | 
    
         
            +
                  end
         
     | 
| 
      
 115 
     | 
    
         
            +
             
     | 
| 
      
 116 
     | 
    
         
            +
                  pipe = Subprocess::PipeWrapper.new
         
     | 
| 
      
 117 
     | 
    
         
            +
             
     | 
| 
      
 118 
     | 
    
         
            +
                  targets[target_name] = {
         
     | 
| 
      
 119 
     | 
    
         
            +
                    :target_reading? => target_reading?,
         
     | 
| 
      
 120 
     | 
    
         
            +
                    :target => target_reading? ? pipe.reader : pipe.writer,
         
     | 
| 
      
 121 
     | 
    
         
            +
                    :complement => target_reading? ? pipe.writer : pipe.reader,
         
     | 
| 
      
 122 
     | 
    
         
            +
                    :buffer => []
         
     | 
| 
      
 123 
     | 
    
         
            +
                  }
         
     | 
| 
      
 124 
     | 
    
         
            +
                end
         
     | 
| 
      
 125 
     | 
    
         
            +
             
     | 
| 
      
 126 
     | 
    
         
            +
                # E.g. Rubysh.stdin < :stdin
         
     | 
| 
      
 127 
     | 
    
         
            +
                def apply_parent!(runner)
         
     | 
| 
      
 128 
     | 
    
         
            +
                  return unless named_target?
         
     | 
| 
      
 129 
     | 
    
         
            +
                  target_state = runner.target_state(target_name)
         
     | 
| 
      
 130 
     | 
    
         
            +
                  target_state[:complement].close
         
     | 
| 
      
 131 
     | 
    
         
            +
                end
         
     | 
| 
      
 132 
     | 
    
         
            +
             
     | 
| 
      
 133 
     | 
    
         
            +
                def apply!(runner)
         
     | 
| 
      
 134 
     | 
    
         
            +
                  Rubysh.log.info("About to apply #{self} for #{$$}")
         
     | 
| 
      
 135 
     | 
    
         
            +
             
     | 
| 
      
 136 
     | 
    
         
            +
                  # Open the target
         
     | 
| 
      
 137 
     | 
    
         
            +
                  target_io = file_as_io(runner, target)
         
     | 
| 
      
 138 
     | 
    
         
            +
             
     | 
| 
      
 139 
     | 
    
         
            +
                  target_fd = Util.to_fileno(target_io)
         
     | 
| 
      
 140 
     | 
    
         
            +
                  source_fd = Util.to_fileno(source)
         
     | 
| 
      
 141 
     | 
    
         
            +
             
     | 
| 
      
 142 
     | 
    
         
            +
                  # Copy target -> source
         
     | 
| 
      
 143 
     | 
    
         
            +
                  Util.dup2(target_fd, source_fd)
         
     | 
| 
      
 144 
     | 
    
         
            +
                  Util.set_cloexec(source_fd, false)
         
     | 
| 
      
 145 
     | 
    
         
            +
                end
         
     | 
| 
      
 146 
     | 
    
         
            +
             
     | 
| 
      
 147 
     | 
    
         
            +
                private
         
     | 
| 
      
 148 
     | 
    
         
            +
             
     | 
| 
      
 149 
     | 
    
         
            +
                # If providing your own open FD, you have to set cloexec yourself.
         
     | 
| 
      
 150 
     | 
    
         
            +
                def file_as_io(runner, file, default_to_cloexec=true)
         
     | 
| 
      
 151 
     | 
    
         
            +
                  return file if file.kind_of?(IO)
         
     | 
| 
      
 152 
     | 
    
         
            +
                  # If it's an FD, canonicalize to the FD number
         
     | 
| 
      
 153 
     | 
    
         
            +
                  file = Util.to_fileno(file)
         
     | 
| 
      
 154 
     | 
    
         
            +
             
     | 
| 
      
 155 
     | 
    
         
            +
                  if file.kind_of?(Integer)
         
     | 
| 
      
 156 
     | 
    
         
            +
                    io = Util.io_without_autoclose(file)
         
     | 
| 
      
 157 
     | 
    
         
            +
                    # Someone else opened
         
     | 
| 
      
 158 
     | 
    
         
            +
                    default_to_cloexec = false
         
     | 
| 
      
 159 
     | 
    
         
            +
                  elsif file.kind_of?(String) && reading?
         
     | 
| 
      
 160 
     | 
    
         
            +
                    io = File.open(file)
         
     | 
| 
      
 161 
     | 
    
         
            +
                  elsif file.kind_of?(String) && writing? && truncate?
         
     | 
| 
      
 162 
     | 
    
         
            +
                    # Make the following cases explicit for future compatability
         
     | 
| 
      
 163 
     | 
    
         
            +
                    # (also to make it clear on an exception which case is at
         
     | 
| 
      
 164 
     | 
    
         
            +
                    # fault).
         
     | 
| 
      
 165 
     | 
    
         
            +
                    io = File.open(file, 'w')
         
     | 
| 
      
 166 
     | 
    
         
            +
                  elsif file.kind_of?(String) && writing? && !truncate?
         
     | 
| 
      
 167 
     | 
    
         
            +
                    io = File.open(file, 'a')
         
     | 
| 
      
 168 
     | 
    
         
            +
                  elsif file.kind_of?(Symbol)
         
     | 
| 
      
 169 
     | 
    
         
            +
                    target_state = runner.target_state(file)
         
     | 
| 
      
 170 
     | 
    
         
            +
                    io = target_state[:complement]
         
     | 
| 
      
 171 
     | 
    
         
            +
                    # Someone else opened
         
     | 
| 
      
 172 
     | 
    
         
            +
                    default_to_cloexec = false
         
     | 
| 
      
 173 
     | 
    
         
            +
                  else
         
     | 
| 
      
 174 
     | 
    
         
            +
                    raise Rubysh::Error::BaseError.new("Unrecognized file spec: #{file.inspect}")
         
     | 
| 
      
 175 
     | 
    
         
            +
                  end
         
     | 
| 
      
 176 
     | 
    
         
            +
             
     | 
| 
      
 177 
     | 
    
         
            +
                  io.fcntl(Fcntl::F_SETFD, Fcntl::FD_CLOEXEC) if default_to_cloexec
         
     | 
| 
      
 178 
     | 
    
         
            +
                  io
         
     | 
| 
      
 179 
     | 
    
         
            +
                end
         
     | 
| 
      
 180 
     | 
    
         
            +
              end
         
     | 
| 
      
 181 
     | 
    
         
            +
            end
         
     |