From 0add2e90dba26f785a5bc46dfe24abeb5039d812 Mon Sep 17 00:00:00 2001 From: Simon Michael Date: Fri, 11 Jul 2025 14:00:38 -0700 Subject: [PATCH] imp: include: glob patterns always exclude the current file Eg include **/*.journal is less likely no complain --- hledger-lib/Hledger/Read/JournalReader.hs | 16 ++++++++++------ hledger/hledger.m4.md | 9 ++++++--- 2 files changed, 16 insertions(+), 9 deletions(-) diff --git a/hledger-lib/Hledger/Read/JournalReader.hs b/hledger-lib/Hledger/Read/JournalReader.hs index 725c7b1fb..ee473f153 100644 --- a/hledger-lib/Hledger/Read/JournalReader.hs +++ b/hledger-lib/Hledger/Read/JournalReader.hs @@ -333,7 +333,7 @@ includedirectivep = do then pure filepaths else customFailure $ parseErrorAt parseroff $ "No files were matched by file pattern: " ++ fileglobpattern - -- Find the files matched by a glob pattern, using filepattern. + -- Find the files matched by a glob pattern, if any, using filepattern. -- Uses the current parse context for detecting the current directory and for error messages. -- This one also ignores all dotted directories (anything under .git/, foo/.secret/, etc.) getFilePaths2 :: MonadIO m => Int -> SourcePos -> FilePath -> JournalParser m [FilePath] @@ -348,19 +348,23 @@ includedirectivep = do realparentfilepath <- liftIO $ canonicalizePath parentfilepath -- Follow a symlink. If the path is already absolute, the operation never fails. let cwd = takeDirectory realparentfilepath - -- find all matched files, in lexicographic order (the order ls would normally show them) + -- Find all matched files, in lexicographic order (the order ls would normally show them). + -- (This might include the current file.) filepaths <- liftIO $ - dbg6 "include: matched files" - . map (cwd ) + map (cwd ) -- . sort -- XXX needed ? <$> getDirectoryFilesIgnore cwd [expandedglob] ["**/.*/**"] - -- throw an error if no files matched + -- Throw an error if no files (not even the current file) were matched. when (null filepaths) $ customFailure $ parseErrorAt off $ "No files were matched by file pattern: " ++ globpattern - pure filepaths + -- If the current file was matched, exclude it now. + let filepaths' = filter (/= realparentfilepath) filepaths + dbg6IO "include: matched files (excluding current file)" filepaths' + + pure filepaths' -- Parse the given included file (and any deeper includes, recursively) -- as if it was inlined in the current (parent) file. diff --git a/hledger/hledger.m4.md b/hledger/hledger.m4.md index fb77c5744..be17d1ca5 100644 --- a/hledger/hledger.m4.md +++ b/hledger/hledger.m4.md @@ -2528,10 +2528,13 @@ Also, the path may have a file type prefix to force a specific file format (as described in [Data formats](#data-formats)): `include timedot:~/notes/2023*.md`. The path may contain [glob patterns] to match multiple files, eg: `include *.journal`. +Note, the current file is always excluded from the matched paths. +(Though include cycles are still possible, and will be reported as an error.) + +The special glob pattern `**` matches any number of path components. +It's useful for searching subdirectories. +Eg to include all .journal files below the current directory: `include **/*.journal`. -The special glob pattern `**/` matches any number of directory parts. -This is not robust; it can hang, and `**/*.journal` is rejected. -But this will work: `include */**/*.journal` (find all .journal files below the current directory). [glob patterns]: https://hackage.haskell.org/package/Glob-0.9.2/docs/System-FilePath-Glob.html#v:compile