From c0ba028a64a70a3d8e92727808b8b894c83f98f4 Mon Sep 17 00:00:00 2001 From: Adrian Sieber Date: Sun, 4 Feb 2024 23:08:03 +0000 Subject: [PATCH] Add support for passing several journal files --- .gitignore | 2 +- cli-spec.ncl | 55 +++-- examples/journal-only-transactions.yaml | 16 ++ src/Main.purs | 254 +++++++++++++++--------- src/Transity/Data/Ledger.purs | 87 ++++---- src/Transity/Utils.purs | 19 +- test/Fixtures.purs | 16 +- test/Main.purs | 32 +-- 8 files changed, 310 insertions(+), 171 deletions(-) create mode 100644 examples/journal-only-transactions.yaml diff --git a/.gitignore b/.gitignore index 089ff3e..c8d761c 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,5 @@ /.parcel-cache -/.psci* +/.psc* /.spago /docs /docs-dev diff --git a/cli-spec.ncl b/cli-spec.ncl index b87d4e4..fc171ab 100644 --- a/cli-spec.ncl +++ b/cli-spec.ncl @@ -1,3 +1,16 @@ +let journalArgs = [ + { + name = "JOURNAL_FILE", + description = "Path to the journal file", + type = 'Text, + }, + { + name = "JOURNAL_FILE", + description = "Additional journal files", + type = 'List-Text, + }, +] +in { name = "transity", description = m%" @@ -9,68 +22,68 @@ { name = "balance", description = "Simple balance of the owner's accounts", - arguments = ["JOURNAL_FILE"], + arguments = journalArgs, }, { name = "balance-all", description = "Simple balance of all accounts", - arguments = ["JOURNAL_FILE"], + arguments = journalArgs, }, { name = "transactions", description = "All transactions and their transfers", - arguments = ["JOURNAL_FILE"], + arguments = journalArgs, }, { name = "transfers", description = "All transfers with one transfer per line", - arguments = ["JOURNAL_FILE"], + arguments = journalArgs, }, { name = "entries", description = m%" All individual deposits & withdrawals, space separated "%, - arguments = ["JOURNAL_FILE"], + arguments = journalArgs, }, { name = "entities", description = "[WIP] List all referenced entities", - arguments = ["JOURNAL_FILE"], + arguments = journalArgs, }, { name = "entities-sorted", description = m%" [WIP] List all referenced entities sorted alphabetically "%, - arguments = ["JOURNAL_FILE"], + arguments = journalArgs, }, { name = "ledger-entries", description = "All entries in Ledger format", - arguments = ["JOURNAL_FILE"], + arguments = journalArgs, }, { name = "csv", description = "Transfers, comma separated (printed to stdout)", - arguments = ["JOURNAL_FILE"], + arguments = journalArgs, }, { name = "tsv", description = "Transfers, tab separated (printed to stdout)", - arguments = ["JOURNAL_FILE"], + arguments = journalArgs, }, { name = "xlsx", description = "XLSX file with all transfers (printed to stdout)", - arguments = ["JOURNAL_FILE"], + arguments = journalArgs, }, { name = "entries-by-account", description = m%" All individual deposits & withdrawals, grouped by account "%, - arguments = ["JOURNAL_FILE"], + arguments = journalArgs, }, { name = "gplot", @@ -78,7 +91,7 @@ Code and data for gnuplot impulse diagram to visualize transfers of all accounts "%, - arguments = ["JOURNAL_FILE"], + arguments = journalArgs, }, { name = "gplot-cumul", @@ -86,7 +99,7 @@ Code and data for cumuluative gnuplot step chart to visualize balance of all accounts "%, - arguments = ["JOURNAL_FILE"], + arguments = journalArgs, }, { name = "unused-files", @@ -94,17 +107,25 @@ Recursively list all files in a directory which are not referenced in the journal "%, - arguments = ["DIRECTORY", "JOURNAL_FILE"], + arguments = + [ + { + name = "DIRECTORY", + description = "Path to the directory", + type = 'Text, + }, + ] + @ journalArgs, }, { name = "help", description = "Print this help dialog", - arguments = ["JOURNAL_FILE"], + arguments = journalArgs, }, { name = "version", description = "Print currently used version", - arguments = ["JOURNAL_FILE"], + arguments = journalArgs, }, ] } diff --git a/examples/journal-only-transactions.yaml b/examples/journal-only-transactions.yaml new file mode 100644 index 0000000..7032031 --- /dev/null +++ b/examples/journal-only-transactions.yaml @@ -0,0 +1,16 @@ +# This file on its own is not valid, +# but it can be merged with other journals +transactions: + - utc: '2020-09-28 14:55' + note: Bread and butter + transfers: + - from: john:giro + to: bakery + amount: 7.64 £ + + - utc: '2021-02-13 07:29' + title: New sneakers + transfers: + - from: anna + to: evil-corp + amount: 122 £ diff --git a/src/Main.purs b/src/Main.purs index 8b29a66..1bbb75b 100644 --- a/src/Main.purs +++ b/src/Main.purs @@ -1,37 +1,37 @@ module Main where import Prelude - ( Unit, bind, discard, pure, unit + ( Unit, bind, discard, pure, show, unit , (#), ($), (/=), (<#>), (<>), (>) ) import Ansi.Codes (Color(..)) import Ansi.Output (withGraphics, foreground) -import Data.Array (concat, cons, difference, filter, null, zip) +import CliSpec (parseCliSpec, callCliApp) +import CliSpec.JsonEmbed as CliSpec.JsonEmbed +import CliSpec.Types (CliArgPrim(..), CliArgument(..)) +import Data.Array (concat, cons, difference, filter, fold, null, zip) import Data.Eq ((==)) import Data.Foldable (foldMap) import Data.Maybe (Maybe(..)) import Data.Newtype (over) -import Data.Result (Result(..), note, isOk, fromEither) +import Data.Result (Result(..), fromEither, isOk, note) import Data.String (Pattern(..), indexOf, length) import Data.Traversable (for_, sequence) import Data.Tuple (Tuple(..), fst, snd) import Effect (Effect) import Effect.Aff (launchAff_) -import Effect.Class.Console (log, warn) +import Effect.Class.Console (log) +import Effect.Console (warn) import Node.Encoding (Encoding(UTF8)) import Node.FS.Async (stat) import Node.FS.Stats (isFile, isDirectory) import Node.FS.Sync as Sync import Node.Path as Path -import Node.Process (cwd) - -import CliSpec.JsonEmbed as CliSpec.JsonEmbed -import CliSpec.Types (CliArgument(..), CliArgPrim(..)) -import CliSpec (parseCliSpec, callCliApp) +import Node.Process (cwd, setExitCode) +import Transity.Data.Config (ColorFlag(..), config) import Transity.Data.Ledger (Ledger(..), BalanceFilter(..)) import Transity.Data.Ledger as Ledger -import Transity.Data.Config (ColorFlag(..), config) import Transity.Data.Transaction (Transaction(..)) import Transity.Plot as Plot import Transity.Utils (SortOrder(..), makeRed, errorAndExit) @@ -98,6 +98,31 @@ checkFilePaths ledgerFilePath (Ledger {transactions}) = do pure $ Ok "" +execForLedger + :: String + -> String + -> String + -> Ledger + -> Effect (Result String String) +execForLedger currentDir filePathRel command ledger = do + filePathAbs <- Path.resolve [currentDir] filePathRel + let + journalDir = + if indexOf (Pattern "/dev/fd/") filePathAbs == Just 0 + then currentDir + else Path.dirname filePathAbs + _ <- checkFilePaths journalDir ledger + + case command of + "xlsx" -> do + launchAff_ $ writeToZip + Nothing -- Means stdout + (entriesAsXlsx ledger) + pure (Ok "") + _ -> + pure $ runSimpleCmd command filePathRel ledger + + loadAndExec :: String -> Array String @@ -108,90 +133,139 @@ loadAndExec currentDir [command, filePathRel] = do case (Ledger.fromYaml ledgerFileContent) of Error msg -> pure $ Error msg - Ok ledger -> do - let - journalDir = - if indexOf (Pattern "/dev/fd/") filePathAbs == Just 0 - then currentDir - else Path.dirname filePathAbs - _ <- checkFilePaths journalDir ledger - - case command of - "xlsx" -> do - launchAff_ $ writeToZip - Nothing -- Means stdout - (entriesAsXlsx ledger) - pure (Ok "") - _ -> - pure $ runSimpleCmd command filePathRel ledger + Ok ledger -> execForLedger currentDir filePathRel command ledger loadAndExec _ _ = pure $ Error "loadAndExec expects an array with length 2" -executor :: String -> String -> Array CliArgument -> Effect Unit +executor :: String -> String -> Array CliArgument -> Effect (Result String Unit) executor cmdName usageString args = do - if cmdName == "unused-files" - then - case args of - [ ValArg (StringArg filesDirPath) - , ValArg (StringArg ledgerFilePath) - ] -> do - ledgerFilePathAbs <- Path.resolve [] ledgerFilePath - ledgerFileContent <- Sync.readTextFile UTF8 ledgerFilePathAbs - - case (Ledger.fromYaml ledgerFileContent) of - Error msg -> errorAndExit config msg - Ok ledger@(Ledger {transactions}) -> do - currentDir <- cwd - let - journalDir = - if indexOf (Pattern "/dev/fd/") ledgerFilePathAbs == Just 0 - then currentDir - else Path.dirname ledgerFilePathAbs - _ <- checkFilePaths journalDir ledger - - filesDir <- Path.resolve [] filesDirPath - foundFiles <- getAllFiles filesDir - let - ledgerFilesRel = foldMap - (\(Transaction tact) -> tact.files) - transactions - ledgerFilesAbs <- sequence $ ledgerFilesRel - <#> (\fileRel -> Path.resolve [journalDir] fileRel) - - let - unusedFiles = difference foundFiles ledgerFilesAbs - makeGreen = withGraphics (foreground Green) - makeYellow = withGraphics (foreground Yellow) - - if null unusedFiles - then log $ makeGreen $"No unused files found in " <> filesDir - else do - warn $ makeYellow $ "Warning: " - <> "Following files are not referenced in the journal" - - for_ unusedFiles $ \filePathAbs -> - log $ makeYellow $ "- " <> filePathAbs - - _ -> - log $ - makeRed config "ERROR: Wrong number of arguments for unused-files" - <> "\n\n" - <> usageString - - else - case args of - [ValArg (StringArg journalPathRel)] -> do - currentDir <- cwd - result <- loadAndExec currentDir [cmdName, journalPathRel] - - case result of - Ok output -> if length output > 0 - then log output - else pure unit - Error message -> errorAndExit config message - _ -> log usageString + case cmdName, args of + "unused-files", + [ ValArg (StringArg filesDirPath) + , ValArg (StringArg ledgerFilePath) + -- TODO: , ValArgList extraJournalPaths + ] -> do + ledgerFilePathAbs <- Path.resolve [] ledgerFilePath + ledgerFileContent <- Sync.readTextFile UTF8 ledgerFilePathAbs + + case (Ledger.fromYaml ledgerFileContent) of + Error msg -> errorAndExit config msg + Ok ledger@(Ledger {transactions}) -> do + currentDir <- cwd + let + journalDir = + if indexOf (Pattern "/dev/fd/") ledgerFilePathAbs == Just 0 + then currentDir + else Path.dirname ledgerFilePathAbs + + _ <- checkFilePaths journalDir ledger + + filesDir <- Path.resolve [] filesDirPath + foundFiles <- getAllFiles filesDir + let + ledgerFilesRel = foldMap + (\(Transaction tact) -> tact.files) + transactions + ledgerFilesAbs <- sequence $ ledgerFilesRel + <#> (\fileRel -> Path.resolve [journalDir] fileRel) + + let + unusedFiles = difference foundFiles ledgerFilesAbs + makeGreen = withGraphics (foreground Green) + makeYellow = withGraphics (foreground Yellow) + + if null unusedFiles + then + log $ makeGreen $ "No unused files found in " <> filesDir + else do + warn $ makeYellow $ "Warning: " + <> "Following files are not referenced in the journal" + + for_ unusedFiles $ \filePathAbs -> + log $ makeYellow $ "- " <> filePathAbs + + pure $ Ok unit + + "unused-files", + _ -> do + let errStr = "ERROR: Wrong number of arguments for unused-files" + log $ makeRed config errStr <> "\n\n" <> usageString + setExitCode 1 + pure $ Error errStr + + _, + [ValArg (StringArg journalPathRel)] -> do + currentDir <- cwd + result <- loadAndExec currentDir [cmdName, journalPathRel] + + case result of + Ok output -> + if length output > 0 + then do + log output + pure $ Ok unit + else + pure $ Ok unit + Error message -> + errorAndExit config message + + _, + [ ValArg (StringArg journalPathRel) + , ValArgList extraJournalPaths + ] -> do + currentDir <- cwd + + let + journalPaths :: Result String (Array String) + journalPaths = sequence $ + [Ok journalPathRel] <> + (extraJournalPaths + <#> (\valArg -> case valArg of + (StringArg path) -> Ok path + _ -> Error $ "Invalid argument type: " <> show valArg + ) + ) + + combineJournals :: Array String -> Effect (Result String Ledger) + combineJournals paths = do + paths + <#> (\filePathRel -> do + filePathAbs <- Path.resolve [currentDir] filePathRel + ledgerFileContent <- Sync.readTextFile UTF8 filePathAbs + + pure $ Ledger.fromYaml ledgerFileContent + ) + # sequence + <#> sequence + <#> (\ledgerRes -> ledgerRes <#> fold ) + + case journalPaths of + Error message -> errorAndExit config message + Ok paths -> do + combineRes <- combineJournals paths + case combineRes of + Error message -> errorAndExit config message + Ok ledger -> do + result <- execForLedger + currentDir + journalPathRel -- TODO: Must incorporate all paths + cmdName + ledger + + case result of + Ok output -> do + log output + pure $ Ok unit + Error message -> + errorAndExit config message + + _, + _ -> do + log usageString + setExitCode 1 + pure $ Ok unit getAllFiles :: String -> Effect (Array String) @@ -224,6 +298,8 @@ getAllFiles directoryPath = main :: Effect Unit main = do - case parseCliSpec CliSpec.JsonEmbed.fileContent of + _ <- case parseCliSpec CliSpec.JsonEmbed.fileContent of Error msg -> errorAndExit config msg Ok cliSpec -> callCliApp cliSpec executor + + pure unit diff --git a/src/Transity/Data/Ledger.purs b/src/Transity/Data/Ledger.purs index 42d2903..9d4884d 100644 --- a/src/Transity/Data/Ledger.purs +++ b/src/Transity/Data/Ledger.purs @@ -1,8 +1,9 @@ module Transity.Data.Ledger where import Prelude - ( class Show, class Eq, bind, compare, identity, map, pure, show - , (#), ($), (+), (<#>), (<>), (||), (&&), (==), (/=), (>>=) + ( class Eq, class Monoid, class Semigroup, class Show + , bind, compare, identity, map, pure, show + , (#), ($), (&&), (+), (/=), (<#>), (<>), (==), (||) ) import Control.Alt ((<|>)) @@ -10,8 +11,8 @@ import Control.Monad.Except (runExcept) import Control.Semigroupoid ((>>>)) import Data.Argonaut.Core (toObject, Json, stringify) import Data.Argonaut.Decode (decodeJson) -import Data.Argonaut.Encode (encodeJson) import Data.Argonaut.Decode.Class (class DecodeJson) +import Data.Argonaut.Encode (encodeJson) import Data.Argonaut.Parser (jsonParser) import Data.Array (concat, groupBy, sort, sortBy, uncons, (!!), length) import Data.Array as Array @@ -19,41 +20,31 @@ import Data.DateTime (DateTime) import Data.Foldable (all, find) import Data.Function (flip) import Data.Generic.Rep (class Generic) -import Data.Show.Generic (genericShow) import Data.HeytingAlgebra (not) import Data.Map as Map -import Data.Maybe (Maybe(..), maybe, fromMaybe, isJust) +import Data.Maybe (Maybe(..), maybe, fromMaybe) import Data.Monoid (power) -import Data.Newtype (unwrap) +import Data.Newtype (class Newtype, unwrap) import Data.Rational as Rational import Data.Result (Result(..), toEither, fromEither) import Data.Set as Set +import Data.Show.Generic (genericShow) import Data.String - ( joinWith - , Pattern(..) - , replace - , replaceAll - , Replacement(..) - , split - , stripPrefix - ) + (joinWith, Pattern(..), replace, replaceAll, Replacement(..), split) import Data.String.Common (toLower) +import Data.String.Utils (startsWith) import Data.Traversable (fold, foldr, intercalate, sequence) import Data.Tuple (Tuple(..)) import Data.Unit (Unit, unit) import Data.YAML.Foreign.Decode (parseYAMLToJson) import Foreign (renderForeignError) - import Transity.Data.Account (Account(..)) import Transity.Data.Account as Account import Transity.Data.Amount (Amount(..), Commodity, isZero) import Transity.Data.Amount as Amount import Transity.Data.CommodityMap - ( CommodityMap - , addAmountToMap - , subtractAmountFromMap - , isCommodityMapZero - , isCommodityZero + ( CommodityMap, addAmountToMap, subtractAmountFromMap + , isCommodityMapZero, isCommodityZero ) import Transity.Data.Config (ColorFlag(..)) import Transity.Data.Entity (Entity(..), toTransfers) @@ -61,29 +52,30 @@ import Transity.Data.Transaction (Transaction(..)) import Transity.Data.Transaction as Transaction import Transity.Data.Transfer (Transfer(..), negateTransfer) import Transity.Utils - ( getFieldMaybe + ( dateShowPretty + , dateShowPrettyLong + , getFieldMaybe , getObjField , mergeWidthRecords - , utcToIsoString - , utcToIsoDateString - , dateShowPretty - , dateShowPrettyLong - , widthRecordZero + , resultWithJsonDecodeError , SortOrder(..) , stringifyJsonDecodeError - , resultWithJsonDecodeError + , utcToIsoDateString + , utcToIsoString + , widthRecordZero ) -- | List of all transactions newtype Ledger = Ledger - { owner :: String + { owner :: Maybe String , entities :: Maybe (Array Entity) , transactions :: Array Transaction } derive instance genericLedger :: Generic Ledger _ derive newtype instance eqLedger :: Eq Ledger +derive instance newtypeLedger :: Newtype Ledger _ instance showLedger :: Show Ledger where show = genericShow @@ -92,6 +84,22 @@ instance decodeLedger :: DecodeJson Ledger where decodeJson json = toEither $ resultWithJsonDecodeError $ decodeJsonLedger json +instance monoidLedger :: Monoid Ledger where + mempty = Ledger + { owner: Nothing + , entities: Nothing + , transactions: [] + } + +instance semigroupLedger :: Semigroup Ledger where + append (Ledger l1) (Ledger l2) = + Ledger + { owner: l1.owner + , entities: l1.entities <> l2.entities + , transactions: l1.transactions <> l2.transactions + } + + data BalanceFilter = BalanceOnly String @@ -99,15 +107,11 @@ data BalanceFilter | BalanceAll -startsWith :: String -> String -> Boolean -startsWith prefix testString = - isJust $ stripPrefix (Pattern prefix) testString - decodeJsonLedger :: Json -> Result String Ledger decodeJsonLedger json = do object <- maybe (Error "Ledger is not an object") Ok (toObject json) - owner <- object `getObjField` "owner" + owner <- object `getFieldMaybe` "owner" entities <- object `getFieldMaybe` "entities" transactions <- object `getObjField` "transactions" pure $ Ledger {owner, entities, transactions} @@ -218,8 +222,8 @@ fromJson json = do jsonObj <- fromEither $ jsonParser json ledger <- stringifyJsonDecodeError $ fromEither $ decodeJson jsonObj pure ledger - >>= verifyAccounts - >>= verifyLedgerBalances + -- TODO: >>= verifyAccounts + -- TODO: >>= verifyLedgerBalances -- TODO: >>= addInitalBalance @@ -238,8 +242,8 @@ fromYaml yaml = Ok json -> stringifyJsonDecodeError $ fromEither $ decodeJson json in unverified - >>= verifyAccounts - >>= verifyLedgerBalances + -- TODO: >>= verifyAccounts + -- TODO: >>= verifyLedgerBalances showPretty :: Ledger -> String @@ -253,7 +257,7 @@ showPrettyAligned colorFlag (Ledger l) = (Transaction.showPrettyAligned colorFlag) l.transactions in "" - <> "Journal for \"" <> l.owner <> "\"\n" + <> "Journal for \"" <> (l.owner # fromMaybe "UNKNOWN") <> "\"\n" <> "=" `power` 80 <> "\n" <> fold transactionsPretty @@ -265,7 +269,7 @@ showTransfers colorFlag (Ledger l) = <#> Transaction.showTransfersWithDate colorFlag # fold in "" - <> "Journal for \"" <> l.owner <> "\"\n" + <> "Journal for \"" <> (l.owner # fromMaybe "UNKNOWN") <> "\"\n" <> "=" `power` 80 <> "\n" <> transactionsPretty @@ -404,7 +408,10 @@ showBalance balFilter colorFlag (Ledger ledger) = Array (Tuple Account.Id CommodityMap)) <#> (\accTuple -> case balFilter of BalanceAll -> accTuple - BalanceOnlyOwner -> mapToEmpty accTuple (ledger.owner) + BalanceOnlyOwner -> + case ledger.owner of + Just owner -> mapToEmpty accTuple owner + _ -> accTuple BalanceOnly account -> mapToEmpty accTuple account ) -- Don't show commodities with an amount of 0 diff --git a/src/Transity/Utils.purs b/src/Transity/Utils.purs index 3c6e8a7..b5860b9 100644 --- a/src/Transity/Utils.purs +++ b/src/Transity/Utils.purs @@ -1,7 +1,7 @@ module Transity.Utils where import Prelude ( - class Eq, bind, map, max, pure, show, Unit, + class Eq, bind, discard, map, max, pure, show, Unit, (#), ($), (+), (-), (/=), (<#>), (<>), (==), (>=), (>>=), (>>>) ) @@ -14,16 +14,13 @@ import Data.Argonaut.Decode.Error (printJsonDecodeError, JsonDecodeError(..)) import Data.Array (elem, all, (!!)) import Data.DateTime (DateTime) import Data.DateTime.Instant (instant, toDateTime) -import Data.Result (Result(..), fromEither) import Data.Formatter.DateTime (Formatter, FormatterCommand(..), format) as Fmt -import Effect (Effect) -import Effect.Exception (throw) -import JS.BigInt (fromInt, fromString, pow) import Data.List (fromFoldable) import Data.Maybe (Maybe(Just,Nothing), fromMaybe) import Data.Monoid (power) import Data.Nullable (Nullable, toMaybe) import Data.Rational (Rational, (%)) +import Data.Result (Result(..), fromEither) import Data.String ( indexOf , length @@ -36,7 +33,11 @@ import Data.String.CodeUnits (toCharArray, fromCharArray) import Data.Time.Duration (Milliseconds(Milliseconds)) import Data.Tuple (Tuple(..)) import Data.Unfoldable (replicate) +import Effect (Effect) +import Effect.Class.Console (error) import Foreign.Object (Object) +import JS.BigInt (fromInt, fromString, pow) +import Node.Process (setExitCode) import Transity.Data.Config @@ -256,14 +257,16 @@ alignNumber colorFlag intWidth fracWidth number = makeRed :: Config -> String -> String makeRed conf str = - if config.colorState == ColorYes + if conf.colorState == ColorYes then withGraphics (foreground Red) str else str -errorAndExit :: Config -> String -> Effect Unit +errorAndExit :: Config -> String -> Effect (Result String Unit) errorAndExit conf message = do - throw (makeRed conf message) + error (makeRed conf message) + setExitCode 1 + pure $ Error message -- | Decimal point is included in fraction => +1 diff --git a/test/Fixtures.purs b/test/Fixtures.purs index 71d5578..98ef307 100644 --- a/test/Fixtures.purs +++ b/test/Fixtures.purs @@ -240,16 +240,24 @@ commodityMapPrettyAligned = "" ledger :: Ledger ledger = Ledger - { owner: "John Doe" + { owner: Just "John Doe" , entities: Nothing , transactions: [ transactionSimple ] } +ledger2 :: Ledger +ledger2 = Ledger + { owner: Just "Anna Smith" + , entities: Nothing + , transactions: + [ transactionSimpleB ] + } + ledgerMultiTrans :: Ledger ledgerMultiTrans = Ledger - { owner: "John Doe" + { owner: Just "John Doe" , entities: Nothing , transactions: [ transactionSimple @@ -260,7 +268,7 @@ ledgerMultiTrans = Ledger ledgerEntities :: Ledger ledgerEntities = Ledger - { owner: "John Doe" + { owner: Just "John Doe" , entities: Just [ wrap $ (unwrap Entity.zero) { id = "Anna" } , wrap $ (unwrap Entity.zero) { id = "Bob" } @@ -478,7 +486,7 @@ ledgerShowed = """ , """ <> idToEntityStr "evil-corp" <> """ , """ <> idToEntityStr "john:giro" <> """ ]) - , owner: "John Doe" + , owner: (Just "John Doe") , transactions: [ """ <> transactionSimpleShowed <> """ ] diff --git a/test/Main.purs b/test/Main.purs index 10dd378..12d2839 100644 --- a/test/Main.purs +++ b/test/Main.purs @@ -1,6 +1,6 @@ module Test.Main where -import Prelude (Unit, (==)) +import Test.Fixtures import Control.Applicative (pure) import Control.Bind (discard, bind, (>>=)) @@ -9,9 +9,7 @@ import Data.Argonaut.Decode (decodeJson) import Data.Argonaut.Encode (encodeJson) import Data.Argonaut.Parser (jsonParser) import Data.Array (zipWith, find) -import JS.BigInt (fromString) as BigInt import Data.Eq ((/=)) -import Data.Result (Result(Error, Ok), fromEither, isOk, isError) import Data.Foldable (fold) import Data.Function ((#), ($)) import Data.Functor (map) @@ -19,8 +17,9 @@ import Data.Map (fromFoldable) import Data.Map as Map import Data.Maybe (Maybe(Just, Nothing), fromJust) import Data.Monoid (power) -import Data.Newtype (over) +import Data.Newtype (modify, over) import Data.Rational (Rational, (%)) +import Data.Result (Result(Error, Ok), fromEither, isOk, isError) import Data.Ring (negate) import Data.Semigroup ((<>)) import Data.Show (show) @@ -33,14 +32,16 @@ import Data.Tuple (Tuple(..)) import Data.Unit (unit) import Effect (Effect) import Effect.Aff (Aff, launchAff_) +import JS.BigInt (fromString) as BigInt import Partial.Unsafe (unsafePartial) -import Test.Fixtures -import Test.Spec (describe , it , pending') +import Prelude (Unit, (==)) +import Test.CliSpec as Test.CliSpec +import Test.Fixtures as Fixtures +import Test.Spec (describe, it, pending') import Test.Spec.Assertions (expectError, fail, shouldEqual) import Test.Spec.Assertions.String (shouldContain) import Test.Spec.Reporter.Console (consoleReporter) import Test.Spec.Runner (runSpec) - import Transity.Data.Account (Account(..)) import Transity.Data.Account as Account import Transity.Data.Amount (Amount(..), Commodity(..)) @@ -60,15 +61,13 @@ import Transity.Data.Transfer as Transfer import Transity.Utils ( digitsToRational , indentSubsequent - , SortOrder(..) - , stringToDateTime , ratioZero + , SortOrder(..) , stringifyJsonDecodeError + , stringToDateTime ) import Transity.Xlsx (entriesAsXlsx, writeToZip, FileEntry(..)) -import Test.CliSpec as Test.CliSpec - rmWhitespace :: String -> String rmWhitespace str = @@ -679,7 +678,7 @@ main = launchAff_ $ runSpec [consoleReporter] do , utc: Nothing }) ]) - , owner: "John Doe" + , owner: Just "John Doe" , transactions: [(Transaction { id: Nothing @@ -837,3 +836,12 @@ main = launchAff_ $ runSpec [consoleReporter] do it "serializes to HLedger format" do (Ledger.entriesToLedger ledger) `shouldEqualString` ledgerLedger + + + it "keeps first owner when combining several ledgers" do + let + (Ledger combined) = + Fixtures.ledger + <> (Fixtures.ledger2 # modify (_ { owner = Nothing })) + + combined.owner `shouldEqual` (Just "John Doe")