packages feed

opt-env-conf 0.4.0.5 → 0.5.0.0

raw patch · 9 files changed

+340/−184 lines, 9 filesPVP ok

version bump matches the API change (PVP)

API changes (from Hackage documentation)

- OptEnvConf.Reader: viaCodec :: HasCodec a => Reader a
+ OptEnvConf.Doc: instance GHC.Base.Functor OptEnvConf.Doc.AnyDocs
+ OptEnvConf.Doc: instance GHC.Base.Functor OptEnvConf.Doc.CommandDoc
+ OptEnvConf.Output: bracketise :: [Chunk] -> [Chunk]
+ OptEnvConf.Output: parenthesise :: [Chunk] -> [Chunk]
+ OptEnvConf.Reader: viaStringCodec :: HasCodec a => Reader a
+ OptEnvConf.Setting: BuildAddConf :: !ConfigValSetting a -> BuildInstruction a
+ OptEnvConf.Setting: BuildAddEnv :: !String -> BuildInstruction a
+ OptEnvConf.Setting: BuildAddExample :: !String -> BuildInstruction a
+ OptEnvConf.Setting: BuildAddHelp :: !String -> BuildInstruction a
+ OptEnvConf.Setting: BuildAddLong :: !NonEmpty Char -> BuildInstruction a
+ OptEnvConf.Setting: BuildAddReader :: !Reader a -> BuildInstruction a
+ OptEnvConf.Setting: BuildAddShort :: !Char -> BuildInstruction a
+ OptEnvConf.Setting: BuildSetDefault :: !a -> !String -> BuildInstruction a
+ OptEnvConf.Setting: BuildSetHidden :: BuildInstruction a
+ OptEnvConf.Setting: BuildSetMetavar :: !String -> BuildInstruction a
+ OptEnvConf.Setting: BuildSetSwitchValue :: !a -> BuildInstruction a
+ OptEnvConf.Setting: BuildTryArgument :: BuildInstruction a
+ OptEnvConf.Setting: BuildTryOption :: BuildInstruction a
+ OptEnvConf.Setting: data BuildInstruction a
- OptEnvConf: enableDisableSwitch :: HasCallStack => Bool -> [Builder Bool] -> Parser Bool
+ OptEnvConf: enableDisableSwitch :: HasCallStack => [Builder Bool] -> Parser Bool
- OptEnvConf: makeDoubleSwitch :: HasCallStack => String -> String -> String -> Bool -> [Builder Bool] -> Parser Bool
+ OptEnvConf: makeDoubleSwitch :: HasCallStack => String -> String -> String -> [Builder Bool] -> Parser Bool
- OptEnvConf: yesNoSwitch :: HasCallStack => Bool -> [Builder Bool] -> Parser Bool
+ OptEnvConf: yesNoSwitch :: HasCallStack => [Builder Bool] -> Parser Bool
- OptEnvConf.Doc: commandParserDocs :: Command a -> CommandDoc SetDoc
+ OptEnvConf.Doc: commandParserDocs :: Command a -> CommandDoc (Maybe SetDoc)
- OptEnvConf.Doc: parserDocs :: Parser a -> AnyDocs SetDoc
+ OptEnvConf.Doc: parserDocs :: Parser a -> AnyDocs (Maybe SetDoc)
- OptEnvConf.Doc: parserOptDocs :: Parser a -> AnyDocs OptDoc
+ OptEnvConf.Doc: parserOptDocs :: Parser a -> AnyDocs (Maybe OptDoc)
- OptEnvConf.Doc: renderCommandHelpPage :: String -> [String] -> CommandDoc SetDoc -> [Chunk]
+ OptEnvConf.Doc: renderCommandHelpPage :: String -> [String] -> CommandDoc (Maybe SetDoc) -> [Chunk]
- OptEnvConf.Doc: renderHelpPage :: String -> String -> AnyDocs SetDoc -> [Chunk]
+ OptEnvConf.Doc: renderHelpPage :: String -> Version -> String -> AnyDocs (Maybe SetDoc) -> [Chunk]
- OptEnvConf.Doc: renderLongOptDocs :: AnyDocs OptDoc -> [Chunk]
+ OptEnvConf.Doc: renderLongOptDocs :: AnyDocs (Maybe OptDoc) -> [Chunk]
- OptEnvConf.Doc: renderManPage :: String -> Version -> String -> AnyDocs SetDoc -> [Chunk]
+ OptEnvConf.Doc: renderManPage :: String -> Version -> String -> AnyDocs (Maybe SetDoc) -> [Chunk]
- OptEnvConf.Doc: renderReferenceDocumentation :: String -> AnyDocs SetDoc -> [Chunk]
+ OptEnvConf.Doc: renderReferenceDocumentation :: String -> AnyDocs (Maybe SetDoc) -> [Chunk]
- OptEnvConf.Doc: renderShortOptDocs :: String -> AnyDocs OptDoc -> [Chunk]
+ OptEnvConf.Doc: renderShortOptDocs :: String -> AnyDocs (Maybe OptDoc) -> [Chunk]
- OptEnvConf.Parser: enableDisableSwitch :: HasCallStack => Bool -> [Builder Bool] -> Parser Bool
+ OptEnvConf.Parser: enableDisableSwitch :: HasCallStack => [Builder Bool] -> Parser Bool
- OptEnvConf.Parser: makeDoubleSwitch :: HasCallStack => String -> String -> String -> Bool -> [Builder Bool] -> Parser Bool
+ OptEnvConf.Parser: makeDoubleSwitch :: HasCallStack => String -> String -> String -> [Builder Bool] -> Parser Bool
- OptEnvConf.Parser: yesNoSwitch :: HasCallStack => Bool -> [Builder Bool] -> Parser Bool
+ OptEnvConf.Parser: yesNoSwitch :: HasCallStack => [Builder Bool] -> Parser Bool
- OptEnvConf.Run: internalParser :: Version -> Parser a -> Parser (Internal a)
+ OptEnvConf.Run: internalParser :: Parser a -> Parser (Internal a)
- OptEnvConf.Run: runHelpParser :: Maybe TerminalCapabilities -> Args -> Parser a -> IO (Either (NonEmpty ParseError) (Maybe ([String], CommandDoc SetDoc)))
+ OptEnvConf.Run: runHelpParser :: Maybe TerminalCapabilities -> Args -> Parser a -> IO (Either (NonEmpty ParseError) (Maybe ([String], CommandDoc (Maybe SetDoc))))
- OptEnvConf.Setting: Builder :: (Setting a -> Setting a) -> Builder a
+ OptEnvConf.Setting: Builder :: [BuildInstruction a] -> Builder a
- OptEnvConf.Setting: [unBuilder] :: Builder a -> Setting a -> Setting a
+ OptEnvConf.Setting: [unBuilder] :: Builder a -> [BuildInstruction a]

Files

CHANGELOG.md view
@@ -1,5 +1,19 @@ # Changelog +## [0.5.0.0] - 2024-08-03++### Changed++* Made `Builder`s contain a list of `BuildInstructions` so library functions+  can pattern-match on the builders.+* `yesNoSwitch` and `enableDisableSwitch` now no longer take a default value,+  but accept default `value` builders instead.+* Renamed 'viaCodec' to 'viaStringCodec'.+* Clearer lints when using `name`.+* Clearer lint about loading configuration.+* Default values are now shown at the end but before example values in documentation.+* Proper `[--optional-option-docs]`+ ## [0.4.0.5] - 2024-07-28  ### Added
opt-env-conf.cabal view
@@ -5,7 +5,7 @@ -- see: https://github.com/sol/hpack  name:           opt-env-conf-version:        0.4.0.5+version:        0.5.0.0 synopsis:       Settings parsing for Haskell: command-line arguments, environment variables, and configuration values. homepage:       https://github.com/NorfairKing/opt-env-conf#readme bug-reports:    https://github.com/NorfairKing/opt-env-conf/issues
src/OptEnvConf/Doc.hs view
@@ -1,4 +1,5 @@ {-# LANGUAGE ApplicativeDo #-}+{-# LANGUAGE DeriveFunctor #-} {-# LANGUAGE GADTs #-} {-# LANGUAGE LambdaCase #-} {-# LANGUAGE OverloadedStrings #-}@@ -42,7 +43,7 @@ import Autodocodec.Schema import Autodocodec.Yaml.Schema import Control.Monad-import Data.List (intersperse)+import Data.List (intercalate, intersperse) import Data.List.NonEmpty (NonEmpty (..)) import qualified Data.List.NonEmpty as NE import Data.Maybe@@ -103,17 +104,21 @@   | AnyDocsAnd ![AnyDocs a]   | AnyDocsOr ![AnyDocs a]   | AnyDocsSingle !a-  deriving (Show)+  deriving (Show, Functor)  data CommandDoc a = CommandDoc   { commandDocArgument :: String,     commandDocHelp :: Help,     commandDocs :: AnyDocs a   }-  deriving (Show)+  deriving (Show, Functor)  mapMaybeDocs :: (a -> Maybe b) -> AnyDocs a -> AnyDocs b-mapMaybeDocs func = simplifyAnyDocs . go+mapMaybeDocs func = simplifyAnyDocs . mapMaybeDocs' func++-- Like mapMaybeDocs but without simplifying+mapMaybeDocs' :: (a -> Maybe b) -> AnyDocs a -> AnyDocs b+mapMaybeDocs' func = go   where     go = \case       AnyDocsCommands cs -> AnyDocsCommands $ map goCommandDoc cs@@ -131,9 +136,7 @@       AnyDocsAnd ds -> case concatMap goAnd ds of         [a] -> a         as -> AnyDocsAnd as-      AnyDocsOr ds -> case concatMap goOr ds of-        [a] -> a-        as -> AnyDocsOr as+      AnyDocsOr ds -> AnyDocsOr $ concatMap goOr ds       AnyDocsSingle v -> AnyDocsSingle v      goDoc cd = cd {commandDocs = go (commandDocs cd)}@@ -151,26 +154,27 @@       ds -> [go ds]  -- | Derive parser documentation-parserDocs :: Parser a -> AnyDocs SetDoc+--+-- API Note: We return a Maybe inside the AnyDocs so that we can keep track of hidden settings and use this information to know whether settings are optional or not.+parserDocs :: Parser a -> AnyDocs (Maybe SetDoc) parserDocs = simplifyAnyDocs . go   where-    noDocs = AnyDocsAnd []-    go :: Parser a -> AnyDocs SetDoc+    go :: Parser a -> AnyDocs (Maybe SetDoc)     go = \case-      ParserPure _ -> noDocs+      ParserPure _ -> AnyDocsSingle Nothing       ParserAp pf pa -> AnyDocsAnd [go pf, go pa]       ParserSelect p1 p2 -> AnyDocsAnd [go p1, go p2]       ParserEmpty _ -> AnyDocsOr []       ParserAlt p1 p2 -> AnyDocsOr [go p1, go p2]-      ParserMany p -> go p -- TODO: is this right?-      ParserSome p -> go p -- TODO: is this right?+      ParserMany p -> AnyDocsOr [go p, AnyDocsSingle Nothing]+      ParserSome p -> AnyDocsAnd [go p, go (ParserMany p)] -- TODO: is this right?       ParserAllOrNothing _ p -> go p       ParserCheck _ _ _ p -> go p       ParserCommands _ cs -> AnyDocsCommands $ map commandParserDocs cs       ParserWithConfig _ p1 p2 -> AnyDocsAnd [go p1, go p2] -- TODO: is this right? Maybe we want to document that it's not a pure parser?-      ParserSetting _ set -> maybe noDocs AnyDocsSingle $ settingSetDoc set+      ParserSetting _ set -> AnyDocsSingle $ settingSetDoc set -commandParserDocs :: Command a -> CommandDoc SetDoc+commandParserDocs :: Command a -> CommandDoc (Maybe SetDoc) commandParserDocs Command {..} =   CommandDoc     { commandDocArgument = commandArg,@@ -178,6 +182,9 @@       commandDocs = parserDocs commandParser     } +withoutHiddenDocs :: AnyDocs (Maybe a) -> AnyDocs a+withoutHiddenDocs = mapMaybeDocs id+ settingSetDoc :: Setting a -> Maybe SetDoc settingSetDoc Setting {..} = do   guard $ not settingHidden@@ -215,13 +222,7 @@ renderSetDocWithoutHeader :: SetDoc -> [[Chunk]] renderSetDocWithoutHeader SetDoc {..} =   concat-    [ [ defaultValueChunks dv-        | dv <- maybeToList setDocDefault-      ],-      [ exampleValuesChunks setDocExamples-        | not (null setDocExamples)-      ],-      [ unwordsChunks+    [ [ unwordsChunks           [ ["argument:"],             [mMetavarChunk setDocMetavar]           ]@@ -267,7 +268,13 @@             )             (NE.toList confs)           | confs <- maybeToList setDocConfKeys-        ]+        ],+      [ defaultValueChunks dv+        | dv <- maybeToList setDocDefault+      ],+      [ exampleValuesChunks setDocExamples+        | not (null setDocExamples)+      ]     ]  helpLines :: Help -> [[Chunk]]@@ -281,10 +288,11 @@   String ->   Version ->   String ->-  AnyDocs SetDoc ->+  AnyDocs (Maybe SetDoc) ->   [Chunk]-renderManPage progname version progDesc docs =-  let optDocs = docsToOptDocs docs+renderManPage progname version progDesc docs' =+  let docs = withHelpAndVersionDocs (Just version) docs'+      optDocs = docsToOptDocs docs       envDocs = docsToEnvDocs docs       confDocs = docsToConfDocs docs       commandDocs = docsToCommandDocs docs@@ -304,7 +312,7 @@               [".Sh ", "VERSION"],               [versionChunk version],               [".Sh ", "SYNOPSIS"],-              renderShortOptDocs progname optDocs,+              renderShortOptDocs progname $ docsToOptDocs docs',               [".Sh ", "SETTINGS"],               renderSetDocs docs             ],@@ -335,15 +343,16 @@           ]  -- | Render reference documentation-renderReferenceDocumentation :: String -> AnyDocs SetDoc -> [Chunk]-renderReferenceDocumentation progname docs =-  let optDocs = docsToOptDocs docs+renderReferenceDocumentation :: String -> AnyDocs (Maybe SetDoc) -> [Chunk]+renderReferenceDocumentation progname docs' =+  let docs = withHelpAndVersionDocs Nothing docs'+      optDocs = docsToOptDocs docs       envDocs = docsToEnvDocs docs       confDocs = docsToConfDocs docs       commandDocs = docsToCommandDocs docs    in unlinesChunks $         concat-          [ [ usageChunk : renderShortOptDocs progname optDocs,+          [ [ usageChunk : renderShortOptDocs progname (docsToOptDocs docs'),               [],               headerChunks "All settings",               renderSetDocs docs@@ -395,21 +404,36 @@       ["\n"]     ] --- | Render the output of @--help@-renderHelpPage :: String -> String -> AnyDocs SetDoc -> [Chunk]-renderHelpPage progname progDesc docs =+-- | Render the output of top-level @--help@+renderHelpPage :: String -> Version -> String -> AnyDocs (Maybe SetDoc) -> [Chunk]+renderHelpPage progname version =+  renderHelpPageHelper progname [] (Just version)++-- | Render the output of a @--help@ with at least one command+renderCommandHelpPage :: String -> [String] -> CommandDoc (Maybe SetDoc) -> [Chunk]+renderCommandHelpPage progname commandPath CommandDoc {..} =+  renderHelpPageHelper+    progname+    (commandPath ++ [commandDocArgument])+    Nothing+    commandDocHelp+    commandDocs++renderHelpPageHelper :: String -> [String] -> Maybe Version -> String -> AnyDocs (Maybe SetDoc) -> [Chunk]+renderHelpPageHelper progname' commandPath mVersion progDesc docs =   unlinesChunks $     concat-      [ [ usageChunk : renderShortOptDocs progname (docsToOptDocs docs),+      [ [ let progname = unwords $ progname' : commandPath+           in usageChunk : renderShortOptDocs progname (docsToOptDocs docs),           [],           unlinesChunks $ progDescLines progDesc         ],-        concat-          [ [ headerChunks "Available settings",-              renderSetDocs docs-            ]-            | not (nullDocs docs)-          ],+        [ headerChunks "Available settings",+          renderSetDocs $+            if null commandPath+              then withHelpAndVersionDocs mVersion docs+              else withHelpDocs docs+        ],         concat           [ [ headerChunks "Available commands",               renderCommandDocsShort docs@@ -418,37 +442,63 @@           ]       ] -renderCommandHelpPage :: String -> [String] -> CommandDoc SetDoc -> [Chunk]-renderCommandHelpPage progname commandPath CommandDoc {..} =-  renderHelpPage (unwords $ progname : commandPath ++ [commandDocArgument]) commandDocHelp commandDocs+withHelpAndVersionDocs :: Maybe Version -> AnyDocs (Maybe SetDoc) -> AnyDocs (Maybe SetDoc)+withHelpAndVersionDocs mVersion sd = simplifyAnyDocs $ AnyDocsOr [helpDocs, versionDocs mVersion, sd] -renderSetDocs :: AnyDocs SetDoc -> [Chunk]-renderSetDocs = unlinesChunks . go+withHelpDocs :: AnyDocs (Maybe SetDoc) -> AnyDocs (Maybe SetDoc)+withHelpDocs sd = simplifyAnyDocs $ AnyDocsOr [helpDocs, sd]++helpDocs :: AnyDocs (Maybe SetDoc)+helpDocs =+  AnyDocsSingle $+    Just+      SetDoc+        { setDocTryArgument = False,+          setDocTrySwitch = True,+          setDocTryOption = False,+          setDocDasheds = ["-h", "--help"],+          setDocEnvVars = Nothing,+          setDocConfKeys = Nothing,+          setDocDefault = Nothing,+          setDocExamples = [],+          setDocMetavar = Nothing,+          setDocHelp = Just "Show this help text"+        }++versionDocs :: Maybe Version -> AnyDocs (Maybe SetDoc)+versionDocs mVersion =+  AnyDocsSingle $+    Just+      SetDoc+        { setDocTryArgument = False,+          setDocTrySwitch = True,+          setDocTryOption = False,+          setDocDasheds = ["--version"],+          setDocEnvVars = Nothing,+          setDocConfKeys = Nothing,+          setDocDefault = Nothing,+          setDocExamples = [],+          setDocMetavar = Nothing,+          setDocHelp = Just $ case mVersion of+            Nothing -> "Output version information"+            Just version -> "Output version information: " <> showVersion version+        }++renderSetDocs :: AnyDocs (Maybe SetDoc) -> [Chunk]+renderSetDocs = unlinesChunks . intercalate [[]] . go . combineSetDocs . withoutHiddenDocs   where-    go :: AnyDocs SetDoc -> [[Chunk]]+    -- One section each, so we can add empty lines inbetween+    go :: AnyDocs (NonEmpty SetDoc) -> [[[Chunk]]]     go = \case       AnyDocsCommands _ -> []       AnyDocsAnd ds -> concatMap go ds-      AnyDocsOr ds -> goOr ds-      AnyDocsSingle d -> indent (renderSetDoc d)--    -- Group together settings with the same help (produced by combinators like enableDisableSwitch)-    goOr :: [AnyDocs SetDoc] -> [[Chunk]]-    goOr = \case-      [] -> []-      [d] -> go d-      (AnyDocsSingle d : ds) ->-        case setDocHelp d of-          Nothing -> go (AnyDocsSingle d) ++ goOr ds-          Just h ->-            let (sds, rest) = goSameHelp h ds-             in concat-                  [ indent $ renderSetDocHeader (Just h),-                    indent $ concatMap renderSetDocWithoutHeader $ d : sds,-                    [[] | not (null rest)],-                    goOr rest-                  ]-      (d : ds) -> go d ++ goOr ds+      AnyDocsOr ds -> concatMap go ds+      AnyDocsSingle (d :| ds) ->+        [ concat+            [ indent $ renderSetDocHeader (setDocHelp d),+              indent $ concatMap renderSetDocWithoutHeader (d : ds)+            ]+        ]      goSameHelp :: Help -> [AnyDocs SetDoc] -> ([SetDoc], [AnyDocs SetDoc])     goSameHelp h = \case@@ -461,8 +511,31 @@           else ([], AnyDocsSingle d : ds)       ds -> ([], ds) -renderCommandDocs :: AnyDocs SetDoc -> [Chunk]-renderCommandDocs = unlinesChunks . go True+    -- Group together settings with the same help (produced by combinators like enableDisableSwitch)+    combineSetDocs :: AnyDocs SetDoc -> AnyDocs (NonEmpty SetDoc)+    combineSetDocs = go'+      where+        go' :: AnyDocs SetDoc -> AnyDocs (NonEmpty SetDoc)+        go' = \case+          AnyDocsCommands _ -> AnyDocsCommands [] -- Don't care about commands here+          AnyDocsOr ds -> AnyDocsOr $ goOr' ds+          AnyDocsAnd ds -> AnyDocsAnd $ map go' ds+          AnyDocsSingle d -> AnyDocsSingle (d :| [])+        goOr' :: [AnyDocs SetDoc] -> [AnyDocs (NonEmpty SetDoc)]+        goOr' = \case+          [] -> []+          [d] -> [go' d]+          (AnyDocsSingle d : ds) ->+            case setDocHelp d of+              Nothing -> go' (AnyDocsSingle d) : goOr' ds+              Just h ->+                let (sds, rest) = goSameHelp h ds+                    ne = d :| sds+                 in AnyDocsSingle ne : goOr' rest+          (d : ds) -> go' d : goOr' ds++renderCommandDocs :: AnyDocs (Maybe SetDoc) -> [Chunk]+renderCommandDocs = unlinesChunks . go True . withoutHiddenDocs   where     go :: Bool -> AnyDocs SetDoc -> [[Chunk]]     go isTopLevel = \case@@ -514,8 +587,8 @@           else ([], AnyDocsSingle d : ds)       ds -> ([], ds) -renderCommandDocsShort :: AnyDocs SetDoc -> [Chunk]-renderCommandDocsShort = layoutAsTable . go+renderCommandDocsShort :: AnyDocs (Maybe SetDoc) -> [Chunk]+renderCommandDocsShort = layoutAsTable . go . withoutHiddenDocs   where     go :: AnyDocs SetDoc -> [[[Chunk]]]     go = \case@@ -528,11 +601,11 @@     goCommand CommandDoc {..} =       [indent [[commandChunk commandDocArgument], [helpChunk commandDocHelp]]] -parserOptDocs :: Parser a -> AnyDocs OptDoc+parserOptDocs :: Parser a -> AnyDocs (Maybe OptDoc) parserOptDocs = docsToOptDocs . parserDocs -docsToOptDocs :: AnyDocs SetDoc -> AnyDocs OptDoc-docsToOptDocs = mapMaybeDocs setDocOptDoc+docsToOptDocs :: AnyDocs (Maybe SetDoc) -> AnyDocs (Maybe OptDoc)+docsToOptDocs = mapMaybeDocs (>>= Just . setDocOptDoc)  setDocOptDoc :: SetDoc -> Maybe OptDoc setDocOptDoc SetDoc {..} = do@@ -548,47 +621,64 @@   pure OptDoc {..}  -- | Render short-form documentation of options-renderShortOptDocs :: String -> AnyDocs OptDoc -> [Chunk]-renderShortOptDocs progname = unwordsChunks . (\cs -> [[progNameChunk progname], cs]) . go+renderShortOptDocs :: String -> AnyDocs (Maybe OptDoc) -> [Chunk]+renderShortOptDocs progname = unwordsChunks . (\cs -> [[progNameChunk progname], cs]) . fromMaybe [] . go False   where-    go :: AnyDocs OptDoc -> [Chunk]-    go = \case-      AnyDocsCommands _ -> ["COMMAND"]-      AnyDocsAnd ds -> unwordsChunks $ map go ds-      AnyDocsOr ds -> renderOrChunks $ map go ds-      AnyDocsSingle OptDoc {..} ->-        unwordsChunks $-          concat-            [ [ [mMetavarChunk optDocMetavar]-                | optDocTryArgument-              ],-              [ concat $ maybeToList $ dashedChunks optDocDasheds-                | optDocTrySwitch-              ],-              [ concat-                  [ concat $ maybeToList $ dashedChunks optDocDasheds,-                    [" ", mMetavarChunk optDocMetavar]+    withoutNothings =+      filter+        ( \case+            AnyDocsSingle Nothing -> False+            AnyDocsAnd [] -> False+            AnyDocsOr [] -> False+            _ -> True+        )+    go ::+      -- Need parens+      Bool ->+      AnyDocs (Maybe OptDoc) ->+      Maybe [Chunk]+    go b =+      \case+        AnyDocsCommands _ -> Just ["COMMAND"]+        AnyDocsAnd ds ->+          case mapMaybe (go False) (withoutNothings ds) of+            [] -> Nothing+            [c] -> Just c+            cs -> Just $ (if b then parenthesise else id) $ unwordsChunks cs+        AnyDocsOr ds ->+          case mapMaybe (go False) (withoutNothings ds) of+            [] -> Nothing+            cs -> Just $ bracketise $ unwordsChunks $ intersperse [orChunk] cs+        AnyDocsSingle mOd -> case mOd of+          Nothing -> Nothing+          Just OptDoc {..} ->+            Just+              $ ( if isJust optDocDefault+                    then bracketise+                    else id+                )+              $ unwordsChunks+              $ concat+                [ [ [mMetavarChunk optDocMetavar]+                    | optDocTryArgument+                  ],+                  [ concat $ maybeToList $ dashedChunks optDocDasheds+                    | optDocTrySwitch+                  ],+                  [ concat+                      [ concat $ maybeToList $ dashedChunks optDocDasheds,+                        [" ", mMetavarChunk optDocMetavar]+                      ]+                    | optDocTryOption                   ]-                | optDocTryOption-              ]-            ]--renderOrChunks :: [[Chunk]] -> [Chunk]-renderOrChunks os =-  unwordsChunks $-    intersperse [orChunk] $-      map parenthesise os-  where-    parenthesise :: [Chunk] -> [Chunk]-    parenthesise [c] = [c]-    parenthesise cs = fore cyan "(" : cs ++ [fore cyan ")"]+                ]  orChunk :: Chunk orChunk = fore cyan "|"  -- | Render long-form documentation of options-renderLongOptDocs :: AnyDocs OptDoc -> [Chunk]-renderLongOptDocs = unlinesChunks . go+renderLongOptDocs :: AnyDocs (Maybe OptDoc) -> [Chunk]+renderLongOptDocs = unlinesChunks . go . withoutHiddenDocs   where     go :: AnyDocs OptDoc -> [[Chunk]]     go = \case@@ -635,8 +725,8 @@ parserEnvDocs :: Parser a -> AnyDocs EnvDoc parserEnvDocs = docsToEnvDocs . parserDocs -docsToEnvDocs :: AnyDocs SetDoc -> AnyDocs EnvDoc-docsToEnvDocs = mapMaybeDocs setDocEnvDoc+docsToEnvDocs :: AnyDocs (Maybe SetDoc) -> AnyDocs EnvDoc+docsToEnvDocs = mapMaybeDocs (>>= setDocEnvDoc)  setDocEnvDoc :: SetDoc -> Maybe EnvDoc setDocEnvDoc SetDoc {..} = do@@ -678,8 +768,8 @@ parserConfDocs :: Parser a -> AnyDocs ConfDoc parserConfDocs = docsToConfDocs . parserDocs -docsToConfDocs :: AnyDocs SetDoc -> AnyDocs ConfDoc-docsToConfDocs = mapMaybeDocs setDocConfDoc+docsToConfDocs :: AnyDocs (Maybe SetDoc) -> AnyDocs ConfDoc+docsToConfDocs = mapMaybeDocs (>>= setDocConfDoc)  setDocConfDoc :: SetDoc -> Maybe ConfDoc setDocConfDoc SetDoc {..} = do@@ -692,7 +782,7 @@ settingConfDoc :: Setting a -> Maybe ConfDoc settingConfDoc = settingSetDoc >=> setDocConfDoc -docsToCommandDocs :: AnyDocs SetDoc -> [CommandDoc SetDoc]+docsToCommandDocs :: AnyDocs (Maybe SetDoc) -> [CommandDoc (Maybe SetDoc)] docsToCommandDocs = \case   AnyDocsCommands cs -> cs   AnyDocsAnd ds -> concatMap docsToCommandDocs ds
src/OptEnvConf/Lint.hs view
@@ -131,6 +131,8 @@           ]         LintErrorNoReaderForOption ->           [ [ functionChunk "option",+              " or ",+              functionChunk "name",               " has no ",               functionChunk "reader",               "."@@ -147,6 +149,8 @@           ]         LintErrorNoMetavarForOption ->           [ [ functionChunk "option",+              " or ",+              functionChunk "name",               " has no ",               functionChunk "metavar",               "."@@ -174,6 +178,8 @@           ]         LintErrorNoReaderForEnvVar ->           [ [ functionChunk "env",+              " or ",+              functionChunk "name",               " has no ",               functionChunk "reader",               "."@@ -181,6 +187,8 @@           ]         LintErrorNoMetavarForEnvVar ->           [ [ functionChunk "env",+              " or ",+              functionChunk "name",               " has no ",               functionChunk "metavar",               "."@@ -201,7 +209,15 @@           ]         LintErrorConfigWithoutLoad ->           [ [ functionChunk "conf",+              " or ",+              functionChunk "name",               " was called with no way to load configuration."+            ],+            [ "You can load configuration with ",+              functionChunk "withConfig",+              ", or explicitly not load any configuration with ",+              functionChunk "withoutConfig",+              "."             ]           ]         LintErrorManyInfinite ->
src/OptEnvConf/Output.hs view
@@ -85,3 +85,11 @@  indent :: [[Chunk]] -> [[Chunk]] indent = map ("  " :)++parenthesise :: [Chunk] -> [Chunk]+parenthesise [c] = [c]+parenthesise cs = fore cyan "(" : cs ++ [fore cyan ")"]++bracketise :: [Chunk] -> [Chunk]+bracketise [c] = [fore cyan "[", c, fore cyan "]"]+bracketise cs = fore cyan "[" : cs ++ [fore cyan "]"]
src/OptEnvConf/Parser.hs view
@@ -728,35 +728,33 @@  -- | Define a setting for a 'Bool' with a given default value. ----- If you pass in `long` values, it will have @--foobar@ and @--no-foobar@ switches.--- If you pass in `env` values, it will read those environment variables too.--- If you pass in `conf` values, it will read those configuration values too.+-- If you pass in 'long' values, it will have @--foobar@ and @--no-foobar@ switches.+-- If you pass in 'env' values, it will read those environment variables too.+-- If you pass in 'conf' values, it will read those configuration values too.+-- If you pass in a 'value' value, it will use that as the default value. yesNoSwitch ::   (HasCallStack) =>-  -- | Default value-  Bool ->   -- | Builders   [Builder Bool] ->   Parser Bool-yesNoSwitch defaultBool builders =+yesNoSwitch builders =   withFrozenCallStack $-    makeDoubleSwitch "" "no-" "[no-]" defaultBool builders+    makeDoubleSwitch "" "no-" "[no-]" builders  -- | Define a setting for a 'Bool' with a given default value. ----- If you pass in `long` values, it will have @--enable-foobar@ and @--disable-foobar@ switches.--- If you pass in `env` values, it will read those environment variables too.--- If you pass in `conf` values, it will read those configuration values too.+-- If you pass in 'long' values, it will have @--enable-foobar@ and @--disable-foobar@ switches.+-- If you pass in 'env' values, it will read those environment variables too.+-- If you pass in 'conf' values, it will read those configuration values too.+-- If you pass in a 'value' value, it will use that as the default value. enableDisableSwitch ::   (HasCallStack) =>-  -- | Default value-  Bool ->   -- | Builders   [Builder Bool] ->   Parser Bool-enableDisableSwitch defaultBool builders =+enableDisableSwitch builders =   withFrozenCallStack $-    makeDoubleSwitch "enable-" "disable-" "(enable|disable)-" defaultBool builders+    makeDoubleSwitch "enable-" "disable-" "(enable|disable)-" builders  makeDoubleSwitch ::   (HasCallStack) =>@@ -766,12 +764,10 @@   String ->   -- | Prefix for the documented 'long's   String ->-  -- | Default nvnalue-  Bool ->   -- | Builders   [Builder Bool] ->   Parser Bool-makeDoubleSwitch truePrefix falsePrefix helpPrefix defaultBool builders =+makeDoubleSwitch truePrefix falsePrefix helpPrefix builders =   withFrozenCallStack $     choice $       catMaybes@@ -780,11 +776,16 @@           Just parseEnableSwitch,           parseEnv,           parseConfigVal,-          Just $ pure defaultBool+          parseDefaultVal         ]   where-    s = buildSetting builders     mLoc = snd <$> listToMaybe (getCallStack callStack)+    s = buildSetting builders+    parseDefaultVal :: Maybe (Parser Bool)+    parseDefaultVal = do+      (dv, _) <- settingDefaultValue s+      pure $ pure dv+     parseEnableSwitch :: Parser Bool     parseEnableSwitch =       ParserSetting mLoc $@@ -865,7 +866,7 @@           { settingDasheds = mapMaybe (prefixDashedLong helpPrefix) (settingDasheds s),             settingReaders = [],             settingTryArgument = False,-            settingSwitchValue = Just defaultBool, -- Unused+            settingSwitchValue = Just True, -- Unused             settingTryOption = False,             settingEnvVars = Nothing,             settingConfigVals = Nothing,
src/OptEnvConf/Reader.hs view
@@ -12,7 +12,7 @@     str,     auto,     exists,-    viaCodec,+    viaStringCodec,      -- * Constructing your own reader     maybeReader,@@ -88,8 +88,9 @@ exists :: Reader Bool exists = Reader $ const $ pure True -viaCodec :: (HasCodec a) => Reader a-viaCodec = eitherReader $ parseEither $ parseJSONViaCodec . JSON.String . T.pack+-- | Read a value as if it were specified as a 'String' and parsed via the given 'Codec'.+viaStringCodec :: (HasCodec a) => Reader a+viaStringCodec = eitherReader $ parseEither $ parseJSONViaCodec . JSON.String . T.pack  -- | Turn a 'Maybe' parsing function into a 'Reader' maybeReader :: (String -> Maybe a) -> Reader a
src/OptEnvConf/Run.hs view
@@ -105,8 +105,7 @@       hPutChunksLocaleWith tc stderr $ renderLintErrors errs       exitFailure     Nothing -> do-      let p' = internalParser version p-      let docs = parserDocs p'+      let docs = parserDocs p        mDebugMode <-         if debugMode@@ -130,7 +129,7 @@             Right mCommandDoc -> do               tc <- getTerminalCapabilitiesFromHandle stdout               hPutChunksLocaleWith tc stdout $ case mCommandDoc of-                Nothing -> renderHelpPage progname progDesc docs+                Nothing -> renderHelpPage progname version progDesc docs                 Just (path, cDoc) -> renderCommandHelpPage progname path cDoc               exitSuccess         else do@@ -152,6 +151,7 @@                   hPutChunksLocaleWith tc stdout ["Settings parsed successfully."]                   exitSuccess             else do+              let p' = internalParser p               errOrResult <-                 runParserOn                   mDebugMode@@ -165,7 +165,6 @@                   hPutChunksLocaleWith tc stderr $ renderErrors errs                   exitFailure                 Right i -> case i of-                  ShowHelp -> die "unreachable, this option is only here for documentation."                   ShowVersion -> do                     progname <- getProgName                     tc <- getTerminalCapabilitiesFromHandle stdout@@ -204,8 +203,7 @@ -- Internal structure to help us do what the framework -- is supposed to. data Internal a-  = ShowHelp-  | ShowVersion+  = ShowVersion   | RenderMan   | RenderDocumentation   | RenderNixosOptions@@ -221,19 +219,13 @@       ![String]   | ParsedNormally !a -internalParser :: Version -> Parser a -> Parser (Internal a)-internalParser version p =+internalParser :: Parser a -> Parser (Internal a)+internalParser p =   choice     [ setting-        [ switch ShowHelp,-          short 'h',-          long "help",-          help "Show this help text"-        ],-      setting         [ switch ShowVersion,           long "version",-          help $ "Output version information: " <> showVersion version+          hidden         ],       setting         [ switch RenderMan,@@ -653,7 +645,7 @@                                                   pure Nothing                                                 Just a -> do                                                   debug-                                                    [ "set based on config value:",+                                                    [ "set based on config value: ",                                                       chunk $ T.pack $ show v                                                     ]                                                   pure $ Just a@@ -721,7 +713,7 @@   Maybe TerminalCapabilities ->   Args ->   Parser a ->-  IO (Either (NonEmpty ParseError) (Maybe ([String], CommandDoc SetDoc)))+  IO (Either (NonEmpty ParseError) (Maybe ([String], CommandDoc (Maybe SetDoc)))) runHelpParser mDebugMode args parser = do   let ppState =         PPState@@ -743,9 +735,9 @@       Success mDocs -> Right mDocs   where     -- We try to parse the commands as deep as possible and ignore everything else.-    go' :: [String] -> Parser a -> PP (Maybe ([String], CommandDoc SetDoc))+    go' :: [String] -> Parser a -> PP (Maybe ([String], CommandDoc (Maybe SetDoc)))     go' path =-      let go :: Parser a -> PP (Maybe ([String], CommandDoc SetDoc))+      let go :: Parser a -> PP (Maybe ([String], CommandDoc (Maybe SetDoc)))           go = go' path        in \case             ParserPure _ -> do
src/OptEnvConf/Setting.hs view
@@ -30,6 +30,7 @@     shownExample,     hidden,     Builder (..),+    BuildInstruction (..),      -- * Internal     showSettingABit,@@ -44,6 +45,7 @@ import Autodocodec import Data.List.NonEmpty (NonEmpty (..), (<|)) import qualified Data.List.NonEmpty as NE+import Data.Maybe import OptEnvConf.Args (Dashed (..)) import OptEnvConf.Casing import OptEnvConf.Reader@@ -156,30 +158,64 @@       . showListWith func as  -- | Builder for a 'Setting'-newtype Builder a = Builder {unBuilder :: Setting a -> Setting a}+newtype Builder a = Builder {unBuilder :: [BuildInstruction a]} +data BuildInstruction a+  = BuildAddHelp !String+  | BuildSetMetavar !String+  | BuildTryArgument+  | BuildTryOption+  | BuildSetSwitchValue !a+  | BuildAddReader !(Reader a)+  | BuildAddLong !(NonEmpty Char)+  | BuildAddShort !Char+  | BuildAddEnv !String+  | BuildAddConf !(ConfigValSetting a)+  | BuildSetDefault !a !String+  | BuildAddExample !String+  | BuildSetHidden++applyBuildInstructions :: [BuildInstruction a] -> Setting a -> Setting a+applyBuildInstructions is s = foldr applyBuildInstruction s is++applyBuildInstruction :: BuildInstruction a -> Setting a -> Setting a+applyBuildInstruction bi s = case bi of+  BuildAddHelp h -> s {settingHelp = Just $ maybe h (<> h) (settingHelp s)}+  BuildSetMetavar mv -> s {settingMetavar = Just mv}+  BuildTryArgument -> s {settingTryArgument = True}+  BuildTryOption -> s {settingTryOption = True}+  BuildSetSwitchValue a -> s {settingSwitchValue = Just a}+  BuildAddReader r -> s {settingReaders = r : settingReaders s}+  BuildAddLong l -> s {settingDasheds = DashedLong l : settingDasheds s}+  BuildAddShort c -> s {settingDasheds = DashedShort c : settingDasheds s}+  BuildAddEnv v -> s {settingEnvVars = Just $ maybe (v :| []) (v <|) $ settingEnvVars s}+  BuildAddConf t -> s {settingConfigVals = Just $ maybe (t :| []) (t <|) $ settingConfigVals s}+  BuildSetDefault a shown -> s {settingDefaultValue = Just (a, shown)}+  BuildAddExample e -> s {settingExamples = e : settingExamples s}+  BuildSetHidden -> s {settingHidden = True}+ instance Semigroup (Builder f) where-  (<>) (Builder f1) (Builder f2) = Builder (f1 . f2)+  (<>) (Builder f1) (Builder f2) = Builder (f1 <> f2)  instance Monoid (Builder f) where-  mempty = Builder id+  mempty = Builder []   mappend = (<>)  -- | Complete a 'Builder' into a 'Setting' completeBuilder :: Builder a -> Setting a-completeBuilder b = unBuilder b emptySetting+completeBuilder b = applyBuildInstructions (unBuilder b) emptySetting  -- | Document a setting -- -- Multiple 'help's concatenate help on new lines. help :: String -> Builder a-help s = Builder $ \op -> op {settingHelp = Just $ maybe s (s <>) (settingHelp op)}+help s = Builder [BuildAddHelp s]  -- | Document an 'option' or 'env' var. -- -- Multiple 'metavar's override eachother. metavar :: String -> Builder a-metavar mv = Builder $ \s -> s {settingMetavar = Just mv}+metavar mv = Builder [BuildSetMetavar mv]  -- | Try to parse an argument. --@@ -187,7 +223,7 @@ -- -- Multiple 'argument's are redundant. argument :: Builder a-argument = Builder $ \s -> s {settingTryArgument = True}+argument = Builder [BuildTryArgument]  -- | Try to parse an argument. --@@ -196,7 +232,7 @@ -- -- Multiple 'option's are redundant. option :: Builder a-option = Builder $ \s -> s {settingTryOption = True}+option = Builder [BuildTryOption]  -- | Try to parse a switch, activate the given value when succesful --@@ -204,11 +240,11 @@ -- -- Multiple 'switch's override eachother. switch :: a -> Builder a-switch v = Builder $ \s -> s {settingSwitchValue = Just v}+switch v = Builder [BuildSetSwitchValue v]  -- | Declare how to parse an argument, option, or environment variable. reader :: Reader a -> Builder a-reader r = Builder $ \s -> s {settingReaders = r : settingReaders s}+reader r = Builder [BuildAddReader r]  -- | Try to parse this 'long' 'option' or 'switch'. --@@ -221,9 +257,7 @@ -- Multiple 'long's will be tried in order. -- Empty 'long's will be ignored. long :: String -> Builder a-long l = Builder $ \s -> case NE.nonEmpty l of-  Nothing -> s-  Just ne -> s {settingDasheds = DashedLong ne : settingDasheds s}+long l = Builder [BuildAddLong ne | ne <- maybeToList (NE.nonEmpty l)]  -- | Try to parse this 'short' 'option' or 'switch'. --@@ -234,7 +268,7 @@ -- -- Multiple 'short's will be tried in order. short :: Char -> Builder a-short c = Builder $ \s -> s {settingDasheds = DashedShort c : settingDasheds s}+short c = Builder [BuildAddShort c]  -- | Try to parse an environment variable. --@@ -242,7 +276,7 @@ -- -- Multiple 'env's will be tried in order. env :: String -> Builder a-env v = Builder $ \s -> s {settingEnvVars = Just $ maybe (v :| []) (v <|) $ settingEnvVars s}+env v = Builder [BuildAddEnv v]  -- | Try to parse a configuration value at the given key. --@@ -250,6 +284,16 @@ conf :: (HasCodec a) => String -> Builder a conf k = confWith k codec +-- | Like 'conf' but with a custom 'Codec' for parsing the value.+confWith :: String -> ValueCodec void a -> Builder a+confWith k c = confWith' k (maybeCodec c)++-- | Like 'confWith' but allows interpreting 'Null' as a value other than "Not found".+confWith' :: String -> ValueCodec void (Maybe a) -> Builder a+confWith' k c =+  let t = ConfigValSetting {configValSettingPath = k :| [], configValSettingCodec = c}+   in Builder [BuildAddConf t]+ -- | Short-hand function for 'option', 'long', 'env', and 'conf' at the same time. -- -- Multiple 'name's will be tried in order.@@ -262,16 +306,6 @@       conf (toConfigCase s)     ] --- | Like 'conf' but with a custom 'Codec' for parsing the value.-confWith :: String -> ValueCodec void a -> Builder a-confWith k c = confWith' k (maybeCodec c)---- | Like 'confWith' but allows interpreting 'Null' as a value other than "Not found".-confWith' :: String -> ValueCodec void (Maybe a) -> Builder a-confWith' k c =-  let t = ConfigValSetting {configValSettingPath = k :| [], configValSettingCodec = c}-   in Builder $ \s -> s {settingConfigVals = Just $ maybe (t :| []) (t <|) $ settingConfigVals s}- -- | Set the default value -- -- Multiple 'value's override eachother.@@ -283,7 +317,7 @@  -- | Set the default value, along with a shown version of it. valueWithShown :: a -> String -> Builder a-valueWithShown a shown = Builder $ \s -> s {settingDefaultValue = Just (a, shown)}+valueWithShown a shown = Builder [BuildSetDefault a shown]  -- | Provide an example value for documentation. --@@ -291,7 +325,7 @@ -- -- If you use 'reader' 'auto', you'll want to use 'shownExample' instead. example :: String -> Builder a-example s = Builder $ \set -> set {settingExamples = s : settingExamples set}+example s = Builder [BuildAddExample s]  -- | Use 'Show' to show an 'example'. --@@ -303,4 +337,4 @@ -- -- Multiple 'hidden's are redundant. hidden :: Builder a-hidden = Builder $ \s -> s {settingHidden = True}+hidden = Builder [BuildSetHidden]