diff --git a/hledger-lib/Hledger/Read.hs b/hledger-lib/Hledger/Read.hs index ecc98d204..014d43e62 100644 --- a/hledger-lib/Hledger/Read.hs +++ b/hledger-lib/Hledger/Read.hs @@ -150,7 +150,7 @@ import Data.Text (Text) import Data.Text qualified as T import Data.Text.IO qualified as T import Data.Time (Day) -import Safe (headDef, headMay) +import Safe (headDef) import System.Directory (doesFileExist) import System.Environment (getEnv) import System.FilePath ((<.>), (), splitDirectories, splitFileName, takeFileName) @@ -223,18 +223,8 @@ defaultJournalPath = do homedir <- fromMaybe "" <$> getHomeSafe let defaultfile = homedir journalDefaultFilename return defaultfile - else do - -- If it contains glob metacharacters, expand the pattern and error if no matches. - -- Otherwise just expand ~ and return the path, even if the file doesn't exist yet. - let hasGlobChars = any (`elem` p) ("*?[{" :: [Char]) - if hasGlobChars - then do - mf <- headMay <$> expandGlob "." p `C.catch` (\(_::C.IOException) -> return []) - case mf of - Just f -> return f - Nothing -> error' $ "LEDGER_FILE glob pattern \"" <> p <> "\" matched no files" - else - expandPath "." p + else + expandPathOrGlob "." p -- | Like defaultJournalPath, but return an error message instead of raising an error. defaultJournalPathSafely :: IO (Either String String) diff --git a/hledger-lib/Hledger/Utils/IO.hs b/hledger-lib/Hledger/Utils/IO.hs index 1227cd719..8826dcfe8 100644 --- a/hledger-lib/Hledger/Utils/IO.hs +++ b/hledger-lib/Hledger/Utils/IO.hs @@ -40,6 +40,7 @@ module Hledger.Utils.IO ( expandHomePath, expandPath, expandGlob, + expandPathOrGlob, sortByModTime, openFileOrStdin, readFileOrStdinPortably, @@ -423,6 +424,26 @@ expandPath curdir p = (if isRelative p then (curdir ) else id) <$> expandHome expandGlob :: FilePath -> FilePath -> IO [FilePath] expandGlob curdir p = expandPath curdir p >>= glob <&> sort -- PARTIAL: +-- | Like expandPath, but if the path contains glob metacharacters (* ? [ {), +-- treats it as a glob pattern and expands it, returning the first match. +-- Raises an error if the glob pattern matches no files. +-- If the path contains no glob metacharacters, just expands ~ and returns the path, +-- even if the file doesn't exist yet. +-- This is useful for options like -f and LEDGER_FILE that should: +-- - accept non-existent files (for commands like add/import that create them) +-- - expand glob patterns and error if they don't match anything +expandPathOrGlob :: FilePath -> FilePath -> IO FilePath +expandPathOrGlob curdir p = do + let hasGlobChars = any (`elem` p) ("*?[{" :: [Char]) + if hasGlobChars + then do + matches <- expandGlob curdir p `catch` (\(_::IOException) -> return []) + case headMay matches of + Just f -> return f + Nothing -> error' $ "glob pattern \"" <> p <> "\" matched no files" + else + expandPath curdir p + -- | Given a list of existing file paths, sort them by modification time (from oldest to newest). sortByModTime :: [FilePath] -> IO [FilePath] sortByModTime fs = do diff --git a/hledger/Hledger/Cli/CliOptions.hs b/hledger/Hledger/Cli/CliOptions.hs index 511c7cfd2..a90603288 100644 --- a/hledger/Hledger/Cli/CliOptions.hs +++ b/hledger/Hledger/Cli/CliOptions.hs @@ -755,7 +755,7 @@ journalFilePathFromOptsNoDefault opts = do expandPathPreservingPrefix :: FilePath -> PrefixedFilePath -> IO PrefixedFilePath expandPathPreservingPrefix d prefixedf = do let (p,f) = splitReaderPrefix prefixedf - f' <- expandPath d f + f' <- expandPathOrGlob d f return $ case p of Just p' -> (show p') ++ ":" ++ f' Nothing -> f'