fix: conf: fix passing of general options to ui, web
This commit is contained in:
parent
65c30bceb6
commit
2a6a5ea042
@ -157,6 +157,8 @@ mainmode addons = defMode {
|
|||||||
-- ]
|
-- ]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
verboseDebugLevel = 8
|
||||||
|
|
||||||
------------------------------------------------------------------------------
|
------------------------------------------------------------------------------
|
||||||
-- | hledger CLI's main procedure.
|
-- | hledger CLI's main procedure.
|
||||||
--
|
--
|
||||||
@ -166,10 +168,12 @@ mainmode addons = defMode {
|
|||||||
-- then run it in the right way, usually reading input data (eg a journal) first.
|
-- then run it in the right way, usually reading input data (eg a journal) first.
|
||||||
--
|
--
|
||||||
-- When making a CLI usable and robust with main command, builtin subcommands,
|
-- When making a CLI usable and robust with main command, builtin subcommands,
|
||||||
-- and various kinds of addon commands, while balancing UX, environment, idioms,
|
-- and various kinds of addon commands, while balancing circular dependencies,
|
||||||
-- legacy, and language and libraries and workarounds with their own requirements
|
-- environment, idioms, legacy, and libraries with their own requirements and limitations:
|
||||||
-- and limitations, things get complicated and bugs can easily creep in.
|
-- things get crazy, and there is a tradeoff against complexity and bug risk.
|
||||||
-- So try to keep the processing below reasonably manageable, testable and clear.
|
-- We try to provide the most intuitive, expressive and robust CLI that's feasible
|
||||||
|
-- while keeping the CLI processing below sufficiently comprehensible, troubleshootable,
|
||||||
|
-- and tested. It's an ongoing quest.
|
||||||
-- See also: Hledger.Cli.CliOptions, cli.test, and --debug=8.
|
-- See also: Hledger.Cli.CliOptions, cli.test, and --debug=8.
|
||||||
--
|
--
|
||||||
main :: IO ()
|
main :: IO ()
|
||||||
@ -178,7 +182,7 @@ main = withGhcDebug' $ do
|
|||||||
-- let's go!
|
-- let's go!
|
||||||
let
|
let
|
||||||
dbgIO, dbgIO1 :: Show a => String -> a -> IO () -- this signature is needed
|
dbgIO, dbgIO1 :: Show a => String -> a -> IO () -- this signature is needed
|
||||||
dbgIO = ptraceAtIO 8
|
dbgIO = ptraceAtIO verboseDebugLevel
|
||||||
dbgIO1 = ptraceAtIO 1
|
dbgIO1 = ptraceAtIO 1
|
||||||
dbgIO "running" prognameandversion
|
dbgIO "running" prognameandversion
|
||||||
|
|
||||||
@ -201,54 +205,53 @@ main = withGhcDebug' $ do
|
|||||||
>>= expandArgsAt -- interpolate @ARGFILEs
|
>>= expandArgsAt -- interpolate @ARGFILEs
|
||||||
<&> replaceNumericFlags -- convert -NUM to --depth=NUM
|
<&> replaceNumericFlags -- convert -NUM to --depth=NUM
|
||||||
let
|
let
|
||||||
cliargswithcmdfirst = cliargs & moveFlagsAfterCommand
|
(clicmdarg, cliargswithoutcmd, cliargswithcmdfirst) = moveFlagsAfterCommand cliargs
|
||||||
isNonEmptyNonFlag s = not $ null s || "-" `isPrefixOf` s
|
cliargswithcmdfirstwithoutclispecific = dropCliSpecificOpts cliargswithcmdfirst
|
||||||
(clicmdarg, cliargswithoutcmd) =
|
|
||||||
case span isNonEmptyNonFlag cliargswithcmdfirst of
|
|
||||||
(a:as,bs) -> (a,as++bs)
|
|
||||||
([],bs) -> ("",bs)
|
|
||||||
nocmdprovided = null clicmdarg
|
|
||||||
(cliargsbeforecmd, cliargsaftercmd) = second (drop 1) $ break (==clicmdarg) cliargs
|
(cliargsbeforecmd, cliargsaftercmd) = second (drop 1) $ break (==clicmdarg) cliargs
|
||||||
dbgIO "cli args" cliargs
|
dbgIO "cli args" cliargs
|
||||||
dbgIO "cli args with command first, if any" cliargswithcmdfirst
|
dbgIO "cli args with command first, if any" cliargswithcmdfirst
|
||||||
dbgIO "command argument found" clicmdarg
|
dbgIO "command argument found" clicmdarg
|
||||||
dbgIO "cli args without command" cliargswithoutcmd
|
|
||||||
dbgIO "cli args before command" cliargsbeforecmd
|
dbgIO "cli args before command" cliargsbeforecmd
|
||||||
dbgIO "cli args after command" cliargsaftercmd
|
dbgIO "cli args after command" cliargsaftercmd
|
||||||
|
|
||||||
-- Now try to identify the full subcommand name, so we can look for
|
-- Now try to identify the full subcommand name, so we can look for
|
||||||
-- command-specific options in config files (clicmdarg may be only an abbreviation).
|
-- command-specific options in config files (clicmdarg may be only an abbreviation).
|
||||||
-- For this we do a preliminary cmdargs parse of the command line arguments.
|
-- For this we do a preliminary cmdargs parse of the command line arguments, with cli-specific options removed.
|
||||||
-- If no command was provided, or if the command line contains a bad flag
|
-- If no command was provided, or if the command line contains a bad flag
|
||||||
-- or a wrongly present/missing flag argument, cmd will be "".
|
-- or a wrongly present/missing flag argument, cmd will be "".
|
||||||
-- (Also find any --conf-file/--no-conf options.)
|
|
||||||
let
|
let
|
||||||
-- cliargswithcmdfirst' = filter (/= "--debug") cliargswithcmdfirst
|
rawopts0 = cmdargsParse cliargswithcmdfirstwithoutclispecific addons
|
||||||
-- XXX files --debug fails here, eg.
|
|
||||||
-- How to parse the command name with cmdargs without passing unsupported flags that it will reject ?
|
|
||||||
-- Is --debug the only flag like this ?
|
|
||||||
rawopts0 = cmdargsParse cliargswithcmdfirst addons
|
|
||||||
cmd = stringopt "command" rawopts0
|
cmd = stringopt "command" rawopts0
|
||||||
-- XXX may need a better error message when cmdargs fails to parse (eg spaced/quoted/malformed flag values)
|
-- XXX may need a better error message when cmdargs fails to parse (eg spaced/quoted/malformed flag values)
|
||||||
|
nocmdprovided = null clicmdarg
|
||||||
badcmdprovided = null cmd && not nocmdprovided
|
badcmdprovided = null cmd && not nocmdprovided
|
||||||
isaddoncmd = not (null cmd) && cmd `elem` addons
|
isaddoncmd = not (null cmd) && cmd `elem` addons
|
||||||
-- isbuiltincmd = cmd `elem` builtinCommandNames
|
-- isbuiltincmd = cmd `elem` builtinCommandNames
|
||||||
mcmdmodeaction = findBuiltinCommand cmd
|
mcmdmodeaction = findBuiltinCommand cmd
|
||||||
effectivemode = maybe (mainmode []) fst mcmdmodeaction
|
effectivemode = maybe (mainmode []) fst mcmdmodeaction
|
||||||
|
dbgIO "cli args with command first and no cli-specific opts" cliargswithcmdfirstwithoutclispecific
|
||||||
|
dbgIO1 "command found" cmd
|
||||||
dbgIO "no command provided" nocmdprovided
|
dbgIO "no command provided" nocmdprovided
|
||||||
dbgIO "bad command provided" badcmdprovided
|
dbgIO "bad command provided" badcmdprovided
|
||||||
dbgIO1 "command found" cmd
|
|
||||||
dbgIO "is addon command" isaddoncmd
|
dbgIO "is addon command" isaddoncmd
|
||||||
|
|
||||||
---------------------------------------------------------------
|
---------------------------------------------------------------
|
||||||
-- Read extra options from a config file.
|
-- Read extra options from a config file.
|
||||||
|
|
||||||
|
-- Identify any --conf-file/--no-conf options.
|
||||||
|
-- For this we parse with cmdargs again, this time with cli-specific options but without a command name.
|
||||||
|
dbgIO "cli args without command" cliargswithoutcmd
|
||||||
|
let rawopts1 = cmdargsParse cliargswithoutcmd addons
|
||||||
|
|
||||||
-- Read any extra general and command-specific args/opts from a config file.
|
-- Read any extra general and command-specific args/opts from a config file.
|
||||||
-- Ignore any general opts not known to be supported by the command.
|
-- Ignore any general opts or cli-specific opts not known to be supported by the command.
|
||||||
(conf, mconffile) <- getConf rawopts0
|
(conf, mconffile) <- getConf rawopts1
|
||||||
let
|
let
|
||||||
genargsfromconf = confLookup "general" conf
|
genargsfromconf = confLookup "general" conf
|
||||||
|
addoncmdssupportinggenopts = ["ui", "web"] -- addons known to support hledger general options
|
||||||
supportedgenargsfromconf
|
supportedgenargsfromconf
|
||||||
|
| cmd `elem` addoncmdssupportinggenopts =
|
||||||
|
[a | a <- genargsfromconf, not $ any (`isPrefixOf` a) addoncmdssupportinggenopts]
|
||||||
| isaddoncmd = []
|
| isaddoncmd = []
|
||||||
| otherwise = dropUnsupportedOpts effectivemode genargsfromconf
|
| otherwise = dropUnsupportedOpts effectivemode genargsfromconf
|
||||||
excludedgenargsfromconf = genargsfromconf \\ supportedgenargsfromconf
|
excludedgenargsfromconf = genargsfromconf \\ supportedgenargsfromconf
|
||||||
@ -270,7 +273,6 @@ main = withGhcDebug' $ do
|
|||||||
(if null clicmdarg then [] else [clicmdarg]) <> supportedgenargsfromconf <> cmdargsfromconf <> cliargswithoutcmd
|
(if null clicmdarg then [] else [clicmdarg]) <> supportedgenargsfromconf <> cmdargsfromconf <> cliargswithoutcmd
|
||||||
& replaceNumericFlags -- convert any -NUM opts from the config file
|
& replaceNumericFlags -- convert any -NUM opts from the config file
|
||||||
-- finalargs' <- expandArgsAt finalargs -- expand @ARGFILEs in the config file ? don't bother
|
-- finalargs' <- expandArgsAt finalargs -- expand @ARGFILEs in the config file ? don't bother
|
||||||
dbgIO "final args to be parsed by cmdargs" finalargs
|
|
||||||
let rawopts = cmdargsParse finalargs addons
|
let rawopts = cmdargsParse finalargs addons
|
||||||
|
|
||||||
---------------------------------------------------------------
|
---------------------------------------------------------------
|
||||||
@ -349,11 +351,12 @@ main = withGhcDebug' $ do
|
|||||||
-- are not passed since we can't be sure they're supported.
|
-- are not passed since we can't be sure they're supported.
|
||||||
| isaddoncmd -> do
|
| isaddoncmd -> do
|
||||||
let
|
let
|
||||||
addonargs = filter (/="--") $ cmdargsfromconf <> cliargsaftercmd
|
addonargs0 = filter (/="--") $ supportedgenargsfromconf <> cmdargsfromconf <> cliargswithoutcmd
|
||||||
|
addonargs = dropCliSpecificOpts addonargs0
|
||||||
shellcmd = printf "%s-%s %s" progname cmd (unwords' addonargs) :: String
|
shellcmd = printf "%s-%s %s" progname cmd (unwords' addonargs) :: String
|
||||||
dbgIO "addon command selected" cmd
|
dbgIO "addon command selected" cmd
|
||||||
dbgIO "addon command arguments" (map quoteIfNeeded addonargs)
|
dbgIO "addon command arguments after removing cli-specific opts" (map quoteIfNeeded addonargs)
|
||||||
dbgIO1 "running" shellcmd
|
dbgIO1 "running addon" shellcmd
|
||||||
system shellcmd >>= exitWith
|
system shellcmd >>= exitWith
|
||||||
|
|
||||||
-- deprecated command found
|
-- deprecated command found
|
||||||
@ -379,8 +382,10 @@ main = withGhcDebug' $ do
|
|||||||
-- or search for addons; to do those things, mimic the code in main for now.
|
-- or search for addons; to do those things, mimic the code in main for now.
|
||||||
argsToCliOpts :: [String] -> [String] -> IO CliOpts
|
argsToCliOpts :: [String] -> [String] -> IO CliOpts
|
||||||
argsToCliOpts args addons = do
|
argsToCliOpts args addons = do
|
||||||
let args' = args & moveFlagsAfterCommand & replaceNumericFlags
|
let
|
||||||
let rawopts = cmdargsParse args' addons
|
(_, _, args0) = moveFlagsAfterCommand args
|
||||||
|
args1 = replaceNumericFlags args0
|
||||||
|
rawopts = cmdargsParse args1 addons
|
||||||
rawOptsToCliOpts rawopts
|
rawOptsToCliOpts rawopts
|
||||||
|
|
||||||
-- | Parse these command line arguments/options with cmdargs using mainmode.
|
-- | Parse these command line arguments/options with cmdargs using mainmode.
|
||||||
@ -390,18 +395,20 @@ argsToCliOpts args addons = do
|
|||||||
cmdargsParse :: [String] -> [String] -> RawOpts
|
cmdargsParse :: [String] -> [String] -> RawOpts
|
||||||
cmdargsParse args0 addons =
|
cmdargsParse args0 addons =
|
||||||
CmdArgs.process (mainmode addons) args & either
|
CmdArgs.process (mainmode addons) args & either
|
||||||
(\err -> error' $ unlines [
|
(\err -> error' $ "cmdargs: " <> err)
|
||||||
"cmdargs: " <> err
|
|
||||||
,"while processing arguments:"
|
|
||||||
,show args
|
|
||||||
])
|
|
||||||
id
|
id
|
||||||
where args = ensureDebugFlagHasVal args0
|
where
|
||||||
|
args = ensureDebugFlagHasVal args0
|
||||||
|
& traceOrLogAtWith verboseDebugLevel (\as ->
|
||||||
|
"cmdargs: parsing with mainmode+subcommand modes+generic addon modes: " <> show as)
|
||||||
|
|
||||||
-- | cmdargs does not allow flags (options) to appear before the subcommand name.
|
-- | cmdargs does not allow flags (options) to appear before the subcommand argument.
|
||||||
-- We prefer to hide this restriction from the user, making the CLI more forgiving.
|
-- We prefer to hide this restriction from the user, making the CLI more forgiving.
|
||||||
-- So this tries to move flags, and their values if any, after the command name.
|
-- So this tries to move flags, and their values if any, after the command argument.
|
||||||
-- This is tricky because of the flexibility of traditional flag syntax.
|
-- It also returns the (possibly empty) command argument and the other arguments,
|
||||||
|
-- separately for convenience.
|
||||||
|
--
|
||||||
|
-- Detecting the command argument is tricky because of the flexibility of traditional flag syntax.
|
||||||
-- Short flags can be joined together, some flags can have a value or no value,
|
-- Short flags can be joined together, some flags can have a value or no value,
|
||||||
-- flags and values can be separated by =, a space, or nothing, etc.
|
-- flags and values can be separated by =, a space, or nothing, etc.
|
||||||
--
|
--
|
||||||
@ -431,9 +438,13 @@ cmdargsParse args0 addons =
|
|||||||
-- and will be moved - but later rejected by cmdargs.
|
-- and will be moved - but later rejected by cmdargs.
|
||||||
-- Instead these should be written to the right of a "--" argument, which hides them.
|
-- Instead these should be written to the right of a "--" argument, which hides them.
|
||||||
--
|
--
|
||||||
moveFlagsAfterCommand :: [String] -> [String]
|
moveFlagsAfterCommand :: [String] -> (String, [String], [String])
|
||||||
moveFlagsAfterCommand args = insertFlagsAfterCommand $ moveFlagArgs (args, [])
|
moveFlagsAfterCommand args =
|
||||||
|
case moveFlagArgs (args, []) of
|
||||||
|
([],as) -> ("", as, as)
|
||||||
|
(cmdarg:unmoved, moved) -> (cmdarg, as, cmdarg:as) where as = unmoved<>moved
|
||||||
where
|
where
|
||||||
|
moveFlagArgs :: ([String], [String]) -> ([String], [String])
|
||||||
moveFlagArgs ((a:b:cs), moved)
|
moveFlagArgs ((a:b:cs), moved)
|
||||||
| isMovableFlagArg a == 2 = moveFlagArgs (cs, moved++[a,b])
|
| isMovableFlagArg a == 2 = moveFlagArgs (cs, moved++[a,b])
|
||||||
| isMovableFlagArg a == 1 = moveFlagArgs (b:cs, moved++[a])
|
| isMovableFlagArg a == 1 = moveFlagArgs (b:cs, moved++[a])
|
||||||
@ -460,9 +471,6 @@ moveFlagsAfterCommand args = insertFlagsAfterCommand $ moveFlagArgs (args, [])
|
|||||||
| otherwise = 0 -- not a flag
|
| otherwise = 0 -- not a flag
|
||||||
moveFlagArgs (as, moved) = (as, moved)
|
moveFlagArgs (as, moved) = (as, moved)
|
||||||
|
|
||||||
insertFlagsAfterCommand ([], flags) = flags
|
|
||||||
insertFlagsAfterCommand (command1:args2, flags) = [command1] ++ flags ++ args2
|
|
||||||
|
|
||||||
-- All Flags provided by hledger and its builtin comands.
|
-- All Flags provided by hledger and its builtin comands.
|
||||||
allbuiltinflags = modeAndSubmodeFlags $ mainmode []
|
allbuiltinflags = modeAndSubmodeFlags $ mainmode []
|
||||||
|
|
||||||
@ -501,6 +509,12 @@ longoptvalflagargs_ = map (++"=") $ filter isLongFlagArg optvalflagargs ++ ["--d
|
|||||||
-- Is this flag arg one that requires a value ?
|
-- Is this flag arg one that requires a value ?
|
||||||
isReqValFlagArg a = a `elem` reqvalflagargs
|
isReqValFlagArg a = a `elem` reqvalflagargs
|
||||||
|
|
||||||
|
-- Drop any arguments which look like cli-specific options (--no-conf, --conf CONFFILE, etc.)
|
||||||
|
dropCliSpecificOpts :: [String] -> [String]
|
||||||
|
dropCliSpecificOpts = dropUnsupportedOpts mainmodegeneral
|
||||||
|
where
|
||||||
|
mainmodegeneral = (mainmode []){modeGroupFlags=(modeGroupFlags (mainmode [])){groupUnnamed=[]}}
|
||||||
|
|
||||||
-- | Given a hledger cmdargs mode and a list of command line arguments, try to drop any of the
|
-- | Given a hledger cmdargs mode and a list of command line arguments, try to drop any of the
|
||||||
-- arguments which seem to be flags not supported by this mode. Also drop their values if any.
|
-- arguments which seem to be flags not supported by this mode. Also drop their values if any.
|
||||||
dropUnsupportedOpts :: Mode RawOpts -> [String] -> [String]
|
dropUnsupportedOpts :: Mode RawOpts -> [String] -> [String]
|
||||||
@ -510,17 +524,11 @@ dropUnsupportedOpts m = \case
|
|||||||
| isLongFlagArg a,
|
| isLongFlagArg a,
|
||||||
let f = takeWhile (/='=') a,
|
let f = takeWhile (/='=') a,
|
||||||
let as' = if isReqValFlagArg f && '=' `notElem` a then drop 1 as else as
|
let as' = if isReqValFlagArg f && '=' `notElem` a then drop 1 as else as
|
||||||
->
|
-> if m `supportsFlag` f then a : go as else go as'
|
||||||
if m `supportsFlag` f
|
|
||||||
then a : go as
|
|
||||||
else go as'
|
|
||||||
| isShortFlagArg a,
|
| isShortFlagArg a,
|
||||||
let f = take 2 a,
|
let f = take 2 a,
|
||||||
let as' = if isReqValFlagArg f && length a == 2 then drop 1 as else as
|
let as' = if isReqValFlagArg f && length a == 2 then drop 1 as else as
|
||||||
->
|
-> if m `supportsFlag` f then a : go as else go as'
|
||||||
if m `supportsFlag` f
|
|
||||||
then a : go as
|
|
||||||
else go as'
|
|
||||||
| otherwise -> a : dropUnsupportedOpts m as
|
| otherwise -> a : dropUnsupportedOpts m as
|
||||||
where
|
where
|
||||||
go = dropUnsupportedOpts m
|
go = dropUnsupportedOpts m
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user