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 +5/−0
- README.org +1/−1
- app/Main.hs +5/−8
- hledger-flow.cabal +3/−2
- src/Hledger/Flow/Common.hs +30/−44
- test/CSVImport/Integration.hs +1/−57
- test/Common/Integration.hs +135/−0
- test/Spec.hs +2/−1
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