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 +46/−0
- opt-env-conf.cabal +2/−2
- src/OptEnvConf.hs +2/−0
- src/OptEnvConf/Completer.hs +76/−11
- src/OptEnvConf/Completion.hs +147/−63
- src/OptEnvConf/Doc.hs +23/−23
- src/OptEnvConf/Lint.hs +2/−2
- src/OptEnvConf/Main.hs +2/−1
- src/OptEnvConf/Nix.hs +22/−0
- src/OptEnvConf/Output.hs +18/−2
- src/OptEnvConf/Parser.hs +15/−14
- src/OptEnvConf/Setting.hs +2/−1
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,