{-# LANGUAGE ScopedTypeVariables, OverloadedStrings #-}
{-# LANGUAGE PackageImports #-}
module Hledger.Read (
PrefixedFilePath,
defaultJournal,
defaultJournalPath,
readJournalFiles,
readJournalFile,
requireJournalFileExists,
ensureJournalFileExists,
splitReaderPrefix,
readJournal,
readJournal',
JournalReader.accountaliasp,
JournalReader.postingp,
module Hledger.Read.Common,
tests_Read,
) where
import Control.Arrow (right)
import qualified Control.Exception as C
import Control.Monad (when)
import "mtl" Control.Monad.Except (runExceptT)
import Data.Default
import Data.List
import Data.Maybe
import Data.Ord
import Data.Text (Text)
import qualified Data.Text as T
import Data.Time (Day)
import Safe
import System.Directory (doesFileExist, getHomeDirectory)
import System.Environment (getEnv)
import System.Exit (exitFailure)
import System.FilePath
import System.Info (os)
import System.IO
import Text.Printf
import Hledger.Data.Dates (getCurrentDay, parsedate, showDate)
import Hledger.Data.Types
import Hledger.Read.Common
import Hledger.Read.JournalReader as JournalReader
import qualified Hledger.Read.TimedotReader as TimedotReader
import qualified Hledger.Read.TimeclockReader as TimeclockReader
import Hledger.Read.CsvReader as CsvReader
import Hledger.Utils
import Prelude hiding (getContents, writeFile)
journalEnvVar :: String
journalEnvVar = "LEDGER_FILE"
journalEnvVar2 :: String
journalEnvVar2 = "LEDGER"
journalDefaultFilename :: String
journalDefaultFilename = ".hledger.journal"
readers :: [Reader]
readers :: [Reader]
readers = [
Reader
JournalReader.reader
,Reader
TimeclockReader.reader
,Reader
TimedotReader.reader
,Reader
CsvReader.reader
]
readerNames :: [String]
readerNames :: [String]
readerNames = (Reader -> String) -> [Reader] -> [String]
forall a b. (a -> b) -> [a] -> [b]
map Reader -> String
rFormat [Reader]
readers
type PrefixedFilePath = FilePath
defaultJournal :: IO Journal
defaultJournal :: IO Journal
defaultJournal = IO String
defaultJournalPath IO String
-> (String -> IO (Either String Journal))
-> IO (Either String Journal)
forall (m :: * -> *) a b. Monad m => m a -> (a -> m b) -> m b
>>= InputOpts -> String -> IO (Either String Journal)
readJournalFile InputOpts
forall a. Default a => a
def IO (Either String Journal)
-> (Either String Journal -> IO Journal) -> IO Journal
forall (m :: * -> *) a b. Monad m => m a -> (a -> m b) -> m b
>>= (String -> IO Journal)
-> (Journal -> IO Journal) -> Either String Journal -> IO Journal
forall a c b. (a -> c) -> (b -> c) -> Either a b -> c
either String -> IO Journal
forall a. String -> a
error' Journal -> IO Journal
forall (m :: * -> *) a. Monad m => a -> m a
return
defaultJournalPath :: IO String
defaultJournalPath :: IO String
defaultJournalPath = do
String
s <- IO String
envJournalPath
if String -> Bool
forall (t :: * -> *) a. Foldable t => t a -> Bool
null String
s then IO String
defaultJournalPath else String -> IO String
forall (m :: * -> *) a. Monad m => a -> m a
return String
s
where
envJournalPath :: IO String
envJournalPath =
String -> IO String
getEnv String
journalEnvVar
IO String -> (IOException -> IO String) -> IO String
forall e a. Exception e => IO a -> (e -> IO a) -> IO a
`C.catch` (\(IOException
_::C.IOException) -> String -> IO String
getEnv String
journalEnvVar2
IO String -> (IOException -> IO String) -> IO String
forall e a. Exception e => IO a -> (e -> IO a) -> IO a
`C.catch` (\(IOException
_::C.IOException) -> String -> IO String
forall (m :: * -> *) a. Monad m => a -> m a
return ""))
defaultJournalPath :: IO String
defaultJournalPath = do
String
home <- IO String
getHomeDirectory IO String -> (IOException -> IO String) -> IO String
forall e a. Exception e => IO a -> (e -> IO a) -> IO a
`C.catch` (\(IOException
_::C.IOException) -> String -> IO String
forall (m :: * -> *) a. Monad m => a -> m a
return "")
String -> IO String
forall (m :: * -> *) a. Monad m => a -> m a
return (String -> IO String) -> String -> IO String
forall a b. (a -> b) -> a -> b
$ String
home String -> String -> String
</> String
journalDefaultFilename
splitReaderPrefix :: PrefixedFilePath -> (Maybe String, FilePath)
splitReaderPrefix :: String -> (Maybe String, String)
splitReaderPrefix f :: String
f =
(Maybe String, String)
-> [(Maybe String, String)] -> (Maybe String, String)
forall a. a -> [a] -> a
headDef (Maybe String
forall a. Maybe a
Nothing, String
f)
[(String -> Maybe String
forall a. a -> Maybe a
Just String
r, Int -> String -> String
forall a. Int -> [a] -> [a]
drop (String -> Int
forall (t :: * -> *) a. Foldable t => t a -> Int
length String
r Int -> Int -> Int
forall a. Num a => a -> a -> a
+ 1) String
f) | String
r <- [String]
readerNames, (String
rString -> String -> String
forall a. [a] -> [a] -> [a]
++":") String -> String -> Bool
forall a. Eq a => [a] -> [a] -> Bool
`isPrefixOf` String
f]
requireJournalFileExists :: FilePath -> IO ()
requireJournalFileExists :: String -> IO ()
requireJournalFileExists "-" = () -> IO ()
forall (m :: * -> *) a. Monad m => a -> m a
return ()
requireJournalFileExists f :: String
f = do
Bool
exists <- String -> IO Bool
doesFileExist String
f
Bool -> IO () -> IO ()
forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
when (Bool -> Bool
not Bool
exists) (IO () -> IO ()) -> IO () -> IO ()
forall a b. (a -> b) -> a -> b
$ do
Handle -> String -> String -> IO ()
forall r. HPrintfType r => Handle -> String -> r
hPrintf Handle
stderr "The hledger journal file \"%s\" was not found.\n" String
f
Handle -> String -> IO ()
forall r. HPrintfType r => Handle -> String -> r
hPrintf Handle
stderr "Please create it first, eg with \"hledger add\" or a text editor.\n"
Handle -> String -> IO ()
forall r. HPrintfType r => Handle -> String -> r
hPrintf Handle
stderr "Or, specify an existing journal file with -f or LEDGER_FILE.\n"
IO ()
forall a. IO a
exitFailure
ensureJournalFileExists :: FilePath -> IO ()
ensureJournalFileExists :: String -> IO ()
ensureJournalFileExists f :: String
f = do
Bool -> IO () -> IO ()
forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
when (String
osString -> String -> Bool
forall a. Eq a => a -> a -> Bool
/="mingw32" Bool -> Bool -> Bool
&& String -> Bool
isWindowsUnsafeDotPath String
f) (IO () -> IO ()) -> IO () -> IO ()
forall a b. (a -> b) -> a -> b
$ do
Handle -> String -> String -> IO ()
forall r. HPrintfType r => Handle -> String -> r
hPrintf Handle
stderr "Part of file path %s\n ends with a dot, which is unsafe on Windows; please use a different path.\n" (String -> String
forall a. Show a => a -> String
show String
f)
IO ()
forall a. IO a
exitFailure
Bool
exists <- String -> IO Bool
doesFileExist String
f
Bool -> IO () -> IO ()
forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
when (Bool -> Bool
not Bool
exists) (IO () -> IO ()) -> IO () -> IO ()
forall a b. (a -> b) -> a -> b
$ do
Handle -> String -> String -> IO ()
forall r. HPrintfType r => Handle -> String -> r
hPrintf Handle
stderr "Creating hledger journal file %s.\n" String
f
IO String
newJournalContent IO String -> (String -> IO ()) -> IO ()
forall (m :: * -> *) a b. Monad m => m a -> (a -> m b) -> m b
>>= String -> String -> IO ()
writeFile String
f
isWindowsUnsafeDotPath :: FilePath -> Bool
isWindowsUnsafeDotPath :: String -> Bool
isWindowsUnsafeDotPath =
Bool -> Bool
not (Bool -> Bool) -> (String -> Bool) -> String -> Bool
forall b c a. (b -> c) -> (a -> b) -> a -> c
. [String] -> Bool
forall (t :: * -> *) a. Foldable t => t a -> Bool
null ([String] -> Bool) -> (String -> [String]) -> String -> Bool
forall b c a. (b -> c) -> (a -> b) -> a -> c
.
(String -> Bool) -> [String] -> [String]
forall a. (a -> Bool) -> [a] -> [a]
filter (Bool -> Bool
not (Bool -> Bool) -> (String -> Bool) -> String -> Bool
forall b c a. (b -> c) -> (a -> b) -> a -> c
. (Char -> Bool) -> String -> Bool
forall (t :: * -> *) a. Foldable t => (a -> Bool) -> t a -> Bool
all (Char -> Char -> Bool
forall a. Eq a => a -> a -> Bool
=='.')) ([String] -> [String])
-> (String -> [String]) -> String -> [String]
forall b c a. (b -> c) -> (a -> b) -> a -> c
.
(String -> Bool) -> [String] -> [String]
forall a. (a -> Bool) -> [a] -> [a]
filter ((Char -> Char -> Bool
forall a. Eq a => a -> a -> Bool
=='.')(Char -> Bool) -> (String -> Char) -> String -> Bool
forall b c a. (b -> c) -> (a -> b) -> a -> c
.String -> Char
forall a. [a] -> a
last) ([String] -> [String])
-> (String -> [String]) -> String -> [String]
forall b c a. (b -> c) -> (a -> b) -> a -> c
.
String -> [String]
splitDirectories
newJournalContent :: IO String
newJournalContent :: IO String
newJournalContent = do
Day
d <- IO Day
getCurrentDay
String -> IO String
forall (m :: * -> *) a. Monad m => a -> m a
return (String -> IO String) -> String -> IO String
forall a b. (a -> b) -> a -> b
$ String -> String -> String
forall r. PrintfType r => String -> r
printf "; journal created %s by hledger\n" (Day -> String
forall a. Show a => a -> String
show Day
d)
readJournal' :: Text -> IO Journal
readJournal' :: Text -> IO Journal
readJournal' t :: Text
t = InputOpts -> Maybe String -> Text -> IO (Either String Journal)
readJournal InputOpts
forall a. Default a => a
def Maybe String
forall a. Maybe a
Nothing Text
t IO (Either String Journal)
-> (Either String Journal -> IO Journal) -> IO Journal
forall (m :: * -> *) a b. Monad m => m a -> (a -> m b) -> m b
>>= (String -> IO Journal)
-> (Journal -> IO Journal) -> Either String Journal -> IO Journal
forall a c b. (a -> c) -> (b -> c) -> Either a b -> c
either String -> IO Journal
forall a. String -> a
error' Journal -> IO Journal
forall (m :: * -> *) a. Monad m => a -> m a
return
findReader :: Maybe StorageFormat -> Maybe FilePath -> Maybe Reader
findReader :: Maybe String -> Maybe String -> Maybe Reader
findReader Nothing Nothing = Maybe Reader
forall a. Maybe a
Nothing
findReader (Just fmt :: String
fmt) _ = [Reader] -> Maybe Reader
forall a. [a] -> Maybe a
headMay [Reader
r | Reader
r <- [Reader]
readers, Reader -> String
rFormat Reader
r String -> String -> Bool
forall a. Eq a => a -> a -> Bool
== String
fmt]
findReader Nothing (Just path :: String
path) =
case Maybe String
prefix of
Just fmt :: String
fmt -> [Reader] -> Maybe Reader
forall a. [a] -> Maybe a
headMay [Reader
r | Reader
r <- [Reader]
readers, Reader -> String
rFormat Reader
r String -> String -> Bool
forall a. Eq a => a -> a -> Bool
== String
fmt]
Nothing -> [Reader] -> Maybe Reader
forall a. [a] -> Maybe a
headMay [Reader
r | Reader
r <- [Reader]
readers, String
ext String -> [String] -> Bool
forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> Bool
`elem` Reader -> [String]
rExtensions Reader
r]
where
(prefix :: Maybe String
prefix,path' :: String
path') = String -> (Maybe String, String)
splitReaderPrefix String
path
ext :: String
ext = Int -> String -> String
forall a. Int -> [a] -> [a]
drop 1 (String -> String) -> String -> String
forall a b. (a -> b) -> a -> b
$ String -> String
takeExtension String
path'
readJournalFiles :: InputOpts -> [PrefixedFilePath] -> IO (Either String Journal)
readJournalFiles :: InputOpts -> [String] -> IO (Either String Journal)
readJournalFiles iopts :: InputOpts
iopts =
(([Journal] -> Journal)
-> Either String [Journal] -> Either String Journal
forall (a :: * -> * -> *) b c d.
ArrowChoice a =>
a b c -> a (Either d b) (Either d c)
right [Journal] -> Journal
forall t. Monoid t => [t] -> t
mconcat1 (Either String [Journal] -> Either String Journal)
-> ([Either String Journal] -> Either String [Journal])
-> [Either String Journal]
-> Either String Journal
forall b c a. (b -> c) -> (a -> b) -> a -> c
. [Either String Journal] -> Either String [Journal]
forall (t :: * -> *) (m :: * -> *) a.
(Traversable t, Monad m) =>
t (m a) -> m (t a)
sequence ([Either String Journal] -> Either String Journal)
-> IO [Either String Journal] -> IO (Either String Journal)
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$>) (IO [Either String Journal] -> IO (Either String Journal))
-> ([String] -> IO [Either String Journal])
-> [String]
-> IO (Either String Journal)
forall b c a. (b -> c) -> (a -> b) -> a -> c
. (String -> IO (Either String Journal))
-> [String] -> IO [Either String Journal]
forall (t :: * -> *) (m :: * -> *) a b.
(Traversable t, Monad m) =>
(a -> m b) -> t a -> m (t b)
mapM (InputOpts -> String -> IO (Either String Journal)
readJournalFile InputOpts
iopts)
where
mconcat1 :: Monoid t => [t] -> t
mconcat1 :: [t] -> t
mconcat1 [] = t
forall a. Monoid a => a
mempty
mconcat1 x :: [t]
x = (t -> t -> t) -> [t] -> t
forall (t :: * -> *) a. Foldable t => (a -> a -> a) -> t a -> a
foldr1 t -> t -> t
forall a. Monoid a => a -> a -> a
mappend [t]
x
readJournalFile :: InputOpts -> PrefixedFilePath -> IO (Either String Journal)
readJournalFile :: InputOpts -> String -> IO (Either String Journal)
readJournalFile iopts :: InputOpts
iopts prefixedfile :: String
prefixedfile = do
let
(mfmt :: Maybe String
mfmt, f :: String
f) = String -> (Maybe String, String)
splitReaderPrefix String
prefixedfile
iopts' :: InputOpts
iopts' = InputOpts
iopts{mformat_ :: Maybe String
mformat_=[Maybe String] -> Maybe String
forall a. Eq a => [Maybe a] -> Maybe a
firstJust [Maybe String
mfmt, InputOpts -> Maybe String
mformat_ InputOpts
iopts]}
String -> IO ()
requireJournalFileExists String
f
Text
t <- String -> IO Text
readFileOrStdinPortably String
f
Either String Journal
ej <- InputOpts -> Maybe String -> Text -> IO (Either String Journal)
readJournal InputOpts
iopts' (String -> Maybe String
forall a. a -> Maybe a
Just String
f) Text
t
case Either String Journal
ej of
Left e :: String
e -> Either String Journal -> IO (Either String Journal)
forall (m :: * -> *) a. Monad m => a -> m a
return (Either String Journal -> IO (Either String Journal))
-> Either String Journal -> IO (Either String Journal)
forall a b. (a -> b) -> a -> b
$ String -> Either String Journal
forall a b. a -> Either a b
Left String
e
Right j :: Journal
j | InputOpts -> Bool
new_ InputOpts
iopts -> do
LatestDates
ds <- String -> IO LatestDates
previousLatestDates String
f
let (newj :: Journal
newj, newds :: LatestDates
newds) = LatestDates -> Journal -> (Journal, LatestDates)
journalFilterSinceLatestDates LatestDates
ds Journal
j
Bool -> IO () -> IO ()
forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
when (InputOpts -> Bool
new_save_ InputOpts
iopts Bool -> Bool -> Bool
&& Bool -> Bool
not (LatestDates -> Bool
forall (t :: * -> *) a. Foldable t => t a -> Bool
null LatestDates
newds)) (IO () -> IO ()) -> IO () -> IO ()
forall a b. (a -> b) -> a -> b
$ LatestDates -> String -> IO ()
saveLatestDates LatestDates
newds String
f
Either String Journal -> IO (Either String Journal)
forall (m :: * -> *) a. Monad m => a -> m a
return (Either String Journal -> IO (Either String Journal))
-> Either String Journal -> IO (Either String Journal)
forall a b. (a -> b) -> a -> b
$ Journal -> Either String Journal
forall a b. b -> Either a b
Right Journal
newj
Right j :: Journal
j -> Either String Journal -> IO (Either String Journal)
forall (m :: * -> *) a. Monad m => a -> m a
return (Either String Journal -> IO (Either String Journal))
-> Either String Journal -> IO (Either String Journal)
forall a b. (a -> b) -> a -> b
$ Journal -> Either String Journal
forall a b. b -> Either a b
Right Journal
j
type LatestDates = [Day]
latestDates :: [Day] -> LatestDates
latestDates :: LatestDates -> LatestDates
latestDates = LatestDates -> [LatestDates] -> LatestDates
forall a. a -> [a] -> a
headDef [] ([LatestDates] -> LatestDates)
-> (LatestDates -> [LatestDates]) -> LatestDates -> LatestDates
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Int -> [LatestDates] -> [LatestDates]
forall a. Int -> [a] -> [a]
take 1 ([LatestDates] -> [LatestDates])
-> (LatestDates -> [LatestDates]) -> LatestDates -> [LatestDates]
forall b c a. (b -> c) -> (a -> b) -> a -> c
. LatestDates -> [LatestDates]
forall a. Eq a => [a] -> [[a]]
group (LatestDates -> [LatestDates])
-> (LatestDates -> LatestDates) -> LatestDates -> [LatestDates]
forall b c a. (b -> c) -> (a -> b) -> a -> c
. LatestDates -> LatestDates
forall a. [a] -> [a]
reverse (LatestDates -> LatestDates)
-> (LatestDates -> LatestDates) -> LatestDates -> LatestDates
forall b c a. (b -> c) -> (a -> b) -> a -> c
. LatestDates -> LatestDates
forall a. Ord a => [a] -> [a]
sort
saveLatestDates :: LatestDates -> FilePath -> IO ()
saveLatestDates :: LatestDates -> String -> IO ()
saveLatestDates dates :: LatestDates
dates f :: String
f = String -> String -> IO ()
writeFile (String -> String
latestDatesFileFor String
f) (String -> IO ()) -> String -> IO ()
forall a b. (a -> b) -> a -> b
$ [String] -> String
unlines ([String] -> String) -> [String] -> String
forall a b. (a -> b) -> a -> b
$ (Day -> String) -> LatestDates -> [String]
forall a b. (a -> b) -> [a] -> [b]
map Day -> String
showDate LatestDates
dates
previousLatestDates :: FilePath -> IO LatestDates
previousLatestDates :: String -> IO LatestDates
previousLatestDates f :: String
f = do
let latestfile :: String
latestfile = String -> String
latestDatesFileFor String
f
Bool
exists <- String -> IO Bool
doesFileExist String
latestfile
if Bool
exists
then (String -> Day) -> [String] -> LatestDates
forall a b. (a -> b) -> [a] -> [b]
map (String -> Day
parsedate (String -> Day) -> (String -> String) -> String -> Day
forall b c a. (b -> c) -> (a -> b) -> a -> c
. String -> String
strip) ([String] -> LatestDates)
-> (Text -> [String]) -> Text -> LatestDates
forall b c a. (b -> c) -> (a -> b) -> a -> c
. String -> [String]
lines (String -> [String]) -> (Text -> String) -> Text -> [String]
forall b c a. (b -> c) -> (a -> b) -> a -> c
. String -> String
strip (String -> String) -> (Text -> String) -> Text -> String
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Text -> String
T.unpack (Text -> LatestDates) -> IO Text -> IO LatestDates
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> String -> IO Text
readFileStrictly String
latestfile
else LatestDates -> IO LatestDates
forall (m :: * -> *) a. Monad m => a -> m a
return []
latestDatesFileFor :: FilePath -> FilePath
latestDatesFileFor :: String -> String
latestDatesFileFor f :: String
f = String
dir String -> String -> String
</> ".latest" String -> String -> String
<.> String
fname
where
(dir :: String
dir, fname :: String
fname) = String -> (String, String)
splitFileName String
f
readFileStrictly :: FilePath -> IO Text
readFileStrictly :: String -> IO Text
readFileStrictly f :: String
f = String -> IO Text
readFilePortably String
f IO Text -> (Text -> IO Text) -> IO Text
forall (m :: * -> *) a b. Monad m => m a -> (a -> m b) -> m b
>>= \t :: Text
t -> Int -> IO Int
forall a. a -> IO a
C.evaluate (Text -> Int
T.length Text
t) IO Int -> IO Text -> IO Text
forall (m :: * -> *) a b. Monad m => m a -> m b -> m b
>> Text -> IO Text
forall (m :: * -> *) a. Monad m => a -> m a
return Text
t
journalFilterSinceLatestDates :: LatestDates -> Journal -> (Journal, LatestDates)
journalFilterSinceLatestDates :: LatestDates -> Journal -> (Journal, LatestDates)
journalFilterSinceLatestDates [] j :: Journal
j = (Journal
j, LatestDates -> LatestDates
latestDates (LatestDates -> LatestDates) -> LatestDates -> LatestDates
forall a b. (a -> b) -> a -> b
$ (Transaction -> Day) -> [Transaction] -> LatestDates
forall a b. (a -> b) -> [a] -> [b]
map Transaction -> Day
tdate ([Transaction] -> LatestDates) -> [Transaction] -> LatestDates
forall a b. (a -> b) -> a -> b
$ Journal -> [Transaction]
jtxns Journal
j)
journalFilterSinceLatestDates ds :: LatestDates
ds@(d :: Day
d:_) j :: Journal
j = (Journal
j', LatestDates
ds')
where
samedateorlaterts :: [Transaction]
samedateorlaterts = (Transaction -> Bool) -> [Transaction] -> [Transaction]
forall a. (a -> Bool) -> [a] -> [a]
filter ((Day -> Day -> Bool
forall a. Ord a => a -> a -> Bool
>= Day
d)(Day -> Bool) -> (Transaction -> Day) -> Transaction -> Bool
forall b c a. (b -> c) -> (a -> b) -> a -> c
.Transaction -> Day
tdate) ([Transaction] -> [Transaction]) -> [Transaction] -> [Transaction]
forall a b. (a -> b) -> a -> b
$ Journal -> [Transaction]
jtxns Journal
j
(samedatets :: [Transaction]
samedatets, laterts :: [Transaction]
laterts) = (Transaction -> Bool)
-> [Transaction] -> ([Transaction], [Transaction])
forall a. (a -> Bool) -> [a] -> ([a], [a])
span ((Day -> Day -> Bool
forall a. Eq a => a -> a -> Bool
== Day
d)(Day -> Bool) -> (Transaction -> Day) -> Transaction -> Bool
forall b c a. (b -> c) -> (a -> b) -> a -> c
.Transaction -> Day
tdate) ([Transaction] -> ([Transaction], [Transaction]))
-> [Transaction] -> ([Transaction], [Transaction])
forall a b. (a -> b) -> a -> b
$ (Transaction -> Transaction -> Ordering)
-> [Transaction] -> [Transaction]
forall a. (a -> a -> Ordering) -> [a] -> [a]
sortBy ((Transaction -> Day) -> Transaction -> Transaction -> Ordering
forall a b. Ord a => (b -> a) -> b -> b -> Ordering
comparing Transaction -> Day
tdate) [Transaction]
samedateorlaterts
newsamedatets :: [Transaction]
newsamedatets = Int -> [Transaction] -> [Transaction]
forall a. Int -> [a] -> [a]
drop (LatestDates -> Int
forall (t :: * -> *) a. Foldable t => t a -> Int
length LatestDates
ds) [Transaction]
samedatets
j' :: Journal
j' = Journal
j{jtxns :: [Transaction]
jtxns=[Transaction]
newsamedatets[Transaction] -> [Transaction] -> [Transaction]
forall a. [a] -> [a] -> [a]
++[Transaction]
laterts}
ds' :: LatestDates
ds' = LatestDates -> LatestDates
latestDates (LatestDates -> LatestDates) -> LatestDates -> LatestDates
forall a b. (a -> b) -> a -> b
$ (Transaction -> Day) -> [Transaction] -> LatestDates
forall a b. (a -> b) -> [a] -> [b]
map Transaction -> Day
tdate ([Transaction] -> LatestDates) -> [Transaction] -> LatestDates
forall a b. (a -> b) -> a -> b
$ [Transaction]
samedatets[Transaction] -> [Transaction] -> [Transaction]
forall a. [a] -> [a] -> [a]
++[Transaction]
laterts
readJournal :: InputOpts -> Maybe FilePath -> Text -> IO (Either String Journal)
readJournal :: InputOpts -> Maybe String -> Text -> IO (Either String Journal)
readJournal iopts :: InputOpts
iopts mfile :: Maybe String
mfile txt :: Text
txt =
InputOpts
-> Maybe String -> [Reader] -> Text -> IO (Either String Journal)
tryReaders InputOpts
iopts Maybe String
mfile [Reader]
specifiedorallreaders Text
txt
where
specifiedorallreaders :: [Reader]
specifiedorallreaders = [Reader] -> (Reader -> [Reader]) -> Maybe Reader -> [Reader]
forall b a. b -> (a -> b) -> Maybe a -> b
maybe [Reader]
stablereaders (Reader -> [Reader] -> [Reader]
forall a. a -> [a] -> [a]
:[]) (Maybe Reader -> [Reader]) -> Maybe Reader -> [Reader]
forall a b. (a -> b) -> a -> b
$ Maybe String -> Maybe String -> Maybe Reader
findReader (InputOpts -> Maybe String
mformat_ InputOpts
iopts) Maybe String
mfile
stablereaders :: [Reader]
stablereaders = (Reader -> Bool) -> [Reader] -> [Reader]
forall a. (a -> Bool) -> [a] -> [a]
filter (Bool -> Bool
not(Bool -> Bool) -> (Reader -> Bool) -> Reader -> Bool
forall b c a. (b -> c) -> (a -> b) -> a -> c
.Reader -> Bool
rExperimental) [Reader]
readers
tryReaders :: InputOpts -> Maybe FilePath -> [Reader] -> Text -> IO (Either String Journal)
tryReaders :: InputOpts
-> Maybe String -> [Reader] -> Text -> IO (Either String Journal)
tryReaders iopts :: InputOpts
iopts mpath :: Maybe String
mpath readers :: [Reader]
readers txt :: Text
txt = [String] -> [Reader] -> IO (Either String Journal)
firstSuccessOrFirstError [] [Reader]
readers
where
firstSuccessOrFirstError :: [String] -> [Reader] -> IO (Either String Journal)
firstSuccessOrFirstError :: [String] -> [Reader] -> IO (Either String Journal)
firstSuccessOrFirstError [] [] = Either String Journal -> IO (Either String Journal)
forall (m :: * -> *) a. Monad m => a -> m a
return (Either String Journal -> IO (Either String Journal))
-> Either String Journal -> IO (Either String Journal)
forall a b. (a -> b) -> a -> b
$ String -> Either String Journal
forall a b. a -> Either a b
Left "no readers found"
firstSuccessOrFirstError errs :: [String]
errs (r :: Reader
r:rs :: [Reader]
rs) = do
String -> String -> IO ()
forall (m :: * -> *) a. (MonadIO m, Show a) => String -> a -> m ()
dbg1IO "trying reader" (Reader -> String
rFormat Reader
r)
Either String Journal
result <- (ExceptT String IO Journal -> IO (Either String Journal)
forall e (m :: * -> *) a. ExceptT e m a -> m (Either e a)
runExceptT (ExceptT String IO Journal -> IO (Either String Journal))
-> (Text -> ExceptT String IO Journal)
-> Text
-> IO (Either String Journal)
forall b c a. (b -> c) -> (a -> b) -> a -> c
. (Reader -> InputOpts -> String -> Text -> ExceptT String IO Journal
rParser Reader
r) InputOpts
iopts String
path) Text
txt
String -> String -> IO ()
forall (m :: * -> *) a. (MonadIO m, Show a) => String -> a -> m ()
dbg1IO "reader result" (String -> IO ()) -> String -> IO ()
forall a b. (a -> b) -> a -> b
$ (String -> String)
-> (Journal -> String) -> Either String Journal -> String
forall a c b. (a -> c) -> (b -> c) -> Either a b -> c
either String -> String
forall a. a -> a
id Journal -> String
forall a. Show a => a -> String
show Either String Journal
result
case Either String Journal
result of Right j :: Journal
j -> Either String Journal -> IO (Either String Journal)
forall (m :: * -> *) a. Monad m => a -> m a
return (Either String Journal -> IO (Either String Journal))
-> Either String Journal -> IO (Either String Journal)
forall a b. (a -> b) -> a -> b
$ Journal -> Either String Journal
forall a b. b -> Either a b
Right Journal
j
Left e :: String
e -> [String] -> [Reader] -> IO (Either String Journal)
firstSuccessOrFirstError ([String]
errs[String] -> [String] -> [String]
forall a. [a] -> [a] -> [a]
++[String
e]) [Reader]
rs
firstSuccessOrFirstError (e :: String
e:_) [] = Either String Journal -> IO (Either String Journal)
forall (m :: * -> *) a. Monad m => a -> m a
return (Either String Journal -> IO (Either String Journal))
-> Either String Journal -> IO (Either String Journal)
forall a b. (a -> b) -> a -> b
$ String -> Either String Journal
forall a b. a -> Either a b
Left String
e
path :: String
path = String -> Maybe String -> String
forall a. a -> Maybe a -> a
fromMaybe "(string)" Maybe String
mpath
tests_Read :: TestTree
tests_Read = String -> [TestTree] -> TestTree
tests "Read" [
TestTree
tests_Common
,TestTree
tests_CsvReader
,TestTree
tests_JournalReader
]