hledger/hledger-lib/Hledger/Data/JournalChecks/Uniqueleafnames.hs
Simon Michael a9779b2377 ref: move journal checking/pretty errors down further, to Hledger.Data
now at Hledger.Data.JournalChecks*, Hledger.Data.Errors
2022-05-21 18:29:13 -10:00

66 lines
2.6 KiB
Haskell
Executable File

{-# LANGUAGE NamedFieldPuns #-}
{-# LANGUAGE OverloadedStrings #-}
module Hledger.Data.JournalChecks.Uniqueleafnames (
journalCheckUniqueleafnames
)
where
import Data.Function (on)
import Data.List (groupBy, sortBy)
import Data.Text (Text)
import qualified Data.Text as T
import Text.Printf (printf)
import Data.Maybe (fromMaybe)
import Hledger.Data.AccountName (accountLeafName)
import Hledger.Data.Errors (makePostingErrorExcerpt)
import Hledger.Data.Journal (journalPostings, journalAccountNamesUsed)
import Hledger.Data.Posting (isVirtual)
import Hledger.Data.Types
-- | Check that all the journal's postings are to accounts with a unique leaf name.
-- Otherwise, return an error message for the first offending posting.
journalCheckUniqueleafnames :: Journal -> Either String ()
journalCheckUniqueleafnames j = do
-- find all duplicate leafnames, and the full account names they appear in
case finddupes $ journalLeafAndFullAccountNames j of
[] -> Right ()
dupes ->
-- report the first posting that references one of them (and its position), for now
mapM_ (checkposting dupes) $ journalPostings j
finddupes :: (Ord leaf, Eq full) => [(leaf, full)] -> [(leaf, [full])]
finddupes leafandfullnames = zip dupLeafs dupAccountNames
where dupLeafs = map (fst . head) d
dupAccountNames = map (map snd) d
d = dupes' leafandfullnames
dupes' = filter ((> 1) . length)
. groupBy ((==) `on` fst)
. sortBy (compare `on` fst)
journalLeafAndFullAccountNames :: Journal -> [(Text, AccountName)]
journalLeafAndFullAccountNames = map leafAndAccountName . journalAccountNamesUsed
where leafAndAccountName a = (accountLeafName a, a)
checkposting :: [(Text,[AccountName])] -> Posting -> Either String ()
checkposting leafandfullnames p@Posting{paccount=a} =
case [lf | lf@(_,fs) <- leafandfullnames, a `elem` fs] of
[] -> Right ()
(leaf,fulls):_ -> Left $ printf
"%s:%d:%d-%d:\n%saccount leaf name \"%s\" is not unique\nit is used in account names: %s"
f l col col2 ex leaf accts
where
-- t = fromMaybe nulltransaction ptransaction -- XXX sloppy
col = maybe 0 fst mcols
col2 = maybe 0 (fromMaybe 0 . snd) mcols
(f,l,mcols,ex) = makePostingErrorExcerpt p finderrcols
where
finderrcols p _ _ = Just (col, Just col2)
where
alen = T.length $ paccount p
llen = T.length $ accountLeafName a
col = 5 + (if isVirtual p then 1 else 0) + alen - llen
col2 = col + llen - 1
accts = T.intercalate ", " $ map (("\""<>).(<>"\"")) fulls