Better support for RFC 1521 quoted-printable specification, along with
addition of interface for consistency with base64 module.  Namely,

encodestring(), decodestring(): New functions which accept a string
object and return a string object.  They just wrap the string in
StringIOs and pass them to the encode() and decode() methods
respectively.  encodestring() accepts a default argument of quotetabs,
defaulting to zero, which is passed on straight through to encode().

encode(): Fix the bug where an extra newline would always be added to
the output, which prevented an idempotent roundtrip through
encode->decode.  Now, if the source string doesn't end in a newline,
then the result string won't end in a newline.

Also, extend the quotetabs argument semantics to include quoting
embedded strings, which is also optional according to the RFC.

test() -> main()

"from quopri import *" also imports encodestring() and decodestring().
diff --git a/Lib/quopri.py b/Lib/quopri.py
index e7e4bec..beb54cb 100755
--- a/Lib/quopri.py
+++ b/Lib/quopri.py
@@ -1,58 +1,96 @@
 #! /usr/bin/env python
 
-"""Conversions to/from quoted-printable transport encoding as per RFC-1521."""
+"""Conversions to/from quoted-printable transport encoding as per RFC 1521."""
 
 # (Dec 1991 version).
 
-__all__ = ["encode","decode"]
+__all__ = ["encode", "decode", "encodestring", "decodestring"]
 
 ESCAPE = '='
 MAXLINESIZE = 76
 HEX = '0123456789ABCDEF'
+EMPTYSTRING = ''
 
+
+
 def needsquoting(c, quotetabs):
     """Decide whether a particular character needs to be quoted.
 
-    The 'quotetabs' flag indicates whether tabs should be quoted."""
-    if c == '\t':
-        return not quotetabs
-    return c == ESCAPE or not(' ' <= c <= '~')
+    The 'quotetabs' flag indicates whether embedded tabs and spaces should be
+    quoted.  Note that line-ending tabs and spaces are always encoded, as per
+    RFC 1521.
+    """
+    if c in ' \t':
+        return quotetabs
+    return c == ESCAPE or not (' ' <= c <= '~')
 
 def quote(c):
     """Quote a single character."""
     i = ord(c)
     return ESCAPE + HEX[i/16] + HEX[i%16]
 
+
+
 def encode(input, output, quotetabs):
     """Read 'input', apply quoted-printable encoding, and write to 'output'.
 
     'input' and 'output' are files with readline() and write() methods.
-    The 'quotetabs' flag indicates whether tabs should be quoted.
-        """
+    The 'quotetabs' flag indicates whether embedded tabs and spaces should be
+    quoted.  Note that line-ending tabs and spaces are always encoded, as per
+    RFC 1521.
+    """
+    def write(s, output=output, lineEnd='\n'):
+        # RFC 1521 requires that the line ending in a space or tab must have
+        # that trailing character encoded.
+        if s and s[-1:] in ' \t':
+            output.write(s[:-1] + quote(s[-1]) + lineEnd)
+        else:
+            output.write(s + lineEnd)
+
+    prevline = None
+    linelen = 0
     while 1:
         line = input.readline()
         if not line:
             break
-        new = ''
-        last = line[-1:]
-        if last == '\n':
+        outline = []
+        # Strip off any readline induced trailing newline
+        stripped = ''
+        if line[-1:] == '\n':
             line = line[:-1]
-        else:
-            last = ''
-        prev = ''
+            stripped = '\n'
         for c in line:
             if needsquoting(c, quotetabs):
                 c = quote(c)
-            if len(new) + len(c) >= MAXLINESIZE:
-                output.write(new + ESCAPE + '\n')
-                new = ''
-            new = new + c
-            prev = c
-        if prev in (' ', '\t'):
-            output.write(new + ESCAPE + '\n\n')
-        else:
-            output.write(new + '\n')
+            # Have we hit the RFC 1521 encoded line maximum?
+            if linelen + len(c) >= MAXLINESIZE:
+                # Write out the previous line
+                if prevline is not None:
+                    write(prevline)
+                prevline = EMPTYSTRING.join(outline)
+                linelen = 0
+                outline = []
+            outline.append(c)
+            linelen += len(c)
+        # Write out the current line
+        if prevline is not None:
+            write(prevline)
+        prevline = EMPTYSTRING.join(outline)
+        linelen = 0
+        outline = []
+    # Write out the last line, without a trailing newline
+    if prevline is not None:
+        write(prevline, lineEnd=stripped)
 
+def encodestring(s, quotetabs=0):
+    from cStringIO import StringIO
+    infp = StringIO(s)
+    outfp = StringIO()
+    encode(infp, outfp, quotetabs)
+    return outfp.getvalue()
+
+
+
 def decode(input, output):
     """Read 'input', apply quoted-printable decoding, and write to 'output'.
 
@@ -87,6 +125,16 @@
     if new:
         output.write(new)
 
+def decodestring(s):
+    from cStringIO import StringIO
+    infp = StringIO(s)
+    outfp = StringIO()
+    decode(infp, outfp)
+    return outfp.getvalue()
+
+
+
+# Other helper functions
 def ishex(c):
     """Return true if the character 'c' is a hexadecimal digit."""
     return '0' <= c <= '9' or 'a' <= c <= 'f' or 'A' <= c <= 'F'
@@ -106,7 +154,9 @@
         bits = bits*16 + (ord(c) - i)
     return bits
 
-def test():
+
+
+def main():
     import sys
     import getopt
     try:
@@ -148,5 +198,7 @@
     if sts:
         sys.exit(sts)
 
+
+
 if __name__ == '__main__':
-    test()
+    main()