Подтвердить что ты не робот

Data.ByteString.Lazy.Char8 преобразование новой строки в Windows --- является ли ошибка в документации?

У меня есть вопрос о библиотеке Data.ByteString.Lazy.Char8 в библиотеке bytestring. В частности, мой вопрос касается функции readFile, которая документируется следующим образом:

Прочитайте весь файл лениво в ByteString. Используйте "текстовый режим" в Windows для интерпретации новых строк

Меня интересует утверждение, что эта функция будет "использовать текстовый режим в Windows для интерпретации новых строк". Исходный код для функции выглядит следующим образом:

-- | Read an entire file /lazily/ into a 'ByteString'. Use 'text mode'
-- on Windows to interpret newlines
readFile :: FilePath -> IO ByteString
readFile f = openFile f ReadMode >>= hGetContents

и мы видим, что в некотором смысле претензия в документации совершенно верна: была использована функция openFile (в отличие от openBinaryFile), и поэтому для файла будет включено преобразование новой строки.

Но файл будет передан hGetContents. Это вызовет Data.ByteString.hGetNonBlocking (см. Исходный код здесь и здесь), который должен быть неблокирующей версией Data.ByteString.hGet (см. документацию); и (наконец) Data.ByteString.hGet вызывает GHC.IO.Handle.hGetBuf (см. документация или исходный код). Эта функция документация говорит, что

hGetBuf игнорирует все, что TextEncoding использует в настоящее время Handle, и считывает байты непосредственно из базового устройства ввода-вывода.

который говорит о том, что факт, что мы открыли файл с использованием readFile, а не readBinaryFile, не имеет значения: данные будут считаны без преобразования строк новой строки, несмотря на претензии в документации, упомянутой в начале вопроса.

Итак, суть вопроса: 1. Я что-то пропустил? Есть ли смысл в том, что утверждение, что Data.ByteString.Lazy.Char8.readFile использует текстовый режим для Windows для интерпретации новых строк, является истинным? Или документация просто вводит в заблуждение?

P.S. Тестирование также указывает на то, что эта функция, по крайней мере, когда она используется наивно, когда я ее использую, не делает преобразование новой строки в Windows.

4b9b3361

Ответ 1

FWIW, сопровождающий пакет, Дункан Куттс, ответил некоторыми очень полезными и просвещенными замечаниями. Я попросил его разрешения опубликовать их здесь, но в промежутке между нами это парафраз.

Основной момент в том, что документация была некорректной, но теперь, вероятно, нет. В частности, когда вы открываете файл в окнах, сама операционная система позволяет открыть его в режиме "текст" или "двоичный". Разница между readFile и readBinaryFile заключалась в том, чтобы открыть файл в текстовом режиме ОС и один в двоичном режиме на Win32. (Они оба будут делать то же самое на POSIX.) Критически, если вы открыли файл в двоичном режиме OS, вы не могли бы читать из файла без преобразования новой строки: это всегда происходило.

Когда все было настроено так, документация, упомянутая в вопросе, была правильной --- Data.ByteString.Lazy.Char8.readFile использовал бы System.IO.readFile; это означало бы, что ОС откроет файл "Текст", и новые строки будут преобразованы, хотя используется hGetBuf.

Затем, позже, Haskell System.IO был настроен, чтобы сделать его обработку новых строк более гибкой - в частности, чтобы разрешить версии Haskell, запущенные на ОС POSIX, где нет возможности читать файлы с использованием новой строки, встроенной в OS, тем не менее, для поддержки чтения файлов с использованием новых строк Windows; или более точно, чтобы поддерживать 'универсальное' преобразование новой строки на обе ОС. Это означало, что:

  • Обработка новых строк была внесена в библиотеки Haskell;
  • Файлы всегда открываются в двоичном режиме в Windows, независимо от того, используете ли вы readFile или readBinaryFile; и
  • Вместо этого выбор между readFile и readBinaryFile повлияет на то, был ли установлен код библиотеки System.IO, который находится в nativeNewlineMode или noNewlineTranslation. Это приведет к тому, что преобразование библиотеки Haskell приведет к соответствующему преобразованию новой строки для вас. Теперь вы можете также запросить universalNewlineMode.

Это примерно в то же самое время, когда Haskell получил правильную поддержку кодирования, встроенную в System.IO (вместо того, чтобы принимать латинский-1 на входе и просто обрезать выходные символы Chars до их первых 8 бит). В целом, это была хорошая вещь.

Но, критически, новое преобразование новой строки, теперь встроенное в библиотеки, никогда не влияет на то, что hPutBuf делает --- предположительно, потому что люди, создающие новую функциональность System.IO, думали, что если кто-то читал штраф в бинарный путь, любое преобразование конверсии новой строки, вероятно, было не тем, чего хотел Программист, т.е. ошибкой. И действительно, это, вероятно, в 99% случаев: но в этом случае это вызывает проблему выше: -)

Дункан говорит, что документы, вероятно, изменятся, чтобы отразить этот смелый новый мир в будущих выпусках библиотеки. Тем временем существует обходной путь, указанный в другом ответе на этот вопрос.

Ответ 2

Копаем еще один слой в исходный файл показывает, что он читает сырые байты:

-- | 'hGetBuf' @hdl buf [email protected] reads data from the handle @[email protected]
-- into the buffer @[email protected] until either EOF is reached or
-- @[email protected] 8-bit bytes have been read.
-- It returns the number of bytes actually read.  This may be zero if
-- EOF was reached before any data was read (or if @[email protected] is zero).
--
-- 'hGetBuf' never raises an EOF exception, instead it returns a value
-- smaller than @[email protected]
--
-- If the handle is a pipe or socket, and the writing end
-- is closed, 'hGetBuf' will behave as if EOF was reached.
--
-- 'hGetBuf' ignores the prevailing 'TextEncoding' and 'NewlineMode'
-- on the 'Handle', and reads bytes directly.

hGetBuf :: Handle -> Ptr a -> Int -> IO Int
hGetBuf h ptr count
  | count == 0 = return 0
  | count <  0 = illegalBufferSize h "hGetBuf" count
  | otherwise = 
      wantReadableHandle_ "hGetBuf" h $ \ [email protected]__{..} -> do
         flushCharReadBuffer h_
         [email protected]{ bufRaw=raw, bufR=w, bufL=r, bufSize=sz }
            <- readIORef haByteBuffer
         if isEmptyBuffer buf
            then bufReadEmpty    h_ buf (castPtr ptr) 0 count
            else bufReadNonEmpty h_ buf (castPtr ptr) 0 count

Ответ 3

Не совсем ответ на заданный вопрос, но я подумал, что я упомянул следующее обходное решение для других, которые столкнулись с этой проблемой и найдут эту страницу в Stack Overflow. Он использует пакет stringsearch.

import qualified Data.ByteString.Lazy as L
import qualified Data.ByteString as B
import qualified Data.ByteString.Lazy.Search as S
import qualified System.IO
import Control.Monad

nativeCallsForConversion        = System.IO.nativeNewline == System.IO.CRLF
readFileUniversalNewlineConversion = let
  str_LF   = B.pack [10]
  str_CRLF = B.pack [13, 10]
  in liftM (S.replace str_CRLF str_LF) . L.readFile
readFileNativeNewlineConversion =
  if nativeCallsForConversion
  then readFileUniversalNewlineConversion
  else L.readFile