= 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.
