Skip to content

Commit

Permalink
run: initial implementation
Browse files Browse the repository at this point in the history
  • Loading branch information
adept committed Feb 11, 2025
1 parent b12d7cb commit 74cd6d9
Show file tree
Hide file tree
Showing 8 changed files with 190 additions and 1 deletion.
1 change: 1 addition & 0 deletions doc/FILES.md
Original file line number Diff line number Diff line change
Expand Up @@ -450,6 +450,7 @@ src/hledger/
Register.md
Rewrite.md
Roi.md
Run.md
Stats.md
Tags.md
Test.md
Expand Down
6 changes: 5 additions & 1 deletion hledger/Hledger/Cli.hs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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,
Expand Down
4 changes: 4 additions & 0 deletions hledger/Hledger/Cli/Commands.hs
Original file line number Diff line number Diff line change
Expand Up @@ -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
)
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -126,6 +128,7 @@ builtinCommands = [
,(registermode , register)
,(rewritemode , rewrite)
,(roimode , roi)
,(runmode , run')
,(statsmode , stats)
,(tagsmode , tags)
,(testmode , testcmd)
Expand Down Expand Up @@ -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..)"
Expand Down
92 changes: 92 additions & 0 deletions hledger/Hledger/Cli/Commands/Run.hs
Original file line number Diff line number Diff line change
@@ -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
42 changes: 42 additions & 0 deletions hledger/Hledger/Cli/Commands/Run.md
Original file line number Diff line number Diff line change
@@ -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 <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.

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

42 changes: 42 additions & 0 deletions hledger/Hledger/Cli/Commands/Run.txt
Original file line number Diff line number Diff line change
@@ -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 <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.

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
2 changes: 2 additions & 0 deletions hledger/hledger.cabal
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down
2 changes: 2 additions & 0 deletions hledger/package.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down

0 comments on commit 74cd6d9

Please sign in to comment.