imp: commodities/payees/tags: used/declared flags, like accounts

And general cleanup of options and help across
the accounts, commodities, payees, tags commands.
This commit is contained in:
Simon Michael 2025-06-14 08:14:00 -10:00
parent 38aadddf7b
commit 4a5775da71
18 changed files with 417 additions and 164 deletions

View File

@ -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} =

View File

@ -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

View File

@ -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)

View File

@ -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

View File

@ -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 : ")

View File

@ -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

View File

@ -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

View File

@ -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`.

View File

@ -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

View File

@ -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.

View File

@ -1,6 +1,6 @@
## descriptions
List the unique descriptions that appear in transactions.
List the unique descriptions used in transactions.
```flags
Flags:

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -1,32 +1,39 @@
## tags
List the tags used in the journal, or their values.
<!-- same section name as Journal > Tags; if reordering this and that, update all #tags[-1] links -->
List the tag names used or declared in the journal, or their values.
<!-- This section has the same name as Journal > Tags;
if reordering this and that, update all #tags[-1] links -->
```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.

View File

@ -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

View File

@ -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