content: import System.IO
  import GHC.IO.Handle
  import Control.Monad
  import Data.List (intercalate)
  
  newlines = ["\n","\r","\r\n","\n\r","\n\n","\r\r"]
  
  -- make sure the file ends in '\r': that's a tricky case for CRLF
  -- conversion, because the IO library has to check whether there's a
  -- following \n before returning the \r.
  content = concat [ show i ++ t | (i,t) <- zip [1..100] (cycle newlines) ]
  
  filename = "newline001.out"
  
  fromCRLF [] = []
  fromCRLF ('\r':'\n':cs) = '\n' : fromCRLF cs
  fromCRLF (c:cs) = c : fromCRLF cs
  
  toCRLF [] = []
  toCRLF ('\n':cs) = '\r':'\n': toCRLF cs
  toCRLF (c:cs) = c : toCRLF cs
  
  main = do
    h <- openBinaryFile filename WriteMode
    hPutStr h content
    hClose h
    testinput NoBuffering
    testinput LineBuffering
    testinput (BlockBuffering Nothing)
    testinput (BlockBuffering (Just 3))
    testinput (BlockBuffering (Just 7))
    testinput (BlockBuffering (Just 16))
    testoutput NoBuffering
    testoutput LineBuffering
    testoutput (BlockBuffering Nothing)
    testoutput (BlockBuffering (Just 3))
    testoutput (BlockBuffering (Just 7))
    testoutput (BlockBuffering (Just 16))
  
  testinput b = do
    h <- openFile filename ReadMode
    hSetBuffering h b
    hSetNewlineMode h noNewlineTranslation
    str <- hGetContents h
    check "in1" b str content
    hClose h
  
    h <- openFile filename ReadMode
    hSetBuffering h b
    hSetNewlineMode h noNewlineTranslation
    str <- read_chars h
    check "in2" b str content
    hClose h
  
    h <- openFile filename ReadMode
    hSetBuffering h b
    hSetNewlineMode h noNewlineTranslation
    str <- read_lines h
    check "in3" b str content
    hClose h
  
    h <- openFile filename ReadMode
    hSetBuffering h b
    hSetNewlineMode h NewlineMode{ inputNL=CRLF, outputNL=LF }
    str <- hGetContents h
    check "in4" b str (fromCRLF content)
    hClose h
  
    h <- openFile filename ReadMode
    hSetBuffering h b
    hSetNewlineMode h NewlineMode{ inputNL=CRLF, outputNL=LF }
    str <- read_chars  h
    check "in5" b str (fromCRLF content)
    hClose h
  
    h <- openFile filename ReadMode
    hSetBuffering h b
    hSetNewlineMode h NewlineMode{ inputNL=CRLF, outputNL=LF }
    str <- read_lines  h
    check "in6" b str (fromCRLF content)
    hClose h
  
  testoutput b = do
    h <- openFile filename WriteMode
    hSetBuffering h b
    hSetNewlineMode h NewlineMode{ inputNL=LF, outputNL=CRLF }
    hPutStr h content
    hClose h
    h <- openBinaryFile filename ReadMode
    str <- hGetContents h
    check "out1" b (toCRLF content) str
    hClose h
  
    h <- openFile filename WriteMode
    hSetBuffering h b
    hSetNewlineMode h NewlineMode{ inputNL=LF, outputNL=CRLF }
    mapM_ (hPutChar h) content
    hClose h
    h <- openBinaryFile filename ReadMode
    str <- hGetContents h
    check "out2" b (toCRLF content) str
    hClose h
  
  check s b str1 str2 =
    when (str1 /= str2) $ error ("failed: " ++ s ++ ", " ++ show b ++ '\n':show str1 ++ '\n':show str2)
  
  read_chars :: Handle -> IO String
  read_chars h = loop h ""
    where loop h acc = do
            b <- hIsEOF h
            if b
            then return (reverse acc)
            else do
                c <- hGetChar h
                loop h (c:acc)
  
  read_lines :: Handle -> IO String
  read_lines h = loop h []
    where loop h acc = do
            b <- hIsEOF h
            if b
            then return (intercalate "\n" (reverse acc))
            else do
                l <- hGetLine h
                loop h (l : acc)
  