Thanks for the kind response.

When I said the test case failed, I meant the actually output our
resulting output encodeing the line has trailing space at the end of a
line. We both escape trailing spaces before we break lines - if the
line breaking moves some code is that not an issue? (the continuation
= might mean that it is not).

Yup there was an issue with masks I fixed that and removed the globals
(my perl just throwing in a $ when in doubt :-) There was also a bug
in the command line driver, which I have fixed. The patched code
follows

> (/(?:(?:[^\n]{74}(?==[\dA-F]{2}))|(?:[^\n]{0,76}(?=\n))|(?:[^\n]{1,75}(?!\n{2})))(?:#{$/}*)/)
> makes you look like a Perl 5 junkie, 

I did this to allow the use of a gsub, which is much faster than the
looping solution. The look aheads and general uglyness handle the
special cases. I probably should use /x and space it out and comment,
but when I am in the regexp zone, I know what I am typing <grin>.

#
# == Synopsis
#
# Ruby Quiz #23
#
# The quoted printable encoding is used in primarily in email, thought it has
# recently seen some use in XML areas as well. The encoding is simple to
# translate to and from.
#
# This week's quiz is to build a filter that handles quoted printable
# translation.
#
# Your script should be a standard Unix filter, reading from files listed on
# the command-line or STDIN and writing to STDOUT. In normal operation, the
# script should encode all text read in the quoted printable format. However,
# your script should also support a -d command-line option and when present,
# text should be decoded from quoted printable instead. Finally, your script
# should understand a -x command-line option and when given, it should encode
# <, > and & for use with XML.
#
# == Usage
#
#    ruby quiz23.rb [-d | --decode ] [ -x | --xml ]
#
# == Author
# Patrick Hurley, Cornell-Mayo Assoc
#
# == Copyright
# Copytright (c) 2005 Cornell-Mayo Assoc
# Licensed under the same terms as Ruby.
#

require 'optparse'
require 'rdoc/usage'

module QuotedPrintable
  MAX_LINE_PRINTABLE_ENCODE_LENGTH = 76

  def from_qp
    result = self.gsub(/=\r\n/, "")
    result.gsub!(/\r\n/m, $/)
    result.gsub!(/=([\dA-F]{2})/) { $1.hex.chr }
    result
  end

  def to_qp(handle_xml = false)
    char_mask = if (handle_xml)
                  /[\x00-\x08\x0b-\x1f\x7f-\xff=<>&]/
                else
                  /[\x00-\x08\x0b-\x1f\x7f-\xff=]/
                end

    # encode the non-space characters
    result = self.gsub(char_mask)  { |ch| "=%02X" % ch[0] }
    # encode the last space character at end of line
    result.gsub!(/(\s)(?=#{$/})/o) { |ch| "=%02X" % ch[0] }

    lines = result.scan(/(?:(?:[^\n]{74}(?==[\dA-F]{2}))|(?:[^\n]{0,76}(?=\n))|(?:[^\n]{1,75}(?!\n{2})))(?:#{$/}*)/);
    lines.join("=\n").gsub(/#{$/}/m, "\r\n")
  end

  def QuotedPrintable.encode(handle_xml=false)
    STDOUT.binmode
    while (line = gets) do
      print line.to_qp(handle_xml)
    end
  end

  def QuotedPrintable.decode
    STDIN.binmode
    while (line = gets) do
      # I am a ruby newbie, and I could
      # not get gets to get the \r\n pairs
      # no matter how I set $/ - any pointers?
      line = line.chomp + "\r\n"
      print line.from_qp
    end
  end

end

class String
  include QuotedPrintable
end

if __FILE__ == $0

  decode = false
  handle_xml = true
  opts = OptionParser.new
  opts.on("-h", "--help")   { RDoc::usage; }
  opts.on("-d", "--decode") { decode = true }
  opts.on("-x", "--xml")    { handle_xml = true }

  opts.parse!(ARGV) rescue RDoc::usage('usage')

  if (decode)
    QuotedPrintable.decode()
  else
    QuotedPrintable.encode(handle_xml)
  end
end