You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

Main.hs 4.2KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135
  1. {-
  2. This file is part of the wc-streams package. It is subject to the
  3. license terms in the LICENSE file found in the top-level directory of
  4. this distribution and at git://pmade.com/wc-streams/LICENSE. No part
  5. of wc-streams package, including this file, may be copied, modified,
  6. propagated, or distributed except according to the terms contained in
  7. the LICENSE file.
  8. -}
  9. --------------------------------------------------------------------------------
  10. module Main (main) where
  11. --------------------------------------------------------------------------------
  12. -- Library Imports.
  13. import Control.Monad
  14. import Data.ByteString (ByteString)
  15. import Data.Char (isSpace)
  16. import Data.Monoid
  17. import Data.Text (Text)
  18. import qualified Data.Text as T
  19. import Prelude hiding (lines, words)
  20. import System.Environment
  21. import System.IO.Streams (InputStream)
  22. import qualified System.IO.Streams as Streams
  23. import Text.Printf (printf)
  24. --------------------------------------------------------------------------------
  25. -- | Keep track of a series of counters while processing files.
  26. -- <<: counters
  27. data Counters = C
  28. { lines :: Int -- ^ Number of lines.
  29. , words :: Int -- ^ Number of words.
  30. , chars :: Int -- ^ Number of characters.
  31. }
  32. instance Monoid Counters where
  33. mempty = C 0 0 0
  34. mappend (C l1 w1 c1) (C l2 w2 c2) =
  35. C (l1 + l2) (w1 + w2) (c1 + c2)
  36. -- :>>
  37. --------------------------------------------------------------------------------
  38. -- | State necessary to process a file in chunks.
  39. -- <<: state
  40. data State = S
  41. { inWord :: Bool
  42. -- ^ Last character was part of a word.
  43. , counters :: Counters
  44. -- ^ Current set of counters.
  45. }
  46. -- :>>
  47. --------------------------------------------------------------------------------
  48. -- | Update the given state based on the given character.
  49. -- <<: wc
  50. wc :: State -> Char -> State
  51. wc state char = case char of
  52. '\n' -> newline
  53. _ | isSpace char -> whitespace
  54. | otherwise -> nonspace
  55. where
  56. -- ...
  57. -- :>>
  58. newline :: State
  59. newline = update (\c -> c {lines = lines c + 1}) whitespace
  60. whitespace :: State
  61. whitespace =
  62. if inWord state
  63. then S False (counters $ update (\c -> c {words = words c + 1}) character)
  64. else S False (counters character)
  65. nonspace :: State
  66. nonspace = S True (counters character)
  67. character :: State
  68. character = update (\c -> c {chars = chars c + 1}) state
  69. update :: (Counters -> Counters) -> State -> State
  70. update f s = s {counters = f (counters s)}
  71. --------------------------------------------------------------------------------
  72. -- | Process all characters in the given stream.
  73. -- <<: stream
  74. stream :: InputStream ByteString -> IO Counters
  75. stream = Streams.decodeUtf8 >=>
  76. go (S False mempty) >=>
  77. return . counters
  78. where
  79. go :: State -> InputStream Text -> IO State
  80. go state upstream = do
  81. textM <- Streams.read upstream
  82. case textM of
  83. Nothing -> return (eof state)
  84. Just text -> go (T.foldl' wc state text) upstream
  85. -- ...
  86. -- :>>
  87. eof :: State -> State
  88. eof state@(S w c) =
  89. if w then S False (c {words = words c + 1}) else state
  90. --------------------------------------------------------------------------------
  91. -- | Print a report to STDOUT.
  92. report :: Int -> String -> Counters -> IO ()
  93. report wid label (C l w c) = printf "%*d %*d %*d %s\n" wid l wid w wid c label
  94. --------------------------------------------------------------------------------
  95. -- | Calculate the maximum width of a report column.
  96. width :: [Counters] -> Int
  97. width cs = go 1 $ maximum (0 : map chars cs) where
  98. go :: Int -> Int -> Int
  99. go width' n | n > 10 = go (width' + 1) (n `div` 10)
  100. | otherwise = width'
  101. --------------------------------------------------------------------------------
  102. -- | Do it!
  103. -- <<: main
  104. main :: IO ()
  105. main = do
  106. args <- getArgs
  107. case args of
  108. -- No command line arguments...
  109. [] -> report 1 "" =<< stream (Streams.stdin)
  110. -- List of files...
  111. fs -> do
  112. cs <- mapM (flip Streams.withFileAsInput stream) fs
  113. mapM_ (uncurry $ report (width cs)) (zip fs cs)
  114. when (length cs > 1) $ report (width cs) "total" (mconcat cs)
  115. -- :>>