Черепаха: работа с не-utf8 вводом

На моем пути к изучению Pipes я столкнулся с проблемами при работе с файлами не-utf8. Вот почему я пошел в библиотеку Turtle, чтобы попытаться понять, как решить проблему там, на более высоком уровне абстракции.

Упражнение, которое я хочу выполнить, довольно простое: найти сумму всех строк всех обычных файлов, доступных из данного каталога. Это легко реализуется с помощью следующей команды оболочки:

find $FPATH -type f -print | xargs cat | wc -l

Я придумал следующее решение:

import qualified Control.Foldl as F
import qualified Turtle        as T

-- | Returns true iff the file path is not a symlink.
noSymLink :: T.FilePath -> IO Bool
noSymLink fPath = (not . T.isSymbolicLink) <$> T.stat fPath

-- | Shell that outputs the regular files in the given directory.
regularFilesIn :: T.FilePath -> T.Shell T.FilePath
regularFilesIn fPath = do
  fInFPath <- T.lsif noSymLink fPath
  st <- T.stat fInFPath
  if T.isRegularFile st
    then return fInFPath
    else T.empty

-- | Read lines of `Text` from all the regular files under the given directory
-- path.
inputDir :: T.FilePath -> T.Shell T.Line
inputDir fPath = do
  file <- regularFilesIn fPath
  T.input file

-- | Print the number of lines in all the files in a directory.
printLinesCountIn :: T.FilePath -> IO ()
printLinesCountIn fPath = do
  count <- T.fold (inputDir fPath) F.length
  print count

Это решение дает правильный результат, если в каталоге нет файлов не-utf8. Если это не так, программа выдаст исключение, подобное следующему:

*** Exception: test/resources/php_ext_syslog.h: hGetLine: invalid argument (invalid byte sequence)

Чего и следовало ожидать, так как:

$ file -I test/resources/php_ext_syslog.h
test/resources/php_ext_syslog.h: text/x-c; charset=iso-8859-1

Мне было интересно, как решить проблему чтения различных кодировок в Text, так что программа может справиться с этим. Для рассматриваемой проблемы, я думаю, я мог бы избежать преобразования в Text, но я бы предпочел знать, как это сделать, поскольку вы можете представить себе ситуацию, в которой, например, я хотел бы сделать набор со всеми словами в определенном каталоге.

РЕДАКТИРОВАТЬ

Для чего стоит единственное решение, которое я мог придумать, это следующее:

mDecodeByteString :: T.Shell ByteString -> T.Shell T.Text
mDecodeByteString = gMDecodeByteString (streamDecodeUtf8With lenientDecode)
  where gMDecodeByteString :: (ByteString -> Decoding)
                             -> T.Shell ByteString
                             -> T.Shell T.Text
        gMDecodeByteString f bss = do
          bs <- bss
          let Some res bs' g = f bs
          if BS.null bs'
            then return res
            else gMDecodeByteString g bss

inputDir' :: T.FilePath -> T.Shell T.Line
inputDir' fPath = do
  file <- regularFilesIn fPath
  text <- mDecodeByteString (TB.input file)
  T.select (NE.toList $ T.textToLines text)

-- | Print the number of lines in all the files in a directory. Using a more
-- robust version of `inputDir`.
printLinesCountIn' :: T.FilePath -> IO ()
printLinesCountIn' fPath = do
  count <- T.fold (inputDir' fPath) T.countLines
  print count

Проблема в том, что это будет считать еще одну строку на файл, но, по крайней мере, позволяет декодировать не-UTF8 ByteStrings.

0 ответов

Другие вопросы по тегам