--- * -*- outline-regexp:"--- \\*"; -*-
--- ** doc
-- In Emacs, use TAB on lines beginning with "-- *" to collapse/expand sections.
{-|

A reader for CSV (character-separated) data.
This also reads a rules file to help interpret the CSV data.

-}

--- ** language
{-# LANGUAGE FlexibleContexts     #-}
{-# LANGUAGE FlexibleInstances    #-}
{-# LANGUAGE OverloadedStrings    #-}
{-# LANGUAGE ScopedTypeVariables  #-}
{-# LANGUAGE TypeFamilies         #-}

--- ** exports
module Hledger.Read.CsvReader (
  -- * Reader
  reader,
  -- * Tests
  tests_CsvReader,
)
where

--- ** imports
import Prelude hiding (Applicative(..))
import Control.Monad.Except       (ExceptT(..), liftEither)
import Control.Monad.IO.Class     (MonadIO)
import Data.Text (Text)

import Hledger.Data
import Hledger.Utils
import Hledger.Read.Common (aliasesFromOpts, Reader(..), InputOpts(..), journalFinalise)
import Hledger.Read.RulesReader (readJournalFromCsv)

--- ** doctest setup
-- $setup
-- >>> :set -XOverloadedStrings

--- ** reader

reader :: MonadIO m => Reader m
reader :: Reader m
reader = Reader :: forall (m :: * -> *).
StorageFormat
-> [StorageFormat]
-> (InputOpts
    -> StorageFormat -> Text -> ExceptT StorageFormat IO Journal)
-> (MonadIO m => ErroringJournalParser m Journal)
-> Reader m
Reader
  {rFormat :: StorageFormat
rFormat     = StorageFormat
"csv"
  ,rExtensions :: [StorageFormat]
rExtensions = [StorageFormat
"csv",StorageFormat
"tsv",StorageFormat
"ssv"]
  ,rReadFn :: InputOpts
-> StorageFormat -> Text -> ExceptT StorageFormat IO Journal
rReadFn     = InputOpts
-> StorageFormat -> Text -> ExceptT StorageFormat IO Journal
parse
  ,rParser :: MonadIO m => ErroringJournalParser m Journal
rParser     = StorageFormat -> ErroringJournalParser m Journal
forall a. StorageFormat -> a
error' StorageFormat
"sorry, CSV files can't be included yet"  -- PARTIAL:
  }

-- | Parse and post-process a "Journal" from CSV data, or give an error.
-- This currently ignores the provided data, and reads it from the file path instead.
-- This file path is normally the CSV(/SSV/TSV) data file, and a corresponding rules file is inferred.
-- But it can also be the rules file, in which case the corresponding data file is inferred.
-- This does not check balance assertions.
parse :: InputOpts -> FilePath -> Text -> ExceptT String IO Journal
parse :: InputOpts
-> StorageFormat -> Text -> ExceptT StorageFormat IO Journal
parse InputOpts
iopts StorageFormat
f Text
t = do
  let mrulesfile :: Maybe StorageFormat
mrulesfile = InputOpts -> Maybe StorageFormat
mrules_file_ InputOpts
iopts
  Maybe (Either CsvRules StorageFormat)
-> StorageFormat -> Text -> ExceptT StorageFormat IO Journal
readJournalFromCsv (StorageFormat -> Either CsvRules StorageFormat
forall a b. b -> Either a b
Right (StorageFormat -> Either CsvRules StorageFormat)
-> Maybe StorageFormat -> Maybe (Either CsvRules StorageFormat)
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> Maybe StorageFormat
mrulesfile) StorageFormat
f Text
t
  -- apply any command line account aliases. Can fail with a bad replacement pattern.
  ExceptT StorageFormat IO Journal
-> (Journal -> ExceptT StorageFormat IO Journal)
-> ExceptT StorageFormat IO Journal
forall (m :: * -> *) a b. Monad m => m a -> (a -> m b) -> m b
>>= Either StorageFormat Journal -> ExceptT StorageFormat IO Journal
forall e (m :: * -> *) a. MonadError e m => Either e a -> m a
liftEither (Either StorageFormat Journal -> ExceptT StorageFormat IO Journal)
-> (Journal -> Either StorageFormat Journal)
-> Journal
-> ExceptT StorageFormat IO Journal
forall b c a. (b -> c) -> (a -> b) -> a -> c
. [AccountAlias] -> Journal -> Either StorageFormat Journal
journalApplyAliases (InputOpts -> [AccountAlias]
aliasesFromOpts InputOpts
iopts)
      -- journalFinalise assumes the journal's items are
      -- reversed, as produced by JournalReader's parser.
      -- But here they are already properly ordered. So we'd
      -- better preemptively reverse them once more. XXX inefficient
      (Journal -> Either StorageFormat Journal)
-> (Journal -> Journal) -> Journal -> Either StorageFormat Journal
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Journal -> Journal
journalReverse
  ExceptT StorageFormat IO Journal
-> (Journal -> ExceptT StorageFormat IO Journal)
-> ExceptT StorageFormat IO Journal
forall (m :: * -> *) a b. Monad m => m a -> (a -> m b) -> m b
>>= InputOpts
-> StorageFormat
-> Text
-> Journal
-> ExceptT StorageFormat IO Journal
journalFinalise InputOpts
iopts{balancingopts_ :: BalancingOpts
balancingopts_=(InputOpts -> BalancingOpts
balancingopts_ InputOpts
iopts){ignore_assertions_ :: Bool
ignore_assertions_=Bool
True}} StorageFormat
f Text
t

--- ** tests

tests_CsvReader :: TestTree
tests_CsvReader = StorageFormat -> [TestTree] -> TestTree
testGroup StorageFormat
"CsvReader" [
  ]