hledger/hledger/Hledger/Cli/Commands.hs
Simon Michael b940254025 imp:cli: detect --color more safely in most cases
--color now also works in a config file, like --pager, except for two
cases: it does not affect colouring of debug output, or the colouring
helpers used in the check recentassertions error message.
2024-10-19 16:28:36 -10:00

758 lines
28 KiB
Haskell

{-|
hledger's built-in commands, and helpers for printing the commands list.
New built-in commands should be added in four places below:
the export list, the import list, builtinCommands, commandsList.
-}
-- Note: commands list rendering is intensely sensitive to change,
-- very easy to break in ways that tests currently do not catch.
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE QuasiQuotes #-}
{-# LANGUAGE TemplateHaskell #-}
module Hledger.Cli.Commands (
testcmd
,builtinCommands
,builtinCommandNames
,findBuiltinCommand
,knownAddonCommands
,knownCommands
,printCommandsList
,tests_Hledger_Cli
,module Hledger.Cli.Commands.Accounts
,module Hledger.Cli.Commands.Activity
,module Hledger.Cli.Commands.Add
,module Hledger.Cli.Commands.Aregister
,module Hledger.Cli.Commands.Balance
,module Hledger.Cli.Commands.Balancesheet
,module Hledger.Cli.Commands.Balancesheetequity
,module Hledger.Cli.Commands.Cashflow
,module Hledger.Cli.Commands.Close
,module Hledger.Cli.Commands.Codes
,module Hledger.Cli.Commands.Commodities
,module Hledger.Cli.Commands.Demo
,module Hledger.Cli.Commands.Descriptions
,module Hledger.Cli.Commands.Diff
,module Hledger.Cli.Commands.Help
,module Hledger.Cli.Commands.Import
,module Hledger.Cli.Commands.Incomestatement
,module Hledger.Cli.Commands.Notes
,module Hledger.Cli.Commands.Payees
,module Hledger.Cli.Commands.Prices
,module Hledger.Cli.Commands.Print
,module Hledger.Cli.Commands.Register
,module Hledger.Cli.Commands.Rewrite
,module Hledger.Cli.Commands.Stats
,module Hledger.Cli.Commands.Tags
)
where
import Data.Char (isAlphaNum, isSpace)
import Data.List
import Data.List.Extra (nubSort)
import Data.Text (Text)
import qualified Data.Text as T
import Data.Time.Calendar
import Safe (headErr)
import String.ANSI
import System.Environment (withArgs)
import System.Console.CmdArgs.Explicit as C
import Test.Tasty (defaultMain)
import Hledger
import Hledger.Cli.CliOptions
import Hledger.Cli.Commands.Accounts
import Hledger.Cli.Commands.Activity
import Hledger.Cli.Commands.Add
import Hledger.Cli.Commands.Aregister
import Hledger.Cli.Commands.Balance
import Hledger.Cli.Commands.Balancesheet
import Hledger.Cli.Commands.Balancesheetequity
import Hledger.Cli.Commands.Cashflow
import Hledger.Cli.Commands.Check
import Hledger.Cli.Commands.Close
import Hledger.Cli.Commands.Codes
import Hledger.Cli.Commands.Commodities
import Hledger.Cli.Commands.Demo
import Hledger.Cli.Commands.Descriptions
import Hledger.Cli.Commands.Diff
import Hledger.Cli.Commands.Files
import Hledger.Cli.Commands.Help
import Hledger.Cli.Commands.Import
import Hledger.Cli.Commands.Incomestatement
import Hledger.Cli.Commands.Notes
import Hledger.Cli.Commands.Payees
import Hledger.Cli.Commands.Prices
import Hledger.Cli.Commands.Print
import Hledger.Cli.Commands.Register
import Hledger.Cli.Commands.Rewrite
import Hledger.Cli.Commands.Roi
import Hledger.Cli.Commands.Stats
import Hledger.Cli.Commands.Tags
import Hledger.Cli.Utils (tests_Cli_Utils)
-- | The cmdargs subcommand mode (for command-line parsing)
-- and IO action (for doing the command's work) for each builtin command.
-- Command actions take parsed CLI options and a (lazy) finalised journal.
builtinCommands :: [(Mode RawOpts, CliOpts -> Journal -> IO ())]
builtinCommands = [
(accountsmode , accounts)
,(activitymode , activity)
,(addmode , add)
,(aregistermode , aregister)
,(balancemode , balance)
,(balancesheetequitymode , balancesheetequity)
,(balancesheetmode , balancesheet)
,(cashflowmode , cashflow)
,(checkmode , check)
,(closemode , close)
,(codesmode , codes)
,(commoditiesmode , commodities)
,(demomode , demo)
,(descriptionsmode , descriptions)
,(diffmode , diff)
,(filesmode , files)
,(helpmode , help')
,(importmode , importcmd)
,(incomestatementmode , incomestatement)
,(notesmode , notes)
,(payeesmode , payees)
,(pricesmode , prices)
,(printmode , print')
,(registermode , register)
,(rewritemode , rewrite)
,(roimode , roi)
,(statsmode , stats)
,(tagsmode , tags)
,(testmode , testcmd)
]
-- figlet -f FONTNAME hledger, then escape backslashes
_banner_slant = drop 1 [""
-----------------------------------------80-------------------------------------
," __ __ __ "
," / /_ / /__ ____/ /___ ____ _____"
," / __ \\/ / _ \\/ __ / __ `/ _ \\/ ___/"
," / / / / / __/ /_/ / /_/ / __/ / "
,"/_/ /_/_/\\___/\\__,_/\\__, /\\___/_/ "
," /____/ "
]
_banner_smslant = drop 1 [""
," __ __ __ "
," / / / /__ ___/ /__ ____ ____"
," / _ \\/ / -_) _ / _ `/ -_) __/"
,"/_//_/_/\\__/\\_,_/\\_, /\\__/_/ "
," /___/ "
]
_banner_speed = drop 1 [""
,"______ ______ _________ "
,"___ /____ /__________ /______ _____________"
,"__ __ \\_ /_ _ \\ __ /__ __ `/ _ \\_ ___/"
,"_ / / / / / __/ /_/ / _ /_/ // __/ / "
,"/_/ /_//_/ \\___/\\__,_/ _\\__, / \\___//_/ "
," /____/ "
]
-- | Choose and apply an accent color for hledger output, if possible
-- picking one that will contrast with the current terminal background colour.
accent :: String -> String
accent
| not useColorOnStdoutUnsafe = id -- XXX unsafe accenting the title banner - seems to work, even respecting config file
| terminalIsLight == Just False = brightWhite
| terminalIsLight == Just True = brightBlack
| otherwise = id
-- | The commands list, showing command names, standard aliases,
-- and short descriptions. This is modified at runtime, as follows:
--
-- progversion is the program name and version.
--
-- Lines beginning with a space represent builtin commands, with format:
-- COMMAND (ALIASES) DESCRIPTION
-- These should be kept synced with builtinCommands above, and
-- their docs (Commands/\*.md).
--
-- Lines beginning with + represent known addon commands. These lines
-- will be suppressed if hledger-CMD is not found in $PATH at runtime.
--
-- OTHER is replaced with additional command lines (without descriptions)
-- for any unknown addon commands found in $PATH at runtime.
--
-- TODO: generate more of this automatically.
--
commandsList :: String -> [String] -> [String]
commandsList progversion othercmds =
map (bold'.accent) _banner_smslant ++
[
-- Keep the following synced with:
-- commands.m4
-- hledger.m4.md -> Commands
-- commandsFromCommandsList. Only commands should begin with space or plus.
-- IN PARTICULAR KEEP SYNCED WITH commandsListExtractCommands,
-- it needs checking/updating after any wording/layout changes here
"-------------------------------------------------------------------------------"
,progversion
,"Usage: hledger COMMAND [OPTIONS] [-- ADDONOPTIONS]"
,"Commands (builtins + addons):"
,""
-----------------------------------------80-------------------------------------
,bold' "HELP (docs, demos..)"
," (no arguments) show this commands list"
," -h [COMMAND] show command line help"
," --tldr [COMMAND] show command examples with tldr"
," --info [COMMAND] show the hledger manual with info"
," --man [COMMAND] show the hledger manual with man"
," help [-i|-m|-p] [TOPIC] show any topic in the hledger manual"
," demo [DEMO] show brief demos in the terminal"
," more help: https://hledger.org"
,""
-----------------------------------------80-------------------------------------
,bold' "USER INTERFACES (alternate UIs)"
,"+ui run terminal UI" -- hledger-ui
,"+web run web UI" -- hledger-web
-- see also: MoLe, https://hledger.org/mobile.html
,""
-----------------------------------------80-------------------------------------
,bold' "ENTERING DATA (add or edit transactions)"
," add add transactions using interactive prompts"
,"+iadd add transactions using a TUI" -- hledger-iadd
," import add new transactions from other files, eg CSV files"
,"+edit edit existing transactions with $EDITOR" -- hledger-utils
,""
-----------------------------------------80-------------------------------------
,bold' "BASIC REPORTS (simple lists)"
," accounts show account names"
," codes show transaction codes"
," commodities show commodity/currency symbols"
," descriptions show transaction descriptions"
," files show data files in use"
," notes show note part of transaction descriptions"
," payees show payee part of transaction descriptions"
," prices show historical market prices"
," stats show journal statistics"
," tags show tag names"
,""
-----------------------------------------80-------------------------------------
,bold' "STANDARD REPORTS (the most useful financial reports)"
," print show full transaction entries, or export journal data"
," aregister (areg) show transactions & running balance in one account"
," register (reg) show postings & running total in one or more accounts"
," balancesheet (bs) show assets and liabilities"
," balancesheetequity (bse) show assets, liabilities and equity"
," cashflow (cf) show changes in liquid assets"
," incomestatement (is) show revenues and expenses"
,""
-----------------------------------------80-------------------------------------
,bold' "ADVANCED REPORTS (more versatile/advanced reports)"
," balance (bal) show balance changes, end balances, gains, budgets.."
,"+lots show a commodity's lots" -- hledger-lots
," roi show return on investments"
,""
-----------------------------------------80-------------------------------------
,bold' "CHARTS (bar charts, line graphs..)"
," activity show posting counts as a bar chart"
,"+bar show balances or changes as a bar chart" -- hledger-bar
,"+plot show advanced matplotlib charts as gui/svg/png/pdf.." -- hledger-utils
,""
-----------------------------------------80-------------------------------------
,bold' "GENERATING DATA (generate or download journal entries; less common)"
,"+autosync download/deduplicate/show OFX data as transactions" -- ledger-autosync
," close generate transactions to zero/restore/assert balances"
,"+interest generate transactions transferring accrued interest" -- hledger-interest
,"+lots sell generate a lot-selling transaction" -- hledger-lots
,"+pricehist download historical market prices" -- pricehist
," rewrite add postings to transactions, like print --auto"
,""
-----------------------------------------80-------------------------------------
,bold' "MAINTENANCE (error checking, data management..)"
," check run any of hledger's built-in correctness checks"
,"+check-fancyassertions check more powerful balance assertions" -- hledger-check-fancyassertions
,"+check-tagfiles check that files referenced in tag values exist" -- hledger-check-tagfiles
," diff compare an account's transactions in two journals"
,"+git save or view journal file history simply in git" -- hledger-git
,"+pijul save or view journal file history simply in pijul" -- hledger-pijul
," test run some self tests"
,""
-----------------------------------------80-------------------------------------
,bold' "OTHER ADDONS (more hledger-* commands found in PATH):"
]
++ map (' ':) (lines $ multicol 79 othercmds)
++ [""]
-- | Extract just the command names from the default commands list above,
-- (the first word of lines between "Usage:" and "OTHER" beginning with a space or plus sign),
-- in the order they occur. With a true first argument, extracts only the addon command names.
commandsListExtractCommands :: Bool -> [String] -> [String]
commandsListExtractCommands addonsonly l =
[ cmdname | prefixchar:line@(firstchar:_) <-
takeWhile (not . isInfixOf "OTHER") $ dropWhile (not . isInfixOf "Usage:") l
, prefixchar `elem` '+':[' '|not addonsonly]
, isAlphaNum firstchar
, not $ "https://" `isInfixOf` line
, let cmdname:_ = words line
]
-- KEEP SYNCED WITH commandsList.
-- | Canonical names of all commands which have a slot in the commands list, in alphabetical order.
-- These include the builtin commands and the known addon commands.
knownCommands :: [String]
knownCommands = nubSort . commandsListExtractCommands False $ commandsList progname []
-- | Canonical names of the known addon commands which have a slot in the commands list,
-- in alphabetical order.
knownAddonCommands :: [String]
knownAddonCommands = nubSort . commandsListExtractCommands True $ commandsList progname []
-- | All names and aliases of the builtin commands.
builtinCommandNames :: [String]
builtinCommandNames = concatMap (modeNames . fst) builtinCommands
-- | Look up a builtin command's mode and action by exact command name or alias.
findBuiltinCommand :: String -> Maybe (Mode RawOpts, CliOpts -> Journal -> IO ())
findBuiltinCommand cmdname = find (elem cmdname . modeNames . fst) builtinCommands
-- | Print the commands list, with a pager if appropriate, customising the
-- commandsList template above with the given version string and the installed addons.
-- Uninstalled known addons will be removed from the list,
-- installed known addons will have the + prefix removed,
-- and installed unknown addons will be added under Misc.
printCommandsList :: String -> [String] -> IO ()
printCommandsList progversion installedaddons =
seq (length $ dbg8 "uninstalledknownaddons" uninstalledknownaddons) $ -- for debug output
seq (length $ dbg8 "installedknownaddons" installedknownaddons) $
seq (length $ dbg8 "installedunknownaddons" installedunknownaddons) $
runPager $ unlines $ map unplus $ filter (not.isuninstalledaddon) $
commandsList progversion installedunknownaddons
where
knownaddons = knownAddonCommands
uninstalledknownaddons = knownaddons \\ installedaddons
installedknownaddons = knownaddons `intersect` installedaddons
installedunknownaddons = installedaddons \\ knownaddons
unplus ('+':cs) = ' ':cs
unplus s = s
isuninstalledaddon =
\case
('+':l) | cmd `notElem` installedaddons ->
dbg9With (const $ "hiding uninstalled addon: "<>cmd) $
True where cmd = takeWhile (not . isSpace) l
_ -> False
-- The test command is defined here for easy access to other modules' tests.
testmode = hledgerCommandMode
$(embedFileRelative "Hledger/Cli/Commands/Test.txt")
[]
[generalflagsgroup3]
[]
([], Just $ argsFlag "[-- TASTYOPTS]")
-- | The test command, which runs the hledger and hledger-lib
-- packages' unit tests. This command also accepts tasty test runner
-- options, written after a -- (double hyphen).
--
-- Unlike most hledger commands, this one does not read the user's journal.
-- A 'Journal' argument remains in the type signature, but it should
-- not be used (and would raise an error).
--
testcmd :: CliOpts -> Journal -> IO ()
testcmd opts _undefined = do
withArgs (listofstringopt "args" $ rawopts_ opts) $
Test.Tasty.defaultMain $ testGroup "hledger" [
tests_Hledger
,tests_Hledger_Cli
]
-- All unit tests for Hledger.Cli, defined here rather than
-- Hledger.Cli so testcmd can use them.
tests_Hledger_Cli = testGroup "Hledger.Cli" [
tests_Cli_Utils
,tests_Commands
]
tests_Commands = testGroup "Commands" [
tests_Balance
,tests_Register
,tests_Aregister
-- some more tests easiest to define here:
,testGroup "apply account directive" [
testCase "works" $ do
let
ignoresourcepos j = j{jtxns=map (\t -> t{tsourcepos=nullsourcepos}) (jtxns j)}
sameParse str1 str2 = do
j1 <- ignoresourcepos <$> readJournal' str1 -- PARTIAL:
j2 <- ignoresourcepos <$> readJournal' str2 -- PARTIAL:
j1 @?= j2{jlastreadtime=jlastreadtime j1, jfiles=jfiles j1} --, jparsestate=jparsestate j1}
sameParse
("2008/12/07 One\n alpha $-1\n beta $1\n" <>
"apply account outer\n2008/12/07 Two\n aigh $-2\n bee $2\n" <>
"apply account inner\n2008/12/07 Three\n gamma $-3\n delta $3\n" <>
"end apply account\n2008/12/07 Four\n why $-4\n zed $4\n" <>
"end apply account\n2008/12/07 Five\n foo $-5\n bar $5\n"
)
("2008/12/07 One\n alpha $-1\n beta $1\n" <>
"2008/12/07 Two\n outer:aigh $-2\n outer:bee $2\n" <>
"2008/12/07 Three\n outer:inner:gamma $-3\n outer:inner:delta $3\n" <>
"2008/12/07 Four\n outer:why $-4\n outer:zed $4\n" <>
"2008/12/07 Five\n foo $-5\n bar $5\n"
)
,testCase "preserves \"virtual\" posting type" $ do
j <- readJournal' "apply account test\n2008/12/07 One\n (from) $-1\n (to) $1\n" -- PARTIAL:
let p = headErr $ tpostings $ headErr $ jtxns j -- PARTIAL headErrs succeed because txns & postings provided
paccount p @?= "test:from"
ptype p @?= VirtualPosting
]
,testCase "alias directive" $ do
j <- readJournal' "!alias expenses = equity:draw:personal\n1/1\n (expenses:food) 1\n" -- PARTIAL:
let p = headErr $ tpostings $ headErr $ jtxns j -- PARTIAL headErrs succeed because txns & postings provided
paccount p @?= "equity:draw:personal:food"
,testCase "Y default year directive" $ do
j <- readJournal' defaultyear_journal_txt -- PARTIAL:
tdate (headErr $ jtxns j) @?= fromGregorian 2009 1 1 -- PARTIAL headErr succeeds because defaultyear_journal_txt has a txn
,testCase "ledgerAccountNames" $
(ledgerAccountNames ledger7)
@?=
["assets","assets:cash","assets:checking","assets:saving","equity","equity:opening balances",
"expenses","expenses:food","expenses:food:dining","expenses:phone","expenses:vacation",
"liabilities","liabilities:credit cards","liabilities:credit cards:discover"]
-- ,testCase "journalCanonicaliseAmounts" ~:
-- "use the greatest precision" ~:
-- (map asprecision $ journalAmountAndPriceCommodities $ journalCanonicaliseAmounts $ journalWithAmounts ["1","2.00"]) @?= [2,2]
-- don't know what this should do
-- ,testCase "elideAccountName" ~: do
-- (elideAccountName 50 "aaaaaaaaaaaaaaaaaaaa:aaaaaaaaaaaaaaaaaaaa:aaaaaaaaaaaaaaaaaaaa"
-- @?= "aa:aaaaaaaaaaaaaaaaaaaa:aaaaaaaaaaaaaaaaaaaa")
-- (elideAccountName 20 "aaaaaaaaaaaaaaaaaaaa:aaaaaaaaaaaaaaaaaaaa:aaaaaaaaaaaaaaaaaaaa"
-- @?= "aa:aa:aaaaaaaaaaaaaa")
,testCase "show dollars" $ showAmount (usd 1) @?= "$1.00"
,testCase "show hours" $ showAmount (hrs 1) @?= "1.00h"
]
-- test data
-- date1 = fromGregorian 2008 11 26
-- t1 = LocalTime date1 midday
{-
samplejournal = readJournal' sample_journal_str
sample_journal_str = unlines
["; A sample journal file."
,";"
,"; Sets up this account tree:"
,"; assets"
,"; bank"
,"; checking"
,"; saving"
,"; cash"
,"; expenses"
,"; food"
,"; supplies"
,"; income"
,"; gifts"
,"; salary"
,"; liabilities"
,"; debts"
,""
,"2008/01/01 income"
," assets:bank:checking $1"
," income:salary"
,""
,"2008/06/01 gift"
," assets:bank:checking $1"
," income:gifts"
,""
,"2008/06/02 save"
," assets:bank:saving $1"
," assets:bank:checking"
,""
,"2008/06/03 * eat & shop"
," expenses:food $1"
," expenses:supplies $1"
," assets:cash"
,""
,"2008/12/31 * pay off"
," liabilities:debts $1"
," assets:bank:checking"
,""
,""
,";final comment"
]
-}
defaultyear_journal_txt :: Text
defaultyear_journal_txt = T.unlines
["Y2009"
,""
,"01/01 A"
," a $1"
," b"
]
-- write_sample_journal = writeFile "sample.journal" sample_journal_str
-- entry2_str = unlines
-- ["2007/01/27 * joes diner"
-- ," expenses:food:dining $10.00"
-- ," expenses:gifts $10.00"
-- ," assets:checking $-20.00"
-- ,""
-- ]
-- entry3_str = unlines
-- ["2007/01/01 * opening balance"
-- ," assets:cash $4.82"
-- ," equity:opening balances"
-- ,""
-- ,"2007/01/01 * opening balance"
-- ," assets:cash $4.82"
-- ," equity:opening balances"
-- ,""
-- ,"2007/01/28 coopportunity"
-- ," expenses:food:groceries $47.18"
-- ," assets:checking"
-- ,""
-- ]
-- periodic_entry1_str = unlines
-- ["~ monthly from 2007/2/2"
-- ," assets:saving $200.00"
-- ," assets:checking"
-- ,""
-- ]
-- periodic_entry2_str = unlines
-- ["~ monthly from 2007/2/2"
-- ," assets:saving $200.00 ;auto savings"
-- ," assets:checking"
-- ,""
-- ]
-- periodic_entry3_str = unlines
-- ["~ monthly from 2007/01/01"
-- ," assets:cash $4.82"
-- ," equity:opening balances"
-- ,""
-- ,"~ monthly from 2007/01/01"
-- ," assets:cash $4.82"
-- ," equity:opening balances"
-- ,""
-- ]
-- journal1_str = unlines
-- [""
-- ,"2007/01/27 * joes diner"
-- ," expenses:food:dining $10.00"
-- ," expenses:gifts $10.00"
-- ," assets:checking $-20.00"
-- ,""
-- ,""
-- ,"2007/01/28 coopportunity"
-- ," expenses:food:groceries $47.18"
-- ," assets:checking $-47.18"
-- ,""
-- ,""
-- ]
-- journal2_str = unlines
-- [";comment"
-- ,"2007/01/27 * joes diner"
-- ," expenses:food:dining $10.00"
-- ," assets:checking $-47.18"
-- ,""
-- ]
-- journal3_str = unlines
-- ["2007/01/27 * joes diner"
-- ," expenses:food:dining $10.00"
-- ,";intra-entry comment"
-- ," assets:checking $-47.18"
-- ,""
-- ]
-- journal4_str = unlines
-- ["include \"somefile\""
-- ,"2007/01/27 * joes diner"
-- ," expenses:food:dining $10.00"
-- ," assets:checking $-47.18"
-- ,""
-- ]
-- journal5_str = ""
-- journal6_str = unlines
-- ["~ monthly from 2007/1/21"
-- ," expenses:entertainment $16.23 ;netflix"
-- ," assets:checking"
-- ,""
-- ,"; 2007/01/01 * opening balance"
-- ,"; assets:saving $200.04"
-- ,"; equity:opening balances "
-- ,""
-- ]
-- journal7_str = unlines
-- ["2007/01/01 * opening balance"
-- ," assets:cash $4.82"
-- ," equity:opening balances "
-- ,""
-- ,"2007/01/01 * opening balance"
-- ," income:interest $-4.82"
-- ," equity:opening balances "
-- ,""
-- ,"2007/01/02 * ayres suites"
-- ," expenses:vacation $179.92"
-- ," assets:checking "
-- ,""
-- ,"2007/01/02 * auto transfer to savings"
-- ," assets:saving $200.00"
-- ," assets:checking "
-- ,""
-- ,"2007/01/03 * poquito mas"
-- ," expenses:food:dining $4.82"
-- ," assets:cash "
-- ,""
-- ,"2007/01/03 * verizon"
-- ," expenses:phone $95.11"
-- ," assets:checking "
-- ,""
-- ,"2007/01/03 * discover"
-- ," liabilities:credit cards:discover $80.00"
-- ," assets:checking "
-- ,""
-- ,"2007/01/04 * blue cross"
-- ," expenses:health:insurance $90.00"
-- ," assets:checking "
-- ,""
-- ,"2007/01/05 * village market liquor"
-- ," expenses:food:dining $6.48"
-- ," assets:checking "
-- ,""
-- ]
journal7 :: Journal
journal7 = nulljournal {jtxns =
[
txnTieKnot Transaction {
tindex=0,
tsourcepos=nullsourcepos,
tdate=fromGregorian 2007 01 01,
tdate2=Nothing,
tstatus=Unmarked,
tcode="*",
tdescription="opening balance",
tcomment="",
ttags=[],
tpostings=
["assets:cash" `post` usd 4.82
,"equity:opening balances" `post` usd (-4.82)
],
tprecedingcomment=""
}
,
txnTieKnot Transaction {
tindex=0,
tsourcepos=nullsourcepos,
tdate=fromGregorian 2007 02 01,
tdate2=Nothing,
tstatus=Unmarked,
tcode="*",
tdescription="ayres suites",
tcomment="",
ttags=[],
tpostings=
["expenses:vacation" `post` usd 179.92
,"assets:checking" `post` usd (-179.92)
],
tprecedingcomment=""
}
,
txnTieKnot Transaction {
tindex=0,
tsourcepos=nullsourcepos,
tdate=fromGregorian 2007 01 02,
tdate2=Nothing,
tstatus=Unmarked,
tcode="*",
tdescription="auto transfer to savings",
tcomment="",
ttags=[],
tpostings=
["assets:saving" `post` usd 200
,"assets:checking" `post` usd (-200)
],
tprecedingcomment=""
}
,
txnTieKnot Transaction {
tindex=0,
tsourcepos=nullsourcepos,
tdate=fromGregorian 2007 01 03,
tdate2=Nothing,
tstatus=Unmarked,
tcode="*",
tdescription="poquito mas",
tcomment="",
ttags=[],
tpostings=
["expenses:food:dining" `post` usd 4.82
,"assets:cash" `post` usd (-4.82)
],
tprecedingcomment=""
}
,
txnTieKnot Transaction {
tindex=0,
tsourcepos=nullsourcepos,
tdate=fromGregorian 2007 01 03,
tdate2=Nothing,
tstatus=Unmarked,
tcode="*",
tdescription="verizon",
tcomment="",
ttags=[],
tpostings=
["expenses:phone" `post` usd 95.11
,"assets:checking" `post` usd (-95.11)
],
tprecedingcomment=""
}
,
txnTieKnot Transaction {
tindex=0,
tsourcepos=nullsourcepos,
tdate=fromGregorian 2007 01 03,
tdate2=Nothing,
tstatus=Unmarked,
tcode="*",
tdescription="discover",
tcomment="",
ttags=[],
tpostings=
["liabilities:credit cards:discover" `post` usd 80
,"assets:checking" `post` usd (-80)
],
tprecedingcomment=""
}
]
}
ledger7 :: Ledger
ledger7 = ledgerFromJournal Any journal7