mdoc-0.1.0.0: src/Options/Applicative/Mdoc.hs
{-# OPTIONS_GHC -Wno-ambiguous-fields #-}
-- |
--
-- Module : Options.Applicative.Mdoc
-- Copyright : (c) 2026 Patrick Brisbin
-- License : AGPL-3
-- Maintainer : pbrisbin@gmail.com
-- Stability : experimental
-- Portability : POSIX
module Options.Applicative.Mdoc
( addToMan1
) where
import Mdoc.Prelude
import Data.List (sort)
import Mdoc.Gen.Argument
import Mdoc.Gen.Described
import Mdoc.Gen.Flag
import Mdoc.Gen.Man1 (Man1 (..))
import Mdoc.Gen.Option
import Mdoc.Gen.Optionality
import Options.Applicative (Parser)
import Options.Applicative.Common (treeMapParser)
import Options.Applicative.Help.Chunk (Chunk (..), unChunk)
import Options.Applicative.Help.Pretty (Doc)
import Options.Applicative.Types qualified as O
import Prettyprinter qualified as Pretty
import Prettyprinter.Render.String qualified as Pretty
-- | Only the bits we work on here
--
-- We walk the options, building up one of these from empty, then we append what
-- we got to the same fields on the input 'Man1'.
data SubMan1 = SubMan1
{ switches :: [Described Flag]
, options :: [Described Option]
, arguments :: [Described Argument]
}
deriving stock (Generic)
deriving (Monoid, Semigroup) via Generically SubMan1
toSub :: Man1 -> SubMan1
toSub Man1 {switches, options, arguments} =
SubMan1 {switches, options, arguments}
fromSub :: Man1 -> SubMan1 -> Man1
fromSub m SubMan1 {switches, options, arguments} =
m
{ switches = m.switches <> switches
, options = m.options <> options
, arguments = m.arguments <> arguments
}
addToMan1 :: Parser a -> Man1 -> Man1
addToMan1 p base =
fromSub base
$ foldOptTree (toSub base) optionToMan1
$ treeMapParser (const void) p
optionToMan1 :: SubMan1 -> Described (O.Option x) -> SubMan1
optionToMan1 acc d = case O.optMain o of
O.OptReader onames _ _ -> fromMaybe acc $ do
flag <- optFlags onames
schema <- metavar
let
argument = Argument {schema, optionality = Required}
option = Option {flag, argument}
pure $ acc {options = acc.options <> [option <$ d]}
O.FlagReader onames _ -> fromMaybe acc $ do
flag <- optFlags onames
pure $ acc {switches = acc.switches <> [flag <$ d]}
O.ArgReader {} -> fromMaybe acc $ do
schema <- metavar
let argument = Argument {schema, optionality = Required}
pure $ acc {arguments = acc.arguments <> [argument <$ d]}
O.CmdReader {} -> acc -- TODO
where
o = d.item
metavar = guarded (not . null) $ O.optMetaVar o
optFlags :: [O.OptName] -> Maybe Flag
optFlags = fmap go . nonEmpty . sort
where
go :: NonEmpty O.OptName -> Flag
go ne =
let aliases = map (aliasedAs []) $ tail ne
in aliasedAs aliases $ head ne
aliasedAs :: [Flag] -> O.OptName -> Flag
aliasedAs xs = \case
O.OptShort x -> Flag x xs
O.OptLong x -> GNUFlag x xs
foldOptTree
:: SubMan1
-> ( SubMan1
-> Described (O.Option x)
-> SubMan1
)
-> O.OptTree (O.Option x)
-> SubMan1
foldOptTree acc f = \case
O.Leaf o ->
case O.optVisibility o of
O.Visible ->
f acc
$ Described
{ item = o
, optionality = maybe Required Defaulted (O.optShowDefault o)
, multiple = False
, helpLines = nonEmpty $ maybe [] lines $ docToString $ O.optHelp o
}
O.Internal -> acc
O.Hidden -> acc
O.MultNode ts ->
maybe acc (foldMap1 $ foldOptTree mempty f) $ nonEmpty ts
O.AltNode O.MarkDefault ts ->
maybe acc (foldMap1 $ foldOptTree mempty fAsOptional) $ nonEmpty ts
O.AltNode O.NoDefault ts ->
maybe acc (foldMap1 $ foldOptTree mempty fAsRequired) $ nonEmpty ts
O.BindNode t -> foldOptTree acc fAsMultiple t
where
fAsOptional m d = f m $ d {optionality = Optional}
fAsRequired m d = f m $ d {optionality = Required}
fAsMultiple m d = f m $ d {multiple = True}
docToString :: Chunk Doc -> Maybe String
docToString =
fmap
( Pretty.renderString
. Pretty.layoutPretty
Pretty.defaultLayoutOptions {Pretty.layoutPageWidth = Pretty.Unbounded}
)
. unChunk