pantry-0.9.0: app/test-pretty-exceptions/Main.hs
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE RecordWildCards #-}
{-# LANGUAGE TupleSections #-}
-- | An executable to allow a person to inspect in a terminal the form of
-- Pantry's pretty exceptions.
module Main
( main
) where
import qualified Data.Conduit.Tar as Tar
import Data.Maybe ( fromJust )
import qualified Data.Text as T
import qualified Distribution.Parsec.Error as C
import qualified Distribution.Parsec.Position as C
import qualified Distribution.Parsec.Warning as C
import qualified Distribution.Types.PackageName as C
import qualified Distribution.Types.Version as C
import Options.Applicative
( Parser, (<**>), auto, execParser, fullDesc, header, help
, helper, info, long, metavar, option, progDesc, showDefault
, strOption, value
)
import Network.HTTP.Types.Status ( Status, mkStatus )
import Pantry
( ArchiveLocation (..), BlobKey (..), CabalFileInfo (..)
, FileSize (..), FuzzyResults (..), Mismatch (..)
, PackageName, PantryException (..), PackageIdentifier (..)
, PackageIdentifierRevision (..), PackageMetadata (..)
, RawPackageLocationImmutable (..), RawPackageMetadata (..)
, RawSnapshotLocation (..), RelFilePath (..), Repo (..)
, RepoType (..), ResolvedPath (..), Revision (..), SHA256
, SafeFilePath, SimpleRepo (..), SnapName (..)
, TreeKey (..), Version, WantedCompiler (..), mkSafeFilePath
)
import Pantry.SHA256 ( hashBytes )
import Path ( File )
import PathAbsExamples
( pathAbsDirExample, pathAbsFileExample
, pathAbsFileExamples
)
import RIO
import qualified RIO.List as L
import RIO.NonEmpty ( nonEmpty )
import RIO.PrettyPrint ( pretty, prettyError )
import RIO.PrettyPrint.Simple ( SimplePrettyApp, runSimplePrettyApp )
import RIO.PrettyPrint.StylesUpdate
( StylesUpdate, parseStylesUpdateFromString )
import RIO.Time ( fromGregorian )
import System.Terminal ( hIsTerminalDeviceOrMinTTY, getTerminalWidth )
-- | Type representing options that can be specified at the command line
data Options = Options
{ colours :: String
, theme :: Theme
}
-- | Type representing styles identified by a theme name
data Theme
= Default
| SolarizedDark
deriving (Bounded, Enum, Read, Show)
options :: Parser Options
options = Options
<$> strOption
( long "colours"
<> metavar "STYLES"
<> help "Specify the output styles; STYLES is a colon-delimited \
\sequence of key=value, where 'key' is a style name and \
\'value' is a semicolon-delimited list of 'ANSI' SGR (Select \
\Graphic Rendition) control codes (in decimal). In shells \
\where a semicolon is a command separator, enclose STYLES in \
\quotes."
<> value ""
)
<*> option auto
( long "theme"
<> metavar "THEME"
<> help ( "Specify a theme for output styles. THEME is one of: "
<> showThemes <> "."
)
<> value Default
<> showDefault
)
where
showThemes = L.intercalate " " $ map show ([minBound .. maxBound] :: [Theme])
fromTheme :: Theme -> StylesUpdate
fromTheme Default = mempty
fromTheme SolarizedDark = parseStylesUpdateFromString
"error=31:good=32:shell=35:dir=34:recommendation=32:target=95:module=35:package-component=95:secondary=92:highlight=32"
main :: IO ()
main = do
isTerminal <- hIsTerminalDeviceOrMinTTY stderr
if isTerminal
then do
terminalWidth <- fromMaybe 80 <$> getTerminalWidth
mainInTerminal terminalWidth =<< execParser opts
else
putStrLn "This executable is intended to be run with the standard error \
\ channel connected to a terminal. No terminal detected."
where
opts = info (options <**> helper)
( fullDesc
<> progDesc "Allows a person to inspect in a terminal the form of Pantry's \
\pretty exceptions."
<> header "test-pretty-exceptions - test Pantry's pretty exceptions"
)
mainInTerminal :: Int -> Options -> IO ()
mainInTerminal terminalWidth Options{..} = do
let stylesUpdate = fromTheme theme <> parseStylesUpdateFromString colours
runSimplePrettyApp terminalWidth stylesUpdate action
where
action :: RIO SimplePrettyApp ()
action = mapM_ (prettyError . pretty) examples
-- | The intention is that there shoud be examples for every data constructor of
-- the PantryException type.
examples :: [PantryException]
examples = concat
[ [ PackageIdentifierRevisionParseFail hackageMsg ]
, [ InvalidCabalFile loc version pErrorExamples pWarningExamples
| loc <- map Left rawPackageLocationImmutableExamples <> [Right pathAbsFileExample]
, version <- [Nothing, Just versionExample]
]
, [ TreeWithoutCabalFile rawPackageLocationImmutable
| rawPackageLocationImmutable <- rawPackageLocationImmutableExamples
]
, [ TreeWithMultipleCabalFiles rawPackageLocationImmutable safeFilePathExamples
| rawPackageLocationImmutable <- rawPackageLocationImmutableExamples
]
, [ MismatchedCabalName pathAbsFileExample packageNameExample ]
, [ NoCabalFileFound pathAbsDirExample ]
, [ MultipleCabalFilesFound pathAbsDirExample pathAbsFileExamples ]
, [ InvalidWantedCompiler "my-wanted-compiler" ]
, [ InvalidSnapshotLocation pathAbsDirExample rawPathExample ]
, [ InvalidOverrideCompiler wantedCompiler1 wantedCompiler2
| wantedCompiler1 <- wantedCompilerExamples
, wantedCompiler2 <- wantedCompilerExamples
]
, [ InvalidFilePathSnapshot rawPathExample ]
, [ InvalidSnapshot rawSnapshotLocation someExceptionExample
| rawSnapshotLocation <- rawSnapshotLocationExamples
]
, [ MismatchedPackageMetadata rawPackageLocationImmutable rawPackageMetadata treeKey packageIdentifierExample
| rawPackageLocationImmutable <- rawPackageLocationImmutableExamples
, rawPackageMetadata <- rawPackageMetadataExamples
, treeKey <- [Nothing, Just treeKeyExample]
]
, [ Non200ResponseStatus statusExample ]
, [ InvalidBlobKey (Mismatch blobKeyExample blobKeyExample) ]
, [ Couldn'tParseSnapshot rawSnapshotLocation errorMessageExample
| rawSnapshotLocation <- rawSnapshotLocationExamples
]
, [ WrongCabalFileName rawPackageLocationImmutable safeFilePathExample packageNameExample
| rawPackageLocationImmutable <- rawPackageLocationImmutableExamples
]
, [ DownloadInvalidSHA256 urlExample (Mismatch sha256Example sha256Example) ]
, [ DownloadInvalidSize urlExample (Mismatch fileSizeExample fileSizeExample) ]
, [ DownloadTooLarge urlExample (Mismatch fileSizeExample fileSizeExample) ]
, [ LocalInvalidSHA256 pathAbsFileExample (Mismatch sha256Example sha256Example) ]
, [ LocalInvalidSize pathAbsFileExample (Mismatch fileSizeExample fileSizeExample) ]
, [ UnknownArchiveType archiveLocation
| archiveLocation <- archiveLocationExamples
]
, [ InvalidTarFileType archiveLocation filePathExample fileTypeExample
| archiveLocation <- archiveLocationExamples
]
, [ UnsupportedTarball archiveLocation (T.pack errorMessageExample)
| archiveLocation <- archiveLocationExamples
]
, [ NoHackageCryptographicHash packageIdentifierExample ]
, [ FailedToCloneRepo simpleRepoExample ]
, [ TreeReferencesMissingBlob rawPackageLocationImmutable safeFilePathExample blobKeyExample
| rawPackageLocationImmutable <- rawPackageLocationImmutableExamples
]
, [ CompletePackageMetadataMismatch rawPackageLocationImmutable packageMetadataExample
| rawPackageLocationImmutable <- rawPackageLocationImmutableExamples
]
, [ CRC32Mismatch archiveLocation filePathExample (Mismatch 1024 1024 )
| archiveLocation <- archiveLocationExamples
]
, [ UnknownHackagePackage packageIdentifierRevisionExample fuzzyResults
| packageIdentifierRevisionExample <- packageIdentifierRevisionExamples
, fuzzyResults <- fuzzyResultsExamples
]
, [ CannotCompleteRepoNonSHA1 repoExample ]
, [ MutablePackageLocationFromUrl urlExample ]
, [ MismatchedCabalFileForHackage packageIdentifierRevision (Mismatch packageIdentifierExample packageIdentifierExample)
| packageIdentifierRevision <- packageIdentifierRevisionExamples
]
, [ PackageNameParseFail rawPackageName ]
, [ PackageVersionParseFail rawPackageVersion ]
, [ InvalidCabalFilePath pathAbsFileExample ]
, [ DuplicatePackageNames sourceMsgExample duplicatePackageNamesExamples ]
, [ MigrationFailure descriptionExample pathAbsFileExample someExceptionExample ]
, [ NoCasaConfig ]
, [ InvalidTreeFromCasa blobKeyExample blobExample ]
, [ ParseSnapNameException rawSnapNameExample ]
, [ HpackLibraryException pathAbsFileExample errorMessageExample ]
, [ HpackExeException hpackCommandExample pathAbsDirExample someExceptionExample ]
]
hackageMsg :: Text
hackageMsg = "<Example message from Hackage.>"
pErrorExamples :: [C.PError]
pErrorExamples =
[ C.PError (C.Position 10 20) "<Example error message1.>"
, C.PError (C.Position 12 10) "<Example error message2.>"
, C.PError (C.Position 14 30) "<Example error message3.>"
]
pWarningExamples :: [C.PWarning]
pWarningExamples =
[ C.PWarning C.PWTOther (C.Position 10 20) "<Example warning message1.>"
, C.PWarning C.PWTOther (C.Position 12 10) "<Example warning message2.>"
, C.PWarning C.PWTOther (C.Position 14 30) "<Example warning message3.>"
]
packageNameExample :: PackageName
packageNameExample = C.mkPackageName "my-package"
versionExample :: Version
versionExample = C.mkVersion [1, 0, 0]
sha256Example :: SHA256
sha256Example = hashBytes "example"
fileSizeExample :: FileSize
fileSizeExample = FileSize 1234
revisionExample :: Revision
revisionExample = Revision 1
cabalFileInfoExamples :: [CabalFileInfo]
cabalFileInfoExamples = concat
[ [CFILatest]
, [ CFIHash sha256Example fileSize
| fileSize <- [Nothing, Just fileSizeExample]
]
, [CFIRevision revisionExample]
]
packageIdentifierRevisionExamples :: [PackageIdentifierRevision]
packageIdentifierRevisionExamples =
[ PackageIdentifierRevision packageNameExample versionExample cabalFileInfo
| cabalFileInfo <- cabalFileInfoExamples
]
blobKeyExample :: BlobKey
blobKeyExample = BlobKey sha256Example fileSizeExample
treeKeyExample :: TreeKey
treeKeyExample = TreeKey blobKeyExample
rawPackageLocationImmutableExamples :: [RawPackageLocationImmutable]
rawPackageLocationImmutableExamples = concat
[ [ RPLIHackage packageIdentifierRevision treeKey
| packageIdentifierRevision <- packageIdentifierRevisionExamples
, treeKey <- [Nothing, Just treeKeyExample]
]
--, RPLIArchive
, [ RPLIRepo repoExample rawPackageMetadata
| rawPackageMetadata <- rawPackageMetadataExamples
]
]
safeFilePathExamples :: [SafeFilePath]
safeFilePathExamples =
[ fromJust $ mkSafeFilePath "Users/jane/my-project-dir/example1.ext"
, fromJust $ mkSafeFilePath "Users/jane/my-project-dir/example2.ext"
, fromJust $ mkSafeFilePath "Users/jane/my-project-dir/example3.ext"
]
rawPathExample :: Text
rawPathExample = "<Example raw path.>"
wantedCompilerExamples :: [WantedCompiler]
wantedCompilerExamples =
[ WCGhc versionExample
, WCGhcGit "<commit1>" "<commit2>"
, WCGhcjs versionExample versionExample
]
data ExceptionExample
= ExceptionExample !Text
deriving (Show, Typeable)
instance Exception ExceptionExample where
displayException (ExceptionExample t) = T.unpack t
errorMessageExample :: String
errorMessageExample =
"This is the first line of some example text for the message in an exception \
\example. This is example text for an exception example.\n\
\This is the second line of some example text for the message in an exception \
\example. This is example text for an exception example."
someExceptionExample :: SomeException
someExceptionExample =
SomeException (ExceptionExample $ T.pack errorMessageExample)
urlExample :: Text
urlExample = "https://example.com"
relFilePathExample :: RelFilePath
relFilePathExample = RelFilePath "jane/my-project-dir"
resolvedPathFileExample :: ResolvedPath File
resolvedPathFileExample = ResolvedPath relFilePathExample pathAbsFileExample
snapNameExamples :: [SnapName]
snapNameExamples =
[ LTS 20 17
, Nightly $ fromGregorian 2023 4 5
]
rawSnapshotLocationExamples :: [RawSnapshotLocation]
rawSnapshotLocationExamples = concat
[ [ RSLCompiler wantedCompiler
| wantedCompiler <- wantedCompilerExamples
]
, [ RSLUrl urlExample blobKey
| blobKey <- [Nothing, Just blobKeyExample]
]
, [ RSLFilePath resolvedPathFileExample ]
, [ RSLSynonym snapNameExample
| snapNameExample <- snapNameExamples
]
]
rawPackageMetadataExamples :: [RawPackageMetadata]
rawPackageMetadataExamples =
[ RawPackageMetadata name version treeKey
| name <- [ Nothing, Just packageNameExample]
, version <- [ Nothing, Just versionExample ]
, treeKey <- [Nothing, Just treeKeyExample]
]
statusExample :: Status
statusExample = mkStatus 100 "<Example status message.>"
safeFilePathExample :: SafeFilePath
safeFilePathExample =
fromJust $ mkSafeFilePath "Users/jane/my-project-dir/example.ext"
archiveLocationExamples :: [ArchiveLocation]
archiveLocationExamples =
[ ALUrl urlExample
, ALFilePath resolvedPathFileExample
]
filePathExample :: FilePath
filePathExample = "<file-path>"
fileTypeExample :: Tar.FileType
fileTypeExample = Tar.FTNormal
commitExample :: Text
commitExample = "b8b34bf5571de75909d97f687e3d37909b1dc9f7"
simpleRepoExample :: SimpleRepo
simpleRepoExample = SimpleRepo urlExample commitExample RepoGit
packageIdentifierExample :: PackageIdentifier
packageIdentifierExample = PackageIdentifier packageNameExample versionExample
packageMetadataExample :: PackageMetadata
packageMetadataExample = PackageMetadata packageIdentifierExample treeKeyExample
fuzzyResultsExamples :: [FuzzyResults]
fuzzyResultsExamples =
[ FRNameNotFound packageNameExamples
, FRVersionNotFound $ fromJust $ nonEmpty packageIdentifierRevisionExamples
, FRRevisionNotFound $ fromJust $ nonEmpty packageIdentifierRevisionExamples
]
repoExample :: Repo
repoExample = Repo urlExample commitExample RepoGit "my-subdirectory"
rawPackageName :: Text
rawPackageName = "<raw-package-name>"
rawPackageVersion :: Text
rawPackageVersion = "<raw-package-version>"
sourceMsgExample :: Utf8Builder
sourceMsgExample = "<Example source message.>"
packageNameExamples :: [PackageName]
packageNameExamples =
[ C.mkPackageName "my-package1"
, C.mkPackageName "my-package2"
, C.mkPackageName "my-package3"
]
duplicatePackageNamesExamples :: [(PackageName, [RawPackageLocationImmutable])]
duplicatePackageNamesExamples = map
((, rawPackageLocationImmutableExamples))
packageNameExamples
descriptionExample :: Text
descriptionExample = "<Example description.>"
blobExample :: ByteString
blobExample = "b8b34bf5571de75909d97f687e3d37909b1dc9f7"
rawSnapNameExample :: Text
rawSnapNameExample = "<raw-snapshot-name>"
hpackCommandExample :: FilePath
hpackCommandExample = "<path-to-hpack>/hpack"