101 lines
		
	
	
		
			4.2 KiB
		
	
	
	
		
			Haskell
		
	
	
	
	
	
			
		
		
	
	
			101 lines
		
	
	
		
			4.2 KiB
		
	
	
	
		
			Haskell
		
	
	
	
	
	
{-|
 | 
						|
Helpers for making error messages.
 | 
						|
-}
 | 
						|
 | 
						|
{-# LANGUAGE OverloadedStrings #-}
 | 
						|
 | 
						|
module Hledger.Data.Errors (
 | 
						|
  makeTransactionErrorExcerpt,
 | 
						|
  makePostingErrorExcerpt,
 | 
						|
  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.Types
 | 
						|
import Hledger.Utils
 | 
						|
 | 
						|
-- | 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.
 | 
						|
makeTransactionErrorExcerpt :: Transaction -> (Transaction -> Maybe (Int, Maybe Int)) -> (FilePath, Int, Maybe (Int, Maybe Int), Text)
 | 
						|
makeTransactionErrorExcerpt t findtxnerrorcolumns = (f, tl, merrcols, ex)
 | 
						|
  -- XXX findtxnerrorcolumns is awkward, I don't think this is the final form
 | 
						|
  where
 | 
						|
    (SourcePos f tpos _) = fst $ tsourcepos t
 | 
						|
    tl = unPos tpos
 | 
						|
    txntxt = showTransaction t & textChomp & (<>"\n")
 | 
						|
    merrcols = findtxnerrorcolumns t
 | 
						|
    ex = decorateTransactionErrorExcerpt tl merrcols txntxt
 | 
						|
 | 
						|
-- | Add megaparsec-style left margin, line number, and optional column marker(s).
 | 
						|
decorateTransactionErrorExcerpt :: Int -> Maybe (Int, Maybe Int) -> Text -> Text
 | 
						|
decorateTransactionErrorExcerpt l mcols txt =
 | 
						|
  T.unlines $ ls' <> colmarkerline <> map (lineprefix<>) ms
 | 
						|
  where
 | 
						|
    (ls,ms) = splitAt 1 $ T.lines txt
 | 
						|
    ls' = map ((T.pack (show l) <> " | ") <>) ls
 | 
						|
    colmarkerline =
 | 
						|
      [lineprefix <> T.replicate (col-1) " " <> T.replicate regionw "^"
 | 
						|
      | Just (col, mendcol) <- [mcols]
 | 
						|
      , let regionw = maybe 1 (subtract col) mendcol + 1
 | 
						|
      ]
 | 
						|
    lineprefix = T.replicate marginw " " <> "| "
 | 
						|
      where  marginw = length (show l) + 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.
 | 
						|
makePostingErrorExcerpt :: Posting -> (Posting -> Transaction -> Text -> Maybe (Int, Maybe Int)) -> (FilePath, Int, Maybe (Int, Maybe Int), Text)
 | 
						|
makePostingErrorExcerpt p findpostingerrorcolumns =
 | 
						|
  case ptransaction p of
 | 
						|
    Nothing -> ("-", 0, Nothing, "")
 | 
						|
    Just t  -> (f, errabsline, merrcols, ex)
 | 
						|
      where
 | 
						|
        (SourcePos f tl _) = fst $ tsourcepos t
 | 
						|
        tcommentlines = max 0 (length (T.lines $ tcomment t) - 1)
 | 
						|
        mpindex = transactionFindPostingIndex (==p) t
 | 
						|
        errrelline = maybe 0 (tcommentlines+) mpindex   -- XXX doesn't count posting coment lines
 | 
						|
        errabsline = unPos tl + errrelline
 | 
						|
        txntxt = showTransaction t & textChomp & (<>"\n")
 | 
						|
        merrcols = findpostingerrorcolumns p t txntxt
 | 
						|
        ex = decoratePostingErrorExcerpt errabsline errrelline merrcols txntxt
 | 
						|
 | 
						|
-- | Add megaparsec-style left margin, line number, and optional column marker(s).
 | 
						|
decoratePostingErrorExcerpt :: Int -> Int -> Maybe (Int, Maybe Int) -> Text -> Text
 | 
						|
decoratePostingErrorExcerpt absline relline mcols txt =
 | 
						|
  T.unlines $ js' <> ks' <> colmarkerline <> ms'
 | 
						|
  where
 | 
						|
    (ls,ms) = splitAt (relline+1) $ T.lines txt
 | 
						|
    (js,ks) = splitAt (length ls - 1) ls
 | 
						|
    (js',ks') = case ks of
 | 
						|
      [k] -> (map (lineprefix<>) js, [T.pack (show absline) <> " | " <> k])
 | 
						|
      _   -> ([], [])
 | 
						|
    ms' = map (lineprefix<>) ms
 | 
						|
    colmarkerline =
 | 
						|
      [lineprefix <> T.replicate (col-1) " " <> T.replicate regionw "^"
 | 
						|
      | Just (col, mendcol) <- [mcols]
 | 
						|
      , let regionw = 1 + maybe 0 (subtract col) mendcol
 | 
						|
      ]
 | 
						|
    lineprefix = T.replicate marginw " " <> "| "
 | 
						|
      where  marginw = length (show absline) + 1
 | 
						|
 | 
						|
-- | Find the 1-based index of the first posting in this transaction
 | 
						|
-- satisfying the given predicate.
 | 
						|
transactionFindPostingIndex :: (Posting -> Bool) -> Transaction -> Maybe Int
 | 
						|
transactionFindPostingIndex ppredicate = 
 | 
						|
  fmap fst . find (ppredicate.snd) . zip [1..] . tpostings
 | 
						|
 |