packages feed

hledger-flow 0.11.1.2 → 0.11.2.0

raw patch · 7 files changed

+96/−50 lines, 7 filesPVP: major bump suggested

API removals or changes: PVP suggests a major version bump

API changes (from Hackage documentation)

+ Hledger.Flow.Common: dummyLogger :: TChan LogMessage -> Text -> IO ()
+ Hledger.Flow.Common: inprocWithErrFun :: (Text -> IO ()) -> ProcInput -> Shell Line
+ Hledger.Flow.Common: timeAndExitOnErr' :: (HasSequential o, HasVerbosity o) => o -> TChan LogMessage -> Text -> [Text] -> (TChan LogMessage -> Text -> IO ()) -> (TChan LogMessage -> Text -> IO ()) -> ProcFun -> ProcInput -> IO FullTimedOutput
+ Hledger.Flow.Types: type ProcFun = Text -> [Text] -> Shell Line -> IO FullOutput
+ Hledger.Flow.Types: type ProcInput = (Text, [Text], Shell Line)
- Hledger.Flow.Common: parAwareProc :: (HasSequential o, MonadIO io) => o -> Text -> [Text] -> Shell Line -> io FullOutput
+ Hledger.Flow.Common: parAwareProc :: HasSequential o => o -> ProcFun
- Hledger.Flow.Common: timeAndExitOnErr :: HasVerbosity o => o -> TChan LogMessage -> Text -> IO FullOutput -> IO FullTimedOutput
+ Hledger.Flow.Common: timeAndExitOnErr :: (HasSequential o, HasVerbosity o) => o -> TChan LogMessage -> Text -> (TChan LogMessage -> Text -> IO ()) -> (TChan LogMessage -> Text -> IO ()) -> ProcFun -> ProcInput -> IO FullTimedOutput

Files

ChangeLog.md view
@@ -1,5 +1,9 @@ # Changelog for [hledger-flow](https://github.com/apauley/hledger-flow) +## 0.11.2++- Improved display of external process output+ ## 0.11.1.2  - Exit with an error code when any external script fails - https://github.com/apauley/hledger-flow/issues/28
README.org view
@@ -38,6 +38,17 @@ with the ideas in [[https://github.com/adept/full-fledged-hledger/wiki][adept's Full-fledged Hledger]] aren't really specific to my own finances, and can be shared. +* How do I install it?+  :PROPERTIES:+  :CUSTOM_ID: how-do-i-install-it+  :END:++The easiest way to get it running is to download [[https://github.com/apauley/hledger-flow/releases][the latest release]]+for your OS, and copy the =hledger-flow= executable to a directory in your PATH.+Then just run it and see what it tells you to do.++You can also compile it yourself by following the [[#build-instructions][build instructions]].+ * Overview of the Basic Workflow   :PROPERTIES:   :CUSTOM_ID: overview-of-the-basic-workflow@@ -524,7 +535,7 @@ Save your =construct= script in the account directory, e.g. =import/john/mybank/savings/construct=. -=hledger-flow= will call your =construct= script with 4 positional+=hledger-flow= will call your =construct= script with 5 positional parameters:  1. The path to the input statement, e.g.@@ -540,8 +551,10 @@ - generate your own =hledger= journal transactions - be idempotent. Running =construct= multiple times on the same files   should produce the same result.-- send all output to =stdout=. =hledger-flow= will pipe your output into+- send all journals to =stdout=. =hledger-flow= will pipe your standard output into   =hledger= which will format it and save it to an output file.++You can still use =stderr= in your construct script for any other output that you may want to see.  *** Stability of this Feature     :PROPERTIES:
hledger-flow.cabal view
@@ -1,13 +1,13 @@ cabal-version: 1.12 --- This file has been generated from package.yaml by hpack version 0.31.2.+-- This file has been generated from package.yaml by hpack version 0.31.1. -- -- see: https://github.com/sol/hpack ----- hash: 752e52ff23c6e0069c279dc21baa46e6c8b0b20124b506e7bdca5d52136bb483+-- hash: 4192698e971b03566fe89299093f48fdb43d3a26f65a2ac17592ae7a294868c4  name:           hledger-flow-version:        0.11.1.2+version:        0.11.2.0 synopsis:       An hledger workflow focusing on automated statement import and classification. description:    Please see the README on GitHub at <https://github.com/apauley/hledger-flow#readme> category:       Finance, Console@@ -57,7 +57,7 @@   hs-source-dirs:       app   ghc-options: -threaded -rtsopts -with-rtsopts=-N -Wall -fPIC-  ld-options: -dynamic+  ld-options: -static   build-depends:       base >=4.7 && <5     , hledger-flow
src/Hledger/Flow/CSVImport.hs view
@@ -85,13 +85,11 @@ preprocess opts ch script bank account owner src = do   let csvOut = changePathAndExtension "2-preprocessed" "csv" src   mktree $ directory csvOut-  let script' = format fp script :: Text-  let action = (parAwareProc opts) script' [format fp src, format fp csvOut, lineToText bank, lineToText account, lineToText owner] empty+  let args = [format fp src, format fp csvOut, lineToText bank, lineToText account, lineToText owner]   let relScript = relativeToBase opts script   let relSrc = relativeToBase opts src-  let msg = format ("executing '"%fp%"' on '"%fp%"'") relScript relSrc-  ((_, stdOut, _), _) <- timeAndExitOnErr opts ch msg action-  channelOut ch stdOut+  let cmdLabel = format ("executing '"%fp%"' on '"%fp%"'") relScript relSrc+  _ <- timeAndExitOnErr opts ch cmdLabel channelOut channelErr (parAwareProc opts) (format fp script, args, empty)   return csvOut  hledgerImport :: ImportOptions -> TChan FlowTypes.LogMessage -> FilePath -> FilePath -> IO FilePath@@ -110,10 +108,9 @@     Just rf -> do       let relRules = relativeToBase opts rf       let hledger = format fp $ FlowTypes.hlPath . hledgerInfo $ opts :: Text-      let action = (parAwareProc opts) hledger ["print", "--rules-file", format fp rf, "--file", format fp csvSrc, "--output-file", format fp journalOut] empty-      let msg = format ("importing '"%fp%"' using rules file '"%fp%"'") relCSV relRules-      ((_, stdOut, _), _) <- timeAndExitOnErr opts ch msg action-      channelOut ch stdOut+      let args = ["print", "--rules-file", format fp rf, "--file", format fp csvSrc, "--output-file", format fp journalOut]+      let cmdLabel = format ("importing '"%fp%"' using rules file '"%fp%"'") relCSV relRules+      _ <- timeAndExitOnErr opts ch cmdLabel channelOut channelErr (parAwareProc opts) (hledger, args, empty)       return journalOut     Nothing ->       do@@ -157,12 +154,13 @@ customConstruct :: ImportOptions -> TChan FlowTypes.LogMessage -> FilePath -> Line -> Line -> Line -> FilePath -> FilePath -> IO FilePath customConstruct opts ch constructScript bank account owner csvSrc journalOut = do   let script = format fp constructScript :: Text-  let importOut = inproc script [format fp csvSrc, "-", lineToText bank, lineToText account, lineToText owner] empty-  let hledger = format fp $ FlowTypes.hlPath . hledgerInfo $ opts :: Text-  let action = (parAwareProc opts) hledger ["print", "--ignore-assertions", "--file", "-", "--output-file", format fp journalOut] importOut   let relScript = relativeToBase opts constructScript+  let constructArgs = [format fp csvSrc, "-", lineToText bank, lineToText account, lineToText owner]+  let constructCmdText = format ("Running: "%fp%" "%s) relScript (showCmdArgs constructArgs)+  let stdLines = inprocWithErrFun (channelErrLn ch) (script, constructArgs, empty)+  let hledger = format fp $ FlowTypes.hlPath . hledgerInfo $ opts :: Text+  let args = ["print", "--ignore-assertions", "--file", "-", "--output-file", format fp journalOut]   let relSrc = relativeToBase opts csvSrc-  let msg = format ("executing '"%fp%"' on '"%fp%"'") relScript relSrc-  ((_, stdOut, _), _) <- timeAndExitOnErr opts ch msg action-  channelOut ch stdOut+  let cmdLabel = format ("executing '"%fp%"' on '"%fp%"'") relScript relSrc+  _ <- timeAndExitOnErr' opts ch cmdLabel [constructCmdText] channelOut channelErr (parAwareProc opts) (hledger, args, stdLines)   return journalOut
src/Hledger/Flow/Common.hs view
@@ -9,12 +9,14 @@     , showCmdArgs     , consoleChannelLoop     , terminateChannelLoop+    , dummyLogger     , channelOut, channelOutLn     , channelErr, channelErrLn     , errExit     , logVerbose-    , timeAndExitOnErr+    , timeAndExitOnErr, timeAndExitOnErr'     , parAwareProc+    , inprocWithErrFun     , verboseTestFile     , relativeToBase     , relativeToBase'@@ -55,7 +57,7 @@ import Data.Time.LocalTime  import Data.Function (on)-import qualified Data.List as List (nub, sort, sortBy, groupBy)+import qualified Data.List as List (nub, null, sort, sortBy, groupBy) import Data.Ord (comparing) import Hledger.Flow.Types import qualified Hledger.Flow.Import.Types as IT@@ -103,6 +105,9 @@ escapeArg :: Text -> Text escapeArg a = if (T.count " " a > 0) then "'" <> a <> "'" else a +dummyLogger :: TChan LogMessage -> Text -> IO ()+dummyLogger _ _ = return ()+ channelOut :: TChan LogMessage -> Text -> IO () channelOut ch txt = atomically $ writeTChan ch $ StdOut txt @@ -153,41 +158,65 @@ logVerbose :: HasVerbosity o => o -> TChan LogMessage -> Text -> IO () logVerbose opts ch msg = if (verbose opts) then logToChannel ch msg else return () -logTimedAction :: HasVerbosity o => o -> TChan LogMessage -> Text -> IO FullOutput -> IO FullTimedOutput-logTimedAction opts ch msg action = do-  logVerbose opts ch $ format ("Begin: "%s) msg-  timed@((ec, _, _), diff) <- time action-  logVerbose opts ch $ format ("End:   "%s%" "%s%" ("%s%")") msg (repr ec) (repr diff)+descriptiveOutput :: Text -> Text -> Text+descriptiveOutput outputLabel outTxt = do+  if not (T.null outTxt)+    then format (s%":\n"%s%"\n") outputLabel outTxt+    else ""++logTimedAction :: HasVerbosity o => o -> TChan LogMessage -> Text -> [Text]+  -> (TChan LogMessage -> Text -> IO ()) -> (TChan LogMessage -> Text -> IO ())+  -> IO FullOutput+  -> IO FullTimedOutput+logTimedAction opts ch cmdLabel extraCmdLabels stdoutLogger stderrLogger action = do+  logVerbose opts ch $ format ("Begin: "%s) cmdLabel+  if (List.null extraCmdLabels) then return () else logVerbose opts ch $ T.intercalate "\n" extraCmdLabels+  timed@((ec, stdOut, stdErr), diff) <- time action+  stdoutLogger ch stdOut+  stderrLogger ch stdErr+  logVerbose opts ch $ format ("End:   "%s%" "%s%" ("%s%")") cmdLabel (repr ec) (repr diff)   return timed -timeAndExitOnErr :: HasVerbosity o => o -> TChan LogMessage -> Text -> IO FullOutput -> IO FullTimedOutput-timeAndExitOnErr opts ch msg action = do-  timed@((ec, stdOut, stdErr), _) <- logTimedAction opts ch msg action-  if not (T.null stdErr)-    then channelErr ch stdErr-    else return ()+timeAndExitOnErr :: (HasSequential o, HasVerbosity o) => o -> TChan LogMessage -> Text+  -> (TChan LogMessage -> Text -> IO ()) -> (TChan LogMessage -> Text -> IO ())+  -> ProcFun -> ProcInput+  -> IO FullTimedOutput+timeAndExitOnErr opts ch cmdLabel = timeAndExitOnErr' opts ch cmdLabel []++timeAndExitOnErr' :: (HasSequential o, HasVerbosity o) => o -> TChan LogMessage -> Text -> [Text]+  -> (TChan LogMessage -> Text -> IO ()) -> (TChan LogMessage -> Text -> IO ())+  -> ProcFun -> ProcInput+  -> IO FullTimedOutput+timeAndExitOnErr' opts ch cmdLabel extraCmdLabels stdoutLogger stderrLogger procFun (cmd, args, stdInput) = do+  let action = procFun cmd args stdInput+  timed@((ec, stdOut, stdErr), _) <- logTimedAction opts ch cmdLabel extraCmdLabels stdoutLogger stderrLogger action   case ec of     ExitFailure i -> do-      let msgOut = if not (T.null stdOut)-            then format ("Standard output:\n"%s%"\n") stdOut-            else ""--      let msgErr = if not (T.null stdErr)-            then format ("Error output:\n"%s%"\n") stdErr-            else ""+      let cmdText = format (s%" "%s) cmd $ showCmdArgs args+      let msgOut = descriptiveOutput "Standard output" stdOut+      let msgErr = descriptiveOutput "Error output" stdErr -      let exitMsg = format ("\nhledger-flow: an external process exited with exit code "%d%". \n"-                            %s%s%"\nSee verbose output for more details.") i msgOut msgErr+      let exitMsg = format ("\n=== Begin Error: "%s%" ===\nExternal command:\n"%s%"\nExit code "%d%"\n"+                            %s%s%"=== End Error: "%s%" ===\n") cmdLabel cmdText i msgOut msgErr cmdLabel       errExit i ch exitMsg timed     ExitSuccess -> return timed -procWithEmptyOutput :: MonadIO io => Text -> [Text] -> Shell Line -> io FullOutput+procWithEmptyOutput :: ProcFun procWithEmptyOutput cmd args stdinput = do   ec <- proc cmd args stdinput   return (ec, T.empty, T.empty) -parAwareProc :: (HasSequential o, MonadIO io) => o -> Text -> [Text] -> Shell Line -> io FullOutput+parAwareProc :: HasSequential o => o -> ProcFun parAwareProc opts = if (sequential opts) then procWithEmptyOutput else procStrictWithErr++inprocWithErrFun :: (Text -> IO ()) -> ProcInput -> Shell Line+inprocWithErrFun errFun (cmd, args, standardInput) = do+  result <- inprocWithErr cmd args standardInput+  case result of+    Right ln -> return ln+    Left  ln -> do+      (liftIO . errFun . lineToText) ln+      empty  verboseTestFile :: (HasVerbosity o, HasBaseDir o) => o -> TChan LogMessage -> FilePath -> IO Bool verboseTestFile opts ch p = do
src/Hledger/Flow/Reports.hs view
@@ -60,11 +60,10 @@   let reportArgs = ["--file", format fp journal] ++ args   let reportDisplayArgs = ["--file", format fp relativeJournal] ++ args   let hledger = format fp $ FlowTypes.hlPath . hledgerInfo $ opts :: Text-  let action = procStrictWithErr hledger reportArgs empty-  let cmd = format ("hledger "%s) $ showCmdArgs reportDisplayArgs-  result@((exitCode, stdOut, _), _) <- timeAndExitOnErr opts ch cmd action+  let cmdLabel = format ("hledger "%s) $ showCmdArgs reportDisplayArgs+  result@((exitCode, stdOut, _), _) <- timeAndExitOnErr opts ch cmdLabel dummyLogger channelErr procStrictWithErr (hledger, reportArgs, empty)   if not (T.null stdOut) then do-    writeTextFile outputFile (cmd <> "\n\n"<> stdOut)+    writeTextFile outputFile (cmdLabel <> "\n\n"<> stdOut)     channelOutLn ch $ format ("Wrote "%fp) $ relativeToBase opts outputFile-    else channelErrLn ch $ format ("No report output for '"%s%"' "%s) cmd (repr exitCode)+    else channelErrLn ch $ format ("No report output for '"%s%"' "%s) cmdLabel (repr exitCode)   return (outputFile, result)
src/Hledger/Flow/Types.hs view
@@ -11,6 +11,9 @@ type FullOutput = (ExitCode, Text, Text) type FullTimedOutput = (FullOutput, NominalDiffTime) +type ProcFun = Text -> [Text] -> Shell Line -> IO FullOutput+type ProcInput = (Text, [Text], Shell Line)+ data HledgerInfo = HledgerInfo { hlPath :: FilePath                                , hlVersion :: Text                                }