imp: -f now errors if given a glob matching no files, like LEDGER_FILE

Previously LEDGER_FILE=foo hledger add did, but hledger -f foo add didn't.
Now they both consistently will error if given a glob
(a path contining [, {, *, or ?) that matches nothing,
rather than auto-creating a file with a glob-like name.

Hledger.Utils.IO:
expandPathOrGlob
This commit is contained in:
Simon Michael 2025-12-31 00:02:22 -10:00
parent 88f6c16dd5
commit e7d7c49562
3 changed files with 25 additions and 14 deletions

View File

@ -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)

View File

@ -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

View File

@ -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'