module Main (main) where import Control.Monad (forM_, when) import qualified Data.Aeson as Aeson import qualified Data.ByteString.Lazy.Char8 as LBS import qualified Data.Char as Char import qualified Data.Map.Strict as Map import qualified Data.Text as T import Domain import FileSystem import Options.Applicative import System.Exit (exitFailure) main :: IO () main = do cmd <- customExecParser (prefs showHelpOnEmpty) commandInfo case cmd of Run {..} -> do parsed <- parseLinks targets let checked = checkLinks parsed broken = filterBrokenLinks checked summary = mkSummary parsed checked broken case verbosity of LogMuted -> return () LogSummary -> case format of Plain -> do when (hasBrokenLinks broken) $ do putStrLn "# Broken links" forM_ broken.unBrokenLinks $ \withRoot -> forM_ (Map.toList withRoot.links) $ \(file, links) -> forM_ links $ \link -> putStrLn $ file <> ":" <> T.unpack (originalLink link) putStrLn "" putStrLn $ "Run over " <> show summary.parsing.remoteBaseURLs <> " base URLs" <> " and " <> show summary.parsing.files <> " files," <> " only " <> show summary.broken.links <> " links where broken out of " <> show summary.parsing.links Json -> LBS.putStr $ Aeson.encode summary LogDetailed -> LBS.putStr $ Aeson.encode $ Aeson.object [ "parsing" Aeson..= parsed, "checking" Aeson..= checked, "broken" Aeson..= broken, "summary" Aeson..= summary ] when (hasBrokenLinks broken) exitFailure -- * CLI data Command = Run {verbosity :: Verbosity, format :: Format, targets :: [(RemoteBaseURL, Folder)]} data Verbosity = LogMuted | LogSummary | LogDetailed deriving stock (Eq, Show) data Format = Plain | Json deriving stock (Eq, Show) commandInfo :: ParserInfo Command commandInfo = info (commandParser <**> helper) ( fullDesc <> header hopHeader <> progDesc hopDesc ) commandParser :: Parser Command commandParser = hsubparser ( command "run" ( info (Run <$> verbosityP <*> formatP <*> many targetP) (progDesc "Look for broken links") ) ) where verbosityP = flag' LogSummary ( long "verbose" <> short 'v' <> help "Display summary" ) <|> flag' LogDetailed ( long "very-verbose" <> short 'V' <> help "Display summary and each phase details (json)" ) <|> pure LogMuted formatP = option ( maybeReader $ \case "plain" -> Just Plain "json" -> Just Json _ -> Nothing ) ( long "format" <> value Plain <> showDefaultWith (map Char.toLower . show) <> metavar "FORMAT" <> help "Output format (plain|json)" ) targetP = (,) <$> option (RemoteBaseURL <$> str) ( long "remote-base-url" <> short 'u' <> metavar "URL" <> help "Remote base URL (e.g. https://example.org/ping/)" ) <*> option (Folder <$> str) ( long "directory" <> short 'd' <> metavar "DIRECTORY_PATH" <> help "Directory to the HTML (e.g. build/public)" ) -- * Meta hopHeader :: String hopHeader = "croque-mort - Dead simple broken links checker on local HTML folders" hopDesc :: String hopDesc = "A CLI to check broken links in HTML folders"