-
Notifications
You must be signed in to change notification settings - Fork 0
/
Wc.hs
executable file
·165 lines (141 loc) · 5.39 KB
/
Wc.hs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
#!/usr/bin/env runhaskell
--
-- Copyright 2014 Wesley Tanaka <http://wtanaka.com/>
--
-- This file is part of https://github.com/wtanaka/haskell
--
-- https://github.com/wtanaka/haskell is free software: you can
-- redistribute it and/or modify it under the terms of the GNU General
-- Public License as published by the Free Software Foundation,
-- either version 3 of the License, or (at your option) any later
-- version.
--
-- https://github.com/wtanaka/haskell is distributed in the hope that
-- it will be useful, but WITHOUT ANY WARRANTY; without even the
-- implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
-- PURPOSE. See the GNU General Public License for more details.
--
-- You should have received a copy of the GNU General Public License
-- along with https://github.com/wtanaka/haskell . If not, see
-- <http://www.gnu.org/licenses/>.
module Main(main) where
import Data.ByteString.Lazy.Builder (Builder)
import Data.Char (ord)
import Data.Int (Int64)
import Data.List (sort)
import Data.Monoid (mappend)
import qualified Data.ByteString.Lazy as BSL
import qualified Data.ByteString.Lazy.Builder as BSB
import qualified Data.Word8 as Word8
import qualified System.Console.GetOpt as GetOpt
import System.Environment
import System.IO
import Text.Printf
-- Represents a single command line option
data Option =
Bytes
| Lines
| Words
| Help
deriving (Show, Eq)
-- Option requested -- should be one of
data OutputOption =
OutputBytes
| OutputLines
| OutputWords
deriving (Show, Eq)
_optionOrdMap OutputLines = 0
_optionOrdMap OutputWords = 1
_optionOrdMap OutputBytes = 2
instance Ord OutputOption where
compare a b = compare (_optionOrdMap a) (_optionOrdMap b)
_optionFunctionMap :: OutputOption -> BSL.ByteString -> Int64
_optionFunctionMap OutputLines = numLines
_optionFunctionMap OutputBytes = numBytes
_optionFunctionMap OutputWords = numWords
_lf = fromIntegral (ord '\n')
newtype WordCountAccum = WordCountAccum Int
_optDescr :: [GetOpt.OptDescr Option]
_optDescr = [
GetOpt.Option "h" ["help"] (GetOpt.NoArg Help)
"display this help and exit"
, GetOpt.Option "l" ["lines"] (GetOpt.NoArg Lines)
"print the newline counts"
, GetOpt.Option "c" ["bytes"] (GetOpt.NoArg Bytes)
"print the byte counts"
, GetOpt.Option "w" ["words"] (GetOpt.NoArg Words)
"print the word counts"
]
appendEol :: Builder -> Builder
appendEol x = x `mappend` BSB.char7 '\n'
numLines :: BSL.ByteString -> Int64
numLines = BSL.count _lf
numBytes :: BSL.ByteString -> Int64
numBytes = BSL.length
_numWordsStartsWithSpace :: BSL.ByteString -> WordCountAccum -> Int64
_numWordsStartsWithSpace bs (WordCountAccum acc) = let
firstWordIdx = BSL.findIndex (not . Word8.isSpace) bs
in case firstWordIdx of
Nothing -> fromIntegral acc
Just idx ->
_numWordsStartsWithNonSpace (BSL.drop idx bs) (WordCountAccum acc)
_numWordsStartsWithNonSpace :: BSL.ByteString -> WordCountAccum -> Int64
_numWordsStartsWithNonSpace bs (WordCountAccum acc)
| BSL.null bs = fromIntegral acc
| otherwise = let firstSpaceIdx = BSL.findIndex Word8.isSpace bs
in case firstSpaceIdx of
Nothing -> fromIntegral (acc + 1)
Just idx ->
_numWordsStartsWithSpace (BSL.drop idx bs) (WordCountAccum (acc + 1))
numWords :: BSL.ByteString -> Int64
numWords bs
| BSL.null bs = 0
| otherwise = (if Word8.isSpace (BSL.index bs 0)
then _numWordsStartsWithSpace
else _numWordsStartsWithNonSpace) bs (WordCountAccum 0)
leftPadUntil :: (Integral a, PrintfArg a) => Int -> a -> Builder
leftPadUntil n value = BSB.string7 $ printf (concat ["%", show n, "d"]) value
-- Get the output flags (lines, words, bytes) from the given input
-- flags
_outOptions :: [Option] -> [OutputOption]
_outOptions [] = []
-- List everything explicitly so that we get a compile error when we
-- add a new Option type.
-- http://www.haskell.org/haskellwiki/Scrap_your_boilerplate may offer
-- a better approach
_outOptions (x : xs) =
(case x of
Bytes -> (:) OutputBytes
Lines -> (:) OutputLines
Words -> (:) OutputWords
Help -> id) $ _outOptions xs
outOptions :: [Option] -> [OutputOption]
outOptions os = let oos = _outOptions os
in sort $ if null oos then [OutputBytes, OutputLines, OutputWords] else oos
singleFlagInteract :: OutputOption -> BSL.ByteString -> Builder
singleFlagInteract option inputStr =
BSB.string7 $ show (_optionFunctionMap option inputStr)
multiFlagInteract :: [OutputOption] -> BSL.ByteString -> Builder
multiFlagInteract [] _ = BSB.string7 ""
multiFlagInteract (o : os) inputStr =
leftPadUntil 7 (_optionFunctionMap o inputStr)
`mappend` multiFlagInteract os inputStr
interactFunction :: [Option] -> BSL.ByteString -> BSL.ByteString
interactFunction opts inputStr = let outOpts = outOptions opts
in BSB.toLazyByteString $ appendEol (
if 1 == length outOpts
then singleFlagInteract (head outOpts) inputStr
else multiFlagInteract outOpts inputStr)
main :: IO ()
main = do
argv <- getArgs
let (opts, _args, errs) = GetOpt.getOpt GetOpt.RequireOrder _optDescr argv
in if not $ null errs
then ioError (userError
(concat errs ++ GetOpt.usageInfo "" _optDescr))
else
if Help `elem` opts
then
hPutStrLn stderr (GetOpt.usageInfo "" _optDescr)
else
BSL.interact $ interactFunction opts