Browse Source

Clean a few things up, implement reading from STDIN or several files

master
Peter J. Jones 5 years ago
parent
commit
8dfb0e9e5c
3 changed files with 94 additions and 6 deletions
  1. 88
    1
      src/Main.hs
  2. 1
    1
      util
  3. 5
    4
      wc-streams.cabal

+ 88
- 1
src/Main.hs View File

@@ -13,5 +13,92 @@ the LICENSE file.
13 13
 module Main (main) where
14 14
 
15 15
 --------------------------------------------------------------------------------
16
+-- Library Imports.
17
+import           Control.Monad
18
+import           Data.ByteString    (ByteString)
19
+import           Data.Char          (isSpace)
20
+import           Data.Monoid
21
+import           Data.Text          (Text)
22
+import qualified Data.Text          as T
23
+import           Prelude hiding     (lines, words)
24
+import           System.Environment
25
+import           System.IO.Streams  (InputStream)
26
+import qualified System.IO.Streams  as Streams
27
+import           Text.Printf        (printf)
28
+
29
+--------------------------------------------------------------------------------
30
+-- | Keep track of a series of counters while processing files.
31
+data Counters = C
32
+  { lines :: Int                -- ^ Number of lines.
33
+  , words :: Int                -- ^ Number of words.
34
+  , chars :: Int                -- ^ Number of characters.
35
+  } deriving Show
36
+
37
+instance Monoid Counters where
38
+  mempty = C 0 0 0
39
+  mappend (C l1 w1 c1) (C l2 w2 c2) = C (l1 + l2) (w1 + w2) (c1 + c2)
40
+
41
+--------------------------------------------------------------------------------
42
+-- | State necessary to process a file in chunks.
43
+data State = S
44
+  { inWord   :: Bool     -- ^ Last character was part of a word.
45
+  , counters :: Counters -- ^ Current set of counters.
46
+  }
47
+
48
+--------------------------------------------------------------------------------
49
+-- | Update the given state based on the given character.
50
+wc :: State -> Char -> State
51
+wc state char = case char of
52
+    '\n'             -> whitespace (\c -> c {lines = lines c + 1})
53
+    _ | isSpace char -> whitespace id
54
+      | otherwise    -> S True (character id)
55
+  where
56
+    whitespace :: (Counters -> Counters) -> State
57
+    whitespace f = if inWord state
58
+                     then S False (character (f . \c -> c {words = words c + 1}))
59
+                     else S False (character f)
60
+
61
+    character :: (Counters -> Counters) -> Counters
62
+    character f = let counters' = f (counters state)
63
+                  in counters' {chars = chars counters' + 1}
64
+
65
+--------------------------------------------------------------------------------
66
+-- | Process all characters in the given stream.
67
+stream :: InputStream ByteString -> IO Counters
68
+stream = Streams.decodeUtf8 >=> go (S False mempty) >=> return . counters
69
+  where
70
+    go :: State -> InputStream Text -> IO State
71
+    go state upstream = do
72
+      textM <- Streams.read upstream
73
+
74
+      case textM of
75
+        Nothing   -> return (eof state)
76
+        Just text -> go (T.foldl' wc state text) upstream
77
+
78
+    eof :: State -> State
79
+    eof state@(S w c) = if w then S False (c {words = words c + 1}) else state
80
+
81
+--------------------------------------------------------------------------------
82
+-- | Print a report to STDOUT.
83
+report :: Int -> String -> Counters -> IO ()
84
+report wid label (C l w c) = printf "%*d %*d %*d %s\n" wid l wid w wid c label
85
+
86
+--------------------------------------------------------------------------------
87
+-- | Calculate the maximum width of a report column.
88
+width :: [Counters] -> Int
89
+width cs = go 1 $ maximum (0 : map chars cs) where
90
+  go :: Int -> Int -> Int
91
+  go width' n | n > 10    = go (width' + 1) (n `div` 10)
92
+              | otherwise = width'
93
+
94
+--------------------------------------------------------------------------------
95
+-- | Do it!
16 96
 main :: IO ()
17
-main = undefined
97
+main = do
98
+  args <- getArgs
99
+
100
+  case args of
101
+    [] -> report (width []) "" =<< stream (Streams.stdin)
102
+    xs -> do cs <- mapM (flip Streams.withFileAsInput stream) xs
103
+             mapM_ (uncurry $ report (width cs)) (zip xs cs)
104
+             report (width cs) "total" (mconcat cs)

+ 1
- 1
util

@@ -1 +1 @@
1
-Subproject commit 12d751d681dc0392b63698541e33af62bb02db6b
1
+Subproject commit 36a98650ac7b3fc615bc30880f1a26e3f2d27ca4

+ 5
- 4
wc-streams.cabal View File

@@ -20,10 +20,8 @@ flag maintainer
20 20
 --------------------------------------------------------------------------------
21 21
 executable wc
22 22
   main-is: Main.hs
23
-  -- other-modules:
24
-  -- other-extensions:
25 23
   hs-source-dirs: src
26
-  default-language:    Haskell2010
24
+  default-language: Haskell2010
27 25
 
28 26
   ghc-options: -Wall -rtsopts
29 27
   ghc-prof-options: -fprof-auto-top -fprof-cafs
@@ -31,4 +29,7 @@ executable wc
31 29
   if flag(maintainer)
32 30
     ghc-options: -Werror
33 31
 
34
-  build-depends: base >= 4.7 && < 5.0
32
+  build-depends: base       >= 4.7  && < 5.0
33
+               , bytestring >= 0.10 && < 0.11
34
+               , io-streams >= 1.1  && < 1.2
35
+               , text       >= 1.1  && < 1.2

Loading…
Cancel
Save