Skip to content

Commit c897e95

Browse files
authored
Merge pull request #37 from apauley/output-usability
Improve Command Output
2 parents bec4f4c + 38e75eb commit c897e95

File tree

8 files changed

+96
-49
lines changed

8 files changed

+96
-49
lines changed

.travis.yml

+3-2
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,8 @@ install:
4141
- stack setup --interleaved-output
4242
- stack exec -- ghc --version
4343

44-
- stack build --interleaved-output --test --bench --no-run-tests --no-run-benchmarks --pedantic
44+
- stack build --interleaved-output --pedantic
45+
- stack build --interleaved-output --test --bench --no-run-tests --no-run-benchmarks
4546
- stack install
4647
- which hledger-flow
4748
- hledger-flow version
@@ -53,7 +54,7 @@ install:
5354
- git clone --recurse-submodules https://github.com/apauley/hledger-flow-example.git $HOME/hledger-flow-example
5455

5556
script:
56-
- stack test --interleaved-output --pedantic
57+
- stack test --interleaved-output
5758
- if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then git checkout HEAD package.yaml; fi
5859
- hledger-flow import --show-options --verbose $HOME/hledger-flow-example
5960
- hledger-flow report --show-options --verbose $HOME/hledger-flow-example

ChangeLog.md

+4
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
# Changelog for [hledger-flow](https://github.com/apauley/hledger-flow)
22

3+
## 0.11.2
4+
5+
- Improved display of external process output
6+
37
## 0.11.1.2
48

59
- Exit with an error code when any external script fails - https://github.com/apauley/hledger-flow/issues/28

README.org

+15-2
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,17 @@ It started when I realized that the scripts I wrote while playing around
3838
with the ideas in [[https://github.com/adept/full-fledged-hledger/wiki][adept's Full-fledged Hledger]] aren't really specific to
3939
my own finances, and can be shared.
4040

41+
* How do I install it?
42+
:PROPERTIES:
43+
:CUSTOM_ID: how-do-i-install-it
44+
:END:
45+
46+
The easiest way to get it running is to download [[https://github.com/apauley/hledger-flow/releases][the latest release]]
47+
for your OS, and copy the =hledger-flow= executable to a directory in your PATH.
48+
Then just run it and see what it tells you to do.
49+
50+
You can also compile it yourself by following the [[#build-instructions][build instructions]].
51+
4152
* Overview of the Basic Workflow
4253
:PROPERTIES:
4354
:CUSTOM_ID: overview-of-the-basic-workflow
@@ -524,7 +535,7 @@ script to do anything that the =preprocess= script would have done.
524535
Save your =construct= script in the account directory, e.g.
525536
=import/john/mybank/savings/construct=.
526537

527-
=hledger-flow= will call your =construct= script with 4 positional
538+
=hledger-flow= will call your =construct= script with 5 positional
528539
parameters:
529540

530541
1. The path to the input statement, e.g.
@@ -540,9 +551,11 @@ Your =construct= script is expected to:
540551
- generate your own =hledger= journal transactions
541552
- be idempotent. Running =construct= multiple times on the same files
542553
should produce the same result.
543-
- send all output to =stdout=. =hledger-flow= will pipe your output into
554+
- send all journals to =stdout=. =hledger-flow= will pipe your standard output into
544555
=hledger= which will format it and save it to an output file.
545556

557+
You can still use =stderr= in your construct script for any other output that you may want to see.
558+
546559
*** Stability of this Feature
547560
:PROPERTIES:
548561
:CUSTOM_ID: stability-of-this-feature-4

package.yaml

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
name: hledger-flow
2-
version: 0.11.1.2
2+
version: 0.11.2.0
33
synopsis: An hledger workflow focusing on automated statement import and classification.
44
category: Finance, Console
55
license: GPL-3

src/Hledger/Flow/CSVImport.hs

+13-15
Original file line numberDiff line numberDiff line change
@@ -85,13 +85,11 @@ preprocess :: ImportOptions -> TChan FlowTypes.LogMessage -> FilePath -> Line ->
8585
preprocess opts ch script bank account owner src = do
8686
let csvOut = changePathAndExtension "2-preprocessed" "csv" src
8787
mktree $ directory csvOut
88-
let script' = format fp script :: Text
89-
let action = (parAwareProc opts) script' [format fp src, format fp csvOut, lineToText bank, lineToText account, lineToText owner] empty
88+
let args = [format fp src, format fp csvOut, lineToText bank, lineToText account, lineToText owner]
9089
let relScript = relativeToBase opts script
9190
let relSrc = relativeToBase opts src
92-
let msg = format ("executing '"%fp%"' on '"%fp%"'") relScript relSrc
93-
((_, stdOut, _), _) <- timeAndExitOnErr opts ch msg action
94-
channelOut ch stdOut
91+
let cmdLabel = format ("executing '"%fp%"' on '"%fp%"'") relScript relSrc
92+
_ <- timeAndExitOnErr opts ch cmdLabel channelOut channelErr (parAwareProc opts) (format fp script, args, empty)
9593
return csvOut
9694

9795
hledgerImport :: ImportOptions -> TChan FlowTypes.LogMessage -> FilePath -> FilePath -> IO FilePath
@@ -110,10 +108,9 @@ hledgerImport' opts ch importDirs csvSrc journalOut = do
110108
Just rf -> do
111109
let relRules = relativeToBase opts rf
112110
let hledger = format fp $ FlowTypes.hlPath . hledgerInfo $ opts :: Text
113-
let action = (parAwareProc opts) hledger ["print", "--rules-file", format fp rf, "--file", format fp csvSrc, "--output-file", format fp journalOut] empty
114-
let msg = format ("importing '"%fp%"' using rules file '"%fp%"'") relCSV relRules
115-
((_, stdOut, _), _) <- timeAndExitOnErr opts ch msg action
116-
channelOut ch stdOut
111+
let args = ["print", "--rules-file", format fp rf, "--file", format fp csvSrc, "--output-file", format fp journalOut]
112+
let cmdLabel = format ("importing '"%fp%"' using rules file '"%fp%"'") relCSV relRules
113+
_ <- timeAndExitOnErr opts ch cmdLabel channelOut channelErr (parAwareProc opts) (hledger, args, empty)
117114
return journalOut
118115
Nothing ->
119116
do
@@ -157,12 +154,13 @@ statementSpecificRulesFiles csvSrc importDirs = do
157154
customConstruct :: ImportOptions -> TChan FlowTypes.LogMessage -> FilePath -> Line -> Line -> Line -> FilePath -> FilePath -> IO FilePath
158155
customConstruct opts ch constructScript bank account owner csvSrc journalOut = do
159156
let script = format fp constructScript :: Text
160-
let importOut = inproc script [format fp csvSrc, "-", lineToText bank, lineToText account, lineToText owner] empty
161-
let hledger = format fp $ FlowTypes.hlPath . hledgerInfo $ opts :: Text
162-
let action = (parAwareProc opts) hledger ["print", "--ignore-assertions", "--file", "-", "--output-file", format fp journalOut] importOut
163157
let relScript = relativeToBase opts constructScript
158+
let constructArgs = [format fp csvSrc, "-", lineToText bank, lineToText account, lineToText owner]
159+
let constructCmdText = format ("Running: "%fp%" "%s) relScript (showCmdArgs constructArgs)
160+
let stdLines = inprocWithErrFun (channelErrLn ch) (script, constructArgs, empty)
161+
let hledger = format fp $ FlowTypes.hlPath . hledgerInfo $ opts :: Text
162+
let args = ["print", "--ignore-assertions", "--file", "-", "--output-file", format fp journalOut]
164163
let relSrc = relativeToBase opts csvSrc
165-
let msg = format ("executing '"%fp%"' on '"%fp%"'") relScript relSrc
166-
((_, stdOut, _), _) <- timeAndExitOnErr opts ch msg action
167-
channelOut ch stdOut
164+
let cmdLabel = format ("executing '"%fp%"' on '"%fp%"'") relScript relSrc
165+
_ <- timeAndExitOnErr' opts ch cmdLabel [constructCmdText] channelOut channelErr (parAwareProc opts) (hledger, args, stdLines)
168166
return journalOut

src/Hledger/Flow/Common.hs

+53-24
Original file line numberDiff line numberDiff line change
@@ -9,12 +9,14 @@ module Hledger.Flow.Common
99
, showCmdArgs
1010
, consoleChannelLoop
1111
, terminateChannelLoop
12+
, dummyLogger
1213
, channelOut, channelOutLn
1314
, channelErr, channelErrLn
1415
, errExit
1516
, logVerbose
16-
, timeAndExitOnErr
17+
, timeAndExitOnErr, timeAndExitOnErr'
1718
, parAwareProc
19+
, inprocWithErrFun
1820
, verboseTestFile
1921
, relativeToBase
2022
, relativeToBase'
@@ -55,7 +57,7 @@ import qualified Data.Map.Strict as Map
5557
import Data.Time.LocalTime
5658

5759
import Data.Function (on)
58-
import qualified Data.List as List (nub, sort, sortBy, groupBy)
60+
import qualified Data.List as List (nub, null, sort, sortBy, groupBy)
5961
import Data.Ord (comparing)
6062
import Hledger.Flow.Types
6163
import qualified Hledger.Flow.Import.Types as IT
@@ -103,6 +105,9 @@ showCmdArgs args = T.intercalate " " (map escapeArg args)
103105
escapeArg :: Text -> Text
104106
escapeArg a = if (T.count " " a > 0) then "'" <> a <> "'" else a
105107

108+
dummyLogger :: TChan LogMessage -> Text -> IO ()
109+
dummyLogger _ _ = return ()
110+
106111
channelOut :: TChan LogMessage -> Text -> IO ()
107112
channelOut ch txt = atomically $ writeTChan ch $ StdOut txt
108113

@@ -153,42 +158,66 @@ terminateChannelLoop ch = atomically $ writeTChan ch Terminate
153158
logVerbose :: HasVerbosity o => o -> TChan LogMessage -> Text -> IO ()
154159
logVerbose opts ch msg = if (verbose opts) then logToChannel ch msg else return ()
155160

156-
logTimedAction :: HasVerbosity o => o -> TChan LogMessage -> Text -> IO FullOutput -> IO FullTimedOutput
157-
logTimedAction opts ch msg action = do
158-
logVerbose opts ch $ format ("Begin: "%s) msg
159-
timed@((ec, _, _), diff) <- time action
160-
logVerbose opts ch $ format ("End: "%s%" "%s%" ("%s%")") msg (repr ec) (repr diff)
161+
descriptiveOutput :: Text -> Text -> Text
162+
descriptiveOutput outputLabel outTxt = do
163+
if not (T.null outTxt)
164+
then format (s%":\n"%s%"\n") outputLabel outTxt
165+
else ""
166+
167+
logTimedAction :: HasVerbosity o => o -> TChan LogMessage -> Text -> [Text]
168+
-> (TChan LogMessage -> Text -> IO ()) -> (TChan LogMessage -> Text -> IO ())
169+
-> IO FullOutput
170+
-> IO FullTimedOutput
171+
logTimedAction opts ch cmdLabel extraCmdLabels stdoutLogger stderrLogger action = do
172+
logVerbose opts ch $ format ("Begin: "%s) cmdLabel
173+
if (List.null extraCmdLabels) then return () else logVerbose opts ch $ T.intercalate "\n" extraCmdLabels
174+
timed@((ec, stdOut, stdErr), diff) <- time action
175+
stdoutLogger ch stdOut
176+
stderrLogger ch stdErr
177+
logVerbose opts ch $ format ("End: "%s%" "%s%" ("%s%")") cmdLabel (repr ec) (repr diff)
161178
return timed
162179

163-
timeAndExitOnErr :: HasVerbosity o => o -> TChan LogMessage -> Text -> IO FullOutput -> IO FullTimedOutput
164-
timeAndExitOnErr opts ch msg action = do
165-
timed@((ec, stdOut, stdErr), _) <- logTimedAction opts ch msg action
166-
if not (T.null stdErr)
167-
then channelErr ch stdErr
168-
else return ()
180+
timeAndExitOnErr :: (HasSequential o, HasVerbosity o) => o -> TChan LogMessage -> Text
181+
-> (TChan LogMessage -> Text -> IO ()) -> (TChan LogMessage -> Text -> IO ())
182+
-> ProcFun -> ProcInput
183+
-> IO FullTimedOutput
184+
timeAndExitOnErr opts ch cmdLabel = timeAndExitOnErr' opts ch cmdLabel []
185+
186+
timeAndExitOnErr' :: (HasSequential o, HasVerbosity o) => o -> TChan LogMessage -> Text -> [Text]
187+
-> (TChan LogMessage -> Text -> IO ()) -> (TChan LogMessage -> Text -> IO ())
188+
-> ProcFun -> ProcInput
189+
-> IO FullTimedOutput
190+
timeAndExitOnErr' opts ch cmdLabel extraCmdLabels stdoutLogger stderrLogger procFun (cmd, args, stdInput) = do
191+
let action = procFun cmd args stdInput
192+
timed@((ec, stdOut, stdErr), _) <- logTimedAction opts ch cmdLabel extraCmdLabels stdoutLogger stderrLogger action
169193
case ec of
170194
ExitFailure i -> do
171-
let msgOut = if not (T.null stdOut)
172-
then format ("Standard output:\n"%s%"\n") stdOut
173-
else ""
174-
175-
let msgErr = if not (T.null stdErr)
176-
then format ("Error output:\n"%s%"\n") stdErr
177-
else ""
195+
let cmdText = format (s%" "%s) cmd $ showCmdArgs args
196+
let msgOut = descriptiveOutput "Standard output" stdOut
197+
let msgErr = descriptiveOutput "Error output" stdErr
178198

179-
let exitMsg = format ("\nhledger-flow: an external process exited with exit code "%d%". \n"
180-
%s%s%"\nSee verbose output for more details.") i msgOut msgErr
199+
let exitMsg = format ("\n=== Begin Error: "%s%" ===\nExternal command:\n"%s%"\nExit code "%d%"\n"
200+
%s%s%"=== End Error: "%s%" ===\n") cmdLabel cmdText i msgOut msgErr cmdLabel
181201
errExit i ch exitMsg timed
182202
ExitSuccess -> return timed
183203

184-
procWithEmptyOutput :: MonadIO io => Text -> [Text] -> Shell Line -> io FullOutput
204+
procWithEmptyOutput :: ProcFun
185205
procWithEmptyOutput cmd args stdinput = do
186206
ec <- proc cmd args stdinput
187207
return (ec, T.empty, T.empty)
188208

189-
parAwareProc :: (HasSequential o, MonadIO io) => o -> Text -> [Text] -> Shell Line -> io FullOutput
209+
parAwareProc :: HasSequential o => o -> ProcFun
190210
parAwareProc opts = if (sequential opts) then procWithEmptyOutput else procStrictWithErr
191211

212+
inprocWithErrFun :: (Text -> IO ()) -> ProcInput -> Shell Line
213+
inprocWithErrFun errFun (cmd, args, standardInput) = do
214+
result <- inprocWithErr cmd args standardInput
215+
case result of
216+
Right ln -> return ln
217+
Left ln -> do
218+
(liftIO . errFun . lineToText) ln
219+
empty
220+
192221
verboseTestFile :: (HasVerbosity o, HasBaseDir o) => o -> TChan LogMessage -> FilePath -> IO Bool
193222
verboseTestFile opts ch p = do
194223
fileExists <- testfile p

src/Hledger/Flow/Reports.hs

+4-5
Original file line numberDiff line numberDiff line change
@@ -60,11 +60,10 @@ generateReport' opts ch journal outputFile args = do
6060
let reportArgs = ["--file", format fp journal] ++ args
6161
let reportDisplayArgs = ["--file", format fp relativeJournal] ++ args
6262
let hledger = format fp $ FlowTypes.hlPath . hledgerInfo $ opts :: Text
63-
let action = procStrictWithErr hledger reportArgs empty
64-
let cmd = format ("hledger "%s) $ showCmdArgs reportDisplayArgs
65-
result@((exitCode, stdOut, _), _) <- timeAndExitOnErr opts ch cmd action
63+
let cmdLabel = format ("hledger "%s) $ showCmdArgs reportDisplayArgs
64+
result@((exitCode, stdOut, _), _) <- timeAndExitOnErr opts ch cmdLabel dummyLogger channelErr procStrictWithErr (hledger, reportArgs, empty)
6665
if not (T.null stdOut) then do
67-
writeTextFile outputFile (cmd <> "\n\n"<> stdOut)
66+
writeTextFile outputFile (cmdLabel <> "\n\n"<> stdOut)
6867
channelOutLn ch $ format ("Wrote "%fp) $ relativeToBase opts outputFile
69-
else channelErrLn ch $ format ("No report output for '"%s%"' "%s) cmd (repr exitCode)
68+
else channelErrLn ch $ format ("No report output for '"%s%"' "%s) cmdLabel (repr exitCode)
7069
return (outputFile, result)

src/Hledger/Flow/Types.hs

+3
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,9 @@ data LogMessage = StdOut Text | StdErr Text | Terminate deriving (Show)
1111
type FullOutput = (ExitCode, Text, Text)
1212
type FullTimedOutput = (FullOutput, NominalDiffTime)
1313

14+
type ProcFun = Text -> [Text] -> Shell Line -> IO FullOutput
15+
type ProcInput = (Text, [Text], Shell Line)
16+
1417
data HledgerInfo = HledgerInfo { hlPath :: FilePath
1518
, hlVersion :: Text
1619
}

0 commit comments

Comments
 (0)