packages feed

mdoc-0.2.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
--
-- See "Mdoc.Examples.Grep".
module Options.Applicative.Mdoc
  ( getPage
  ) where

import Mdoc.Prelude

import Data.List (sort)
import Mdoc.Data.Argument
import Mdoc.Data.Described
import Mdoc.Data.Flag
import Mdoc.Data.Option
import Mdoc.Data.Optionality
import Mdoc.Data.Page
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.Text qualified as Pretty

getPage :: Parser a -> Page
getPage p = foldOptTree 0 mempty optionToMan1 $ treeMapParser (const void) p

optionToMan1
  :: Int
  -> Page
  -> Described (O.Option x)
  -> Page
optionToMan1 index acc d = case O.optMain o of
  O.OptReader onames _ _ -> fromMaybe acc $ do
    flag <- optFlags onames
    schema <- metavar
    let
      argument = Argument {index, schema, optionality = Required}
      option = Option {flag, argument}
    pure $ addOption (option <$ d) acc
  O.FlagReader onames _ -> fromMaybe acc $ do
    flag <- optFlags onames
    pure $ addSwitch (flag <$ d) acc
  O.ArgReader {} -> fromMaybe acc $ do
    schema <- metavar
    let argument = Argument {index, schema, optionality = Required}
    pure $ addArgument (argument <$ d) acc
  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
  :: Int
  -> Page
  -> (Int -> Page -> Described (O.Option x) -> Page)
  -> O.OptTree (O.Option x)
  -> Page
foldOptTree index acc f = \case
  O.Leaf o ->
    case O.optVisibility o of
      O.Visible ->
        f index acc
          $ Described
            { item = o
            , optionality = maybe Required Defaulted (O.optShowDefault o)
            , multiple = False
            , help = textToMdoc =<< docToText (O.optHelp o)
            }
      O.Internal -> acc
      O.Hidden -> acc
  O.MultNode ts -> recur f ts
  O.AltNode O.MarkDefault ts -> recur fAsOptional ts
  O.AltNode O.NoDefault ts -> recur fAsRequired ts
  O.BindNode t -> foldOptTree (index + 1) acc fAsMultiple t
 where
  recur g = maybe acc (foldMap1 $ foldOptTree (index + 1) acc g) . nonEmpty

  fAsOptional i m d = f i m $ d {optionality = Optional}
  fAsRequired i m d = f i m $ d {optionality = Required}
  fAsMultiple i m d = f i m $ d {multiple = True}

docToText :: Chunk Doc -> Maybe Text
docToText =
  fmap
    ( Pretty.renderStrict
        . Pretty.layoutPretty
          Pretty.defaultLayoutOptions {Pretty.layoutPageWidth = Pretty.Unbounded}
    )
    . unChunk