packages feed

croque-mort-0.1.0.0: app/Main.hs

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"