module Stackctl.Spec.Changes.Format
( Format (..)
, formatOption
, OmitFull (..)
, omitFullOption
, formatChangeSet
, formatRemovedStack
, formatTTY
) where
import Stackctl.Prelude
import qualified Data.List.NonEmpty as NE
import qualified Data.Text as T
import Options.Applicative hiding (action)
import Stackctl.AWS
import Stackctl.Colors
data Format
= FormatTTY
| FormatPullRequest
deriving stock (Bounded, Enum, Show)
data OmitFull
= OmitFull
| IncludeFull
formatOption :: Parser Format
formatOption =
option (eitherReader readFormat)
$ mconcat
[ short 'f'
, long "format"
, help "Format to output changes in"
, value FormatTTY
, showDefaultWith showFormat
]
readFormat :: String -> Either String Format
readFormat = \case
"tty" -> Right FormatTTY
"pr" -> Right FormatPullRequest
x -> Left $ "Invalid format: " <> x
showFormat :: Format -> String
showFormat = \case
FormatTTY -> "tty"
FormatPullRequest -> "pr"
-- brittany-disable-next-binding
omitFullOption :: Parser OmitFull
omitFullOption =
flag
IncludeFull
OmitFull
( long "no-include-full"
<> help "Don't include full ChangeSet JSON details"
)
formatChangeSet
:: Colors -> OmitFull -> Text -> Format -> Maybe ChangeSet -> Text
formatChangeSet colors omitFull name = \case
FormatTTY -> formatTTY colors name
FormatPullRequest -> formatPullRequest omitFull name
formatRemovedStack :: Colors -> Format -> Stack -> Text
formatRemovedStack Colors {..} format stack = case format of
FormatTTY -> red "DELETE" <> " stack " <> cyan name
FormatPullRequest -> ":x: This PR will **delete** the stack `" <> name <> "`"
where
name = stack ^. stack_stackName
formatTTY :: Colors -> Text -> Maybe ChangeSet -> Text
formatTTY colors@Colors {..} name mChangeSet = case (mChangeSet, rChanges) of
(Nothing, _) -> "No changes for " <> name
(_, Nothing) -> "Metadata only changes (e.g. Tags or Outputs)"
(_, Just rcs) ->
("\n" <>) $ (<> "\n") $ mconcat $ ("Changes for " <> cyan name <> ":")
: map
(("\n " <>) . formatResourceChange)
(NE.toList rcs)
where
rChanges = do
cs <- mChangeSet
changes <- csChanges cs
NE.nonEmpty $ mapMaybe resourceChange changes
formatResourceChange ResourceChange' {..} =
maybe "" colorAction action
<> " "
<> maybe "" toText logicalResourceId
<> " ("
<> maybe "" cyan resourceType
<> ")"
<> maybe "" ((" " <>) . magenta) physicalResourceId
<> maybe "" (("\n Replacement: " <>) . colorReplacement) replacement
<> maybe "" (("\n Scope: " <>) . T.intercalate ", " . map toText) scope
<> maybe "" (("\n Details:" <>) . formatDetails) details
formatDetails =
mconcat . map ("\n * " <>) . mapMaybe (formatDetail colors)
colorAction = \case
x@ChangeAction_Add -> green (toText x)
x@ChangeAction_Modify -> yellow (toText x)
x@ChangeAction_Remove -> red (toText x)
ChangeAction' x -> x
colorReplacement = \case
x@Replacement_True -> red (toText x)
x@Replacement_False -> green (toText x)
x@Replacement_Conditional -> yellow (toText x)
Replacement' x -> x
formatPullRequest :: OmitFull -> Text -> Maybe ChangeSet -> Text
formatPullRequest omitFull name mChangeSet =
emoji
<> " This PR generates "
<> description
<> " for `"
<> name
<> "`."
<> fromMaybe "" (commentBody omitFull <$> mChangeSet <*> rChanges)
<> "\n"
where
emoji = case (mChangeSet, nChanges) of
(Nothing, _) -> ":heavy_check_mark:"
(_, Nothing) -> ":book:"
(_, Just _) -> ":warning:"
description = case (mChangeSet, nChanges) of
(Nothing, _) -> "no changes"
(_, Nothing) -> "only metadata changes (Tags, Outputs, etc)"
(_, Just 1) -> "**1** change"
(_, Just n) -> "**" <> pack (show n) <> "** changes"
nChanges = length <$> rChanges
rChanges = do
cs <- mChangeSet
changes <- csChanges cs
NE.nonEmpty $ mapMaybe resourceChange changes
commentBody :: OmitFull -> ChangeSet -> NonEmpty ResourceChange -> Text
commentBody omitFull cs rcs =
mconcat
$ [ "\n"
, "\n| Action | Logical Id | Physical Id | Type | Replacement | Scope | Details |"
, "\n| --- | --- | --- | --- | --- | --- | --- |"
]
<> map commentTableRow (NE.toList rcs)
<> case omitFull of
OmitFull -> []
IncludeFull ->
[ "\n"
, "\nFull changes
"
, "\n"
, "\n```json"
, "\n" <> changeSetJSON cs
, "\n```"
, "\n"
, "\n