hurl-workbench-cli-0.1.0.0: src/HurlWorkbench/Cli/Command/List.hs
-- | @hurl-workbench list@: print workspace entities and their relationships
-- as aligned, line-oriented tables in declaration order.
--
-- Listing never reads parameter environments and never prints secret
-- values; an @ENVIRONMENT@ column names the variable, not its value.
module HurlWorkbench.Cli.Command.List
( runList,
listLines,
)
where
import Data.Generics.Labels ()
import Data.Text qualified as Text
import HurlWorkbench.Cli.Options (GlobalOptions, ListCategory (..))
import HurlWorkbench.Cli.Output (CommandResult, renderTable, success)
import HurlWorkbench.Cli.Workspace (withValidatedWorkspace)
import HurlWorkbench.Prelude
import HurlWorkbench.Workspace.Context (ValidatedWorkspace, lookupMatrix, lookupRecipe, validatedWorkspace)
import HurlWorkbench.Workspace.Types
runList :: GlobalOptions -> FilePath -> ListCategory -> IO CommandResult
runList options currentDirectory category =
withValidatedWorkspace options currentDirectory (success . listLines category)
listLines :: ListCategory -> ValidatedWorkspace -> [Text]
listLines category vw = case category of
AllCategories ->
intercalateBlank
[ heading title : section c
| c <- [ParametersCategory .. SuitesCategory],
let title = categoryTitle c
]
single -> section single
where
ws = validatedWorkspace vw
heading title = title <> ":"
intercalateBlank = concat . zipWith (\i block -> [Text.empty | i > (0 :: Int)] <> block) [0 ..]
section = \case
AllCategories -> []
ParametersCategory -> table ["NAME", "KIND", "DEFAULT", "ENVIRONMENT", "DESCRIPTION"] (map parameterRow (ws ^. #parameters))
FragmentsCategory -> table ["NAME", "PATH", "DESCRIPTION"] (map fragmentRow (ws ^. #fragments))
WorkflowsCategory -> table ["NAME", "FRAGMENTS", "PARAMETERS", "DESCRIPTION"] (map workflowRow (ws ^. #workflows))
RecipesCategory -> table ["NAME", "WORKFLOW", "SAFETY", "BINDINGS", "DESCRIPTION"] (map recipeRow (ws ^. #recipes))
MatricesCategory -> table ["NAME", "RECIPE", "CASES", "FAIL-FAST", "DESCRIPTION"] (map matrixRow (ws ^. #matrices))
ChecksCategory -> table ["NAME", "EXPECTED-STATUS", "TIMEOUT", "DESCRIPTION"] (map checkRow (ws ^. #checks))
ServicesCategory -> table ["NAME", "COMMAND", "READINESS", "DESCRIPTION"] (map serviceRow (ws ^. #services))
SuitesCategory -> table ["NAME", "RUNS", "SERVICE", "SAFETY", "FAIL-FAST", "DESCRIPTION"] (map (suiteRow vw) (ws ^. #suites))
table :: [Text] -> [[Text]] -> [Text]
table _ [] = ["(none)"]
table headings rows = renderTable headings rows
categoryTitle :: ListCategory -> Text
categoryTitle = \case
AllCategories -> "All"
ParametersCategory -> "Parameters"
FragmentsCategory -> "Fragments"
WorkflowsCategory -> "Workflows"
RecipesCategory -> "Recipes"
MatricesCategory -> "Matrices"
ChecksCategory -> "Checks"
ServicesCategory -> "Services"
SuitesCategory -> "Suites"
parameterRow :: Parameter -> [Text]
parameterRow p =
[ unParameterName (p ^. #name),
case p ^. #kind of
Generated generator -> "generated " <> Text.pack (show generator)
Plain -> "plain"
Secret -> "secret",
maybe "" (displayLiteral . hurlValueLiteralText) (p ^. #defaultValue),
fromMaybe "" (p ^. #environment),
describe (p ^. #description)
]
fragmentRow :: Fragment -> [Text]
fragmentRow f = [unFragmentName (f ^. #name), Text.pack (f ^. #path), describe (f ^. #description)]
workflowRow :: Workflow -> [Text]
workflowRow w =
[ unWorkflowName (w ^. #name),
commaList (map unFragmentName (w ^. #fragments)),
commaList (map unParameterName (w ^. #parameters)),
describe (w ^. #description)
]
recipeRow :: Recipe -> [Text]
recipeRow r =
[ unRecipeName (r ^. #name),
unWorkflowName (r ^. #workflow),
case r ^. #safety of
ReadOnly -> "read-only"
Mutating -> "mutating",
bindingList (r ^. #bindings),
describe (r ^. #description)
]
matrixRow :: Matrix -> [Text]
matrixRow m =
[ unMatrixName (m ^. #name),
unRecipeName (m ^. #recipe),
commaList (map (unMatrixCaseName . view #name) (m ^. #cases)),
yesNo (m ^. #failFast),
describe (m ^. #description)
]
checkRow :: Check -> [Text]
checkRow check = [unCheckName (check ^. #name), Text.pack (show (check ^. #expectedStatus)), maybe "60" (Text.pack . show) (check ^. #timeoutSeconds), describe (check ^. #description)]
serviceRow :: Service -> [Text]
serviceRow s =
[ unServiceName (s ^. #name),
s ^. #command . #executable,
case s ^. #readiness of
HttpReadinessCheck http -> "http " <> http ^. #url
CommandReadinessCheck check -> "command " <> check ^. #command . #executable,
describe (s ^. #description)
]
suiteRow :: ValidatedWorkspace -> Suite -> [Text]
suiteRow validated s =
[ unSuiteName (s ^. #name),
commaList (map runLabel (s ^. #runs)),
commaList (map unServiceName (suiteServices s)),
suiteSafety validated s,
yesNo (s ^. #failFast),
describe (s ^. #description)
]
where
runLabel = \case
WorkflowRun w -> "workflow:" <> unWorkflowName w
RecipeRun r -> "recipe:" <> unRecipeName r
MatrixRun m -> "matrix:" <> unMatrixName m
CheckRun c -> "check:" <> unCheckName c
ServiceRun step -> "service:" <> Text.pack (show step)
suiteSafety :: ValidatedWorkspace -> Suite -> Text
suiteSafety validated suite =
case (any (== MutatingRun) dispositions, any (== UnclassifiedRun) dispositions) of
(False, False) -> "read-only"
(True, False) -> "mutating"
(False, True) -> "unclassified"
(True, True) -> "mutating, unclassified"
where
dispositions = map disposition (suite ^. #runs)
disposition = \case
CheckRun _ -> ReadOnlyRun
ServiceRun _ -> ReadOnlyRun
WorkflowRun _ -> UnclassifiedRun
RecipeRun name -> maybe UnclassifiedRun recipeDisposition (lookupRecipe name validated)
MatrixRun name -> case lookupMatrix name validated >>= (\matrix -> lookupRecipe (matrix ^. #recipe) validated) of
Nothing -> UnclassifiedRun
Just recipe -> recipeDisposition recipe
recipeDisposition recipe = case recipe ^. #safety of
ReadOnly -> ReadOnlyRun
Mutating -> MutatingRun
data ListedSafety = ReadOnlyRun | MutatingRun | UnclassifiedRun
deriving stock (Eq)
-- | Committed plain bindings are part of the workspace source, so they are
-- safe to display.
bindingList :: [Binding] -> Text
bindingList bindings =
commaList [unParameterName (b ^. #parameter) <> "=" <> displayLiteral (hurlValueLiteralText (b ^. #value)) | b <- bindings]
displayLiteral :: Text -> Text
displayLiteral value = if Text.null value then "\"\"" else value
commaList :: [Text] -> Text
commaList = Text.intercalate ", "
describe :: Maybe Text -> Text
describe = fromMaybe ""
yesNo :: Bool -> Text
yesNo b = if b then "yes" else "no"