packages feed

opt-env-conf 0.13.0.0 → 0.15.0.0

raw patch · 12 files changed

+357/−119 lines, 12 filesPVP ok

version bump matches the API change (PVP)

API changes (from Hackage documentation)

+ OptEnvConf: filePathWithExtension :: String -> Completer
+ OptEnvConf: filePathWithExtensions :: [String] -> Completer
+ OptEnvConf.Completer: CompletionFinal :: CompletionFinality
+ OptEnvConf.Completer: CompletionNotFinal :: CompletionFinality
+ OptEnvConf.Completer: CompletionResult :: !String -> !CompletionFinality -> CompletionResult
+ OptEnvConf.Completer: [completionResultFinality] :: CompletionResult -> !CompletionFinality
+ OptEnvConf.Completer: [completionResultValue] :: CompletionResult -> !String
+ OptEnvConf.Completer: data CompletionFinality
+ OptEnvConf.Completer: data CompletionResult
+ OptEnvConf.Completer: filePathWithExtension :: String -> Completer
+ OptEnvConf.Completer: filePathWithExtensions :: [String] -> Completer
+ OptEnvConf.Completer: finalResult :: String -> CompletionResult
+ OptEnvConf.Completer: instance GHC.Classes.Eq OptEnvConf.Completer.CompletionFinality
+ OptEnvConf.Completer: instance GHC.Classes.Eq OptEnvConf.Completer.CompletionResult
+ OptEnvConf.Completer: instance GHC.Classes.Ord OptEnvConf.Completer.CompletionFinality
+ OptEnvConf.Completer: instance GHC.Classes.Ord OptEnvConf.Completer.CompletionResult
+ OptEnvConf.Completer: instance GHC.Show.Show OptEnvConf.Completer.CompletionFinality
+ OptEnvConf.Completer: instance GHC.Show.Show OptEnvConf.Completer.CompletionResult
+ OptEnvConf.Nix: renderParserNixOptionsWithGeneratedComment :: String -> Parser a -> Text
+ OptEnvConf.Nix: renderSettingsNixOptionsWithGeneratedComment :: HasParser a => String -> Text
+ OptEnvConf.Nix: withGeneratedComment :: String -> Text -> Text
- OptEnvConf.Completer: Completer :: (String -> IO [String]) -> Completer
+ OptEnvConf.Completer: Completer :: (String -> IO [CompletionResult]) -> Completer
- OptEnvConf.Completer: [unCompleter] :: Completer -> String -> IO [String]
+ OptEnvConf.Completer: [unCompleter] :: Completer -> String -> IO [CompletionResult]
- OptEnvConf.Completer: mkCompleter :: (String -> IO [String]) -> Completer
+ OptEnvConf.Completer: mkCompleter :: (String -> IO [CompletionResult]) -> Completer
- OptEnvConf.Completion: evalCompletion :: String -> Completion Suggestion -> IO [Completion String]
+ OptEnvConf.Completion: evalCompletion :: String -> Completion Suggestion -> IO [Completion CompletionResult]
- OptEnvConf.Completion: evalCompletions :: String -> [Completion Suggestion] -> IO [Completion String]
+ OptEnvConf.Completion: evalCompletions :: String -> [Completion Suggestion] -> IO [Completion CompletionResult]
- OptEnvConf.Completion: evalSuggestion :: String -> Suggestion -> IO [String]
+ OptEnvConf.Completion: evalSuggestion :: String -> Suggestion -> IO [CompletionResult]

Files

CHANGELOG.md view
@@ -1,5 +1,51 @@ # Changelog +## [0.15.0.0] - 2026-03-17++### Added++* `filePathWithExtension` and `filePathWithExtensions` completers for+  completing only files with specific extensions.+* `CompletionResult` and `CompletionFinality` types for per-result+  trailing-space suppression in zsh.++### Changed++* **Breaking:** `Completer` now wraps `String -> IO [CompletionResult]`+  instead of `String -> IO [String]`. Each result carries a+  `CompletionFinality` indicating whether it is final (e.g. a file — shell+  appends trailing space) or non-final (e.g. a directory — no trailing+  space). The zsh completion script uses this signal instead of the `*/`+  suffix heuristic.+* `withLocalYamlConfig` (via `configuredConfigFile`) now completes only+  `.yaml` and `.yml` files instead of all files.+* Fixed `filePathSetting` and `directoryPathSetting` so that user-provided+  builders (e.g. `completer`, `metavar`) take precedence over the built-in+  defaults instead of being silently overridden.+* Fixed completion for options next to default commands with arguments.+  When a default command's argument parser speculatively consumed a dashed+  option (e.g. `--archive-dir`) as a positional value, sibling parsers in+  the same applicative could no longer see it, suppressing their completers.++## [0.14.1.0] - 2026-02-25++### Added++* `renderSettingsNixOptionsWithGeneratedComment`+* `renderParserNixOptionsWithGeneratedComment`+* `withGeneratedComment`++### Changed++* The `--render-nix-options` CLI output now includes a generated file comment++## [0.14.0.0] - 2025-12-03++### Changed++* Source location package names are now cleaned up to not contain a hash.+  This makes it possible to use them in golden tests.+ ## [0.13.0.0] - 2025-11-18  ### Added
opt-env-conf.cabal view
@@ -1,11 +1,11 @@ cabal-version: 1.12 --- This file has been generated from package.yaml by hpack version 0.36.1.+-- This file has been generated from package.yaml by hpack version 0.38.3. -- -- see: https://github.com/sol/hpack  name:           opt-env-conf-version:        0.13.0.0+version:        0.15.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.hs view
@@ -55,6 +55,8 @@      -- ** Completers     filePath,+    filePathWithExtension,+    filePathWithExtensions,     directoryPath,      -- ** Prefixing parsers
src/OptEnvConf/Completer.hs view
@@ -3,9 +3,14 @@ module OptEnvConf.Completer   ( Completer (..),     mkCompleter,+    CompletionFinality (..),+    CompletionResult (..),+    finalResult,     listCompleter,     listIOCompleter,     filePath,+    filePathWithExtension,+    filePathWithExtensions,     directoryPath,   ) where@@ -15,17 +20,45 @@ import Path import Path.IO -newtype Completer = Completer {unCompleter :: String -> IO [String]}+-- | Whether a shell should consider a completion result to be complete.+--+-- A final result like @file.txt@ means the user is done typing this+-- argument, so the shell should append a trailing space.+--+-- A non-final result like @dir/@ means the user likely wants to keep+-- typing (e.g. to complete a file inside the directory), so the shell+-- should not append a trailing space.+data CompletionFinality+  = -- | The completion is complete; the shell should append a trailing space.+    CompletionFinal+  | -- | The completion may be extended further; no trailing space.+    CompletionNotFinal+  deriving (Show, Eq, Ord) +data CompletionResult = CompletionResult+  { completionResultValue :: !String,+    completionResultFinality :: !CompletionFinality+  }+  deriving (Show, Eq, Ord)++finalResult :: String -> CompletionResult+finalResult s =+  CompletionResult+    { completionResultValue = s,+      completionResultFinality = CompletionFinal+    }++newtype Completer = Completer {unCompleter :: String -> IO [CompletionResult]}+ -- Forward-compatible synonym for the 'Completer' constructor-mkCompleter :: (String -> IO [String]) -> Completer+mkCompleter :: (String -> IO [CompletionResult]) -> Completer mkCompleter = Completer  listCompleter :: [String] -> Completer listCompleter ss = listIOCompleter $ pure ss  listIOCompleter :: IO [String] -> Completer-listIOCompleter act = Completer $ \s -> filterPrefix s <$> act+listIOCompleter act = Completer $ \s -> filterPrefix s . map finalResult <$> act  filePath :: Completer filePath = Completer $ \fp' -> do@@ -34,7 +67,7 @@   -- An empty string is not a valid relative file or dir, but it is the most   -- common option so we special case it here   let (prefix, fp) = stripCurDir fp'-  fmap (filterPrefix fp' . map (prefix <>)) $ do+  fmap (filterPrefix fp' . map (addPrefix prefix)) $ do     let listDirForgiving d = fromMaybe ([], []) <$> forgivingAbsence (listDirRel d)     (dirsFromParentListing, filesFromParentListing) <- case parseSomeDir fp of       Nothing -> case fp of@@ -91,12 +124,27 @@      pure $       concat-        [ filesFromPartialListing,-          filesFromParentListing,-          dirsFromPartialListing,-          dirsFromParentListing+        [ map fileResult filesFromPartialListing,+          map fileResult filesFromParentListing,+          map dirResult dirsFromPartialListing,+          map dirResult dirsFromParentListing         ]+  where+    addPrefix :: String -> CompletionResult -> CompletionResult+    addPrefix pfx cr = cr {completionResultValue = pfx <> completionResultValue cr} +filePathWithExtension :: String -> Completer+filePathWithExtension ext = filePathWithExtensions [ext]++filePathWithExtensions :: [String] -> Completer+filePathWithExtensions exts = Completer $ \s -> do+  results <- unCompleter filePath s+  pure $ filter matchesExtension results+  where+    matchesExtension cr+      | "/" `isSuffixOf` completionResultValue cr = True+      | otherwise = any (`isSuffixOf` completionResultValue cr) exts+ directoryPath :: Completer directoryPath = Completer $ \fp' -> do   here <- getCurrentDir@@ -104,7 +152,7 @@   -- An empty string is not a valid relative file or dir, but it is the most   -- common option so we special case it here   let (prefix, fp) = stripCurDir fp'-  fmap (filterPrefix fp' . map (prefix <>)) $ do+  fmap (filterPrefix fp' . map (addPrefix prefix . dirResult)) $ do     let listDirForgiving d = fromMaybe ([], []) <$> forgivingAbsence (listDirRel d)     dirsFromParentListing <- case parseSomeDir fp of       Nothing -> case fp of@@ -146,7 +194,24 @@         [ dirsFromPartialListing,           dirsFromParentListing         ]+  where+    addPrefix :: String -> CompletionResult -> CompletionResult+    addPrefix pfx cr = cr {completionResultValue = pfx <> completionResultValue cr} +fileResult :: String -> CompletionResult+fileResult s =+  CompletionResult+    { completionResultValue = s,+      completionResultFinality = CompletionFinal+    }++dirResult :: String -> CompletionResult+dirResult s =+  CompletionResult+    { completionResultValue = s,+      completionResultFinality = CompletionNotFinal+    }+ hiddenRel :: Path Rel f -> Bool hiddenRel p = case toFilePath p of   ('.' : _) -> True@@ -159,5 +224,5 @@      in ("./" <> pf, rest)   p -> ("", p) -filterPrefix :: String -> [String] -> [String]-filterPrefix s = filter (s `isPrefixOf`)+filterPrefix :: String -> [CompletionResult] -> [CompletionResult]+filterPrefix s = filter ((s `isPrefixOf`) . completionResultValue)
src/OptEnvConf/Completion.hs view
@@ -88,12 +88,19 @@       "     if [[ $word[1] == \"-\" ]]; then",       "       local desc=(\"$parts[1] ($parts[2])\")",       "       compadd -d desc -- $parts[1]",+      "     elif [[ $parts[3] == 'N' ]]; then",+      "       local desc=($(print -f  \"%-019s -- %s\" $parts[1] $parts[2]))",+      "       compadd -f -l -S '' -d desc -- $parts[1]",       "     else",       "       local desc=($(print -f  \"%-019s -- %s\" $parts[1] $parts[2]))",-      "       compadd -l -d desc -- $parts[1]",+      "       compadd -f -l -d desc -- $parts[1]",       "     fi",       "  else",-      "    compadd -f -- $word",+      "    if [[ $parts[3] == 'N' ]]; then",+      "      compadd -f -S '' -- $parts[1]",+      "    else",+      "      compadd -f -- $parts[1]",+      "    fi",       "  fi",       "done"     ]@@ -116,10 +123,11 @@           "      set tmpline $tmpline --completion-word $arg",           "    end",           "    for opt in (" ++ fromAbsFile progPath ++ " $tmpline)",-          "      if test -d $opt",-          "        echo -E \"$opt/\"",+          "      set -l val (string split \\t -- $opt)[1]",+          "      if test -d $val",+          "        echo -E \"$val/\"",           "      else",-          "        echo -E \"$opt\"",+          "        echo -E \"$val\"",           "      end",           "    end",           "end",@@ -165,12 +173,17 @@       putStr $         unlines $           map-            ( \Completion {..} -> case completionDescription of-                Nothing -> completionSuggestion-                Just d -> completionSuggestion <> "\t" <> d+            ( \Completion {..} ->+                let val = completionResultValue completionSuggestion+                    notFinal = completionResultFinality completionSuggestion == CompletionNotFinal+                 in case (notFinal, completionDescription) of+                      (False, Nothing) -> val+                      (False, Just d) -> val <> "\t" <> d+                      (True, Nothing) -> val <> "\t\tN"+                      (True, Just d) -> val <> "\t" <> d <> "\tN"             )             evaluatedCompletions-    else putStr $ unlines $ map completionSuggestion evaluatedCompletions+    else putStr $ unlines $ map (completionResultValue . completionSuggestion) evaluatedCompletions   pure ()  -- Because the first arg has already been skipped we get input like this here:@@ -201,13 +214,13 @@         completionDescription = Nothing       } -evalCompletions :: String -> [Completion Suggestion] -> IO [Completion String]+evalCompletions :: String -> [Completion Suggestion] -> IO [Completion CompletionResult] evalCompletions arg = fmap concat . mapM (evalCompletion arg) -evalCompletion :: String -> Completion Suggestion -> IO [Completion String]+evalCompletion :: String -> Completion Suggestion -> IO [Completion CompletionResult] evalCompletion arg c = do-  ss <- evalSuggestion arg (completionSuggestion c)-  pure $ map (\s -> c {completionSuggestion = s}) ss+  rs <- evalSuggestion arg (completionSuggestion c)+  pure $ map (\r -> c {completionSuggestion = r}) rs  data Suggestion   = SuggestionBare !String@@ -217,9 +230,9 @@ instance IsString Suggestion where   fromString = SuggestionBare -evalSuggestion :: String -> Suggestion -> IO [String]+evalSuggestion :: String -> Suggestion -> IO [CompletionResult] evalSuggestion arg = \case-  SuggestionBare s -> pure $ filter (arg `isPrefixOf`) [s]+  SuggestionBare s -> pure $ filter ((arg `isPrefixOf`) . completionResultValue) [finalResult s]   SuggestionCompleter (Completer act) -> act arg  pureCompletionQuery :: Parser a -> Int -> [String] -> [Completion Suggestion]@@ -227,8 +240,10 @@   fromMaybe [] $ evalState (go parser) selectedArgs   where     (selectedArgs, mCursorArg) = selectArgs ix args+    -- Complete within a command's parser. The command name itself is+    -- already handled in the ParserCommands branch (argsAtEnd case).     goCommand :: Command a -> State Args (Maybe [Completion Suggestion])-    goCommand = go . commandParser -- TODO complete with the command+    goCommand = go . commandParser     combineOptions = Just . concat . catMaybes      tryOrRestore :: State Args (Maybe a) -> State Args (Maybe a)@@ -241,6 +256,37 @@           pure Nothing         Just a -> pure (Just a) +    -- Completions for many/some: try the parser repeatedly.+    -- Each iteration either advances the args state (consuming input)+    -- or produces end-of-input suggestions. We keep completions from+    -- the iteration that is at the cursor position (the last one that+    -- advanced state, or the first if none advance).+    manyCompletions :: Parser x -> State Args (Maybe [Completion Suggestion])+    manyCompletions p = do+      before <- get+      mR <- go p+      case mR of+        Nothing -> pure Nothing+        Just os -> do+          after <- get+          if after == before+            then -- State did not advance; return these completions.+              pure $ Just os+            else -- State advanced: something was consumed. Try the+            -- next iteration. Its completions supersede ours+            -- only if it also has a valid result.+              do+                mMore <- manyCompletions p+                case mMore of+                  Nothing -> pure $ Just os+                  Just more+                    -- If the next iteration only produced stale+                    -- dashed suggestions (state didn't advance+                    -- further), prefer our completions which came+                    -- from the advancing iteration.+                    | null os -> pure $ Just more+                    | otherwise -> pure $ Just os+     orCompletions :: Parser x -> Parser y -> State Args (Maybe [Completion Suggestion])     orCompletions p1 p2 = do       p1s <- tryOrRestore $ go p1@@ -273,48 +319,73 @@       ParserAlt p1 p2 -> orCompletions p1 p2       ParserSelect p1 p2 -> andCompletions p1 p2       ParserEmpty _ -> pure Nothing-      ParserMany _ p -> do-        mR <- go p-        case mR of-          Nothing -> pure Nothing-          Just os -> fmap (os ++) <$> go p-      ParserSome _ p -> do-        mR <- go p-        case mR of-          Nothing -> pure Nothing-          Just os -> fmap (os ++) <$> go p+      ParserMany _ p -> manyCompletions p+      ParserSome _ p -> manyCompletions p       ParserAllOrNothing _ p -> go p       ParserCheck _ _ _ _ p -> go p       ParserWithConfig _ p1 p2 ->         -- The config-file auto-completion is probably less important, we put it second.         andCompletions p2 p1-      ParserCommands _ _ cs -> do+      ParserCommands _ mDefault cs -> do+        let mDefaultCommand = do+              d <- mDefault+              find ((== d) . commandArg) cs         as <- get         let possibilities = Args.consumeArgument as-        fmap combineOptions $ forM possibilities $ \(mArg, rest) -> do-          case mArg of-            Nothing -> do-              if argsAtEnd rest-                then do-                  let arg = fromMaybe "" mCursorArg-                  let matchingCommands = filter ((arg `isPrefixOf`) . commandArg) cs-                  pure $-                    Just $-                      map-                        ( \Command {..} ->-                            Completion-                              { completionSuggestion = SuggestionBare commandArg,-                                completionDescription = Just commandHelp-                              }-                        )-                        matchingCommands-                else pure Nothing -- TODO: What does this mean?-            Just arg ->-              case find ((== arg) . commandArg) cs of-                Just c -> do-                  put rest-                  goCommand c-                Nothing -> pure Nothing -- Invalid command+        -- First, try matching an explicit command name.+        explicitCommandCompletions <-+          fmap combineOptions $ forM possibilities $ \(mArg, rest) -> do+            case mArg of+              Nothing -> do+                if argsAtEnd rest+                  then do+                    let arg = fromMaybe "" mCursorArg+                    let matchingCommands = filter ((arg `isPrefixOf`) . commandArg) cs+                    pure $+                      Just $+                        map+                          ( \Command {..} ->+                              Completion+                                { completionSuggestion = SuggestionBare commandArg,+                                  completionDescription = Just commandHelp+                                }+                          )+                          matchingCommands+                  else -- consumeArgument offered "don't consume" but there+                  -- are still live args remaining. This possibility is+                  -- invalid for commands: if we didn't consume a command+                  -- name then the remaining args have nowhere to go.+                    pure Nothing+              Just arg ->+                case find ((== arg) . commandArg) cs of+                  Just c -> do+                    put rest+                    goCommand c+                  Nothing -> pure Nothing+        -- If there is a default command, also try completing within+        -- the default command's parser, since that is what would run+        -- if the user provides no command.+        case mDefaultCommand of+          Nothing -> pure explicitCommandCompletions+          Just dc -> do+            defaultCompletions <- tryOrRestore $ goCommand dc+            case defaultCompletions of+              Nothing -> pure explicitCommandCompletions+              Just dcs+                -- If no args were consumed (we were already at end),+                -- combine the explicit command listing with the default+                -- command's completions.+                | argsAtEnd as -> pure $ combineOptions [explicitCommandCompletions, Just dcs]+                | otherwise -> do+                    -- The default command consumed args, so its+                    -- completions are valid.  But we must restore+                    -- the state: the consumed args may also be+                    -- intended for sibling parsers in an+                    -- applicative (<*>), e.g. an option like+                    -- --archive-dir that the default command+                    -- swallowed as a positional argument.+                    put as+                    pure $ Just dcs       ParserSetting _ Setting {..} -> do         let arg = fromMaybe "" mCursorArg         let completionDescription = settingHelp@@ -346,16 +417,26 @@             as <- get             if settingTryArgument               then do-                case Args.consumeArgument as of-                  [] -> completeWithCompleterAtEnd-                  -- TODO in theory we really need to try all possible consumptions of an argument.-                  -- This would complicate this function quite a bit, so we-                  -- just try the first option and leave it there for now.-                  (mConsumed, as') : _ -> do+                let possibilities = Args.consumeArgument as+                -- Try all possible consumptions of the argument.+                -- If any possibility actually consumes a value, prefer+                -- that over the "don't consume" fallback, because a+                -- consumed value means the user already provided input.+                case filter (isJust . fst) possibilities of+                  (_, as') : _ -> do                     put as'-                    case mConsumed of-                      Nothing -> completeWithCompleterAtEnd-                      Just _ -> pure $ Just []+                    pure $ Just []+                  [] ->+                    -- No possibility consumed a value.  This is either+                    -- because there are no args at all (the [] case from+                    -- consumeArgument) or because only the consume-nothing+                    -- fallback matched.  In both cases, offer the+                    -- completer if we are at the end.+                    case possibilities of+                      [] -> completeWithCompleterAtEnd+                      (_, as') : _ -> do+                        put as'+                        completeWithCompleterAtEnd               else                 if isJust settingSwitchValue                   then do@@ -397,7 +478,10 @@                         -- We can't auto-complete settings parsed from env vars                         -- or config values, but this path is still valid.                         ---                        -- TODO consider checking if env vars or config vals-                        -- are parsed, then this path may still be invalid-                        -- afteral.+                        -- If we checked whether the env var is set or the+                        -- config val is present, we could return Nothing when+                        -- they are absent. That would let alternatives reject+                        -- this branch, improving completions when one branch+                        -- is env/conf-only and the other has args/options.+                        -- This would require IO or an environment parameter.                         pure $ Just []
src/OptEnvConf/Doc.hs view
@@ -229,29 +229,29 @@           [ ["argument:"],             [mMetavarChunk setDocMetavar]           ]-        | setDocTryArgument+      | setDocTryArgument       ],       [ unwordsChunks           [ ["switch:"],             dashedChunksNE dasheds           ]-        | setDocTrySwitch,-          dasheds <- maybeToList (NE.nonEmpty setDocDasheds)+      | setDocTrySwitch,+        dasheds <- maybeToList (NE.nonEmpty setDocDasheds)       ],       [ unwordsChunks           [ ["option:"],             dashedChunksNE dasheds               ++ [" ", mMetavarChunk setDocMetavar]           ]-        | setDocTryOption,-          dasheds <- maybeToList (NE.nonEmpty setDocDasheds)+      | setDocTryOption,+        dasheds <- maybeToList (NE.nonEmpty setDocDasheds)       ],       [ unwordsChunks           [ ["env:"],             envVarChunksNE vars               ++ [" ", mMetavarChunk setDocMetavar]           ]-        | vars <- maybeToList setDocEnvVars+      | vars <- maybeToList setDocEnvVars       ],       concat         [ concatMap@@ -270,13 +270,13 @@                         )             )             (NE.toList confs)-          | confs <- maybeToList setDocConfKeys+        | confs <- maybeToList setDocConfKeys         ],       [ defaultValueChunks dv-        | dv <- maybeToList setDocDefault+      | dv <- maybeToList setDocDefault       ],       [ exampleValuesChunks setDocExamples-        | not (null setDocExamples)+      | not (null setDocExamples)       ]     ] @@ -323,25 +323,25 @@               [ [ [".Sh ", "COMMANDS"],                   renderCommandDocs docs                 ]-                | not (null commandDocs)+              | not (null commandDocs)               ],             concat               [ [ [".Sh ", "OPTIONS"],                   renderLongOptDocs optDocs                 ]-                | not (nullDocs optDocs)+              | not (nullDocs optDocs)               ],             concat               [ [ [".Sh ", "ENVIRONMENT VARIABLES"],                   renderEnvDocs envDocs                 ]-                | not (nullDocs envDocs)+              | not (nullDocs envDocs)               ],             concat               [ [ [".Sh ", "CONFIGURATION VALUES"],                   renderConfDocs confDocs                 ]-                | not (nullDocs confDocs)+              | not (nullDocs confDocs)               ]           ] @@ -364,25 +364,25 @@               [ [ headerChunks "All commands",                   renderCommandDocs docs                 ]-                | not (null commandDocs)+              | not (null commandDocs)               ],             concat               [ [ headerChunks "Options",                   renderLongOptDocs optDocs                 ]-                | not (nullDocs optDocs)+              | not (nullDocs optDocs)               ],             concat               [ [ headerChunks "Environment Variables",                   renderEnvDocs envDocs                 ]-                | not (nullDocs envDocs)+              | not (nullDocs envDocs)               ],             concat               [ [ headerChunks "Configuration Values",                   renderConfDocs confDocs                 ]-                | not (nullDocs confDocs)+              | not (nullDocs confDocs)               ]           ] @@ -441,7 +441,7 @@           [ [ headerChunks "Available commands",               renderCommandDocsShort docs             ]-            | not (null (docsToCommandDocs docs))+          | not (null (docsToCommandDocs docs))           ]       ] @@ -576,7 +576,7 @@                             indent $ concatMap renderSetDocWithoutHeader $ d : sds,                             [[]]                           ]-                        | not isTopLevel+                      | not isTopLevel                       ],                     goOr isTopLevel rest                   ]@@ -669,16 +669,16 @@               $ unwordsChunks               $ concat                 [ [ [mMetavarChunk optDocMetavar]-                    | optDocTryArgument+                  | optDocTryArgument                   ],                   [ concat $ maybeToList $ dashedChunks optDocDasheds-                    | optDocTrySwitch+                  | optDocTrySwitch                   ],                   [ concat                       [ concat $ maybeToList $ dashedChunks optDocDasheds,                         [" ", mMetavarChunk optDocMetavar]                       ]-                    | optDocTryOption+                  | optDocTryOption                   ]                 ] @@ -724,7 +724,7 @@             [ maybeToList $ dashedChunks optDocDasheds,               [ [ mMetavarChunk optDocMetavar                 ]-                | optDocTryArgument+              | optDocTryArgument               ]             ],         [mHelpChunk optDocHelp]
src/OptEnvConf/Lint.hs view
@@ -21,7 +21,7 @@ import qualified Data.List.NonEmpty as NE import Data.Maybe import qualified Data.Text as T-import GHC.Stack (SrcLoc, prettySrcLoc)+import GHC.Stack (SrcLoc) import OptEnvConf.Args import OptEnvConf.Output import OptEnvConf.Parser@@ -228,7 +228,7 @@             ],             ["This is not allowed because the parser would run infinitely."]           ],-      maybe [] (pure . ("Defined at: " :) . pure . fore cyan . chunk . T.pack . prettySrcLoc) lintErrorSrcLoc+      maybe [] (pure . ("Defined at: " :) . pure . srcLocChunk) lintErrorSrcLoc     ]  lintParser :: Parser a -> Maybe (NonEmpty LintError)
src/OptEnvConf/Main.hs view
@@ -142,7 +142,8 @@                     hPutChunksLocaleWith tc stdout $ renderReferenceDocumentation progname docs                     exitSuccess                   RenderNixosOptions -> do-                    putStrLn $ T.unpack $ renderParserNixOptions p'+                    progname <- getProgName+                    putStrLn $ T.unpack $ renderParserNixOptionsWithGeneratedComment progname p'                     exitSuccess                   BashCompletionScript progPath -> do                     progname <- getProgName
src/OptEnvConf/Nix.hs view
@@ -1,8 +1,10 @@ {-# LANGUAGE AllowAmbiguousTypes #-} {-# LANGUAGE GADTs #-} {-# LANGUAGE LambdaCase #-}+{-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE RecordWildCards #-} {-# LANGUAGE ScopedTypeVariables #-}+{-# LANGUAGE TypeApplications #-}  module OptEnvConf.Nix where @@ -16,6 +18,26 @@ import qualified Data.Text as T import OptEnvConf.Parser import OptEnvConf.Setting++renderSettingsNixOptionsWithGeneratedComment :: forall a. (HasParser a) => String -> Text+renderSettingsNixOptionsWithGeneratedComment progname =+  withGeneratedComment progname (renderSettingsNixOptions @a)++renderParserNixOptionsWithGeneratedComment :: String -> Parser a -> Text+renderParserNixOptionsWithGeneratedComment progname p =+  withGeneratedComment progname (renderParserNixOptions p)++withGeneratedComment :: String -> Text -> Text+withGeneratedComment progname content =+  T.pack $+    unlines $+      map+        ("# " <>)+        [ "DO NOT EDIT THIS FILE DIRECTLY",+          "This file was generated by running",+          unwords [progname, "--render-nix-options"],+          T.unpack content+        ]  renderSettingsNixOptions :: forall a. (HasParser a) => Text renderSettingsNixOptions = renderParserNixOptions (settingsParser :: Parser a)
src/OptEnvConf/Output.hs view
@@ -2,6 +2,7 @@  module OptEnvConf.Output where +import Data.Char as Char import Data.List (intercalate, intersperse) import Data.List.NonEmpty (NonEmpty (..)) import qualified Data.List.NonEmpty as NE@@ -11,7 +12,7 @@ import Data.Text (Text) import qualified Data.Text as T import Data.Version-import GHC.Stack (SrcLoc, prettySrcLoc)+import GHC.Stack (SrcLoc (..), prettySrcLoc) import OptEnvConf.Args (Dashed (..)) import qualified OptEnvConf.Args as Args import OptEnvConf.Parser@@ -94,7 +95,22 @@ mSrcLocChunk = maybe "without srcLoc" srcLocChunk  srcLocChunk :: SrcLoc -> Chunk-srcLocChunk = fore cyan . chunk . T.pack . prettySrcLoc+srcLocChunk = fore cyan . chunk . T.pack . prettySrcLoc . cleanSrcLoc+  where+    -- GHC puts the package hash in there, which may change accross compilation+    -- and that messes with golden tests.+    -- We try to remove the hash from the package here.+    -- The srcLocPackage looks like this:+    -- opt-env-conf-test-0.0.0.3-6OHAhVu967XFT6LS52oRkR-opt-env-conf-test+    -- which looks like:+    -- <name>-<version>-<hash>-<name>+    -- So we take every '-'-separated part until one starts with a number.+    cleanSrcLoc loc = loc {srcLocPackage = cleanPackage (srcLocPackage loc)}+    cleanPackage pkg =+      T.unpack $+        T.intercalate "-" $+          takeWhile (not . maybe False (Char.isDigit . fst) . T.uncons) $+            T.splitOn "-" (T.pack pkg)  indent :: [[Chunk]] -> [[Chunk]] indent = map ("  " :)
src/OptEnvConf/Parser.hs view
@@ -294,7 +294,7 @@   some = fmap NE.toList . ParserSome Nothing  showParserABit :: Parser a -> String-showParserABit = ($ "") . showParserPrec 0+showParserABit = flip (showParserPrec 0) ""  showParserPrec :: Int -> Parser a -> ShowS showParserPrec = go@@ -479,11 +479,11 @@   mapIO resolveFile' $     withFrozenCallStack $       setting $-        [ reader str,-          metavar "FILE_PATH",-          completer filePath-        ]-          ++ builders+        builders+          ++ [ reader str,+               metavar "FILE_PATH",+               completer filePath+             ]  -- | A setting for @Path Abs dir@. --@@ -496,11 +496,11 @@   mapIO resolveDir' $     withFrozenCallStack $       setting $-        [ reader str,-          metavar "DIRECTORY_PATH",-          completer directoryPath-        ]-          ++ builders+        builders+          ++ [ reader str,+               metavar "DIRECTORY_PATH",+               completer directoryPath+             ]  -- | A 'setting' with 'option', a 'reader' set to 'str', and the 'metavar' set to @STR@. --@@ -752,7 +752,7 @@   withFrozenCallStack $     withConfig $       mapIO readFirstYamlConfigFile $-        (<>) <$> (maybeToList <$> optional configuredConfigFile) <*> parsers+        ((<>) . maybeToList <$> optional configuredConfigFile) <*> parsers  -- | Combine all Yaml config files that exist into a single combined config object. withCombinedYamlConfigs :: Parser [Path Abs File] -> Parser a -> Parser a@@ -763,7 +763,7 @@   withFrozenCallStack $     withConfig $       mapIO (foldM resolveYamlConfigFile Nothing) $-        (<>) <$> (maybeToList <$> optional configuredConfigFile) <*> parsers+        ((<>) . maybeToList <$> optional configuredConfigFile) <*> parsers   where     resolveYamlConfigFile :: Maybe JSON.Object -> Path Abs File -> IO (Maybe JSON.Object)     resolveYamlConfigFile acc = fmap (combineMaybeObjects acc . join) . readYamlConfigFile@@ -825,7 +825,8 @@     [ option,       long "config-file",       env "CONFIG_FILE",-      help "Path to the configuration file"+      help "Path to the configuration file",+      completer (filePathWithExtensions [".yaml", ".yml"])     ]  -- | Define a setting for a 'Bool' with a given default value.
src/OptEnvConf/Setting.hs view
@@ -152,7 +152,8 @@ suffixEnvVarSetting :: String -> EnvVarSetting -> EnvVarSetting suffixEnvVarSetting suffix e = e {envVarSettingVar = envVarSettingVar e <> suffix} -data ConfigValSetting a = forall void.+data ConfigValSetting a+  = forall void.   ConfigValSetting   { configValSettingPath :: !(NonEmpty String),     configValSettingAllowPrefix :: !Bool,