wc そのよん
id:ha-tan:20061114:1163507847の続き。ざっとリファクタリングしてみました。出力桁数は、前もってファイルサイズから計算しておくようにしました。GNU wcも似たような感じになってました。
module Main (main) where import Control.Monad (when) import Data.Char (isSpace) import System.Environment (getArgs) import System.IO (stdin, hGetContents, openBinaryFile, openFile, hClose, IOMode(ReadMode), hFileSize, hSetBinaryMode) import Text.Printf (printf) readBinaryFile :: FilePath -> IO String readBinaryFile path = hGetContents =<< openBinaryFile path ReadMode getBinaryContents :: IO String getBinaryContents = do hSetBinaryMode stdin True getContents computeWidth :: [FilePath] -> IO Int computeWidth files = return . length . show . sum =<< mapM size files where size file = do f <- openFile file ReadMode b <- hFileSize f hClose f return b wc :: String -> (Int, Int, Int) wc = wc' (0, 0, 0) False where wc' (l, w, b) sp [] | sp = (l, w, b) | otherwise = (l, w + 1, b) wc' (l, w, b) sp (x : xs) | x == '\n' = seq ln $ seq bn $ wc' (ln, w, bn) True xs | sp && isSpace x = seq bn $ wc' (l, w, bn) True xs | isSpace x = seq wn $ seq bn $ wc' (l, wn, bn) True xs | otherwise = seq bn $ wc' (l, w, bn) False xs where (ln, wn, bn) = (l + 1, w + 1, b + 1) wcStdin :: IO () wcStdin = putStr . wcFormat 7 "" . wc =<< getBinaryContents wcFile :: Int -> FilePath -> IO (Int, Int, Int) wcFile width path = do s <- readBinaryFile path let w = wc s putStr $ wcFormat width path w return w wcTotal :: Int -> [(Int, Int, Int)] -> IO () wcTotal width ws = do let l = sum $ map (\ (l, _, _) -> l) ws w = sum $ map (\ (_, w, _) -> w) ws b = sum $ map (\ (_, _, b) -> b) ws putStr $ wcFormat width "total" (l, w, b) wcFormat :: Int -> String -> (Int, Int, Int) -> String wcFormat len path (l, w, b) = let s = show len fmt = "%" ++ s ++ "d %" ++ s ++ "d %" ++ s ++ "d %s\n" in printf fmt l w b path main :: IO () main = do args <- getArgs if (null args) then wcStdin else do width <- computeWidth args ws <- mapM (wcFile width) args when (length ws > 1) $ wcTotal width ws
BUGS:
- 普通のファイル以外(ディレクトリとか)を指定したときにエラーで終了します。うーん、そのうち何とかしたいなぁ。