{-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE ScopedTypeVariables #-} {-# LANGUAGE StandaloneDeriving #-} import Test.HUnit hiding (test) import Test.Framework.Providers.HUnit (hUnitTestToTests) import Test.Framework.Runners.Console (defaultMain) import Control.Applicative (Applicative(..)) import Control.Arrow (ArrowChoice(..)) import Control.Monad (liftM) import Control.Monad.IO.Class (liftIO) import Data.Bool (Bool(..)) import Data.Decimal (DecimalRaw(..)) import Data.Either (rights, either) import Data.Eq (Eq(..)) import Data.Function (($), (.), id, const) import Data.Functor (Functor(..), (<$>)) import Data.List ((++)) import Data.List.NonEmpty (NonEmpty(..)) import qualified Data.Map.Strict as Map import Data.Maybe (Maybe(..)) import Data.Monoid (Monoid(..)) import Data.Text (Text) import qualified Data.Text as Text import qualified Data.Text.Lazy as TL import qualified Data.Time.Calendar as Time import qualified Data.Time.LocalTime as Time import System.IO (IO) import qualified Text.Parsec as R hiding (char, space, spaces, string) import qualified Text.Parsec.Pos as R import Text.Show import qualified Hcompta.Chart as Chart import qualified Hcompta.Date as Date import qualified Hcompta.Lib.Parsec as R import qualified Hcompta.Posting as Posting import qualified Hcompta.Tag as Tag import qualified Hcompta.Transaction as Transaction import qualified Hcompta.Format.JCC as F import qualified Hcompta.Format.JCC.Read as F import qualified Hcompta.Format.JCC.Write as F import Prelude (error) deriving instance Eq F.Amount main :: IO () main = defaultMain $ hUnitTestToTests test test :: Test test = TestList [ "Read" ~: TestList [ {- "read_date" ~: let (==>) (txt::Text) = (~:) (Text.unpack txt) . (~?=) (rights [R.runParser_with_Error (F.read_date id Nothing <* R.eof) () "" txt]) in TestList $ [ "2000-01-01" ==> [ Time.zonedTimeToUTC $ Time.ZonedTime (Time.LocalTime (Time.fromGregorian 2000 01 01) (Time.TimeOfDay 0 0 0)) (Time.utc) ] , "2000/01/01" ==> [] , "2000-01-01_12:34" ==> [ Time.zonedTimeToUTC $ Time.ZonedTime (Time.LocalTime (Time.fromGregorian 2000 01 01) (Time.TimeOfDay 12 34 0)) (Time.utc) ] , "2000-01-01_12:34:56" ==> [ Time.zonedTimeToUTC $ Time.ZonedTime (Time.LocalTime (Time.fromGregorian 2000 01 01) (Time.TimeOfDay 12 34 56)) (Time.utc) ] , "2000-01-01_12:34_CET" ==> [ Time.zonedTimeToUTC $ Time.ZonedTime (Time.LocalTime (Time.fromGregorian 2000 01 01) (Time.TimeOfDay 12 34 0)) (Time.TimeZone 60 True "CET") ] , "2000-01-01_12:34+01:30" ==> [ Time.zonedTimeToUTC $ Time.ZonedTime (Time.LocalTime (Time.fromGregorian 2000 01 01) (Time.TimeOfDay 12 34 0)) (Time.TimeZone 90 False "+01:30") ] , "2000-01-01_12:34:56_CET" ==> [ Time.zonedTimeToUTC $ Time.ZonedTime (Time.LocalTime (Time.fromGregorian 2000 01 01) (Time.TimeOfDay 12 34 56)) (Time.TimeZone 60 True "CET") ] , "2001-02-29" ==> [] ] ++ let (==>) (txt::Text, def) = (~:) (Text.unpack txt) . (~?=) (rights [R.runParser_with_Error (F.read_date id (Just def) <* R.eof) () "" txt]) in [ ("01-01", 2000) ==> [ Time.zonedTimeToUTC $ Time.ZonedTime (Time.LocalTime (Time.fromGregorian 2000 01 01) (Time.TimeOfDay 0 0 0)) (Time.utc)] ] , "read_account_section" ~: let (==>) (txt::Text) b = (~:) (Text.unpack txt) $ (~?=) (rights [R.runParser (F.read_account_section <* R.eof) () "" txt]) (if b then [txt] else []) in TestList [ "" ==> False , "A" ==> True , "AA" ==> True , " " ==> False , "/" ==> False , "A/" ==> False , "/A" ==> False , "A " ==> False , "A A" ==> False , "A " ==> False , "A\t" ==> False , "A \n" ==> False , "(A)A" ==> False , "( )A" ==> False , "(A) A" ==> False , "[ ] A" ==> False , "(A) " ==> False , "(A)" ==> False , "A(A)" ==> False , "[A]A" ==> False , "[A] A" ==> False , "[A] " ==> False , "[A]" ==> False , "\"A \"" ~: (rights [R.runParser (F.read_account_section) () "" ("A "::Text)]) ~?= ["A"] ] , "read_account" ~: let (==>) (txt::Text) = (~:) (Text.unpack txt) . (~?=) (rights [R.runParser (F.read_account <* R.eof) () "" txt]) in TestList [ "" ==> [] , "A" ==> [] , "A/" ==> [] , "/A" ==> ["A":|[]] , "A " ==> [] , " A" ==> [] , "/A/B" ==> ["A":|["B"]] , "/A/B/C" ==> ["A":|["B","C"]] , "/Aa/Bbb/Cccc" ==> ["Aa":|["Bbb", "Cccc"]] , "/A a / B b b / C c c c" ==> [] , "/A/ /C" ==> [] , "/A//C" ==> [] , "/A/B/(C)" ==> [] ] , "read_amount" ~: let (==>) (txt::Text) = (~:) (Text.unpack txt) . (~?=) (rights [R.runParser (F.read_amount <* R.eof) () "" txt]) in TestList [ "" ==> [] , "0" ==> [( mempty , F.amount { F.amount_quantity = Decimal 0 0 } )] , "00" ==> [( mempty , F.amount { F.amount_quantity = Decimal 0 0 } )] , "0." ==> [( mempty { F.amount_style_fractioning = Just '.' } , F.amount { F.amount_quantity = Decimal 0 0 } )] , ".0" ==> [( mempty { F.amount_style_fractioning = Just '.' } , F.amount { F.amount_quantity = Decimal 1 0 } )] , "0," ==> [( mempty { F.amount_style_fractioning = Just ',' } , F.amount { F.amount_quantity = Decimal 0 0 } )] , ",0" ==> [( mempty { F.amount_style_fractioning = Just ',' } , F.amount { F.amount_quantity = Decimal 1 0 } )] , "0_" ==> [] , "_0" ==> [] , "0.0" ==> [( mempty { F.amount_style_fractioning = Just '.' } , F.amount { F.amount_quantity = Decimal 1 0 } )] , "00.00" ==> [( mempty { F.amount_style_fractioning = Just '.' } , F.amount { F.amount_quantity = Decimal 2 0 } )] , "0,0" ==> [( mempty { F.amount_style_fractioning = Just ',' } , F.amount { F.amount_quantity = Decimal 1 0 } )] , "00,00" ==> [( mempty { F.amount_style_fractioning = Just ',' } , F.amount { F.amount_quantity = Decimal 2 0 } )] , "0_0" ==> [( mempty { F.amount_style_grouping_integral = Just $ F.Amount_Style_Grouping '_' [1] } , F.amount { F.amount_quantity = Decimal 0 0 } )] , "00_00" ==> [( mempty { F.amount_style_grouping_integral = Just $ F.Amount_Style_Grouping '_' [2] } , F.amount { F.amount_quantity = Decimal 0 0 } )] , "0,000.00" ==> [( mempty { F.amount_style_fractioning = Just '.' , F.amount_style_grouping_integral = Just $ F.Amount_Style_Grouping ',' [3] } , F.amount { F.amount_quantity = Decimal 2 0 } )] , "0.000,00" ==> [( mempty { F.amount_style_fractioning = Just ',' , F.amount_style_grouping_integral = Just $ F.Amount_Style_Grouping '.' [3] } , F.amount { F.amount_quantity = Decimal 2 0 } )] , "1,000.00" ==> [( mempty { F.amount_style_fractioning = Just '.' , F.amount_style_grouping_integral = Just $ F.Amount_Style_Grouping ',' [3] } , F.amount { F.amount_quantity = Decimal 2 100000 } )] , "1.000,00" ==> [( mempty { F.amount_style_fractioning = Just ',' , F.amount_style_grouping_integral = Just $ F.Amount_Style_Grouping '.' [3] } , F.amount { F.amount_quantity = Decimal 2 100000 } )] , "1,000.00." ==> [] , "1.000,00," ==> [] , "1,000.00_" ==> [] , "123" ==> [( mempty , F.amount { F.amount_quantity = Decimal 0 123 } )] , "1.2" ==> [( mempty { F.amount_style_fractioning = Just '.' } , F.amount { F.amount_quantity = Decimal 1 12 } )] , "1,2" ==> [( mempty { F.amount_style_fractioning = Just ',' } , F.amount { F.amount_quantity = Decimal 1 12 } )] , "12.34" ==> [( mempty { F.amount_style_fractioning = Just '.' } , F.amount { F.amount_quantity = Decimal 2 1234 } )] , "12,34" ==> [( mempty { F.amount_style_fractioning = Just ',' } , F.amount { F.amount_quantity = Decimal 2 1234 } )] , "1_2" ==> [( mempty { F.amount_style_grouping_integral = Just $ F.Amount_Style_Grouping '_' [1] } , F.amount { F.amount_quantity = Decimal 0 12 } )] , "1_23" ==> [( mempty { F.amount_style_grouping_integral = Just $ F.Amount_Style_Grouping '_' [2] } , F.amount { F.amount_quantity = Decimal 0 123 } )] , "1_23_456" ==> [( mempty { F.amount_style_grouping_integral = Just $ F.Amount_Style_Grouping '_' [3, 2] } , F.amount { F.amount_quantity = Decimal 0 123456 } )] , "1_23_456,7890_12345_678901" ==> [( mempty { F.amount_style_fractioning = Just ',' , F.amount_style_grouping_integral = Just $ F.Amount_Style_Grouping '_' [3, 2] , F.amount_style_grouping_fractional = Just $ F.Amount_Style_Grouping '_' [4, 5, 6] } , F.amount { F.amount_quantity = Decimal 15 123456789012345678901 } )] , "1_23_456.7890_12345_678901" ==> [( mempty { F.amount_style_fractioning = Just '.' , F.amount_style_grouping_integral = Just $ F.Amount_Style_Grouping '_' [3, 2] , F.amount_style_grouping_fractional = Just $ F.Amount_Style_Grouping '_' [4, 5, 6] } , F.amount { F.amount_quantity = Decimal 15 123456789012345678901 } )] , "1,23,456.7890_12345_678901" ==> [( mempty { F.amount_style_fractioning = Just '.' , F.amount_style_grouping_integral = Just $ F.Amount_Style_Grouping ',' [3, 2] , F.amount_style_grouping_fractional = Just $ F.Amount_Style_Grouping '_' [4, 5, 6] } , F.amount { F.amount_quantity = Decimal 15 123456789012345678901 } )] , "1.23.456,7890_12345_678901" ==> [( mempty { F.amount_style_fractioning = Just ',' , F.amount_style_grouping_integral = Just $ F.Amount_Style_Grouping '.' [3, 2] , F.amount_style_grouping_fractional = Just $ F.Amount_Style_Grouping '_' [4, 5, 6] } , F.amount { F.amount_quantity = Decimal 15 123456789012345678901 } )] , "123456_78901_2345.678_90_1" ==> [( mempty { F.amount_style_fractioning = Just '.' , F.amount_style_grouping_integral = Just $ F.Amount_Style_Grouping '_' [4, 5, 6] , F.amount_style_grouping_fractional = Just $ F.Amount_Style_Grouping '_' [3, 2] } , F.amount { F.amount_quantity = Decimal 6 123456789012345678901 } )] , "$1" ==> [( mempty { F.amount_style_unit_side = Just F.Amount_Style_Side_Left , F.amount_style_unit_spaced = Just False } , F.amount { F.amount_quantity = Decimal 0 1 , F.amount_unit = "$" } )] , "1$" ==> [( mempty { F.amount_style_unit_side = Just F.Amount_Style_Side_Right , F.amount_style_unit_spaced = Just False } , F.amount { F.amount_quantity = Decimal 0 1 , F.amount_unit = "$" } )] , "$ 1" ==> [( mempty { F.amount_style_unit_side = Just F.Amount_Style_Side_Left , F.amount_style_unit_spaced = Just True } , F.amount { F.amount_quantity = Decimal 0 1 , F.amount_unit = "$" } )] , "1 $" ==> [( mempty { F.amount_style_unit_side = Just F.Amount_Style_Side_Right , F.amount_style_unit_spaced = Just True } , F.amount { F.amount_quantity = Decimal 0 1 , F.amount_unit = "$" } )] , "-$1" ==> [( mempty { F.amount_style_unit_side = Just F.Amount_Style_Side_Left , F.amount_style_unit_spaced = Just False } , F.amount { F.amount_quantity = Decimal 0 (-1) , F.amount_unit = "$" } )] , "\"4 2\"1" ==> [( mempty { F.amount_style_unit_side = Just F.Amount_Style_Side_Left , F.amount_style_unit_spaced = Just False } , F.amount { F.amount_quantity = Decimal 0 1 , F.amount_unit = "4 2" } )] , "1\"4 2\"" ==> [( mempty { F.amount_style_unit_side = Just F.Amount_Style_Side_Right , F.amount_style_unit_spaced = Just False } , F.amount { F.amount_quantity = Decimal 0 1 , F.amount_unit = "4 2" } )] , "$1.000,00" ==> [( mempty { F.amount_style_fractioning = Just ',' , F.amount_style_grouping_integral = Just $ F.Amount_Style_Grouping '.' [3] , F.amount_style_unit_side = Just F.Amount_Style_Side_Left , F.amount_style_unit_spaced = Just False } , F.amount { F.amount_quantity = Decimal 2 100000 , F.amount_unit = "$" } )] , "1.000,00$" ==> [( mempty { F.amount_style_fractioning = Just ',' , F.amount_style_grouping_integral = Just $ F.Amount_Style_Grouping '.' [3] , F.amount_style_unit_side = Just F.Amount_Style_Side_Right , F.amount_style_unit_spaced = Just False } , F.amount { F.amount_quantity = Decimal 2 100000 , F.amount_unit = "$" } )] ] , "read_comment" ~: let (==>) (txt::Text, end) = (~:) (Text.unpack txt) . (~?=) (rights [R.runParser (F.read_comment <* end) () "" txt]) in TestList [ ("; some comment", R.eof) ==> [" some comment"] , ("; some comment \n", R.newline <* R.eof) ==> [ " some comment " ] , ("; some comment \r\n", R.string "\r\n" <* R.eof) ==> [ " some comment " ] ] , "read_comments" ~: let (==>) (txt::Text, end) = (~:) (Text.unpack txt) . (~?=) (rights [R.runParser (F.read_comments <* end) () "" txt]) in TestList [ ("; some comment\n ; some other comment", R.eof) ==> [ [" some comment", " some other comment"] ] , ("; some comment \n", R.string "\n" <* R.eof) ==> [ [" some comment "] ] ] , "read_transaction_tag" ~: let (==>) (txt::Text, end) = (~:) (Text.unpack txt) . (~?=) ((\(Transaction.Transaction_Tag t) -> t) <$> rights [R.runParser (F.read_transaction_tag <* end) () "" txt]) in TestList [ ("#Name" , R.eof) ==> [("Name":|[], "")] , ("#Name:" , R.eof) ==> [] , ("#Name:name" , R.eof) ==> [("Name":|["name"], "")] , ("#Name=Value" , R.eof) ==> [("Name":|[], "Value")] , ("#Name = Value" , R.eof) ==> [("Name":|[], "Value")] , ("#Name=Value\n" , R.string "\n" <* R.eof) ==> [("Name":|[], "Value")] , ("#Name=Val ue" , R.eof) ==> [("Name":|[], "Val ue")] , ("#Name=," , R.eof) ==> [("Name":|[], ",")] , ("#Name=Val,ue" , R.eof) ==> [("Name":|[], "Val,ue")] , ("#Name=Val,ue:" , R.eof) ==> [("Name":|[], "Val,ue:")] , ("#Name=Val,ue :", R.eof) ==> [("Name":|[], "Val,ue :")] ] ,-} "read_posting" ~: let (==>) (txt::Text) = let read_context = ( F.read_context (const ()) F.journal ::F.Read_Context () ()) in (~:) (Text.unpack txt) . (~?=) ( either (error . show) (pure) $ R.runParser_with_Error (F.read_posting <* R.eof) read_context "" txt) . fmap (\p -> p { F.posting_sourcepos = R.newPos "" 1 1 }) in TestList [ "/A/B/C" ==> [F.posting ("A":|["B", "C"])] , "/A/B/C $1" ==> [(F.posting ("A":|["B", "C"])) { F.posting_amounts = Map.fromList [("$", 1)] }] , "/A/B/C $1" ==> [(F.posting ("A":|["B", "C"])) { F.posting_amounts = Map.fromList [("$", 1)] }] , "/A/B/C 1€" ==> [(F.posting ("A":|["B", "C"])) { F.posting_amounts = Map.fromList [("€", 1)] }] , "/A/B/C $1; some comment" ==> [(F.posting ("A":|["B", "C"])) { F.posting_amounts = Map.fromList [("$", 1)] , F.posting_comments = [" some comment"] }] , "/A/B/C; some comment" ==> [(F.posting ("A":|["B", "C"])) { F.posting_amounts = Map.fromList [] , F.posting_comments = [" some comment"] }] , "/A/B/C ; some comment" ==> [(F.posting ("A":|["B", "C"])) { F.posting_amounts = Map.fromList [] , F.posting_comments = [" some comment"] }] , "/A/B/C ; some comment\n ; some other comment" ==> [(F.posting ("A":|["B", "C"])) { F.posting_amounts = Map.fromList [] , F.posting_comments = [" some comment", " some other comment"] }] , "/A/B/C $1 ; some comment" ==> [(F.posting ("A":|["B", "C"])) { F.posting_amounts = Map.fromList [("$", 1)] , F.posting_comments = [" some comment"] }] , "/A/B/C #N=V" ==> [(F.posting ("A":|["B", "C"])) { F.posting_tags = Posting.Posting_Tags $ Tag.from_List [ ("N":|[], "V") ] }] , "/A/B/C #N:O=V" ==> [(F.posting ("A":|["B", "C"])) { F.posting_tags = Posting.Posting_Tags $ Tag.from_List [ ("N":|["O"], "V") ] }] , "/A/B/C #N=Val;ue" ==> [(F.posting ("A":|["B", "C"])) { F.posting_tags = Posting.Posting_Tags $ Tag.from_List [ ("N":|[], "Val;ue") ] }] , "/A/B/C #N=Val#ue" ==> [(F.posting ("A":|["B", "C"])) { F.posting_tags = Posting.Posting_Tags $ Tag.from_List [ ("N":|[], "Val#ue") ] }] , "/A/B/C #N=V ; some comment" ==> [(F.posting ("A":|["B", "C"])) { F.posting_tags = Posting.Posting_Tags $ Tag.from_List [ ("N":|[], "V") ] , F.posting_comments = [" some comment"] }] , "/A/B/C #N=V #O" ==> [(F.posting ("A":|["B", "C"])) { F.posting_tags = Posting.Posting_Tags $ Tag.from_List [ ("N":|[], "V"), ("O":|[], "") ] }] , "/A/B/C #N#O" ==> [] , "/A/B/C #N; #O" ==> [(F.posting ("A":|["B", "C"])) { F.posting_tags = Posting.Posting_Tags $ Tag.from_List [ ("N":|[], "") ] , F.posting_comments = [" #O"] }] , "/A/B/C #N #O" ==> [(F.posting ("A":|["B", "C"])) { F.posting_tags = Posting.Posting_Tags $ Tag.from_List [ ("N":|[], ""), ("O":|[], "") ] }] , "/A/B/C \n #N=V" ==> [(F.posting ("A":|["B", "C"])) { F.posting_tags = Posting.Posting_Tags $ Tag.from_List [ ("N":|[], "V") ] }] , "/A/B/C ; some comment\n #N=V" ==> [(F.posting ("A":|["B", "C"])) { F.posting_comments = [" some comment"] , F.posting_tags = Posting.Posting_Tags $ Tag.from_List [ ("N":|[], "V") ] }] , "/A/B/C ; some comment\n #N=V v\n #N2=V2 v2" ==> [(F.posting ("A":|["B", "C"])) { F.posting_comments = [" some comment"] , F.posting_tags = Posting.Posting_Tags $ Tag.from_List [ ("N":|[], "V v") , ("N2":|[], "V2 v2") ] }] , "/A/B/C\n #N=V\n #N=V2" ==> [(F.posting ("A":|["B", "C"])) { F.posting_tags = Posting.Posting_Tags $ Tag.from_List [ ("N":|[], "V") , ("N":|[], "V2") ] }] , "/A/B/C\n #N=V\n #N2=V" ==> [(F.posting ("A":|["B", "C"])) { F.posting_tags = Posting.Posting_Tags $ Tag.from_List [ ("N":|[], "V") , ("N2":|[], "V") ] }] ] , "read_transaction" ~: let (==>) (txt::Text) = let read_context = ( F.read_context (const ()) F.journal ::F.Read_Context () ()) in (~:) (Text.unpack txt) . (~?=) ( either (error . show) (pure) $ R.runParser_with_Error (F.read_transaction <* R.newline <* R.eof) read_context "" txt) . fmap (\t -> t { F.transaction_sourcepos = R.newPos "" 1 1 }) in TestList [ Text.unlines [ "2000-01-01 some wording" , " /A/B/C $1" , " /a/b/c $-1" ] ==> [F.transaction { F.transaction_dates= ( Time.zonedTimeToUTC $ Time.ZonedTime (Time.LocalTime (Time.fromGregorian 2000 01 01) (Time.TimeOfDay 0 0 0)) (Time.utc) , [] ) , F.transaction_wording="some wording" , F.transaction_postings = F.postings_by_account [ (F.posting ("A":|["B", "C"])) { F.posting_amounts = Map.fromList [ ("$", 1) ] , F.posting_sourcepos = R.newPos "" 2 2 } , (F.posting ("a":|["b", "c"])) { F.posting_amounts = Map.fromList [ ("$", -1) ] , F.posting_sourcepos = R.newPos "" 3 2 } ] }] , Text.unlines [ "2000-01-01 some wording ; some comment" , "; some other;comment" , " ; some last comment" , " /A/B/C $1" , " /a/b/c" ] ==> [F.transaction { F.transaction_comments = [ " some comment" , " some other;comment" , " some last comment" ] , F.transaction_dates= ( Time.zonedTimeToUTC $ Time.ZonedTime (Time.LocalTime (Time.fromGregorian 2000 01 01) (Time.TimeOfDay 0 0 0)) (Time.utc) , [] ) , F.transaction_wording="some wording" , F.transaction_postings = F.postings_by_account [ (F.posting ("A":|["B", "C"])) { F.posting_amounts = Map.fromList [ ("$", 1) ] , F.posting_sourcepos = R.newPos "" 4 2 } , (F.posting ("a":|["b", "c"])) { F.posting_amounts = Map.fromList [ ("$", -1) ] , F.posting_sourcepos = R.newPos "" 5 2 } ] }] ] , "read_journal" ~: TestList [ let (==>) (txt::Text) e = (~:) (Text.unpack txt) $ TestCase $ do jnl <- liftIO $ right (\j -> j{F.journal_last_read_time=Date.nil}) <$> R.runParserT_with_Error (F.read_journal "" {-<* R.eof-}) ( F.read_context id F.journal ::F.Read_Context (F.Charted F.Transaction) ([F.Charted F.Transaction])) "" (txt::Text) (@?=) (rights [jnl]) e in TestList [ Text.unlines [ "2000-01-01 1° wording" , " /A/B/C $1" , " /a/b/c" , "2000-01-02 2° wording" , " /A/B/C $1" , " /x/y/z" ] ==> [ F.journal { F.journal_content = fmap (Chart.Charted mempty) $ [ F.transaction { F.transaction_dates= ( Time.zonedTimeToUTC $ Time.ZonedTime (Time.LocalTime (Time.fromGregorian 2000 01 02) (Time.TimeOfDay 0 0 0)) (Time.utc) , [] ) , F.transaction_wording="2° wording" , F.transaction_postings = F.postings_by_account [ (F.posting ("A":|["B", "C"])) { F.posting_amounts = Map.fromList [ ("$", 1) ] , F.posting_sourcepos = R.newPos "" 5 2 } , (F.posting ("x":|["y", "z"])) { F.posting_amounts = Map.fromList [ ("$", -1) ] , F.posting_sourcepos = R.newPos "" 6 2 } ] , F.transaction_sourcepos = R.newPos "" 4 1 } , F.transaction { F.transaction_dates= ( Time.zonedTimeToUTC $ Time.ZonedTime (Time.LocalTime (Time.fromGregorian 2000 01 01) (Time.TimeOfDay 0 0 0)) (Time.utc) , [] ) , F.transaction_wording="1° wording" , F.transaction_postings = F.postings_by_account [ (F.posting ("A":|["B", "C"])) { F.posting_amounts = Map.fromList [ ("$", 1) ] , F.posting_sourcepos = R.newPos "" 2 2 } , (F.posting ("a":|["b", "c"])) { F.posting_amounts = Map.fromList [ ("$", -1) ] , F.posting_sourcepos = R.newPos "" 3 2 } ] , F.transaction_sourcepos = R.newPos "" 1 1 } ] , F.journal_files = [""] , F.journal_amount_styles = F.Amount_Styles $ Map.fromList [ ( F.Unit "$" , mempty { F.amount_style_unit_side = Just F.Amount_Style_Side_Left , F.amount_style_unit_spaced = Just False } ) ] } ] ] ] ] {-, "Write" ~: TestList [ "write_date" ~: let (==>) (txt::Text) e = (~:) (Text.unpack txt) $ (~?=) (F.write F.write_style { F.write_style_color = False , F.write_style_align = True } . F.write_date <$> rights [R.runParser_with_Error (F.read_date id Nothing <* R.eof) () "" txt]) [e] in TestList [ "" ~: ((F.write F.write_style { F.write_style_color = False , F.write_style_align = True } $ F.write_date Date.nil) ~?= "1970-01-01") , "2000-01-01" ==> "2000-01-01" , "2000-01-01_12:34:51_CET" ==> "2000-01-01_11:34:51" , "2000-01-01_12:34:51+01:10" ==> "2000-01-01_11:24:51" , "2000-01-01_12:34:51-01:10" ==> "2000-01-01_13:44:51" , "2000-01-01_01:02:03" ==> "2000-01-01_01:02:03" , "2000-01-01_01:02" ==> "2000-01-01_01:02" , "2000-01-01_01:00" ==> "2000-01-01_01:00" ] , "write_amount" ~: let (<==) (txt::Text) e = (~:) (Text.unpack txt) $ (~?=) (F.write F.write_style { F.write_style_color = False , F.write_style_align = True } $ F.write_amount e) (TL.fromStrict txt) in TestList [ "0" <== ( mempty , F.amount ) , "0.00" <== ( mempty , F.amount { F.amount_quantity = Decimal 2 0 } ) , "123" <== ( mempty , F.amount { F.amount_quantity = Decimal 0 123 } ) , "-123" <== ( mempty , F.amount { F.amount_quantity = Decimal 0 (- 123) } ) , "12.3" <== ( mempty { F.amount_style_fractioning = Just '.' } , F.amount { F.amount_quantity = Decimal 1 123 } ) , "1,234.56" <== ( mempty { F.amount_style_fractioning = Just '.' , F.amount_style_grouping_integral = Just $ F.Amount_Style_Grouping ',' [3] } , F.amount { F.amount_quantity = Decimal 2 123456 }) , "123,456,789,01,2.3456789" <== ( mempty { F.amount_style_fractioning = Just '.' , F.amount_style_grouping_integral = Just $ F.Amount_Style_Grouping ',' [1, 2, 3] } , F.amount { F.amount_quantity = Decimal 7 1234567890123456789 } ) , "1234567.8_90_123_456_789" <== ( mempty { F.amount_style_fractioning = Just '.' , F.amount_style_grouping_fractional = Just $ F.Amount_Style_Grouping '_' [1, 2, 3] } , F.amount { F.amount_quantity = Decimal 12 1234567890123456789 }) , "1,2,3,4,5,6,7,89,012.3456789" <== ( mempty { F.amount_style_fractioning = Just '.' , F.amount_style_grouping_integral = Just $ F.Amount_Style_Grouping ',' [3, 2, 1] } , F.amount { F.amount_quantity = Decimal 7 1234567890123456789 }) , "1234567.890_12_3_4_5_6_7_8_9" <== ( mempty { F.amount_style_fractioning = Just '.' , F.amount_style_grouping_fractional = Just $ F.Amount_Style_Grouping '_' [3, 2, 1] } , F.amount { F.amount_quantity = Decimal 12 1234567890123456789 }) ] , "write_amount_length" ~: let (==>) (txt::Text) = (~:) (Text.unpack txt) $ (~?=) (F.write_amount_length <$> rights [R.runParser (F.read_amount <* R.eof) () "" txt]) [Text.length txt] in TestList $ (==>) <$> [ "0.00" , "123" , "-123" , "12.3" , "12.5" , "12.3" , "1,234.56" , "123,456,789,01,2.3456789" , "1234567.8_90_123_456_789" , "1,2,3,4,5,6,7,89,012.3456789" , "1234567.890_12_3_4_5_6_7_8_9" , "1000000.000_00_0_0_0_0_0_0_0" , "999" , "1000" , "10,00€" , "10,00 €" , "€10,00" , "€ 10,00" , "EUR 10,00" , "10,00 EUR" , "\"4 2\" 10,00" ] , "write_account" ~: let (==>) txt = (~:) (Text.unpack txt) $ (~?=) (let read (t::Text) = rights [R.runParser (F.read_account <* R.eof) () "" t] in F.write F.write_style { F.write_style_color = False , F.write_style_align = True } <$> (liftM F.write_account $ read txt) ) [TL.fromStrict txt] in TestList $ (==>) <$> [ "/A/B/C" ] , "write_transaction" ~: let (==>) (txt::Text) = (~:) (Text.unpack txt) . (~?=) ( let write (txn, ctx) = F.write F.write_style { F.write_style_color = False , F.write_style_align = True } $ let jnl = F.read_context_journal ctx in let sty = F.journal_amount_styles jnl in F.write_transaction sty txn in either -- (const []) (pure . TL.pack . show) (pure . write) $ R.runParser_with_Error (R.and_state (F.read_transaction <* R.newline <* R.eof)) ( F.read_context Chart.charted F.journal ::F.Read_Context F.Transaction [F.Transaction] ) "" txt) in TestList $ [ Text.unlines [ "2000-01-01 some wording" , " /A/B/C $1" , " /a/b/c" ] ==> [TL.unlines [ "2000-01-01 some wording" , " /A/B/C $1" , " /a/b/c $-1" ]] , Text.unlines [ "2000-01-01 some wording" , " /A/B/C $1" , " /a/b/c" , " ; first comment" , " ; second comment" , " ; third comment" ] ==> [TL.unlines [ "2000-01-01 some wording" , " /A/B/C $1" , " /a/b/c $-1" , " ; first comment" , " ; second comment" , " ; third comment" ]] , Text.unlines [ "2000-01-01 some wording" , " /A/B/C $1" , " /AA/BB/CC $123" ] ==> [] ] ++ [ "nil" ~: ((F.write F.write_style { F.write_style_color = False , F.write_style_align = True } $ F.write_transaction F.amount_styles F.transaction) ~?= "1970-01-01\n\n") ] ] -}]