packages feed

brassica-1.1.0: test/DocTests.hs

{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE OverloadedStrings #-}

module Main where

import Data.Functor ((<&>))
import Data.List (intercalate)
import Data.Maybe (mapMaybe)
import Data.Traversable (for)
import Text.Pandoc
import Test.Tasty (defaultMain, testGroup)
import Test.Tasty.Providers

import qualified Data.Text as T
import qualified Data.Text.IO as T

import Brassica.SoundChange (applyChanges, concatWithBoundary, getOutput)
import Brassica.SoundChange.Expand (expandSoundChanges)
import Brassica.SoundChange.Parse (parseSoundChanges, errorBundlePretty)
import Brassica.SoundChange.Tokenise (withFirstCategoriesDecl, tokeniseWord)

main :: IO ()
main = do
    t1 <- doctest "docs/Writing-Sound-Changes.md" ""
    t2 <- doctest "docs/Reference.md" "\
\categories noreplace\n\
\C = m n p t ch k b d j g f s sh h v z r l w y\n\
\-Stress = a e i o u\n\
\+Stress = á é í ó ú\n\
\auto -Stress\n\
\V = &&Stress\n\
\end\n"
    defaultMain $ testGroup "brassica-doctests" [t1, t2]

doctest :: FilePath -> String -> IO TestTree
doctest path prefix = do
    doc <- T.readFile path
    tests <- runIO (readMarkdown def { readerExtensions = githubMarkdownExtensions } doc) <&> \case
        Left err -> error $ T.unpack $ renderError err
        Right (Pandoc _ blocks) ->
            flip mapMaybe (zip [(0::Int)..] blocks) $ \case
                    (i, CodeBlock (_, cs, _) text)
                        | "brassica" `elem` cs -> Just $
                          singleTest (show i) $ DocTest (T.pack prefix) text
                    _ -> Nothing
    pure $ testGroup path tests

data DocTest = DocTest T.Text T.Text

instance IsTest DocTest where
    testOptions = pure []
    run _ (DocTest prefix text) _ = pure $
        let rule = T.unpack (prefix <> text)
            examples = flip mapMaybe (T.lines text) $ \x ->
                case words $ T.unpack x of
                    ";":i:"→":o:_ -> Just (i, o)
                    _ -> Nothing
            results = case parseSoundChanges rule of
                Left err -> Left $ errorBundlePretty err
                Right scs ->
                    case expandSoundChanges scs of
                        Left err -> Left $ show err
                        Right scs' -> for examples $ \(i, o) ->
                            case withFirstCategoriesDecl tokeniseWord scs' i of
                                Left err -> Left $ errorBundlePretty err
                                Right i' ->
                                    let o' = mapMaybe getOutput $ applyChanges scs' i'
                                        o'' =
                                            if null o'
                                            then "(deleted)"
                                            else intercalate "/" $ concatWithBoundary <$> o'
                                    in if o == o''
                                            then Right ("" :: String)
                                            else Left $
                                                    "expected " ++ i ++ "→" ++ o ++
                                                    ", got " ++ o'' ++
                                                    "\nfor sound change:\n" ++ rule
        in case results of
            Left err -> testFailed err
            Right _ -> testPassed ""