IO::Like - in the Likeness of IO

The IO::Like module provides all of the methods of typical IO implementations such as File; most importantly the read, write, and seek series of methods. A class which includes IO::Like needs to provide only a few methods in order to enable the higher level methods. Buffering is automatically provided by default for the methods which normally provide it in IO.

See the documentation for IO::Like for more details regarding the necessary methods.

License

Copyright © 2008,2009 Jeremy Bopp <jeremy at bopp dot net>

Licensed under the same terms as Ruby – See the included LICENSE file for details

Some parts licensed under the same terms as the rubyspec project – See the included LEGAL and LICENSE.rubyspec files for details

Installation/Removal

Download the GEM file and install it with:

% sudo gem install io-like-VERSION.gem

or directly with:

% sudo gem install io-like

Removal is the same in either case:

% sudo gem uninstall io-like

Example

More examples can be found in the examples directory of the source distribution.

A simple ROT13 codec:

gem 'io-like'  # Use require_gem for rubygems versions older than 0.9.0.
require 'io/like'

class ROT13Filter
  include IO::Like

  def self.open(delegate_io)
    filter = new(delegate_io)
    return filter unless block_given?

    begin
      yield(filter)
    ensure
      filter.close unless filter.closed?
    end
  end

  def initialize(delegate_io)
    @delegate_io = delegate_io
  end

  private

  def encode_rot13(string)
    result = string.dup
    0.upto(result.length) do |i|
      case result[i]
      when 65..90
        result[i] = (result[i] - 52) % 26 + 65
      when 97..122
        result[i] = (result[i] - 84) % 26 + 97
      end
    end
    result
  end

  def unbuffered_read(length)
    encode_rot13(@delegate_io.sysread(length))
  end

  def unbuffered_seek(offset, whence = IO::SEEK_SET)
    @delegate_io.sysseek(offset, whence)
  end

  def unbuffered_write(string)
    @delegate_io.syswrite(encode_rot13(string))
  end
end

File.open('normal_file.txt', 'w') do |f|
  f.puts('This is a test')
end

File.open('rot13_file.txt', 'w') do |f|
  ROT13Filter.open(f) do |rot13|
    rot13.puts('This is a test')
  end
end

File.open('normal_file.txt') do |f|
  ROT13Filter.open(f) do |rot13|
    puts(rot13.read)                      # -> Guvf vf n grfg
  end
end

File.open('rot13_file.txt') do |f|
  ROT13Filter.open(f) do |rot13|
    puts(rot13.read)                      # -> This is a test
  end
end

File.open('normal_file.txt') do |f|
  ROT13Filter.open(f) do |rot13|
    rot13.pos = 5
    puts(rot13.read)                      # -> vf n grfg
  end
end

File.open('rot13_file.txt') do |f|
  ROT13Filter.open(f) do |rot13|
    rot13.pos = 5
    puts(rot13.read)                      # -> is a test
  end
end

File.open('normal_file.txt') do |f|
  ROT13Filter.open(f) do |rot13|
    ROT13Filter.open(rot13) do |rot26|    # ;-)
      puts(rot26.read)                    # -> This is a test
    end
  end
end

Known Bugs/Limitations

  1. Only up to version 1.8.6 of Ruby's IO interface is implemented. Version 1.8.7 and eventually 1.9.0/2.0.0 support are coming soon.

  2. Ruby's finalization capabilities fall a bit short in a few respects, and as a result, it is impossible to cause the close, close_read, or close_write methods to be called automatically when an including class is garbage collected. Define a class open method in the manner of File.open which guarantees that an appropriate close method will be called after executing a block. Other than that, be diligent about calling the close methods.

Contributing

Contributions for bug fixes, documentation, extensions, tests, etc. are encouraged. Please read the file HACKING for details.