packages feed

document-build-0.2.0.0: src/Document/Build/Error.hs

{-# LANGUAGE ConstraintKinds #-}
{-# LANGUAGE DeriveAnyClass #-}
{-# LANGUAGE DeriveDataTypeable #-}
{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE DerivingStrategies #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE TypeOperators #-}
{-# OPTIONS_GHC -Wall #-}

{- | Errors that occur while building documents.

A 'BuildError' is a sum: each constructor has a name, a prism. A constructor
with two or more fields holds a product type of its own, such as
'ProcessFailed', whose own name is the constructor's name. 'BuildError'
inherits the names of every type it holds: those it reaches through two or
more constructors, such as 'commandLine' and 'file', are affine traversals.

Code that fails with a 'BuildError', in an error type @e@ that it builds with
@'review' 'buildError'@, has the constraint @'ReviewBuildError' e@.
-}
module Document.Build.Error (
  -- * Type
  BuildError (..),
  ReviewBuildError,

  -- * Errors
  buildErrorExitCode,
  displayBuildError,

  -- * Optics
  BuildErrorLike (..),
  lawNamesBuildError,
  InheritBuildError,
  inheritBuildError,
  inheritsBuildError,
) where

import Control.DeepSeq (NFData)
import Control.Lens (Choice, Optic, prism', review)
import Data.Data (Data)
import Data.Foldable (toList)
import Data.Functor.Const (Const)
import Data.Functor.Identity (Identity)
import Data.Proxy (Proxy (..))
import Data.Tagged (Tagged)
import Document.Build.CommandLine (CommandLineLike (..), displayCommandLine)
import Document.Build.Error.FileTypeUnsupported (FileTypeUnsupported (..), FileTypeUnsupportedLike (..), inheritFileTypeUnsupported)
import Document.Build.Error.InputFilesTooFew (InputFilesTooFew (..), InputFilesTooFewLike (..), inheritInputFilesTooFew)
import Document.Build.Error.OutputFileMissing (OutputFileMissing (..), OutputFileMissingLike (..), inheritOutputFileMissing)
import Document.Build.Error.OutputNotInDirectory (OutputNotInDirectory (..), OutputNotInDirectoryLike (..), inheritOutputNotInDirectory)
import Document.Build.Error.ProcessFailed (ProcessFailed (..), ProcessFailedLike (..), inheritProcessFailed)
import Document.Build.Error.ProcessOutputUnrecognised (ProcessOutputUnrecognised (..), ProcessOutputUnrecognisedLike (..), inheritProcessOutputUnrecognised)
import Document.Build.Error.StringsMissing (StringsMissing (..), StringsMissingLike (..), inheritStringsMissing)
import Document.Build.Name.CommandLineArguments (CommandLineArgumentsLike (..))
import Document.Build.Name.CommandLineProgram (CommandLineProgramLike (..))
import Document.Build.Name.Directory (DirectoryLike (..))
import Document.Build.Name.ExpectedOutput (ExpectedOutputLike (..))
import Document.Build.Name.FileNotInGit (FileNotInGitLike (..))
import Document.Build.Name.FileNotUtf8 (FileNotUtf8Like (..))
import Document.Build.Name.InputFileCount (InputFileCountLike (..))
import Document.Build.Name.InputFileMissing (InputFileMissingLike (..))
import Document.Build.Name.InputFilesNeeded (InputFilesNeededLike (..))
import Document.Build.Name.MissingStrings (MissingStringsLike (..))
import Document.Build.Name.OutputDirectory (OutputDirectoryLike (..))
import Document.Build.Name.ShallowRepository (ShallowRepositoryLike (..))
import Document.Build.Name.StandardError (StandardErrorLike (..))
import Document.Build.Name.StandardOutput (StandardOutputLike (..))
import Document.Build.Name.SupportedExtensions (SupportedExtensionsLike (..))
import GHC.Generics (Generic)
import Natural (NotZero, notZeroOr)
import System.FilePather.Cycle (Cycle (..), CycleLike (..), displayCycle, inheritCycle)
import System.FilePather.Name.File (FileLike (..))
import System.FilePather.Name.StepInput (StepInputLike (..))
import System.FilePather.Name.StepName (StepNameLike (..))
import System.FilePather.Name.Steps (StepsLike (..))
import System.FilePather.RepeatsStep (RepeatsStepLike (..))
import System.FilePather.ReproducesInput (ReproducesInputLike (..))
import System.FilePather.Step (StepLike (..))
import System.FilePather.Trail (TrailLike (..))
import Prelude hiding (cycle)

{- $setup
>>> import Control.Lens (preview, review)
>>> import Data.List.NonEmpty (NonEmpty (..))
-}

{- | An error that occurs while building documents.

>>> preview inputFileMissing (InputFileMissing "report.fodt")
Just "report.fodt"

>>> review fileNotUtf8 "report.odt" :: BuildError
FileNotUtf8 "report.odt"

>>> preview file (BuildErrorStringsMissing (StringsMissing "report.fodt" ("${UPDATED}" :| [])))
Just "report.fodt"

@since 0.1.0.0
-}
data BuildError
  = -- | A process exited with a non-zero exit code, with its standard output
    -- and standard error.
    -- Its name is 'processFailed'.
    BuildErrorProcessFailed ProcessFailed
  | -- | The output of a process was not recognised, with a description of what
    -- was expected.
    -- Its name is 'processOutputUnrecognised'.
    BuildErrorProcessOutputUnrecognised ProcessOutputUnrecognised
  | -- | An input file does not exist.
    -- Its name is 'inputFileMissing'.
    InputFileMissing FilePath
  | -- | A process succeeded, but did not produce an output file it should
    -- have.
    -- Its name is 'outputFileMissing'.
    BuildErrorOutputFileMissing OutputFileMissing
  | -- | A file does not contain strings it is required to contain.
    -- Its name is 'stringsMissing'.
    BuildErrorStringsMissing StringsMissing
  | -- | A file does not have one of the supported extensions.
    -- Its name is 'fileTypeUnsupported'.
    BuildErrorFileTypeUnsupported FileTypeUnsupported
  | -- | A file has no history in git.
    -- Its name is 'fileNotInGit'.
    FileNotInGit FilePath
  | -- | A file is not valid UTF-8 text. ('fileNotUtf8')
    FileNotUtf8 FilePath
  | -- | A chain of build steps was stopped, because it would go round in a
    -- cycle.
    -- Its name is 'cycle'.
    BuildCycle Cycle
  | -- | A directory has fewer input files than a command needs.
    -- Its name is 'inputFilesTooFew'.
    BuildErrorInputFilesTooFew InputFilesTooFew
  | -- | A build step made a file outside the output directory.
    -- Its name is 'outputNotInDirectory'.
    BuildErrorOutputNotInDirectory OutputNotInDirectory
  | -- | The git history of a directory is shallow, so the last commit to
    -- change a file in it is not known.
    -- Its name is 'shallowRepository'.
    ShallowRepository FilePath
  deriving stock (Eq, Ord, Show, Generic, Data)
  deriving anyclass (NFData)

{- | The constraint of code that fails with a 'BuildError' in the error type
@e@: a 'BuildError' is made into an @e@ with @'review' 'buildError'@.

@since 0.2.0.0
-}
type ReviewBuildError e =
  BuildErrorLike Tagged Identity e e

{- | The class of 'BuildError'.

@since 0.2.0.0
-}
class BuildErrorLike p f x x' where
  buildError ::
    Optic p f x x' BuildError BuildError

{- | Identity

@since 0.2.0.0
-}
instance (x' ~ BuildError) => BuildErrorLike p f BuildError x' where
  buildError =
    id
  {-# INLINE buildError #-}

{- | Prism: 'processFailed' is the default name of 'BuildErrorProcessFailed'.

@since 0.2.0.0
-}
instance (Choice p, Applicative f, x' ~ BuildError) => ProcessFailedLike p f BuildError x' where
  processFailed =
    prism'
      BuildErrorProcessFailed
      ( \case
          BuildErrorProcessFailed x -> Just x
          BuildErrorProcessOutputUnrecognised _ -> Nothing
          InputFileMissing _ -> Nothing
          BuildErrorOutputFileMissing _ -> Nothing
          BuildErrorStringsMissing _ -> Nothing
          BuildErrorFileTypeUnsupported _ -> Nothing
          FileNotInGit _ -> Nothing
          FileNotUtf8 _ -> Nothing
          BuildCycle _ -> Nothing
          BuildErrorInputFilesTooFew _ -> Nothing
          BuildErrorOutputNotInDirectory _ -> Nothing
          ShallowRepository _ -> Nothing
      )
  {-# INLINE processFailed #-}

{- | Prism: 'processOutputUnrecognised' is the default name of 'BuildErrorProcessOutputUnrecognised'.

@since 0.2.0.0
-}
instance (Choice p, Applicative f, x' ~ BuildError) => ProcessOutputUnrecognisedLike p f BuildError x' where
  processOutputUnrecognised =
    prism'
      BuildErrorProcessOutputUnrecognised
      ( \case
          BuildErrorProcessFailed _ -> Nothing
          BuildErrorProcessOutputUnrecognised x -> Just x
          InputFileMissing _ -> Nothing
          BuildErrorOutputFileMissing _ -> Nothing
          BuildErrorStringsMissing _ -> Nothing
          BuildErrorFileTypeUnsupported _ -> Nothing
          FileNotInGit _ -> Nothing
          FileNotUtf8 _ -> Nothing
          BuildCycle _ -> Nothing
          BuildErrorInputFilesTooFew _ -> Nothing
          BuildErrorOutputNotInDirectory _ -> Nothing
          ShallowRepository _ -> Nothing
      )
  {-# INLINE processOutputUnrecognised #-}

{- | Prism

@since 0.2.0.0
-}
instance (Choice p, Applicative f, x' ~ BuildError) => InputFileMissingLike p f BuildError x' where
  inputFileMissing =
    prism'
      InputFileMissing
      ( \case
          BuildErrorProcessFailed _ -> Nothing
          BuildErrorProcessOutputUnrecognised _ -> Nothing
          InputFileMissing x -> Just x
          BuildErrorOutputFileMissing _ -> Nothing
          BuildErrorStringsMissing _ -> Nothing
          BuildErrorFileTypeUnsupported _ -> Nothing
          FileNotInGit _ -> Nothing
          FileNotUtf8 _ -> Nothing
          BuildCycle _ -> Nothing
          BuildErrorInputFilesTooFew _ -> Nothing
          BuildErrorOutputNotInDirectory _ -> Nothing
          ShallowRepository _ -> Nothing
      )
  {-# INLINE inputFileMissing #-}

{- | Prism: 'outputFileMissing' is the default name of 'BuildErrorOutputFileMissing'.

@since 0.2.0.0
-}
instance (Choice p, Applicative f, x' ~ BuildError) => OutputFileMissingLike p f BuildError x' where
  outputFileMissing =
    prism'
      BuildErrorOutputFileMissing
      ( \case
          BuildErrorProcessFailed _ -> Nothing
          BuildErrorProcessOutputUnrecognised _ -> Nothing
          InputFileMissing _ -> Nothing
          BuildErrorOutputFileMissing x -> Just x
          BuildErrorStringsMissing _ -> Nothing
          BuildErrorFileTypeUnsupported _ -> Nothing
          FileNotInGit _ -> Nothing
          FileNotUtf8 _ -> Nothing
          BuildCycle _ -> Nothing
          BuildErrorInputFilesTooFew _ -> Nothing
          BuildErrorOutputNotInDirectory _ -> Nothing
          ShallowRepository _ -> Nothing
      )
  {-# INLINE outputFileMissing #-}

{- | Prism: 'stringsMissing' is the default name of 'BuildErrorStringsMissing'.

@since 0.2.0.0
-}
instance (Choice p, Applicative f, x' ~ BuildError) => StringsMissingLike p f BuildError x' where
  stringsMissing =
    prism'
      BuildErrorStringsMissing
      ( \case
          BuildErrorProcessFailed _ -> Nothing
          BuildErrorProcessOutputUnrecognised _ -> Nothing
          InputFileMissing _ -> Nothing
          BuildErrorOutputFileMissing _ -> Nothing
          BuildErrorStringsMissing x -> Just x
          BuildErrorFileTypeUnsupported _ -> Nothing
          FileNotInGit _ -> Nothing
          FileNotUtf8 _ -> Nothing
          BuildCycle _ -> Nothing
          BuildErrorInputFilesTooFew _ -> Nothing
          BuildErrorOutputNotInDirectory _ -> Nothing
          ShallowRepository _ -> Nothing
      )
  {-# INLINE stringsMissing #-}

{- | Prism: 'fileTypeUnsupported' is the default name of 'BuildErrorFileTypeUnsupported'.

@since 0.2.0.0
-}
instance (Choice p, Applicative f, x' ~ BuildError) => FileTypeUnsupportedLike p f BuildError x' where
  fileTypeUnsupported =
    prism'
      BuildErrorFileTypeUnsupported
      ( \case
          BuildErrorProcessFailed _ -> Nothing
          BuildErrorProcessOutputUnrecognised _ -> Nothing
          InputFileMissing _ -> Nothing
          BuildErrorOutputFileMissing _ -> Nothing
          BuildErrorStringsMissing _ -> Nothing
          BuildErrorFileTypeUnsupported x -> Just x
          FileNotInGit _ -> Nothing
          FileNotUtf8 _ -> Nothing
          BuildCycle _ -> Nothing
          BuildErrorInputFilesTooFew _ -> Nothing
          BuildErrorOutputNotInDirectory _ -> Nothing
          ShallowRepository _ -> Nothing
      )
  {-# INLINE fileTypeUnsupported #-}

{- | Prism

@since 0.2.0.0
-}
instance (Choice p, Applicative f, x' ~ BuildError) => FileNotInGitLike p f BuildError x' where
  fileNotInGit =
    prism'
      FileNotInGit
      ( \case
          BuildErrorProcessFailed _ -> Nothing
          BuildErrorProcessOutputUnrecognised _ -> Nothing
          InputFileMissing _ -> Nothing
          BuildErrorOutputFileMissing _ -> Nothing
          BuildErrorStringsMissing _ -> Nothing
          BuildErrorFileTypeUnsupported _ -> Nothing
          FileNotInGit x -> Just x
          FileNotUtf8 _ -> Nothing
          BuildCycle _ -> Nothing
          BuildErrorInputFilesTooFew _ -> Nothing
          BuildErrorOutputNotInDirectory _ -> Nothing
          ShallowRepository _ -> Nothing
      )
  {-# INLINE fileNotInGit #-}

{- | Prism

@since 0.2.0.0
-}
instance (Choice p, Applicative f, x' ~ BuildError) => FileNotUtf8Like p f BuildError x' where
  fileNotUtf8 =
    prism'
      FileNotUtf8
      ( \case
          BuildErrorProcessFailed _ -> Nothing
          BuildErrorProcessOutputUnrecognised _ -> Nothing
          InputFileMissing _ -> Nothing
          BuildErrorOutputFileMissing _ -> Nothing
          BuildErrorStringsMissing _ -> Nothing
          BuildErrorFileTypeUnsupported _ -> Nothing
          FileNotInGit _ -> Nothing
          FileNotUtf8 x -> Just x
          BuildCycle _ -> Nothing
          BuildErrorInputFilesTooFew _ -> Nothing
          BuildErrorOutputNotInDirectory _ -> Nothing
          ShallowRepository _ -> Nothing
      )
  {-# INLINE fileNotUtf8 #-}

{- | Prism: 'cycle' is the default name of 'BuildCycle'.

@since 0.2.0.0
-}
instance (Choice p, Applicative f, x' ~ BuildError) => CycleLike p f BuildError x' where
  cycle =
    prism'
      BuildCycle
      ( \case
          BuildErrorProcessFailed _ -> Nothing
          BuildErrorProcessOutputUnrecognised _ -> Nothing
          InputFileMissing _ -> Nothing
          BuildErrorOutputFileMissing _ -> Nothing
          BuildErrorStringsMissing _ -> Nothing
          BuildErrorFileTypeUnsupported _ -> Nothing
          FileNotInGit _ -> Nothing
          FileNotUtf8 _ -> Nothing
          BuildCycle x -> Just x
          BuildErrorInputFilesTooFew _ -> Nothing
          BuildErrorOutputNotInDirectory _ -> Nothing
          ShallowRepository _ -> Nothing
      )
  {-# INLINE cycle #-}

{- | Prism: 'inputFilesTooFew' is the default name of 'BuildErrorInputFilesTooFew'.

@since 0.2.0.0
-}
instance (Choice p, Applicative f, x' ~ BuildError) => InputFilesTooFewLike p f BuildError x' where
  inputFilesTooFew =
    prism'
      BuildErrorInputFilesTooFew
      ( \case
          BuildErrorProcessFailed _ -> Nothing
          BuildErrorProcessOutputUnrecognised _ -> Nothing
          InputFileMissing _ -> Nothing
          BuildErrorOutputFileMissing _ -> Nothing
          BuildErrorStringsMissing _ -> Nothing
          BuildErrorFileTypeUnsupported _ -> Nothing
          FileNotInGit _ -> Nothing
          FileNotUtf8 _ -> Nothing
          BuildCycle _ -> Nothing
          BuildErrorInputFilesTooFew x -> Just x
          BuildErrorOutputNotInDirectory _ -> Nothing
          ShallowRepository _ -> Nothing
      )
  {-# INLINE inputFilesTooFew #-}

{- | Prism: 'outputNotInDirectory' is the default name of 'BuildErrorOutputNotInDirectory'.

@since 0.2.0.0
-}
instance (Choice p, Applicative f, x' ~ BuildError) => OutputNotInDirectoryLike p f BuildError x' where
  outputNotInDirectory =
    prism'
      BuildErrorOutputNotInDirectory
      ( \case
          BuildErrorProcessFailed _ -> Nothing
          BuildErrorProcessOutputUnrecognised _ -> Nothing
          InputFileMissing _ -> Nothing
          BuildErrorOutputFileMissing _ -> Nothing
          BuildErrorStringsMissing _ -> Nothing
          BuildErrorFileTypeUnsupported _ -> Nothing
          FileNotInGit _ -> Nothing
          FileNotUtf8 _ -> Nothing
          BuildCycle _ -> Nothing
          BuildErrorInputFilesTooFew _ -> Nothing
          BuildErrorOutputNotInDirectory x -> Just x
          ShallowRepository _ -> Nothing
      )
  {-# INLINE outputNotInDirectory #-}

{- | Prism

@since 0.2.0.0
-}
instance (Choice p, Applicative f, x' ~ BuildError) => ShallowRepositoryLike p f BuildError x' where
  shallowRepository =
    prism'
      ShallowRepository
      ( \case
          BuildErrorProcessFailed _ -> Nothing
          BuildErrorProcessOutputUnrecognised _ -> Nothing
          InputFileMissing _ -> Nothing
          BuildErrorOutputFileMissing _ -> Nothing
          BuildErrorStringsMissing _ -> Nothing
          BuildErrorFileTypeUnsupported _ -> Nothing
          FileNotInGit _ -> Nothing
          FileNotUtf8 _ -> Nothing
          BuildCycle _ -> Nothing
          BuildErrorInputFilesTooFew _ -> Nothing
          BuildErrorOutputNotInDirectory _ -> Nothing
          ShallowRepository x -> Just x
      )
  {-# INLINE shallowRepository #-}

{- | Affine traversal: 'commandLine' is reached through 3 constructors, once each: 'BuildErrorProcessFailed', 'BuildErrorProcessOutputUnrecognised', 'BuildErrorOutputFileMissing'.

@since 0.2.0.0
-}
instance (p ~ (->), Applicative f, x' ~ BuildError) => CommandLineLike p f BuildError x' where
  commandLine f = \case
    BuildErrorProcessFailed x -> BuildErrorProcessFailed <$> commandLine f x
    BuildErrorProcessOutputUnrecognised x -> BuildErrorProcessOutputUnrecognised <$> commandLine f x
    InputFileMissing x -> pure (InputFileMissing x)
    BuildErrorOutputFileMissing x -> BuildErrorOutputFileMissing <$> commandLine f x
    BuildErrorStringsMissing x -> pure (BuildErrorStringsMissing x)
    BuildErrorFileTypeUnsupported x -> pure (BuildErrorFileTypeUnsupported x)
    FileNotInGit x -> pure (FileNotInGit x)
    FileNotUtf8 x -> pure (FileNotUtf8 x)
    BuildCycle x -> pure (BuildCycle x)
    BuildErrorInputFilesTooFew x -> pure (BuildErrorInputFilesTooFew x)
    BuildErrorOutputNotInDirectory x -> pure (BuildErrorOutputNotInDirectory x)
    ShallowRepository x -> pure (ShallowRepository x)
  {-# INLINE commandLine #-}

{- | Affine traversal: through 'BuildErrorProcessFailed', a prism, then 'ProcessFailed''s 'standardOutput', a lens.

@since 0.2.0.0
-}
instance (p ~ (->), Applicative f, x' ~ BuildError) => StandardOutputLike p f BuildError x' where
  standardOutput =
    processFailed . standardOutput
  {-# INLINE standardOutput #-}

{- | Affine traversal: through 'BuildErrorProcessFailed', a prism, then 'ProcessFailed''s 'standardError', a lens.

@since 0.2.0.0
-}
instance (p ~ (->), Applicative f, x' ~ BuildError) => StandardErrorLike p f BuildError x' where
  standardError =
    processFailed . standardError
  {-# INLINE standardError #-}

{- | Affine traversal: 'commandLineProgram' is reached through 3 constructors, once each: 'BuildErrorProcessFailed', 'BuildErrorProcessOutputUnrecognised', 'BuildErrorOutputFileMissing'.

@since 0.2.0.0
-}
instance (p ~ (->), Applicative f, x' ~ BuildError) => CommandLineProgramLike p f BuildError x' where
  commandLineProgram f = \case
    BuildErrorProcessFailed x -> BuildErrorProcessFailed <$> commandLineProgram f x
    BuildErrorProcessOutputUnrecognised x -> BuildErrorProcessOutputUnrecognised <$> commandLineProgram f x
    InputFileMissing x -> pure (InputFileMissing x)
    BuildErrorOutputFileMissing x -> BuildErrorOutputFileMissing <$> commandLineProgram f x
    BuildErrorStringsMissing x -> pure (BuildErrorStringsMissing x)
    BuildErrorFileTypeUnsupported x -> pure (BuildErrorFileTypeUnsupported x)
    FileNotInGit x -> pure (FileNotInGit x)
    FileNotUtf8 x -> pure (FileNotUtf8 x)
    BuildCycle x -> pure (BuildCycle x)
    BuildErrorInputFilesTooFew x -> pure (BuildErrorInputFilesTooFew x)
    BuildErrorOutputNotInDirectory x -> pure (BuildErrorOutputNotInDirectory x)
    ShallowRepository x -> pure (ShallowRepository x)
  {-# INLINE commandLineProgram #-}

{- | Affine traversal: 'commandLineArguments' is reached through 3 constructors, once each: 'BuildErrorProcessFailed', 'BuildErrorProcessOutputUnrecognised', 'BuildErrorOutputFileMissing'.

@since 0.2.0.0
-}
instance (p ~ (->), Applicative f, x' ~ BuildError) => CommandLineArgumentsLike p f BuildError x' where
  commandLineArguments f = \case
    BuildErrorProcessFailed x -> BuildErrorProcessFailed <$> commandLineArguments f x
    BuildErrorProcessOutputUnrecognised x -> BuildErrorProcessOutputUnrecognised <$> commandLineArguments f x
    InputFileMissing x -> pure (InputFileMissing x)
    BuildErrorOutputFileMissing x -> BuildErrorOutputFileMissing <$> commandLineArguments f x
    BuildErrorStringsMissing x -> pure (BuildErrorStringsMissing x)
    BuildErrorFileTypeUnsupported x -> pure (BuildErrorFileTypeUnsupported x)
    FileNotInGit x -> pure (FileNotInGit x)
    FileNotUtf8 x -> pure (FileNotUtf8 x)
    BuildCycle x -> pure (BuildCycle x)
    BuildErrorInputFilesTooFew x -> pure (BuildErrorInputFilesTooFew x)
    BuildErrorOutputNotInDirectory x -> pure (BuildErrorOutputNotInDirectory x)
    ShallowRepository x -> pure (ShallowRepository x)
  {-# INLINE commandLineArguments #-}

{- | Affine traversal: through 'BuildErrorProcessOutputUnrecognised', a prism, then 'ProcessOutputUnrecognised''s 'expectedOutput', a lens.

@since 0.2.0.0
-}
instance (p ~ (->), Applicative f, x' ~ BuildError) => ExpectedOutputLike p f BuildError x' where
  expectedOutput =
    processOutputUnrecognised . expectedOutput
  {-# INLINE expectedOutput #-}

{- | Affine traversal: 'file' is reached through 5 constructors, once each: 'BuildErrorOutputFileMissing', 'BuildErrorStringsMissing', 'BuildErrorFileTypeUnsupported', 'BuildCycle', 'BuildErrorOutputNotInDirectory'.

@since 0.2.0.0
-}
instance (p ~ (->), Applicative f, x' ~ BuildError) => FileLike p f BuildError x' where
  file f = \case
    BuildErrorProcessFailed x -> pure (BuildErrorProcessFailed x)
    BuildErrorProcessOutputUnrecognised x -> pure (BuildErrorProcessOutputUnrecognised x)
    InputFileMissing x -> pure (InputFileMissing x)
    BuildErrorOutputFileMissing x -> BuildErrorOutputFileMissing <$> file f x
    BuildErrorStringsMissing x -> BuildErrorStringsMissing <$> file f x
    BuildErrorFileTypeUnsupported x -> BuildErrorFileTypeUnsupported <$> file f x
    FileNotInGit x -> pure (FileNotInGit x)
    FileNotUtf8 x -> pure (FileNotUtf8 x)
    BuildCycle x -> BuildCycle <$> file f x
    BuildErrorInputFilesTooFew x -> pure (BuildErrorInputFilesTooFew x)
    BuildErrorOutputNotInDirectory x -> BuildErrorOutputNotInDirectory <$> file f x
    ShallowRepository x -> pure (ShallowRepository x)
  {-# INLINE file #-}

{- | Affine traversal: through 'BuildErrorStringsMissing', a prism, then 'StringsMissing''s 'missingStrings', a lens.

@since 0.2.0.0
-}
instance (p ~ (->), Applicative f, x' ~ BuildError) => MissingStringsLike p f BuildError x' where
  missingStrings =
    stringsMissing . missingStrings
  {-# INLINE missingStrings #-}

{- | Affine traversal: through 'BuildErrorFileTypeUnsupported', a prism, then 'FileTypeUnsupported''s 'supportedExtensions', a lens.

@since 0.2.0.0
-}
instance (p ~ (->), Applicative f, x' ~ BuildError) => SupportedExtensionsLike p f BuildError x' where
  supportedExtensions =
    fileTypeUnsupported . supportedExtensions
  {-# INLINE supportedExtensions #-}

{- | Prism: through 'BuildCycle', a prism, then 'Cycle''s 'reproducesInput', a prism.

@since 0.2.0.0
-}
instance (Choice p, Applicative f, x' ~ BuildError) => ReproducesInputLike p f BuildError x' where
  reproducesInput =
    cycle . reproducesInput
  {-# INLINE reproducesInput #-}

{- | Prism: through 'BuildCycle', a prism, then 'Cycle''s 'repeatsStep', a prism.

@since 0.2.0.0
-}
instance (Choice p, Applicative f, x' ~ BuildError) => RepeatsStepLike p f BuildError x' where
  repeatsStep =
    cycle . repeatsStep
  {-# INLINE repeatsStep #-}

{- | Affine traversal: through 'BuildCycle', a prism, then 'Cycle''s 'step', an affine traversal.

@since 0.2.0.0
-}
instance (p ~ (->), Applicative f, x' ~ BuildError) => StepLike p f BuildError x' where
  step =
    cycle . step
  {-# INLINE step #-}

{- | Affine traversal: through 'BuildCycle', a prism, then 'Cycle''s 'trail', an affine traversal.

@since 0.2.0.0
-}
instance (p ~ (->), Applicative f, x' ~ BuildError) => TrailLike p f BuildError x' where
  trail =
    cycle . trail
  {-# INLINE trail #-}

{- | Affine traversal: through 'BuildCycle', a prism, then 'Cycle''s 'stepName', an affine traversal.

@since 0.2.0.0
-}
instance (p ~ (->), Applicative f, x' ~ BuildError) => StepNameLike p f BuildError x' where
  stepName =
    cycle . stepName
  {-# INLINE stepName #-}

{- | Affine traversal: through 'BuildCycle', a prism, then 'Cycle''s 'stepInput', an affine traversal.

@since 0.2.0.0
-}
instance (p ~ (->), Applicative f, x' ~ BuildError) => StepInputLike p f BuildError x' where
  stepInput =
    cycle . stepInput
  {-# INLINE stepInput #-}

{- | Affine traversal: through 'BuildCycle', a prism, then 'Cycle''s 'steps', an affine traversal.

@since 0.2.0.0
-}
instance (p ~ (->), Applicative f, x' ~ BuildError) => StepsLike p f BuildError x' where
  steps =
    cycle . steps
  {-# INLINE steps #-}

{- | Affine traversal: through 'BuildErrorInputFilesTooFew', a prism, then 'InputFilesTooFew''s 'directory', a lens.

@since 0.2.0.0
-}
instance (p ~ (->), Applicative f, x' ~ BuildError) => DirectoryLike p f BuildError x' where
  directory =
    inputFilesTooFew . directory
  {-# INLINE directory #-}

{- | Affine traversal: through 'BuildErrorInputFilesTooFew', a prism, then 'InputFilesTooFew''s 'inputFileCount', a lens.

@since 0.2.0.0
-}
instance (p ~ (->), Applicative f, x' ~ BuildError) => InputFileCountLike p f BuildError x' where
  inputFileCount =
    inputFilesTooFew . inputFileCount
  {-# INLINE inputFileCount #-}

{- | Affine traversal: through 'BuildErrorInputFilesTooFew', a prism, then 'InputFilesTooFew''s 'inputFilesNeeded', a lens.

@since 0.2.0.0
-}
instance (p ~ (->), Applicative f, x' ~ BuildError) => InputFilesNeededLike p f BuildError x' where
  inputFilesNeeded =
    inputFilesTooFew . inputFilesNeeded
  {-# INLINE inputFilesNeeded #-}

{- | Affine traversal: through 'BuildErrorOutputNotInDirectory', a prism, then 'OutputNotInDirectory''s 'outputDirectory', a lens.

@since 0.2.0.0
-}
instance (p ~ (->), Applicative f, x' ~ BuildError) => OutputDirectoryLike p f BuildError x' where
  outputDirectory =
    outputNotInDirectory . outputDirectory
  {-# INLINE outputDirectory #-}

{- | Rebuild a value using only the name of each constructor.

Laws:

> lawNamesBuildError == id

@since 0.2.0.0
-}
lawNamesBuildError :: BuildError -> BuildError
lawNamesBuildError = \case
  BuildErrorProcessFailed x -> review processFailed x
  BuildErrorProcessOutputUnrecognised x -> review processOutputUnrecognised x
  InputFileMissing x -> review inputFileMissing x
  BuildErrorOutputFileMissing x -> review outputFileMissing x
  BuildErrorStringsMissing x -> review stringsMissing x
  BuildErrorFileTypeUnsupported x -> review fileTypeUnsupported x
  FileNotInGit x -> review fileNotInGit x
  FileNotUtf8 x -> review fileNotUtf8 x
  BuildCycle x -> review cycle x
  BuildErrorInputFilesTooFew x -> review inputFilesTooFew x
  BuildErrorOutputNotInDirectory x -> review outputNotInDirectory x
  ShallowRepository x -> review shallowRepository x

{- | The classes that a data type holding 'BuildError' inherits.

@since 0.2.0.0
-}
type InheritBuildError s =
  ( BuildErrorLike (->) (Const ()) s s
  , ProcessFailedLike (->) (Const ()) s s
  , ProcessOutputUnrecognisedLike (->) (Const ()) s s
  , InputFileMissingLike (->) (Const ()) s s
  , OutputFileMissingLike (->) (Const ()) s s
  , StringsMissingLike (->) (Const ()) s s
  , FileTypeUnsupportedLike (->) (Const ()) s s
  , FileNotInGitLike (->) (Const ()) s s
  , FileNotUtf8Like (->) (Const ()) s s
  , CycleLike (->) (Const ()) s s
  , InputFilesTooFewLike (->) (Const ()) s s
  , OutputNotInDirectoryLike (->) (Const ()) s s
  , ShallowRepositoryLike (->) (Const ()) s s
  , CommandLineLike (->) (Const ()) s s
  , StandardOutputLike (->) (Const ()) s s
  , StandardErrorLike (->) (Const ()) s s
  , CommandLineProgramLike (->) (Const ()) s s
  , CommandLineArgumentsLike (->) (Const ()) s s
  , ExpectedOutputLike (->) (Const ()) s s
  , FileLike (->) (Const ()) s s
  , MissingStringsLike (->) (Const ()) s s
  , SupportedExtensionsLike (->) (Const ()) s s
  , ReproducesInputLike (->) (Const ()) s s
  , RepeatsStepLike (->) (Const ()) s s
  , StepLike (->) (Const ()) s s
  , TrailLike (->) (Const ()) s s
  , StepNameLike (->) (Const ()) s s
  , StepInputLike (->) (Const ()) s s
  , StepsLike (->) (Const ()) s s
  , DirectoryLike (->) (Const ()) s s
  , InputFileCountLike (->) (Const ()) s s
  , InputFilesNeededLike (->) (Const ()) s s
  , OutputDirectoryLike (->) (Const ()) s s
  )

{- | Check, where it is applied, that @s@ inherits every name of 'BuildError'.

@since 0.2.0.0
-}
inheritBuildError :: (InheritBuildError s) => proxy s -> ()
inheritBuildError _ =
  ()

{- | 'BuildError' inherits every name of each data type that it holds in a
constructor.

@since 0.2.0.0
-}
inheritsBuildError :: [()]
inheritsBuildError =
  let p = Proxy :: Proxy BuildError
   in [ inheritProcessFailed p
      , inheritProcessOutputUnrecognised p
      , inheritOutputFileMissing p
      , inheritStringsMissing p
      , inheritFileTypeUnsupported p
      , inheritCycle p
      , inheritInputFilesTooFew p
      , inheritOutputNotInDirectory p
      ]

{- | The exit code for an error that is not the failure of a process. A
failed process keeps its own exit code, and this function gives @1@ for it.

>>> buildErrorExitCode (InputFileMissing "report.fodt")
NotZero True (Positive 22)

@since 0.1.0.0
-}
buildErrorExitCode :: BuildError -> NotZero
buildErrorExitCode e =
  notZeroOr $
    case e of
      BuildErrorProcessFailed _ -> 1 :: Int
      FileNotInGit _ -> 12
      BuildErrorProcessOutputUnrecognised _ -> 14
      InputFileMissing _ -> 22
      BuildErrorStringsMissing _ -> 26
      BuildErrorOutputFileMissing _ -> 28
      BuildErrorFileTypeUnsupported _ -> 30
      FileNotUtf8 _ -> 32
      BuildCycle _ -> 34
      BuildErrorInputFilesTooFew _ -> 36
      BuildErrorOutputNotInDirectory _ -> 38
      ShallowRepository _ -> 40

{- | A description of an error, for people to read.

>>> putStr (displayBuildError (BuildErrorStringsMissing (StringsMissing "report.fodt" ("${UPDATED}" :| []))))
report.fodt does not contain:
  ${UPDATED}

@since 0.1.0.0
-}
displayBuildError :: BuildError -> String
displayBuildError e =
  case e of
    BuildErrorProcessFailed (ProcessFailed c o r) ->
      unlines (("command failed: " <> displayCommandLine c) : section "stdout" o <> section "stderr" r)
    BuildErrorProcessOutputUnrecognised (ProcessOutputUnrecognised c x) ->
      unlines ["output not recognised: " <> displayCommandLine c, "  expected " <> x]
    InputFileMissing p ->
      unlines ["input file does not exist: " <> p]
    BuildErrorOutputFileMissing (OutputFileMissing c p) ->
      unlines ["command did not produce " <> p <> ": " <> displayCommandLine c]
    BuildErrorStringsMissing (StringsMissing p xs) ->
      unlines ((p <> " does not contain:") : fmap ("  " <>) (toList xs))
    BuildErrorFileTypeUnsupported (FileTypeUnsupported p xs) ->
      unlines [p <> " is not one of the supported file types: " <> unwords xs]
    FileNotInGit p ->
      unlines ["file has no git history: " <> p]
    FileNotUtf8 p ->
      unlines ["file is not UTF-8 text: " <> p]
    BuildCycle c ->
      unlines ["build step stopped, because it would go round in a cycle:", "  " <> displayCycle c]
    BuildErrorInputFilesTooFew (InputFilesTooFew d n m) ->
      unlines ["directory has " <> show n <> " input files, and needs at least " <> show m <> ": " <> d]
    BuildErrorOutputNotInDirectory (OutputNotInDirectory f d) ->
      unlines ["build step made a file outside the output directory " <> d <> ": " <> f]
    ShallowRepository d ->
      unlines ["git history is shallow, so the last commit to change each file is not known: " <> d, "  fetch the whole history, such as with GIT_DEPTH: 0 in GitLab CI"]

section :: String -> String -> [String]
section name x =
  if null x
    then []
    else ("  " <> name <> ":") : fmap ("    " <>) (lines x)