mdoc-0.2.0.0: src/OptEnvConf/Mdoc.hs
-- |
--
-- Module : OptEnvConf.Mdoc
-- Copyright : (c) 2026 Patrick Brisbin
-- License : AGPL-3
-- Maintainer : pbrisbin@gmail.com
-- Stability : experimental
-- Portability : POSIX
--
-- See "Mdoc.Examples.OptEnvConf".
module OptEnvConf.Mdoc
( getPage
) where
import Mdoc.Prelude
import Autodocodec.Schema.Mdoc qualified as JSONSchema
import Mdoc.Data.Argument
import Mdoc.Data.Config
import Mdoc.Data.Described
import Mdoc.Data.EnvVar
import Mdoc.Data.Flag
import Mdoc.Data.Option
import Mdoc.Data.Optionality
import Mdoc.Data.Page
import OptEnvConf (ConfDoc (..), EnvDoc (..), OptDoc (..), Parser)
import OptEnvConf.Args (Dashed (..))
import OptEnvConf.Doc
( AnyDocs (..)
, parserConfDocs
, parserEnvDocs
, parserOptDocs
)
getPage :: Parser a -> Page
getPage p =
flip appEndo mempty
$ mconcat
[ foldMap (Endo . either addSwitch addOption . splitEither) $ getParserOpts p
, foldMap (Endo . addArgument) $ getParserArgs p
, foldMap (Endo . addEnvVar) $ getParserEnvs p
, foldMap (Endo . addConfig) $ getParserConfs p
]
where
splitEither :: Described (Either a b) -> Either (Described a) (Described b)
splitEither d = case d.item of
Left a -> Left $ a <$ d
Right b -> Right $ b <$ d
getParserOpts :: Parser a -> [Described (Either Flag Option)]
getParserOpts = walkNonCommandDocs 0 (maybe [] . optDocToOpt) . parserOptDocs
getParserArgs :: Parser a -> [Described Argument]
getParserArgs = walkNonCommandDocs 0 (maybe [] . optDocToArg) . parserOptDocs
getParserEnvs :: Parser a -> [Described EnvVar]
getParserEnvs = walkNonCommandDocs 0 envDocToEnvVar . parserEnvDocs
-- getParserCmds :: Parser a -> [Command]
-- getParserCmds = walkCommandDocs commandDocToCommand . parserOptDocs
getParserConfs :: Parser a -> [Described Config]
getParserConfs = walkNonCommandDocs 0 confToConfig . parserConfDocs
optDocToOpt :: Int -> OptDoc -> [Described (Either Flag Option)]
optDocToOpt _ doc =
fromMaybe [] $ do
flag <- dashedFlags $ optDocDasheds doc
let item = case optDocMetavar doc of
Nothing -> Left flag
Just schema -> Right $ Option {flag, argument = fromString schema}
pure
[ Described
{ item
, optionality = maybe Required Defaulted $ optDocDefault doc
, multiple = False
, help = textToMdoc . pack =<< optDocHelp doc
}
]
optDocToArg :: Int -> OptDoc -> [Described Argument]
optDocToArg index doc =
case (optDocDasheds doc, optDocMetavar doc) of
([], Just schema) ->
[ Described
{ item =
Argument
{ index
, schema
, optionality = Required
}
, optionality = maybe Required Defaulted $ optDocDefault doc
, multiple = False
, help = textToMdoc . pack =<< optDocHelp doc
}
]
_ -> []
envDocToEnvVar :: Int -> EnvDoc -> [Described EnvVar]
envDocToEnvVar _ doc =
[ Described
{ item =
EnvVar
{ names = envDocVars doc
, argument = envDocMetavar doc <&> fromString
}
, optionality = maybe Required Defaulted $ envDocDefault doc
, multiple = False
, help = textToMdoc . pack =<< envDocHelp doc
}
]
-- commandDocToCommand :: CommandDoc (Maybe OptDoc) -> [Command]
-- commandDocToCommand c =
-- [ Command
-- { name = commandDocArgument c
-- , help = Just $ commandDocHelp c
-- , opts = walkNonCommandDocs 0 (maybe [] . optDocToOpt) $ commandDocs c
-- , args = walkNonCommandDocs 0 (maybe [] . optDocToArg) $ commandDocs c
-- , visible = True
-- , required = True
-- , multiple = False
-- , def = Nothing
-- }
-- ]
confToConfig :: Int -> ConfDoc -> [Described Config]
confToConfig _ doc =
concatMap (\(keys, js) -> addMeta $ JSONSchema.getConfigs (Just keys) js)
$ toList
$ confDocKeys doc
where
addMeta :: [Described Config] -> [Described Config]
addMeta = \case
[] -> []
(d : ds) ->
d
{ optionality = maybe Required Defaulted $ confDocDefault doc
, multiple = False
, help = textToMdoc . pack =<< confDocHelp doc
}
: ds
dashedFlags :: [Dashed] -> Maybe Flag
dashedFlags = fmap go . nonEmpty
where
go :: NonEmpty Dashed -> Flag
go ne =
let aliases = map (aliasedAs []) $ tail ne
in aliasedAs aliases $ head ne
aliasedAs :: [Flag] -> Dashed -> Flag
aliasedAs xs = \case
DashedShort x -> Flag x xs
DashedLong x -> GNUFlag (toList x) xs
-- walkCommandDocs :: (CommandDoc a -> [b]) -> AnyDocs a -> [b]
-- walkCommandDocs f = \case
-- AnyDocsCommands _mDefault cmds -> concatMap f cmds
-- AnyDocsAnd ds -> concatMap (walkCommandDocs f) ds
-- AnyDocsOr ds -> concatMap (walkCommandDocs f) ds
-- AnyDocsSingle {} -> []
walkNonCommandDocs
:: Int -> (Int -> a -> [Described b]) -> AnyDocs a -> [Described b]
walkNonCommandDocs index f = \case
AnyDocsCommands {} -> []
AnyDocsAnd ds -> concatMap (walkNonCommandDocs (index + 1) f) ds
AnyDocsOr ds -> concatMap (map mkMultiple . walkNonCommandDocs (index + 1) f) ds
AnyDocsSingle d -> f index d
where
-- This is suspect, but it seems we can't distinguish if the Or is being used
-- to indicate some/many or optionality. We'll just treat it as both since it
-- passes our current tests.
mkMultiple d = d {optionality = Optional, multiple = True}