Skip to content

Instantly share code, notes, and snippets.

@johan--
Forked from rklemme/tee.rb
Created July 16, 2010 08:22
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save johan--/478116 to your computer and use it in GitHub Desktop.
Save johan--/478116 to your computer and use it in GitHub Desktop.
#!/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
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment