feat:run: run multiple commands efficiently, as a script or at a REPL (Dmitry Astapov, #2323)
This commit is contained in:
parent
9597210556
commit
d84050ec05
@ -450,6 +450,7 @@ src/hledger/
|
|||||||
Register.md
|
Register.md
|
||||||
Rewrite.md
|
Rewrite.md
|
||||||
Roi.md
|
Roi.md
|
||||||
|
Run.md
|
||||||
Stats.md
|
Stats.md
|
||||||
Tags.md
|
Tags.md
|
||||||
Test.md
|
Test.md
|
||||||
|
|||||||
@ -118,6 +118,7 @@ import Hledger
|
|||||||
import Hledger.Cli.CliOptions
|
import Hledger.Cli.CliOptions
|
||||||
import Hledger.Cli.Conf
|
import Hledger.Cli.Conf
|
||||||
import Hledger.Cli.Commands
|
import Hledger.Cli.Commands
|
||||||
|
import Hledger.Cli.Commands.Run
|
||||||
import Hledger.Cli.DocFiles
|
import Hledger.Cli.DocFiles
|
||||||
import Hledger.Cli.Utils
|
import Hledger.Cli.Utils
|
||||||
import Hledger.Cli.Version
|
import Hledger.Cli.Version
|
||||||
@ -417,7 +418,10 @@ main = withGhcDebug' $ do
|
|||||||
ensureJournalFileExists . NE.head =<< journalFilePathFromOpts opts
|
ensureJournalFileExists . NE.head =<< journalFilePathFromOpts opts
|
||||||
withJournalDo opts (cmdaction 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
|
| otherwise -> withJournalDo opts $ cmdaction opts
|
||||||
|
|
||||||
-- 6.6. external addon command found - run it,
|
-- 6.6. external addon command found - run it,
|
||||||
|
|||||||
@ -46,6 +46,7 @@ module Hledger.Cli.Commands (
|
|||||||
,module Hledger.Cli.Commands.Print
|
,module Hledger.Cli.Commands.Print
|
||||||
,module Hledger.Cli.Commands.Register
|
,module Hledger.Cli.Commands.Register
|
||||||
,module Hledger.Cli.Commands.Rewrite
|
,module Hledger.Cli.Commands.Rewrite
|
||||||
|
,module Hledger.Cli.Commands.Run
|
||||||
,module Hledger.Cli.Commands.Stats
|
,module Hledger.Cli.Commands.Stats
|
||||||
,module Hledger.Cli.Commands.Tags
|
,module Hledger.Cli.Commands.Tags
|
||||||
)
|
)
|
||||||
@ -91,6 +92,7 @@ import Hledger.Cli.Commands.Print
|
|||||||
import Hledger.Cli.Commands.Register
|
import Hledger.Cli.Commands.Register
|
||||||
import Hledger.Cli.Commands.Rewrite
|
import Hledger.Cli.Commands.Rewrite
|
||||||
import Hledger.Cli.Commands.Roi
|
import Hledger.Cli.Commands.Roi
|
||||||
|
import Hledger.Cli.Commands.Run
|
||||||
import Hledger.Cli.Commands.Stats
|
import Hledger.Cli.Commands.Stats
|
||||||
import Hledger.Cli.Commands.Tags
|
import Hledger.Cli.Commands.Tags
|
||||||
import Hledger.Cli.Utils (tests_Cli_Utils)
|
import Hledger.Cli.Utils (tests_Cli_Utils)
|
||||||
@ -126,6 +128,7 @@ builtinCommands = [
|
|||||||
,(registermode , register)
|
,(registermode , register)
|
||||||
,(rewritemode , rewrite)
|
,(rewritemode , rewrite)
|
||||||
,(roimode , roi)
|
,(roimode , roi)
|
||||||
|
,(runmode , run')
|
||||||
,(statsmode , stats)
|
,(statsmode , stats)
|
||||||
,(tagsmode , tags)
|
,(tagsmode , tags)
|
||||||
,(testmode , testcmd)
|
,(testmode , testcmd)
|
||||||
@ -253,6 +256,7 @@ commandsList progversion othercmds =
|
|||||||
," balance (bal) show balance changes, end balances, gains, budgets.."
|
," balance (bal) show balance changes, end balances, gains, budgets.."
|
||||||
,"+lots show a commodity's lots" -- hledger-lots
|
,"+lots show a commodity's lots" -- hledger-lots
|
||||||
," roi show return on investments"
|
," roi show return on investments"
|
||||||
|
," run run multiple commands from a file (EXPERIMENTAL)"
|
||||||
,""
|
,""
|
||||||
-----------------------------------------80-------------------------------------
|
-----------------------------------------80-------------------------------------
|
||||||
,bold' "CHARTS (bar charts, line graphs..)"
|
,bold' "CHARTS (bar charts, line graphs..)"
|
||||||
|
|||||||
112
hledger/Hledger/Cli/Commands/Run.hs
Normal file
112
hledger/Hledger/Cli/Commands/Run.hs
Normal file
@ -0,0 +1,112 @@
|
|||||||
|
{-|
|
||||||
|
|
||||||
|
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.Directory (doesFileExist)
|
||||||
|
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 args = dbg1 "args" $ listofstringopt "args" rawopts
|
||||||
|
case args of
|
||||||
|
[] -> runREPL findBuiltinCommand j
|
||||||
|
maybeFile:_ -> do
|
||||||
|
-- Check if arguments could be interpreted as files.
|
||||||
|
-- If not, assume that they are files
|
||||||
|
isFile <- doesFileExist maybeFile
|
||||||
|
case isFile of
|
||||||
|
True -> runFromFiles findBuiltinCommand args j
|
||||||
|
False -> runFromArgs findBuiltinCommand args 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 . parseCommand)
|
||||||
|
|
||||||
|
runFromArgs :: (String -> Maybe (Mode RawOpts, CliOpts -> Journal -> IO ())) -> [String] -> Journal -> IO ()
|
||||||
|
runFromArgs findBuiltinCommand args j = do
|
||||||
|
-- read commands from all the inputfiles
|
||||||
|
let commands = dbg1 "commands from args" $ splitAtElement "--" args
|
||||||
|
forM_ commands (runCommand findBuiltinCommand j)
|
||||||
|
|
||||||
|
-- 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
|
||||||
|
parseCommand :: String -> [String]
|
||||||
|
parseCommand line =
|
||||||
|
-- # begins a comment, ignore everything after #
|
||||||
|
takeWhile (not. ((Just '#')==) . headMay) $ words' (strip line)
|
||||||
|
|
||||||
|
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 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 $ parseCommand input
|
||||||
|
loop
|
||||||
62
hledger/Hledger/Cli/Commands/Run.md
Normal file
62
hledger/Hledger/Cli/Commands/Run.md
Normal file
@ -0,0 +1,62 @@
|
|||||||
|
## run
|
||||||
|
|
||||||
|
Runs a sequence of hledger commands on the same input file(s), either interactively or as a script.
|
||||||
|
|
||||||
|
This command is EXPERIMENTAL and syntax could change in the future.
|
||||||
|
|
||||||
|
```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.
|
||||||
|
|
||||||
|
"run" has three ways of invocation:
|
||||||
|
- when invoked without arguments, it start a read-eval-print loop (REPL) where you can enter commands interactively. To exit REPL, use "exit" or "quit", or send EOF.
|
||||||
|
|
||||||
|
- when file names are given to "run", it will read commands from these files, in order.
|
||||||
|
|
||||||
|
- lastly, commands could be specified directly on the command line. All commands (including the very first one) should be preceded by argument "--"
|
||||||
|
|
||||||
|
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 <text>` 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. If this complains about "binary `hledger run` not found", use `/usr/bin/env -S hledger run`.
|
||||||
|
|
||||||
|
### Caveats:
|
||||||
|
|
||||||
|
- If you meant to provide file name as an argument, but made a mistake and a gave file name that does not exist, "run" will attempt to interpret it as a command.
|
||||||
|
|
||||||
|
- Numeric flags like `-3` do not work, use long form `--depth 3`
|
||||||
|
|
||||||
|
### Examples:
|
||||||
|
|
||||||
|
To start the REPL:
|
||||||
|
```cli
|
||||||
|
hledger run
|
||||||
|
```
|
||||||
|
|
||||||
|
To provide commands on the command line, separate them with `--`:
|
||||||
|
```cli
|
||||||
|
hledger run -f some.journal -- balance assets --depth 2 -- balance liabilities --depth 3 --transpose
|
||||||
|
```
|
||||||
|
|
||||||
|
To provide commands in the file, as a runnable scripts:
|
||||||
|
```cli
|
||||||
|
#!/usr/bin/env -S hledger run
|
||||||
|
echo "List of accounts"
|
||||||
|
accounts
|
||||||
|
|
||||||
|
echo "Assets"
|
||||||
|
balance assets --depth 2
|
||||||
|
|
||||||
|
echo "Liabilities"
|
||||||
|
balance liabilities --depth 3 --transpose
|
||||||
|
```
|
||||||
|
|
||||||
67
hledger/Hledger/Cli/Commands/Run.txt
Normal file
67
hledger/Hledger/Cli/Commands/Run.txt
Normal file
@ -0,0 +1,67 @@
|
|||||||
|
run
|
||||||
|
|
||||||
|
Runs a sequence of hledger commands on the same input file(s), either
|
||||||
|
interactively or as a script.
|
||||||
|
|
||||||
|
This command is EXPERIMENTAL and syntax could change in the future.
|
||||||
|
|
||||||
|
Flags:
|
||||||
|
no command-specific flags
|
||||||
|
|
||||||
|
The commands will run more quickly than if run individually, because the
|
||||||
|
input files would be parsed only once.
|
||||||
|
|
||||||
|
"run" has three ways of invocation: - when invoked without arguments, it
|
||||||
|
start a read-eval-print loop (REPL) where you can enter commands
|
||||||
|
interactively. To exit REPL, use "exit" or "quit", or send EOF.
|
||||||
|
|
||||||
|
- when file names are given to "run", it will read commands from these
|
||||||
|
files, in order.
|
||||||
|
|
||||||
|
- lastly, commands could be specified directly on the command line.
|
||||||
|
All commands (including the very first one) should be preceded by
|
||||||
|
argument "--"
|
||||||
|
|
||||||
|
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 <text> 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. If this complains about "binary hledger run
|
||||||
|
not found", use /usr/bin/env -S hledger run.
|
||||||
|
|
||||||
|
Caveats:
|
||||||
|
|
||||||
|
- If you meant to provide file name as an argument, but made a mistake
|
||||||
|
and a gave file name that does not exist, "run" will attempt to
|
||||||
|
interpret it as a command.
|
||||||
|
|
||||||
|
- Numeric flags like -3 do not work, use long form --depth 3
|
||||||
|
|
||||||
|
Examples:
|
||||||
|
|
||||||
|
To start the REPL:
|
||||||
|
|
||||||
|
hledger run
|
||||||
|
|
||||||
|
To provide commands on the command line, separate them with --:
|
||||||
|
|
||||||
|
hledger run -f some.journal -- balance assets --depth 2 -- balance liabilities --depth 3 --transpose
|
||||||
|
|
||||||
|
To provide commands in the file, as a runnable scripts:
|
||||||
|
|
||||||
|
#!/usr/bin/env -S hledger run
|
||||||
|
echo "List of accounts"
|
||||||
|
accounts
|
||||||
|
|
||||||
|
echo "Assets"
|
||||||
|
balance assets --depth 2
|
||||||
|
|
||||||
|
echo "Liabilities"
|
||||||
|
balance liabilities --depth 3 --transpose
|
||||||
@ -84,6 +84,7 @@ extra-source-files:
|
|||||||
Hledger/Cli/Commands/Register.txt
|
Hledger/Cli/Commands/Register.txt
|
||||||
Hledger/Cli/Commands/Rewrite.txt
|
Hledger/Cli/Commands/Rewrite.txt
|
||||||
Hledger/Cli/Commands/Roi.txt
|
Hledger/Cli/Commands/Roi.txt
|
||||||
|
Hledger/Cli/Commands/Run.txt
|
||||||
Hledger/Cli/Commands/Stats.txt
|
Hledger/Cli/Commands/Stats.txt
|
||||||
Hledger/Cli/Commands/Tags.txt
|
Hledger/Cli/Commands/Tags.txt
|
||||||
Hledger/Cli/Commands/Test.txt
|
Hledger/Cli/Commands/Test.txt
|
||||||
@ -140,6 +141,7 @@ library
|
|||||||
Hledger.Cli.Commands.Register
|
Hledger.Cli.Commands.Register
|
||||||
Hledger.Cli.Commands.Rewrite
|
Hledger.Cli.Commands.Rewrite
|
||||||
Hledger.Cli.Commands.Roi
|
Hledger.Cli.Commands.Roi
|
||||||
|
Hledger.Cli.Commands.Run
|
||||||
Hledger.Cli.Commands.Stats
|
Hledger.Cli.Commands.Stats
|
||||||
Hledger.Cli.Commands.Tags
|
Hledger.Cli.Commands.Tags
|
||||||
Hledger.Cli.CompoundBalanceCommand
|
Hledger.Cli.CompoundBalanceCommand
|
||||||
|
|||||||
@ -83,6 +83,7 @@ extra-source-files:
|
|||||||
- Hledger/Cli/Commands/Register.txt
|
- Hledger/Cli/Commands/Register.txt
|
||||||
- Hledger/Cli/Commands/Rewrite.txt
|
- Hledger/Cli/Commands/Rewrite.txt
|
||||||
- Hledger/Cli/Commands/Roi.txt
|
- Hledger/Cli/Commands/Roi.txt
|
||||||
|
- Hledger/Cli/Commands/Run.txt
|
||||||
- Hledger/Cli/Commands/Stats.txt
|
- Hledger/Cli/Commands/Stats.txt
|
||||||
- Hledger/Cli/Commands/Tags.txt
|
- Hledger/Cli/Commands/Tags.txt
|
||||||
- Hledger/Cli/Commands/Test.txt
|
- Hledger/Cli/Commands/Test.txt
|
||||||
@ -201,6 +202,7 @@ library:
|
|||||||
- Hledger.Cli.Commands.Register
|
- Hledger.Cli.Commands.Register
|
||||||
- Hledger.Cli.Commands.Rewrite
|
- Hledger.Cli.Commands.Rewrite
|
||||||
- Hledger.Cli.Commands.Roi
|
- Hledger.Cli.Commands.Roi
|
||||||
|
- Hledger.Cli.Commands.Run
|
||||||
- Hledger.Cli.Commands.Stats
|
- Hledger.Cli.Commands.Stats
|
||||||
- Hledger.Cli.Commands.Tags
|
- Hledger.Cli.Commands.Tags
|
||||||
- Hledger.Cli.CompoundBalanceCommand
|
- Hledger.Cli.CompoundBalanceCommand
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user