dev: cli: command line processing clarifications

This commit is contained in:
Simon Michael 2024-10-12 08:00:32 -10:00
parent 490ea3ccf1
commit a928ed994b

View File

@ -190,7 +190,7 @@ confflagsmode = defMode{
main :: IO () main :: IO ()
main = withGhcDebug' $ do main = withGhcDebug' $ do
-- let's go! -- 0. let's go!
let let
-- Trace helpers. These always trace to stderr, even when running `hledger ui`; -- Trace helpers. These always trace to stderr, even when running `hledger ui`;
-- that's ok as conf is a hledger cli feature for now. -- that's ok as conf is a hledger cli feature for now.
@ -213,8 +213,9 @@ main = withGhcDebug' $ do
addons <- hledgerAddons <&> filter (not . (`elem` builtinCommandNames) . dropExtension) addons <- hledgerAddons <&> filter (not . (`elem` builtinCommandNames) . dropExtension)
--------------------------------------------------------------- ---------------------------------------------------------------
-- Preliminary command line parsing. -- 1. Preliminary command line parsing.
dbgIO "\n1. Preliminary command line parsing" ()
-- Do some argument preprocessing to help cmdargs -- Do some argument preprocessing to help cmdargs
cliargs <- getArgs cliargs <- getArgs
>>= expandArgsAt -- interpolate @ARGFILEs >>= expandArgsAt -- interpolate @ARGFILEs
@ -235,11 +236,11 @@ main = withGhcDebug' $ do
-- 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 "".
let let
rawopts0 = cmdargsParse rawopts1 = cmdargsParse
"to get command name" "for command name"
(mainmode addons) (mainmode addons)
cliargswithcmdfirstwithoutclispecific cliargswithcmdfirstwithoutclispecific
cmd = stringopt "command" rawopts0 cmd = stringopt "command" rawopts1
-- XXX better error message when cmdargs fails (eg spaced/quoted/malformed flag values) ? -- XXX better error message when cmdargs fails (eg spaced/quoted/malformed flag values) ?
nocmdprovided = null clicmdarg nocmdprovided = null clicmdarg
badcmdprovided = null cmd && not nocmdprovided badcmdprovided = null cmd && not nocmdprovided
@ -254,18 +255,19 @@ main = withGhcDebug' $ do
dbgIO "is addon command" isaddoncmd dbgIO "is addon command" isaddoncmd
--------------------------------------------------------------- ---------------------------------------------------------------
-- Read extra options from a config file. -- 2. Read extra options from a config file.
dbgIO "\n2. Read options from a config file" ()
-- Identify any --conf/--no-conf options. -- Identify any --conf/--no-conf options.
-- For this parse with cmdargs again, this time with just the args that look conf-related. -- For this parse with cmdargs a second time, this time with just the args that look conf-related.
let cliconfargs = dropUnsupportedOpts confflagsmode cliargswithoutcmd let cliconfargs = dropUnsupportedOpts confflagsmode cliargswithoutcmd
dbgIO "cli args without command" cliargswithoutcmd dbgIO "cli args without command" cliargswithoutcmd
-- dbgIO "cli conf args" cliconfargs -- dbgIO "cli conf args" cliconfargs
let rawopts1 = cmdargsParse "to get conf file" confflagsmode cliconfargs let rawopts2 = cmdargsParse "for conf options" confflagsmode cliconfargs
-- Read extra general and command-specific args/opts from the config file if found. -- Read extra general and command-specific args/opts from the config file if found.
-- Ignore any general opts or cli-specific 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 rawopts1 (conf, mconffile) <- getConf rawopts2
let let
genargsfromconf = confLookup "general" conf genargsfromconf = confLookup "general" conf
addoncmdssupportinggenopts = ["ui", "web"] -- addons known to support hledger general options addoncmdssupportinggenopts = ["ui", "web"] -- addons known to support hledger general options
@ -285,52 +287,54 @@ main = withGhcDebug' $ do
dbgIO1 "using extra command args from config file" cmdargsfromconf dbgIO1 "using extra command args from config file" cmdargsfromconf
--------------------------------------------------------------- ---------------------------------------------------------------
-- Combine cli and config file args and parse with cmdargs. -- 3. Combine cli and config file args and parse with cmdargs a third time.
-- A bad flag or flag argument will cause the program to exit with an error here. -- A bad flag or flag argument will cause the program to exit with an error here.
dbgIO "\n3. Combine command line and config file args" ()
let let
finalargs = -- (avoid breaking vs code haskell highlighting..) finalargs =
(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
let rawopts = cmdargsParse "to get options" (mainmode addons) finalargs let rawopts3 = cmdargsParse "for all options" (mainmode addons) finalargs
--------------------------------------------------------------- ---------------------------------------------------------------
-- Finally, select an action and run it. -- 4. Finally, select an action and run it.
dbgIO "\n4. Select an action" ()
-- We check for the help/doc/version flags first, since they are a high priority. -- We check for the help/doc/version flags first, since they are a high priority.
-- (A perfectionist might think they should be so high priority that adding -h -- (A perfectionist might think they should be so high priority that adding -h
-- to an invalid command line would show help. But cmdargs tends to fail first, -- to an invalid command line would show help. But cmdargs tends to fail first,
-- preventing this, and trying to detect them without cmdargs, and always do the -- preventing this, and trying to detect them without cmdargs, and always do the
-- right thing with builtin commands and addon commands, gets much too complicated.) -- right thing with builtin commands and addon commands, gets much too complicated.)
let let
helpFlag = boolopt "help" rawopts helpFlag = boolopt "help" rawopts3
tldrFlag = boolopt "tldr" rawopts tldrFlag = boolopt "tldr" rawopts3
infoFlag = boolopt "info" rawopts infoFlag = boolopt "info" rawopts3
manFlag = boolopt "man" rawopts manFlag = boolopt "man" rawopts3
versionFlag = boolopt "version" rawopts versionFlag = boolopt "version" rawopts3
if if
-- no command and a help/doc flag found - show general help/docs -- 4.1. no command and a help/doc flag found - show general help/docs
| nocmdprovided && helpFlag -> pager $ showModeUsage (mainmode []) ++ "\n" | nocmdprovided && helpFlag -> pager $ showModeUsage (mainmode []) ++ "\n"
| nocmdprovided && tldrFlag -> runTldrForPage "hledger" | nocmdprovided && tldrFlag -> runTldrForPage "hledger"
| nocmdprovided && infoFlag -> runInfoForTopic "hledger" Nothing | nocmdprovided && infoFlag -> runInfoForTopic "hledger" Nothing
| nocmdprovided && manFlag -> runManForTopic "hledger" Nothing | nocmdprovided && manFlag -> runManForTopic "hledger" Nothing
-- --version flag found and none of these other conditions - show version -- 4.2. --version flag found and none of these other conditions - show version
| versionFlag && not (isaddoncmd || helpFlag || tldrFlag || infoFlag || manFlag) -> putStrLn prognameandversion | versionFlag && not (isaddoncmd || helpFlag || tldrFlag || infoFlag || manFlag) -> putStrLn prognameandversion
-- there's a command argument, but it's bad - show error -- 4.3. there's a command argument, but it's bad - show error
| badcmdprovided -> error' $ "command "++clicmdarg++" is not recognized, run with no command to see a list" | badcmdprovided -> error' $ "command "++clicmdarg++" is not recognized, run with no command to see a list"
-- no command found, nothing else to do - show the commands list -- 4.4. no command found, nothing else to do - show the commands list
| nocmdprovided -> dbgIO "" "no command, showing commands list" >> printCommandsList prognameandversion addons | nocmdprovided -> dbgIO "" "no command, showing commands list" >> printCommandsList prognameandversion addons
-- builtin command found -- 4.5. builtin command found
| Just (cmdmode, cmdaction) <- mcmdmodeaction -> do | Just (cmdmode, cmdaction) <- mcmdmodeaction -> do
-- validate opts/args more and convert to CliOpts -- validate opts/args more and convert to CliOpts
opts <- rawOptsToCliOpts rawopts >>= \opts0 -> return opts0{progstarttime_=starttime} opts <- rawOptsToCliOpts rawopts3 >>= \opts0 -> return opts0{progstarttime_=starttime}
dbgIO2 "processed opts" opts dbgIO2 "processed opts" opts
dbgIO "period from opts" (period_ . _rsReportOpts $ reportspec_ opts) dbgIO "period from opts" (period_ . _rsReportOpts $ reportspec_ opts)
dbgIO "interval from opts" (interval_ . _rsReportOpts $ reportspec_ opts) dbgIO "interval from opts" (interval_ . _rsReportOpts $ reportspec_ opts)
@ -359,7 +363,7 @@ main = withGhcDebug' $ do
-- all other builtin commands - read the journal and if successful run the command with it -- all other builtin commands - read the journal and if successful run the command with it
| otherwise -> withJournalDo opts $ cmdaction opts | otherwise -> withJournalDo opts $ cmdaction opts
-- external addon command found - run it, -- 4.6. external addon command found - run it,
-- passing any cli arguments written after the command name -- passing any cli arguments written after the command name
-- and any command-specific opts from the config file. -- and any command-specific opts from the config file.
-- Any "--" arguments, which sometimes must be used in the command line -- Any "--" arguments, which sometimes must be used in the command line
@ -382,13 +386,13 @@ main = withGhcDebug' $ do
-- deprecated command found -- deprecated command found
-- cmd == "convert" = error' (modeHelp oldconvertmode) >> exitFailure -- cmd == "convert" = error' (modeHelp oldconvertmode) >> exitFailure
-- something else (shouldn't happen) - show an error -- 4.7. something else (shouldn't happen) - show an error
| otherwise -> usageError $ | otherwise -> usageError $
"could not understand the arguments "++show finalargs "could not understand the arguments "++show finalargs
<> if null genargsfromconf then "" else "\ngeneral arguments added from config file: "++show genargsfromconf <> if null genargsfromconf then "" else "\ngeneral arguments added from config file: "++show genargsfromconf
<> if null cmdargsfromconf then "" else "\ncommand arguments added from config file: "++show cmdargsfromconf <> if null cmdargsfromconf then "" else "\ncommand arguments added from config file: "++show cmdargsfromconf
-- And we're done. -- 5. And we're done.
-- Give ghc-debug a final chance to take control. -- Give ghc-debug a final chance to take control.
when (ghcDebugMode == GDPauseAtEnd) $ ghcDebugPause' when (ghcDebugMode == GDPauseAtEnd) $ ghcDebugPause'
@ -405,8 +409,8 @@ argsToCliOpts args addons = do
let let
(_, _, args0) = moveFlagsAfterCommand args (_, _, args0) = moveFlagsAfterCommand args
args1 = replaceNumericFlags args0 args1 = replaceNumericFlags args0
rawopts = cmdargsParse "to get options" (mainmode addons) args1 rawopts3 = cmdargsParse "for options" (mainmode addons) args1
rawOptsToCliOpts rawopts rawOptsToCliOpts rawopts3
-- | Parse the given command line arguments/options with the given cmdargs mode, -- | Parse the given command line arguments/options with the given cmdargs mode,
-- after adding values to any valueless --debug flags, -- after adding values to any valueless --debug flags,