public
Last active — forked from rklemme/tee.rb

  • Download Gist
tee.rb
Ruby
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124
#!/usr/bin/env ruby19
 
require 'optparse'
 
# Report erros that are caused by invoking a method when
# an instance is not in the proper state to do so.
class StateError < Exception
end
 
# Instances of class Tee can send output to several destinations
# at the same time much like the command line utility 'tee'.
# Every IO stream that reports an error is silently closed and
# not written to any more.
class Tee
 
# Open the given files for writing in the given mode
# (see #initialize). If the mode is omitted it defaults
# to "w" (overwrite). There are two operation modes of
# this method:
# * When invoked with a block the tee
# instance is yielded to that block and closed when the
# block is terminated. In this case return value is result
# of the block evaluation.
# * Without a block the newly created and opened Tee instance
# is returned
def self.open(file_names, mode = "w")
tee = new(file_names, mode)
tee.open
 
if block_given?
begin
yield tee
ensure
tee.close
end
else
tee
end
end
 
# Initializes a Tee instance with a list of file names and
# an IO mode which must be any of "w", "wb", "a" or "ab".
def initialize(file_names, mode)
raise ArgumentError, "Wrong mode %p" % mode unless /\A[wa]b?\z/i =~ mode
@file_names = file_names
@mode = mode
end
 
# Opens all the files for writing. If files are opened
# already nothing happens.
# returns self
def open
raise StateError, "Already open" if @ios
 
@ios = @file_names.map do |name|
File.open(name, @mode) rescue nil
end.compact
 
self
end
 
# All files are closed. If they are not opened this is
# a nop.
def close
if @ios
@ios.each {|io| io.close}
@ios = nil
end
end
 
# Invokes 'puts' on all streams with the given arguments.
# returns self.
def puts(*args) delegate(:puts, args) end
# Writes the given argument to all streams via IO#write.
# returns self.
def write(str) delegate(:write, str) end
 
# Appends argument to all files via IO#<<.
# returns self.
def <<(arg) delegate(:<<, arg) end
 
private
 
# Send the given method with the given arguments
# to all IO instances. Every instance which has
# errors is closed and removed from the list.
def delegate(meth, args)
@ios.delete_if do |io|
ex = nil
begin
io.send(meth, *args)
rescue SystemCallError => ex
io.close rescue nil
end
ex
end
 
self
end
end
 
# start of MAIN
file_mode = "w"
 
OptionParser.new do |opts|
opts.on "-a", "--append",
"Appends to files instead of overwriting them" do
file_mode = "a"
end
opts.on_tail "-h", "--help",
"Print this help" do
puts opts
exit 0
end
end.parse! ARGV
 
Tee.open ARGV, file_mode do |tee|
$stdin.each do |line|
tee.puts(line)
$stdout.puts(line)
end
end

Please sign in to comment on this gist.

Something went wrong with that request. Please try again.