dojang-0.2.0: src/Dojang/Commands/Status.hs
{-# LANGUAGE OverloadedRecordDot #-}
{-# LANGUAGE OverloadedStrings #-}
module Dojang.Commands.Status
( StatusOptions (..)
, defaultStatusOptions
, formatWarning
, printWarnings
, status
) where
import Control.Monad (forM, forM_)
import Control.Monad.IO.Class (MonadIO (liftIO))
import Data.List.NonEmpty (NonEmpty ((:|)), toList)
import System.Exit (ExitCode (..))
import System.IO (Handle, stderr)
import Data.CaseInsensitive (original)
import Data.Text (Text, intercalate, pack)
import System.Console.Pretty (Color (..))
import System.Directory.OsPath (makeAbsolute)
import System.OsPath (addTrailingPathSeparator, makeRelative)
import TextShow (FromStringShow (FromStringShow), TextShow (showt))
import Dojang.App (App, ensureContext)
import Dojang.Commands
( Admonition (..)
, codeStyleFor
, pathStyleFor
, printStderr'
, printTable
)
import Dojang.MonadFileSystem (MonadFileSystem (..))
import Dojang.Types.Context
( Context (..)
, FileCorrespondence (..)
, FileDeltaKind (..)
, FileEntry (..)
, FileStat (..)
, makeCorrespond
)
import Dojang.Types.EnvironmentPredicate.Evaluate (EvaluationWarning (..))
import Dojang.Types.FilePathExpression.Expansion (ExpansionWarning (..))
import Dojang.Types.MonikerName ()
import Dojang.Types.Repository (Repository (..), RouteMapWarning (..))
data StatusOptions = StatusOptions
{ noTrailingSlash :: Bool
, onlyChanges :: Bool
, showDestinationPath :: Bool
}
deriving (Show)
defaultStatusOptions :: StatusOptions
defaultStatusOptions =
StatusOptions
{ noTrailingSlash = False
, onlyChanges = False
, showDestinationPath = False
}
status :: (MonadFileSystem i, MonadIO i) => StatusOptions -> App i ExitCode
status options = do
ctx <- ensureContext
(files, ws) <- makeCorrespond ctx
let files' = if options.onlyChanges then filter isChanged files else files
sourcePath <- liftIO $ makeAbsolute ctx.repository.sourcePath
rows <- forM files' $ \file -> do
displayPath <-
if options.showDestinationPath
then liftIO $ makeAbsolute file.destination.path
else do
path <- liftIO $ makeAbsolute file.source.path
return $ makeRelative sourcePath path
let displayPathS =
if not options.noTrailingSlash
&& ( (file.source.stat == Directory)
|| (file.source.stat == Missing)
&& (file.destination.stat == Directory)
)
then addTrailingPathSeparator displayPath
else displayPath
displayPathFP <- decodePath displayPathS
return
[ renderDeltaKind file.sourceDelta
, renderFileStat file.source.stat
, renderDeltaKind file.destinationDelta
, renderFileStat file.destination.stat
, (Default, pack displayPathFP)
]
printTable
[ "Source"
, "ST"
, "Destination"
, "DT"
, if options.showDestinationPath then "Destination File" else "Source File"
]
rows
printWarnings ws
return ExitSuccess
isChanged :: FileCorrespondence -> Bool
isChanged file =
file.sourceDelta /= Unchanged || file.destinationDelta /= Unchanged
renderDeltaKind :: FileDeltaKind -> (Color, Text)
renderDeltaKind Unchanged = (Default, "unchanged")
renderDeltaKind Added = (Green, "added")
renderDeltaKind Removed = (Red, "removed")
renderDeltaKind Modified = (Yellow, "modified")
renderFileStat :: FileStat -> (Color, Text)
renderFileStat Missing = (Red, "-")
renderFileStat Directory = (Default, "D")
renderFileStat (File _) = (Default, "F")
renderFileStat (Symlink _) = (Default, "L")
-- TODO: This should be in a separate module:
formatWarning :: (MonadIO i) => Handle -> RouteMapWarning -> i Text
formatWarning handle (EnvironmentPredicateWarning w) = do
codeStyle <- codeStyleFor handle
case w of
(UndefinedMoniker moniker) ->
return $
"Reference to an undefined moniker: "
<> codeStyle (original moniker.name)
<> "."
(UnrecognizedOperatingSystem os) ->
return $
"Unrecognized operating system: "
<> codeStyle (showt $ FromStringShow os)
<> "."
(UnrecognizedArchitecture arch) ->
return $
"Unrecognized architecture: "
<> codeStyle (showt $ FromStringShow arch)
<> "."
formatWarning handle (FilePathExpressionWarning (UndefinedEnvironmentVariable envVar)) = do
codeStyle <- codeStyleFor handle
return $
"Reference to an undefined environment variable: "
<> codeStyle envVar
<> "."
formatWarning handle (OverlapDestinationPathsWarning name dst paths) = do
pathStyle <- pathStyleFor handle
pairStrings <- forM paths $ \(from, to) -> do
return $ pathStyle from <> " -> " <> pathStyle to
case pairStrings of
pairString :| [] ->
return $
pathStyle name
<> " -> "
<> pathStyle dst
<> " overlaps with: "
<> pairString
<> "."
_ ->
return $
pathStyle name
<> " -> "
<> pathStyle dst
<> " overlaps with:\n "
<> intercalate "\n " (toList pairStrings)
-- TODO: This should be in a separate module:
printWarnings :: (MonadIO i) => [RouteMapWarning] -> App i ()
printWarnings ws =
forM_ ws $ \w -> do
formatted <- formatWarning stderr w
printStderr' Warning formatted