Class: Rubysh::Redirect

Inherits:
BaseDirective show all
Defined in:
lib/rubysh/redirect.rb

Overview

Note that in bash, the semantics of redirection appear to be following (tested empirically, rather than reading a spec):

  • a<&b

    and [a>&b] mean the same thing: copy FD b to a

    (try ‘echo test 3>/tmp/testing.txt 1<&3’)

  • a<&a

    appears to be a no-op: ls /dev/fd 9<&9

  • If b != a is an invalid file descriptor, then [a>&b] throws an error.

  • Pathnames can only be on the right-hand side of a redirect.

Constant Summary collapse

VALID_DIRECTIONS =
['<', '>', '>>']

Instance Attribute Summary collapse

Instance Method Summary collapse

Methods inherited from BaseDirective

#state

Constructor Details

#initialize(source, direction, target, opts = nil) ⇒ Redirect

Returns a new instance of Redirect.



18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
# File 'lib/rubysh/redirect.rb', line 18

def initialize(source, direction, target, opts=nil)
  unless VALID_DIRECTIONS.include?(direction)
    raise Rubysh::Error::BaseError.new("Direction must be one of #{VALID_DIRECTIONS.join(', ')}, not #{direction.inspect}")
  end

  unless source.kind_of?(IO) || source.kind_of?(FD) || source.kind_of?(Integer)
    raise Rubysh::Error::BaseError.new("Invalid source: #{source.inspect}. Source must be an IO, a Rubysh::FD, or an Integer.")
  end

  unless target.respond_to?(:fileno) || target.kind_of?(Integer) || target.kind_of?(String) || target.kind_of?(Symbol)
    raise Rubysh::Error::BaseError.new("Invalid target: #{target.inspect}. Target must respond to :fileno or be an Integer, a String, or a Symbol.")
  end

  @source = source
  @target = target
  @direction = direction
  @opts = opts || {}
end

Instance Attribute Details

#directionObject

Returns the value of attribute direction.



16
17
18
# File 'lib/rubysh/redirect.rb', line 16

def direction
  @direction
end

#sourceObject

Returns the value of attribute source.



16
17
18
# File 'lib/rubysh/redirect.rb', line 16

def source
  @source
end

#targetObject

Returns the value of attribute target.



16
17
18
# File 'lib/rubysh/redirect.rb', line 16

def target
  @target
end

Instance Method Details

#==(other) ⇒ Object



73
74
75
76
77
# File 'lib/rubysh/redirect.rb', line 73

def ==(other)
  self.class == other.class &&
    self.printable_source == other.printable_source &&
    self.printable_target == other.printable_target
end

#apply!(runner) ⇒ Object



142
143
144
145
146
147
148
149
150
151
152
153
154
# File 'lib/rubysh/redirect.rb', line 142

def apply!(runner)
  Rubysh.log.info("About to apply #{self} for #{$$}")

  # Open the target
  target_io = file_as_io(runner, target)

  target_fd = Util.to_fileno(target_io)
  source_fd = Util.to_fileno(source)

  # Copy target -> source
  Util.dup2(target_fd, source_fd)
  Util.set_cloexec(source_fd, false)
end

#apply_parent!(runner) ⇒ Object

E.g. Rubysh.stdin < :stdin



136
137
138
139
140
# File 'lib/rubysh/redirect.rb', line 136

def apply_parent!(runner)
  return unless named_target?
  target_state = runner.target_state(target_name)
  target_state[:complement].close
end

#named_target?Boolean

Returns:

  • (Boolean)


99
100
101
# File 'lib/rubysh/redirect.rb', line 99

def named_target?
  target.kind_of?(Symbol)
end

#prepare!(runner) ⇒ Object



108
109
110
# File 'lib/rubysh/redirect.rb', line 108

def prepare!(runner)
  prepare_target(runner)
end

#prepare_target(runner) ⇒ Object



112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
# File 'lib/rubysh/redirect.rb', line 112

def prepare_target(runner)
  return unless named_target?
  targets = runner.targets
  if targets.include?(target_name)
    raise Rubysh::Error::BaseError.new("#{runner} already has a named target: #{target_name.inspect}")
  end

  pipe = Subprocess::PipeWrapper.new

  targets[target_name] = {
    :target_reading? => target_reading?,
    :target => target_reading? ? pipe.reader : pipe.writer,
    :complement => target_reading? ? pipe.writer : pipe.reader,
    :buffer => StringIO.new,
    :target_name => target_name,
    :read_pos => 0,
    :subprocess_fd_number => Util.to_fileno(source),
    :tee => @opts[:tee],
    :on_read => @opts[:on_read],
    :on_write => @opts[:on_write],
  }
end

#printable_sourceObject



37
38
39
# File 'lib/rubysh/redirect.rb', line 37

def printable_source
  Util.to_fileno(source)
end

#printable_targetObject



41
42
43
44
45
46
47
48
# File 'lib/rubysh/redirect.rb', line 41

def printable_target
  case target
  when Symbol
    target.inspect
  else
    Util.to_fileno(target)
  end
end

#reading?Boolean

Returns:

  • (Boolean)


79
80
81
# File 'lib/rubysh/redirect.rb', line 79

def reading?
  direction == '<'
end

#stringifyObject

TODO: support files



51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
# File 'lib/rubysh/redirect.rb', line 51

def stringify
  source_file = printable_source
  target_file = printable_target

  case direction
  when '<', '>>'
    source_file = nil if source_file == 0
  when '>'
    source_file = nil if source_file == 1
  else
    raise Rubysh::Error::BaseError.new("Unrecognized direction: #{direction.inspect}")
  end

  ampersand = target_file.kind_of?(Integer) ? '&' : nil

  "#{source_file}#{direction}#{ampersand}#{target_file}"
end

#target_nameObject



103
104
105
106
# File 'lib/rubysh/redirect.rb', line 103

def target_name
  raise Rubysh::Error::BaseError.new("Not a named target") unless named_target?
  target
end

#target_reading?Boolean

Returns:

  • (Boolean)


83
84
85
# File 'lib/rubysh/redirect.rb', line 83

def target_reading?
  !reading?
end

#target_writing?Boolean

Returns:

  • (Boolean)


91
92
93
# File 'lib/rubysh/redirect.rb', line 91

def target_writing?
  !writing?
end

#to_sObject



69
70
71
# File 'lib/rubysh/redirect.rb', line 69

def to_s
  "Redirect: #{stringify}"
end

#truncate?Boolean

Returns:

  • (Boolean)


95
96
97
# File 'lib/rubysh/redirect.rb', line 95

def truncate?
  direction == '>'
end

#writing?Boolean

Returns:

  • (Boolean)


87
88
89
# File 'lib/rubysh/redirect.rb', line 87

def writing?
  !reading?
end