{-# LANGUAGE NamedFieldPuns #-}
module Env.Internal.Help
( helpInfo
, helpDoc
, Info
, ErrorHandler
, defaultInfo
, defaultErrorHandler
, header
, desc
, footer
, handleError
) where
import Data.Foldable (asum)
import qualified Data.List as List
import qualified Data.Map as Map
import Data.Maybe (catMaybes, mapMaybe)
import Data.Ord (comparing)
import Env.Internal.Error (Error)
import qualified Env.Internal.Error as Error
import Env.Internal.Free
import Env.Internal.Parser hiding (Mod)
helpInfo :: Info e -> Parser e b -> [(String, e)] -> String
helpInfo Info {infoHeader, infoDesc, infoFooter, infoHandleError} p errors =
List.intercalate "\n\n" $ catMaybes
[ infoHeader
, fmap (List.intercalate "\n" . splitWords 50) infoDesc
, Just (helpDoc p)
, fmap (List.intercalate "\n" . splitWords 50) infoFooter
] ++ helpErrors infoHandleError errors
helpDoc :: Parser e a -> String
helpDoc p =
List.intercalate "\n" ("Available environment variables:\n" : helpParserDoc p)
helpParserDoc :: Parser e a -> [String]
helpParserDoc =
concat . Map.elems . foldAlt (\v -> Map.singleton (varfName v) (helpVarfDoc v)) . unParser
helpVarfDoc :: VarF e a -> [String]
helpVarfDoc VarF {varfName, varfHelp, varfHelpDef} =
case varfHelp of
Nothing -> [indent 2 varfName]
Just h
| k > 15 -> indent 2 varfName : map (indent 25) (splitWords 30 t)
| otherwise ->
case zipWith indent (23 - k : repeat 25) (splitWords 30 t) of
(x : xs) -> (indent 2 varfName ++ x) : xs
[] -> [indent 2 varfName]
where k = length varfName
t = maybe h (\s -> h ++ " (default: " ++ s ++")") varfHelpDef
splitWords :: Int -> String -> [String]
splitWords n =
go [] 0 . words
where
go acc _ [] = prep acc
go acc k (w : ws)
| k + z < n = go (w : acc) (k + z) ws
| z > n = prep acc ++ case splitAt n w of (w', w'') -> w' : go [] 0 (w'' : ws)
| otherwise = prep acc ++ go [w] z ws
where
z = length w
prep [] = []
prep acc = [unwords (reverse acc)]
indent :: Int -> String -> String
indent n s =
replicate n ' ' ++ s
helpErrors :: ErrorHandler e -> [(String, e)] -> [String]
helpErrors _ [] = []
helpErrors handler fs =
[ "Parsing errors:"
, List.intercalate "\n" (mapMaybe (uncurry handler) (List.sortBy (comparing varName) fs))
]
data Info e = Info
{ infoHeader :: Maybe String
, infoDesc :: Maybe String
, infoFooter :: Maybe String
, infoHandleError :: ErrorHandler e
}
type ErrorHandler e = String -> e -> Maybe String
defaultInfo :: Info Error
defaultInfo = Info
{ infoHeader = Nothing
, infoDesc = Nothing
, infoFooter = Nothing
, infoHandleError = defaultErrorHandler
}
header :: String -> Info e -> Info e
header h i = i {infoHeader=Just h}
desc :: String -> Info e -> Info e
desc h i = i {infoDesc=Just h}
footer :: String -> Info e -> Info e
footer h i = i {infoFooter=Just h}
handleError :: ErrorHandler e -> Info x -> Info e
handleError handler i = i {infoHandleError=handler}
defaultErrorHandler :: (Error.AsUnset e, Error.AsEmpty e, Error.AsUnread e) => ErrorHandler e
defaultErrorHandler name err =
asum [handleUnsetError name err, handleEmptyError name err, handleUnreadError name err]
handleUnsetError :: Error.AsUnset e => ErrorHandler e
handleUnsetError name =
fmap (\() -> indent 2 (name ++ " is unset")) . Error.tryUnset
handleEmptyError :: Error.AsEmpty e => ErrorHandler e
handleEmptyError name =
fmap (\() -> indent 2 (name ++ " is empty")) . Error.tryEmpty
handleUnreadError :: Error.AsUnread e => ErrorHandler e
handleUnreadError name =
fmap (\val -> indent 2 (name ++ " has value " ++ val ++ " that cannot be parsed")) . Error.tryUnread
varName :: (String, e) -> String
varName (n, _) = n