diff --git a/hledger-lib/Hledger/Data/Amount.hs b/hledger-lib/Hledger/Data/Amount.hs index 609918e69..cf8f903d1 100644 --- a/hledger-lib/Hledger/Data/Amount.hs +++ b/hledger-lib/Hledger/Data/Amount.hs @@ -79,6 +79,7 @@ module Hledger.Data.Amount ( amountUnstyled, commodityStylesFromAmounts, -- canonicalStyleFrom, + getAmounts, -- ** rendering AmountFormat(..), @@ -566,6 +567,13 @@ instance HasAmounts Amount where olds) Nothing -> olds +-- | Get an amount and its attached cost amount if any. Returns one or two amounts. +getAmounts :: Amount -> [Amount] +getAmounts a@Amount{acost} = a : case acost of + Nothing -> [] + Just (UnitCost c) -> [c] + Just (TotalCost c) -> [c] + -- AmountStyle helpers -- | Replace one AmountStyle with another, but don't just replace the display precision; @@ -1094,13 +1102,17 @@ mixedAmountSetStyles = styleAmounts -- v4 instance HasAmounts MixedAmount where styleAmounts styles = mapMixedAmountUnsafe (styleAmounts styles) + -- getAmounts = concatMap getAmounts . amounts instance HasAmounts BalanceData where styleAmounts styles balance@BalanceData{bdexcludingsubs,bdincludingsubs} = balance{bdexcludingsubs=styleAmounts styles bdexcludingsubs, bdincludingsubs=styleAmounts styles bdincludingsubs} + -- getAmounts BalanceData{bdexcludingsubs, bdincludingsubs} = + -- getAmounts bdexcludingsubs <> getAmounts bdincludingsubs instance HasAmounts a => HasAmounts (PeriodData a) where styleAmounts styles = fmap (styleAmounts styles) + -- getAmounts instance HasAmounts a => HasAmounts (Account a) where styleAmounts styles acct@Account{adata} = diff --git a/hledger-lib/Hledger/Data/Journal.hs b/hledger-lib/Hledger/Data/Journal.hs index 667997305..2d05de113 100644 --- a/hledger-lib/Hledger/Data/Journal.hs +++ b/hledger-lib/Hledger/Data/Journal.hs @@ -71,9 +71,14 @@ module Hledger.Data.Journal ( journalTagsDeclared, journalTagsUsed, journalTagsDeclaredOrUsed, + journalAmounts, + journalPostingAmounts, + journalPostingAndCostAmounts, journalCommoditiesDeclared, journalCommoditiesUsed, journalCommodities, + journalCommoditiesFromPriceDirectives, + journalCommoditiesFromTransactions, journalDateSpan, journalDateSpanBothDates, journalStartDate, @@ -86,8 +91,7 @@ module Hledger.Data.Journal ( journalNextTransaction, journalPrevTransaction, journalPostings, - journalPostingAmounts, - showJournalAmountsDebug, + showJournalPostingAmountsDebug, journalTransactionsSimilarTo, -- * Account types journalAccountType, @@ -395,25 +399,47 @@ journalPostings = concatMap tpostings . jtxns journalPostingAmounts :: Journal -> [MixedAmount] journalPostingAmounts = map pamount . journalPostings --- | Show the journal amounts rendered, suitable for debug logging. -showJournalAmountsDebug :: Journal -> String -showJournalAmountsDebug = show.map showMixedAmountOneLine.journalPostingAmounts +-- | Show the journal posting amounts rendered, suitable for debug logging. +showJournalPostingAmountsDebug :: Journal -> String +showJournalPostingAmountsDebug = show . map showMixedAmountOneLine . journalPostingAmounts + +-- | All raw amounts used in this journal's postings and costs, +-- with MixedAmounts flattened, in parse order. +journalPostingAndCostAmounts :: Journal -> [Amount] +journalPostingAndCostAmounts = concatMap getAmounts . concatMap (amountsRaw . pamount) . journalPostings + +-- | All raw amounts appearing in this journal, with MixedAmounts flattened, in no particular order. +-- (Including from posting amounts, cost amounts, P directives, and the last D directive.) +journalAmounts :: Journal -> S.Set Amount +journalAmounts = S.fromList . journalStyleInfluencingAmounts True -- | Sorted unique commodity symbols declared by commodity directives in this journal. journalCommoditiesDeclared :: Journal -> [CommoditySymbol] journalCommoditiesDeclared = M.keys . jdeclaredcommodities --- | Sorted unique commodity symbols used in this journal. +-- | Sorted unique commodity symbols used anywhere in this journal, including +-- commodity directives, P directives, the last D directive, posting amounts and cost amounts. journalCommoditiesUsed :: Journal -> [CommoditySymbol] -journalCommoditiesUsed = S.elems . S.fromList . concatMap (map acommodity . amounts) . journalPostingAmounts +journalCommoditiesUsed j = S.elems $ + journalCommoditiesFromPriceDirectives j <> + (S.fromList $ map acommodity $ journalStyleInfluencingAmounts True j) --- | Sorted unique commodity symbols mentioned in this journal. +-- | Sorted unique commodity symbols mentioned anywhere in this journal. +-- (Including commodity directives, P directives, the last D directive, posting amounts and cost amounts.) journalCommodities :: Journal -> S.Set CommoditySymbol journalCommodities j = M.keysSet (jdeclaredcommodities j) - <> M.keysSet (jinferredcommoditystyles j) - <> S.fromList (concatMap pdcommodities $ jpricedirectives j) - where pdcommodities pd = [pdcommodity pd, acommodity $ pdamount pd] + <> journalCommoditiesFromPriceDirectives j + <> S.fromList (map acommodity $ journalStyleInfluencingAmounts True j) + +-- | Sorted unique commodity symbols mentioned in this journal's P directives. +journalCommoditiesFromPriceDirectives :: Journal -> S.Set CommoditySymbol +journalCommoditiesFromPriceDirectives = S.fromList . concatMap pdcomms . jpricedirectives + where pdcomms pd = [pdcommodity pd, acommodity $ pdamount pd] + +-- | Sorted unique commodity symbols used in transactions, in either posting or cost amounts. +journalCommoditiesFromTransactions :: Journal -> S.Set CommoditySymbol +journalCommoditiesFromTransactions j = S.fromList $ map acommodity $ journalPostingAndCostAmounts j -- | Unique transaction descriptions used in this journal. journalDescriptions :: Journal -> [Text] @@ -901,11 +927,11 @@ journalCommodityStylesWith :: Rounding -> Journal -> M.Map CommoditySymbol Amoun journalCommodityStylesWith r = amountStylesSetRounding r . journalCommodityStyles -- | Collect and save inferred amount styles for each commodity based on --- the posting amounts in that commodity (excluding price amounts). +-- P directive amounts, posting amounts but not cost amounts, and maybe the last D amount, in that commodity. -- Can return an error message eg if inconsistent number formats are found. journalInferCommodityStyles :: Journal -> Either String Journal journalInferCommodityStyles j = - case commodityStylesFromAmounts $ journalStyleInfluencingAmounts j of + case commodityStylesFromAmounts $ journalStyleInfluencingAmounts False j of Left e -> Left e Right cs -> Right j{jinferredcommoditystyles = dbg7 "journalInferCommodityStyles" cs} @@ -982,23 +1008,23 @@ journalInferEquityFromCosts verbosetags j = -- Just (UnitCost ma) -> c:(concatMap amountCommodities $ amounts ma) -- Just (TotalCost ma) -> c:(concatMap amountCommodities $ amounts ma) --- | Get an ordered list of amounts in this journal which can --- influence canonical amount display styles. Those amounts are, in --- the following order: +-- | Get an ordered list of amounts in this journal which can influence +-- canonical amount display styles (excluding the ones in commodity directives). +-- They are, in the following order: -- -- * amounts in market price (P) directives (in parse order) --- * posting amounts in transactions (in parse order) +-- * posting amounts and optionally cost amounts (in parse order) -- * the amount in the final default commodity (D) directive -- --- Transaction price amounts (posting amounts' acost field) are not included. --- -journalStyleInfluencingAmounts :: Journal -> [Amount] -journalStyleInfluencingAmounts j = +journalStyleInfluencingAmounts :: Bool -> Journal -> [Amount] +journalStyleInfluencingAmounts includecost j = dbg7 "journalStyleInfluencingAmounts" $ catMaybes $ concat [ [mdefaultcommodityamt] ,map (Just . pdamount) $ jpricedirectives j - ,map Just . concatMap (amountsRaw . pamount) $ journalPostings j + ,map Just $ if includecost + then journalPostingAndCostAmounts j + else concatMap amountsRaw $ journalPostingAmounts j ] where -- D's amount style isn't actually stored as an amount, make it into one diff --git a/hledger-lib/Hledger/Data/Posting.hs b/hledger-lib/Hledger/Data/Posting.hs index 94cccfa4b..f64363d91 100644 --- a/hledger-lib/Hledger/Data/Posting.hs +++ b/hledger-lib/Hledger/Data/Posting.hs @@ -435,7 +435,7 @@ postingStatus Posting{pstatus=s, ptransaction=mt} = case s of postingAllTags :: Posting -> [Tag] postingAllTags p = ptags p ++ maybe [] ttags (ptransaction p) --- | Tags for this transaction including any from its postings. +-- | Tags for this transaction including any from its postings (which includes any from the postings' accounts). transactionAllTags :: Transaction -> [Tag] transactionAllTags t = ttags t ++ concatMap ptags (tpostings t) diff --git a/hledger-lib/Hledger/Query.hs b/hledger-lib/Hledger/Query.hs index a626dcbf2..54d3dddf2 100644 --- a/hledger-lib/Hledger/Query.hs +++ b/hledger-lib/Hledger/Query.hs @@ -67,7 +67,8 @@ module Hledger.Query ( matchesMixedAmount, matchesAmount, matchesCommodity, - matchesTags, + matchesTag, + -- patternsMatchTags, matchesPriceDirective, words'', queryprefixes, @@ -883,7 +884,7 @@ matchesAccountExtra atypes atags (And qs ) a = all (\q -> matchesAccountExtra at matchesAccountExtra atypes atags (AnyPosting qs ) a = all (\q -> matchesAccountExtra atypes atags q a) qs matchesAccountExtra atypes atags (AllPostings qs ) a = all (\q -> matchesAccountExtra atypes atags q a) qs matchesAccountExtra atypes _ (Type ts) a = maybe False (\t -> any (t `isAccountSubtypeOf`) ts) $ atypes a -matchesAccountExtra _ atags (Tag npat vpat) a = matchesTags npat vpat $ atags a +matchesAccountExtra _ atags (Tag npat vpat) a = patternsMatchTags npat vpat $ atags a matchesAccountExtra _ _ q a = matchesAccount q a -- | Does the match expression match this posting ? @@ -911,7 +912,7 @@ matchesPosting (Sym r) Posting{pamount=as} = any (matchesCommodity (Sym r) . aco matchesPosting (Tag n v) p = case (reString n, v) of ("payee", Just v') -> maybe False (regexMatchText v' . transactionPayee) $ ptransaction p ("note", Just v') -> maybe False (regexMatchText v' . transactionNote) $ ptransaction p - (_, mv) -> matchesTags n mv $ postingAllTags p + (_, mv) -> patternsMatchTags n mv $ postingAllTags p matchesPosting (Type _) _ = False -- | Like matchesPosting, but if the posting's account's type is provided, @@ -958,7 +959,7 @@ matchesTransaction q@(Sym _) t = any (q `matchesPosting`) $ tpostings t matchesTransaction (Tag n v) t = case (reString n, v) of ("payee", Just v') -> regexMatchText v' $ transactionPayee t ("note", Just v') -> regexMatchText v' $ transactionNote t - (_, v') -> matchesTags n v' $ transactionAllTags t + (_, v') -> patternsMatchTags n v' $ transactionAllTags t matchesTransaction (Type _) _ = False -- | Like matchesTransaction, but if the journal's account types are provided, @@ -974,7 +975,7 @@ matchesTransactionExtra atype q@(Type _) t = any (matchesPostingExtra atype q) $ matchesTransactionExtra _ q t = matchesTransaction q t -- | Does the query match this transaction description ? --- Tests desc: terms, any other terms are ignored. +-- Non-desc: query terms are ignored (this might disrupt some boolean queries). matchesDescription :: Query -> Text -> Bool matchesDescription (Not q) d = not $ q `matchesDescription` d matchesDescription (Any) _ = True @@ -994,12 +995,25 @@ matchesDescription _ _ = False matchesPayeeWIP :: Query -> Payee -> Bool matchesPayeeWIP = matchesDescription --- | Does the query match the name and optionally the value of any of these tags ? -matchesTags :: Regexp -> Maybe Regexp -> [Tag] -> Bool -matchesTags namepat valuepat = any (matches namepat valuepat) +-- | Do this name regex and optional value regex match the name and value of any of these tags ? +patternsMatchTags :: Regexp -> Maybe Regexp -> [Tag] -> Bool +patternsMatchTags namepat valuepat = any (matches namepat valuepat) where matches npat vpat (n,v) = regexMatchText npat n && maybe (const True) regexMatchText vpat v +-- | Does the query match the name and optionally the value of this tag ? +-- Non-tag: query terms are ignored (this might disrupt some boolean queries). +matchesTag :: Query -> Tag -> Bool +matchesTag (Not q) t = not $ q `matchesTag` t +matchesTag (Any) _ = True +matchesTag (None) _ = False +matchesTag (Or qs) t = any (`matchesTag` t) $ filter queryIsTag qs +matchesTag (And qs) t = all (`matchesTag` t) $ filter queryIsTag qs +matchesTag (AnyPosting qs) t = all (`matchesTag` t) $ filter queryIsTag qs +matchesTag (AllPostings qs) t = all (`matchesTag` t) $ filter queryIsTag qs +matchesTag (Tag npat mvpat) t = patternsMatchTags npat mvpat [t] +matchesTag _ _ = False + -- | Does the query match this market price ? matchesPriceDirective :: Query -> PriceDirective -> Bool matchesPriceDirective (None) _ = False diff --git a/hledger-lib/Hledger/Read/Common.hs b/hledger-lib/Hledger/Read/Common.hs index bf5911218..7b7efbfe0 100644 --- a/hledger-lib/Hledger/Read/Common.hs +++ b/hledger-lib/Hledger/Read/Common.hs @@ -388,16 +388,16 @@ journalFinalise iopts@InputOpts{auto_,balancingopts_,infer_costs_,infer_equity_, -- XXX how to force debug output here ? -- >>= Right . dbg0With (concatMap (T.unpack.showTransaction).jtxns) -- >>= \j -> deepseq (concatMap (T.unpack.showTransaction).jtxns $ j) (return j) - <&> dbg9With (lbl "amounts after styling, forecasting, auto-posting".showJournalAmountsDebug) + <&> dbg9With (lbl "amounts after styling, forecasting, auto-posting".showJournalPostingAmountsDebug) >>= (\j -> if checkordereddates then journalCheckOrdereddates j $> j else Right j) -- check ordereddates before assertions. The outer parentheses are needed. >>= journalBalanceTransactions balancingopts_{ignore_assertions_=not checkassertions} -- infer balance assignments and missing amounts, and maybe check balance assertions. - <&> dbg9With (lbl "amounts after transaction-balancing".showJournalAmountsDebug) - -- <&> dbg9With (("journalFinalise amounts after styling, forecasting, auto postings, transaction balancing"<>).showJournalAmountsDebug) + <&> dbg9With (lbl "amounts after transaction-balancing".showJournalPostingAmountsDebug) + -- <&> dbg9With (("journalFinalise amounts after styling, forecasting, auto postings, transaction balancing"<>).showJournalPostingAmountsDebug) >>= journalInferCommodityStyles -- infer commodity styles once more now that all posting amounts are present -- >>= Right . dbg0With (pshow.journalCommodityStyles) >>= (if infer_costs_ then journalTagCostsAndEquityAndMaybeInferCosts verbose_tags_ True else pure) -- With --infer-costs, infer costs from equity postings where possible <&> (if infer_equity_ then journalInferEquityFromCosts verbose_tags_ else id) -- With --infer-equity, infer equity postings from costs where possible - <&> dbg9With (lbl "amounts after equity-inferring".showJournalAmountsDebug) + <&> dbg9With (lbl "amounts after equity-inferring".showJournalPostingAmountsDebug) <&> journalInferMarketPricesFromTransactions -- infer market prices from commodity-exchanging transactions -- <&> dbg6Msg fname -- debug logging <&> dbgJournalAcctDeclOrder (fname <> ": acct decls : ") diff --git a/hledger/Hledger/Cli/CliOptions.hs b/hledger/Hledger/Cli/CliOptions.hs index cdbe6669a..a1f4eeb79 100644 --- a/hledger/Hledger/Cli/CliOptions.hs +++ b/hledger/Hledger/Cli/CliOptions.hs @@ -81,6 +81,8 @@ module Hledger.Cli.CliOptions ( -- * Other utils topicForMode, + UsedOrDeclared(..), + usedOrDeclaredFromOpts, -- -- * Convenience re-exports -- module Data.String.Here, @@ -815,6 +817,29 @@ registerWidthsFromOpts CliOpts{width_=Just s} = eof return (totalwidth, descwidth) +-- A common choice for filtering lists of declarable things. +data UsedOrDeclared + = Used + | Declared + | Undeclared + | Unused + deriving (Show, Eq) + +-- Get the flag of this kind from opts, or raise an error if there's more than one. +usedOrDeclaredFromOpts :: CliOpts -> Maybe UsedOrDeclared +usedOrDeclaredFromOpts CliOpts{rawopts_=rawopts} = + case ( boolopt "used" rawopts + , boolopt "declared" rawopts + , boolopt "undeclared" rawopts + , boolopt "unused" rawopts + ) of + (False, False, False, False) -> Nothing + (True, False, False, False) -> Just Used + (False, True, False, False) -> Just Declared + (False, False, True, False) -> Just Undeclared + (False, False, False, True) -> Just Unused + _ -> error' "please pick at most one of --used, --declared, --undeclared, --unused" + -- Other utils -- None of https://hackage.haskell.org/package/directory-1.3.8.1/docs/System-Directory.html#g:5 diff --git a/hledger/Hledger/Cli/Commands/Accounts.hs b/hledger/Hledger/Cli/Commands/Accounts.hs index 190ea0c05..cc94d3168 100644 --- a/hledger/Hledger/Cli/Commands/Accounts.hs +++ b/hledger/Hledger/Cli/Commands/Accounts.hs @@ -36,33 +36,29 @@ import Safe (headDef) accountsmode = hledgerCommandMode $(embedFileRelative "Hledger/Cli/Commands/Accounts.txt") ( - [flagNone ["used","u"] (setboolopt "used") "show only accounts used by transactions" - ,flagNone ["declared","d"] (setboolopt "declared") "show only accounts declared by account directive" -- no s to avoid line wrap - ,flagNone ["unused"] (setboolopt "unused") "show only accounts declared but not used" - ,flagNone ["undeclared"] (setboolopt "undeclared") "show only accounts used but not declared" + [flagNone ["used","u"] (setboolopt "used") "list accounts used" + ,flagNone ["declared","d"] (setboolopt "declared") "list accounts declared" + ,flagNone ["undeclared"] (setboolopt "undeclared") "list accounts used but not declared" + ,flagNone ["unused"] (setboolopt "unused") "list accounts declared but not used" + ,flagNone ["find"] (setboolopt "find") "list the first account matched by the first argument (a case-insensitive infix regexp)" + ,flagNone ["types"] (setboolopt "types") "also show account types when known" ,flagNone ["positions"] (setboolopt "positions") "also show where accounts were declared" ,flagNone ["directives"] (setboolopt "directives") "show as account directives, for use in journals" - ,flagNone ["find"] (setboolopt "find") "find the first account matched by the first argument (a case-insensitive infix regexp or account name)" ] ++ flattreeflags False ++ [flagReq ["drop"] (\s opts -> Right $ setopt "drop" s opts) "N" "flat mode: omit N leading account name parts"] ) cligeneralflagsgroups1 hiddenflags - ([], Just $ argsFlag "[QUERY]") + ([], Just $ argsFlag "[QUERY..]") -- | The accounts command. accounts :: CliOpts -> Journal -> IO () -accounts CliOpts{rawopts_=rawopts, reportspec_=ReportSpec{_rsQuery=query,_rsReportOpts=ropts}} j = do +accounts opts@CliOpts{rawopts_=rawopts, reportspec_=ReportSpec{_rsQuery=query,_rsReportOpts=ropts}} j = do -- 1. identify the accounts we'll show let tree = tree_ ropts - used = boolopt "used" rawopts - decl = boolopt "declared" rawopts - unused = boolopt "unused" rawopts - undecl = boolopt "undeclared" rawopts - find_ = boolopt "find" rawopts types = boolopt "types" rawopts positions = boolopt "positions" rawopts directives = boolopt "directives" rawopts @@ -71,13 +67,13 @@ accounts CliOpts{rawopts_=rawopts, reportspec_=ReportSpec{_rsQuery=query,_rsRepo -- just the acct: part of the query will be reapplied later, after clipping acctq = dbg4 "acctq" $ filterQuery queryIsAcct query dep = dbg4 "depth" $ queryDepth $ filterQuery queryIsDepth query - matcheddeclaredaccts = dbg5 "matcheddeclaredaccts" $ + matchedused = dbg5 "matchedused" $ nub $ map paccount $ journalPostings $ filterJournalPostings nodepthq j + matcheddeclared = dbg5 "matcheddeclared" $ nub $ filter (matchesAccountExtra (journalAccountType j) (journalInheritedAccountTags j) nodepthq) $ map fst $ jdeclaredaccounts j - matchedusedaccts = dbg5 "matchedusedaccts" $ nub $ map paccount $ journalPostings $ filterJournalPostings nodepthq j - matchedunusedaccts = dbg5 "matchedunusedaccts" $ nub $ matcheddeclaredaccts \\ matchedusedaccts - matchedundeclaredaccts = dbg5 "matchedundeclaredaccts" $ nub $ matchedusedaccts \\ matcheddeclaredaccts + matchedundeclared = dbg5 "matchedundeclared" $ nub $ matchedused \\ matcheddeclared + matchedunused = dbg5 "matchedunused" $ nub $ matcheddeclared \\ matchedused -- keep synced with aregister matchedacct = dbg5 "matchedacct" $ fromMaybe (error' $ show apat ++ " did not match any account.") -- PARTIAL: @@ -89,14 +85,16 @@ accounts CliOpts{rawopts_=rawopts, reportspec_=ReportSpec{_rsQuery=query,_rsRepo apat = headDef (error' "With --find, please provide an account name or\naccount pattern (case-insensitive, infix, regexp) as first command argument.") $ listofstringopt "args" rawopts - - accts = dbg5 "accts to show" $ if - | not decl && used -> matchedusedaccts - | decl && not used -> matcheddeclaredaccts - | unused -> matchedunusedaccts - | undecl -> matchedundeclaredaccts - | find_ -> [matchedacct] - | otherwise -> matcheddeclaredaccts ++ matchedusedaccts + matchedall = matcheddeclared ++ matchedused + accts = dbg5 "accts to show" $ + case (usedOrDeclaredFromOpts opts, boolopt "find" rawopts) of + (Nothing, False) -> matchedall + (Nothing, True) -> [matchedacct] + (Just Used, False) -> matchedused + (Just Declared, False) -> matcheddeclared + (Just Undeclared, False) -> matchedundeclared + (Just Unused, False) -> matchedunused + _ -> error' "please pick at most one of --used, --declared, --undeclared, --unused, --find" -- 2. sort them by declaration order (then undeclared accounts alphabetically) -- within each group of siblings diff --git a/hledger/Hledger/Cli/Commands/Accounts.md b/hledger/Hledger/Cli/Commands/Accounts.md index 15346476c..9e0064edd 100644 --- a/hledger/Hledger/Cli/Commands/Accounts.md +++ b/hledger/Hledger/Cli/Commands/Accounts.md @@ -1,39 +1,34 @@ ## accounts -List account names. +List the account names used or declared in the journal. ```flags Flags: - -u --used show only accounts used by transactions - -d --declared show only accounts declared by account directive - --unused show only accounts declared but not used - --undeclared show only accounts used but not declared + -u --used list accounts used + -d --declared list accounts declared + --undeclared list accounts used but not declared + --unused list accounts declared but not used + --find list the first account matched by the first + argument (a case-insensitive infix regexp) --types also show account types when known --positions also show where accounts were declared --directives show as account directives, for use in journals - --find find the first account matched by the first - argument (a case-insensitive infix regexp or - account name) -l --flat list/tree mode: show accounts as a flat list (default) -t --tree list/tree mode: show accounts as a tree --drop=N flat mode: omit N leading account name parts ``` -This command lists account names. -By default it shows all known accounts, either used in transactions or declared with account directives. +This command lists account names - all of them by default. +or just the ones which have been used in transactions, +or declared with `account` directives, +or used but not declared, +or declared but not used, +or just the first account name matched by a pattern. -With query arguments, only matched account names and account names referenced by matched postings are shown. +You can add query arguments to select a subset of transactions or accounts. -Or it can show just -the used accounts (`--used`/`-u`), -the declared accounts (`--declared`/`-d`), -the accounts declared but not used (`--unused`), -the accounts used but not declared (`--undeclared`), -or the first account matched by an account name pattern, if any (`--find`). - -It shows a flat list by default. With `--tree`, it uses indentation to -show the account hierarchy. +It shows a flat list by default. With `--tree`, it uses indentation to show the account hierarchy. In flat mode you can add `--drop N` to omit the first few account name components. Account names can be depth-clipped with `depth:N` or `--depth N` or `-N`. @@ -44,8 +39,7 @@ With `--positions`, it also shows the file and line number of each account's declaration, if any, and the account's overall declaration order; these may be useful when troubleshooting account display order. -With `--directives`, it adds the `account` keyword, showing -valid account directives which can be pasted into a journal file. +With `--directives`, it shows valid account directives which could be pasted into a journal file. This is useful together with `--undeclared` when updating your account declarations to satisfy `hledger check accounts`. diff --git a/hledger/Hledger/Cli/Commands/Commodities.hs b/hledger/Hledger/Cli/Commands/Commodities.hs index 152573146..5470d7012 100644 --- a/hledger/Hledger/Cli/Commands/Commodities.hs +++ b/hledger/Hledger/Cli/Commands/Commodities.hs @@ -12,22 +12,47 @@ module Hledger.Cli.Commands.Commodities ( ,commodities ) where +import qualified Data.Map as M import qualified Data.Set as S import qualified Data.Text.IO as T +import System.Console.CmdArgs.Explicit import Hledger import Hledger.Cli.CliOptions +import Data.List.Extra (nubSort) +import Data.List ((\\)) -- | Command line options for this command. commoditiesmode = hledgerCommandMode $(embedFileRelative "Hledger/Cli/Commands/Commodities.txt") - [] + [flagNone ["used"] (setboolopt "used") "list commodities used" + ,flagNone ["declared"] (setboolopt "declared") "list commodities declared" + ,flagNone ["undeclared"] (setboolopt "undeclared") "list commodities used but not declared" + ,flagNone ["unused"] (setboolopt "unused") "list commodities declared but not used" + ] [generalflagsgroup2] [] - ([], Nothing) + ([], Just $ argsFlag "[QUERY..]") commodities :: CliOpts -> Journal -> IO () -commodities _copts = - -- TODO support --declared/--used like accounts, payees - mapM_ T.putStrLn . S.filter (/= "AUTO") . journalCommodities +commodities opts@CliOpts{reportspec_ = ReportSpec{_rsQuery = query}} j = do + let + used = dbg5 "used" $ + S.toList $ journalCommoditiesFromPriceDirectives j <> journalCommoditiesFromTransactions j + declared' = dbg5 "declared" $ M.keys $ jdeclaredcommodities j + unused = dbg5 "unused" $ declared' \\ used + undeclared = dbg5 "undeclared" $ used \\ declared' + all' = dbg5 "all" $ nubSort $ concat [ + journalCommoditiesDeclared j + ,map pdcommodity $ jpricedirectives j -- gets the first symbol from P directives + ,map acommodity (S.toList $ journalAmounts j) -- includes the second symbol from P directives + ] + + mapM_ T.putStrLn $ filter (matchesCommodity query) $ + case usedOrDeclaredFromOpts opts of + Nothing -> all' + Just Used -> used + Just Declared -> declared' + Just Undeclared -> undeclared + Just Unused -> unused diff --git a/hledger/Hledger/Cli/Commands/Commodities.md b/hledger/Hledger/Cli/Commands/Commodities.md index 465ffe6ab..ce97b0f83 100644 --- a/hledger/Hledger/Cli/Commands/Commodities.md +++ b/hledger/Hledger/Cli/Commands/Commodities.md @@ -1,9 +1,19 @@ ## commodities -List all commodity/currency symbols used or declared in the journal. +List the commodity symbols used or declared in the journal. ```flags Flags: -no command-specific flags + --used list commodities used + --declared list commodities declared + --undeclared list commodities used but not declared + --unused list commodities declared but not used ``` +This command lists commodity symbols/names - all of them by default, +or just the ones which have been used in transactions or `P` directives, +or declared with `commodity` directives, +or used but not declared, +or declared but not used. + +You can add cur: query arguments to further limit the commodities. diff --git a/hledger/Hledger/Cli/Commands/Descriptions.md b/hledger/Hledger/Cli/Commands/Descriptions.md index 5984df825..4363cc915 100644 --- a/hledger/Hledger/Cli/Commands/Descriptions.md +++ b/hledger/Hledger/Cli/Commands/Descriptions.md @@ -1,6 +1,6 @@ ## descriptions -List the unique descriptions that appear in transactions. +List the unique descriptions used in transactions. ```flags Flags: diff --git a/hledger/Hledger/Cli/Commands/Payees.hs b/hledger/Hledger/Cli/Commands/Payees.hs index 9df001827..12227ecc8 100644 --- a/hledger/Hledger/Cli/Commands/Payees.hs +++ b/hledger/Hledger/Cli/Commands/Payees.hs @@ -14,35 +14,41 @@ module Hledger.Cli.Commands.Payees ( ,payees ) where -import qualified Data.Set as S import qualified Data.Text.IO as T -import System.Console.CmdArgs.Explicit as C +import System.Console.CmdArgs.Explicit import Hledger import Hledger.Cli.CliOptions +import Data.List ((\\)) +import Data.List.Extra (nubSort) -- | Command line options for this command. payeesmode = hledgerCommandMode $(embedFileRelative "Hledger/Cli/Commands/Payees.txt") - [flagNone ["declared"] (setboolopt "declared") "show payees declared with payee directives" - ,flagNone ["used"] (setboolopt "used") "show payees referenced by transactions" + [flagNone ["used"] (setboolopt "used") "list payees used" + ,flagNone ["declared"] (setboolopt "declared") "list payees declared" + ,flagNone ["undeclared"] (setboolopt "undeclared") "list payees used but not declared" + ,flagNone ["unused"] (setboolopt "unused") "list payees declared but not used" ] cligeneralflagsgroups1 hiddenflags - ([], Just $ argsFlag "[QUERY]") + ([], Just $ argsFlag "[QUERY..]") -- | The payees command. payees :: CliOpts -> Journal -> IO () -payees CliOpts{rawopts_=rawopts, reportspec_=ReportSpec{_rsQuery=query}} j = do +payees opts@CliOpts{reportspec_=ReportSpec{_rsQuery=query}} j = do let - decl = boolopt "declared" rawopts - used = boolopt "used" rawopts - -- XXX matchesPayee is currently an alias for matchesDescription, not sure if it matters - matcheddeclaredpayees = S.fromList . filter (matchesPayeeWIP query) $ journalPayeesDeclared j - matchedusedpayees = S.fromList . map transactionPayee $ filter (matchesTransaction query) $ jtxns j - payees' = - if | decl && not used -> matcheddeclaredpayees - | not decl && used -> matchedusedpayees - | otherwise -> matcheddeclaredpayees <> matchedusedpayees - mapM_ T.putStrLn payees' + -- XXX matchesPayeeWIP is currently an alias for matchesDescription, not sure if it matters + matchedused = dbg5 "matchedused" $ nubSort $ map transactionPayee $ filter (matchesTransaction query) $ jtxns j + matcheddeclared = dbg5 "matcheddeclared" $ nubSort $ filter (matchesPayeeWIP query) $ journalPayeesDeclared j + matchedunused = dbg5 "matchedunused" $ nubSort $ matcheddeclared \\ matchedused + matchedundeclared = dbg5 "matchedundeclared" $ nubSort $ matchedused \\ matcheddeclared + matchedall = dbg5 "matchedall" $ nubSort $ matcheddeclared ++ matchedused + mapM_ T.putStrLn $ case usedOrDeclaredFromOpts opts of + Nothing -> matchedall + Just Used -> matchedused + Just Declared -> matcheddeclared + Just Undeclared -> matchedundeclared + Just Unused -> matchedunused + diff --git a/hledger/Hledger/Cli/Commands/Payees.md b/hledger/Hledger/Cli/Commands/Payees.md index 1f160347a..79fbdc28f 100644 --- a/hledger/Hledger/Cli/Commands/Payees.md +++ b/hledger/Hledger/Cli/Commands/Payees.md @@ -1,23 +1,25 @@ ## payees -List the unique payee/payer names that appear in transactions. +List the payee/payer names used or declared in the journal. ```flags Flags: - --declared show payees declared with payee directives - --used show payees referenced by transactions + --used list payees used + --declared list payees declared + --undeclared list payees used but not declared + --unused list payees declared but not used ``` -This command lists unique payee/payer names which have been -declared with payee directives (--declared), -used in transaction descriptions (--used), -or both (the default). +This command lists unique payee/payer names - all of them by default, +or just the ones which have been used in transaction descriptions, +or declared with `payee` directives, +or used but not declared, +or declared but not used. -The payee/payer is the part of the transaction description before a | character +The payee/payer name is the part of the transaction description before a | character (or if there is no |, the whole description). -You can add query arguments to select a subset of transactions. This implies --used. - +You can add query arguments to select a subset of transactions or payees. Example: ```cli diff --git a/hledger/Hledger/Cli/Commands/Print.hs b/hledger/Hledger/Cli/Commands/Print.hs index 153a06a82..957e63ce2 100644 --- a/hledger/Hledger/Cli/Commands/Print.hs +++ b/hledger/Hledger/Cli/Commands/Print.hs @@ -60,7 +60,7 @@ printmode = hledgerCommandMode flagReq ["match","m"] (\s opts -> Right $ setopt "match" s opts) arg ("fuzzy search for one recent transaction with description closest to "++arg) ,flagReq ["base-url"] (\s opts -> Right $ setopt "base-url" s opts) "URLPREFIX" "in html output, generate links to hledger-web, with this prefix. (Usually the base url shown by hledger-web; can also be relative.)" - ,flagNone ["location"] (setboolopt "location") "add file/line number tags to print output" + ,flagNone ["location"] (setboolopt "location") "add tags showing file paths and line numbers" ,outputFormatFlag ["txt","beancount","csv","tsv","html","fods","json","sql"] ,outputFileFlag ]) @@ -113,9 +113,9 @@ print' opts@CliOpts{rawopts_=rawopts} j = do let -- lbl = lbl_ "print'" j' = j - -- & dbg9With (lbl "amounts before setting full precision".showJournalAmountsDebug) + -- & dbg9With (lbl "amounts before setting full precision".showJournalPostingAmountsDebug) & journalMapPostingAmounts mixedAmountSetFullPrecision - -- & dbg9With (lbl "amounts after setting full precision: ".showJournalAmountsDebug) + -- & dbg9With (lbl "amounts after setting full precision: ".showJournalPostingAmountsDebug) & if boolopt "location" rawopts then journalMapTransactions addLocationTag else id case maybestringopt "match" $ rawopts_ opts of diff --git a/hledger/Hledger/Cli/Commands/Tags.hs b/hledger/Hledger/Cli/Commands/Tags.hs index bcb392e2f..f3b53ec82 100644 --- a/hledger/Hledger/Cli/Commands/Tags.hs +++ b/hledger/Hledger/Cli/Commands/Tags.hs @@ -1,5 +1,6 @@ {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE TemplateHaskell #-} +{-# LANGUAGE TupleSections #-} module Hledger.Cli.Commands.Tags ( tagsmode @@ -12,47 +13,73 @@ import Data.List.Extra (nubSort) import qualified Data.Text as T import qualified Data.Text.IO as T import Safe -import System.Console.CmdArgs.Explicit as C +import System.Console.CmdArgs.Explicit + import Hledger import Hledger.Cli.CliOptions + tagsmode = hledgerCommandMode $(embedFileRelative "Hledger/Cli/Commands/Tags.txt") - [flagNone ["values"] (setboolopt "values") "list tag values instead of tag names" - ,flagNone ["parsed"] (setboolopt "parsed") "show tags/values in the order they were parsed, including duplicates" + [ + flagNone ["used"] (setboolopt "used") "list tags used" + ,flagNone ["declared"] (setboolopt "declared") "list tags declared" + ,flagNone ["undeclared"] (setboolopt "undeclared") "list tags used but not declared" + ,flagNone ["unused"] (setboolopt "unused") "list tags declared but not used" + ,flagNone ["values"] (setboolopt "values") "list tag values instead of tag names" + ,flagNone ["parsed"] (setboolopt "parsed") "show them in the order they were parsed (mostly), including duplicates" ] cligeneralflagsgroups1 hiddenflags - ([], Just $ argsFlag "[TAGREGEX [QUERY...]]") + ([], Just $ argsFlag "[TAGREGEX [QUERY..]]") tags :: CliOpts -> Journal -> IO () -tags CliOpts{rawopts_=rawopts,reportspec_=rspec} j = do +tags opts@CliOpts{rawopts_=rawopts, reportspec_=rspec@ReportSpec{_rsQuery=_q, _rsReportOpts=ropts}} j = do let today = _rsDay rspec args = listofstringopt "args" rawopts - -- first argument is a tag name pattern, others are a hledger query: hledger tags [TAGREGEX [QUERYARGS..]] + -- For convenience/power, the first argument is a tag name regex, + -- separate from the main query arguments: hledger tags [TAGREGEX [QUERYARGS..]] + -- So we have to re-parse the query here. Overcomplicated ? mtagpat <- mapM (either Fail.fail pure . toRegexCI . T.pack) $ headMay args let - querystr = map T.pack $ drop 1 args values = boolopt "values" rawopts parsed = boolopt "parsed" rawopts - empty = empty_ $ _rsReportOpts rspec + empty = empty_ ropts + querystr = map T.pack $ drop 1 args query <- either usageError (return . fst) $ parseQueryList today querystr let - q = simplifyQuery $ And [queryFromFlags $ _rsReportOpts rspec, query] - matchedtxns = filter (q `matchesTransaction`) $ jtxns $ journalApplyValuationFromOpts rspec j - -- also list tags from matched account declarations, but not if there is - -- a query for something transaction-related, like date: or amt:. - matchedaccts = dbg4 "accts" $ - if dbg4 "queryIsTransactionRelated" $ queryIsTransactionRelated $ dbg4 "q" q + q = simplifyQuery $ And [queryFromFlags ropts, query] + txns = filter (q `matchesTransaction`) $ jtxns $ journalApplyValuationFromOpts rspec j + accts = + -- also search for tags in matched account declarations, + -- unless there is a query for something transaction-specific, like date: or amt:. + if dbg5 "queryIsTransactionRelated" $ queryIsTransactionRelated $ dbg4 "q" q then [] else filter (matchesAccountExtra (journalAccountType j) (journalInheritedAccountTags j) q) $ map fst $ jdeclaredaccounts j - tagsorvalues = + + used = dbg5 "used" $ concatMap (journalAccountTags j) accts ++ concatMap transactionAllTags txns + declared' = dbg5 "declared" $ filter (q `matchesTag`) $ map (,"") $ journalTagsDeclared j + (usednames, declarednames) = (map fst used, map fst declared') + unused = dbg5 "unused" $ filter (not . (`elem` usednames) . fst) declared' + undeclared = dbg5 "undeclared" $ filter (not . (`elem` declarednames) . fst) used + all' = dbg5 "all" $ declared' <> used + + tags' = + case usedOrDeclaredFromOpts opts of + Nothing -> all' + Just Used -> used + Just Declared -> declared' + Just Undeclared -> undeclared + Just Unused -> unused + + results = (if parsed then id else nubSort) [ r - | (t,v) <- concatMap (journalAccountTags j) matchedaccts ++ concatMap transactionAllTags matchedtxns + | (t,v) <- tags' , maybe True (`regexMatchText` t) mtagpat , let r = if values then v else t , not (values && T.null v && not empty) ] - mapM_ T.putStrLn tagsorvalues + + mapM_ T.putStrLn results diff --git a/hledger/Hledger/Cli/Commands/Tags.md b/hledger/Hledger/Cli/Commands/Tags.md index e174f5129..9d733682b 100644 --- a/hledger/Hledger/Cli/Commands/Tags.md +++ b/hledger/Hledger/Cli/Commands/Tags.md @@ -1,32 +1,39 @@ ## tags -List the tags used in the journal, or their values. - +List the tag names used or declared in the journal, or their values. + ```flags Flags: + --used list tags used + --declared list tags declared + --undeclared list tags used but not declared + --unused list tags declared but not used --values list tag values instead of tag names - --parsed show tags/values in the order they were parsed, + --parsed show them in the order they were parsed (mostly), including duplicates ``` -This command lists the tag names used in the journal, -whether on transactions, postings, or account declarations. +This command lists tag names - all of them by default, +or just the ones which have been used on transactions/postings/accounts, +or declared with `tag` directives, +or used but not declared, +or declared but not used. -With a TAGREGEX argument, only tag names matching this regular expression -(case insensitive, infix matched) are shown. +You can add one TAGREGEX argument, to show only tags whose name is +matched by this case-insensitive, infix-matching regular expression. -With QUERY arguments, only transactions and accounts matching this query are considered. -If the query involves transaction fields (date:, desc:, amt:, ...), -the search is restricted to the matched transactions and their accounts. +After that, you can add query arguments to filter the +transactions, postings, or accounts providing tags. -With the --values flag, the tags' unique non-empty values are listed instead. -With -E/--empty, blank/empty values are also shown. +With `--values`, the tags' unique non-empty values are listed instead. -With --parsed, tags or values are shown in the order they were parsed, with duplicates included. +With `-E`/`--empty`, blank/empty values are also shown. + +With `--parsed`, tags or values are shown in the order they were parsed, with duplicates included. (Except, tags from account declarations are always shown first.) -Tip: remember, -accounts also acquire tags from their parents, -postings also acquire tags from their account and transaction, -transactions also acquire tags from their postings. +Remember that accounts also acquire tags from their parents; +postings also acquire tags from their account and transaction; +and transactions also acquire tags from their postings. diff --git a/hledger/test/commodities.test b/hledger/test/commodities.test new file mode 100644 index 000000000..b1964e334 --- /dev/null +++ b/hledger/test/commodities.test @@ -0,0 +1,73 @@ +# * commodities command +# "This command lists commodity symbols/names - all of them by default, +# or just the ones which have been used in transactions or `P` directives, +# or declared with `commodity` directives, +# or used but not declared, +# or declared but not used." + +# ** 1. all commodities +< +commodity 1. CA +commodity 1. CB +commodity 1. CC +commodity 1. CD +commodity 1. CE + +D 1. DH + +P 2025-01-01 CA 1 CB +P 2025-01-01 PI 1 PJ + +2025-01-01 + a 1 CC @ 1 CD + b 1 TK @ 1 TL + d + +$ hledger -f - commodities +CA +CB +CC +CD +CE +DH +PI +PJ +TK +TL + +# ** 2. used +$ hledger -f - commodities --used +CA +CB +CC +CD +PI +PJ +TK +TL + +# ** 3. declared +$ hledger -f - commodities --declared +CA +CB +CC +CD +CE + +# ** 4. undeclared +$ hledger -f - commodities --undeclared +PI +PJ +TK +TL + +# ** 5. unused +$ hledger -f - commodities --unused +CE + +# ** 6. with a query +$ hledger -f - commodities --used expr:"cur:CA or cur:'P.*'" +CA +PI +PJ + diff --git a/hledger/test/tags.test b/hledger/test/tags.test index d903455ab..fa5587166 100644 --- a/hledger/test/tags.test +++ b/hledger/test/tags.test @@ -1,5 +1,19 @@ # * tags command +# ** 1. show account tags even when there are no transactions (#1857) +< +account a ; t1: + +$ hledger -f- tags +t1 + +# ** 2. show all tags +< +tag t1 +tag t3 +tag t4 +tag t7 + account a ; t1:v1, an account tag account b:bb ; t2:v2, an unused account, depth 2 @@ -9,7 +23,6 @@ account b:bb ; t2:v2, an unused account, depth 2 2000/1/2 ; t5:v4, a reused value (c) 1 ; t6:v6, an undeclared account -# ** 1. show all tags $ hledger -f- tags t1 t2 @@ -17,8 +30,35 @@ t3 t4 t5 t6 +t7 -# ** 2. show all tag values +# ** . used +$ hledger -f- tags --used +t1 +t2 +t3 +t4 +t5 +t6 + +# ** . declared +$ hledger -f- tags --declared +t1 +t3 +t4 +t7 + +# ** . undeclared +$ hledger -f- tags --undeclared +t2 +t5 +t6 + +# ** . unused +$ hledger -f- tags --unused +t7 + +# ** . show (non empty) values $ hledger -f- tags --values v1 v2 @@ -26,14 +66,14 @@ v3 v4 v6 -# ** 3. show tags matching a regex +# ** . show tags matching a regex $ hledger -f- tags '[1-4]' t1 t2 t3 t4 -# ** 4. show tags matching (a regex and) a hledger query. +# ** . show tags matching (a regex and) a hledger query. # If the query is applicable to both transactions and account declarations, # both are searched for tags. $ hledger -f- tags . b c @@ -41,16 +81,10 @@ t2 t5 t6 -# ** 5. If the query involves transaction attributes, -# only accounts used by the matched transactions will contribute tags. +# ** . If the query involves transaction attributes, +# only the matched transactions and accounts they use will contribute tags. $ hledger -f- tags . date:2000/1/1 t1 t3 t4 -# ** 6. show account tags even when there are no transactions (#1857) -< -account a ; t1: - -$ hledger -f- tags -t1