packages feed

hledger-flow 0.14.2.0 → 0.14.3.0

raw patch · 19 files changed

+1062/−676 lines, 19 filesdep +filepathdep +gitrev

Dependencies added: filepath, gitrev

Files

ChangeLog.md view
@@ -1,5 +1,11 @@ # Changelog for [hledger-flow](https://github.com/apauley/hledger-flow) +## 0.14.3++Ensure that generated include files only contain files ending with .journal++Fixes [#92](https://github.com/apauley/hledger-flow/issues/92)+ ## 0.14.2  Add an optional `--start-year` command-line option for imports:
app/Main.hs view
@@ -12,6 +12,7 @@  import Hledger.Flow.PathHelpers (TurtlePath) import Hledger.Flow.Common+import Hledger.Flow.Internals (versionInfo, systemInfo) import Hledger.Flow.BaseDir import qualified Hledger.Flow.RuntimeOptions as RT import Hledger.Flow.Reports@@ -41,7 +42,7 @@ main = do   cmd <- Turtle.options "An hledger workflow focusing on automated statement import and classification:\nhttps://github.com/apauley/hledger-flow#readme" baseCommandParser   case cmd of-    Version                                -> Turtle.stdout $ Turtle.select versionInfo+    Version                                -> T.putStrLn versionInfo     Command mainParams' (Import subParams) -> toRuntimeOptionsImport mainParams' subParams >>= importCSVs     Command mainParams' (Report subParams) -> toRuntimeOptionsReport mainParams' subParams >>= generateReports @@ -57,7 +58,7 @@                            , RT.importRunDir = runDir                            , RT.importStartYear = startYear                            , RT.onlyNewFiles = onlyNewFiles subParams'-                           , RT.hfVersion = versionInfo'+                           , RT.hfVersion = versionInfo                            , RT.hledgerInfo = hli                            , RT.sysInfo = systemInfo                            , RT.verbose = verbosity mainParams' > 0@@ -73,7 +74,7 @@                            , RT.importRunDir = [reldir|.|]                            , RT.importStartYear = Nothing                            , RT.onlyNewFiles = False-                           , RT.hfVersion = versionInfo'+                           , RT.hfVersion = versionInfo                            , RT.hledgerInfo = hli                            , RT.sysInfo = systemInfo                            , RT.verbose = verbosity mainParams' > 0
hledger-flow.cabal view
@@ -4,10 +4,10 @@ -- -- see: https://github.com/sol/hpack ----- hash: cb2578ca8c6701d18ddc78599992215ad8c3b4f13ee2deb248bd30112365d3e4+-- hash: 7dede2e183b932b3b9af01bd855ca6725acdbefbc962fee1ed1eb113df327286  name:           hledger-flow-version:        0.14.2.0+version:        0.14.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@@ -35,7 +35,9 @@       Hledger.Flow.DocHelpers       Hledger.Flow.Import.CSVImport       Hledger.Flow.Import.ImportHelpers+      Hledger.Flow.Import.ImportHelpersTurtle       Hledger.Flow.Import.Types+      Hledger.Flow.Internals       Hledger.Flow.Logging       Hledger.Flow.PathHelpers       Hledger.Flow.Reports@@ -50,7 +52,9 @@       base >=4.7 && <5     , containers     , exceptions+    , filepath     , foldl+    , gitrev     , path     , path-io     , stm@@ -84,10 +88,13 @@       BaseDir.Integration       Common.Integration       Common.Unit+      CSVImport.ImportHelperTests+      CSVImport.ImportHelperTurtleTests       CSVImport.Integration       CSVImport.Unit       PathHelpers.Unit       TestHelpers+      TestHelpersTurtle       Paths_hledger_flow   hs-source-dirs:       test
src/Hledger/Flow/Common.hs view
@@ -1,7 +1,11 @@ {-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE QuasiQuotes #-}  module Hledger.Flow.Common where +import Path (absfile, relfile)+import qualified Path.IO as Path+ import qualified Turtle import Turtle ((%), (</>), (<.>)) @@ -12,66 +16,41 @@ import qualified GHC.IO.Handle.FD as H  import Data.Char (isDigit)-import Data.Maybe import Data.Either  import qualified Control.Foldl as Fold import qualified Data.Map.Strict as Map  import Data.Function (on)-import qualified Data.List as List (nub, null, sort, sortBy, groupBy)+import qualified Data.List as List (null, sortBy, groupBy) import Data.Ord (comparing)  import Hledger.Flow.Types-import qualified Hledger.Flow.Import.Types as IT  import Hledger.Flow.Logging-import Hledger.Flow.PathHelpers (TurtlePath)-import Hledger.Flow.BaseDir (turtleBaseDir, relativeToBase, relativeToBase')-import Hledger.Flow.DocHelpers (docURL)+import Hledger.Flow.PathHelpers (AbsFile, TurtlePath, fromTurtleAbsFile, pathToTurtle)+import Hledger.Flow.BaseDir (turtleBaseDir, relativeToBase)  import Control.Concurrent.STM -import qualified Data.List.NonEmpty as NE-import Paths_hledger_flow (version)-import qualified Data.Version as Version (showVersion)-import qualified System.Info as Sys--type InputFileBundle = Map.Map TurtlePath [TurtlePath]--versionInfo :: NE.NonEmpty Turtle.Line-versionInfo = Turtle.textToLines versionInfo'--versionInfo' :: T.Text-versionInfo' = T.pack ("hledger-flow " ++ Version.showVersion version ++ " " ++-                       os systemInfo ++ " " ++ arch systemInfo ++ " " ++-                       compilerName systemInfo ++ " " ++-                       Version.showVersion (compilerVersion systemInfo))--systemInfo :: SystemInfo-systemInfo = SystemInfo { os = Sys.os-                        , arch = Sys.arch-                        , compilerName = Sys.compilerName-                        , compilerVersion = Sys.compilerVersion-                        }--hledgerPathFromOption :: Maybe TurtlePath -> IO TurtlePath+hledgerPathFromOption :: Maybe TurtlePath -> IO AbsFile hledgerPathFromOption pathOption = do   case pathOption of     Just h  -> do-      isOnDisk <- Turtle.testfile h-      if isOnDisk then return h else do+      hlAbs <- fromTurtleAbsFile h+      isOnDisk <- Path.doesFileExist hlAbs+      if isOnDisk then return hlAbs else do         let msg = Turtle.format ("Unable to find hledger at "%Turtle.fp) h-        errExit' 1 (T.hPutStrLn H.stderr) msg h+        errExit' 1 (T.hPutStrLn H.stderr) msg hlAbs     Nothing -> do-      maybeH <- Turtle.which "hledger"+      maybeH <- Path.findExecutable [relfile|hledger|]       case maybeH of         Just h  -> return h         Nothing -> do           let msg = "Unable to find hledger in your path.\n"                 <> "You need to either install hledger, or add it to your PATH, or provide the path to an hledger executable.\n\n"                 <> "There are a number of installation options on the hledger website: https://hledger.org/download.html"-          errExit' 1 (T.hPutStrLn H.stderr) msg "/"+          errExit' 1 (T.hPutStrLn H.stderr) msg [absfile|/hledger|]  hledgerVersionFromPath :: TurtlePath -> IO T.Text hledgerVersionFromPath hlp = fmap (T.strip . Turtle.linesToText) (Turtle.single $ shellToList $ Turtle.inproc (Turtle.format Turtle.fp hlp) ["--version"] Turtle.empty)@@ -79,7 +58,7 @@ hledgerInfoFromPath :: Maybe TurtlePath -> IO HledgerInfo hledgerInfoFromPath pathOption = do   hlp <- hledgerPathFromOption pathOption-  hlv <- hledgerVersionFromPath hlp+  hlv <- hledgerVersionFromPath $ pathToTurtle hlp   return $ HledgerInfo hlp hlv  showCmdArgs :: [T.Text] -> T.Text@@ -185,36 +164,9 @@ groupValuesBy :: (Ord k, Ord v) => (v -> k) -> [v] -> Map.Map k [v] groupValuesBy keyFun = groupPairs . pairBy keyFun -initialIncludeFilePath :: TurtlePath -> TurtlePath-initialIncludeFilePath p = (Turtle.parent . Turtle.parent . Turtle.parent) p </> includeFileName p--parentIncludeFilePath :: TurtlePath -> TurtlePath-parentIncludeFilePath p = (Turtle.parent . Turtle.parent) p </> (Turtle.filename p)--allYearsPath :: TurtlePath -> TurtlePath-allYearsPath = allYearsPath' Turtle.directory--allYearsPath' :: (TurtlePath -> TurtlePath) -> TurtlePath -> TurtlePath-allYearsPath' dir p = dir p </> "all-years.journal"- allYearsFileName :: TurtlePath allYearsFileName = "all-years" <.> "journal" -groupIncludeFiles :: [TurtlePath] -> (InputFileBundle, InputFileBundle)-groupIncludeFiles = allYearIncludeFiles . groupIncludeFilesPerYear--groupIncludeFilesPerYear :: [TurtlePath] -> InputFileBundle-groupIncludeFilesPerYear [] = Map.empty-groupIncludeFilesPerYear ps@(p:_) = case extractImportDirs p of-    Right _ -> (groupValuesBy initialIncludeFilePath) ps-    Left  _ -> (groupValuesBy parentIncludeFilePath)  ps--allYearIncludeFiles :: InputFileBundle -> (InputFileBundle, InputFileBundle)-allYearIncludeFiles m = (m, yearsIncludeMap $ Map.keys m)--yearsIncludeMap :: [TurtlePath] -> InputFileBundle-yearsIncludeMap = groupValuesBy allYearsPath- lsDirs :: TurtlePath -> Turtle.Shell TurtlePath lsDirs = onlyDirs . Turtle.ls @@ -276,65 +228,6 @@ shellToList :: Turtle.Shell a -> Turtle.Shell [a] shellToList files = Turtle.fold files Fold.list -includeFileName :: TurtlePath -> TurtlePath-includeFileName = (<.> "journal") . Turtle.fromText . (Turtle.format (Turtle.fp%"-include")) . Turtle.dirname--toIncludeFiles :: (HasBaseDir o, HasVerbosity o) => o -> TChan LogMessage -> InputFileBundle -> IO (Map.Map TurtlePath T.Text)-toIncludeFiles opts ch m = do-  preMap  <- extraIncludes opts ch (Map.keys m) ["opening.journal"] ["pre-import.journal"] []-  postMap <- extraIncludes opts ch (Map.keys m) ["closing.journal"] ["post-import.journal"] ["prices.journal"]-  return $ (addPreamble . toIncludeFiles' preMap postMap) m--extraIncludes :: (HasBaseDir o, HasVerbosity o) => o -> TChan LogMessage -> [TurtlePath] -> [T.Text] -> [TurtlePath] -> [TurtlePath] -> IO InputFileBundle-extraIncludes opts ch = extraIncludes' opts ch Map.empty--extraIncludes' :: (HasBaseDir o, HasVerbosity o) => o -> TChan LogMessage -> InputFileBundle -> [TurtlePath] -> [T.Text] -> [TurtlePath] -> [TurtlePath] -> IO InputFileBundle-extraIncludes' _ _ acc [] _ _ _ = return acc-extraIncludes' opts ch acc (file:files) extraSuffixes manualFiles prices = do-  extra <- extraIncludesForFile opts ch file extraSuffixes manualFiles prices-  extraIncludes' opts ch (Map.unionWith (++) acc extra) files extraSuffixes manualFiles prices--extraIncludesForFile :: (HasVerbosity o, HasBaseDir o) => o -> TChan LogMessage -> TurtlePath -> [T.Text] -> [TurtlePath] -> [TurtlePath] -> IO InputFileBundle-extraIncludesForFile opts ch file extraSuffixes manualFiles prices = do-  let dirprefix = Turtle.fromText $ fst $ T.breakOn "-" $ Turtle.format Turtle.fp $ Turtle.basename file-  let fileNames = map (\suff -> Turtle.fromText $ Turtle.format (Turtle.fp%"-"%Turtle.s) dirprefix suff) extraSuffixes-  let suffixFiles = map (Turtle.directory file </>) fileNames-  let suffixDirFiles = map (Turtle.directory file </> "_manual_" </> dirprefix </>) manualFiles-  let priceFiles = map (Turtle.directory file </> ".." </> "prices" </> dirprefix </>) prices-  let extraFiles = suffixFiles ++ suffixDirFiles ++ priceFiles-  filtered <- Turtle.single $ filterPaths Turtle.testfile extraFiles-  let logMsg = Turtle.format ("Looking for possible extra include files for '"%Turtle.fp%"' among these "%Turtle.d%" options: "%Turtle.s%". Found "%Turtle.d%": "%Turtle.s)-               (relativeToBase opts file) (length extraFiles) (Turtle.repr $ relativeFilesAsText opts extraFiles)-               (length filtered) (Turtle.repr $ relativeFilesAsText opts filtered)-  logVerbose opts ch logMsg-  return $ Map.fromList [(file, filtered)]--relativeFilesAsText :: HasBaseDir o => o -> [TurtlePath] -> [T.Text]-relativeFilesAsText opts ps = map ((Turtle.format Turtle.fp) . (relativeToBase opts)) ps--toIncludeFiles' :: InputFileBundle -> InputFileBundle -> InputFileBundle -> Map.Map TurtlePath T.Text-toIncludeFiles' preMap postMap = Map.mapWithKey $ generatedIncludeText preMap postMap--addPreamble :: Map.Map TurtlePath T.Text -> Map.Map TurtlePath T.Text-addPreamble = Map.map (\txt -> includePreamble <> "\n" <> txt)--toIncludeLine :: TurtlePath -> TurtlePath -> T.Text-toIncludeLine base file = Turtle.format ("!include "%Turtle.fp) $ relativeToBase' base file--generatedIncludeText :: InputFileBundle -> InputFileBundle -> TurtlePath -> [TurtlePath] -> T.Text-generatedIncludeText preMap postMap outputFile fs = do-  let preFiles = fromMaybe [] $ Map.lookup outputFile preMap-  let files = List.nub . List.sort $ fs-  let postFiles = fromMaybe [] $ Map.lookup outputFile postMap-  let lns = map (toIncludeLine $ Turtle.directory outputFile) $ preFiles ++ files ++ postFiles-  T.intercalate "\n" $ lns ++ [""]--includePreamble :: T.Text-includePreamble = "### Generated by hledger-flow - DO NOT EDIT ###\n"--groupAndWriteIncludeFiles :: (HasBaseDir o, HasVerbosity o) => o -> TChan LogMessage -> [TurtlePath] -> IO [TurtlePath]-groupAndWriteIncludeFiles opts ch = writeFileMap opts ch . groupIncludeFiles- writeFiles :: IO (Map.Map TurtlePath T.Text) -> IO [TurtlePath] writeFiles fileMap = do   m <- fileMap@@ -348,26 +241,6 @@ writeTextMap :: Map.Map TurtlePath T.Text -> IO () writeTextMap = Map.foldlWithKey (\a k v -> a <> Turtle.writeTextFile k v) (return ()) -writeFileMap :: (HasBaseDir o, HasVerbosity o) => o -> TChan LogMessage -> (InputFileBundle, InputFileBundle) -> IO [TurtlePath]-writeFileMap opts ch (m, allYears) = do-  _ <- writeFiles' $ (addPreamble . toIncludeFiles' Map.empty Map.empty) allYears-  writeFiles . (toIncludeFiles opts ch) $ m--writeIncludesUpTo :: (HasBaseDir o, HasVerbosity o) => o -> TChan LogMessage -> TurtlePath -> [TurtlePath] -> IO [TurtlePath]-writeIncludesUpTo _ _ _ [] = return []-writeIncludesUpTo opts ch stopAt journalFiles = do-  let shouldStop = any (\dir -> dir == stopAt) $ map Turtle.parent journalFiles-  if shouldStop-    then return journalFiles-    else do-      newJournalFiles <- groupAndWriteIncludeFiles opts ch journalFiles-      writeIncludesUpTo opts ch stopAt newJournalFiles--writeToplevelAllYearsInclude :: (HasBaseDir o, HasVerbosity o) => o -> IO [TurtlePath]-writeToplevelAllYearsInclude opts = do-  let allTop = Map.singleton (turtleBaseDir opts </> allYearsFileName) ["import" </> allYearsFileName]-  writeFiles' $ (addPreamble . toIncludeFiles' Map.empty Map.empty) allTop- changeExtension :: T.Text -> TurtlePath -> TurtlePath changeExtension ext path = (Turtle.dropExtension path) <.> ext @@ -377,26 +250,6 @@ changeOutputPath :: TurtlePath -> TurtlePath -> TurtlePath changeOutputPath newOutputLocation srcFile = mconcat $ map changeSrcDir $ Turtle.splitDirectories srcFile   where changeSrcDir file = if file == "1-in/" || file == "2-preprocessed/" then newOutputLocation else file--importDirBreakdown ::  TurtlePath -> [TurtlePath]-importDirBreakdown = importDirBreakdown' []--importDirBreakdown' :: [TurtlePath] -> TurtlePath -> [TurtlePath]-importDirBreakdown' acc path = do-  let dir = Turtle.directory path-  if Turtle.dirname dir == "import" || (Turtle.dirname dir == "")-    then dir:acc-    else importDirBreakdown' (dir:acc) $ Turtle.parent dir--extractImportDirs :: TurtlePath -> Either T.Text IT.ImportDirs-extractImportDirs inputFile = do-  case importDirBreakdown inputFile of-    [bd,owner,bank,account,filestate,year] -> Right $ IT.ImportDirs bd owner bank account filestate year-    _ -> do-      Left $ Turtle.format ("I couldn't find the right number of directories between \"import\" and the input file:\n"%Turtle.fp-                      %"\n\nhledger-flow expects to find input files in this structure:\n"%-                      "import/owner/bank/account/filestate/year/trxfile\n\n"%-                      "Have a look at the documentation for a detailed explanation:\n"%Turtle.s) inputFile (docURL "input-files")  listOwners :: HasBaseDir o => o -> Turtle.Shell TurtlePath listOwners opts = fmap Turtle.basename $ lsDirs $ (turtleBaseDir opts) </> "import"
src/Hledger/Flow/Import/CSVImport.hs view
@@ -13,6 +13,7 @@ import Hledger.Flow.Import.Types import Hledger.Flow.BaseDir (relativeToBase, effectiveRunDir) import Hledger.Flow.Import.ImportHelpers+import Hledger.Flow.Import.ImportHelpersTurtle (extractImportDirs, writeIncludesUpTo, writeToplevelAllYearsInclude) import Hledger.Flow.PathHelpers (TurtlePath, pathToTurtle) import Hledger.Flow.DocHelpers (docURL) import Hledger.Flow.Common@@ -153,7 +154,7 @@   case maybeRulesFile of     Just rf -> do       let relRules = relativeToBase opts rf-      let hledger = Turtle.format Turtle.fp $ FlowTypes.hlPath . hledgerInfo $ opts :: T.Text+      let hledger = Turtle.format Turtle.fp $ pathToTurtle . FlowTypes.hlPath . hledgerInfo $ opts :: T.Text       let args = ["print", "--rules-file", Turtle.format Turtle.fp rf, "--file", Turtle.format Turtle.fp csvSrc, "--output-file", Turtle.format Turtle.fp journalOut]       let cmdLabel = Turtle.format ("importing '"%Turtle.fp%"' using rules file '"%Turtle.fp%"'") relCSV relRules       _ <- timeAndExitOnErr opts ch cmdLabel channelOut channelErr (parAwareProc opts) (hledger, args, Turtle.empty)@@ -204,7 +205,7 @@   let constructArgs = [Turtle.format Turtle.fp csvSrc, "-", Turtle.lineToText bank, Turtle.lineToText account, Turtle.lineToText owner]   let constructCmdText = Turtle.format ("Running: "%Turtle.fp%" "%Turtle.s) relScript (showCmdArgs constructArgs)   let stdLines = inprocWithErrFun (channelErrLn ch) (script, constructArgs, Turtle.empty)-  let hledger = Turtle.format Turtle.fp $ FlowTypes.hlPath . hledgerInfo $ opts :: T.Text+  let hledger = Turtle.format Turtle.fp $ pathToTurtle . FlowTypes.hlPath . hledgerInfo $ opts :: T.Text   let args = ["print", "--ignore-assertions", "--file", "-", "--output-file", Turtle.format Turtle.fp journalOut]   let relSrc = relativeToBase opts csvSrc   let cmdLabel = Turtle.format ("executing '"%Turtle.fp%"' on '"%Turtle.fp%"'") relScript relSrc
src/Hledger/Flow/Import/ImportHelpers.hs view
@@ -1,21 +1,27 @@ {-# LANGUAGE QuasiQuotes #-} -module Hledger.Flow.Import.ImportHelpers (findInputFiles, findJournalFiles) where+module Hledger.Flow.Import.ImportHelpers (findInputFiles, findJournalFiles, groupIncludesUpTo, includeFileName) where  import Path import Data.Char (isDigit)+import Data.Maybe (fromMaybe)+import System.FilePath (dropTrailingPathSeparator) -import Hledger.Flow.PathHelpers (AbsDir, AbsFile, RelDir, findFilesIn)+import Hledger.Flow.Common (groupValuesBy)+import Hledger.Flow.PathHelpers (AbsDir, AbsFile, RelDir, RelFile, findFilesIn, pathSize)+import Hledger.Flow.Import.Types (InputFileBundle) +import qualified Data.Map.Strict as Map+ findInputFiles :: Integer -> AbsDir -> IO [AbsFile] findInputFiles startYear = do-  let excludeDirs = [[Path.reldir|2-preprocessed|], [Path.reldir|3-journal|]] ++ commonExcludeDirs-  findFilesIn (includeYearFilesForParent [Path.reldir|1-in|] startYear) excludeDirs+  let excludeDirs = [[reldir|2-preprocessed|], [reldir|3-journal|]] ++ commonExcludeDirs+  findFilesIn (includeYearFilesForParent [reldir|1-in|] startYear) excludeDirs  findJournalFiles :: AbsDir -> IO [AbsFile] findJournalFiles = do-  let excludeDirs = [[Path.reldir|1-in|], [Path.reldir|2-preprocessed|]] ++ commonExcludeDirs-  findFilesIn (includeYearFilesForParent [Path.reldir|3-journal|] 0) excludeDirs+  let excludeDirs = [[reldir|1-in|], [reldir|2-preprocessed|]] ++ commonExcludeDirs+  findFilesIn (includeYearFilesForParent [reldir|3-journal|] 0) excludeDirs  -- | Include only files directly underneath parentDir/yearDir, e.g. 1-in/2020/* or 3-journal/2020/* includeYearFilesForParent :: RelDir -> Integer -> AbsDir -> Bool@@ -29,4 +35,33 @@ dirToStringNoSlash = init . Path.toFilePath . Path.dirname  commonExcludeDirs :: [RelDir]-commonExcludeDirs = [[Path.reldir|_manual_|], [Path.reldir|__pycache__|]]+commonExcludeDirs = [[reldir|_manual_|], [reldir|__pycache__|]]++groupIncludesUpTo :: RelDir -> [RelFile] -> InputFileBundle+groupIncludesUpTo = groupIncludesUpTo' Map.empty++groupIncludesUpTo' :: InputFileBundle -> RelDir -> [RelFile] -> InputFileBundle+groupIncludesUpTo' acc _ [] = acc+groupIncludesUpTo' acc stopAt journals = do+  let dirs = map parent journals :: [RelDir]+  let shouldStop = stopAt `elem` dirs+  if shouldStop then acc else do+    let grouped = groupIncludeFilesPerYear journals+    groupIncludesUpTo' (acc <> grouped) stopAt (Map.keys grouped)++groupIncludeFilesPerYear :: [RelFile] -> InputFileBundle+groupIncludeFilesPerYear [] = Map.empty+groupIncludeFilesPerYear ps@(p:_) = if pathSize (parent p) == 6+  then groupValuesBy initialIncludeFilePath ps+  else groupValuesBy parentIncludeFilePath ps++initialIncludeFilePath :: RelFile -> RelFile+initialIncludeFilePath p = (parent . parent . parent) p </> includeFileName p++parentIncludeFilePath :: RelFile -> RelFile+parentIncludeFilePath p = (parent . parent) p </> filename p++includeFileName :: RelFile -> RelFile+includeFileName f = do+  let includeFile = (dropTrailingPathSeparator . toFilePath . dirname . parent) f ++ "-include.journal"+  fromMaybe [relfile|unknown-include.journal|] $ parseRelFile includeFile
+ src/Hledger/Flow/Import/ImportHelpersTurtle.hs view
@@ -0,0 +1,168 @@+{-# LANGUAGE OverloadedStrings #-}++-- | Functions which currently uses TurtlePath and will be replaced with Path eventually+module Hledger.Flow.Import.ImportHelpersTurtle (+    allYearIncludeFiles+  , extractImportDirs+  , extraIncludesForFile+  , groupIncludeFiles+  , groupAndWriteIncludeFiles+  , includePreamble+  , toIncludeFiles+  , toIncludeLine+  , writeIncludesUpTo+  , writeToplevelAllYearsInclude+  , yearsIncludeMap+  )+ where++import Hledger.Flow.PathHelpers (TurtlePath)+import Hledger.Flow.DocHelpers (docURL)+import Hledger.Flow.Common (allYearsFileName, filterPaths, groupValuesBy, writeFiles, writeFiles')+import Hledger.Flow.BaseDir (relativeToBase, relativeToBase', turtleBaseDir)+import Hledger.Flow.Logging (logVerbose)++import Hledger.Flow.Types+import Hledger.Flow.Import.Types++import qualified Data.List as List (nub, sort)+import qualified Data.Text as T+import qualified Data.Map.Strict as Map++import qualified Turtle+import Turtle ((%), (</>), (<.>))++import Control.Concurrent.STM (TChan)+import Data.Maybe (fromMaybe)++extractImportDirs :: TurtlePath -> Either T.Text ImportDirs+extractImportDirs inputFile = do+  case importDirBreakdown inputFile of+    [bd,owner,bank,account,filestate,year] -> Right $ ImportDirs bd owner bank account filestate year+    _ -> do+      Left $ Turtle.format ("I couldn't find the right number of directories between \"import\" and the input file:\n"%Turtle.fp+                      %"\n\nhledger-flow expects to find input files in this structure:\n"%+                      "import/owner/bank/account/filestate/year/trxfile\n\n"%+                      "Have a look at the documentation for a detailed explanation:\n"%Turtle.s) inputFile (docURL "input-files")++importDirBreakdown ::  TurtlePath -> [TurtlePath]+importDirBreakdown = importDirBreakdown' []++importDirBreakdown' :: [TurtlePath] -> TurtlePath -> [TurtlePath]+importDirBreakdown' acc path = do+  let dir = Turtle.directory path+  if Turtle.dirname dir == "import" || (Turtle.dirname dir == "")+    then dir:acc+    else importDirBreakdown' (dir:acc) $ Turtle.parent dir++groupIncludeFiles :: [TurtlePath] -> (TurtleFileBundle, TurtleFileBundle)+groupIncludeFiles = allYearIncludeFiles . groupIncludeFilesPerYear . filter isJournalFile++isJournalFile :: TurtlePath -> Bool+isJournalFile f = Turtle.extension f == Just "journal"++allYearIncludeFiles :: TurtleFileBundle -> (TurtleFileBundle, TurtleFileBundle)+allYearIncludeFiles m = (m, yearsIncludeMap $ Map.keys m)++yearsIncludeMap :: [TurtlePath] -> TurtleFileBundle+yearsIncludeMap = groupValuesBy allYearsPath++allYearsPath :: TurtlePath -> TurtlePath+allYearsPath = allYearsPath' Turtle.directory++allYearsPath' :: (TurtlePath -> TurtlePath) -> TurtlePath -> TurtlePath+allYearsPath' dir p = dir p </> allYearsFileName++includeFileName :: TurtlePath -> TurtlePath+includeFileName = (<.> "journal") . Turtle.fromText . (Turtle.format (Turtle.fp%"-include")) . Turtle.dirname++groupIncludeFilesPerYear :: [TurtlePath] -> TurtleFileBundle+groupIncludeFilesPerYear [] = Map.empty+groupIncludeFilesPerYear ps@(p:_) = case extractImportDirs p of+    Right _ -> groupValuesBy initialIncludeFilePath ps+    Left  _ -> groupValuesBy parentIncludeFilePath ps++initialIncludeFilePath :: TurtlePath -> TurtlePath+initialIncludeFilePath p = (Turtle.parent . Turtle.parent . Turtle.parent) p </> includeFileName p++parentIncludeFilePath :: TurtlePath -> TurtlePath+parentIncludeFilePath p = (Turtle.parent . Turtle.parent) p </> Turtle.filename p++toIncludeFiles :: (HasBaseDir o, HasVerbosity o) => o -> TChan LogMessage -> TurtleFileBundle -> IO (Map.Map TurtlePath T.Text)+toIncludeFiles opts ch m = do+  preMap  <- extraIncludes opts ch (Map.keys m) ["opening.journal"] ["pre-import.journal"] []+  postMap <- extraIncludes opts ch (Map.keys m) ["closing.journal"] ["post-import.journal"] ["prices.journal"]+  return $ (addPreamble . toIncludeFiles' preMap postMap) m++toIncludeFiles' :: TurtleFileBundle -> TurtleFileBundle -> TurtleFileBundle -> Map.Map TurtlePath T.Text+toIncludeFiles' preMap postMap = Map.mapWithKey $ generatedIncludeText preMap postMap++addPreamble :: Map.Map TurtlePath T.Text -> Map.Map TurtlePath T.Text+addPreamble = Map.map (\txt -> includePreamble <> "\n" <> txt)++toIncludeLine :: TurtlePath -> TurtlePath -> T.Text+toIncludeLine base file = Turtle.format ("!include "%Turtle.fp) $ relativeToBase' base file++generatedIncludeText :: TurtleFileBundle -> TurtleFileBundle -> TurtlePath -> [TurtlePath] -> T.Text+generatedIncludeText preMap postMap outputFile fs = do+  let preFiles = fromMaybe [] $ Map.lookup outputFile preMap+  let files = List.nub . List.sort $ fs+  let postFiles = fromMaybe [] $ Map.lookup outputFile postMap+  let lns = map (toIncludeLine $ Turtle.directory outputFile) $ preFiles ++ files ++ postFiles+  T.intercalate "\n" $ lns ++ [""]++includePreamble :: T.Text+includePreamble = "### Generated by hledger-flow - DO NOT EDIT ###\n"++groupAndWriteIncludeFiles :: (HasBaseDir o, HasVerbosity o) => o -> TChan LogMessage -> [TurtlePath] -> IO [TurtlePath]+groupAndWriteIncludeFiles opts ch = writeFileMap opts ch . groupIncludeFiles++writeFileMap :: (HasBaseDir o, HasVerbosity o) => o -> TChan LogMessage -> (TurtleFileBundle, TurtleFileBundle) -> IO [TurtlePath]+writeFileMap opts ch (m, allYears) = do+  _ <- writeFiles' $ (addPreamble . toIncludeFiles' Map.empty Map.empty) allYears+  writeFiles . (toIncludeFiles opts ch) $ m++writeIncludesUpTo :: (HasBaseDir o, HasVerbosity o) => o -> TChan LogMessage -> TurtlePath -> [TurtlePath] -> IO [TurtlePath]+writeIncludesUpTo _ _ _ [] = return []+writeIncludesUpTo opts ch stopAt journalFiles = do+  let shouldStop = any (\dir -> dir == stopAt) $ map Turtle.parent journalFiles+  if shouldStop+    then return journalFiles+    else do+      newJournalFiles <- groupAndWriteIncludeFiles opts ch journalFiles+      writeIncludesUpTo opts ch stopAt newJournalFiles++writeToplevelAllYearsInclude :: (HasBaseDir o, HasVerbosity o) => o -> IO [TurtlePath]+writeToplevelAllYearsInclude opts = do+  let directivesFile = turtleBaseDir opts </> "directives" <.> "journal"+  directivesExists <- Turtle.testfile directivesFile+  let preMap = if directivesExists then Map.singleton (turtleBaseDir opts </> allYearsFileName) [directivesFile] else Map.empty+  let allTop = Map.singleton (turtleBaseDir opts </> allYearsFileName) ["import" </> allYearsFileName]+  writeFiles' $ (addPreamble . toIncludeFiles' preMap Map.empty) allTop++extraIncludes :: (HasBaseDir o, HasVerbosity o) => o -> TChan LogMessage -> [TurtlePath] -> [T.Text] -> [TurtlePath] -> [TurtlePath] -> IO TurtleFileBundle+extraIncludes opts ch = extraIncludes' opts ch Map.empty++extraIncludes' :: (HasBaseDir o, HasVerbosity o) => o -> TChan LogMessage -> TurtleFileBundle -> [TurtlePath] -> [T.Text] -> [TurtlePath] -> [TurtlePath] -> IO TurtleFileBundle+extraIncludes' _ _ acc [] _ _ _ = return acc+extraIncludes' opts ch acc (file:files) extraSuffixes manualFiles prices = do+  extra <- extraIncludesForFile opts ch file extraSuffixes manualFiles prices+  extraIncludes' opts ch (Map.unionWith (++) acc extra) files extraSuffixes manualFiles prices++extraIncludesForFile :: (HasVerbosity o, HasBaseDir o) => o -> TChan LogMessage -> TurtlePath -> [T.Text] -> [TurtlePath] -> [TurtlePath] -> IO TurtleFileBundle+extraIncludesForFile opts ch file extraSuffixes manualFiles prices = do+  let dirprefix = Turtle.fromText $ fst $ T.breakOn "-" $ Turtle.format Turtle.fp $ Turtle.basename file+  let fileNames = map (Turtle.fromText . Turtle.format (Turtle.fp % "-" % Turtle.s) dirprefix) extraSuffixes+  let suffixFiles = map (Turtle.directory file </>) fileNames+  let suffixDirFiles = map (Turtle.directory file </> "_manual_" </> dirprefix </>) manualFiles+  let priceFiles = map (Turtle.directory file </> ".." </> "prices" </> dirprefix </>) prices+  let extraFiles = suffixFiles ++ suffixDirFiles ++ priceFiles+  filtered <- Turtle.single $ filterPaths Turtle.testfile extraFiles+  let logMsg = Turtle.format ("Looking for possible extra include files for '"%Turtle.fp%"' among these "%Turtle.d%" options: "%Turtle.s%". Found "%Turtle.d%": "%Turtle.s)+               (relativeToBase opts file) (length extraFiles) (Turtle.repr $ relativeFilesAsText opts extraFiles)+               (length filtered) (Turtle.repr $ relativeFilesAsText opts filtered)+  logVerbose opts ch logMsg+  return $ Map.fromList [(file, filtered)]++relativeFilesAsText :: HasBaseDir o => o -> [TurtlePath] -> [T.Text]+relativeFilesAsText opts = map (Turtle.format Turtle.fp . relativeToBase opts)
src/Hledger/Flow/Import/Types.hs view
@@ -1,7 +1,10 @@-module Hledger.Flow.Import.Types-where+module Hledger.Flow.Import.Types where -import Hledger.Flow.PathHelpers (TurtlePath)+import Hledger.Flow.PathHelpers (RelFile, TurtlePath)+import qualified Data.Map.Strict as Map++type TurtleFileBundle = Map.Map TurtlePath [TurtlePath]+type InputFileBundle = Map.Map RelFile [RelFile]  data ImportDirs = ImportDirs { importDir  :: TurtlePath                              , ownerDir   :: TurtlePath
+ src/Hledger/Flow/Internals.hs view
@@ -0,0 +1,31 @@+{-# LANGUAGE TemplateHaskell #-}++module Hledger.Flow.Internals where++import Development.GitRev+import Data.Version (Version, showVersion)+import Paths_hledger_flow (version)++import qualified Data.Text as T+import qualified System.Info as Sys++data SystemInfo = SystemInfo { os :: String+                             , arch :: String+                             , compilerName :: String+                             , compilerVersion :: Version+                             }+                deriving (Show)++versionInfo :: T.Text+versionInfo = T.pack ("hledger-flow " ++ showVersion version ++ " " +++                       os systemInfo ++ " " ++ arch systemInfo ++ " " +++                       compilerName systemInfo ++ " " +++                       showVersion (compilerVersion systemInfo) +++                       " " ++ $(gitHash))++systemInfo :: SystemInfo+systemInfo = SystemInfo { os = Sys.os+                        , arch = Sys.arch+                        , compilerName = Sys.compilerName+                        , compilerVersion = Sys.compilerVersion+                        }
src/Hledger/Flow/Reports.hs view
@@ -18,7 +18,7 @@  import qualified Data.Text as T import qualified Hledger.Flow.Types as FlowTypes-import Hledger.Flow.PathHelpers (TurtlePath)+import Hledger.Flow.PathHelpers (TurtlePath, pathToTurtle) import Hledger.Flow.Logging import qualified Data.List as List @@ -112,7 +112,7 @@   let relativeOutputFile = relativeToBase opts outputFile   let reportArgs = ["--file", Turtle.format Turtle.fp journal, "--period", Turtle.repr year] ++ args   let reportDisplayArgs = ["--file", Turtle.format Turtle.fp relativeJournal, "--period", Turtle.repr year] ++ args-  let hledger = Turtle.format Turtle.fp $ FlowTypes.hlPath . hledgerInfo $ opts :: T.Text+  let hledger = Turtle.format Turtle.fp $ pathToTurtle . FlowTypes.hlPath . hledgerInfo $ opts :: T.Text   let cmdLabel = Turtle.format ("hledger "%Turtle.s) $ showCmdArgs reportDisplayArgs   ((exitCode, stdOut, _), _) <- timeAndExitOnErr opts ch cmdLabel dummyLogger channelErr Turtle.procStrictWithErr (hledger, reportArgs, mempty)   if (successCheck stdOut)
src/Hledger/Flow/RuntimeOptions.hs view
@@ -4,6 +4,7 @@ import qualified Data.Text as T import Prelude hiding (putStrLn) import Hledger.Flow.Types+import Hledger.Flow.Internals (SystemInfo)  data RuntimeOptions = RuntimeOptions { baseDir :: BaseDir                                      , importRunDir :: RunDir
src/Hledger/Flow/Types.hs view
@@ -1,11 +1,9 @@-{-# LANGUAGE TypeSynonymInstances #-} {-# LANGUAGE FlexibleInstances #-}  module Hledger.Flow.Types where  import qualified Turtle (ExitCode, NominalDiffTime, Shell, Line) import qualified Data.Text as T-import Data.Version  import Hledger.Flow.PathHelpers @@ -19,17 +17,10 @@ type ProcFun = T.Text -> [T.Text] -> Turtle.Shell Turtle.Line -> IO FullOutput type ProcInput = (T.Text, [T.Text], Turtle.Shell Turtle.Line) -data HledgerInfo = HledgerInfo { hlPath :: TurtlePath+data HledgerInfo = HledgerInfo { hlPath :: AbsFile                                , hlVersion :: T.Text                                }                  deriving (Show)--data SystemInfo = SystemInfo { os :: String-                             , arch :: String-                             , compilerName :: String-                             , compilerVersion :: Version-                             }-                deriving (Show)  class HasVerbosity a where   verbose :: a -> Bool
+ test/CSVImport/ImportHelperTests.hs view
@@ -0,0 +1,119 @@+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE OverloadedLists #-}+{-# LANGUAGE QuasiQuotes #-}++module CSVImport.ImportHelperTests where++import Test.HUnit++import Hledger.Flow.Import.Types (InputFileBundle)+import Hledger.Flow.Import.ImportHelpers (groupIncludesUpTo, includeFileName)++import Path+import TestHelpers++testToJournal :: Test+testToJournal = TestCase (+  do+    let journal = toJournal [relfile|import/jane/bogartbank/investment/1-in/2020/2020-09-30.csv|]+    let expected = [relfile|import/jane/bogartbank/investment/3-journal/2020/2020-09-30.journal|]+    assertEqual "toJournal" expected journal+  )++testIncludeFileName :: Test+testIncludeFileName = TestCase (+  do+    let includeFile = includeFileName [relfile|import/jane/bogartbank/investment/3-journals/2020/2020-09-30.journal|]+    assertEqual "includeFileName" [relfile|2020-include.journal|] includeFile+  )++testGroupIncludesUpToTinySet :: Test+testGroupIncludesUpToTinySet = TestCase (+  do+    let expected = [+           ([relfile|import/jane/bogartbank/savings/2017-include.journal|], [janeSavingsJournal2017])+         , ([relfile|import/jane/bogartbank/2017-include.journal|], [[relfile|import/jane/bogartbank/savings/2017-include.journal|]])+         , ([relfile|import/jane/2017-include.journal|],            [[relfile|import/jane/bogartbank/2017-include.journal|]])+         ] :: InputFileBundle++    let grouped = groupIncludesUpTo [reldir|import/jane|] [janeSavingsJournal2017]+    assertEqual "groupIncludesUpTo: A single journal file grouping" expected grouped+  )++testGroupIncludesUpToSmallSet :: Test+testGroupIncludesUpToSmallSet = TestCase (+  do+    let expected = [+           ([relfile|import/jane/bogartbank/savings/2017-include.journal|], [janeSavingsJournal2017])+         , ([relfile|import/jane/bogartbank/savings/2018-include.journal|], janeSavingsJournals2018)+         , ([relfile|import/jane/bogartbank/savings/2019-include.journal|], [janeSavingsJournal2019])+         , ([relfile|import/jane/bogartbank/2017-include.journal|], [[relfile|import/jane/bogartbank/savings/2017-include.journal|]])+         , ([relfile|import/jane/bogartbank/2018-include.journal|], [[relfile|import/jane/bogartbank/savings/2018-include.journal|]])+         , ([relfile|import/jane/bogartbank/2019-include.journal|], [[relfile|import/jane/bogartbank/savings/2019-include.journal|]])+         , ([relfile|import/jane/2017-include.journal|],            [[relfile|import/jane/bogartbank/2017-include.journal|]])+         , ([relfile|import/jane/2018-include.journal|],            [[relfile|import/jane/bogartbank/2018-include.journal|]])+         , ([relfile|import/jane/2019-include.journal|],            [[relfile|import/jane/bogartbank/2019-include.journal|]])+         ] :: InputFileBundle++    let grouped = groupIncludesUpTo [reldir|import/jane|] janeSavingsJournals+    assertEqual "groupIncludesUpTo: A small set of journal files - same account over 3 years" expected grouped+  )++testGroupIncludesUpTo :: Test+testGroupIncludesUpTo = TestCase (+  do+    let expected = [+           ([relfile|import/john/bogartbank/savings/2017-include.journal|], johnSavingsJournals2017)+         , ([relfile|import/john/bogartbank/savings/2018-include.journal|], johnSavingsJournals2018)+         , ([relfile|import/john/bogartbank/checking/2018-include.journal|], johnCheckingJournals2018)+         , ([relfile|import/john/bogartbank/checking/2019-include.journal|], johnCheckingJournals2019)++         , ([relfile|import/john/otherbank/creditcard/2017-include.journal|], [johnCCJournal2017])+         , ([relfile|import/john/otherbank/creditcard/2018-include.journal|], [johnCCJournal2018])+         , ([relfile|import/john/otherbank/investments/2018-include.journal|], [johnInvestJournal2018])+         , ([relfile|import/john/otherbank/investments/2019-include.journal|], [johnInvestJournal2019])++         , ([relfile|import/jane/bogartbank/savings/2017-include.journal|], [janeSavingsJournal2017])+         , ([relfile|import/jane/bogartbank/savings/2018-include.journal|], janeSavingsJournals2018)+         , ([relfile|import/jane/bogartbank/savings/2019-include.journal|], [janeSavingsJournal2019])++         , ([relfile|import/jane/otherbank/creditcard/2017-include.journal|], [janeCCJournal2017])+         , ([relfile|import/jane/otherbank/creditcard/2018-include.journal|], [janeCCJournal2018])+         , ([relfile|import/jane/otherbank/investments/2018-include.journal|], [janeInvestJournal2018])+         , ([relfile|import/jane/otherbank/investments/2019-include.journal|], [janeInvestJournal2019])++         , ([relfile|import/john/bogartbank/2017-include.journal|], [[relfile|import/john/bogartbank/savings/2017-include.journal|]])+         , ([relfile|import/john/bogartbank/2018-include.journal|], [[relfile|import/john/bogartbank/checking/2018-include.journal|], [relfile|import/john/bogartbank/savings/2018-include.journal|]])+         , ([relfile|import/john/bogartbank/2019-include.journal|], [[relfile|import/john/bogartbank/checking/2019-include.journal|]])++         , ([relfile|import/john/otherbank/2017-include.journal|], [[relfile|import/john/otherbank/creditcard/2017-include.journal|]])+         , ([relfile|import/john/otherbank/2018-include.journal|], [[relfile|import/john/otherbank/creditcard/2018-include.journal|], [relfile|import/john/otherbank/investments/2018-include.journal|]])+         , ([relfile|import/john/otherbank/2019-include.journal|], [[relfile|import/john/otherbank/investments/2019-include.journal|]])++         , ([relfile|import/jane/bogartbank/2017-include.journal|], [[relfile|import/jane/bogartbank/savings/2017-include.journal|]])+         , ([relfile|import/jane/bogartbank/2018-include.journal|], [[relfile|import/jane/bogartbank/savings/2018-include.journal|]])+         , ([relfile|import/jane/bogartbank/2019-include.journal|], [[relfile|import/jane/bogartbank/savings/2019-include.journal|]])++         , ([relfile|import/jane/otherbank/2017-include.journal|], [[relfile|import/jane/otherbank/creditcard/2017-include.journal|]])+         , ([relfile|import/jane/otherbank/2018-include.journal|], [[relfile|import/jane/otherbank/creditcard/2018-include.journal|], [relfile|import/jane/otherbank/investments/2018-include.journal|]])+         , ([relfile|import/jane/otherbank/2019-include.journal|], [[relfile|import/jane/otherbank/investments/2019-include.journal|]])++         , ([relfile|import/john/2017-include.journal|],            [[relfile|import/john/bogartbank/2017-include.journal|], [relfile|import/john/otherbank/2017-include.journal|]])+         , ([relfile|import/john/2018-include.journal|],            [[relfile|import/john/bogartbank/2018-include.journal|], [relfile|import/john/otherbank/2018-include.journal|]])+         , ([relfile|import/john/2019-include.journal|],            [[relfile|import/john/bogartbank/2019-include.journal|], [relfile|import/john/otherbank/2019-include.journal|]])++         , ([relfile|import/jane/2017-include.journal|],            [[relfile|import/jane/bogartbank/2017-include.journal|], [relfile|import/jane/otherbank/2017-include.journal|]])+         , ([relfile|import/jane/2018-include.journal|],            [[relfile|import/jane/bogartbank/2018-include.journal|], [relfile|import/jane/otherbank/2018-include.journal|]])+         , ([relfile|import/jane/2019-include.journal|],            [[relfile|import/jane/bogartbank/2019-include.journal|], [relfile|import/jane/otherbank/2019-include.journal|]])++         , ([relfile|import/2017-include.journal|],            [[relfile|import/jane/2017-include.journal|], [relfile|import/john/2017-include.journal|]])+         , ([relfile|import/2018-include.journal|],            [[relfile|import/jane/2018-include.journal|], [relfile|import/john/2018-include.journal|]])+         , ([relfile|import/2019-include.journal|],            [[relfile|import/jane/2019-include.journal|], [relfile|import/john/2019-include.journal|]])+         ] :: InputFileBundle++    let grouped = groupIncludesUpTo [reldir|import|] journalFiles+    assertEqual "groupIncludesUpTo: A full set of journal files" expected grouped+  )++tests :: Test+tests = TestList [testToJournal, testIncludeFileName, testGroupIncludesUpToTinySet, testGroupIncludesUpToSmallSet, testGroupIncludesUpTo]
+ test/CSVImport/ImportHelperTurtleTests.hs view
@@ -0,0 +1,432 @@+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE OverloadedLists #-}+{-# LANGUAGE QuasiQuotes #-}++module CSVImport.ImportHelperTurtleTests where++import Test.HUnit+import Path+import Turtle+import qualified Data.Map.Strict as Map+import Control.Concurrent.STM++import TestHelpers (defaultOpts)+import TestHelpersTurtle+import Hledger.Flow.PathHelpers (TurtlePath)+import Hledger.Flow.Common+import Hledger.Flow.Import.Types (TurtleFileBundle)+import Hledger.Flow.Import.ImportHelpersTurtle (allYearIncludeFiles, groupIncludeFiles, toIncludeFiles, toIncludeLine, yearsIncludeMap)++import Data.Either+import qualified Data.Text as T++groupedJaneBogart :: TurtleFileBundle+groupedJaneBogart = [+  ("./import/jane/bogartbank/checking/2018-include.journal",+   ["import/jane/bogartbank/checking/3-journal/2018/2018-12-30.journal"]),+  ("./import/jane/bogartbank/checking/2019-include.journal",+   ["import/jane/bogartbank/checking/3-journal/2019/2019-01-30.journal"]),+  ("./import/jane/bogartbank/savings/2017-include.journal",+   ["import/jane/bogartbank/savings/3-journal/2017/2017-12-30.journal"]),+  ("./import/jane/bogartbank/savings/2018-include.journal",+   ["import/jane/bogartbank/savings/3-journal/2018/2018-01-30.journal"])]++groupedJaneOther :: TurtleFileBundle+groupedJaneOther = [+  ("./import/jane/otherbank/creditcard/2017-include.journal",+   ["import/jane/otherbank/creditcard/3-journal/2017/2017-12-30.journal"]),+  ("./import/jane/otherbank/creditcard/2018-include.journal",+   ["import/jane/otherbank/creditcard/3-journal/2018/2018-01-30.journal"]),+  ("./import/jane/otherbank/investments/2018-include.journal",+   ["import/jane/otherbank/investments/3-journal/2018/2018-12-30.journal"]),+  ("./import/jane/otherbank/investments/2019-include.journal",+   ["import/jane/otherbank/investments/3-journal/2019/2019-01-30.journal"])]++groupedJohnBogart :: TurtleFileBundle+groupedJohnBogart = [+  ("./import/john/bogartbank/checking/2018-include.journal",+   ["import/john/bogartbank/checking/3-journal/2018/2018-11-30.journal",+    "import/john/bogartbank/checking/3-journal/2018/2018-10-30.journal",+    "import/john/bogartbank/checking/3-journal/2018/2018-12-30.journal"]),+  ("./import/john/bogartbank/checking/2019-include.journal",+   ["import/john/bogartbank/checking/3-journal/2019/2019-01-30.journal",+    "import/john/bogartbank/checking/3-journal/2019/2019-02-30.journal"]),+  ("./import/john/bogartbank/savings/2017-include.journal",+   ["import/john/bogartbank/savings/3-journal/2017/2017-11-30.journal",+    "import/john/bogartbank/savings/3-journal/2017/2017-12-30.journal"]),+  ("./import/john/bogartbank/savings/2018-include.journal",+   ["import/john/bogartbank/savings/3-journal/2018/2018-02-30.journal",+    "import/john/bogartbank/savings/3-journal/2018/2018-01-30.journal"])]++groupedJohnOther :: TurtleFileBundle+groupedJohnOther = [+  ("./import/john/otherbank/creditcard/2017-include.journal",+   ["import/john/otherbank/creditcard/3-journal/2017/2017-12-30.journal"]),+  ("./import/john/otherbank/creditcard/2018-include.journal",+   ["import/john/otherbank/creditcard/3-journal/2018/2018-01-30.journal"]),+  ("./import/john/otherbank/investments/2018-include.journal",+   ["import/john/otherbank/investments/3-journal/2018/2018-12-30.journal"]),+  ("./import/john/otherbank/investments/2019-include.journal",+   ["import/john/otherbank/investments/3-journal/2019/2019-01-30.journal"])]++groupedIncludeFiles :: TurtleFileBundle+groupedIncludeFiles = groupedJaneBogart <> groupedJaneOther <>+                      groupedJohnBogart <> groupedJohnOther++testYearsIncludeMap :: Test+testYearsIncludeMap = TestCase (+  do+    let maps = allYearIncludeFiles groupedJohnOther+    let yearGrouping = [("./import/john/otherbank/creditcard/all-years.journal",+                         ["./import/john/otherbank/creditcard/2017-include.journal",+                          "./import/john/otherbank/creditcard/2018-include.journal"]),+                        ("./import/john/otherbank/investments/all-years.journal",+                         ["./import/john/otherbank/investments/2018-include.journal",+                          "./import/john/otherbank/investments/2019-include.journal"])]+    let expected = (groupedJohnOther, yearGrouping)+    assertEqual "An augmented map with grouped years per level added" expected maps+  )++testYearsIncludeGrouping :: Test+testYearsIncludeGrouping = TestCase (+  do+    let yearsMap = yearsIncludeMap (Map.keys groupedJohnOther)+    let expected = [("./import/john/otherbank/creditcard/all-years.journal",+                     ["./import/john/otherbank/creditcard/2017-include.journal",+                      "./import/john/otherbank/creditcard/2018-include.journal"]),+                    ("./import/john/otherbank/investments/all-years.journal",+                     ["./import/john/otherbank/investments/2018-include.journal",+                      "./import/john/otherbank/investments/2019-include.journal"])]+    assertEqual "A basic map with grouped years per level" expected yearsMap+  )++testGroupIncludeFilesTinySet :: Test+testGroupIncludeFilesTinySet = TestCase (+  do+    let journals1 = [   "import/jane/bogartbank/savings/3-journals/2017/2017-12-30.journal"]+    let expected1 = [("./import/jane/bogartbank/savings/2017-include.journal", journals1)] :: TurtleFileBundle+    let expectedAllYears1 = [("./import/jane/bogartbank/savings/all-years.journal", ["./import/jane/bogartbank/savings/2017-include.journal"])]+    let (group1, allYears1) = groupIncludeFiles journals1+    assertEqual "groupIncludeFiles small allYears 1" expectedAllYears1 allYears1+    assertEqual "groupIncludeFiles small set 1" expected1 group1++    let journals2 = [(fst . head . Map.toList) expected1] :: [TurtlePath]+    let expected2 = [("./import/jane/bogartbank/2017-include.journal", journals2)] :: TurtleFileBundle+    let expectedAllYears2 = [("./import/jane/bogartbank/all-years.journal", ["./import/jane/bogartbank/2017-include.journal"])]+    let (group2, allYears2) = groupIncludeFiles journals2+    assertEqual "groupIncludeFiles small allYears 2" expectedAllYears2 allYears2+    assertEqual "groupIncludeFiles small set 2" expected2 group2++    let journals3 = [(fst . head . Map.toList) expected2] :: [TurtlePath]+    let expected3 = [("./import/jane/2017-include.journal", journals3)] :: TurtleFileBundle+    let expectedAllYears3 = [("./import/jane/all-years.journal", ["./import/jane/2017-include.journal"])]+    let (group3, allYears3) = groupIncludeFiles journals3+    assertEqual "groupIncludeFiles small allYears 3" expectedAllYears3 allYears3+    assertEqual "groupIncludeFiles small set 3" expected3 group3+  )++testGroupIncludeFilesSmallSet :: Test+testGroupIncludeFilesSmallSet = TestCase (+  do+    let (group1, allYears1) = groupIncludeFiles (toJournals inputJaneBogart)+    let expectedAllYears1 = [+          ("./import/jane/bogartbank/checking/all-years.journal",+           ["./import/jane/bogartbank/checking/2018-include.journal",+            "./import/jane/bogartbank/checking/2019-include.journal"]),+          ("./import/jane/bogartbank/savings/all-years.journal",+           ["./import/jane/bogartbank/savings/2017-include.journal",+            "./import/jane/bogartbank/savings/2018-include.journal"])]++    assertEqual "groupIncludeFiles Jane AllYears 1" expectedAllYears1 allYears1+    assertEqual "groupIncludeFiles Jane 1" groupedJaneBogart group1++    let (group2, allYears2) = groupIncludeFiles (Map.keys group1)+    let expectedAllYears2 = [+          ("./import/jane/bogartbank/all-years.journal",+           ["./import/jane/bogartbank/2017-include.journal",+            "./import/jane/bogartbank/2018-include.journal",+            "./import/jane/bogartbank/2019-include.journal"])]+    assertEqual "groupIncludeFiles Jane AllYears 2" expectedAllYears2 allYears2+    let expectedGroup2 = [+          ("./import/jane/bogartbank/2017-include.journal",+           ["./import/jane/bogartbank/savings/2017-include.journal"]),+          ("./import/jane/bogartbank/2018-include.journal",+           ["./import/jane/bogartbank/checking/2018-include.journal",+            "./import/jane/bogartbank/savings/2018-include.journal"]),+          ("./import/jane/bogartbank/2019-include.journal",+           ["./import/jane/bogartbank/checking/2019-include.journal"])]+    assertEqual "groupIncludeFiles Jane 2" expectedGroup2 group2++    let (group3, allYears3) = groupIncludeFiles (Map.keys group2)+    let expectedAllYears3 = [+          ("./import/jane/all-years.journal",+           ["./import/jane/2017-include.journal",+            "./import/jane/2018-include.journal",+            "./import/jane/2019-include.journal"])]+    assertEqual "groupIncludeFiles Jane AllYears 3" expectedAllYears3 allYears3+    let expectedGroup3 = [+          ("./import/jane/2017-include.journal",+           ["./import/jane/bogartbank/2017-include.journal"]),+          ("./import/jane/2018-include.journal",+           ["./import/jane/bogartbank/2018-include.journal"]),+          ("./import/jane/2019-include.journal",+           ["./import/jane/bogartbank/2019-include.journal"])]+    assertEqual "groupIncludeFiles Jane 3" expectedGroup3 group3++    let (group4, allYears4) = groupIncludeFiles (Map.keys group3)+    let expectedAllYears4 = [+          ("./import/all-years.journal",+           ["./import/2017-include.journal",+            "./import/2018-include.journal",+            "./import/2019-include.journal"])]+    assertEqual "groupIncludeFiles Jane AllYears 4" expectedAllYears4 allYears4+    let expectedGroup4 = [+          ("./import/2017-include.journal",+           ["./import/jane/2017-include.journal"]),+          ("./import/2018-include.journal",+           ["./import/jane/2018-include.journal"]),+          ("./import/2019-include.journal",+           ["./import/jane/2019-include.journal"])]+    assertEqual "groupIncludeFiles Jane 4" expectedGroup4 group4++    let (group5, allYears5) = groupIncludeFiles (Map.keys group4)+    let expectedAllYears5 = [("./all-years.journal", ["./2017-include.journal", "./2018-include.journal", "./2019-include.journal"])]+    assertEqual "groupIncludeFiles Jane AllYears 5" expectedAllYears5 allYears5+    let expectedGroup5 = [+          ("./2017-include.journal",+           ["./import/2017-include.journal"]),+          ("./2018-include.journal",+           ["./import/2018-include.journal"]),+          ("./2019-include.journal",+           ["./import/2019-include.journal"])]+    assertEqual "groupIncludeFiles Jane 5" expectedGroup5 group5+ )++testGroupIncludeFiles :: Test+testGroupIncludeFiles = TestCase (+  do+    let (group1, allYears1) = groupIncludeFiles journalFiles+    let expectedAllYears1 = [+          ("./import/jane/bogartbank/checking/all-years.journal",+            ["./import/jane/bogartbank/checking/2018-include.journal",+             "./import/jane/bogartbank/checking/2019-include.journal"]),+          ("./import/jane/bogartbank/savings/all-years.journal",+            ["./import/jane/bogartbank/savings/2017-include.journal",+             "./import/jane/bogartbank/savings/2018-include.journal"]),+          ("./import/jane/otherbank/creditcard/all-years.journal",+            ["./import/jane/otherbank/creditcard/2017-include.journal",+             "./import/jane/otherbank/creditcard/2018-include.journal"]),+          ("./import/jane/otherbank/investments/all-years.journal",+            ["./import/jane/otherbank/investments/2018-include.journal",+             "./import/jane/otherbank/investments/2019-include.journal"]),+          ("./import/john/bogartbank/checking/all-years.journal",+            ["./import/john/bogartbank/checking/2018-include.journal",+             "./import/john/bogartbank/checking/2019-include.journal"]),+          ("./import/john/bogartbank/savings/all-years.journal",+            ["./import/john/bogartbank/savings/2017-include.journal",+             "./import/john/bogartbank/savings/2018-include.journal"]),+          ("./import/john/otherbank/creditcard/all-years.journal",+            ["./import/john/otherbank/creditcard/2017-include.journal",+             "./import/john/otherbank/creditcard/2018-include.journal"]),+          ("./import/john/otherbank/investments/all-years.journal",+            ["./import/john/otherbank/investments/2018-include.journal",+             "./import/john/otherbank/investments/2019-include.journal"])]+    assertEqual "groupIncludeFiles Jane AllYears 1" expectedAllYears1 allYears1+    assertEqual "groupIncludeFiles 1" groupedIncludeFiles group1++    let (group2, allYears2) = groupIncludeFiles (Map.keys group1)+    let expectedAllYears2 = [+          ("./import/jane/bogartbank/all-years.journal",+            ["./import/jane/bogartbank/2017-include.journal",+             "./import/jane/bogartbank/2018-include.journal",+             "./import/jane/bogartbank/2019-include.journal"]),+          ("./import/jane/otherbank/all-years.journal",+            ["./import/jane/otherbank/2017-include.journal",+             "./import/jane/otherbank/2018-include.journal",+             "./import/jane/otherbank/2019-include.journal"]),+          ("./import/john/bogartbank/all-years.journal",+            ["./import/john/bogartbank/2017-include.journal",+             "./import/john/bogartbank/2018-include.journal",+             "./import/john/bogartbank/2019-include.journal"]),+          ("./import/john/otherbank/all-years.journal",+            ["./import/john/otherbank/2017-include.journal",+             "./import/john/otherbank/2018-include.journal",+             "./import/john/otherbank/2019-include.journal"])]++    assertEqual "groupIncludeFiles Jane AllYears 2" expectedAllYears2 allYears2+    let expectedGroup2 = [("./import/jane/bogartbank/2017-include.journal",+                           ["./import/jane/bogartbank/savings/2017-include.journal"]),+                          ("./import/jane/bogartbank/2018-include.journal",+                           ["./import/jane/bogartbank/checking/2018-include.journal",+                            "./import/jane/bogartbank/savings/2018-include.journal"]),+                          ("./import/jane/bogartbank/2019-include.journal",+                           ["./import/jane/bogartbank/checking/2019-include.journal"]),++                          ("./import/jane/otherbank/2017-include.journal",+                           ["./import/jane/otherbank/creditcard/2017-include.journal"]),+                          ("./import/jane/otherbank/2018-include.journal",+                           ["./import/jane/otherbank/creditcard/2018-include.journal",+                            "./import/jane/otherbank/investments/2018-include.journal"]),+                          ("./import/jane/otherbank/2019-include.journal",+                           ["./import/jane/otherbank/investments/2019-include.journal"]),++                          ("./import/john/bogartbank/2017-include.journal",+                           ["./import/john/bogartbank/savings/2017-include.journal"]),+                          ("./import/john/bogartbank/2018-include.journal",+                           ["./import/john/bogartbank/checking/2018-include.journal",+                            "./import/john/bogartbank/savings/2018-include.journal"]),+                          ("./import/john/bogartbank/2019-include.journal",+                           ["./import/john/bogartbank/checking/2019-include.journal"]),++                          ("./import/john/otherbank/2017-include.journal",+                           ["./import/john/otherbank/creditcard/2017-include.journal"]),+                          ("./import/john/otherbank/2018-include.journal",+                           ["./import/john/otherbank/creditcard/2018-include.journal",+                            "./import/john/otherbank/investments/2018-include.journal"]),+                          ("./import/john/otherbank/2019-include.journal",+                           ["./import/john/otherbank/investments/2019-include.journal"])]+    assertEqual "groupIncludeFiles 2 - diff 1" [] (expectedGroup2 Map.\\ group2)+    assertEqual "groupIncludeFiles 2 - diff 2" [] (group2 Map.\\ expectedGroup2)+    assertEqual "groupIncludeFiles 2" expectedGroup2 group2++    let (group3, allYears3) = groupIncludeFiles (Map.keys group2)+    let expectedAllYears3 = [+          ("./import/jane/all-years.journal",+            ["./import/jane/2017-include.journal",+             "./import/jane/2018-include.journal",+             "./import/jane/2019-include.journal"]),+          ("./import/john/all-years.journal",+            ["./import/john/2017-include.journal",+             "./import/john/2018-include.journal",+             "./import/john/2019-include.journal"])]++    assertEqual "groupIncludeFiles Jane AllYears 3" expectedAllYears3 allYears3+    let expectedGroup3 = [("./import/jane/2017-include.journal",+                           ["./import/jane/bogartbank/2017-include.journal",+                            "./import/jane/otherbank/2017-include.journal"]),+                          ("./import/jane/2018-include.journal",+                           ["./import/jane/bogartbank/2018-include.journal",+                            "./import/jane/otherbank/2018-include.journal"]),+                          ("./import/jane/2019-include.journal",+                           ["./import/jane/bogartbank/2019-include.journal",+                            "./import/jane/otherbank/2019-include.journal"]),+                          ("./import/john/2017-include.journal",+                           ["./import/john/bogartbank/2017-include.journal",+                            "./import/john/otherbank/2017-include.journal"]),+                          ("./import/john/2018-include.journal",+                           ["./import/john/bogartbank/2018-include.journal",+                            "./import/john/otherbank/2018-include.journal"]),+                          ("./import/john/2019-include.journal",+                           ["./import/john/bogartbank/2019-include.journal",+                            "./import/john/otherbank/2019-include.journal"])]+    assertEqual "groupIncludeFiles 3 - diff 1" [] (expectedGroup3 Map.\\ group3)+    assertEqual "groupIncludeFiles 3 - diff 2" [] (group3 Map.\\ expectedGroup3)+    assertEqual "groupIncludeFiles 3" expectedGroup3 group3++    let (group4, allYears4) = groupIncludeFiles (Map.keys group3)+    let expectedAllYears4 = [+          ("./import/all-years.journal",+            ["./import/2017-include.journal",+             "./import/2018-include.journal",+             "./import/2019-include.journal"])]+    assertEqual "groupIncludeFiles Jane AllYears 4" expectedAllYears4 allYears4+    let expectedGroup4 = [("./import/2017-include.journal",+                           ["./import/jane/2017-include.journal",+                            "./import/john/2017-include.journal"]),+                          ("./import/2018-include.journal",+                           ["./import/jane/2018-include.journal",+                            "./import/john/2018-include.journal"]),+                          ("./import/2019-include.journal",+                           ["./import/jane/2019-include.journal",+                            "./import/john/2019-include.journal"])]+    assertEqual "groupIncludeFiles 4 - diff 1" [] (expectedGroup4 Map.\\ group4)+    assertEqual "groupIncludeFiles 4 - diff 2" [] (group4 Map.\\ expectedGroup4)+    assertEqual "groupIncludeFiles 4" expectedGroup4 group4++    let (group5, allYears5) = groupIncludeFiles (Map.keys group4)+    let expectedAllYears5 = [("./all-years.journal", ["./2017-include.journal", "./2018-include.journal", "./2019-include.journal"])]+    assertEqual "groupIncludeFiles Jane AllYears 5" expectedAllYears5 allYears5+    let expectedGroup5 = [+          ("./2017-include.journal",+           ["./import/2017-include.journal"]),+          ("./2018-include.journal",+           ["./import/2018-include.journal"]),+          ("./2019-include.journal",+           ["./import/2019-include.journal"])]+    assertEqual "groupIncludeFiles 5 - diff 1" [] (expectedGroup5 Map.\\ group5)+    assertEqual "groupIncludeFiles 5 - diff 2" [] (group5 Map.\\ expectedGroup5)+    assertEqual "groupIncludeFiles 5" expectedGroup5 group5+  )++testIncludeYears :: Test+testIncludeYears = TestCase (+  do+    let txterr = "Some text without years"+    let expectederr = ["Unable to extract years from the following text:", txterr, "Errors:"]+    let actualerr = (init . head) $ map (T.lines) $ lefts [includeYears' txterr] :: [Text]+    assertEqual "Get a list of years from an include file - error case" expectederr actualerr++    let txt1 = "### Generated by hledger-flow - DO NOT EDIT ###\n\n" <>+          "!include import/2014-include.journal\n" <>+          "!include import/2015-include.journal\n" <>+          "!include import/2016-include.journal\n" <>+          "!include import/2017-include.journal\n" <>+          "!include import/2018-include.journal\n" <>+          "!include import/2019-include.journal"++    let expected1 = Right [2014..2019]+    let actual1 = includeYears' txt1+    assertEqual "Get a list of years from an include file - success case 1" expected1 actual1++    let txt2 = "!include 2019-include.journal"++    let expected2 = Right [2019]+    let actual2 = includeYears' txt2+    assertEqual "Get a list of years from an include file - success case 2" expected2 actual2+  )++testToIncludeLine :: Test+testToIncludeLine = TestCase (+  do+    let expected = "!include file1.journal"+    let relativeWithTrailingSlash = toIncludeLine "./base/dir/" "./base/dir/file1.journal"+    assertEqual "Include line - relative base dir with trailing slash" expected relativeWithTrailingSlash++    let relativeNoTrailingSlash = toIncludeLine "./base/dir" "./base/dir/file1.journal"+    assertEqual "Include line - relative base dir without a trailing slash" expected relativeNoTrailingSlash++    let absoluteWithTrailingSlash = toIncludeLine "/base/dir/" "/base/dir/file1.journal"+    assertEqual "Include line - absolute base dir with trailing slash" expected absoluteWithTrailingSlash++    let absoluteNoTrailingSlash = toIncludeLine "/base/dir" "/base/dir/file1.journal"+    assertEqual "Include line - absolute base dir without a trailing slash" expected absoluteNoTrailingSlash+  )++testToIncludeFiles :: Test+testToIncludeFiles = TestCase (+  do+    let expected = [+          ("./import/john/bogartbank/checking/2018-include.journal",+           "### Generated by hledger-flow - DO NOT EDIT ###\n\n" <>+           "!include import/john/bogartbank/checking/3-journal/2018/2018-10-30.journal\n" <>+           "!include import/john/bogartbank/checking/3-journal/2018/2018-11-30.journal\n" <>+           "!include import/john/bogartbank/checking/3-journal/2018/2018-12-30.journal\n"),+          ("./import/john/bogartbank/checking/2019-include.journal",+           "### Generated by hledger-flow - DO NOT EDIT ###\n\n" <>+           "!include import/john/bogartbank/checking/3-journal/2019/2019-01-30.journal\n" <>+           "!include import/john/bogartbank/checking/3-journal/2019/2019-02-30.journal\n"),+          ("./import/john/bogartbank/savings/2017-include.journal",+           "### Generated by hledger-flow - DO NOT EDIT ###\n\n" <>+           "!include import/john/bogartbank/savings/3-journal/2017/2017-11-30.journal\n" <>+           "!include import/john/bogartbank/savings/3-journal/2017/2017-12-30.journal\n"),+          ("./import/john/bogartbank/savings/2018-include.journal",+           "### Generated by hledger-flow - DO NOT EDIT ###\n\n" <>+           "!include import/john/bogartbank/savings/3-journal/2018/2018-01-30.journal\n" <>+           "!include import/john/bogartbank/savings/3-journal/2018/2018-02-30.journal\n")]++    ch <- newTChanIO+    txt <- toIncludeFiles (defaultOpts [absdir|/|]) ch groupedJohnBogart+    assertEqual "Convert a grouped map of paths, to a map with text contents for each file" expected txt)++tests :: Test+tests = TestList [testYearsIncludeMap, testYearsIncludeGrouping, testGroupIncludeFilesTinySet, testGroupIncludeFilesSmallSet, testGroupIncludeFiles, testIncludeYears, testToIncludeLine, testToIncludeFiles]
test/CSVImport/Integration.hs view
@@ -8,9 +8,12 @@ import qualified Data.Map.Strict as Map import qualified Data.List as List (sort) -import TestHelpers+import TestHelpers (defaultOpts)+import TestHelpersTurtle (journalFiles, hiddenFiles, touchAll, extraFiles) import Hledger.Flow.Common+import Hledger.Flow.Import.ImportHelpersTurtle (extraIncludesForFile, groupAndWriteIncludeFiles, includePreamble, toIncludeFiles) import Hledger.Flow.PathHelpers+ import Control.Concurrent.STM  testExtraIncludesForFile :: Test
test/CSVImport/Unit.hs view
@@ -1,429 +1,9 @@-{-# LANGUAGE OverloadedStrings #-}-{-# LANGUAGE OverloadedLists #-}-{-# LANGUAGE QuasiQuotes #-}- module CSVImport.Unit where  import Test.HUnit-import Path-import Turtle-import qualified Data.Map.Strict as Map-import Control.Concurrent.STM -import TestHelpers-import Hledger.Flow.PathHelpers (TurtlePath)-import Hledger.Flow.Common--import Data.Either-import qualified Data.Text as T--groupedJaneBogart :: Map.Map TurtlePath [TurtlePath]-groupedJaneBogart = [-  ("./import/jane/bogartbank/checking/2018-include.journal",-   ["import/jane/bogartbank/checking/3-journal/2018/2018-12-30.journal"]),-  ("./import/jane/bogartbank/checking/2019-include.journal",-   ["import/jane/bogartbank/checking/3-journal/2019/2019-01-30.journal"]),-  ("./import/jane/bogartbank/savings/2017-include.journal",-   ["import/jane/bogartbank/savings/3-journal/2017/2017-12-30.journal"]),-  ("./import/jane/bogartbank/savings/2018-include.journal",-   ["import/jane/bogartbank/savings/3-journal/2018/2018-01-30.journal"])]--groupedJaneOther :: Map.Map TurtlePath [TurtlePath]-groupedJaneOther = [-  ("./import/jane/otherbank/creditcard/2017-include.journal",-   ["import/jane/otherbank/creditcard/3-journal/2017/2017-12-30.journal"]),-  ("./import/jane/otherbank/creditcard/2018-include.journal",-   ["import/jane/otherbank/creditcard/3-journal/2018/2018-01-30.journal"]),-  ("./import/jane/otherbank/investments/2018-include.journal",-   ["import/jane/otherbank/investments/3-journal/2018/2018-12-30.journal"]),-  ("./import/jane/otherbank/investments/2019-include.journal",-   ["import/jane/otherbank/investments/3-journal/2019/2019-01-30.journal"])]--groupedJohnBogart :: Map.Map TurtlePath [TurtlePath]-groupedJohnBogart = [-  ("./import/john/bogartbank/checking/2018-include.journal",-   ["import/john/bogartbank/checking/3-journal/2018/2018-11-30.journal",-    "import/john/bogartbank/checking/3-journal/2018/2018-10-30.journal",-    "import/john/bogartbank/checking/3-journal/2018/2018-12-30.journal"]),-  ("./import/john/bogartbank/checking/2019-include.journal",-   ["import/john/bogartbank/checking/3-journal/2019/2019-01-30.journal",-    "import/john/bogartbank/checking/3-journal/2019/2019-02-30.journal"]),-  ("./import/john/bogartbank/savings/2017-include.journal",-   ["import/john/bogartbank/savings/3-journal/2017/2017-11-30.journal",-    "import/john/bogartbank/savings/3-journal/2017/2017-12-30.journal"]),-  ("./import/john/bogartbank/savings/2018-include.journal",-   ["import/john/bogartbank/savings/3-journal/2018/2018-02-30.journal",-    "import/john/bogartbank/savings/3-journal/2018/2018-01-30.journal"])]--groupedJohnOther :: Map.Map TurtlePath [TurtlePath]-groupedJohnOther = [-  ("./import/john/otherbank/creditcard/2017-include.journal",-   ["import/john/otherbank/creditcard/3-journal/2017/2017-12-30.journal"]),-  ("./import/john/otherbank/creditcard/2018-include.journal",-   ["import/john/otherbank/creditcard/3-journal/2018/2018-01-30.journal"]),-  ("./import/john/otherbank/investments/2018-include.journal",-   ["import/john/otherbank/investments/3-journal/2018/2018-12-30.journal"]),-  ("./import/john/otherbank/investments/2019-include.journal",-   ["import/john/otherbank/investments/3-journal/2019/2019-01-30.journal"])]--groupedIncludeFiles :: Map.Map TurtlePath [TurtlePath]-groupedIncludeFiles = groupedJaneBogart <> groupedJaneOther <>-                      groupedJohnBogart <> groupedJohnOther--testYearsIncludeMap :: Test-testYearsIncludeMap = TestCase (-  do-    let maps = allYearIncludeFiles groupedJohnOther-    let yearGrouping = [("./import/john/otherbank/creditcard/all-years.journal",-                         ["./import/john/otherbank/creditcard/2017-include.journal",-                          "./import/john/otherbank/creditcard/2018-include.journal"]),-                        ("./import/john/otherbank/investments/all-years.journal",-                         ["./import/john/otherbank/investments/2018-include.journal",-                          "./import/john/otherbank/investments/2019-include.journal"])]-    let expected = (groupedJohnOther, yearGrouping)-    assertEqual "An augmented map with grouped years per level added" expected maps-  )--testYearsIncludeGrouping :: Test-testYearsIncludeGrouping = TestCase (-  do-    let yearsMap = yearsIncludeMap (Map.keys groupedJohnOther)-    let expected = [("./import/john/otherbank/creditcard/all-years.journal",-                     ["./import/john/otherbank/creditcard/2017-include.journal",-                      "./import/john/otherbank/creditcard/2018-include.journal"]),-                    ("./import/john/otherbank/investments/all-years.journal",-                     ["./import/john/otherbank/investments/2018-include.journal",-                      "./import/john/otherbank/investments/2019-include.journal"])]-    assertEqual "A basic map with grouped years per level" expected yearsMap-  )--testGroupIncludeFilesTinySet :: Test-testGroupIncludeFilesTinySet = TestCase (-  do-    let journals1 = [   "import/jane/bogartbank/savings/3-journals/2017/2017-12-30.journal"]-    let expected1 = [("./import/jane/bogartbank/savings/2017-include.journal", journals1)] :: Map.Map TurtlePath [TurtlePath]-    let expectedAllYears1 = [("./import/jane/bogartbank/savings/all-years.journal", ["./import/jane/bogartbank/savings/2017-include.journal"])]-    let (group1, allYears1) = groupIncludeFiles journals1-    assertEqual "groupIncludeFiles small allYears 1" expectedAllYears1 allYears1-    assertEqual "groupIncludeFiles small set 1" expected1 group1--    let journals2 = [(fst . head . Map.toList) expected1] :: [TurtlePath]-    let expected2 = [("./import/jane/bogartbank/2017-include.journal", journals2)] :: Map.Map TurtlePath [TurtlePath]-    let expectedAllYears2 = [("./import/jane/bogartbank/all-years.journal", ["./import/jane/bogartbank/2017-include.journal"])]-    let (group2, allYears2) = groupIncludeFiles journals2-    assertEqual "groupIncludeFiles small allYears 2" expectedAllYears2 allYears2-    assertEqual "groupIncludeFiles small set 2" expected2 group2--    let journals3 = [(fst . head . Map.toList) expected2] :: [TurtlePath]-    let expected3 = [("./import/jane/2017-include.journal", journals3)] :: Map.Map TurtlePath [TurtlePath]-    let expectedAllYears3 = [("./import/jane/all-years.journal", ["./import/jane/2017-include.journal"])]-    let (group3, allYears3) = groupIncludeFiles journals3-    assertEqual "groupIncludeFiles small allYears 3" expectedAllYears3 allYears3-    assertEqual "groupIncludeFiles small set 3" expected3 group3-  )--testGroupIncludeFilesSmallSet :: Test-testGroupIncludeFilesSmallSet = TestCase (-  do-    let (group1, allYears1) = groupIncludeFiles (toJournals inputJaneBogart)-    let expectedAllYears1 = [-          ("./import/jane/bogartbank/checking/all-years.journal",-           ["./import/jane/bogartbank/checking/2018-include.journal",-            "./import/jane/bogartbank/checking/2019-include.journal"]),-          ("./import/jane/bogartbank/savings/all-years.journal",-           ["./import/jane/bogartbank/savings/2017-include.journal",-            "./import/jane/bogartbank/savings/2018-include.journal"])]--    assertEqual "groupIncludeFiles Jane AllYears 1" expectedAllYears1 allYears1-    assertEqual "groupIncludeFiles Jane 1" groupedJaneBogart group1--    let (group2, allYears2) = groupIncludeFiles (Map.keys group1)-    let expectedAllYears2 = [-          ("./import/jane/bogartbank/all-years.journal",-           ["./import/jane/bogartbank/2017-include.journal",-            "./import/jane/bogartbank/2018-include.journal",-            "./import/jane/bogartbank/2019-include.journal"])]-    assertEqual "groupIncludeFiles Jane AllYears 2" expectedAllYears2 allYears2-    let expectedGroup2 = [-          ("./import/jane/bogartbank/2017-include.journal",-           ["./import/jane/bogartbank/savings/2017-include.journal"]),-          ("./import/jane/bogartbank/2018-include.journal",-           ["./import/jane/bogartbank/checking/2018-include.journal",-            "./import/jane/bogartbank/savings/2018-include.journal"]),-          ("./import/jane/bogartbank/2019-include.journal",-           ["./import/jane/bogartbank/checking/2019-include.journal"])]-    assertEqual "groupIncludeFiles Jane 2" expectedGroup2 group2--    let (group3, allYears3) = groupIncludeFiles (Map.keys group2)-    let expectedAllYears3 = [-          ("./import/jane/all-years.journal",-           ["./import/jane/2017-include.journal",-            "./import/jane/2018-include.journal",-            "./import/jane/2019-include.journal"])]-    assertEqual "groupIncludeFiles Jane AllYears 3" expectedAllYears3 allYears3-    let expectedGroup3 = [-          ("./import/jane/2017-include.journal",-           ["./import/jane/bogartbank/2017-include.journal"]),-          ("./import/jane/2018-include.journal",-           ["./import/jane/bogartbank/2018-include.journal"]),-          ("./import/jane/2019-include.journal",-           ["./import/jane/bogartbank/2019-include.journal"])]-    assertEqual "groupIncludeFiles Jane 3" expectedGroup3 group3--    let (group4, allYears4) = groupIncludeFiles (Map.keys group3)-    let expectedAllYears4 = [-          ("./import/all-years.journal",-           ["./import/2017-include.journal",-            "./import/2018-include.journal",-            "./import/2019-include.journal"])]-    assertEqual "groupIncludeFiles Jane AllYears 4" expectedAllYears4 allYears4-    let expectedGroup4 = [-          ("./import/2017-include.journal",-           ["./import/jane/2017-include.journal"]),-          ("./import/2018-include.journal",-           ["./import/jane/2018-include.journal"]),-          ("./import/2019-include.journal",-           ["./import/jane/2019-include.journal"])]-    assertEqual "groupIncludeFiles Jane 4" expectedGroup4 group4--    let (group5, allYears5) = groupIncludeFiles (Map.keys group4)-    let expectedAllYears5 = [("./all-years.journal", ["./2017-include.journal", "./2018-include.journal", "./2019-include.journal"])]-    assertEqual "groupIncludeFiles Jane AllYears 5" expectedAllYears5 allYears5-    let expectedGroup5 = [-          ("./2017-include.journal",-           ["./import/2017-include.journal"]),-          ("./2018-include.journal",-           ["./import/2018-include.journal"]),-          ("./2019-include.journal",-           ["./import/2019-include.journal"])]-    assertEqual "groupIncludeFiles Jane 5" expectedGroup5 group5- )--testGroupIncludeFiles :: Test-testGroupIncludeFiles = TestCase (-  do-    let (group1, allYears1) = groupIncludeFiles journalFiles-    let expectedAllYears1 = [-          ("./import/jane/bogartbank/checking/all-years.journal",-            ["./import/jane/bogartbank/checking/2018-include.journal",-             "./import/jane/bogartbank/checking/2019-include.journal"]),-          ("./import/jane/bogartbank/savings/all-years.journal",-            ["./import/jane/bogartbank/savings/2017-include.journal",-             "./import/jane/bogartbank/savings/2018-include.journal"]),-          ("./import/jane/otherbank/creditcard/all-years.journal",-            ["./import/jane/otherbank/creditcard/2017-include.journal",-             "./import/jane/otherbank/creditcard/2018-include.journal"]),-          ("./import/jane/otherbank/investments/all-years.journal",-            ["./import/jane/otherbank/investments/2018-include.journal",-             "./import/jane/otherbank/investments/2019-include.journal"]),-          ("./import/john/bogartbank/checking/all-years.journal",-            ["./import/john/bogartbank/checking/2018-include.journal",-             "./import/john/bogartbank/checking/2019-include.journal"]),-          ("./import/john/bogartbank/savings/all-years.journal",-            ["./import/john/bogartbank/savings/2017-include.journal",-             "./import/john/bogartbank/savings/2018-include.journal"]),-          ("./import/john/otherbank/creditcard/all-years.journal",-            ["./import/john/otherbank/creditcard/2017-include.journal",-             "./import/john/otherbank/creditcard/2018-include.journal"]),-          ("./import/john/otherbank/investments/all-years.journal",-            ["./import/john/otherbank/investments/2018-include.journal",-             "./import/john/otherbank/investments/2019-include.journal"])]-    assertEqual "groupIncludeFiles Jane AllYears 1" expectedAllYears1 allYears1-    assertEqual "groupIncludeFiles 1" groupedIncludeFiles group1--    let (group2, allYears2) = groupIncludeFiles (Map.keys group1)-    let expectedAllYears2 = [-          ("./import/jane/bogartbank/all-years.journal",-            ["./import/jane/bogartbank/2017-include.journal",-             "./import/jane/bogartbank/2018-include.journal",-             "./import/jane/bogartbank/2019-include.journal"]),-          ("./import/jane/otherbank/all-years.journal",-            ["./import/jane/otherbank/2017-include.journal",-             "./import/jane/otherbank/2018-include.journal",-             "./import/jane/otherbank/2019-include.journal"]),-          ("./import/john/bogartbank/all-years.journal",-            ["./import/john/bogartbank/2017-include.journal",-             "./import/john/bogartbank/2018-include.journal",-             "./import/john/bogartbank/2019-include.journal"]),-          ("./import/john/otherbank/all-years.journal",-            ["./import/john/otherbank/2017-include.journal",-             "./import/john/otherbank/2018-include.journal",-             "./import/john/otherbank/2019-include.journal"])]--    assertEqual "groupIncludeFiles Jane AllYears 2" expectedAllYears2 allYears2-    let expectedGroup2 = [("./import/jane/bogartbank/2017-include.journal",-                           ["./import/jane/bogartbank/savings/2017-include.journal"]),-                          ("./import/jane/bogartbank/2018-include.journal",-                           ["./import/jane/bogartbank/checking/2018-include.journal",-                            "./import/jane/bogartbank/savings/2018-include.journal"]),-                          ("./import/jane/bogartbank/2019-include.journal",-                           ["./import/jane/bogartbank/checking/2019-include.journal"]),--                          ("./import/jane/otherbank/2017-include.journal",-                           ["./import/jane/otherbank/creditcard/2017-include.journal"]),-                          ("./import/jane/otherbank/2018-include.journal",-                           ["./import/jane/otherbank/creditcard/2018-include.journal",-                            "./import/jane/otherbank/investments/2018-include.journal"]),-                          ("./import/jane/otherbank/2019-include.journal",-                           ["./import/jane/otherbank/investments/2019-include.journal"]),--                          ("./import/john/bogartbank/2017-include.journal",-                           ["./import/john/bogartbank/savings/2017-include.journal"]),-                          ("./import/john/bogartbank/2018-include.journal",-                           ["./import/john/bogartbank/checking/2018-include.journal",-                            "./import/john/bogartbank/savings/2018-include.journal"]),-                          ("./import/john/bogartbank/2019-include.journal",-                           ["./import/john/bogartbank/checking/2019-include.journal"]),--                          ("./import/john/otherbank/2017-include.journal",-                           ["./import/john/otherbank/creditcard/2017-include.journal"]),-                          ("./import/john/otherbank/2018-include.journal",-                           ["./import/john/otherbank/creditcard/2018-include.journal",-                            "./import/john/otherbank/investments/2018-include.journal"]),-                          ("./import/john/otherbank/2019-include.journal",-                           ["./import/john/otherbank/investments/2019-include.journal"])]-    assertEqual "groupIncludeFiles 2 - diff 1" [] (expectedGroup2 Map.\\ group2)-    assertEqual "groupIncludeFiles 2 - diff 2" [] (group2 Map.\\ expectedGroup2)-    assertEqual "groupIncludeFiles 2" expectedGroup2 group2--    let (group3, allYears3) = groupIncludeFiles (Map.keys group2)-    let expectedAllYears3 = [-          ("./import/jane/all-years.journal",-            ["./import/jane/2017-include.journal",-             "./import/jane/2018-include.journal",-             "./import/jane/2019-include.journal"]),-          ("./import/john/all-years.journal",-            ["./import/john/2017-include.journal",-             "./import/john/2018-include.journal",-             "./import/john/2019-include.journal"])]--    assertEqual "groupIncludeFiles Jane AllYears 3" expectedAllYears3 allYears3-    let expectedGroup3 = [("./import/jane/2017-include.journal",-                           ["./import/jane/bogartbank/2017-include.journal",-                            "./import/jane/otherbank/2017-include.journal"]),-                          ("./import/jane/2018-include.journal",-                           ["./import/jane/bogartbank/2018-include.journal",-                            "./import/jane/otherbank/2018-include.journal"]),-                          ("./import/jane/2019-include.journal",-                           ["./import/jane/bogartbank/2019-include.journal",-                            "./import/jane/otherbank/2019-include.journal"]),-                          ("./import/john/2017-include.journal",-                           ["./import/john/bogartbank/2017-include.journal",-                            "./import/john/otherbank/2017-include.journal"]),-                          ("./import/john/2018-include.journal",-                           ["./import/john/bogartbank/2018-include.journal",-                            "./import/john/otherbank/2018-include.journal"]),-                          ("./import/john/2019-include.journal",-                           ["./import/john/bogartbank/2019-include.journal",-                            "./import/john/otherbank/2019-include.journal"])]-    assertEqual "groupIncludeFiles 3 - diff 1" [] (expectedGroup3 Map.\\ group3)-    assertEqual "groupIncludeFiles 3 - diff 2" [] (group3 Map.\\ expectedGroup3)-    assertEqual "groupIncludeFiles 3" expectedGroup3 group3--    let (group4, allYears4) = groupIncludeFiles (Map.keys group3)-    let expectedAllYears4 = [-          ("./import/all-years.journal",-            ["./import/2017-include.journal",-             "./import/2018-include.journal",-             "./import/2019-include.journal"])]-    assertEqual "groupIncludeFiles Jane AllYears 4" expectedAllYears4 allYears4-    let expectedGroup4 = [("./import/2017-include.journal",-                           ["./import/jane/2017-include.journal",-                            "./import/john/2017-include.journal"]),-                          ("./import/2018-include.journal",-                           ["./import/jane/2018-include.journal",-                            "./import/john/2018-include.journal"]),-                          ("./import/2019-include.journal",-                           ["./import/jane/2019-include.journal",-                            "./import/john/2019-include.journal"])]-    assertEqual "groupIncludeFiles 4 - diff 1" [] (expectedGroup4 Map.\\ group4)-    assertEqual "groupIncludeFiles 4 - diff 2" [] (group4 Map.\\ expectedGroup4)-    assertEqual "groupIncludeFiles 4" expectedGroup4 group4--    let (group5, allYears5) = groupIncludeFiles (Map.keys group4)-    let expectedAllYears5 = [("./all-years.journal", ["./2017-include.journal", "./2018-include.journal", "./2019-include.journal"])]-    assertEqual "groupIncludeFiles Jane AllYears 5" expectedAllYears5 allYears5-    let expectedGroup5 = [-          ("./2017-include.journal",-           ["./import/2017-include.journal"]),-          ("./2018-include.journal",-           ["./import/2018-include.journal"]),-          ("./2019-include.journal",-           ["./import/2019-include.journal"])]-    assertEqual "groupIncludeFiles 5 - diff 1" [] (expectedGroup5 Map.\\ group5)-    assertEqual "groupIncludeFiles 5 - diff 2" [] (group5 Map.\\ expectedGroup5)-    assertEqual "groupIncludeFiles 5" expectedGroup5 group5-  )--testIncludeYears :: Test-testIncludeYears = TestCase (-  do-    let txterr = "Some text without years"-    let expectederr = ["Unable to extract years from the following text:", txterr, "Errors:"]-    let actualerr = (init . head) $ map (T.lines) $ lefts [includeYears' txterr] :: [Text]-    assertEqual "Get a list of years from an include file - error case" expectederr actualerr--    let txt1 = "### Generated by hledger-flow - DO NOT EDIT ###\n\n" <>-          "!include import/2014-include.journal\n" <>-          "!include import/2015-include.journal\n" <>-          "!include import/2016-include.journal\n" <>-          "!include import/2017-include.journal\n" <>-          "!include import/2018-include.journal\n" <>-          "!include import/2019-include.journal"--    let expected1 = Right [2014..2019]-    let actual1 = includeYears' txt1-    assertEqual "Get a list of years from an include file - success case 1" expected1 actual1--    let txt2 = "!include 2019-include.journal"--    let expected2 = Right [2019]-    let actual2 = includeYears' txt2-    assertEqual "Get a list of years from an include file - success case 2" expected2 actual2-  )--testToIncludeLine :: Test-testToIncludeLine = TestCase (-  do-    let expected = "!include file1.journal"-    let relativeWithTrailingSlash = toIncludeLine "./base/dir/" "./base/dir/file1.journal"-    assertEqual "Include line - relative base dir with trailing slash" expected relativeWithTrailingSlash--    let relativeNoTrailingSlash = toIncludeLine "./base/dir" "./base/dir/file1.journal"-    assertEqual "Include line - relative base dir without a trailing slash" expected relativeNoTrailingSlash--    let absoluteWithTrailingSlash = toIncludeLine "/base/dir/" "/base/dir/file1.journal"-    assertEqual "Include line - absolute base dir with trailing slash" expected absoluteWithTrailingSlash--    let absoluteNoTrailingSlash = toIncludeLine "/base/dir" "/base/dir/file1.journal"-    assertEqual "Include line - absolute base dir without a trailing slash" expected absoluteNoTrailingSlash-  )--testToIncludeFiles :: Test-testToIncludeFiles = TestCase (-  do-    let expected = [-          ("./import/john/bogartbank/checking/2018-include.journal",-           "### Generated by hledger-flow - DO NOT EDIT ###\n\n" <>-           "!include import/john/bogartbank/checking/3-journal/2018/2018-10-30.journal\n" <>-           "!include import/john/bogartbank/checking/3-journal/2018/2018-11-30.journal\n" <>-           "!include import/john/bogartbank/checking/3-journal/2018/2018-12-30.journal\n"),-          ("./import/john/bogartbank/checking/2019-include.journal",-           "### Generated by hledger-flow - DO NOT EDIT ###\n\n" <>-           "!include import/john/bogartbank/checking/3-journal/2019/2019-01-30.journal\n" <>-           "!include import/john/bogartbank/checking/3-journal/2019/2019-02-30.journal\n"),-          ("./import/john/bogartbank/savings/2017-include.journal",-           "### Generated by hledger-flow - DO NOT EDIT ###\n\n" <>-           "!include import/john/bogartbank/savings/3-journal/2017/2017-11-30.journal\n" <>-           "!include import/john/bogartbank/savings/3-journal/2017/2017-12-30.journal\n"),-          ("./import/john/bogartbank/savings/2018-include.journal",-           "### Generated by hledger-flow - DO NOT EDIT ###\n\n" <>-           "!include import/john/bogartbank/savings/3-journal/2018/2018-01-30.journal\n" <>-           "!include import/john/bogartbank/savings/3-journal/2018/2018-02-30.journal\n")]--    ch <- newTChanIO-    txt <- toIncludeFiles (defaultOpts [absdir|/|]) ch groupedJohnBogart-    assertEqual "Convert a grouped map of paths, to a map with text contents for each file" expected txt)+import qualified CSVImport.ImportHelperTests+import qualified CSVImport.ImportHelperTurtleTests  tests :: Test-tests = TestList [testYearsIncludeMap, testYearsIncludeGrouping, testGroupIncludeFilesTinySet, testGroupIncludeFilesSmallSet, testGroupIncludeFiles, testIncludeYears, testToIncludeLine, testToIncludeFiles]+tests = TestList [CSVImport.ImportHelperTests.tests, CSVImport.ImportHelperTurtleTests.tests]
test/Common/Integration.hs view
@@ -7,7 +7,7 @@ import Turtle import qualified Data.List as List (sort) -import TestHelpers+import TestHelpersTurtle import Hledger.Flow.PathHelpers (TurtlePath) import Hledger.Flow.Common @@ -23,7 +23,7 @@         let tmpHidden = map (tmpdir </>) hiddenFiles :: [TurtlePath]         let onDisk = List.sort $ tmpJournals ++ tmpExtras ++ tmpHidden         touchAll onDisk-        filtered <- (fmap List.sort) $ shellToList $ onlyFiles $ select onDisk+        filtered <- fmap List.sort $ shellToList $ onlyFiles $ select onDisk         let expected = List.sort $ tmpExtras ++ tmpJournals         liftIO $ assertEqual "Hidden files should be excluded" expected filtered      )
test/TestHelpers.hs view
@@ -1,64 +1,20 @@ {-# LANGUAGE OverloadedStrings #-}-{-# LANGUAGE OverloadedLists #-} {-# LANGUAGE QuasiQuotes #-}  module TestHelpers where  import Path-import Turtle -import qualified Hledger.Flow.Types as FlowTypes-import Hledger.Flow.PathHelpers (TurtlePath)-import Hledger.Flow.Common-import Hledger.Flow.RuntimeOptions--inputJohnBogart :: [TurtlePath]-inputJohnBogart = [-  "import/john/bogartbank/savings/1-in/2017/2017-11-30.csv",-  "import/john/bogartbank/savings/1-in/2017/2017-12-30.csv",-  "import/john/bogartbank/savings/1-in/2018/2018-02-30.csv",-  "import/john/bogartbank/savings/1-in/2018/2018-01-30.csv",-  "import/john/bogartbank/checking/1-in/2018/2018-11-30.csv",-  "import/john/bogartbank/checking/1-in/2018/2018-10-30.csv",-  "import/john/bogartbank/checking/1-in/2018/2018-12-30.csv",-  "import/john/bogartbank/checking/1-in/2019/2019-01-30.csv",-  "import/john/bogartbank/checking/1-in/2019/2019-02-30.csv"]--inputJohnOther :: [TurtlePath]-inputJohnOther = [-  "import/john/otherbank/creditcard/1-in/2017/2017-12-30.csv",-  "import/john/otherbank/creditcard/1-in/2018/2018-01-30.csv",-  "import/john/otherbank/investments/1-in/2018/2018-12-30.csv",-  "import/john/otherbank/investments/1-in/2019/2019-01-30.csv"]--inputJaneBogart :: [TurtlePath]-inputJaneBogart = [-  "import/jane/bogartbank/savings/1-in/2017/2017-12-30.csv",-  "import/jane/bogartbank/savings/1-in/2018/2018-01-30.csv",-  "import/jane/bogartbank/checking/1-in/2018/2018-12-30.csv",-  "import/jane/bogartbank/checking/1-in/2019/2019-01-30.csv"]--inputJaneOther :: [TurtlePath]-inputJaneOther = [-  "import/jane/otherbank/creditcard/1-in/2017/2017-12-30.csv",-  "import/jane/otherbank/creditcard/1-in/2018/2018-01-30.csv",-  "import/jane/otherbank/investments/1-in/2018/2018-12-30.csv",-  "import/jane/otherbank/investments/1-in/2019/2019-01-30.csv"]--inputFiles :: [TurtlePath]-inputFiles = inputJohnBogart <> inputJohnOther <> inputJaneBogart <> inputJaneOther--journalFiles :: [TurtlePath]-journalFiles = toJournals inputFiles+import Data.Maybe (fromMaybe) -extraFiles :: [TurtlePath]-extraFiles = ["import/john/bogartbank/savings/2017-opening.journal"]+import Hledger.Flow.Internals (versionInfo, systemInfo)+import Hledger.Flow.PathHelpers (RelFile) -hiddenFiles :: [TurtlePath]-hiddenFiles = [".hiddenfile", "checking/.DS_Store", "import/john/bogartbank/savings/1-in/.anotherhiddenfile", "import/john/bogartbank/checking/1-in/2018/.hidden"]+import qualified Hledger.Flow.Types as FlowTypes+import Hledger.Flow.RuntimeOptions  defaultHlInfo :: FlowTypes.HledgerInfo-defaultHlInfo = FlowTypes.HledgerInfo "/path/to/hledger" "1.2.3"+defaultHlInfo = FlowTypes.HledgerInfo [absfile|/path/to/hledger|] "1.2.3"  defaultOpts :: FlowTypes.BaseDir -> RuntimeOptions defaultOpts bd = RuntimeOptions {@@ -66,7 +22,7 @@   , importRunDir = [reldir|./|]   , importStartYear = Nothing   , onlyNewFiles = False-  , hfVersion = versionInfo'+  , hfVersion = versionInfo   , hledgerInfo = defaultHlInfo   , sysInfo = systemInfo   , verbose = False@@ -74,13 +30,141 @@   , sequential = False } -toJournals :: [TurtlePath] -> [TurtlePath]-toJournals = map (changePathAndExtension "3-journal" "journal")+toJournal :: RelFile -> RelFile+toJournal inFile = do+  let journalFile = fromMaybe [relfile|oops.err|] $ replaceExtension ".journal" inFile+  let journalName = filename journalFile+  let yearDir = dirname . parent $ inFile+  (parent . parent . parent) inFile </> [reldir|3-journal|] </> yearDir </> journalName -touchAll :: [TurtlePath] -> Shell ()-touchAll = foldl (\acc file -> acc <> superTouch file) (return ())+inputJohnSavings2017 :: [RelFile]+inputJohnSavings2017 = [+  [relfile|import/john/bogartbank/savings/1-in/2017/2017-11-30.csv|],+  [relfile|import/john/bogartbank/savings/1-in/2017/2017-12-30.csv|]+  ] -superTouch :: TurtlePath -> Shell ()-superTouch file = do-  mktree $ directory file-  touch file+johnSavingsJournals2017 :: [RelFile]+johnSavingsJournals2017 = map toJournal inputJohnSavings2017++inputJohnSavings2018 :: [RelFile]+inputJohnSavings2018 = [+  [relfile|import/john/bogartbank/savings/1-in/2018/2018-02-30.csv|],+  [relfile|import/john/bogartbank/savings/1-in/2018/2018-01-30.csv|]+  ]++johnSavingsJournals2018 :: [RelFile]+johnSavingsJournals2018 = map toJournal inputJohnSavings2018++inputJohnChecking2018 :: [RelFile]+inputJohnChecking2018 = [+  [relfile|import/john/bogartbank/checking/1-in/2018/2018-11-30.csv|],+  [relfile|import/john/bogartbank/checking/1-in/2018/2018-10-30.csv|],+  [relfile|import/john/bogartbank/checking/1-in/2018/2018-12-30.csv|]+  ]++johnCheckingJournals2018 :: [RelFile]+johnCheckingJournals2018 = map toJournal inputJohnChecking2018++inputJohnChecking2019 :: [RelFile]+inputJohnChecking2019 = [+  [relfile|import/john/bogartbank/checking/1-in/2019/2019-01-30.csv|],+  [relfile|import/john/bogartbank/checking/1-in/2019/2019-02-30.csv|]+  ]++johnCheckingJournals2019 :: [RelFile]+johnCheckingJournals2019 = map toJournal inputJohnChecking2019++inputJohnBogart :: [RelFile]+inputJohnBogart = inputJohnSavings2017 <> inputJohnSavings2018 <> inputJohnChecking2018 <> inputJohnChecking2019++johnCC2017 :: RelFile+johnCC2017 = [relfile|import/john/otherbank/creditcard/1-in/2017/2017-12-30.csv|]++johnCCJournal2017 :: RelFile+johnCCJournal2017 = toJournal johnCC2017++johnCC2018 :: RelFile+johnCC2018 = [relfile|import/john/otherbank/creditcard/1-in/2018/2018-01-30.csv|]++johnCCJournal2018 :: RelFile+johnCCJournal2018 = toJournal johnCC2018++johnInvest2018 :: RelFile+johnInvest2018 = [relfile|import/john/otherbank/investments/1-in/2018/2018-12-30.csv|]++johnInvestJournal2018 :: RelFile+johnInvestJournal2018 = toJournal johnInvest2018++johnInvest2019 :: RelFile+johnInvest2019 = [relfile|import/john/otherbank/investments/1-in/2019/2019-01-30.csv|]++johnInvestJournal2019 :: RelFile+johnInvestJournal2019 = toJournal johnInvest2019++inputJohnOther :: [RelFile]+inputJohnOther = [johnCC2017, johnCC2018, johnInvest2018, johnInvest2019]++janeSavings2017 :: RelFile+janeSavings2017 = [relfile|import/jane/bogartbank/savings/1-in/2017/2017-12-30.csv|]++janeSavings2018 :: [RelFile]+janeSavings2018 = [+    [relfile|import/jane/bogartbank/savings/1-in/2018/2018-01-30.csv|]+  , [relfile|import/jane/bogartbank/savings/1-in/2018/2018-12-30.csv|]+  ]++janeSavings2019 :: RelFile+janeSavings2019 = [relfile|import/jane/bogartbank/savings/1-in/2019/2019-01-30.csv|]++inputJaneBogart :: [RelFile]+inputJaneBogart = [+  janeSavings2017+  , [relfile|import/jane/bogartbank/savings/3-journals/2018/2018-01-30.journal|]+  , [relfile|import/jane/bogartbank/savings/3-journals/2018/2018-12-30.journal|]+  , janeSavings2019+  ]++janeSavingsJournal2017 :: RelFile+janeSavingsJournal2017 = toJournal janeSavings2017++janeSavingsJournals2018 :: [RelFile]+janeSavingsJournals2018 = map toJournal janeSavings2018++janeSavingsJournal2019 :: RelFile+janeSavingsJournal2019 = toJournal janeSavings2019++janeSavingsJournals :: [RelFile]+janeSavingsJournals = [janeSavingsJournal2017] ++ janeSavingsJournals2018 ++ [janeSavingsJournal2019]++janeCC2017 :: RelFile+janeCC2017 = [relfile|import/jane/otherbank/creditcard/1-in/2017/2017-12-30.csv|]++janeCCJournal2017 :: RelFile+janeCCJournal2017 = toJournal janeCC2017++janeCC2018 :: RelFile+janeCC2018 = [relfile|import/jane/otherbank/creditcard/1-in/2018/2018-01-30.csv|]++janeCCJournal2018 :: RelFile+janeCCJournal2018 = toJournal janeCC2018++janeInvest2018 :: RelFile+janeInvest2018 = [relfile|import/jane/otherbank/investments/1-in/2018/2018-12-30.csv|]++janeInvestJournal2018 :: RelFile+janeInvestJournal2018 = toJournal janeInvest2018++janeInvest2019 :: RelFile+janeInvest2019 = [relfile|import/jane/otherbank/investments/1-in/2019/2019-01-30.csv|]++janeInvestJournal2019 :: RelFile+janeInvestJournal2019 = toJournal janeInvest2019++inputJaneOther :: [RelFile]+inputJaneOther = [janeCC2017, janeCC2018, janeInvest2018, janeInvest2019]++inputFiles :: [RelFile]+inputFiles = inputJohnBogart <> inputJohnOther <> inputJaneBogart <> inputJaneOther++journalFiles :: [RelFile]+journalFiles = map toJournal inputFiles
+ test/TestHelpersTurtle.hs view
@@ -0,0 +1,71 @@+{-# LANGUAGE OverloadedStrings #-}++module TestHelpersTurtle where++import Turtle++import Hledger.Flow.PathHelpers (TurtlePath)+import Hledger.Flow.Common (changePathAndExtension)++inputJohnBogart :: [TurtlePath]+inputJohnBogart = [+  "import/john/bogartbank/savings/1-in/2017/2017-11-30.csv",+  "import/john/bogartbank/savings/1-in/2017/2017-12-30.csv",+  "import/john/bogartbank/savings/1-in/2018/2018-02-30.csv",+  "import/john/bogartbank/savings/1-in/2018/2018-01-30.csv",+  "import/john/bogartbank/checking/1-in/2018/2018-11-30.csv",+  "import/john/bogartbank/checking/1-in/2018/2018-10-30.csv",+  "import/john/bogartbank/checking/1-in/2018/2018-12-30.csv",+  "import/john/bogartbank/checking/1-in/2019/2019-01-30.csv",+  "import/john/bogartbank/checking/1-in/2019/2019-02-30.csv"]++inputJohnOther :: [TurtlePath]+inputJohnOther = [+  "import/john/otherbank/creditcard/1-in/2017/2017-12-30.csv",+  "import/john/otherbank/creditcard/1-in/2018/2018-01-30.csv",+  "import/john/otherbank/investments/1-in/2018/2018-12-30.csv",+  "import/john/otherbank/investments/1-in/2019/2019-01-30.csv"]++inputJaneBogart :: [TurtlePath]+inputJaneBogart = [+  "import/jane/bogartbank/savings/1-in/2017/2017-12-30.csv",+  "import/jane/bogartbank/savings/1-in/2018/2018-01-30.csv",+  "import/jane/bogartbank/checking/1-in/2018/2018-12-30.csv",+  "import/jane/bogartbank/checking/1-in/2019/2019-01-30.csv"]++inputJaneOther :: [TurtlePath]+inputJaneOther = [+  "import/jane/otherbank/creditcard/1-in/2017/2017-12-30.csv",+  "import/jane/otherbank/creditcard/1-in/2018/2018-01-30.csv",+  "import/jane/otherbank/investments/1-in/2018/2018-12-30.csv",+  "import/jane/otherbank/investments/1-in/2019/2019-01-30.csv"]++inputFiles :: [TurtlePath]+inputFiles = inputJohnBogart <> inputJohnOther <> inputJaneBogart <> inputJaneOther++journalFiles :: [TurtlePath]+journalFiles = toJournals inputFiles ++ ignoredJournalFiles++ignoredJournalFiles :: [TurtlePath]+ignoredJournalFiles = ["import/john/bogartbank/checking/3-journal/2018/not-a-journal.pdf"]++extraFiles :: [TurtlePath]+extraFiles = ["import/john/bogartbank/savings/2017-opening.journal"]++hiddenFiles :: [TurtlePath]+hiddenFiles = [".hiddenfile",+               "checking/.DS_Store",+               "import/john/bogartbank/savings/1-in/.anotherhiddenfile",+               "import/john/bogartbank/checking/1-in/2018/.hidden",+               "import/john/bogartbank/checking/3-journal/2018/.hidden"]++toJournals :: [TurtlePath] -> [TurtlePath]+toJournals = map (changePathAndExtension "3-journal" "journal")++touchAll :: [TurtlePath] -> Shell ()+touchAll = foldl (\acc file -> acc <> superTouch file) (return ())++superTouch :: TurtlePath -> Shell ()+superTouch file = do+  mktree $ directory file+  touch file