imp: setup v2, simpler implementation and shorter output
This one tests only with the currently running hledger, and requires being installed in PATH to show full info.
This commit is contained in:
parent
78baaee6c5
commit
4a5b0d46b5
@ -23,11 +23,11 @@ module Hledger.Cli.Commands.Setup (
|
|||||||
)
|
)
|
||||||
where
|
where
|
||||||
|
|
||||||
import Control.Applicative ((<|>))
|
|
||||||
import Control.Exception
|
import Control.Exception
|
||||||
import Control.Monad
|
import Control.Monad
|
||||||
import qualified Data.ByteString as B
|
-- import qualified Data.ByteString as B
|
||||||
import Data.Char
|
import Data.Char
|
||||||
|
import Data.Default (def)
|
||||||
import Data.List
|
import Data.List
|
||||||
import qualified Data.Map as M
|
import qualified Data.Map as M
|
||||||
import Data.Maybe
|
import Data.Maybe
|
||||||
@ -49,7 +49,7 @@ import Hledger hiding (setupPager)
|
|||||||
import Hledger.Cli.CliOptions
|
import Hledger.Cli.CliOptions
|
||||||
import Hledger.Cli.Conf
|
import Hledger.Cli.Conf
|
||||||
import Hledger.Cli.Version
|
import Hledger.Cli.Version
|
||||||
import Data.Default (def)
|
import System.IO (localeEncoding)
|
||||||
|
|
||||||
|
|
||||||
setupmode = hledgerCommandMode
|
setupmode = hledgerCommandMode
|
||||||
@ -69,27 +69,16 @@ without premature termination or misformatting.
|
|||||||
The tests are grouped into setup* routines, so named because they might do more
|
The tests are grouped into setup* routines, so named because they might do more
|
||||||
than just test in future.
|
than just test in future.
|
||||||
|
|
||||||
The first group of tests checks the hledger executable found in PATH.
|
This is the second version of setup. If it finds that the currently
|
||||||
Note, later tests which require running hledger should use this executable,
|
running hledger is not the one installed in PATH (by comparing --version output),
|
||||||
rather than the API of the hledger currently running (in case they're different).
|
it refuses to proceed further until that has been done.
|
||||||
|
This means it can rely on all the latest features and use the hledger API
|
||||||
This hledger executable could be old; we can't assume it has modern flags/commands.
|
within this process, simplifying things greatly.
|
||||||
Eg its --version output may be less than usual, or it may not have the check command.
|
|
||||||
|
|
||||||
Or it may not accept -n/--no-conf, which we may need to avoid disruption from config files.
|
|
||||||
Eg there's an inconvenient bug in hledger 1.40-1.42ish: without -n, a bad config file
|
|
||||||
breaks pretty much everything, including --version, --help, and test.
|
|
||||||
|
|
||||||
So we first try hledger --version -n; if this fails, we try hledger --version.
|
|
||||||
(For absolute precision we should perhaps check the failure output for "Unknown flag: -n"..)
|
|
||||||
If we are able to detect the version, we use that to decide how and whether to run
|
|
||||||
later tests. Eg, for hledger <1.40, we won't run config tests, and won't use -n.
|
|
||||||
|
|
||||||
-}
|
-}
|
||||||
setup :: CliOpts -> Journal -> IO ()
|
setup :: CliOpts -> Journal -> IO ()
|
||||||
setup _opts@CliOpts{rawopts_=_rawopts, reportspec_=_rspec} _ignoredj = do
|
setup _opts@CliOpts{rawopts_=_rawopts, reportspec_=_rspec} _ignoredj = do
|
||||||
-- This command is not given a journal and should not use _ignoredj;
|
-- This command is not given a journal and should not use _ignoredj;
|
||||||
-- instead detect it ourselves when we are ready.
|
-- instead read it ourselves when we are ready.
|
||||||
putStrLn "Checking your hledger setup.."
|
putStrLn "Checking your hledger setup.."
|
||||||
color <- useColorOnStdout
|
color <- useColorOnStdout
|
||||||
when color $
|
when color $
|
||||||
@ -99,331 +88,228 @@ setup _opts@CliOpts{rawopts_=_rawopts, reportspec_=_rspec} _ignoredj = do
|
|||||||
,warning "unknown"
|
,warning "unknown"
|
||||||
,bad "warning"
|
,bad "warning"
|
||||||
]
|
]
|
||||||
mversion <- setupHledger
|
conf <- fromMaybe nullconf <$> setupHledger
|
||||||
case mversion of
|
setupTerminal conf
|
||||||
Nothing -> return ()
|
setupJournal
|
||||||
Just HledgerBinaryVersion{hbinPackageVersion=version} -> do
|
-- setupStrictness conf
|
||||||
setupLocale
|
|
||||||
conf <- fromMaybe nullconf <$> setupConfig version
|
|
||||||
setupColor version conf
|
|
||||||
setupPager version conf
|
|
||||||
setupPretty version conf
|
|
||||||
setupCompletions version
|
|
||||||
ej <- setupJournal version
|
|
||||||
let mj = either (const Nothing) Just ej
|
|
||||||
setupAccounts version mj
|
|
||||||
setupCommodities version mj
|
|
||||||
return ()
|
|
||||||
putStr "\n"
|
putStr "\n"
|
||||||
|
|
||||||
-- Test a hledger version for support of various features.
|
|
||||||
ver >=! str = ver >= (fromJust $ toVersion str)
|
|
||||||
supportsIgnoreAssertions = (>=! "0.24") -- --ignore-assertions (2014)
|
|
||||||
supportsCommodityDirective = (>=! "1.0") -- commodity directive (2016)
|
|
||||||
supportsPretty = (>=! "1.2") -- --pretty, to use box-drawing characters (2017)
|
|
||||||
supportsAccountDirective = (>=! "1.9") -- account directive (2018)
|
|
||||||
supportsAccountTypes = (>=! "1.13") -- ALERX account types, type: tag (2019)
|
|
||||||
supportsCashAccountType = (>=! "1.19") -- C/Cash account type (2020)
|
|
||||||
supportsBasicColor = (>=! "1.19") -- basic color detection/control (2020)
|
|
||||||
supportsConversionAccountType = (>=! "1.25") -- V/Conversion account type, accounts --types (2022)
|
|
||||||
supportsConfigFiles = (>=! "1.40") -- config files (2024)
|
|
||||||
supportsColor = (>=! "1.41") -- robust color detection/control (2024)
|
|
||||||
supportsPager = (>=! "1.41") -- use a pager for all output (2024)
|
|
||||||
supportsBashCompletions = (>=! "1.41") -- up to date bash shell completions (2024)
|
|
||||||
|
|
||||||
------------------------------------------------------------------------------
|
------------------------------------------------------------------------------
|
||||||
|
|
||||||
-- | This first test group looks for a "hledger" executable in PATH;
|
-- | This first test group looks for a "hledger" executable in PATH;
|
||||||
-- if found, tests it in various ways;
|
-- if found, tests it in various ways;
|
||||||
-- and if it ran successfully, returns the full --version output
|
-- and if it ran successfully, returns the full --version output
|
||||||
-- and the numeric Version parsed from that.
|
-- and the numeric Version parsed from that.
|
||||||
setupHledger :: IO (Maybe HledgerBinaryVersion)
|
setupHledger :: IO (Maybe Conf)
|
||||||
setupHledger = do
|
setupHledger = do
|
||||||
pgroup "hledger"
|
pgroup "hledger"
|
||||||
|
|
||||||
pdesc "is in PATH ?"
|
pdesc "is a released version ?"
|
||||||
|
let isreleased = if isReleaseVersion $ hbinPackageVersion binaryinfo then Y else N
|
||||||
|
i isreleased prognameandversion
|
||||||
|
|
||||||
|
pdesc "is up to date ?"
|
||||||
|
elatestversionnumstr <- getLatestHledgerVersion
|
||||||
|
case elatestversionnumstr of
|
||||||
|
Left e -> p U ("couldn't read " <> latestHledgerVersionUrlStr <> " , " <> e)
|
||||||
|
Right latestversionnumstr ->
|
||||||
|
case toVersion latestversionnumstr of
|
||||||
|
Nothing -> p U "couldn't parse latest version number"
|
||||||
|
Just latestversion -> p
|
||||||
|
(if hbinPackageVersion binaryinfo >= latestversion then Y else N)
|
||||||
|
(showVersion (hbinPackageVersion binaryinfo) <> " installed, latest is " <> latestversionnumstr)
|
||||||
|
|
||||||
|
pdesc "is a native binary for this machine ?"
|
||||||
|
case hbinArch binaryinfo of
|
||||||
|
Nothing -> p U $ "couldn't detect this binary's architecture"
|
||||||
|
Just a | a /= arch -> p N $ "binary is for " <> a <> ", system is " <> arch <> ", may run slowly"
|
||||||
|
Just a -> p Y a
|
||||||
|
|
||||||
|
pdesc "is installed in PATH ?"
|
||||||
pathexes <- findExecutables progname
|
pathexes <- findExecutables progname
|
||||||
home <- getHomeDirectory
|
let msg = "please install this hledger in PATH and run setup again"
|
||||||
appdata <- getXdgDirectory XdgData ""
|
case pathexes of
|
||||||
otherexes <- flip findExecutablesInDirectories progname $
|
[] -> p N msg >> exitFailure
|
||||||
[home </> ".local/bin"
|
exe:_ -> do
|
||||||
,home </> ".cabal/bin"
|
|
||||||
,home </> ".nix-profile/bin"
|
|
||||||
,"/opt/homebrew/bin"
|
|
||||||
,"/usr/local/bin"
|
|
||||||
,"/usr/bin"
|
|
||||||
]
|
|
||||||
++ [appdata </> "local/bin" | os == "mingw32"]
|
|
||||||
++ [appdata </> "cabal/bin" | os == "mingw32"]
|
|
||||||
case (pathexes, otherexes) of
|
|
||||||
([], []) -> do
|
|
||||||
p N $ "Move the " <> progname <> " binary to a directory in your shell's PATH"
|
|
||||||
return Nothing
|
|
||||||
([], otherexe:_) -> do
|
|
||||||
let otherdir = takeDirectory otherexe
|
|
||||||
p N $ unlines
|
|
||||||
["Add " <> otherdir <> " to PATH in your shell config."
|
|
||||||
," Eg on unix: echo 'export PATH=" <> otherdir <> ":$PATH' >> ~/.profile"
|
|
||||||
," and start a new shell session."
|
|
||||||
]
|
|
||||||
return Nothing
|
|
||||||
(pathexe:_, _) -> do
|
|
||||||
p Y (quoteIfNeeded pathexe)
|
|
||||||
|
|
||||||
-- hledger was found in PATH, continue
|
|
||||||
|
|
||||||
pdesc "runs and --version looks like hledger ?"
|
|
||||||
eerrout <- tryHledgerArgs [["--version", "--no-conf"], ["--version"]]
|
eerrout <- tryHledgerArgs [["--version", "--no-conf"], ["--version"]]
|
||||||
case eerrout of
|
case eerrout of
|
||||||
Left err ->
|
Left err -> p U (progname <> " --version failed: " <> err) >> exitFailure
|
||||||
p N (progname <> " --version failed: " <> err) >> return Nothing
|
Right out -> do
|
||||||
Right out | versionoutput <- rstrip out -> do
|
case parseHledgerVersion out of
|
||||||
case parseHledgerVersion versionoutput of
|
Left _ -> p U ("couldn't parse " <> progname <> " --version: " <> rstrip out) >> exitFailure
|
||||||
Left _ -> p N (progname <> " --version shows: " <> rstrip out) >> return Nothing
|
Right pathbin -> do
|
||||||
Right bininfo@HledgerBinaryVersion{..} -> do
|
let pathversion = hbinVersionOutput pathbin
|
||||||
p Y versionoutput
|
if pathversion /= prognameandversion
|
||||||
|
then p N (unlines [
|
||||||
|
""
|
||||||
|
,"PATH hledger is " <> pathversion <> " (" <> exe <> ")"
|
||||||
|
,"this hledger is " <> prognameandversion
|
||||||
|
,msg
|
||||||
|
]) >> exitFailure
|
||||||
|
else p Y exe
|
||||||
|
|
||||||
-- It runs and --version output looks ok, continue
|
pdesc "has a system text encoding configured ?"
|
||||||
|
let encoding = localeEncoding -- the initial system encoding
|
||||||
|
if map toLower (show encoding) == "ascii"
|
||||||
|
then p N (show encoding <> " - please configure an encoding to handle non-ascii text")
|
||||||
|
else p Y (show encoding)
|
||||||
|
|
||||||
pdesc "is a native binary ?"
|
-- pdesc "can handle UTF-8 text ?"
|
||||||
case hbinArch of
|
-- let
|
||||||
Nothing -> p U $ "couldn't detect arch in --version output"
|
-- eAcuteUtf8 = B.pack [0xC3, 0xA9]
|
||||||
Just binarch | binarch /= arch -> p N $ "installed binary is for " <> binarch <> ", system is " <> arch
|
-- eAcuteLatin1 = B.pack [0xE9]
|
||||||
Just binarch -> p Y binarch
|
-- case T.decodeUtf8' eAcuteUtf8 of
|
||||||
|
-- Left _ -> p N "hledger's docs and examples use UTF-8"
|
||||||
|
-- Right t -> p Y (T.unpack t)
|
||||||
|
|
||||||
pdesc "is up to date ?"
|
-- pdesc "can report text decoding failures ?"
|
||||||
let binversion = hbinPackageVersion
|
-- i U (T.unpack $ T.decodeUtf8 eAcuteLatin1)
|
||||||
elatestversionnumstr <- getLatestHledgerVersion
|
|
||||||
case elatestversionnumstr of
|
|
||||||
Left e -> p U ("couldn't read " <> latestHledgerVersionUrlStr <> " , " <> e)
|
|
||||||
Right latestversionnumstr ->
|
|
||||||
case toVersion latestversionnumstr of
|
|
||||||
Nothing -> p U "couldn't parse latest version number"
|
|
||||||
Just latestversion -> p
|
|
||||||
(if binversion >= latestversion then Y else N)
|
|
||||||
(showVersion hbinPackageVersion <> " installed, latest is " <> latestversionnumstr)
|
|
||||||
|
|
||||||
pdesc "is same as the hledger running setup ?"
|
pdesc "has a user config file ? (optional)"
|
||||||
if prognameandversion == hbinVersionOutput
|
muf <- activeUserConfFile
|
||||||
then i Y ""
|
let
|
||||||
else i N prognameandversion
|
(ok, msg) = case muf of
|
||||||
|
Just f -> (Y, f)
|
||||||
|
Nothing -> (N, "")
|
||||||
|
i ok msg
|
||||||
|
|
||||||
return $ Just bininfo
|
pdesc "current directory has a local config ?"
|
||||||
|
mlf <- activeLocalConfFile
|
||||||
|
let
|
||||||
|
(ok, msg) = case mlf of
|
||||||
|
Just f -> (Y, f) -- <> if isJust muf then " (masking user config)" else "")
|
||||||
|
Nothing -> (N, "")
|
||||||
|
i ok msg
|
||||||
|
|
||||||
|
when (isJust muf && isJust mlf) $ do
|
||||||
|
pdesc "local config is masking user config ?"
|
||||||
|
i Y ""
|
||||||
|
|
||||||
|
if (isJust muf || isJust mlf) then do
|
||||||
|
pdesc "the config file is readable ?"
|
||||||
|
econf <- getConf def
|
||||||
|
case econf of
|
||||||
|
Left e -> p N (show e) >> return Nothing
|
||||||
|
Right (conf, f) -> do
|
||||||
|
p Y (fromMaybe "" f)
|
||||||
|
|
||||||
|
-- pdesc "common general options are configured ?"
|
||||||
|
-- --infer-costs"
|
||||||
|
-- print --explicit --show-costs"
|
||||||
|
|
||||||
|
return $ Just conf
|
||||||
|
else
|
||||||
|
return Nothing
|
||||||
|
|
||||||
------------------------------------------------------------------------------
|
------------------------------------------------------------------------------
|
||||||
|
|
||||||
setupConfig :: Version -> IO (Maybe Conf)
|
setupTerminal conf = do
|
||||||
setupConfig version = do
|
pgroup "terminal"
|
||||||
pgroup "config"
|
|
||||||
|
|
||||||
pdesc "hledger supports config files ?"
|
pdesc "the NO_COLOR variable is defined ?"
|
||||||
if (not $ supportsConfigFiles version)
|
mnocolor <- lookupEnv "NO_COLOR"
|
||||||
then p N "hledger 1.40+ needed" >> return Nothing
|
case mnocolor of
|
||||||
else do
|
Nothing -> i N ""
|
||||||
p Y ""
|
Just _ -> i Y ""
|
||||||
|
|
||||||
pdesc "a user config file exists ? (optional)"
|
meconfigcolor <- do
|
||||||
muf <- activeUserConfFile
|
pdesc "color is configured in the config file ?" -- we check the general section only
|
||||||
let
|
|
||||||
(ok, msg) = case muf of
|
|
||||||
Just f -> (Y, f)
|
|
||||||
Nothing -> (N, "")
|
|
||||||
i ok msg
|
|
||||||
|
|
||||||
pdesc "a local config file exists ?"
|
|
||||||
mlf <- activeLocalConfFile
|
|
||||||
let
|
|
||||||
(ok, msg) = case mlf of
|
|
||||||
Just f -> (Y, f) -- <> if isJust muf then " (masking user config)" else "")
|
|
||||||
Nothing -> (N, "")
|
|
||||||
i ok msg
|
|
||||||
|
|
||||||
when (isJust muf && isJust mlf) $ do
|
|
||||||
pdesc "local config is masking user config ?"
|
|
||||||
i Y ""
|
|
||||||
|
|
||||||
let mf = mlf <|> muf
|
|
||||||
case mf of
|
|
||||||
Nothing -> return Nothing
|
|
||||||
Just _ -> do
|
|
||||||
pdesc "hledger can read the config file ?"
|
|
||||||
-- Test config file readability, without requiring journal file readability, forward compatibly.
|
|
||||||
(exit, _, err) <- readProcessWithExitCode progname ["print", "-f-"] ""
|
|
||||||
case exit of
|
|
||||||
ExitFailure _ -> p N ("\n"<>err) >> return Nothing
|
|
||||||
ExitSuccess -> do
|
|
||||||
p Y ""
|
|
||||||
|
|
||||||
-- Read config file ourselves for closer inspection
|
|
||||||
-- econfsections <- readFile f <&> parseConf f . T.pack
|
|
||||||
econf <- getConf def -- safer; should agree with file detection above
|
|
||||||
case econf of
|
|
||||||
Left _ -> return Nothing
|
|
||||||
Right (conf, _) -> do
|
|
||||||
|
|
||||||
-- pdesc "common general options configured ?"
|
|
||||||
-- --pretty --ignore-assertions --infer-costs"
|
|
||||||
-- print --explicit --show-costs"
|
|
||||||
|
|
||||||
return $ Just conf
|
|
||||||
|
|
||||||
------------------------------------------------------------------------------
|
|
||||||
|
|
||||||
setupLocale = do
|
|
||||||
pgroup "locale"
|
|
||||||
|
|
||||||
-- pdesc "a locale is set to allow UTF-8 text ?"
|
|
||||||
-- pdesc "the locale allows decoding UTF-8 text ?"
|
|
||||||
pdesc "the locale allows handling UTF-8 text ?"
|
|
||||||
case T.decodeUtf8' $ B.pack [0xC3, 0xA9] of -- é
|
|
||||||
Left _ -> p N "hint: set a UTF-8 locale / enable UTF-8 text"
|
|
||||||
Right t -> p Y (T.unpack t)
|
|
||||||
--- XXX also recognise non-utf8 locales I guess, as long as some text encoding is supported
|
|
||||||
|
|
||||||
------------------------------------------------------------------------------
|
|
||||||
|
|
||||||
setupColor version conf = do
|
|
||||||
pgroup "color"
|
|
||||||
|
|
||||||
pdesc "hledger supports robust color output ?"
|
|
||||||
if not $ supportsColor version then p N "hledger 1.41+ needed"
|
|
||||||
else do
|
|
||||||
p Y ""
|
|
||||||
|
|
||||||
pdesc "the NO_COLOR variable is defined ?"
|
|
||||||
mnocolor <- lookupEnv "NO_COLOR"
|
|
||||||
case mnocolor of
|
|
||||||
Nothing -> i N ""
|
|
||||||
Just _ -> i Y ""
|
|
||||||
|
|
||||||
meconfigcolor <- do
|
|
||||||
pdesc "color is configured in the config file ?" -- we check the general section only
|
|
||||||
let
|
|
||||||
confgenargs = confLookup "general" conf
|
|
||||||
mcolorarg = find (\a -> any (`isPrefixOf` a) ["--color", "--colour"]) confgenargs
|
|
||||||
case mcolorarg of
|
|
||||||
Nothing -> i N "" >> return Nothing
|
|
||||||
Just a -> do
|
|
||||||
i Y a
|
|
||||||
let
|
|
||||||
arg = reverse $ takeWhile (`notElem` ['=',' ']) $ reverse a
|
|
||||||
return $ Just $ parseYNA arg
|
|
||||||
|
|
||||||
pdesc "hledger will use color when possible ?"
|
|
||||||
case (meconfigcolor, isJust mnocolor) of
|
|
||||||
(Just (Right Yes), _) -> i Y ""
|
|
||||||
(Just (Right No), _) -> i N ""
|
|
||||||
(_, True) -> i N ""
|
|
||||||
(_, False) -> i Y ""
|
|
||||||
|
|
||||||
------------------------------------------------------------------------------
|
|
||||||
|
|
||||||
setupPager version conf = do
|
|
||||||
pgroup "pager"
|
|
||||||
|
|
||||||
pdesc "hledger supports paged output ?"
|
|
||||||
if not $ supportsPager version then p N "hledger 1.41+ needed"
|
|
||||||
else do
|
|
||||||
p Y ""
|
|
||||||
|
|
||||||
pdesc "the PAGER variable is defined ?"
|
|
||||||
mv <- lookupEnv "PAGER"
|
|
||||||
case mv of
|
|
||||||
Nothing -> i N ""
|
|
||||||
Just v -> i Y v
|
|
||||||
|
|
||||||
pdesc "pager is configured in the config file ?"
|
|
||||||
let
|
let
|
||||||
confgenargs = confLookup "general" conf
|
confgenargs = confLookup "general" conf
|
||||||
mpagerarg = find ("--pager" `isPrefixOf`) confgenargs
|
mcolorarg = find (\a -> any (`isPrefixOf` a) ["--color", "--colour"]) confgenargs
|
||||||
meconfpager <- case mpagerarg of
|
case mcolorarg of
|
||||||
Nothing -> i N "" >> return Nothing
|
Nothing -> i N "" >> return Nothing
|
||||||
Just a -> do
|
Just a -> do
|
||||||
i Y a
|
i Y a
|
||||||
let arg = reverse $ takeWhile (`notElem` ['=',' ']) $ reverse a
|
let
|
||||||
|
arg = reverse $ takeWhile (`notElem` ['=',' ']) $ reverse a
|
||||||
return $ Just $ parseYNA arg
|
return $ Just $ parseYNA arg
|
||||||
|
|
||||||
pdesc "hledger will use a pager when needed ?"
|
pdesc "hledger will use color by default ?"
|
||||||
mpager <- findPager
|
case (meconfigcolor, isJust mnocolor) of
|
||||||
case mpager of
|
(Just (Right Yes), _) -> p Y ""
|
||||||
Nothing -> p N "no pager was found"
|
(Just (Right No), _) -> i N ""
|
||||||
Just pager ->
|
(_, True) -> i N ""
|
||||||
case meconfpager of
|
(_, False) -> p Y ""
|
||||||
Just (Right No) -> p N ""
|
|
||||||
_ -> do
|
|
||||||
p Y pager
|
|
||||||
|
|
||||||
when (map toLower (takeBaseName pager) == "more") $ do
|
pdesc "the PAGER variable is defined ?"
|
||||||
pdesc "the MORE variable is defined ?"
|
mv <- lookupEnv "PAGER"
|
||||||
mv <- lookupEnv "MORE"
|
case mv of
|
||||||
case mv of
|
Nothing -> i N ""
|
||||||
Nothing -> i N ""
|
Just v -> i Y v
|
||||||
Just v -> i Y v
|
|
||||||
|
|
||||||
when (map toLower (takeBaseName pager) == "less") $ do
|
pdesc "pager is configured in the config file ?"
|
||||||
pdesc "the LESS variable is defined ?"
|
let
|
||||||
mLESS <- lookupEnv "LESS"
|
confgenargs = confLookup "general" conf
|
||||||
case mLESS of
|
mpagerarg = find ("--pager" `isPrefixOf`) confgenargs
|
||||||
Nothing -> i N ""
|
meconfpager <- case mpagerarg of
|
||||||
Just _ -> i Y ""
|
Nothing -> i N "" >> return Nothing
|
||||||
|
Just a -> do
|
||||||
|
i Y a
|
||||||
|
let arg = reverse $ takeWhile (`notElem` ['=',' ']) $ reverse a
|
||||||
|
return $ Just $ parseYNA arg
|
||||||
|
|
||||||
pdesc "the HLEDGER_LESS variable is defined ?"
|
pdesc "hledger will use a pager when needed ?"
|
||||||
mHLEDGER_LESS <- lookupEnv "HLEDGER_LESS"
|
mpager <- findPager
|
||||||
case mHLEDGER_LESS of
|
case mpager of
|
||||||
Nothing -> i N ""
|
Nothing -> p N "no pager was found"
|
||||||
Just v -> i Y v
|
Just pager ->
|
||||||
|
case meconfpager of
|
||||||
|
Just (Right No) -> p N "disabled in config file"
|
||||||
|
_ -> do
|
||||||
|
p Y pager
|
||||||
|
|
||||||
when (isNothing mHLEDGER_LESS) $ do
|
when (map toLower (takeBaseName pager) == "more") $ do
|
||||||
pdesc "adjusting LESS variable for color etc. ?"
|
pdesc "the MORE variable is defined ?"
|
||||||
usecolor <- useColorOnStdout
|
mv <- lookupEnv "MORE"
|
||||||
i (if usecolor then Y else N) ""
|
case mv of
|
||||||
|
Nothing -> i N ""
|
||||||
|
Just v -> i Y v
|
||||||
|
|
||||||
|
when (map toLower (takeBaseName pager) == "less") $ do
|
||||||
|
pdesc "the LESS variable is defined ?"
|
||||||
|
mLESS <- lookupEnv "LESS"
|
||||||
|
case mLESS of
|
||||||
|
Nothing -> i N ""
|
||||||
|
Just _ -> i Y ""
|
||||||
|
|
||||||
|
pdesc "the HLEDGER_LESS variable is defined ?"
|
||||||
|
mHLEDGER_LESS <- lookupEnv "HLEDGER_LESS"
|
||||||
|
case mHLEDGER_LESS of
|
||||||
|
Nothing -> i N ""
|
||||||
|
Just v -> i Y v
|
||||||
|
|
||||||
|
when (isNothing mHLEDGER_LESS) $ do
|
||||||
|
pdesc "adjusting LESS variable for color etc. ?"
|
||||||
|
usecolor <- useColorOnStdout
|
||||||
|
i (if usecolor then Y else N) ""
|
||||||
|
|
||||||
|
pdesc "--pretty is enabled in the config file ?"
|
||||||
|
let
|
||||||
|
confgenargs = confLookup "general" conf
|
||||||
|
confpretty = isJust $ find ("--pretty" ==) confgenargs
|
||||||
|
if confpretty
|
||||||
|
then i Y "tables will use box-drawing characters"
|
||||||
|
else i N "tables will use ASCII characters"
|
||||||
|
|
||||||
|
pdesc "bash shell completions are installed ?" >> p U ""
|
||||||
|
pdesc "zsh shell completions are installed ?" >> p U ""
|
||||||
|
|
||||||
------------------------------------------------------------------------------
|
------------------------------------------------------------------------------
|
||||||
|
|
||||||
setupPretty version conf = do
|
setupJournal :: IO (Either String Journal)
|
||||||
pgroup "pretty"
|
setupJournal = do
|
||||||
|
|
||||||
pdesc "hledger supports pretty table borders ?"
|
|
||||||
if not $ supportsPretty version then p N "hledger 1.2+ needed"
|
|
||||||
else do
|
|
||||||
p Y ""
|
|
||||||
|
|
||||||
pdesc "--pretty is enabled in the config file ?"
|
|
||||||
let
|
|
||||||
confgenargs = confLookup "general" conf
|
|
||||||
confpretty = isJust $ find ("--pretty" ==) confgenargs
|
|
||||||
i (if confpretty then Y else N) ""
|
|
||||||
|
|
||||||
------------------------------------------------------------------------------
|
|
||||||
|
|
||||||
setupCompletions version = do
|
|
||||||
pgroup "completions"
|
|
||||||
|
|
||||||
pdesc "up to date bash completions available ?"
|
|
||||||
if not $ supportsBashCompletions version then p N "hledger 1.41+ needed"
|
|
||||||
else do
|
|
||||||
p Y ""
|
|
||||||
|
|
||||||
pdesc "bash shell completions are installed ?"
|
|
||||||
p U ""
|
|
||||||
|
|
||||||
------------------------------------------------------------------------------
|
|
||||||
|
|
||||||
setupJournal :: Version -> IO (Either String Journal)
|
|
||||||
setupJournal version = do
|
|
||||||
pgroup "journal"
|
pgroup "journal"
|
||||||
|
|
||||||
pdesc "a home directory journal file exists ?"
|
-- pdesc "a home directory journal file exists ?"
|
||||||
mh <- getHomeSafe
|
-- mh <- getHomeSafe
|
||||||
(ok,msg) <- case mh of
|
-- (ok,msg) <- case mh of
|
||||||
Just h -> do
|
-- Just h -> do
|
||||||
let f = h </> journalDefaultFilename
|
-- let f = h </> journalDefaultFilename
|
||||||
e <- doesFileExist f
|
-- e <- doesFileExist f
|
||||||
return (if e then Y else N, if e then f else "")
|
-- return (if e then Y else N, if e then f else "")
|
||||||
Nothing -> return (N, "")
|
-- Nothing -> return (N, "")
|
||||||
i ok msg
|
-- i ok msg
|
||||||
|
|
||||||
pdesc "the LEDGER_FILE variable is defined ?"
|
pdesc "the LEDGER_FILE variable is defined ?"
|
||||||
mf <- lookupEnv journalEnvVar
|
mf <- lookupEnv journalEnvVar
|
||||||
@ -444,139 +330,104 @@ setupJournal version = do
|
|||||||
-- pdesc "$LEDGER_FILE is masking home journal ?"
|
-- pdesc "$LEDGER_FILE is masking home journal ?"
|
||||||
-- i Y ""
|
-- i Y ""
|
||||||
|
|
||||||
pdesc "a default journal file exists ?"
|
pdesc "a default journal file is readable ?"
|
||||||
jfile <- defaultJournalPath
|
jfile <- defaultJournalPath
|
||||||
exists <- doesFileExist jfile
|
|
||||||
let (ok, msg) = (if exists then Y else N, if exists then jfile else "")
|
|
||||||
p ok msg
|
|
||||||
|
|
||||||
when exists $ do
|
-- let
|
||||||
|
-- args = concat [
|
||||||
when (os == "mingw32") $ do
|
-- ["print"],
|
||||||
pdesc "default journal file path is safe for Windows ?"
|
-- ["--ignore-assertions" | supportsIgnoreAssertions version],
|
||||||
|
-- ["--no-conf" | supportsConfigFiles version]
|
||||||
|
-- ]
|
||||||
|
-- (exit, _, err) <- readProcessWithExitCode progname args ""
|
||||||
|
-- XXX can this ignore assertions and config files, like the above ?
|
||||||
|
ej <- defaultJournalSafely
|
||||||
|
case ej of
|
||||||
|
Left e -> do
|
||||||
|
p N (jfile <> ":\n" <> show e)
|
||||||
|
return $ Left e
|
||||||
|
Right j@Journal{..} -> do
|
||||||
|
p Y jfile
|
||||||
|
|
||||||
|
pdesc "it includes additional files ?"
|
||||||
|
let numfiles = length jfiles
|
||||||
|
if numfiles > 1
|
||||||
|
then i Y (show $ numfiles - 1)
|
||||||
|
else i N ""
|
||||||
|
|
||||||
|
pdesc "all commodities are declared ?"
|
||||||
let
|
let
|
||||||
(ok, msg) =
|
numcommodities = length $ journalCommodities j
|
||||||
-- like ensureJournalFileExists:
|
undeclaredcommodities = journalCommoditiesUsed j \\ journalCommoditiesDeclared j
|
||||||
if isWindowsUnsafeDotPath jfile
|
if null undeclaredcommodities
|
||||||
then (N, "the file name ends with a dot, this is unsafe on Windows")
|
then p Y (show numcommodities)
|
||||||
else (Y, "")
|
else p N (show (length undeclaredcommodities) <> "; declaring helps set their precision")
|
||||||
p ok msg
|
|
||||||
|
|
||||||
pdesc "hledger can read the default journal ?"
|
let
|
||||||
-- Basic readability check: ignoring config files if it's hledger >=1.40,
|
accttypes = [Asset, Liability, Equity, Revenue, Expense, Cash, Conversion]
|
||||||
-- and balance assertions if possible (can't if it's hledger <=0.23),
|
typesdeclaredorinferred = nub $ M.elems jaccounttypes
|
||||||
-- try read the file (ie do the parseable and autobalanced checks pass).
|
typesnotfound = filter (not.(`elem` typesdeclaredorinferred)) accttypes
|
||||||
let
|
acctswithdeclaredorinferredtype = nub (M.keys jaccounttypes)
|
||||||
args = concat [
|
numaccts = length $ journalAccountNames j
|
||||||
["print"],
|
untypedaccts = journalAccountNames j \\ acctswithdeclaredorinferredtype
|
||||||
["--ignore-assertions" | supportsIgnoreAssertions version],
|
undeclaredaccts = journalAccountNamesUsed j \\ journalAccountNamesDeclared j
|
||||||
["--no-conf" | supportsConfigFiles version]
|
-- hasdeclaredaccts t = case M.lookup t jdeclaredaccounttypes of
|
||||||
]
|
-- Just (_ : _) -> True
|
||||||
(exit, _, err) <- readProcessWithExitCode progname args ""
|
-- _ -> False
|
||||||
case exit of
|
|
||||||
ExitSuccess -> p Y ""
|
|
||||||
ExitFailure _ -> p N ("\n"<>err)
|
|
||||||
|
|
||||||
-- Try to read the journal ourselves for closer inspection later.
|
-- pdesc "Asset accounts declared ?"
|
||||||
-- There's the possibility this could read the journal differently from the hledger in PATH,
|
-- if hasdeclaredaccts Asset then i Y "" else i N ""
|
||||||
-- if that's a different version.
|
|
||||||
-- Also we assume defaultJournalPath will detect the same file as the logic above.
|
|
||||||
defaultJournalSafely
|
|
||||||
|
|
||||||
|
-- pdesc "Liability accounts declared ?"
|
||||||
|
-- if hasdeclaredaccts Liability then i Y "" else i N ""
|
||||||
|
|
||||||
|
-- pdesc "Equity accounts declared ?"
|
||||||
|
-- if hasdeclaredaccts Equity then i Y "" else i N ""
|
||||||
|
|
||||||
|
-- pdesc "Revenue accounts declared ?"
|
||||||
|
-- if hasdeclaredaccts Revenue then i Y "" else i N ""
|
||||||
|
|
||||||
|
-- pdesc "Expense accounts declared ?"
|
||||||
|
-- if hasdeclaredaccts Expense then i Y "" else i N ""
|
||||||
|
|
||||||
|
-- pdesc "Cash accounts declared ?"
|
||||||
|
-- if hasdeclaredaccts Cash then i Y "" else i N ""
|
||||||
|
|
||||||
|
-- pdesc "Conversion accounts declared ?"
|
||||||
|
-- if hasdeclaredaccts Conversion then i Y "" else i N "" -- ("--infer-equity will use a default conversion account name")
|
||||||
|
|
||||||
|
-- XXX hard to detect accounts where type was inferred from name
|
||||||
|
-- unless arealltypesdeclared $ do
|
||||||
|
-- let
|
||||||
|
-- acctswithdeclaredtype = concat (M.elems jdeclaredaccounttypes)
|
||||||
|
-- acctswithinferredtype = acctswithdeclaredorinferredtype \\ acctswithdeclaredtype
|
||||||
|
-- arealltypesdeclared = all hasdeclaredaccts accttypes
|
||||||
|
-- typesinferredfromnames =
|
||||||
|
-- if arealltypesdeclared then []
|
||||||
|
-- else sort $ nub $ catMaybes $ map (flip M.lookup jaccounttypes) acctswithinferredtype
|
||||||
|
-- pdesc "types detected from account names ?"
|
||||||
|
-- if null typesinferredfromnames then i N "" else i Y (concatMap show typesinferredfromnames)
|
||||||
|
|
||||||
|
pdesc "all accounts are declared ?"
|
||||||
|
if null undeclaredaccts then i Y (show numaccts) else i N (show (length undeclaredaccts) <> " undeclared")
|
||||||
|
|
||||||
|
pdesc "all accounts have types ?"
|
||||||
|
if null untypedaccts then i Y "" else i N (show (length untypedaccts) <> " untyped")
|
||||||
|
|
||||||
|
pdesc "accounts of each type were detected ?"
|
||||||
|
if null typesnotfound
|
||||||
|
then p Y (concatMap show accttypes)
|
||||||
|
else p N (concatMap show typesnotfound <> "not found; type: queries, bs/cf/is reports may not work")
|
||||||
|
|
||||||
|
return $ Right j
|
||||||
|
|
||||||
------------------------------------------------------------------------------
|
------------------------------------------------------------------------------
|
||||||
|
|
||||||
setupAccounts version mj = do
|
setupStrictness = do
|
||||||
pgroup "accounts"
|
pgroup "strictness"
|
||||||
|
pdesc "balance assertions are checked by default ?"
|
||||||
pdesc "hledger supports account directives ?"
|
-- ignore-assertions
|
||||||
if not $ supportsAccountDirective version
|
pdesc "commodities/conversions/accounts are checked by default ?"
|
||||||
then p N "hledger 1.9+ needed"
|
|
||||||
else do
|
|
||||||
p Y ""
|
|
||||||
|
|
||||||
pdesc "hledger supports all account types ?" -- (ALERXCV)
|
|
||||||
if not $ supportsConversionAccountType version
|
|
||||||
then p N "hledger 1.25+ needed"
|
|
||||||
else do
|
|
||||||
p Y ""
|
|
||||||
|
|
||||||
case mj of
|
|
||||||
Nothing -> return ()
|
|
||||||
Just j@Journal{..} -> do
|
|
||||||
|
|
||||||
let
|
|
||||||
accttypes = [Asset, Liability, Equity, Revenue, Expense, Cash, Conversion]
|
|
||||||
typesdeclaredorinferred = nub $ M.elems jaccounttypes
|
|
||||||
acctswithdeclaredorinferredtype = nub (M.keys jaccounttypes)
|
|
||||||
untypedaccts = journalAccountNames j \\ acctswithdeclaredorinferredtype
|
|
||||||
undeclaredaccts = journalAccountNamesUsed j \\ journalAccountNamesDeclared j
|
|
||||||
hasdeclaredaccts t = case M.lookup t jdeclaredaccounttypes of
|
|
||||||
Just (_ : _) -> True
|
|
||||||
_ -> False
|
|
||||||
|
|
||||||
pdesc "Asset accounts declared ?"
|
|
||||||
if hasdeclaredaccts Asset then i Y "" else i N ""
|
|
||||||
|
|
||||||
pdesc "Liability accounts declared ?"
|
|
||||||
if hasdeclaredaccts Liability then i Y "" else i N ""
|
|
||||||
|
|
||||||
pdesc "Equity accounts declared ?"
|
|
||||||
if hasdeclaredaccts Equity then i Y "" else i N ""
|
|
||||||
|
|
||||||
pdesc "Revenue accounts declared ?"
|
|
||||||
if hasdeclaredaccts Revenue then i Y "" else i N ""
|
|
||||||
|
|
||||||
pdesc "Expense accounts declared ?"
|
|
||||||
if hasdeclaredaccts Expense then i Y "" else i N ""
|
|
||||||
|
|
||||||
pdesc "Cash accounts declared ?"
|
|
||||||
if hasdeclaredaccts Cash then i Y "" else i N ""
|
|
||||||
|
|
||||||
pdesc "Conversion accounts declared ?"
|
|
||||||
if hasdeclaredaccts Conversion then i Y "" else i N "" -- ("--infer-equity will use a default conversion account name")
|
|
||||||
|
|
||||||
-- XXX hard to detect accounts where type was inferred from name
|
|
||||||
-- unless arealltypesdeclared $ do
|
|
||||||
-- let
|
|
||||||
-- acctswithdeclaredtype = concat (M.elems jdeclaredaccounttypes)
|
|
||||||
-- acctswithinferredtype = acctswithdeclaredorinferredtype \\ acctswithdeclaredtype
|
|
||||||
-- arealltypesdeclared = all hasdeclaredaccts accttypes
|
|
||||||
-- typesinferredfromnames =
|
|
||||||
-- if arealltypesdeclared then []
|
|
||||||
-- else sort $ nub $ catMaybes $ map (flip M.lookup jaccounttypes) acctswithinferredtype
|
|
||||||
-- pdesc "types detected from account names ?"
|
|
||||||
-- if null typesinferredfromnames then i N "" else i Y (concatMap show typesinferredfromnames)
|
|
||||||
|
|
||||||
pdesc "an account of each type was detected ?"
|
|
||||||
if all (`elem` typesdeclaredorinferred) accttypes
|
|
||||||
then p Y ""
|
|
||||||
else p N "reports like bs, cf, is may be empty"
|
|
||||||
|
|
||||||
pdesc "all accounts have types ?"
|
|
||||||
if null untypedaccts then i Y "" else i N ""
|
|
||||||
|
|
||||||
pdesc "all accounts are declared ?"
|
|
||||||
if null undeclaredaccts then i Y "" else i N ""
|
|
||||||
|
|
||||||
------------------------------------------------------------------------------
|
|
||||||
|
|
||||||
setupCommodities version mj = do
|
|
||||||
pgroup "commodities"
|
|
||||||
|
|
||||||
pdesc "hledger supports commodity directives ?"
|
|
||||||
if not $ supportsCommodityDirective version
|
|
||||||
then p N "hledger 1.0+ needed"
|
|
||||||
else do
|
|
||||||
p Y ""
|
|
||||||
|
|
||||||
case mj of
|
|
||||||
Nothing -> return ()
|
|
||||||
Just j -> do
|
|
||||||
|
|
||||||
pdesc "all commodities are declared ?"
|
|
||||||
let undeclaredcommodities = journalCommoditiesUsed j \\ journalCommoditiesDeclared j
|
|
||||||
if null undeclaredcommodities then p Y "" else p N "declaring helps set their precision"
|
|
||||||
|
|
||||||
------------------------------------------------------------------------------
|
------------------------------------------------------------------------------
|
||||||
|
|
||||||
@ -586,6 +437,21 @@ setupCommodities version mj = do
|
|||||||
|
|
||||||
------------------------------------------------------------------------------
|
------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
-- Test a hledger version for support of various features.
|
||||||
|
ver >=! str = ver >= (fromJust $ toVersion str)
|
||||||
|
supportsIgnoreAssertions = (>=! "0.24") -- --ignore-assertions (2014)
|
||||||
|
supportsCommodityDirective = (>=! "1.0") -- commodity directive (2016)
|
||||||
|
supportsPretty = (>=! "1.2") -- --pretty, to use box-drawing characters (2017)
|
||||||
|
supportsAccountDirective = (>=! "1.9") -- account directive (2018)
|
||||||
|
supportsAccountTypes = (>=! "1.13") -- ALERX account types, type: tag (2019)
|
||||||
|
supportsCashAccountType = (>=! "1.19") -- C/Cash account type (2020)
|
||||||
|
supportsBasicColor = (>=! "1.19") -- basic color detection/control (2020)
|
||||||
|
supportsConversionAccountType = (>=! "1.25") -- V/Conversion account type, accounts --types (2022)
|
||||||
|
supportsConfigFiles = (>=! "1.40") -- config files (2024)
|
||||||
|
supportsColor = (>=! "1.41") -- robust color detection/control (2024)
|
||||||
|
supportsPager = (>=! "1.41") -- use a pager for all output (2024)
|
||||||
|
supportsBashCompletions = (>=! "1.41") -- up to date bash shell completions (2024)
|
||||||
|
|
||||||
-- yes, no, unknown
|
-- yes, no, unknown
|
||||||
data YNU = Y | N | U deriving (Eq)
|
data YNU = Y | N | U deriving (Eq)
|
||||||
|
|
||||||
@ -599,12 +465,12 @@ bad = bold' . brightRed'
|
|||||||
instance Show YNU where
|
instance Show YNU where
|
||||||
show Y = good "yes" -- ✅ apple emojis - won't work everywhere
|
show Y = good "yes" -- ✅ apple emojis - won't work everywhere
|
||||||
show N = bad " no" -- ❌
|
show N = bad " no" -- ❌
|
||||||
show U = warning " ? "
|
show U = warning " ?"
|
||||||
|
|
||||||
-- Show status, in blue/yellow if supported.
|
-- Show status, in blue/yellow if supported.
|
||||||
showInfo Y = neutral "yes" -- ℹ️
|
showInfo Y = neutral "yes" -- ℹ️
|
||||||
showInfo N = neutral " no" -- ℹ️
|
showInfo N = neutral " no" -- ℹ️
|
||||||
showInfo U = warning " ? "
|
showInfo U = warning " ?"
|
||||||
|
|
||||||
-- | Print a test's pass or fail status, as "yes" or "no" or "",
|
-- | Print a test's pass or fail status, as "yes" or "no" or "",
|
||||||
-- in green/red if supported, and the (possibly empty) provided message.
|
-- in green/red if supported, and the (possibly empty) provided message.
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user