packages feed

xrefcheck-0.2.1: src/Xrefcheck/Command.hs

{- SPDX-FileCopyrightText: 2021 Serokell <https://serokell.io>
 -
 - SPDX-License-Identifier: MPL-2.0
 -}

module Xrefcheck.Command
  ( defaultAction
  ) where

import Universum

import Data.Yaml (decodeFileEither, prettyPrintParseException)
import Fmt (blockListF', build, fmt, fmtLn, indentF)
import System.Directory (doesFileExist)

import Xrefcheck.CLI (Options (..), addTraversalOptions, addVerifyOptions, defaultConfigPaths)
import Xrefcheck.Config
  (Config, Config' (..), ScannersConfig, ScannersConfig' (..), defConfig, normaliseConfigFilePaths,
  overrideConfig)
import Xrefcheck.Core (Flavor (..))
import Xrefcheck.Progress (allowRewrite)
import Xrefcheck.Scan (FormatsSupport, scanRepo, specificFormatsSupport, ScanResult (..), ScanError (..))
import Xrefcheck.Scanners.Markdown (markdownSupport)
import Xrefcheck.System (askWithinCI)
import Xrefcheck.Verify (verifyErrors, verifyRepo)

readConfig :: FilePath -> IO Config
readConfig path = fmap (normaliseConfigFilePaths . overrideConfig) do
  decodeFileEither path
    >>= either (error . toText . prettyPrintParseException) pure

formats :: ScannersConfig -> FormatsSupport
formats ScannersConfig{..} = specificFormatsSupport
    [ markdownSupport scMarkdown
    ]

findFirstExistingFile :: [FilePath] -> IO (Maybe FilePath)
findFirstExistingFile = \case
  [] -> pure Nothing
  (file : files) -> do
    exists <- doesFileExist file
    if exists then pure (Just file) else findFirstExistingFile files

defaultAction :: Options -> IO ()
defaultAction Options{..} = do
    config <- case oConfigPath of
      Just configPath -> readConfig configPath
      Nothing -> do
        mConfigPath <- findFirstExistingFile defaultConfigPaths
        case mConfigPath of
          Just configPath -> readConfig configPath
          Nothing -> do
            hPutStrLn @Text stderr
              "Configuration file not found, using default config \
              \for GitHub repositories\n"
            pure $ defConfig GitHub

    withinCI <- askWithinCI
    let showProgressBar = oShowProgressBar ?: not withinCI

    (ScanResult scanErrs repoInfo) <- allowRewrite showProgressBar $ \rw -> do
      let fullConfig = addTraversalOptions (cTraversal config) oTraversalOptions
      scanRepo rw (formats $ cScanners config) fullConfig oRoot

    when oVerbose $
      fmtLn $ "=== Repository data ===\n\n" <> indentF 2 (build repoInfo)

    unless (null scanErrs) . reportScanErrs $ sortBy (compare `on` seFile) scanErrs

    verifyRes <- allowRewrite showProgressBar $ \rw -> do
      let fullConfig = addVerifyOptions (cVerification config) oVerifyOptions
      verifyRepo rw fullConfig oMode oRoot repoInfo

    case verifyErrors verifyRes of
      Nothing | null scanErrs -> fmtLn "All repository links are valid."
      Nothing -> exitFailure
      Just (toList -> verifyErrs) -> do
        fmt "\n\n"
        reportVerifyErrs verifyErrs
        exitFailure
  where
    reportScanErrs errs = do
      void . fmt $ "=== Scan errors found ===\n\n" <>
        indentF 2 (blockListF' "➥ " build errs)
      fmtLn $ "Scan errors dumped, " <> build (length errs) <> " in total."

    reportVerifyErrs errs = do
      void . fmt $ "=== Invalid references found ===\n\n" <>
              indentF 2 (blockListF' "➥ " build errs)
      fmtLn $ "Invalid references dumped, " <> build (length errs) <> " in total."