1{-# LANGUAGE LambdaCase #-}2{-# LANGUAGE RecordWildCards #-}34module Main where56import Control.Exception7import Control.Monad8import qualified Data.Text as T9import Options.Applicative10import System.Directory11import System.FilePath12import System.IO13import System.Exit (die)1415import SchemeDoc16import SchemeDoc.Error17import qualified SchemeDoc.Format.Library as L18import SchemeDoc.Parser.R7RS19import SchemeDoc.Types2021data Opts = Opts22 { css :: String23 , noWarn :: Bool24 , title :: String25 , directory :: FilePath26 , libraries :: [FilePath]27 }2829{- FOURMOLU_DISABLE -}30parseOpts :: Parser Opts31parseOpts = Opts32 <$> option str33 ( long "stylesheet"34 <> short 's'35 <> value "https://cdn.jsdelivr.net/gh/kognise/water.css@latest/dist/dark.css"36 <> help "URL used for the stylesheet in the generated HTML" )37 <*> switch38 ( long "no-warn"39 <> short 'w'40 <> help "Don't warn about unexported identifiers" )41 <*> option str42 ( long "title"43 <> short 't'44 <> value ""45 <> help "Title used for the metadata of the generated HTML" )46 <*> option str47 ( long "output"48 <> short 'o'49 <> value "."50 <> help "Output directory to which generated files are written" )51 <*> some (argument str (metavar "FILE..."))52{- FOURMOLU_ENABLE -}5354------------------------------------------------------------------------5556libFileName :: DocLib -> FilePath57libFileName (_, l) =58 T.unpack $59 T.append60 (T.map (\c -> if c == ' ' then '/' else c) $ L.name l)61 (T.pack ".html")6263writeDoc :: Opts -> FilePath -> FilePath -> DocLib -> IO ()64writeDoc Opts{..} inFp outFp docLib@(_, lib) = do65 -- Expand all includes relative to given Scheme file.66 (comps, failed) <- withCurrentDirectory (takeDirectory inFp) (docDecls docLib)6768 forM_69 failed70 (\f -> warn $ "Failed to find formatter for:\n\n\t" ++ show f ++ "\n")71 unless noWarn $72 forM_73 (findUndocumented lib comps)74 (\i -> warn $ "Exported but undocumented: " ++ show i)7576 let hTitle =77 if null title78 then show $ L.ident lib79 else title8081 let hbody = docFmt docLib comps82 let html = mkDoc hTitle css hbody83 writeFile outFp $ html ++ "\n"84 where85 warn msg = hPutStrLn stderr $ "WARNING: " ++ msg8687writeAll :: Opts -> [(FilePath, DocLib)] -> IO ()88writeAll opts@(Opts{directory = optDir}) =89 mapM_90 (\(p, l) -> mkDestPath l >>= flip (writeDoc opts p) l)91 where92 mkDestPath :: DocLib -> IO FilePath93 mkDestPath l =94 let fp = joinPath [optDir, libFileName l]95 in fp <$ createDirectoryIfMissing True (takeDirectory fp)9697findAllLibs :: [[Sexp]] -> [FilePath] -> IO [(FilePath, DocLib)]98findAllLibs srcs files =99 foldM100 ( \acc (path, src) -> do101 l <- findDocLibs' src102 pure $ map ((,) path) l ++ acc103 )104 []105 (zip files srcs)106 where107 findDocLibs' :: [Sexp] -> IO [DocLib]108 findDocLibs' exprs = either (throwIO . ErrSyntax) pure $ findDocLibs exprs109110main' :: Opts -> IO ()111main' opts@(Opts{libraries = optFiles}) =112 catch113 ( do114 srcs <- mapM parseFromFile optFiles115 libs <- findAllLibs srcs optFiles116117 if null libs118 then die "Found no documented define-library expression"119 else writeAll opts libs120 )121 ( \case122 ErrSyntax (SyntaxError expr err) ->123 hPutStrLn stderr $ "Syntax error on expression `" ++ show expr ++ "`: " ++ err124 ErrParser err ->125 hPrint stderr err126 )127128main :: IO ()129main = main' =<< execParser opts130 where131 opts =132 info133 (parseOpts <**> helper)134 ( fullDesc135 <> progDesc "Generate HTML documentation for a R⁷RS Scheme library"136 )