simple shell-like programs using Data.ByteString.Streaming, following the io-streams tutorial
January 17, 2017 · View on GitHub
-- These examples are based on the tutorial module in the io-streams package
{-#LANGUAGE OverloadedStrings #-} import Streaming import Streaming.Prelude (yield, next, each, for, with, subst) import qualified Streaming.Prelude as S import qualified Data.ByteString.Char8 as B import Data.ByteString.Streaming (ByteString) import qualified Data.ByteString.Streaming.Char8 as Q import System.IO (withFile, IOMode(..)) import Control.Monad import Control.Applicative import Data.Monoid
cat :: FilePath -> IO () cat file = withFile file ReadMode $ \h -> Q.stdout (Q.fromHandle h)
echo :: IO () echo = Q.stdout Q.stdin
-- Send the first n lines to stdout. -- Note that this streams even if it hits a 10 terabyte "line" head :: Int -> FilePath -> IO () head n file = withFile file ReadMode Q.unlines -- ByteString m () -- insert '\n' between bytestream layers Q.lines -- Stream (ByteString m) m () -- divided into Stream layers $ Q.fromHandle h -- ByteString m () -- raw bytes
yes :: IO () yes = Q.stdout $ Q.cycle "y\n" -- uses OverloadedStrings instance for 'ByteString m ()'
grep :: B.ByteString -> FilePath -> IO () grep pat file = withFile file ReadMode $ \h -> do let raw :: ByteString IO () -- get raw bytes raw = Q.fromHandle h
segmented :: Stream (ByteString IO) IO () -- divide on newlines
segmented = Q.lines raw
individualized :: Stream (Of B.ByteString) IO ()
individualized = mapped Q.toStrict segmented -- danger: concatenate 'real' bytestrings!
matching :: Stream (Of B.ByteString) IO () -- filter out matching bytestrings
matching = S.filter (B.isInfixOf pat) individualized
deindividualized :: Stream (ByteString IO) IO () -- restream (implicitly using
deindividualized = with matching Q.chunk -- the new chunk structure)
unsegmented :: ByteString IO () -- add newlines
unsegmented = Q.unlines deindividualized
Q.stdout unsegmented -- stream to IO.stdout
-- or, more compactly: grep' :: B.ByteString -> FilePath -> IO () grep' pat file = withFile file ReadMode Q.unlines S.filter (B.isInfixOf pat) Q.lines \h -> do -- grep' pat file = withFile file ReadMode $ \h -> do
data Option = Bytes | Words | Lines len = S.fold (\n _ -> n + 1) 0 id
wc :: Option -> FilePath -> IO () wc opt file = withFile file ReadMode mapped blank_layer mapped blank_layer $ Q.lines is blank_layer :: Monad m => ByteString m r -> m (Of Int r) blank_layer = liftM (1 :>) . Q.effects -- replace each layer with (1 :> ...); here we do not accumlate strict-bs words
-- >>> wc Words "examples.hs" -- 801 :> ()
-- exercise: write wc to permit a list of options, using foldl to combine
-- the different folds. This would require a more direct implementation
-- of what might be called line_count :: Fold Char Int and word_count :: Fold Char Int
paste file file' = withFile file ReadMode \h' ->
Q.stdout
let left = Q.lines (Q.fromHandle h)
right = Q.lines (Q.fromHandle h' )
center = repeats $ Q.chunk "\t"
in left <|> center <|> right
-- >>> paste "nums.txt" "numbers.txt" -- 1.1 -- 1 2.2 -- 2 -- 3 3.3 -- 4 -- 5 4.4 -- 6 5.5 -- this program is ziplike and breaks where either does.
-- number the lines of a file nl :: FilePath -> IO () nl file = withFile file ReadMode Q.unlines -- ByteString IO () | S.zipWith nlpad (each [1..]) -- Stream (Of B.ByteString) IO () | Q.lines -- Stream (ByteString IO) IO () | $ Q.fromHandle h -- ByteString IO () | where nlpad :: Int -> B.ByteString -> B.ByteString nlpad n bs = padding <> B.pack (show n <> " ") <> bs where len = length (show n); diff = 9 - len padding = if diff > 0 then B.replicate diff ' ' else B.singleton ' '
nl_streaming :: FilePath -> IO ()
nl_streaming file = withFile file ReadMode Q.unlines
Q.lines -- note proper streaming
$ Q.fromHandle h
where
numerals :: Monad m => Stream (ByteString m) m ()
numerals = maps trans (each [1..]) where
trans (n:>r) = Q.chunk stuff >> return r
where
len = length (show n); diff = 9 - len
padding = if diff > 0 then B.replicate diff ' ' else B.singleton ' '
stuff = padding <> B.pack (show n ++ " ")