{-|
Helpers for making error messages.
-}

{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE RecordWildCards #-}

module Hledger.Data.Errors (
  makeAccountTagErrorExcerpt,
  makeTransactionErrorExcerpt,
  makePostingErrorExcerpt,
  makePostingAccountErrorExcerpt,
  makeBalanceAssertionErrorExcerpt,
  transactionFindPostingIndex,
)
where

import Data.Function ((&))
import Data.List (find)
import Data.Text (Text)
import qualified Data.Text as T

import Hledger.Data.Transaction (showTransaction)
import Hledger.Data.Posting (postingStripPrices)
import Hledger.Data.Types
import Hledger.Utils
import Data.Maybe
import Safe (headMay)
import Hledger.Data.Posting (isVirtual)

-- | Given an account name and its account directive, and a problem tag within the latter:
-- render it as a megaparsec-style excerpt, showing the original line number and
-- marked column or region.
-- Returns the file path, line number, column(s) if known,
-- and the rendered excerpt, or as much of these as is possible.
-- The returned columns will be accurate for the rendered error message but not for the original journal data.
makeAccountTagErrorExcerpt :: (AccountName, AccountDeclarationInfo) -> TagName -> (FilePath, Int, Maybe (Int, Maybe Int), Text)
makeAccountTagErrorExcerpt :: (AccountName, AccountDeclarationInfo)
-> AccountName
-> (FilePath, Int, Maybe (Int, Maybe Int), AccountName)
makeAccountTagErrorExcerpt (AccountName
a, AccountDeclarationInfo
adi) AccountName
_t = (FilePath
f, Int
l, Maybe (Int, Maybe Int)
forall a. Maybe a
merrcols, AccountName
ex)
  -- XXX findtxnerrorcolumns is awkward, I don't think this is the final form
  where
    (SourcePos FilePath
f Pos
pos Pos
_) = AccountDeclarationInfo -> SourcePos
adisourcepos AccountDeclarationInfo
adi
    l :: Int
l = Pos -> Int
unPos Pos
pos
    txt :: AccountName
txt   = (AccountName, AccountDeclarationInfo) -> AccountName
showAccountDirective (AccountName
a, AccountDeclarationInfo
adi) AccountName -> (AccountName -> AccountName) -> AccountName
forall a b. a -> (a -> b) -> b
& AccountName -> AccountName
textChomp AccountName -> (AccountName -> AccountName) -> AccountName
forall a b. a -> (a -> b) -> b
& (AccountName -> AccountName -> AccountName
forall a. Semigroup a => a -> a -> a
<>AccountName
"\n")
    ex :: AccountName
ex = Int -> Maybe (Int, Maybe Int) -> AccountName -> AccountName
decorateTagErrorExcerpt Int
l Maybe (Int, Maybe Int)
forall a. Maybe a
merrcols AccountName
txt
    -- Calculate columns which will help highlight the region in the excerpt
    -- (but won't exactly match the real data, so won't be shown in the main error line)
    merrcols :: Maybe a
merrcols = Maybe a
forall a. Maybe a
Nothing
      -- don't bother for now
      -- Just (col, Just col2)
      -- where
      --   col  = undefined -- T.length (showTransactionLineFirstPart t') + 2
      --   col2 = undefined -- col + T.length tagname - 1      

showAccountDirective :: (AccountName, AccountDeclarationInfo) -> AccountName
showAccountDirective (AccountName
a, AccountDeclarationInfo{Int
[Tag]
AccountName
SourcePos
adideclarationorder :: AccountDeclarationInfo -> Int
aditags :: AccountDeclarationInfo -> [Tag]
adicomment :: AccountDeclarationInfo -> AccountName
adisourcepos :: SourcePos
adideclarationorder :: Int
aditags :: [Tag]
adicomment :: AccountName
adisourcepos :: AccountDeclarationInfo -> SourcePos
..}) =
  AccountName
"account " AccountName -> AccountName -> AccountName
forall a. Semigroup a => a -> a -> a
<> AccountName
a
  AccountName -> AccountName -> AccountName
forall a. Semigroup a => a -> a -> a
<> (if Bool -> Bool
not (Bool -> Bool) -> Bool -> Bool
forall a b. (a -> b) -> a -> b
$ AccountName -> Bool
T.null AccountName
adicomment then AccountName
"    ; " AccountName -> AccountName -> AccountName
forall a. Semigroup a => a -> a -> a
<> AccountName
adicomment else AccountName
"")

-- | Add megaparsec-style left margin, line number, and optional column marker(s).
decorateTagErrorExcerpt :: Int -> Maybe (Int, Maybe Int) -> Text -> Text
decorateTagErrorExcerpt :: Int -> Maybe (Int, Maybe Int) -> AccountName -> AccountName
decorateTagErrorExcerpt Int
l Maybe (Int, Maybe Int)
mcols AccountName
txt =
  [AccountName] -> AccountName
T.unlines ([AccountName] -> AccountName) -> [AccountName] -> AccountName
forall a b. (a -> b) -> a -> b
$ [AccountName]
ls' [AccountName] -> [AccountName] -> [AccountName]
forall a. Semigroup a => a -> a -> a
<> [AccountName]
colmarkerline [AccountName] -> [AccountName] -> [AccountName]
forall a. Semigroup a => a -> a -> a
<> (AccountName -> AccountName) -> [AccountName] -> [AccountName]
forall a b. (a -> b) -> [a] -> [b]
map (AccountName
lineprefixAccountName -> AccountName -> AccountName
forall a. Semigroup a => a -> a -> a
<>) [AccountName]
ms
  where
    ([AccountName]
ls,[AccountName]
ms) = Int -> [AccountName] -> ([AccountName], [AccountName])
forall a. Int -> [a] -> ([a], [a])
splitAt Int
1 ([AccountName] -> ([AccountName], [AccountName]))
-> [AccountName] -> ([AccountName], [AccountName])
forall a b. (a -> b) -> a -> b
$ AccountName -> [AccountName]
T.lines AccountName
txt
    ls' :: [AccountName]
ls' = (AccountName -> AccountName) -> [AccountName] -> [AccountName]
forall a b. (a -> b) -> [a] -> [b]
map ((FilePath -> AccountName
T.pack (Int -> FilePath
forall a. Show a => a -> FilePath
show Int
l) AccountName -> AccountName -> AccountName
forall a. Semigroup a => a -> a -> a
<> AccountName
" | ") AccountName -> AccountName -> AccountName
forall a. Semigroup a => a -> a -> a
<>) [AccountName]
ls
    colmarkerline :: [AccountName]
colmarkerline =
      [AccountName
lineprefix AccountName -> AccountName -> AccountName
forall a. Semigroup a => a -> a -> a
<> Int -> AccountName -> AccountName
T.replicate (Int
colInt -> Int -> Int
forall a. Num a => a -> a -> a
-Int
1) AccountName
" " AccountName -> AccountName -> AccountName
forall a. Semigroup a => a -> a -> a
<> Int -> AccountName -> AccountName
T.replicate Int
regionw AccountName
"^"
      | Just (Int
col, Maybe Int
mendcol) <- [Maybe (Int, Maybe Int)
mcols]
      , let regionw :: Int
regionw = Int -> (Int -> Int) -> Maybe Int -> Int
forall b a. b -> (a -> b) -> Maybe a -> b
maybe Int
1 (Int -> Int -> Int
forall a. Num a => a -> a -> a
subtract Int
col) Maybe Int
mendcol Int -> Int -> Int
forall a. Num a => a -> a -> a
+ Int
1
      ]
    lineprefix :: AccountName
lineprefix = Int -> AccountName -> AccountName
T.replicate Int
marginw AccountName
" " AccountName -> AccountName -> AccountName
forall a. Semigroup a => a -> a -> a
<> AccountName
"| "
      where  marginw :: Int
marginw = FilePath -> Int
forall (t :: * -> *) a. Foldable t => t a -> Int
length (Int -> FilePath
forall a. Show a => a -> FilePath
show Int
l) Int -> Int -> Int
forall a. Num a => a -> a -> a
+ Int
1

_showAccountDirective :: a
_showAccountDirective = a
forall a. HasCallStack => a
undefined

-- | Given a problem transaction and a function calculating the best
-- column(s) for marking the error region:
-- render it as a megaparsec-style excerpt, showing the original line number
-- on the transaction line, and a column(s) marker.
-- Returns the file path, line number, column(s) if known,
-- and the rendered excerpt, or as much of these as is possible.
-- The returned columns will be accurate for the rendered error message but not for the original journal data.
makeTransactionErrorExcerpt :: Transaction -> (Transaction -> Maybe (Int, Maybe Int)) -> (FilePath, Int, Maybe (Int, Maybe Int), Text)
makeTransactionErrorExcerpt :: Transaction
-> (Transaction -> Maybe (Int, Maybe Int))
-> (FilePath, Int, Maybe (Int, Maybe Int), AccountName)
makeTransactionErrorExcerpt Transaction
t Transaction -> Maybe (Int, Maybe Int)
findtxnerrorcolumns = (FilePath
f, Int
tl, Maybe (Int, Maybe Int)
merrcols, AccountName
ex)
  -- XXX findtxnerrorcolumns is awkward, I don't think this is the final form
  where
    (SourcePos FilePath
f Pos
tpos Pos
_) = (SourcePos, SourcePos) -> SourcePos
forall a b. (a, b) -> a
fst ((SourcePos, SourcePos) -> SourcePos)
-> (SourcePos, SourcePos) -> SourcePos
forall a b. (a -> b) -> a -> b
$ Transaction -> (SourcePos, SourcePos)
tsourcepos Transaction
t
    tl :: Int
tl = Pos -> Int
unPos Pos
tpos
    txntxt :: AccountName
txntxt = Transaction -> AccountName
showTransaction Transaction
t AccountName -> (AccountName -> AccountName) -> AccountName
forall a b. a -> (a -> b) -> b
& AccountName -> AccountName
textChomp AccountName -> (AccountName -> AccountName) -> AccountName
forall a b. a -> (a -> b) -> b
& (AccountName -> AccountName -> AccountName
forall a. Semigroup a => a -> a -> a
<>AccountName
"\n")
    merrcols :: Maybe (Int, Maybe Int)
merrcols = Transaction -> Maybe (Int, Maybe Int)
findtxnerrorcolumns Transaction
t
    ex :: AccountName
ex = Int -> Maybe (Int, Maybe Int) -> AccountName -> AccountName
decorateTransactionErrorExcerpt Int
tl Maybe (Int, Maybe Int)
merrcols AccountName
txntxt

-- | Add megaparsec-style left margin, line number, and optional column marker(s).
decorateTransactionErrorExcerpt :: Int -> Maybe (Int, Maybe Int) -> Text -> Text
decorateTransactionErrorExcerpt :: Int -> Maybe (Int, Maybe Int) -> AccountName -> AccountName
decorateTransactionErrorExcerpt Int
l Maybe (Int, Maybe Int)
mcols AccountName
txt =
  [AccountName] -> AccountName
T.unlines ([AccountName] -> AccountName) -> [AccountName] -> AccountName
forall a b. (a -> b) -> a -> b
$ [AccountName]
ls' [AccountName] -> [AccountName] -> [AccountName]
forall a. Semigroup a => a -> a -> a
<> [AccountName]
colmarkerline [AccountName] -> [AccountName] -> [AccountName]
forall a. Semigroup a => a -> a -> a
<> (AccountName -> AccountName) -> [AccountName] -> [AccountName]
forall a b. (a -> b) -> [a] -> [b]
map (AccountName
lineprefixAccountName -> AccountName -> AccountName
forall a. Semigroup a => a -> a -> a
<>) [AccountName]
ms
  where
    ([AccountName]
ls,[AccountName]
ms) = Int -> [AccountName] -> ([AccountName], [AccountName])
forall a. Int -> [a] -> ([a], [a])
splitAt Int
1 ([AccountName] -> ([AccountName], [AccountName]))
-> [AccountName] -> ([AccountName], [AccountName])
forall a b. (a -> b) -> a -> b
$ AccountName -> [AccountName]
T.lines AccountName
txt
    ls' :: [AccountName]
ls' = (AccountName -> AccountName) -> [AccountName] -> [AccountName]
forall a b. (a -> b) -> [a] -> [b]
map ((FilePath -> AccountName
T.pack (Int -> FilePath
forall a. Show a => a -> FilePath
show Int
l) AccountName -> AccountName -> AccountName
forall a. Semigroup a => a -> a -> a
<> AccountName
" | ") AccountName -> AccountName -> AccountName
forall a. Semigroup a => a -> a -> a
<>) [AccountName]
ls
    colmarkerline :: [AccountName]
colmarkerline =
      [AccountName
lineprefix AccountName -> AccountName -> AccountName
forall a. Semigroup a => a -> a -> a
<> Int -> AccountName -> AccountName
T.replicate (Int
colInt -> Int -> Int
forall a. Num a => a -> a -> a
-Int
1) AccountName
" " AccountName -> AccountName -> AccountName
forall a. Semigroup a => a -> a -> a
<> Int -> AccountName -> AccountName
T.replicate Int
regionw AccountName
"^"
      | Just (Int
col, Maybe Int
mendcol) <- [Maybe (Int, Maybe Int)
mcols]
      , let regionw :: Int
regionw = Int -> (Int -> Int) -> Maybe Int -> Int
forall b a. b -> (a -> b) -> Maybe a -> b
maybe Int
1 (Int -> Int -> Int
forall a. Num a => a -> a -> a
subtract Int
col) Maybe Int
mendcol Int -> Int -> Int
forall a. Num a => a -> a -> a
+ Int
1
      ]
    lineprefix :: AccountName
lineprefix = Int -> AccountName -> AccountName
T.replicate Int
marginw AccountName
" " AccountName -> AccountName -> AccountName
forall a. Semigroup a => a -> a -> a
<> AccountName
"| "
      where  marginw :: Int
marginw = FilePath -> Int
forall (t :: * -> *) a. Foldable t => t a -> Int
length (Int -> FilePath
forall a. Show a => a -> FilePath
show Int
l) Int -> Int -> Int
forall a. Num a => a -> a -> a
+ Int
1

-- | Given a problem posting and a function calculating the best
-- column(s) for marking the error region:
-- look up error info from the parent transaction, and render the transaction
-- as a megaparsec-style excerpt, showing the original line number
-- on the problem posting's line, and a column indicator.
-- Returns the file path, line number, column(s) if known,
-- and the rendered excerpt, or as much of these as is possible.
-- A limitation: columns will be accurate for the rendered error message but not for the original journal data.
makePostingErrorExcerpt :: Posting -> (Posting -> Transaction -> Text -> Maybe (Int, Maybe Int)) -> (FilePath, Int, Maybe (Int, Maybe Int), Text)
makePostingErrorExcerpt :: Posting
-> (Posting
    -> Transaction -> AccountName -> Maybe (Int, Maybe Int))
-> (FilePath, Int, Maybe (Int, Maybe Int), AccountName)
makePostingErrorExcerpt Posting
p Posting -> Transaction -> AccountName -> Maybe (Int, Maybe Int)
findpostingerrorcolumns =
  case Posting -> Maybe Transaction
ptransaction Posting
p of
    Maybe Transaction
Nothing -> (FilePath
"-", Int
0, Maybe (Int, Maybe Int)
forall a. Maybe a
Nothing, AccountName
"")
    Just Transaction
t  -> (FilePath
f, Int
errabsline, Maybe (Int, Maybe Int)
merrcols, AccountName
ex)
      where
        (SourcePos FilePath
f Pos
tl Pos
_) = (SourcePos, SourcePos) -> SourcePos
forall a b. (a, b) -> a
fst ((SourcePos, SourcePos) -> SourcePos)
-> (SourcePos, SourcePos) -> SourcePos
forall a b. (a -> b) -> a -> b
$ Transaction -> (SourcePos, SourcePos)
tsourcepos Transaction
t
        -- p had cost removed in balanceTransactionAndCheckAssertionsB,
        -- must remove them from t's postings too (#2083)
        mpindex :: Maybe Int
mpindex = (Posting -> Bool) -> Transaction -> Maybe Int
transactionFindPostingIndex ((Posting -> Posting -> Bool
forall a. Eq a => a -> a -> Bool
==Posting
p)(Posting -> Bool) -> (Posting -> Posting) -> Posting -> Bool
forall b c a. (b -> c) -> (a -> b) -> a -> c
.Posting -> Posting
postingStripPrices) Transaction
t
        errrelline :: Int
errrelline = case Maybe Int
mpindex of
          Maybe Int
Nothing -> Int
0
          Just Int
pindex ->
            AccountName -> Int
commentExtraLines (Transaction -> AccountName
tcomment Transaction
t) Int -> Int -> Int
forall a. Num a => a -> a -> a
+ 
            [Int] -> Int
forall (t :: * -> *) a. (Foldable t, Num a) => t a -> a
sum ((Posting -> Int) -> [Posting] -> [Int]
forall a b. (a -> b) -> [a] -> [b]
map Posting -> Int
postingLines ([Posting] -> [Int]) -> [Posting] -> [Int]
forall a b. (a -> b) -> a -> b
$ Int -> [Posting] -> [Posting]
forall a. Int -> [a] -> [a]
take Int
pindex ([Posting] -> [Posting]) -> [Posting] -> [Posting]
forall a b. (a -> b) -> a -> b
$ Transaction -> [Posting]
tpostings Transaction
t)
            where
              -- How many lines are used to render this posting ?
              postingLines :: Posting -> Int
postingLines Posting
p' = Int
1 Int -> Int -> Int
forall a. Num a => a -> a -> a
+ AccountName -> Int
commentExtraLines (Posting -> AccountName
pcomment Posting
p')
              -- How many extra lines does this comment add to a transaction or posting rendering ?
              commentExtraLines :: AccountName -> Int
commentExtraLines AccountName
c = Int -> Int -> Int
forall a. Ord a => a -> a -> a
max Int
0 ([AccountName] -> Int
forall (t :: * -> *) a. Foldable t => t a -> Int
length (AccountName -> [AccountName]
T.lines AccountName
c) Int -> Int -> Int
forall a. Num a => a -> a -> a
- Int
1)
        errabsline :: Int
errabsline = Pos -> Int
unPos Pos
tl Int -> Int -> Int
forall a. Num a => a -> a -> a
+ Int
errrelline
        txntxt :: AccountName
txntxt = Transaction -> AccountName
showTransaction Transaction
t AccountName -> (AccountName -> AccountName) -> AccountName
forall a b. a -> (a -> b) -> b
& AccountName -> AccountName
textChomp AccountName -> (AccountName -> AccountName) -> AccountName
forall a b. a -> (a -> b) -> b
& (AccountName -> AccountName -> AccountName
forall a. Semigroup a => a -> a -> a
<>AccountName
"\n")
        merrcols :: Maybe (Int, Maybe Int)
merrcols = Posting -> Transaction -> AccountName -> Maybe (Int, Maybe Int)
findpostingerrorcolumns Posting
p Transaction
t AccountName
txntxt
        ex :: AccountName
ex = Int -> Int -> Maybe (Int, Maybe Int) -> AccountName -> AccountName
decoratePostingErrorExcerpt Int
errabsline Int
errrelline Maybe (Int, Maybe Int)
merrcols AccountName
txntxt

-- | Add megaparsec-style left margin, line number, and optional column marker(s).
decoratePostingErrorExcerpt :: Int -> Int -> Maybe (Int, Maybe Int) -> Text -> Text
decoratePostingErrorExcerpt :: Int -> Int -> Maybe (Int, Maybe Int) -> AccountName -> AccountName
decoratePostingErrorExcerpt Int
absline Int
relline Maybe (Int, Maybe Int)
mcols AccountName
txt =
  [AccountName] -> AccountName
T.unlines ([AccountName] -> AccountName) -> [AccountName] -> AccountName
forall a b. (a -> b) -> a -> b
$ [AccountName]
js' [AccountName] -> [AccountName] -> [AccountName]
forall a. Semigroup a => a -> a -> a
<> [AccountName]
ks' [AccountName] -> [AccountName] -> [AccountName]
forall a. Semigroup a => a -> a -> a
<> [AccountName]
colmarkerline [AccountName] -> [AccountName] -> [AccountName]
forall a. Semigroup a => a -> a -> a
<> [AccountName]
ms'
  where
    ([AccountName]
ls,[AccountName]
ms) = Int -> [AccountName] -> ([AccountName], [AccountName])
forall a. Int -> [a] -> ([a], [a])
splitAt (Int
rellineInt -> Int -> Int
forall a. Num a => a -> a -> a
+Int
1) ([AccountName] -> ([AccountName], [AccountName]))
-> [AccountName] -> ([AccountName], [AccountName])
forall a b. (a -> b) -> a -> b
$ AccountName -> [AccountName]
T.lines AccountName
txt
    ([AccountName]
js,[AccountName]
ks) = Int -> [AccountName] -> ([AccountName], [AccountName])
forall a. Int -> [a] -> ([a], [a])
splitAt ([AccountName] -> Int
forall (t :: * -> *) a. Foldable t => t a -> Int
length [AccountName]
ls Int -> Int -> Int
forall a. Num a => a -> a -> a
- Int
1) [AccountName]
ls
    ([AccountName]
js',[AccountName]
ks') = case [AccountName]
ks of
      [AccountName
k] -> ((AccountName -> AccountName) -> [AccountName] -> [AccountName]
forall a b. (a -> b) -> [a] -> [b]
map (AccountName
lineprefixAccountName -> AccountName -> AccountName
forall a. Semigroup a => a -> a -> a
<>) [AccountName]
js, [FilePath -> AccountName
T.pack (Int -> FilePath
forall a. Show a => a -> FilePath
show Int
absline) AccountName -> AccountName -> AccountName
forall a. Semigroup a => a -> a -> a
<> AccountName
" | " AccountName -> AccountName -> AccountName
forall a. Semigroup a => a -> a -> a
<> AccountName
k])
      [AccountName]
_   -> ([], [])
    ms' :: [AccountName]
ms' = (AccountName -> AccountName) -> [AccountName] -> [AccountName]
forall a b. (a -> b) -> [a] -> [b]
map (AccountName
lineprefixAccountName -> AccountName -> AccountName
forall a. Semigroup a => a -> a -> a
<>) [AccountName]
ms
    colmarkerline :: [AccountName]
colmarkerline =
      [AccountName
lineprefix AccountName -> AccountName -> AccountName
forall a. Semigroup a => a -> a -> a
<> Int -> AccountName -> AccountName
T.replicate (Int
colInt -> Int -> Int
forall a. Num a => a -> a -> a
-Int
1) AccountName
" " AccountName -> AccountName -> AccountName
forall a. Semigroup a => a -> a -> a
<> Int -> AccountName -> AccountName
T.replicate Int
regionw AccountName
"^"
      | Just (Int
col, Maybe Int
mendcol) <- [Maybe (Int, Maybe Int)
mcols]
      , let regionw :: Int
regionw = Int
1 Int -> Int -> Int
forall a. Num a => a -> a -> a
+ Int -> (Int -> Int) -> Maybe Int -> Int
forall b a. b -> (a -> b) -> Maybe a -> b
maybe Int
0 (Int -> Int -> Int
forall a. Num a => a -> a -> a
subtract Int
col) Maybe Int
mendcol
      ]
    lineprefix :: AccountName
lineprefix = Int -> AccountName -> AccountName
T.replicate Int
marginw AccountName
" " AccountName -> AccountName -> AccountName
forall a. Semigroup a => a -> a -> a
<> AccountName
"| "
      where  marginw :: Int
marginw = FilePath -> Int
forall (t :: * -> *) a. Foldable t => t a -> Int
length (Int -> FilePath
forall a. Show a => a -> FilePath
show Int
absline) Int -> Int -> Int
forall a. Num a => a -> a -> a
+ Int
1

-- | Find the 1-based index of the first posting in this transaction
-- satisfying the given predicate.
transactionFindPostingIndex :: (Posting -> Bool) -> Transaction -> Maybe Int
transactionFindPostingIndex :: (Posting -> Bool) -> Transaction -> Maybe Int
transactionFindPostingIndex Posting -> Bool
ppredicate = 
  ((Int, Posting) -> Int) -> Maybe (Int, Posting) -> Maybe Int
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
fmap (Int, Posting) -> Int
forall a b. (a, b) -> a
fst (Maybe (Int, Posting) -> Maybe Int)
-> (Transaction -> Maybe (Int, Posting))
-> Transaction
-> Maybe Int
forall b c a. (b -> c) -> (a -> b) -> a -> c
. ((Int, Posting) -> Bool)
-> [(Int, Posting)] -> Maybe (Int, Posting)
forall (t :: * -> *) a. Foldable t => (a -> Bool) -> t a -> Maybe a
find (Posting -> Bool
ppredicate(Posting -> Bool)
-> ((Int, Posting) -> Posting) -> (Int, Posting) -> Bool
forall b c a. (b -> c) -> (a -> b) -> a -> c
.(Int, Posting) -> Posting
forall a b. (a, b) -> b
snd) ([(Int, Posting)] -> Maybe (Int, Posting))
-> (Transaction -> [(Int, Posting)])
-> Transaction
-> Maybe (Int, Posting)
forall b c a. (b -> c) -> (a -> b) -> a -> c
. [Int] -> [Posting] -> [(Int, Posting)]
forall a b. [a] -> [b] -> [(a, b)]
zip [Int
1..] ([Posting] -> [(Int, Posting)])
-> (Transaction -> [Posting]) -> Transaction -> [(Int, Posting)]
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Transaction -> [Posting]
tpostings

-- | From the given posting, make an error excerpt showing the transaction with
-- this posting's account part highlighted.
makePostingAccountErrorExcerpt :: Posting -> (FilePath, Int, Maybe (Int, Maybe Int), Text)
makePostingAccountErrorExcerpt :: Posting -> (FilePath, Int, Maybe (Int, Maybe Int), AccountName)
makePostingAccountErrorExcerpt Posting
p = Posting
-> (Posting
    -> Transaction -> AccountName -> Maybe (Int, Maybe Int))
-> (FilePath, Int, Maybe (Int, Maybe Int), AccountName)
makePostingErrorExcerpt Posting
p Posting -> Transaction -> AccountName -> Maybe (Int, Maybe Int)
forall p p. Posting -> p -> p -> Maybe (Int, Maybe Int)
finderrcols
  where
    -- Calculate columns suitable for highlighting the synthetic excerpt.
    finderrcols :: Posting -> p -> p -> Maybe (Int, Maybe Int)
finderrcols Posting
p' p
_ p
_ = (Int, Maybe Int) -> Maybe (Int, Maybe Int)
forall a. a -> Maybe a
Just (Int
col, Int -> Maybe Int
forall a. a -> Maybe a
Just Int
col2)
      where
        col :: Int
col = Int
5 Int -> Int -> Int
forall a. Num a => a -> a -> a
+ if Posting -> Bool
isVirtual Posting
p' then Int
1 else Int
0
        col2 :: Int
col2 = Int
col Int -> Int -> Int
forall a. Num a => a -> a -> a
+ AccountName -> Int
T.length (Posting -> AccountName
paccount Posting
p') Int -> Int -> Int
forall a. Num a => a -> a -> a
- Int
1

-- | From the given posting, make an error excerpt showing the transaction with
-- the balance assertion highlighted.
makeBalanceAssertionErrorExcerpt :: Posting -> (FilePath, Int, Maybe (Int, Maybe Int), Text)
makeBalanceAssertionErrorExcerpt :: Posting -> (FilePath, Int, Maybe (Int, Maybe Int), AccountName)
makeBalanceAssertionErrorExcerpt Posting
p = Posting
-> (Posting
    -> Transaction -> AccountName -> Maybe (Int, Maybe Int))
-> (FilePath, Int, Maybe (Int, Maybe Int), AccountName)
makePostingErrorExcerpt Posting
p Posting -> Transaction -> AccountName -> Maybe (Int, Maybe Int)
finderrcols
  where
    finderrcols :: Posting -> Transaction -> AccountName -> Maybe (Int, Maybe Int)
finderrcols Posting
p' Transaction
t AccountName
trendered = (Int, Maybe Int) -> Maybe (Int, Maybe Int)
forall a. a -> Maybe a
Just (Int
col, Int -> Maybe Int
forall a. a -> Maybe a
Just Int
col2)
      where
        -- Analyse the rendering to find the columns to highlight.
        tlines :: Int
tlines = FilePath -> Int -> Int
forall a. Show a => FilePath -> a -> a
dbg5 FilePath
"tlines" (Int -> Int) -> Int -> Int
forall a b. (a -> b) -> a -> b
$ Int -> Int -> Int
forall a. Ord a => a -> a -> a
max Int
1 (Int -> Int) -> Int -> Int
forall a b. (a -> b) -> a -> b
$ [AccountName] -> Int
forall (t :: * -> *) a. Foldable t => t a -> Int
length ([AccountName] -> Int) -> [AccountName] -> Int
forall a b. (a -> b) -> a -> b
$ AccountName -> [AccountName]
T.lines (AccountName -> [AccountName]) -> AccountName -> [AccountName]
forall a b. (a -> b) -> a -> b
$ Transaction -> AccountName
tcomment Transaction
t  -- transaction comment can generate extra lines
        (Int
col, Int
col2) =
          let def :: (Int, Int)
def = (Int
5, [Int] -> Int
forall (t :: * -> *) a. (Foldable t, Ord a) => t a -> a
maximum ((AccountName -> Int) -> [AccountName] -> [Int]
forall a b. (a -> b) -> [a] -> [b]
map AccountName -> Int
T.length ([AccountName] -> [Int]) -> [AccountName] -> [Int]
forall a b. (a -> b) -> a -> b
$ AccountName -> [AccountName]
T.lines AccountName
trendered))  -- fallback: underline whole posting. Shouldn't happen.
          in
            case (Posting -> Bool) -> Transaction -> Maybe Int
transactionFindPostingIndex (Posting -> Posting -> Bool
forall a. Eq a => a -> a -> Bool
==Posting
p') Transaction
t of
              Maybe Int
Nothing  -> (Int, Int)
def
              Just Int
idx -> (Int, Int) -> Maybe (Int, Int) -> (Int, Int)
forall a. a -> Maybe a -> a
fromMaybe (Int, Int)
def (Maybe (Int, Int) -> (Int, Int)) -> Maybe (Int, Int) -> (Int, Int)
forall a b. (a -> b) -> a -> b
$ do
                let
                  beforeps :: [Posting]
beforeps = Int -> [Posting] -> [Posting]
forall a. Int -> [a] -> [a]
take (Int
idxInt -> Int -> Int
forall a. Num a => a -> a -> a
-Int
1) ([Posting] -> [Posting]) -> [Posting] -> [Posting]
forall a b. (a -> b) -> a -> b
$ Transaction -> [Posting]
tpostings Transaction
t
                  beforepslines :: Int
beforepslines = FilePath -> Int -> Int
forall a. Show a => FilePath -> a -> a
dbg5 FilePath
"beforepslines" (Int -> Int) -> Int -> Int
forall a b. (a -> b) -> a -> b
$ [Int] -> Int
forall (t :: * -> *) a. (Foldable t, Num a) => t a -> a
sum ([Int] -> Int) -> [Int] -> Int
forall a b. (a -> b) -> a -> b
$ (Posting -> Int) -> [Posting] -> [Int]
forall a b. (a -> b) -> [a] -> [b]
map (Int -> Int -> Int
forall a. Ord a => a -> a -> a
max Int
1 (Int -> Int) -> (Posting -> Int) -> Posting -> Int
forall b c a. (b -> c) -> (a -> b) -> a -> c
. [AccountName] -> Int
forall (t :: * -> *) a. Foldable t => t a -> Int
length ([AccountName] -> Int)
-> (Posting -> [AccountName]) -> Posting -> Int
forall b c a. (b -> c) -> (a -> b) -> a -> c
. AccountName -> [AccountName]
T.lines (AccountName -> [AccountName])
-> (Posting -> AccountName) -> Posting -> [AccountName]
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Posting -> AccountName
pcomment) [Posting]
beforeps   -- posting comment can generate extra lines (assume only one commodity shown)
                AccountName
assertionline <- FilePath -> Maybe AccountName -> Maybe AccountName
forall a. Show a => FilePath -> a -> a
dbg5 FilePath
"assertionline" (Maybe AccountName -> Maybe AccountName)
-> Maybe AccountName -> Maybe AccountName
forall a b. (a -> b) -> a -> b
$ [AccountName] -> Maybe AccountName
forall a. [a] -> Maybe a
headMay ([AccountName] -> Maybe AccountName)
-> [AccountName] -> Maybe AccountName
forall a b. (a -> b) -> a -> b
$ Int -> [AccountName] -> [AccountName]
forall a. Int -> [a] -> [a]
drop (Int
tlines Int -> Int -> Int
forall a. Num a => a -> a -> a
+ Int
beforepslines) ([AccountName] -> [AccountName]) -> [AccountName] -> [AccountName]
forall a b. (a -> b) -> a -> b
$ AccountName -> [AccountName]
T.lines AccountName
trendered
                let
                  col2' :: Int
col2' = AccountName -> Int
T.length AccountName
assertionline
                  l :: FilePath
l = (Char -> Bool) -> FilePath -> FilePath
forall a. (a -> Bool) -> [a] -> [a]
dropWhile (Char -> Char -> Bool
forall a. Eq a => a -> a -> Bool
/= Char
'=') (FilePath -> FilePath) -> FilePath -> FilePath
forall a b. (a -> b) -> a -> b
$ FilePath -> FilePath
forall a. [a] -> [a]
reverse (FilePath -> FilePath) -> FilePath -> FilePath
forall a b. (a -> b) -> a -> b
$ AccountName -> FilePath
T.unpack AccountName
assertionline
                  l' :: FilePath
l' = (Char -> Bool) -> FilePath -> FilePath
forall a. (a -> Bool) -> [a] -> [a]
dropWhile (Char -> FilePath -> Bool
forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> Bool
`elem` [Char
'=',Char
'*']) FilePath
l
                  col' :: Int
col' = FilePath -> Int
forall (t :: * -> *) a. Foldable t => t a -> Int
length FilePath
l' Int -> Int -> Int
forall a. Num a => a -> a -> a
+ Int
1
                (Int, Int) -> Maybe (Int, Int)
forall (m :: * -> *) a. Monad m => a -> m a
return (Int
col', Int
col2')