module Snipcheck where
import Control.Monad
import Data.Maybe
import qualified Data.Map as Map
import Data.Monoid
import System.Process(readCreateProcess, shell)
import Text.Pandoc (Block(..))
import qualified Text.Pandoc as Pandoc
data Sloppy a = Skip | Must a deriving (Show, Functor)
sloppyString :: String -> Sloppy String
sloppyString "..." = Skip
sloppyString str = Must str
checkSloppy :: Eq a => [a] -> [Sloppy a] -> Bool
checkSloppy (a:as) (Must a':as')
| a == a' = checkSloppy as as'
| otherwise = False
checkSloppy (a:as) as'@(Skip:Must a':as'')
| a == a' = checkSloppy as as''
| otherwise = checkSloppy as as'
checkSloppy as (Skip:Skip:as') = checkSloppy as (Skip:as')
checkSloppy [] (Must{}:_) = False
checkSloppy [] (Skip:as') = checkSloppy [] as'
checkSloppy [] [] = True
checkSloppy (_:_) [] = False
checkSloppy _ [Skip] = True
checkMarkdownFile :: FilePath -> IO ()
checkMarkdownFile fp = do
content <- readFile fp
let Right (Pandoc.Pandoc meta blocks) = Pandoc.readMarkdown Pandoc.def content
sections = findSections meta
blocks' =
if null sections
then blocks
else filterBlocksBySectionName sections blocks
forM_ blocks' check
data AcceptSection
= GoodSection
| BadSection
| Dunno
filterBlocksBySectionName :: [String] -> [Pandoc.Block] -> [Pandoc.Block]
filterBlocksBySectionName secs = skipThese
where
skipThese, keepThese :: [Pandoc.Block] -> [Pandoc.Block]
skipThese (b:bs) =
case acceptSection b of
GoodSection -> keepThese bs
_ -> skipThese bs
skipThese [] = []
keepThese (b:bs) = b : case acceptSection b of
BadSection -> skipThese bs
_ -> keepThese bs
keepThese [] = []
acceptSection :: Pandoc.Block -> AcceptSection
acceptSection (Pandoc.Header _ (hName,_,_) _)
| hName `elem` secs = GoodSection
| otherwise = BadSection
acceptSection _ = Dunno
findSections :: Pandoc.Meta -> [String]
findSections (Pandoc.unMeta -> meta) =
case Map.lookup "sc_check-sections" meta of
Just (Pandoc.MetaList ss) -> join $ unMetaString <$> ss
_ -> []
where
unMetaString :: Pandoc.MetaValue -> [String]
unMetaString (Pandoc.MetaString s) =[s]
unMetaString (Pandoc.MetaInlines is) = mapMaybe unMetaStr is
unMetaString _ = []
unMetaStr :: Pandoc.Inline -> Maybe String
unMetaStr (Pandoc.Str s) = Just s
unMetaStr _ = Nothing
check :: Pandoc.Block -> IO ()
check (CodeBlock (typ, classes, kvs) content)
| "shell" `elem` classes = do
let Right cmds = extractCommands content
forM_ cmds $ \(cmd, expected) -> do
actual <- lines <$> readCreateProcess (shell cmd) ""
let expected' = sloppyString <$> expected
unless (checkSloppy actual expected') $ error $ mconcat
[ "Couldnt match expected ", show expected'
, " with " <> show actual
]
| otherwise = print (typ, classes, kvs)
check _ = return ()
extractCommands :: String -> Either String [(String, [String])]
extractCommands str = go (lines str)
where
go :: [String] -> Either String [(String, [String])]
go (l:ls) | Just cmd <- toCommand l =
let (output, rest) = break isCommand ls
in ((cmd,output):) <$> go rest
| otherwise = Left $ "Expected a command, got " <> l
go [] = Right []
toCommand :: String -> Maybe String
toCommand ('$':cmd) = Just cmd
toCommand _ = Nothing
isCommand :: String -> Bool
isCommand = isJust . toCommand
someFunc :: IO ()
someFunc = putStrLn "someFunc"