imp: config file: --conf, --no-conf/-n, improve debug output
This commit is contained in:
parent
6180a162b2
commit
5739bff249
@ -1,8 +1,10 @@
|
|||||||
|
#!/usr/bin/env -S hledger --conf
|
||||||
# hledger.conf - extra options(/arguments) to be added to hledger commands.
|
# hledger.conf - extra options(/arguments) to be added to hledger commands.
|
||||||
|
|
||||||
# hledger looks for a hledger.conf file in the current directory or above,
|
# hledger looks for a hledger.conf file in the current directory or above,
|
||||||
# or in your home directory with a dotted name: $HOME/.hledger.conf,
|
# or in your home directory with a dotted name: $HOME/.hledger.conf,
|
||||||
# or in your XDG config directory: $HOME/.config/hledger/hledger.conf.
|
# or in your XDG config directory: $HOME/.config/hledger/hledger.conf.
|
||||||
|
# You can also execute a conf file with a shebang line like the one above.
|
||||||
|
|
||||||
# 1. This first, unnamed section is typically used for general options.
|
# 1. This first, unnamed section is typically used for general options.
|
||||||
# These affect all commands, or when not supported will be ignored.
|
# These affect all commands, or when not supported will be ignored.
|
||||||
|
|||||||
@ -88,7 +88,7 @@ module Hledger.Cli (
|
|||||||
)
|
)
|
||||||
where
|
where
|
||||||
|
|
||||||
import Control.Monad (when)
|
import Control.Monad (when, unless)
|
||||||
import Data.List
|
import Data.List
|
||||||
import qualified Data.List.NonEmpty as NE
|
import qualified Data.List.NonEmpty as NE
|
||||||
import Data.Time.Clock.POSIX (getPOSIXTime)
|
import Data.Time.Clock.POSIX (getPOSIXTime)
|
||||||
@ -112,6 +112,7 @@ import Data.Bifunctor (second)
|
|||||||
import Data.Function ((&))
|
import Data.Function ((&))
|
||||||
import Data.Functor ((<&>))
|
import Data.Functor ((<&>))
|
||||||
import Data.List.Extra (nubSort)
|
import Data.List.Extra (nubSort)
|
||||||
|
import Data.Maybe (isJust)
|
||||||
|
|
||||||
|
|
||||||
-- | The overall cmdargs mode describing hledger's command-line options and subcommands.
|
-- | The overall cmdargs mode describing hledger's command-line options and subcommands.
|
||||||
@ -134,7 +135,10 @@ mainmode addons = defMode {
|
|||||||
-- flags in named groups: (keep synced with Hledger.Cli.CliOptions.highlightHelp)
|
-- flags in named groups: (keep synced with Hledger.Cli.CliOptions.highlightHelp)
|
||||||
groupNamed = cligeneralflagsgroups1
|
groupNamed = cligeneralflagsgroups1
|
||||||
-- flags in the unnamed group, shown last:
|
-- flags in the unnamed group, shown last:
|
||||||
,groupUnnamed = []
|
,groupUnnamed = [
|
||||||
|
flagReq ["conf"] (\s opts -> Right $ setopt "conf" s opts) "CONFFILE" "Use extra options defined in this config file. If not specified, searches upward and in XDG config dir for hledger.conf (or .hledger.conf in $HOME)."
|
||||||
|
,flagNone ["no-conf","n"] (setboolopt "no-conf") "ignore any config file"
|
||||||
|
]
|
||||||
-- flags handled but not shown in the help:
|
-- flags handled but not shown in the help:
|
||||||
,groupHidden =
|
,groupHidden =
|
||||||
[detailedversionflag]
|
[detailedversionflag]
|
||||||
@ -216,12 +220,14 @@ main = withGhcDebug' $ do
|
|||||||
-- 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.
|
||||||
-- 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
|
-- cliargswithcmdfirst' = filter (/= "--debug") cliargswithcmdfirst
|
||||||
-- XXX files --debug fails here, eg.
|
-- XXX files --debug fails here, eg.
|
||||||
-- How to parse the command name with cmdargs without passing unsupported flags that it will reject ?
|
-- How to parse the command name with cmdargs without passing unsupported flags that it will reject ?
|
||||||
-- Is --debug the only flag like this ?
|
-- Is --debug the only flag like this ?
|
||||||
cmd = cmdargsParse cliargswithcmdfirst addons & stringopt "command"
|
rawopts0 = cmdargsParse cliargswithcmdfirst addons
|
||||||
|
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)
|
||||||
badcmdprovided = null cmd && not nocmdprovided
|
badcmdprovided = null cmd && not nocmdprovided
|
||||||
isaddoncmd = not (null cmd) && cmd `elem` addons
|
isaddoncmd = not (null cmd) && cmd `elem` addons
|
||||||
@ -238,7 +244,7 @@ main = withGhcDebug' $ do
|
|||||||
|
|
||||||
-- 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 not known to be supported by the command.
|
||||||
conf <- getConf
|
(conf, mconffile) <- getConf rawopts0
|
||||||
let
|
let
|
||||||
genargsfromconf = confLookup "general" conf
|
genargsfromconf = confLookup "general" conf
|
||||||
supportedgenargsfromconf
|
supportedgenargsfromconf
|
||||||
@ -248,9 +254,11 @@ main = withGhcDebug' $ do
|
|||||||
cmdargsfromconf
|
cmdargsfromconf
|
||||||
| null cmd = []
|
| null cmd = []
|
||||||
| otherwise = confLookup cmd conf & if isaddoncmd then ("--":) else id
|
| otherwise = confLookup cmd conf & if isaddoncmd then ("--":) else id
|
||||||
dbgIO1 "extra general args from config file" genargsfromconf
|
when (isJust mconffile) $ do
|
||||||
dbgIO1 "excluded general args from config file, not supported by this command" excludedgenargsfromconf
|
dbgIO1 "extra general args from config file" genargsfromconf
|
||||||
dbgIO1 "extra command args from config file" cmdargsfromconf
|
unless (null excludedgenargsfromconf) $
|
||||||
|
dbgIO1 "excluded general args from config file, not supported by this command" excludedgenargsfromconf
|
||||||
|
dbgIO1 "extra command args from config file" cmdargsfromconf
|
||||||
|
|
||||||
---------------------------------------------------------------
|
---------------------------------------------------------------
|
||||||
-- Combine cli and config file args and parse with cmdargs.
|
-- Combine cli and config file args and parse with cmdargs.
|
||||||
|
|||||||
@ -146,8 +146,8 @@ prognameandversion =
|
|||||||
-- | Common input-related flags: --file, --rules-file, --alias...
|
-- | Common input-related flags: --file, --rules-file, --alias...
|
||||||
inputflags :: [Flag RawOpts]
|
inputflags :: [Flag RawOpts]
|
||||||
inputflags = [
|
inputflags = [
|
||||||
flagReq ["file","f"] (\s opts -> Right $ setopt "file" s opts) "FILE" "Read data from FILE, or from stdin if -. Can be specified more than once. If not specified, reads from $LEDGER_FILE or $HOME/.hledger.journal."
|
flagReq ["file","f"] (\s opts -> Right $ setopt "file" s opts) "[FMT:]FILE" "Read data from FILE, or from stdin if FILE is -, inferring format from extension or a FMT: prefix. Can be specified more than once. If not specified, reads from $LEDGER_FILE or $HOME/.hledger.journal."
|
||||||
,flagReq ["rules-file"] (\s opts -> Right $ setopt "rules-file" s opts) "RULEFILE" "Use conversion rules from this file for converting subsequent CSV/SSV/TSV files. If not specified, uses FILE.rules for each such FILE."
|
,flagReq ["rules"] (\s opts -> Right $ setopt "rules" s opts) "RULESFILE" "Use rules defined in this rules file for converting subsequent CSV/SSV/TSV files. If not specified, uses FILE.csv.rules for each FILE.csv." -- see also hiddenflags
|
||||||
|
|
||||||
,flagReq ["alias"] (\s opts -> Right $ setopt "alias" s opts) "A=B|/RGX/=RPL" "transform account names from A to B, or by replacing regular expression matches"
|
,flagReq ["alias"] (\s opts -> Right $ setopt "alias" s opts) "A=B|/RGX/=RPL" "transform account names from A to B, or by replacing regular expression matches"
|
||||||
,flagNone ["auto"] (setboolopt "auto") "generate extra postings by applying auto posting rules (\"=\") to all transactions"
|
,flagNone ["auto"] (setboolopt "auto") "generate extra postings by applying auto posting rules (\"=\") to all transactions"
|
||||||
|
|||||||
@ -5,6 +5,7 @@ Read extra CLI arguments from a hledger config file.
|
|||||||
{-# LANGUAGE ScopedTypeVariables #-}
|
{-# LANGUAGE ScopedTypeVariables #-}
|
||||||
{-# LANGUAGE NamedFieldPuns #-}
|
{-# LANGUAGE NamedFieldPuns #-}
|
||||||
{-# LANGUAGE TupleSections #-}
|
{-# LANGUAGE TupleSections #-}
|
||||||
|
{-# LANGUAGE MultiWayIf #-}
|
||||||
|
|
||||||
module Hledger.Cli.Conf (
|
module Hledger.Cli.Conf (
|
||||||
getConf
|
getConf
|
||||||
@ -25,7 +26,7 @@ import System.FilePath ((</>), takeDirectory)
|
|||||||
import Text.Megaparsec
|
import Text.Megaparsec
|
||||||
import Text.Megaparsec.Char
|
import Text.Megaparsec.Char
|
||||||
|
|
||||||
import Hledger (error', strip, words')
|
import Hledger (error', strip, words', RawOpts, boolopt, maybestringopt, expandPath)
|
||||||
import Hledger.Read.Common
|
import Hledger.Read.Common
|
||||||
import Hledger.Utils.Parse
|
import Hledger.Utils.Parse
|
||||||
import Hledger.Utils.Debug
|
import Hledger.Utils.Debug
|
||||||
@ -70,55 +71,86 @@ confLookup cmd Conf{confSections} =
|
|||||||
M.lookup cmd $
|
M.lookup cmd $
|
||||||
M.fromList [(csName,csArgs) | ConfSection{csName,csArgs} <- confSections]
|
M.fromList [(csName,csArgs) | ConfSection{csName,csArgs} <- confSections]
|
||||||
|
|
||||||
-- | Try to read a hledger config file from several places.
|
-- | Try to read a hledger config file from several places,
|
||||||
-- If no config file is found, this returns a null Conf.
|
-- or from a file specified by --conf,
|
||||||
|
-- returning its path and a Conf parsed from it.
|
||||||
|
-- If no config file is found, or --no-conf is used, this returns a null Conf.
|
||||||
-- Any other IO or parse failure will raise an error.
|
-- Any other IO or parse failure will raise an error.
|
||||||
getConf :: IO Conf
|
getConf :: RawOpts -> IO (Conf, Maybe FilePath)
|
||||||
getConf = do
|
getConf rawopts = do
|
||||||
mconftxt <- confFilePaths >>= readFirstConfFile
|
defconfpaths <- confFilePaths
|
||||||
case mconftxt of
|
let noconf = boolopt "no-conf" rawopts
|
||||||
Nothing -> return $ traceAt 1 "no config file found" nullconf
|
let mconffile0 = maybestringopt "conf" rawopts
|
||||||
Just (f,s) ->
|
mconffile <- case mconffile0 of
|
||||||
|
Just f -> Just <$> (getCurrentDirectory >>= flip expandPath f)
|
||||||
|
Nothing -> return Nothing
|
||||||
|
let confpaths = maybe defconfpaths (:[]) mconffile
|
||||||
|
mconftxt <- readFirstConfFile confpaths
|
||||||
|
if
|
||||||
|
| noconf -> return $ traceAt 1 "ignoring config files" (nullconf, Nothing)
|
||||||
|
| Nothing <- mconftxt -> return $ traceAt 1 "no config file found" (nullconf, Nothing)
|
||||||
|
| Just (f,s) <- mconftxt ->
|
||||||
case parseConf f (T.pack s) of
|
case parseConf f (T.pack s) of
|
||||||
Left err -> error' $ errorBundlePretty err -- customErrorBundlePretty err
|
Left err -> error' $ errorBundlePretty err -- customErrorBundlePretty err
|
||||||
Right ss -> return nullconf{
|
Right ss -> return (nullconf{
|
||||||
confFile = f
|
confFile = f
|
||||||
,confText = s
|
,confText = s
|
||||||
,confFormat = 1
|
,confFormat = 1
|
||||||
,confSections = ss
|
,confSections = ss
|
||||||
}
|
}, Just f)
|
||||||
|
|
||||||
-- | Get the possible paths for a hledger config file, depending on the current directory.
|
-- | Get the possible paths for a hledger config file, depending on the current directory.
|
||||||
confFilePaths :: IO [FilePath]
|
confFilePaths :: IO [FilePath]
|
||||||
confFilePaths = do
|
confFilePaths = do
|
||||||
ds <- confDirs
|
ds <- confDirs
|
||||||
home <- getHomeDirectory
|
home <- getHomeDirectory
|
||||||
return $ dbg1 "possible config files" $
|
return $ dbg8 "possible config files" $
|
||||||
flip map ds $ \d -> d </> if d==home then ".hledger.conf" else "hledger.conf"
|
flip map ds $ \d -> d </> if d==home then ".hledger.conf" else "hledger.conf"
|
||||||
|
|
||||||
-- | Get the directories to check for a hledger config file.
|
-- | Get the directories to check for a hledger config file.
|
||||||
confDirs :: IO [FilePath]
|
confDirs :: IO [FilePath]
|
||||||
confDirs = do
|
confDirs = do
|
||||||
dirs <- getDirsUpToHomeOrRoot
|
xdgc <- getXdgDirectory XdgConfig "hledger"
|
||||||
xdgdir <- getXdgDirectory XdgConfig "hledger"
|
|
||||||
return $ dbg1 "conf dirs" $ dirs <> [xdgdir]
|
|
||||||
|
|
||||||
-- | Get this directory and all of its parents up to ~ or /.
|
|
||||||
getDirsUpToHomeOrRoot :: IO [FilePath]
|
|
||||||
getDirsUpToHomeOrRoot = do
|
|
||||||
home <- getHomeDirectory
|
home <- getHomeDirectory
|
||||||
let
|
here <- getCurrentDirectory
|
||||||
go d =
|
-- lowdirs <- getDirsUpToHomeOrRoot
|
||||||
if d=="/" || d==home
|
-- highdirs <-
|
||||||
then [d]
|
-- case lastMay lowdirs of
|
||||||
else d : go (takeDirectory d)
|
-- Just d | d==home -> getDirsUpToRoot $ takeDirectory d
|
||||||
dbg1 "dirs up to home or root" .
|
-- _ -> return []
|
||||||
go <$> getCurrentDirectory
|
-- let dirs = lowdirs <> [xdgdir] <> highdirs
|
||||||
|
dirs <- getDirsUpToRoot here
|
||||||
|
let dirs2 = if home `elem` dirs then dirs else dirs <> [home]
|
||||||
|
let dirs3 = if xdgc `elem` dirs2 then dirs2 else dirs2 <> [xdgc]
|
||||||
|
return $ dbg1 "searching config dirs" dirs3
|
||||||
|
|
||||||
|
-- -- | Get this directory and all of its parents up to ~ or /.
|
||||||
|
-- getDirsUpToHomeOrRoot :: IO [FilePath]
|
||||||
|
-- getDirsUpToHomeOrRoot = do
|
||||||
|
-- home <- getHomeDirectory
|
||||||
|
-- let
|
||||||
|
-- go d =
|
||||||
|
-- if d=="/" || d==home
|
||||||
|
-- then [d]
|
||||||
|
-- else d : go (takeDirectory d)
|
||||||
|
-- dbg1 "dirs up to home or root" .
|
||||||
|
-- go <$> getCurrentDirectory
|
||||||
|
|
||||||
|
-- | Get this directory and all of its parents up to /.
|
||||||
|
getDirsUpToRoot :: FilePath -> IO [FilePath]
|
||||||
|
getDirsUpToRoot dir = return $ go [] dir
|
||||||
|
where
|
||||||
|
go seen d = if
|
||||||
|
| d `elem` seen || length seen >= 100 -> [] -- just in case
|
||||||
|
| d=="/" -> [d]
|
||||||
|
| otherwise -> d : go (d:seen) (takeDirectory d)
|
||||||
|
-- dbg1 "dirs up to root" .
|
||||||
|
-- go <$> getCurrentDirectory
|
||||||
|
|
||||||
-- | Read the first of these files that exists.
|
-- | Read the first of these files that exists.
|
||||||
readFirstConfFile :: [FilePath] -> IO (Maybe (FilePath, String))
|
readFirstConfFile :: [FilePath] -> IO (Maybe (FilePath, String))
|
||||||
readFirstConfFile fs = do
|
readFirstConfFile fs = do
|
||||||
let dd = dbg1With (("config file found: "<>).fst)
|
let dd = dbg1With (("using config file: "<>).fst)
|
||||||
mapM (fmap (fmap dd).readConfFile) fs <&> headMay . catMaybes
|
mapM (fmap (fmap dd).readConfFile) fs <&> headMay . catMaybes
|
||||||
|
|
||||||
-- | Read this file and return its path and contents, if it exists.
|
-- | Read this file and return its path and contents, if it exists.
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user