imp:cli: --no-pager -> --pager, make this and --color safe options

This commit is contained in:
Simon Michael 2024-10-18 04:01:22 -10:00
parent 7fee69d7ab
commit 711d921774
10 changed files with 88 additions and 19 deletions

View File

@ -9,6 +9,7 @@ more easily by hledger commands/scripts in this and other packages.
module Hledger.Data.RawOptions ( module Hledger.Data.RawOptions (
RawOpts, RawOpts,
YNA,
mkRawOpts, mkRawOpts,
overRawOpts, overRawOpts,
setopt, setopt,
@ -26,7 +27,9 @@ module Hledger.Data.RawOptions (
posintopt, posintopt,
maybeintopt, maybeintopt,
maybeposintopt, maybeposintopt,
maybecharopt maybecharopt,
maybeynopt,
maybeynaopt,
) )
where where
@ -35,6 +38,8 @@ import Data.Default (Default(..))
import Safe (headMay, lastMay, readDef) import Safe (headMay, lastMay, readDef)
import Hledger.Utils import Hledger.Utils
import Data.Char (toLower)
import Data.List (intercalate)
-- | The result of running cmdargs: an association list of option names to string values. -- | The result of running cmdargs: an association list of option names to string values.
@ -139,3 +144,22 @@ maybeclippedintopt minVal maxVal name =
++ " must lie in the range " ++ " must lie in the range "
++ show minVal ++ " to " ++ show maxVal ++ show minVal ++ " to " ++ show maxVal
++ ", but is " ++ show n ++ ", but is " ++ show n
maybeynopt :: String -> RawOpts -> Maybe Bool
maybeynopt name rawopts =
case maybestringopt name rawopts of
Just v | map toLower v `elem` ["y","yes","always"] -> Just True
Just v | map toLower v `elem` ["n","no","never"] -> Just False
Just _ -> error' $ name <> " value should be one of " <> (intercalate ", " ["y","yes","n","no"])
_ -> Nothing
data YNA = Yes | No | Auto deriving (Eq,Show)
maybeynaopt :: String -> RawOpts -> Maybe YNA
maybeynaopt name rawopts =
case maybestringopt name rawopts of
Just v | map toLower v `elem` ["y","yes","always"] -> Just Yes
Just v | map toLower v `elem` ["n","no","never"] -> Just No
Just v | map toLower v `elem` ["a","auto"] -> Just Auto
Just _ -> error' $ name <> " value should be one of " <> (intercalate ", " ["y","yes","n","no","a","auto"])
_ -> Nothing

View File

@ -346,7 +346,7 @@ ynopt opt rawopts = case maybestringopt opt rawopts of
Just "never" -> Just False Just "never" -> Just False
Just "no" -> Just False Just "no" -> Just False
Just "n" -> Just False Just "n" -> Just False
Just _ -> usageError "--pretty's argument should be \"yes\" or \"no\" (or y, n, always, never)" Just _ -> usageError "this argument should be one of y, yes, n, no"
_ -> Nothing _ -> Nothing
balanceAccumulationOverride :: RawOpts -> Maybe BalanceAccumulation balanceAccumulationOverride :: RawOpts -> Maybe BalanceAccumulation

View File

@ -20,8 +20,8 @@ module Hledger.Utils.IO (
pprint', pprint',
-- * Viewing with pager -- * Viewing with pager
pager,
setupPager, setupPager,
runPager,
-- * Terminal size -- * Terminal size
getTerminalHeightWidth, getTerminalHeightWidth,
@ -32,6 +32,9 @@ module Hledger.Utils.IO (
progArgs, progArgs,
outputFileOption, outputFileOption,
hasOutputFile, hasOutputFile,
splitFlagsAndVals,
getLongOpt,
parseYN,
-- * ANSI color -- * ANSI color
colorOption, colorOption,
@ -130,6 +133,7 @@ import Text.Pretty.Simple
defaultOutputOptionsDarkBg, defaultOutputOptionsNoColor, pShowOpt, pPrintOpt) defaultOutputOptionsDarkBg, defaultOutputOptionsNoColor, pShowOpt, pPrintOpt)
import Hledger.Utils.Text (WideBuilder(WideBuilder)) import Hledger.Utils.Text (WideBuilder(WideBuilder))
import Data.Char (toLower)
-- Pretty showing/printing with pretty-simple -- Pretty showing/printing with pretty-simple
@ -210,13 +214,18 @@ setupPager = do
-- supports those, the pager should be configured to display those, otherwise -- supports those, the pager should be configured to display those, otherwise
-- users will see junk on screen (#2015). -- users will see junk on screen (#2015).
-- Call "setupPager" at program startup to make that less likely. -- Call "setupPager" at program startup to make that less likely.
pager :: String -> IO () --
-- Pager use is influenced by the --pager option, at least.
-- Rather than pass in a huge CliOpts, or duplicate conditional logic at every call site,
-- this does some redundant local options parsing.
runPager :: String -> IO ()
#ifdef mingw32_HOST_OS #ifdef mingw32_HOST_OS
pager = putStr runPager = putStr
#else #else
pager s = do runPager s = do
-- disable pager when --no-pager is specified -- disable pager with --pager=no
nopager <- elem "--no-pager" <$> getArgs mpager <- getLongOpt "pager"
let nopager = not $ maybe True parseYN mpager
-- disable pager when TERM=dumb (for Emacs shell users) -- disable pager when TERM=dumb (for Emacs shell users)
dumbterm <- (== Just "dumb") <$> lookupEnv "TERM" dumbterm <- (== Just "dumb") <$> lookupEnv "TERM"
-- disable pager with single-line output (https://github.com/pharpend/pager/issues/2) -- disable pager with single-line output (https://github.com/pharpend/pager/issues/2)
@ -238,6 +247,33 @@ pager s = do
s s
#endif #endif
-- | Given a list of arguments, split any of the form --flag=val into --flag, val.
splitFlagsAndVals :: [String] -> [String]
splitFlagsAndVals =
concatMap
(\a ->
if "--" `isPrefixOf` a && '=' `elem` a
then let (x,y) = break (=='=') a in [x, drop 1 y]
else [a])
-- | Read the value of the rightmost long option of this name from the command line arguments.
-- If the value is missing raise an error.
getLongOpt :: String -> IO (Maybe String)
getLongOpt name = do
rargs <- reverse . splitFlagsAndVals <$> getArgs
let flag = "--"<>name
return $
case break (==flag) rargs of
([],_) -> error' $ flag <> " requires a value"
(argsafter,_) -> Just $ last argsafter
-- | Parse y/yes/always or n/no/never to true or false, or with any other value raise an error.
parseYN :: String -> Bool
parseYN s
| l `elem` ["y","yes","always"] = True
| l `elem` ["n","no","never"] = False
| otherwise = error' $ "value should be one of " <> (intercalate ", " ["y","yes","n","no"])
where l = map toLower s
-- Command line arguments -- Command line arguments

View File

@ -75,7 +75,7 @@ hledgerUiMain = withGhcDebug' $ withProgName "hledger-ui.log" $ do -- force Hle
let copts' = copts{inputopts_=iopts{forecast_=forecast_ iopts <|> Just nulldatespan}} let copts' = copts{inputopts_=iopts{forecast_=forecast_ iopts <|> Just nulldatespan}}
case True of case True of
_ | boolopt "help" rawopts -> pager $ showModeUsage uimode ++ "\n" _ | boolopt "help" rawopts -> runPager $ showModeUsage uimode ++ "\n"
_ | boolopt "tldr" rawopts -> runTldrForPage "hledger-ui" _ | boolopt "tldr" rawopts -> runTldrForPage "hledger-ui"
_ | boolopt "info" rawopts -> runInfoForTopic "hledger-ui" Nothing _ | boolopt "info" rawopts -> runInfoForTopic "hledger-ui" Nothing
_ | boolopt "man" rawopts -> runManForTopic "hledger-ui" Nothing _ | boolopt "man" rawopts -> runManForTopic "hledger-ui" Nothing

View File

@ -55,7 +55,7 @@ hledgerWebMain = withGhcDebug' $ do
wopts@WebOpts{cliopts_=copts@CliOpts{debug_, rawopts_}} <- getHledgerWebOpts wopts@WebOpts{cliopts_=copts@CliOpts{debug_, rawopts_}} <- getHledgerWebOpts
when (debug_ > 0) $ printf "%s\n" prognameandversion >> printf "opts: %s\n" (show wopts) when (debug_ > 0) $ printf "%s\n" prognameandversion >> printf "opts: %s\n" (show wopts)
if if
| boolopt "help" rawopts_ -> pager $ showModeUsage webmode ++ "\n" | boolopt "help" rawopts_ -> runPager $ showModeUsage webmode ++ "\n"
| boolopt "tldr" rawopts_ -> runTldrForPage "hledger-web" | boolopt "tldr" rawopts_ -> runTldrForPage "hledger-web"
| boolopt "info" rawopts_ -> runInfoForTopic "hledger-web" Nothing | boolopt "info" rawopts_ -> runInfoForTopic "hledger-web" Nothing
| boolopt "man" rawopts_ -> runManForTopic "hledger-web" Nothing | boolopt "man" rawopts_ -> runManForTopic "hledger-web" Nothing

View File

@ -1,3 +1,6 @@
# This is an empty config file, to disable your personal hledger config # This is an empty config file, to disable your personal hledger config
# while developing and testing hledger in this directory. # while developing and testing hledger in this directory.
# For an example config, see hledger.conf.sample. # For an example config, see hledger.conf.sample.
--pager yes
# --color=no
# --debug

View File

@ -345,7 +345,7 @@ main = withGhcDebug' $ do
withArgs (progname:finalargs) $ withArgs (progname:finalargs) $
if if
-- 6.1. no command and a help/doc flag found - show general help/docs -- 6.1. no command and a help/doc flag found - show general help/docs
| nocmdprovided && helpFlag -> pager $ showModeUsage (mainmode []) ++ "\n" | nocmdprovided && helpFlag -> runPager $ 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
@ -375,7 +375,7 @@ main = withGhcDebug' $ do
-- run the builtin command according to its type -- run the builtin command according to its type
if if
-- 6.5.1. help/doc flag - show command help/docs -- 6.5.1. help/doc flag - show command help/docs
| helpFlag -> pager $ showModeUsage cmdmode ++ "\n" | helpFlag -> runPager $ showModeUsage cmdmode ++ "\n"
| tldrFlag -> runTldrForPage tldrpagename | tldrFlag -> runTldrForPage tldrpagename
| infoFlag -> runInfoForTopic "hledger" mmodecmdname | infoFlag -> runInfoForTopic "hledger" mmodecmdname
| manFlag -> runManForTopic "hledger" mmodecmdname | manFlag -> runManForTopic "hledger" mmodecmdname

View File

@ -22,8 +22,8 @@ module Hledger.Cli.CliOptions (
-- * cmdargs flags & modes -- * cmdargs flags & modes
inputflags, inputflags,
reportflags, reportflags,
terminalflags,
helpflags, helpflags,
terminalflags,
helpflagstitle, helpflagstitle,
flattreeflags, flattreeflags,
confflags, confflags,
@ -240,17 +240,17 @@ helpflags = [
-- flagOpt would be more correct for --debug, showing --debug[=LVL] rather than --debug=[LVL] in help. -- flagOpt would be more correct for --debug, showing --debug[=LVL] rather than --debug=[LVL] in help.
-- But flagReq plus special handling in Cli.hs makes the = optional, removing a source of confusion. -- But flagReq plus special handling in Cli.hs makes the = optional, removing a source of confusion.
,flagReq ["debug"] (\s opts -> Right $ setopt "debug" s opts) "[1-9]" "show this much debug output (default: 1)" ,flagReq ["debug"] (\s opts -> Right $ setopt "debug" s opts) "[1-9]" "show this much debug output (default: 1)"
] ] -- XXX why are these duplicated in defCommandMode below ?
-- XXX why are these duplicated in defCommandMode below ?
<> terminalflags <> terminalflags
-- Low-level flags affecting terminal output. -- Low-level flags affecting terminal output.
-- These are included in helpflags so they appear everywhere. -- These are included in helpflags so they appear everywhere.
terminalflags = [ terminalflags = [
flagNone ["no-pager"] (setboolopt "no-pager") "don't use a pager for long output" flagReq ["pager"] (\s opts -> Right $ setopt "pager" s opts) "YN"
"use pager for long output ? y/yes or n/no"
-- This has special support in hledger-lib:colorOption, keep synced -- This has special support in hledger-lib:colorOption, keep synced
,flagReq ["color","colour"] (\s opts -> Right $ setopt "color" s opts) "YN" ,flagReq ["color","colour"] (\s opts -> Right $ setopt "color" s opts) "YN"
"use ANSI color in terminal ? 'y'/'yes', 'n'/'no', or 'auto' (default)" "use ANSI color ? y/yes, n/no, or auto (default)"
] ]
-- | Flags for selecting flat/tree mode, used for reports organised by account. -- | Flags for selecting flat/tree mode, used for reports organised by account.
@ -542,6 +542,8 @@ data CliOpts = CliOpts {
,reportspec_ :: ReportSpec ,reportspec_ :: ReportSpec
,output_file_ :: Maybe FilePath ,output_file_ :: Maybe FilePath
,output_format_ :: Maybe String ,output_format_ :: Maybe String
,pageropt_ :: Maybe Bool -- ^ --pager
,coloropt_ :: Maybe YNA -- ^ --color. Controls use of ANSI color and ANSI styles.
,debug_ :: Int -- ^ debug level, set by @--debug[=N]@. See also 'Hledger.Utils.debugLevel'. ,debug_ :: Int -- ^ debug level, set by @--debug[=N]@. See also 'Hledger.Utils.debugLevel'.
,no_new_accounts_ :: Bool -- add ,no_new_accounts_ :: Bool -- add
,width_ :: Maybe String -- ^ the --width value provided, if any ,width_ :: Maybe String -- ^ the --width value provided, if any
@ -563,6 +565,8 @@ defcliopts = CliOpts
, reportspec_ = def , reportspec_ = def
, output_file_ = Nothing , output_file_ = Nothing
, output_format_ = Nothing , output_format_ = Nothing
, pageropt_ = Nothing
, coloropt_ = Nothing
, debug_ = 0 , debug_ = 0
, no_new_accounts_ = False , no_new_accounts_ = False
, width_ = Nothing , width_ = Nothing
@ -621,6 +625,8 @@ rawOptsToCliOpts rawopts = do
,reportspec_ = rspec ,reportspec_ = rspec
,output_file_ = maybestringopt "output-file" rawopts ,output_file_ = maybestringopt "output-file" rawopts
,output_format_ = maybestringopt "output-format" rawopts ,output_format_ = maybestringopt "output-format" rawopts
,pageropt_ = maybeynopt "pager" rawopts
,coloropt_ = maybeynaopt "color" rawopts
,debug_ = posintopt "debug" rawopts ,debug_ = posintopt "debug" rawopts
,no_new_accounts_ = boolopt "no-new-accounts" rawopts -- add ,no_new_accounts_ = boolopt "no-new-accounts" rawopts -- add
,width_ = maybestringopt "width" rawopts ,width_ = maybestringopt "width" rawopts

View File

@ -327,7 +327,7 @@ printCommandsList progversion installedaddons =
seq (length $ dbg8 "uninstalledknownaddons" uninstalledknownaddons) $ -- for debug output seq (length $ dbg8 "uninstalledknownaddons" uninstalledknownaddons) $ -- for debug output
seq (length $ dbg8 "installedknownaddons" installedknownaddons) $ seq (length $ dbg8 "installedknownaddons" installedknownaddons) $
seq (length $ dbg8 "installedunknownaddons" installedunknownaddons) $ seq (length $ dbg8 "installedunknownaddons" installedunknownaddons) $
pager $ unlines $ map unplus $ filter (not.isuninstalledaddon) $ runPager $ unlines $ map unplus $ filter (not.isuninstalledaddon) $
commandsList progversion installedunknownaddons commandsList progversion installedunknownaddons
where where
knownaddons = knownAddonCommands knownaddons = knownAddonCommands

View File

@ -130,7 +130,7 @@ writeOutput opts s = do
writeOutputLazyText :: CliOpts -> TL.Text -> IO () writeOutputLazyText :: CliOpts -> TL.Text -> IO ()
writeOutputLazyText opts s = do writeOutputLazyText opts s = do
f <- outputFileFromOpts opts f <- outputFileFromOpts opts
maybe (pager . TL.unpack) TL.writeFile f s maybe (runPager . TL.unpack) TL.writeFile f s
-- -- | Get a journal from the given string and options, or throw an error. -- -- | Get a journal from the given string and options, or throw an error.
-- readJournal :: CliOpts -> String -> IO Journal -- readJournal :: CliOpts -> String -> IO Journal