module Format where import qualified ModuleName import Parse (Module(..), Line, Column) import Test.DocTest.Parse (DocTest(Property, Example)) import Test.DocTest.Location (Located(Located)) import qualified Data.Monoid.HT as Mn import qualified Data.List.Match as Match import qualified Data.List.HT as ListHT import Data.Semigroup ((<>)) import Data.Foldable (foldMap) import Data.Char (isSpace) import qualified System.Path.IO as PathIO import qualified System.Path.PartClass as PathClass import qualified System.Path as Path import System.Path.Directory (createDirectoryIfMissing) import System.Path (()) import Text.Printf (printf) indentRemainder :: Int -> String -> String indentRemainder n str = let (prefix, suffix) = break isSpace str in prefix ++ Mn.when (not $ null suffix) (replicate n ' ' ++ suffix) type Pos = (Line, Column) type Flags = (Bool, Bool) writeTestSuite :: (PathClass.AbsRel ar) => Path.Dir ar -> ModuleName.T -> Flags -> [Module [Located Pos DocTest]] -> IO () writeTestSuite outDir testPrefix flags ms = do let testDir = outDir ModuleName.dirPath testPrefix mapM_ (writeTestModule testDir testPrefix flags) ms importDriver :: String importDriver = "import qualified Test.DocTest.Driver as DocTest" writeTestMain :: (PathClass.AbsRel ar) => Bool -> Path.File ar -> ModuleName.T -> ModuleName.T -> [Module [Located Pos DocTest]] -> IO () writeTestMain run path mainName testPrefix ms = do let indent = map (" " ++) let prefixed = ModuleName.string . (testPrefix<>) . moduleName PathIO.writeFile path $ unlines $ "-- Do not edit! Automatically created with doctest-extract." : printf "module %s where" (ModuleName.string mainName) : "" : map (printf "import qualified %s" . prefixed) ms ++ "" : importDriver : "" : (if run then "main :: IO ()" : "main = DocTest.run $ do" : [] else "main :: DocTest.T ()" : "main = do" : [] )++ indent (map (printf "%s.test" . prefixed) ms) writeTestModule :: (PathClass.AbsRel ar) => Path.Dir ar -> ModuleName.T -> Flags -> Module [Located Pos DocTest] -> IO () writeTestModule testDir testPrefix flags m = do let path = testDir ModuleName.filePath (moduleName m) createDirectoryIfMissing True $ Path.takeDirectory path PathIO.writeFile path $ formatTestModule testPrefix flags m -- ToDo: move to List.HT mapLast :: (a -> a) -> [a] -> [a] mapLast f xs = zipWith id (drop 1 (Match.replicate xs id) ++ [f]) xs formatTestModule :: ModuleName.T -> Flags -> Module [Located Pos DocTest] -> String formatTestModule testPrefix (verbose,importTested) m = let escapedPath = show $ Path.toString $ modulePath m formatLinePragma loc = printf "{-# LINE %d %s #-}" loc escapedPath formatPragma (Located loc lns) = unlines $ formatLinePragma loc : map (\ln -> Mn.when (not $ null ln) (printf "{-# OPTIONS_GHC %s #-}" ln)) lns formatImport (Located loc lns) = unlines $ formatLinePragma loc : map (\(Located col ln) -> indentRemainder col ln) lns isExample (Located _loc (Example _ _)) = True; isExample _ = False formatTest (Located (loc,col) body) = let testCode command mark code = (if verbose then [printf " DocTest.printLine %s\n" (show code)] else []) ++ formatLinePragma loc : (' ':command) : formatLinePragma loc : (case lines code of (':':'{':firstLine) : remainingLines -> (replicate (col + length mark) ' ' ++ '(':firstLine) : map (replicate (max 2 col) ' ' ++) (mapLast ((++")") . ListHT.dropRev 2) remainingLines) _ -> [replicate (col + length mark) ' ' ++ '(':code++")"]) ++ [] in (if verbose then printf " DocTest.printLine ('\\n':%s++\":%d:1\")" escapedPath loc else printf " DocTest.printPrefix \"%s:%d: \"" (ModuleName.string $ moduleName m) loc) : case body of Property prop -> testCode "DocTest.property" "prop>" prop Example str results -> testCode "DocTest.example" ">>>" str ++ (" " ++ showsPrec 11 results "") : [] in printf "-- Do not edit! Automatically created with doctest-extract from %s\n" (Path.toString $ modulePath m) ++ foldMap formatPragma (modulePragma m) ++ printf "module %s where\n\n" (ModuleName.string $ testPrefix <> moduleName m) ++ Mn.when importTested (printf "import %s\n" $ ModuleName.string $ moduleName m) ++ Mn.when (any isExample $ concat $ moduleContent m) "import Test.DocTest.Base\n" ++ importDriver ++ "\n\n" ++ foldMap formatImport (moduleSetup m) ++ "\n" ++ "test :: DocTest.T ()\n" ++ "test = do\n" ++ (unlines $ concatMap formatTest $ concat $ moduleContent m)