run: properly retrieve cached files when there are nested "run"s

This commit is contained in:
Dmitry Astapov 2025-02-26 19:06:58 +00:00 committed by Simon Michael
parent e665bf124d
commit b5f5c39d48

View File

@ -15,8 +15,6 @@ module Hledger.Cli.Commands.Run (
,run' ,run'
) where ) where
import qualified Data.List.NonEmpty as NE
import qualified Data.Map.Strict as Map import qualified Data.Map.Strict as Map
import Data.Semigroup (sconcat) import Data.Semigroup (sconcat)
import qualified Data.Text as T import qualified Data.Text as T
@ -61,36 +59,34 @@ run' _opts _j = return ()
-- | The actual run command. -- | The actual run command.
run :: (String -> Maybe (Mode RawOpts, CliOpts -> Journal -> IO ())) -> CliOpts -> Journal -> IO () run :: (String -> Maybe (Mode RawOpts, CliOpts -> Journal -> IO ())) -> CliOpts -> Journal -> IO ()
run findBuiltinCommand CliOpts{rawopts_=rawopts} j = do run findBuiltinCommand CliOpts{rawopts_=rawopts} j = do
-- Add current journal to cache, so that any commands that dont specify `-f` could fetch it from there
addJournalToCache j defaultJournalKey
let args = dbg1 "args" $ listofstringopt "args" rawopts let args = dbg1 "args" $ listofstringopt "args" rawopts
case args of if args == []
[] -> runREPL findBuiltinCommand then runREPL j findBuiltinCommand
maybeFile:_ -> do else do
-- Check if arguments could be interpreted as files. -- Check if arguments could be interpreted as files.
-- If not, assume that they are files -- If not, assume that they are commands specified directly on the command line
isFile <- doesFileExist maybeFile allAreFiles <- and <$> mapM (doesFileExist . snd . splitReaderPrefix) args
case isFile of case allAreFiles of
True -> runFromFiles findBuiltinCommand args True -> runFromFiles j findBuiltinCommand args
False -> runFromArgs findBuiltinCommand args False -> runFromArgs j findBuiltinCommand args
-- | Run commands from files given to "run". -- | Run commands from files given to "run".
runFromFiles :: (String -> Maybe (Mode RawOpts, CliOpts -> Journal -> IO ())) -> [String] -> IO () runFromFiles :: Journal -> (String -> Maybe (Mode RawOpts, CliOpts -> Journal -> IO ())) -> [String] -> IO ()
runFromFiles findBuiltinCommand inputfiles = do runFromFiles defaultJrnl findBuiltinCommand inputfiles = do
dbg1IO "inputfiles" inputfiles dbg1IO "inputfiles" inputfiles
-- read commands from all the inputfiles -- read commands from all the inputfiles
commands <- (flip concatMapM) inputfiles $ \f -> do commands <- (flip concatMapM) inputfiles $ \f -> do
dbg1IO "reading commands" f dbg1IO "reading commands" f
lines . T.unpack <$> T.readFile f lines . T.unpack <$> T.readFile f
forM_ commands (runCommand findBuiltinCommand . parseCommand) forM_ commands (runCommand defaultJrnl findBuiltinCommand . parseCommand)
-- | Run commands from command line arguments given to "run". -- | Run commands from command line arguments given to "run".
runFromArgs :: (String -> Maybe (Mode RawOpts, CliOpts -> Journal -> IO ())) -> [String] -> IO () runFromArgs :: Journal -> (String -> Maybe (Mode RawOpts, CliOpts -> Journal -> IO ())) -> [String] -> IO ()
runFromArgs findBuiltinCommand args = do runFromArgs defaultJrnl findBuiltinCommand args = do
-- read commands from all the inputfiles -- read commands from all the inputfiles
let commands = dbg1 "commands from args" $ splitAtElement "--" args let commands = dbg1 "commands from args" $ splitAtElement "--" args
forM_ commands (runCommand findBuiltinCommand) forM_ commands (runCommand defaultJrnl findBuiltinCommand)
-- When commands are passed on the command line, shell will parse them for us -- When commands are passed on the command line, shell will parse them for us
-- When commands are read from file, we need to split the line into command and arguments -- When commands are read from file, we need to split the line into command and arguments
@ -100,8 +96,8 @@ parseCommand line =
takeWhile (not. ((Just '#')==) . headMay) $ words' (strip line) takeWhile (not. ((Just '#')==) . headMay) $ words' (strip line)
-- | Take a single command line (from file, or REPL, or "--"-surrounded block of the args), and run it. -- | Take a single command line (from file, or REPL, or "--"-surrounded block of the args), and run it.
runCommand :: (String -> Maybe (Mode RawOpts, CliOpts -> Journal -> IO ())) -> [String] -> IO () runCommand :: Journal -> (String -> Maybe (Mode RawOpts, CliOpts -> Journal -> IO ())) -> [String] -> IO ()
runCommand findBuiltinCommand cmdline = do runCommand defaultJrnl findBuiltinCommand cmdline = do
dbg1IO "runCommand for" cmdline dbg1IO "runCommand for" cmdline
case cmdline of case cmdline of
"echo":args -> putStrLn $ unwords $ args "echo":args -> putStrLn $ unwords $ args
@ -116,12 +112,12 @@ runCommand findBuiltinCommand cmdline = do
args' <- expandArgsAt args args' <- expandArgsAt args
dbg1IO "runCommand final args" (cmdname,args') dbg1IO "runCommand final args" (cmdname,args')
opts <- getHledgerCliOpts' cmdmode args' opts <- getHledgerCliOpts' cmdmode args'
withJournalCached opts (cmdaction' opts) withJournalCached defaultJrnl opts (cmdaction' opts)
[] -> return () [] -> return ()
-- | Run an interactive REPL. -- | Run an interactive REPL.
runREPL :: (String -> Maybe (Mode RawOpts, CliOpts -> Journal -> IO ())) -> IO () runREPL :: Journal -> (String -> Maybe (Mode RawOpts, CliOpts -> Journal -> IO ())) -> IO ()
runREPL findBuiltinCommand = do runREPL defaultJrnl findBuiltinCommand = do
putStrLn "Enter hledger commands. To exit, enter 'quit' or 'exit', or send EOF." putStrLn "Enter hledger commands. To exit, enter 'quit' or 'exit', or send EOF."
runInputT defaultSettings loop runInputT defaultSettings loop
where where
@ -133,7 +129,7 @@ runREPL findBuiltinCommand = do
Just "quit" -> return () Just "quit" -> return ()
Just "exit" -> return () Just "exit" -> return ()
Just input -> do Just input -> do
liftIO $ runCommand findBuiltinCommand $ parseCommand input liftIO $ runCommand defaultJrnl findBuiltinCommand $ parseCommand input
loop loop
-- | Cache of all journals that have been read by commands given to "run", -- | Cache of all journals that have been read by commands given to "run",
@ -142,27 +138,13 @@ journalCache :: MVar (Map.Map String Journal)
journalCache = unsafePerformIO $ newMVar Map.empty journalCache = unsafePerformIO $ newMVar Map.empty
{-# NOINLINE journalCache #-} {-# NOINLINE journalCache #-}
-- | Key used to cache the journal given in the arguments to 'run' itself.
defaultJournalKey :: String
defaultJournalKey = "journal specified in the args of run"
addJournalToCache :: Journal -> String -> IO ()
addJournalToCache j key = modifyMVar_ journalCache $ \cache ->
case Map.lookup key cache of
Just _ -> return cache
Nothing -> return $ Map.insert key j cache
-- | Similar to `withJournal`, but uses caches all the journals it reads. -- | Similar to `withJournal`, but uses caches all the journals it reads.
withJournalCached :: CliOpts -> (Journal -> IO ()) -> IO () withJournalCached :: Journal -> CliOpts -> (Journal -> IO ()) -> IO ()
withJournalCached cliopts cmd = do withJournalCached defaultJrnl cliopts cmd = do
mbjournalpaths <- journalFilePathFromOptsNoDefault cliopts mbjournalpaths <- journalFilePathFromOptsNoDefault cliopts
let journalpaths = case mbjournalpaths of j <- case mbjournalpaths of
Nothing -> Nothing -> return defaultJrnl -- use the journal given to the "run" itself
-- If the command does not have -f args, it will use the default journal Just journalpaths -> journalTransform cliopts . sconcat <$> mapM (readAndCacheJournalFile (inputopts_ cliopts)) journalpaths
-- that was supplied to the "run" itself
NE.fromList [defaultJournalKey]
Just paths -> paths
j <- journalTransform cliopts . sconcat <$> mapM (readAndCacheJournalFile (inputopts_ cliopts)) journalpaths
cmd j cmd j
where where
-- | Read a journal file, caching it if it has not been read before. -- | Read a journal file, caching it if it has not been read before.