From 74cd6d93f2a1e44a328a76d11bb137e0838a750f Mon Sep 17 00:00:00 2001 From: Dmitry Astapov Date: Tue, 11 Feb 2025 12:47:14 +0000 Subject: [PATCH] run: initial implementation --- doc/FILES.md | 1 + hledger/Hledger/Cli.hs | 6 +- hledger/Hledger/Cli/Commands.hs | 4 ++ hledger/Hledger/Cli/Commands/Run.hs | 92 ++++++++++++++++++++++++++++ hledger/Hledger/Cli/Commands/Run.md | 42 +++++++++++++ hledger/Hledger/Cli/Commands/Run.txt | 42 +++++++++++++ hledger/hledger.cabal | 2 + hledger/package.yaml | 2 + 8 files changed, 190 insertions(+), 1 deletion(-) create mode 100644 hledger/Hledger/Cli/Commands/Run.hs create mode 100644 hledger/Hledger/Cli/Commands/Run.md create mode 100644 hledger/Hledger/Cli/Commands/Run.txt diff --git a/doc/FILES.md b/doc/FILES.md index 9bb3c0832a26..7879c00c4908 100644 --- a/doc/FILES.md +++ b/doc/FILES.md @@ -450,6 +450,7 @@ src/hledger/ Register.md Rewrite.md Roi.md + Run.md Stats.md Tags.md Test.md diff --git a/hledger/Hledger/Cli.hs b/hledger/Hledger/Cli.hs index e224e596369d..975911cffe58 100644 --- a/hledger/Hledger/Cli.hs +++ b/hledger/Hledger/Cli.hs @@ -118,6 +118,7 @@ import Hledger import Hledger.Cli.CliOptions import Hledger.Cli.Conf import Hledger.Cli.Commands +import Hledger.Cli.Commands.Run import Hledger.Cli.DocFiles import Hledger.Cli.Utils import Hledger.Cli.Version @@ -417,7 +418,10 @@ main = withGhcDebug' $ do ensureJournalFileExists . NE.head =<< journalFilePathFromOpts opts withJournalDo opts (cmdaction opts) - -- 6.5.4. all other builtin commands - read the journal and if successful run the command with it + -- 6.5.4. run needs findBuiltinCommands passed to it to avoid circular dependency in the code + | cmdname == "run" -> do withJournalDo opts $ Hledger.Cli.Commands.Run.run findBuiltinCommand opts + + -- 6.5.5. all other builtin commands - read the journal and if successful run the command with it | otherwise -> withJournalDo opts $ cmdaction opts -- 6.6. external addon command found - run it, diff --git a/hledger/Hledger/Cli/Commands.hs b/hledger/Hledger/Cli/Commands.hs index 6c5fd0a861d9..1848c29d782b 100644 --- a/hledger/Hledger/Cli/Commands.hs +++ b/hledger/Hledger/Cli/Commands.hs @@ -46,6 +46,7 @@ module Hledger.Cli.Commands ( ,module Hledger.Cli.Commands.Print ,module Hledger.Cli.Commands.Register ,module Hledger.Cli.Commands.Rewrite + ,module Hledger.Cli.Commands.Run ,module Hledger.Cli.Commands.Stats ,module Hledger.Cli.Commands.Tags ) @@ -91,6 +92,7 @@ import Hledger.Cli.Commands.Print import Hledger.Cli.Commands.Register import Hledger.Cli.Commands.Rewrite import Hledger.Cli.Commands.Roi +import Hledger.Cli.Commands.Run import Hledger.Cli.Commands.Stats import Hledger.Cli.Commands.Tags import Hledger.Cli.Utils (tests_Cli_Utils) @@ -126,6 +128,7 @@ builtinCommands = [ ,(registermode , register) ,(rewritemode , rewrite) ,(roimode , roi) + ,(runmode , run') ,(statsmode , stats) ,(tagsmode , tags) ,(testmode , testcmd) @@ -253,6 +256,7 @@ commandsList progversion othercmds = ," balance (bal) show balance changes, end balances, gains, budgets.." ,"+lots show a commodity's lots" -- hledger-lots ," roi show return on investments" + ," run run multiple commands from a file" ,"" -----------------------------------------80------------------------------------- ,bold' "CHARTS (bar charts, line graphs..)" diff --git a/hledger/Hledger/Cli/Commands/Run.hs b/hledger/Hledger/Cli/Commands/Run.hs new file mode 100644 index 000000000000..dc49808147f8 --- /dev/null +++ b/hledger/Hledger/Cli/Commands/Run.hs @@ -0,0 +1,92 @@ +{-| + +The @run@ command allows you to run multiple commands via REPL or from the supplied file(s). + +-} + +{-# LANGUAGE MultiWayIf #-} +{-# LANGUAGE OverloadedStrings #-} +{-# LANGUAGE ScopedTypeVariables #-} +{-# LANGUAGE TemplateHaskell #-} + +module Hledger.Cli.Commands.Run ( + runmode + ,run + ,run' +) where + +import qualified Data.Text as T +import qualified Data.Text.IO as T +import System.Console.CmdArgs.Explicit as C ( Mode ) +import Hledger +import Hledger.Cli.CliOptions + +import Control.Monad (forM_) +import Control.Monad.IO.Class (liftIO) +import Control.Monad.Extra (concatMapM) + +import System.Console.Haskeline + +import Safe (headMay) + +-- | Command line options for this command. +runmode = hledgerCommandMode + $(embedFileRelative "Hledger/Cli/Commands/Run.txt") + ( + [] + ) + cligeneralflagsgroups1 + hiddenflags + ([], Just $ argsFlag "[COMMANDS_FILE1 COMMANDS_FILE2 ...]") + +-- | The fake run command introduced to break circular dependency +run' :: CliOpts -> Journal -> IO () +run' _opts _j = return () + +-- | The actual run command. +run :: (String -> Maybe (Mode RawOpts, CliOpts -> Journal -> IO ())) -> CliOpts -> Journal -> IO () +run findBuiltinCommand CliOpts{rawopts_=rawopts} j = do + let inputfiles = listofstringopt "args" rawopts + case inputfiles of + [] -> runREPL findBuiltinCommand j + _ -> runFromFiles findBuiltinCommand inputfiles j + +runFromFiles :: (String -> Maybe (Mode RawOpts, CliOpts -> Journal -> IO ())) -> [String] -> Journal -> IO () +runFromFiles findBuiltinCommand inputfiles j = do + dbg1IO "inputfiles" inputfiles + -- read commands from all the inputfiles + commands <- (flip concatMapM) inputfiles $ \f -> do + dbg1IO "reading commands" f + lines . T.unpack <$> T.readFile f + + forM_ commands (runCommand findBuiltinCommand j) + +runCommand :: (String -> Maybe (Mode RawOpts, CliOpts -> Journal -> IO ())) -> Journal -> String -> IO () +runCommand findBuiltinCommand j cmdline = do + dbg1IO "running command" cmdline + -- # begins a comment, ignore everything after # + case takeWhile (not. ((Just '#')==) . headMay) $ words' (strip cmdline) of + "echo":args -> putStrLn $ unwords $ args + cmdname:args -> + case findBuiltinCommand cmdname of + Nothing -> putStrLn $ unwords (cmdname:args) + Just (cmdmode,cmdaction) -> do + opts <- getHledgerCliOpts' cmdmode args + cmdaction opts j + [] -> return () + +runREPL :: (String -> Maybe (Mode RawOpts, CliOpts -> Journal -> IO ())) -> Journal -> IO () +runREPL findBuiltinCommand j = do + putStrLn "Enter hledger commands, or 'help' for help." + runInputT defaultSettings loop + where + loop :: InputT IO () + loop = do + minput <- getInputLine "% " + case minput of + Nothing -> return () + Just "quit" -> return () + Just "exit" -> return () + Just input -> do + liftIO $ runCommand findBuiltinCommand j input + loop \ No newline at end of file diff --git a/hledger/Hledger/Cli/Commands/Run.md b/hledger/Hledger/Cli/Commands/Run.md new file mode 100644 index 000000000000..74495e0c3fb1 --- /dev/null +++ b/hledger/Hledger/Cli/Commands/Run.md @@ -0,0 +1,42 @@ +## run + +Runs a sequnce of hledger commands on the same input file(s), either interactively or as a script. + +```flags +Flags: +no command-specific flags +``` + +The commands will run more quickly than if run individually, because the input files would be parsed only once. + +When file names are given to "run", it will read commands from these files, in order. + +With no arguments, "run" will start a read-eval-print loop (REPL) +where you can enter commands interactively. To exit REPL, use "exit" +or "quit", or send EOF. + +Syntax of the commands (either in the file, or in REPL) is intentionally simple: +- each line is a single hledger command +- lines that can't be interpreted as hledger commands are printed out as-is +- empty lines are skipped +- everything after `#` is considered to be a comment and will be ignored, and will not be printed out +- `echo ` will print out text, even if it could be recognized as a hledger command + +You can use single quotes or double quotes to quote aguments that need quoting. + +You can use `#!/usr/bin/env hledger run` in the first line of the file to make it a runnable script. + +For example: + +```cli +#!/usr/bin/env hledger run +echo "List of accounts" +accounts + +echo "Assets" +balance assets --depth 2 + +echo "Liabilities" +balance liabilities --depth 3 --transpose +``` + diff --git a/hledger/Hledger/Cli/Commands/Run.txt b/hledger/Hledger/Cli/Commands/Run.txt new file mode 100644 index 000000000000..7c3df2b57403 --- /dev/null +++ b/hledger/Hledger/Cli/Commands/Run.txt @@ -0,0 +1,42 @@ +run + +Runs a sequnce of hledger commands on the same input file(s), either +interactively or as a script. + +Flags: +no command-specific flags + +The commands will run more quickly than if run individually, because the +input files would be parsed only once. + +When file names are given to "run", it will read commands from these +files, in order. + +With no arguments, "run" will start a read-eval-print loop (REPL) where +you can enter commands interactively. To exit REPL, use "exit" or +"quit", or send EOF. + +Syntax of the commands (either in the file, or in REPL) is intentionally +simple: - each line is a single hledger command - lines that can't be +interpreted as hledger commands are printed out as-is - empty lines are +skipped - everything after # is considered to be a comment and will be +ignored, and will not be printed out - echo will print out text, +even if it could be recognized as a hledger command + +You can use single quotes or double quotes to quote aguments that need +quoting. + +You can use #!/usr/bin/env hledger run in the first line of the file to +make it a runnable script. + +For example: + +#!/usr/bin/env hledger run +echo "List of accounts" +accounts + +echo "Assets" +balance assets --depth 2 + +echo "Liabilities" +balance liabilities --depth 3 --transpose diff --git a/hledger/hledger.cabal b/hledger/hledger.cabal index d51c9b02fed3..5d027ec803c9 100644 --- a/hledger/hledger.cabal +++ b/hledger/hledger.cabal @@ -84,6 +84,7 @@ extra-source-files: Hledger/Cli/Commands/Register.txt Hledger/Cli/Commands/Rewrite.txt Hledger/Cli/Commands/Roi.txt + Hledger/Cli/Commands/Run.txt Hledger/Cli/Commands/Stats.txt Hledger/Cli/Commands/Tags.txt Hledger/Cli/Commands/Test.txt @@ -140,6 +141,7 @@ library Hledger.Cli.Commands.Register Hledger.Cli.Commands.Rewrite Hledger.Cli.Commands.Roi + Hledger.Cli.Commands.Run Hledger.Cli.Commands.Stats Hledger.Cli.Commands.Tags Hledger.Cli.CompoundBalanceCommand diff --git a/hledger/package.yaml b/hledger/package.yaml index 585528ec941e..2c877f34f32c 100644 --- a/hledger/package.yaml +++ b/hledger/package.yaml @@ -83,6 +83,7 @@ extra-source-files: - Hledger/Cli/Commands/Register.txt - Hledger/Cli/Commands/Rewrite.txt - Hledger/Cli/Commands/Roi.txt +- Hledger/Cli/Commands/Run.txt - Hledger/Cli/Commands/Stats.txt - Hledger/Cli/Commands/Tags.txt - Hledger/Cli/Commands/Test.txt @@ -201,6 +202,7 @@ library: - Hledger.Cli.Commands.Register - Hledger.Cli.Commands.Rewrite - Hledger.Cli.Commands.Roi + - Hledger.Cli.Commands.Run - Hledger.Cli.Commands.Stats - Hledger.Cli.Commands.Tags - Hledger.Cli.CompoundBalanceCommand