packages feed

hledger-flow 0.11.2.0 → 0.11.3.0

raw patch · 8 files changed

+182/−113 lines, 8 filesPVP ok

version bump matches the API change (PVP)

API changes (from Hackage documentation)

+ Hledger.Flow.Common: addPreamble :: Map FilePath Text -> Map FilePath Text
+ Hledger.Flow.Common: allYearsPath :: FilePath -> FilePath
+ Hledger.Flow.Common: allYearsPath' :: (FilePath -> FilePath) -> FilePath -> FilePath
+ Hledger.Flow.Common: changeExtension :: Text -> FilePath -> FilePath
+ Hledger.Flow.Common: changeOutputPath :: FilePath -> FilePath -> FilePath
+ Hledger.Flow.Common: descriptiveOutput :: Text -> Text -> Text
+ Hledger.Flow.Common: determineBaseDir :: Maybe FilePath -> IO FilePath
+ Hledger.Flow.Common: determineBaseDir' :: FilePath -> IO FilePath
+ Hledger.Flow.Common: determineBaseDir'' :: FilePath -> FilePath -> IO (Either Text FilePath)
+ Hledger.Flow.Common: errExit' :: Int -> (Text -> IO ()) -> Text -> a -> IO a
+ Hledger.Flow.Common: errorMessageBaseDir :: FilePath -> Text
+ Hledger.Flow.Common: escapeArg :: Text -> Text
+ Hledger.Flow.Common: excludeHiddenFiles :: Shell FilePath -> Shell FilePath
+ Hledger.Flow.Common: excludeWeirdPaths :: Shell FilePath -> Shell FilePath
+ Hledger.Flow.Common: extraIncludes :: (HasBaseDir o, HasVerbosity o) => o -> TChan LogMessage -> [FilePath] -> [Text] -> [FilePath] -> Shell InputFileBundle
+ Hledger.Flow.Common: extraIncludes' :: (HasBaseDir o, HasVerbosity o) => o -> TChan LogMessage -> InputFileBundle -> [FilePath] -> [Text] -> [FilePath] -> Shell InputFileBundle
+ Hledger.Flow.Common: filterPaths' :: [FilePath] -> (FilePath -> IO Bool) -> [FilePath] -> Shell [FilePath]
+ Hledger.Flow.Common: filterPathsByFileStatus :: (FileStatus -> Bool) -> Shell FilePath -> Shell FilePath
+ Hledger.Flow.Common: filterPathsByFileStatus' :: (FileStatus -> Bool) -> [FilePath] -> [FilePath] -> Shell [FilePath]
+ Hledger.Flow.Common: forceTrailingSlash :: FilePath -> FilePath
+ Hledger.Flow.Common: generatedIncludeText :: InputFileBundle -> InputFileBundle -> FilePath -> [FilePath] -> Text
+ Hledger.Flow.Common: groupIncludeFilesPerYear :: [FilePath] -> InputFileBundle
+ Hledger.Flow.Common: groupPairs' :: (Eq a, Ord a) => [(a, b)] -> [(a, [b])]
+ Hledger.Flow.Common: importDirBreakdown :: FilePath -> [FilePath]
+ Hledger.Flow.Common: importDirBreakdown' :: [FilePath] -> FilePath -> [FilePath]
+ Hledger.Flow.Common: includeFileName :: FilePath -> FilePath
+ Hledger.Flow.Common: initialIncludeFilePath :: FilePath -> FilePath
+ Hledger.Flow.Common: logTimedAction :: HasVerbosity o => o -> TChan LogMessage -> Text -> [Text] -> (TChan LogMessage -> Text -> IO ()) -> (TChan LogMessage -> Text -> IO ()) -> IO FullOutput -> IO FullTimedOutput
+ Hledger.Flow.Common: logToChannel :: TChan LogMessage -> Text -> IO ()
+ Hledger.Flow.Common: parentIncludeFilePath :: FilePath -> FilePath
+ Hledger.Flow.Common: procWithEmptyOutput :: ProcFun
+ Hledger.Flow.Common: relativeFilesAsText :: HasBaseDir o => o -> [FilePath] -> [Text]
+ Hledger.Flow.Common: timestampPrefix :: Text -> IO Text
+ Hledger.Flow.Common: toIncludeFiles' :: InputFileBundle -> InputFileBundle -> InputFileBundle -> Map FilePath Text
+ Hledger.Flow.Common: type InputFileBundle = Map FilePath [FilePath]
+ Hledger.Flow.Common: writeFileMap :: (HasBaseDir o, HasVerbosity o) => o -> TChan LogMessage -> (InputFileBundle, InputFileBundle) -> Shell [FilePath]
+ Hledger.Flow.Common: writeFiles :: Shell (Map FilePath Text) -> Shell [FilePath]
+ Hledger.Flow.Common: writeFiles' :: Map FilePath Text -> Shell [FilePath]
+ Hledger.Flow.Common: writeTextMap :: Map FilePath Text -> IO ()

Files

ChangeLog.md view
@@ -1,5 +1,10 @@ # Changelog for [hledger-flow](https://github.com/apauley/hledger-flow) +## 0.11.3++- Detect the hledger-flow base directory correctly, even when in a subdirectory. Similar to how git behaves.+- Change the version subcommand into a flag - thanks to [jecaro](https://github.com/apauley/hledger-flow/pull/38) for the contribution.+ ## 0.11.2  - Improved display of external process output
README.org view
@@ -129,7 +129,7 @@ You can see the example imported financial transactions as it was generated by the step-by-step instructions here: -[[https://github.com/apauley/hledger-flow-example-finances][https://github.com/apauley/hledger-flow-example-finances]]+[[https://github.com/apauley/hledger-flow-example][https://github.com/apauley/hledger-flow-example]]  * After Cloning This Repository   :PROPERTIES:
app/Main.hs view
@@ -21,19 +21,19 @@                                          , sequential :: Bool                                          }                       deriving (Show)-data Command = Version (Maybe Text) | Import SubcommandParams | Report SubcommandParams deriving (Show)+data Command = Version | Import SubcommandParams | Report SubcommandParams deriving (Show)  main :: IO () main = do   cmd <- options "An hledger workflow focusing on automated statement import and classification:\nhttps://github.com/apauley/hledger-flow#readme" parser   case cmd of-    Version _        -> stdout $ select versionInfo+    Version          -> stdout $ select versionInfo     Import subParams -> toImportOptions subParams >>= importCSVs     Report subParams -> toReportOptions subParams >>= generateReports  toImportOptions :: SubcommandParams -> IO IT.ImportOptions toImportOptions params = do-  bd <- dirOrPwd $ maybeBaseDir params+  bd <- determineBaseDir $ maybeBaseDir params   hli <- hledgerInfoFromPath $ hledgerPathOpt params   return IT.ImportOptions { IT.baseDir = bd                           , IT.hledgerInfo = hli@@ -43,7 +43,7 @@  toReportOptions :: SubcommandParams -> IO RT.ReportOptions toReportOptions params = do-  bd <- dirOrPwd $ maybeBaseDir params+  bd <- determineBaseDir $ maybeBaseDir params   hli <- hledgerInfoFromPath $ hledgerPathOpt params   return RT.ReportOptions { RT.baseDir = bd                           , RT.hledgerInfo = hli@@ -54,7 +54,7 @@ parser :: Parser Command parser = fmap Import (subcommand "import" "Converts CSV transactions into categorised journal files" subcommandParser)   <|> fmap Report (subcommand "report" "Generate Reports" subcommandParser)-  <|> fmap Version (subcommand "version" "Display version information" noArgs)+  <|> flag' Version (long "version" <> short 'V' <> help "Display version information")  subcommandParser :: Parser SubcommandParams subcommandParser = SubcommandParams@@ -63,6 +63,3 @@   <*> switch (long "verbose" <> short 'v' <> help "Print more verbose output")   <*> switch (long "show-options" <> help "Print the options this program will run with")   <*> switch (long "sequential" <> help "Disable parallel processing")--noArgs :: Parser (Maybe Text)-noArgs = optional (argText "" "")
hledger-flow.cabal view
@@ -4,10 +4,10 @@ -- -- see: https://github.com/sol/hpack ----- hash: 4192698e971b03566fe89299093f48fdb43d3a26f65a2ac17592ae7a294868c4+-- hash: 2a75e18a525c30319738a863343f42c79804c552261cad30da8b396adcdab18b  name:           hledger-flow-version:        0.11.2.0+version:        0.11.3.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@@ -70,6 +70,7 @@   type: exitcode-stdio-1.0   main-is: Spec.hs   other-modules:+      Common.Integration       Common.Unit       CSVImport.Integration       CSVImport.Unit
src/Hledger/Flow/Common.hs view
@@ -1,49 +1,6 @@ {-# LANGUAGE OverloadedStrings #-} -module Hledger.Flow.Common-    ( docURL-    , versionInfo-    , hledgerPathFromOption-    , hledgerVersionFromPath-    , hledgerInfoFromPath-    , showCmdArgs-    , consoleChannelLoop-    , terminateChannelLoop-    , dummyLogger-    , channelOut, channelOutLn-    , channelErr, channelErrLn-    , errExit-    , logVerbose-    , timeAndExitOnErr, timeAndExitOnErr'-    , parAwareProc-    , inprocWithErrFun-    , verboseTestFile-    , relativeToBase-    , relativeToBase'-    , lsDirs-    , onlyFiles-    , onlyDirs-    , filterPaths-    , changePathAndExtension-    , basenameLine-    , buildFilename-    , shellToList-    , firstExistingFile-    , groupValuesBy-    , groupIncludeFiles-    , allYearIncludeFiles-    , yearsIncludeMap-    , extraIncludesForFile-    , groupPairs-    , pairBy-    , includePreamble-    , toIncludeFiles-    , toIncludeLine-    , groupAndWriteIncludeFiles-    , writeIncludesUpTo-    , dirOrPwd-    , extractImportDirs-    ) where+module Hledger.Flow.Common where  import Turtle import Prelude hiding (FilePath, putStrLn)@@ -434,6 +391,35 @@ changeOutputPath :: FilePath -> FilePath -> FilePath changeOutputPath newOutputLocation srcFile = mconcat $ map changeSrcDir $ splitDirectories srcFile   where changeSrcDir file = if (file == "1-in/" || file == "2-preprocessed/") then newOutputLocation else file++errorMessageBaseDir :: FilePath -> Text+errorMessageBaseDir startDir = format ("Unable to find an hledger-flow import directory at '"%fp+                                       %"' (or in any of its parent directories).\n\n"+                                       %"Have a look at the documentation for more information:\n"%s)+                               startDir (docURL "input-files")++determineBaseDir :: Maybe FilePath -> IO FilePath+determineBaseDir (Just suppliedDir) = determineBaseDir' suppliedDir+determineBaseDir Nothing = pwd >>= determineBaseDir'++determineBaseDir' :: FilePath -> IO FilePath+determineBaseDir' startDir = do+  ee <- determineBaseDir'' startDir startDir+  case ee of+    Right bd -> return bd+    Left  t  -> die t++determineBaseDir'' :: FilePath -> FilePath -> IO (Either Text FilePath)+determineBaseDir'' startDir currentDir = do+  foundBaseDir <- testdir $ currentDir </> "import"+  if foundBaseDir+    then return $ Right $ forceTrailingSlash currentDir+    else+    do+      let doneSearching = (currentDir `elem` ["/", "./"])+      if doneSearching+        then return $ Left $ errorMessageBaseDir startDir+        else determineBaseDir'' startDir $ parent currentDir  dirOrPwd :: Maybe FilePath -> IO FilePath dirOrPwd maybeBaseDir = fmap forceTrailingSlash (fromMaybe pwd $ fmap realpath maybeBaseDir)
test/CSVImport/Integration.hs view
@@ -16,60 +16,6 @@ import Hledger.Flow.Common import Control.Concurrent.STM -testHiddenFiles = TestCase (-  sh (-      do-        tmpdir <- using (mktempdir "." "hlflow")-        let tmpJournals = map (tmpdir </>) journalFiles :: [FilePath]-        let tmpExtras = map (tmpdir </>) extraFiles :: [FilePath]-        let tmpHidden = map (tmpdir </>) hiddenFiles :: [FilePath]-        let onDisk = List.sort $ tmpJournals ++ tmpExtras ++ tmpHidden-        touchAll onDisk-        filtered <- (fmap List.sort) $ shellToList $ onlyFiles $ select onDisk-        let expected = List.sort $ tmpExtras ++ tmpJournals-        liftIO $ assertEqual "Hidden files should be excluded" expected filtered-     )-  )--testDirOrPwd = TestCase (-  sh (-      do-        currentDir <- fmap (\p -> directory (p </> "t")) pwd-        tmpdir <- using (mktempdir "." "hlflow")-        let fooDir = collapse $ currentDir </> tmpdir </> "foo/"-        let barDir = collapse $ currentDir </> tmpdir </> "bar/"-        mkdir fooDir-        mkdir barDir-        d1 <- liftIO $ dirOrPwd Nothing-        liftIO $ assertEqual "dirOrPwd returns pwd as a fallback" currentDir d1-        liftIO $ assertEqual "dirOrPwd assumes the fallback is a directory" (directory d1) d1-        d2 <- liftIO $ dirOrPwd $ Just $ tmpdir </> "foo"-        liftIO $ assertEqual "dirOrPwd returns the supplied dir - no trailing slash supplied" fooDir d2-        liftIO $ assertEqual "dirOrPwd assumes the supplied dir is a directory - no trailing slash supplied" (directory d2) d2-        d3 <- liftIO $ dirOrPwd $ Just $ tmpdir </> "bar/"-        liftIO $ assertEqual "dirOrPwd returns the supplied dir - trailing slash supplied" barDir d3-        liftIO $ assertEqual "dirOrPwd assumes the supplied dir is a directory - trailing slash supplied" (directory d3) d3-     )-  )--testFilterPaths = TestCase (-  sh (-      do-        tmpdir <- using (mktempdir "." "hlflow")-        let tmpJournals = map (tmpdir </>) journalFiles :: [FilePath]-        let tmpExtras = map (tmpdir </>) extraFiles :: [FilePath]-        let tmpHidden = map (tmpdir </>) hiddenFiles :: [FilePath]-        let onDisk = List.sort $ tmpJournals ++ tmpExtras ++ tmpHidden-        touchAll onDisk--        let nonExistant = map (tmpdir </>) ["where", "is", "my", "mind"]-        let toFilter = nonExistant ++ onDisk-        filtered <- single $ filterPaths testfile toFilter-        let actual = List.sort filtered-        liftIO $ assertEqual "The filtered paths should exclude files not actually on disk" onDisk actual-     )-  )- testExtraIncludesForFile = TestCase (   sh (       do@@ -223,9 +169,7 @@               <> "!include 3-journal/2018/2018-12-30.journal\n"         actualJane7Contents <- liftIO $ readTextFile jane7         liftIO $ assertEqual "Jane7: The include file contents should be the journal files" expectedJane7Contents actualJane7Contents-      )   ) -tests = TestList [testDirOrPwd, testExtraIncludesForFile, testIncludesPrePost, testIncludesOpeningClosing,-                  testHiddenFiles, testFilterPaths, testWriteIncludeFiles]+tests = TestList [testExtraIncludesForFile, testIncludesPrePost, testIncludesOpeningClosing, testWriteIncludeFiles]
+ test/Common/Integration.hs view
@@ -0,0 +1,135 @@+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE OverloadedLists #-}++module Common.Integration (tests) where++import Test.HUnit+import Turtle+import Prelude hiding (FilePath)+import qualified Data.Map.Strict as Map+import qualified Control.Foldl as Fold+import qualified Data.Text as T+import qualified Data.List as List (sort)++import TestHelpers+import Hledger.Flow.Import.Types+import Hledger.Flow.Common+import Control.Concurrent.STM++testHiddenFiles = TestCase (+  sh (+      do+        tmpdir <- using (mktempdir "." "hlflow")+        let tmpJournals = map (tmpdir </>) journalFiles :: [FilePath]+        let tmpExtras = map (tmpdir </>) extraFiles :: [FilePath]+        let tmpHidden = map (tmpdir </>) hiddenFiles :: [FilePath]+        let onDisk = List.sort $ tmpJournals ++ tmpExtras ++ tmpHidden+        touchAll onDisk+        filtered <- (fmap List.sort) $ shellToList $ onlyFiles $ select onDisk+        let expected = List.sort $ tmpExtras ++ tmpJournals+        liftIO $ assertEqual "Hidden files should be excluded" expected filtered+     )+  )++testDirOrPwd = TestCase (+  sh (+      do+        currentDir <- fmap (\p -> directory (p </> "t")) pwd+        tmpdir <- using (mktempdir "." "hlflow")+        let fooDir = collapse $ currentDir </> tmpdir </> "foo/"+        let barDir = collapse $ currentDir </> tmpdir </> "bar/"+        mkdir fooDir+        mkdir barDir+        d1 <- liftIO $ dirOrPwd Nothing+        liftIO $ assertEqual "dirOrPwd returns pwd as a fallback" currentDir d1+        liftIO $ assertEqual "dirOrPwd assumes the fallback is a directory" (directory d1) d1+        d2 <- liftIO $ dirOrPwd $ Just $ tmpdir </> "foo"+        liftIO $ assertEqual "dirOrPwd returns the supplied dir - no trailing slash supplied" fooDir d2+        liftIO $ assertEqual "dirOrPwd assumes the supplied dir is a directory - no trailing slash supplied" (directory d2) d2+        d3 <- liftIO $ dirOrPwd $ Just $ tmpdir </> "bar/"+        liftIO $ assertEqual "dirOrPwd returns the supplied dir - trailing slash supplied" barDir d3+        liftIO $ assertEqual "dirOrPwd assumes the supplied dir is a directory - trailing slash supplied" (directory d3) d3+     )+  )++testDetermineBaseDir = TestCase (+  sh (+      do+        error1 <- liftIO $ determineBaseDir'' "/path/to/dir" "/path/to/dir"+        liftIO $ assertEqual "determineBaseDir produces an error message when given a non-existant dir" (Left $ errorMessageBaseDir "/path/to/dir") error1+        tmpdir <- using (mktempdir "." "hlflow")++        let unrelatedDir = collapse $ tmpdir </> "unrelated"+        mkdir unrelatedDir++        bdUnrelated <- liftIO $ determineBaseDir'' unrelatedDir unrelatedDir+        liftIO $ assertEqual "determineBaseDir produces an error message when it cannot find a baseDir" (Left $ errorMessageBaseDir unrelatedDir) bdUnrelated++        currentDir <- pwd+        let baseDir = forceTrailingSlash $ collapse $ currentDir </> tmpdir </> "bd1"++        let importDir = baseDir </> "import"+        let ownerDir = importDir </> "john"+        let bankDir = ownerDir </> "mybank"+        let accDir = bankDir </> "myacc"+        let inDir = accDir </> "1-in"+        let yearDir = inDir </> "2019"+        mktree yearDir++        let reportDir = baseDir </> "report"+        mkdir reportDir++        cd baseDir+        bd <- liftIO $ determineBaseDir Nothing+        liftIO $ assertEqual "determineBaseDir searches from pwd upwards until it finds a dir containing 'import' - in the base dir" baseDir bd++        cd reportDir+        bdReport <- liftIO $ determineBaseDir Nothing+        liftIO $ assertEqual "determineBaseDir searches from pwd upwards until it finds a dir containing 'import' - report dir" baseDir bdReport++        cd yearDir+        bdYear <- liftIO $ determineBaseDir Nothing+        liftIO $ assertEqual "determineBaseDir searches from pwd upwards until it finds a dir containing 'import' - year dir" baseDir bdYear++        cd inDir+        bdIn <- liftIO $ determineBaseDir Nothing+        liftIO $ assertEqual "determineBaseDir searches from pwd upwards until it finds a dir containing 'import' - input dir" baseDir bdIn++        cd accDir+        bdAcc <- liftIO $ determineBaseDir Nothing+        liftIO $ assertEqual "determineBaseDir searches from pwd upwards until it finds a dir containing 'import' - account dir" baseDir bdAcc++        cd bankDir+        bdBank <- liftIO $ determineBaseDir Nothing+        liftIO $ assertEqual "determineBaseDir searches from pwd upwards until it finds a dir containing 'import' - bank dir" baseDir bdBank++        cd ownerDir+        bdOwner <- liftIO $ determineBaseDir Nothing+        liftIO $ assertEqual "determineBaseDir searches from pwd upwards until it finds a dir containing 'import' - owner dir" baseDir bdOwner++        cd importDir+        bdImport <- liftIO $ determineBaseDir Nothing+        liftIO $ assertEqual "determineBaseDir searches from pwd upwards until it finds a dir containing 'import' - import dir" baseDir bdImport+     )+  )++testFilterPaths = TestCase (+  sh (+      do+        tmpdir <- using (mktempdir "." "hlflow")+        let tmpJournals = map (tmpdir </>) journalFiles :: [FilePath]+        let tmpExtras = map (tmpdir </>) extraFiles :: [FilePath]+        let tmpHidden = map (tmpdir </>) hiddenFiles :: [FilePath]+        let onDisk = List.sort $ tmpJournals ++ tmpExtras ++ tmpHidden+        touchAll onDisk++        let nonExistant = map (tmpdir </>) ["where", "is", "my", "mind"]+        let toFilter = nonExistant ++ onDisk+        filtered <- single $ filterPaths testfile toFilter+        let actual = List.sort filtered+        liftIO $ assertEqual "The filtered paths should exclude files not actually on disk" onDisk actual+     )+  )+++tests = TestList [testDirOrPwd, testDetermineBaseDir, testHiddenFiles, testFilterPaths]
test/Spec.hs view
@@ -8,10 +8,11 @@ import Prelude hiding (FilePath)  import qualified Common.Unit+import qualified Common.Integration import qualified CSVImport.Unit import qualified CSVImport.Integration -tests = TestList [Common.Unit.tests, CSVImport.Unit.tests, CSVImport.Integration.tests]+tests = TestList [Common.Unit.tests, Common.Integration.tests, CSVImport.Unit.tests, CSVImport.Integration.tests]  main :: IO Counts main = do