lib,cli: Make showTransaction return Text rather than String.
This commit is contained in:
parent
dbe7015502
commit
74b296f865
@ -895,7 +895,7 @@ checkBalanceAssertionOneCommodityB p@Posting{paccount=assertedacct} assertedamt
|
|||||||
Nothing -> "?" -- shouldn't happen
|
Nothing -> "?" -- shouldn't happen
|
||||||
Just t -> printf "%s\ntransaction:\n%s"
|
Just t -> printf "%s\ntransaction:\n%s"
|
||||||
(showGenericSourcePos pos)
|
(showGenericSourcePos pos)
|
||||||
(chomp $ showTransaction t)
|
(textChomp $ showTransaction t)
|
||||||
:: String
|
:: String
|
||||||
where
|
where
|
||||||
pos = baposition $ fromJust $ pbalanceassertion p
|
pos = baposition $ fromJust $ pbalanceassertion p
|
||||||
@ -926,11 +926,11 @@ checkIllegalBalanceAssignmentB p = do
|
|||||||
checkBalanceAssignmentPostingDateB :: Posting -> Balancing s ()
|
checkBalanceAssignmentPostingDateB :: Posting -> Balancing s ()
|
||||||
checkBalanceAssignmentPostingDateB p =
|
checkBalanceAssignmentPostingDateB p =
|
||||||
when (hasBalanceAssignment p && isJust (pdate p)) $
|
when (hasBalanceAssignment p && isJust (pdate p)) $
|
||||||
throwError $ unlines $
|
throwError . T.unpack $ T.unlines
|
||||||
["postings which are balance assignments may not have a custom date."
|
["postings which are balance assignments may not have a custom date."
|
||||||
,"Please write the posting amount explicitly, or remove the posting date:"
|
,"Please write the posting amount explicitly, or remove the posting date:"
|
||||||
,""
|
,""
|
||||||
,maybe (unlines $ showPostingLines p) showTransaction $ ptransaction p
|
,maybe (T.unlines $ showPostingLines p) showTransaction $ ptransaction p
|
||||||
]
|
]
|
||||||
|
|
||||||
-- | Throw an error if this posting is trying to do a balance assignment and
|
-- | Throw an error if this posting is trying to do a balance assignment and
|
||||||
@ -940,16 +940,16 @@ checkBalanceAssignmentUnassignableAccountB :: Posting -> Balancing s ()
|
|||||||
checkBalanceAssignmentUnassignableAccountB p = do
|
checkBalanceAssignmentUnassignableAccountB p = do
|
||||||
unassignable <- R.asks bsUnassignable
|
unassignable <- R.asks bsUnassignable
|
||||||
when (hasBalanceAssignment p && paccount p `S.member` unassignable) $
|
when (hasBalanceAssignment p && paccount p `S.member` unassignable) $
|
||||||
throwError $ unlines $
|
throwError . T.unpack $ T.unlines
|
||||||
["balance assignments cannot be used with accounts which are"
|
["balance assignments cannot be used with accounts which are"
|
||||||
,"posted to by transaction modifier rules (auto postings)."
|
,"posted to by transaction modifier rules (auto postings)."
|
||||||
,"Please write the posting amount explicitly, or remove the rule."
|
,"Please write the posting amount explicitly, or remove the rule."
|
||||||
,""
|
,""
|
||||||
,"account: "++T.unpack (paccount p)
|
,"account: " <> paccount p
|
||||||
,""
|
,""
|
||||||
,"transaction:"
|
,"transaction:"
|
||||||
,""
|
,""
|
||||||
,maybe (unlines $ showPostingLines p) showTransaction $ ptransaction p
|
,maybe (T.unlines $ showPostingLines p) showTransaction $ ptransaction p
|
||||||
]
|
]
|
||||||
|
|
||||||
--
|
--
|
||||||
|
|||||||
@ -16,6 +16,7 @@ where
|
|||||||
import Data.Semigroup ((<>))
|
import Data.Semigroup ((<>))
|
||||||
#endif
|
#endif
|
||||||
import qualified Data.Text as T
|
import qualified Data.Text as T
|
||||||
|
import qualified Data.Text.IO as T
|
||||||
import Text.Printf
|
import Text.Printf
|
||||||
|
|
||||||
import Hledger.Data.Types
|
import Hledger.Data.Types
|
||||||
@ -40,7 +41,7 @@ _ptgen str = do
|
|||||||
case checkPeriodicTransactionStartDate i s t of
|
case checkPeriodicTransactionStartDate i s t of
|
||||||
Just e -> error' e -- PARTIAL:
|
Just e -> error' e -- PARTIAL:
|
||||||
Nothing ->
|
Nothing ->
|
||||||
mapM_ (putStr . showTransaction) $
|
mapM_ (T.putStr . showTransaction) $
|
||||||
runPeriodicTransaction
|
runPeriodicTransaction
|
||||||
nullperiodictransaction{ ptperiodexpr=t , ptspan=s, ptinterval=i, ptpostings=["a" `post` usd 1] }
|
nullperiodictransaction{ ptperiodexpr=t , ptspan=s, ptinterval=i, ptpostings=["a" `post` usd 1] }
|
||||||
nulldatespan
|
nulldatespan
|
||||||
@ -52,7 +53,7 @@ _ptgenspan str span = do
|
|||||||
case checkPeriodicTransactionStartDate i s t of
|
case checkPeriodicTransactionStartDate i s t of
|
||||||
Just e -> error' e -- PARTIAL:
|
Just e -> error' e -- PARTIAL:
|
||||||
Nothing ->
|
Nothing ->
|
||||||
mapM_ (putStr . showTransaction) $
|
mapM_ (T.putStr . showTransaction) $
|
||||||
runPeriodicTransaction
|
runPeriodicTransaction
|
||||||
nullperiodictransaction{ ptperiodexpr=t , ptspan=s, ptinterval=i, ptpostings=["a" `post` usd 1] }
|
nullperiodictransaction{ ptperiodexpr=t , ptspan=s, ptinterval=i, ptpostings=["a" `post` usd 1] }
|
||||||
span
|
span
|
||||||
|
|||||||
@ -90,8 +90,8 @@ errorWithSourceLine line msg = error $ "line " ++ show line ++ ": " ++ msg
|
|||||||
entryFromTimeclockInOut :: TimeclockEntry -> TimeclockEntry -> Transaction
|
entryFromTimeclockInOut :: TimeclockEntry -> TimeclockEntry -> Transaction
|
||||||
entryFromTimeclockInOut i o
|
entryFromTimeclockInOut i o
|
||||||
| otime >= itime = t
|
| otime >= itime = t
|
||||||
| otherwise =
|
| otherwise = error' . T.unpack $
|
||||||
error' $ "clock-out time less than clock-in time in:\n" ++ showTransaction t -- PARTIAL:
|
"clock-out time less than clock-in time in:\n" <> showTransaction t -- PARTIAL:
|
||||||
where
|
where
|
||||||
t = Transaction {
|
t = Transaction {
|
||||||
tindex = 0,
|
tindex = 0,
|
||||||
|
|||||||
@ -63,7 +63,6 @@ import Data.Maybe
|
|||||||
import Data.Text (Text)
|
import Data.Text (Text)
|
||||||
import qualified Data.Text as T
|
import qualified Data.Text as T
|
||||||
import Data.Time.Calendar
|
import Data.Time.Calendar
|
||||||
import Text.Printf
|
|
||||||
import qualified Data.Map as M
|
import qualified Data.Map as M
|
||||||
|
|
||||||
import Hledger.Utils
|
import Hledger.Utils
|
||||||
@ -148,53 +147,54 @@ To facilitate this, postings with explicit multi-commodity amounts
|
|||||||
are displayed as multiple similar postings, one per commodity.
|
are displayed as multiple similar postings, one per commodity.
|
||||||
(Normally does not happen with this function).
|
(Normally does not happen with this function).
|
||||||
-}
|
-}
|
||||||
showTransaction :: Transaction -> String
|
showTransaction :: Transaction -> Text
|
||||||
showTransaction = showTransactionHelper False
|
showTransaction = showTransactionHelper False
|
||||||
|
|
||||||
-- | Deprecated alias for 'showTransaction'
|
-- | Deprecated alias for 'showTransaction'
|
||||||
showTransactionUnelided :: Transaction -> String
|
showTransactionUnelided :: Transaction -> Text
|
||||||
showTransactionUnelided = showTransaction -- TODO: drop it
|
showTransactionUnelided = showTransaction -- TODO: drop it
|
||||||
|
|
||||||
-- | Like showTransaction, but explicit multi-commodity amounts
|
-- | Like showTransaction, but explicit multi-commodity amounts
|
||||||
-- are shown on one line, comma-separated. In this case the output will
|
-- are shown on one line, comma-separated. In this case the output will
|
||||||
-- not be parseable journal syntax.
|
-- not be parseable journal syntax.
|
||||||
showTransactionOneLineAmounts :: Transaction -> String
|
showTransactionOneLineAmounts :: Transaction -> Text
|
||||||
showTransactionOneLineAmounts = showTransactionHelper True
|
showTransactionOneLineAmounts = showTransactionHelper True
|
||||||
|
|
||||||
-- | Deprecated alias for 'showTransactionOneLineAmounts'
|
-- | Deprecated alias for 'showTransactionOneLineAmounts'
|
||||||
showTransactionUnelidedOneLineAmounts :: Transaction -> String
|
showTransactionUnelidedOneLineAmounts :: Transaction -> Text
|
||||||
showTransactionUnelidedOneLineAmounts = showTransactionOneLineAmounts -- TODO: drop it
|
showTransactionUnelidedOneLineAmounts = showTransactionOneLineAmounts -- TODO: drop it
|
||||||
|
|
||||||
-- | Helper for showTransaction*.
|
-- | Helper for showTransaction*.
|
||||||
showTransactionHelper :: Bool -> Transaction -> String
|
showTransactionHelper :: Bool -> Transaction -> Text
|
||||||
showTransactionHelper onelineamounts t =
|
showTransactionHelper onelineamounts t =
|
||||||
unlines $ [descriptionline]
|
T.unlines $
|
||||||
++ newlinecomments
|
descriptionline
|
||||||
++ (postingsAsLines onelineamounts (tpostings t))
|
: newlinecomments
|
||||||
++ [""]
|
++ (postingsAsLines onelineamounts (tpostings t))
|
||||||
where
|
++ [""]
|
||||||
descriptionline = rstrip $ concat [date, status, code, desc, samelinecomment]
|
where
|
||||||
date = showDate (tdate t) ++ maybe "" (("="++) . showDate) (tdate2 t)
|
descriptionline = T.stripEnd $ T.concat [date, status, code, desc, samelinecomment]
|
||||||
status | tstatus t == Cleared = " *"
|
date = T.pack $ showDate (tdate t) ++ maybe "" (("="++) . showDate) (tdate2 t)
|
||||||
| tstatus t == Pending = " !"
|
status | tstatus t == Cleared = " *"
|
||||||
| otherwise = ""
|
| tstatus t == Pending = " !"
|
||||||
code = if T.length (tcode t) > 0 then printf " (%s)" $ T.unpack $ tcode t else ""
|
| otherwise = ""
|
||||||
desc = if null d then "" else " " ++ d where d = T.unpack $ tdescription t
|
code = if T.null (tcode t) then "" else wrap " (" ")" $ tcode t
|
||||||
(samelinecomment, newlinecomments) =
|
desc = if T.null d then "" else " " <> d where d = tdescription t
|
||||||
case renderCommentLines (tcomment t) of [] -> ("",[])
|
(samelinecomment, newlinecomments) =
|
||||||
c:cs -> (c,cs)
|
case renderCommentLines (tcomment t) of [] -> ("",[])
|
||||||
|
c:cs -> (c,cs)
|
||||||
|
|
||||||
-- | Render a transaction or posting's comment as indented, semicolon-prefixed comment lines.
|
-- | Render a transaction or posting's comment as indented, semicolon-prefixed comment lines.
|
||||||
-- The first line (unless empty) will have leading space, subsequent lines will have a larger indent.
|
-- The first line (unless empty) will have leading space, subsequent lines will have a larger indent.
|
||||||
renderCommentLines :: Text -> [String]
|
renderCommentLines :: Text -> [Text]
|
||||||
renderCommentLines t =
|
renderCommentLines t =
|
||||||
case lines $ T.unpack t of
|
case T.lines t of
|
||||||
[] -> []
|
[] -> []
|
||||||
[l] -> [(commentSpace . comment) l] -- single-line comment
|
[l] -> [commentSpace $ comment l] -- single-line comment
|
||||||
("":ls) -> "" : map (lineIndent . comment) ls -- multi-line comment with empty first line
|
("":ls) -> "" : map (lineIndent . comment) ls -- multi-line comment with empty first line
|
||||||
(l:ls) -> (commentSpace . comment) l : map (lineIndent . comment) ls
|
(l:ls) -> commentSpace (comment l) : map (lineIndent . comment) ls
|
||||||
where
|
where
|
||||||
comment = ("; "++)
|
comment = ("; "<>)
|
||||||
|
|
||||||
-- | Given a transaction and its postings, render the postings, suitable
|
-- | Given a transaction and its postings, render the postings, suitable
|
||||||
-- for `print` output. Normally this output will be valid journal syntax which
|
-- for `print` output. Normally this output will be valid journal syntax which
|
||||||
@ -214,7 +214,7 @@ renderCommentLines t =
|
|||||||
-- Posting amounts will be aligned with each other, starting about 4 columns
|
-- Posting amounts will be aligned with each other, starting about 4 columns
|
||||||
-- beyond the widest account name (see postingAsLines for details).
|
-- beyond the widest account name (see postingAsLines for details).
|
||||||
--
|
--
|
||||||
postingsAsLines :: Bool -> [Posting] -> [String]
|
postingsAsLines :: Bool -> [Posting] -> [Text]
|
||||||
postingsAsLines onelineamounts ps = concatMap (postingAsLines False onelineamounts ps) ps
|
postingsAsLines onelineamounts ps = concatMap (postingAsLines False onelineamounts ps) ps
|
||||||
|
|
||||||
-- | Render one posting, on one or more lines, suitable for `print` output.
|
-- | Render one posting, on one or more lines, suitable for `print` output.
|
||||||
@ -236,23 +236,25 @@ postingsAsLines onelineamounts ps = concatMap (postingAsLines False onelineamoun
|
|||||||
-- increased if needed to match the posting with the longest account name.
|
-- increased if needed to match the posting with the longest account name.
|
||||||
-- This is used to align the amounts of a transaction's postings.
|
-- This is used to align the amounts of a transaction's postings.
|
||||||
--
|
--
|
||||||
postingAsLines :: Bool -> Bool -> [Posting] -> Posting -> [String]
|
postingAsLines :: Bool -> Bool -> [Posting] -> Posting -> [Text]
|
||||||
postingAsLines elideamount onelineamounts pstoalignwith p = concat [
|
postingAsLines elideamount onelineamounts pstoalignwith p = concat [
|
||||||
postingblock
|
postingblock
|
||||||
++ newlinecomments
|
++ newlinecomments
|
||||||
| postingblock <- postingblocks]
|
| postingblock <- postingblocks]
|
||||||
where
|
where
|
||||||
postingblocks = [map rstrip $ lines $ concatTopPadded [statusandaccount, " ", amt, assertion, samelinecomment] | amt <- shownAmounts]
|
postingblocks = [map (T.stripEnd . T.pack) . lines $
|
||||||
|
concatTopPadded [T.unpack statusandaccount, " ", amt, assertion, T.unpack samelinecomment]
|
||||||
|
| amt <- shownAmounts]
|
||||||
assertion = maybe "" ((' ':).showBalanceAssertion) $ pbalanceassertion p
|
assertion = maybe "" ((' ':).showBalanceAssertion) $ pbalanceassertion p
|
||||||
statusandaccount = lineIndent $ fitString (Just $ minwidth) Nothing False True $ pstatusandacct p
|
statusandaccount = lineIndent . fitText (Just $ minwidth) Nothing False True $ pstatusandacct p
|
||||||
where
|
where
|
||||||
-- pad to the maximum account name width, plus 2 to leave room for status flags, to keep amounts aligned
|
-- pad to the maximum account name width, plus 2 to leave room for status flags, to keep amounts aligned
|
||||||
minwidth = maximum $ map ((2+) . textWidth . T.pack . pacctstr) pstoalignwith
|
minwidth = maximum $ map ((2+) . textWidth . pacctstr) pstoalignwith
|
||||||
pstatusandacct p' = pstatusprefix p' ++ pacctstr p'
|
pstatusandacct p' = pstatusprefix p' <> pacctstr p'
|
||||||
pstatusprefix p' | null s = ""
|
pstatusprefix p' = case pstatus p' of
|
||||||
| otherwise = s ++ " "
|
Unmarked -> ""
|
||||||
where s = show $ pstatus p'
|
s -> T.pack (show s) <> " "
|
||||||
pacctstr p' = showAccountName Nothing (ptype p') (paccount p')
|
pacctstr p' = showAccountName Nothing (ptype p') (paccount p')
|
||||||
|
|
||||||
-- currently prices are considered part of the amount string when right-aligning amounts
|
-- currently prices are considered part of the amount string when right-aligning amounts
|
||||||
shownAmounts
|
shownAmounts
|
||||||
@ -286,33 +288,27 @@ showBalanceAssertion BalanceAssertion{..} =
|
|||||||
|
|
||||||
-- | Render a posting, at the appropriate width for aligning with
|
-- | Render a posting, at the appropriate width for aligning with
|
||||||
-- its siblings if any. Used by the rewrite command.
|
-- its siblings if any. Used by the rewrite command.
|
||||||
showPostingLines :: Posting -> [String]
|
showPostingLines :: Posting -> [Text]
|
||||||
showPostingLines p = postingAsLines False False ps p where
|
showPostingLines p = postingAsLines False False ps p where
|
||||||
ps | Just t <- ptransaction p = tpostings t
|
ps | Just t <- ptransaction p = tpostings t
|
||||||
| otherwise = [p]
|
| otherwise = [p]
|
||||||
|
|
||||||
-- | Prepend a suitable indent for a posting (or transaction/posting comment) line.
|
-- | Prepend a suitable indent for a posting (or transaction/posting comment) line.
|
||||||
lineIndent :: String -> String
|
lineIndent :: Text -> Text
|
||||||
lineIndent = (" "++)
|
lineIndent = (" "<>)
|
||||||
|
|
||||||
-- | Prepend the space required before a same-line comment.
|
-- | Prepend the space required before a same-line comment.
|
||||||
commentSpace :: String -> String
|
commentSpace :: Text -> Text
|
||||||
commentSpace = (" "++)
|
commentSpace = (" "<>)
|
||||||
|
|
||||||
-- | Show an account name, clipped to the given width if any, and
|
-- | Show an account name, clipped to the given width if any, and
|
||||||
-- appropriately bracketed/parenthesised for the given posting type.
|
-- appropriately bracketed/parenthesised for the given posting type.
|
||||||
showAccountName :: Maybe Int -> PostingType -> AccountName -> String
|
showAccountName :: Maybe Int -> PostingType -> AccountName -> Text
|
||||||
showAccountName w = fmt
|
showAccountName w = fmt
|
||||||
where
|
where
|
||||||
fmt RegularPosting = maybe id take w . T.unpack
|
fmt RegularPosting = maybe id T.take w
|
||||||
fmt VirtualPosting = parenthesise . maybe id (takeEnd . subtract 2) w . T.unpack
|
fmt VirtualPosting = wrap "(" ")" . maybe id (T.takeEnd . subtract 2) w
|
||||||
fmt BalancedVirtualPosting = bracket . maybe id (takeEnd . subtract 2) w . T.unpack
|
fmt BalancedVirtualPosting = wrap "[" "]" . maybe id (T.takeEnd . subtract 2) w
|
||||||
|
|
||||||
parenthesise :: String -> String
|
|
||||||
parenthesise s = "("++s++")"
|
|
||||||
|
|
||||||
bracket :: String -> String
|
|
||||||
bracket s = "["++s++"]"
|
|
||||||
|
|
||||||
hasRealPostings :: Transaction -> Bool
|
hasRealPostings :: Transaction -> Bool
|
||||||
hasRealPostings = not . null . realPostings
|
hasRealPostings = not . null . realPostings
|
||||||
@ -427,7 +423,7 @@ transactionBalanceError t errs =
|
|||||||
|
|
||||||
annotateErrorWithTransaction :: Transaction -> String -> String
|
annotateErrorWithTransaction :: Transaction -> String -> String
|
||||||
annotateErrorWithTransaction t s =
|
annotateErrorWithTransaction t s =
|
||||||
unlines [showGenericSourcePos $ tsourcepos t, s, rstrip $ showTransaction t]
|
unlines [showGenericSourcePos $ tsourcepos t, s, T.unpack . T.stripEnd $ showTransaction t]
|
||||||
|
|
||||||
-- | Infer up to one missing amount for this transactions's real postings, and
|
-- | Infer up to one missing amount for this transactions's real postings, and
|
||||||
-- likewise for its balanced virtual postings, if needed; or return an error
|
-- likewise for its balanced virtual postings, if needed; or return an error
|
||||||
@ -678,7 +674,7 @@ tests_Transaction =
|
|||||||
Right nulltransaction{tpostings = ["a" `post` usd (-5), "b" `post` usd 5]}
|
Right nulltransaction{tpostings = ["a" `post` usd (-5), "b" `post` usd 5]}
|
||||||
(fst <$> inferBalancingAmount M.empty nulltransaction{tpostings = ["a" `post` usd (-5), "b" `post` (eur 3 @@ usd 4), "c" `post` missingamt]}) @?=
|
(fst <$> inferBalancingAmount M.empty nulltransaction{tpostings = ["a" `post` usd (-5), "b" `post` (eur 3 @@ usd 4), "c" `post` missingamt]}) @?=
|
||||||
Right nulltransaction{tpostings = ["a" `post` usd (-5), "b" `post` (eur 3 @@ usd 4), "c" `post` usd 1]}
|
Right nulltransaction{tpostings = ["a" `post` usd (-5), "b" `post` (eur 3 @@ usd 4), "c" `post` usd 1]}
|
||||||
|
|
||||||
, tests "showTransaction" [
|
, tests "showTransaction" [
|
||||||
test "null transaction" $ showTransaction nulltransaction @?= "0000-01-01\n\n"
|
test "null transaction" $ showTransaction nulltransaction @?= "0000-01-01\n\n"
|
||||||
, test "non-null transaction" $ showTransaction
|
, test "non-null transaction" $ showTransaction
|
||||||
@ -701,7 +697,7 @@ tests_Transaction =
|
|||||||
}
|
}
|
||||||
]
|
]
|
||||||
} @?=
|
} @?=
|
||||||
unlines
|
T.unlines
|
||||||
[ "2012-05-14=2012-05-15 (code) desc ; tcomment1"
|
[ "2012-05-14=2012-05-15 (code) desc ; tcomment1"
|
||||||
, " ; tcomment2"
|
, " ; tcomment2"
|
||||||
, " * a $1.00"
|
, " * a $1.00"
|
||||||
@ -727,7 +723,7 @@ tests_Transaction =
|
|||||||
, posting {paccount = "assets:checking", pamount = Mixed [usd (-47.18)], ptransaction = Just t}
|
, posting {paccount = "assets:checking", pamount = Mixed [usd (-47.18)], ptransaction = Just t}
|
||||||
]
|
]
|
||||||
in showTransaction t) @?=
|
in showTransaction t) @?=
|
||||||
(unlines
|
(T.unlines
|
||||||
[ "2007-01-28 coopportunity"
|
[ "2007-01-28 coopportunity"
|
||||||
, " expenses:food:groceries $47.18"
|
, " expenses:food:groceries $47.18"
|
||||||
, " assets:checking $-47.18"
|
, " assets:checking $-47.18"
|
||||||
@ -750,7 +746,7 @@ tests_Transaction =
|
|||||||
[ posting {paccount = "expenses:food:groceries", pamount = Mixed [usd 47.18]}
|
[ posting {paccount = "expenses:food:groceries", pamount = Mixed [usd 47.18]}
|
||||||
, posting {paccount = "assets:checking", pamount = Mixed [usd (-47.19)]}
|
, posting {paccount = "assets:checking", pamount = Mixed [usd (-47.19)]}
|
||||||
])) @?=
|
])) @?=
|
||||||
(unlines
|
(T.unlines
|
||||||
[ "2007-01-28 coopportunity"
|
[ "2007-01-28 coopportunity"
|
||||||
, " expenses:food:groceries $47.18"
|
, " expenses:food:groceries $47.18"
|
||||||
, " assets:checking $-47.19"
|
, " assets:checking $-47.19"
|
||||||
@ -771,9 +767,9 @@ tests_Transaction =
|
|||||||
""
|
""
|
||||||
[]
|
[]
|
||||||
[posting {paccount = "expenses:food:groceries", pamount = missingmixedamt}])) @?=
|
[posting {paccount = "expenses:food:groceries", pamount = missingmixedamt}])) @?=
|
||||||
(unlines ["2007-01-28 coopportunity", " expenses:food:groceries", ""])
|
(T.unlines ["2007-01-28 coopportunity", " expenses:food:groceries", ""])
|
||||||
, test "show a transaction with a priced commodityless amount" $
|
, test "show a transaction with a priced commodityless amount" $
|
||||||
(showTransaction
|
(T.unpack $ showTransaction
|
||||||
(txnTieKnot $
|
(txnTieKnot $
|
||||||
Transaction
|
Transaction
|
||||||
0
|
0
|
||||||
|
|||||||
@ -62,7 +62,7 @@ modifyTransactions d tmods ts = do
|
|||||||
-- postings when certain other postings are present.
|
-- postings when certain other postings are present.
|
||||||
--
|
--
|
||||||
-- >>> t = nulltransaction{tpostings=["ping" `post` usd 1]}
|
-- >>> t = nulltransaction{tpostings=["ping" `post` usd 1]}
|
||||||
-- >>> test = either putStr (putStr.showTransaction) . fmap ($ t) . transactionModifierToFunction nulldate
|
-- >>> test = either putStr (putStr.T.unpack.showTransaction) . fmap ($ t) . transactionModifierToFunction nulldate
|
||||||
-- >>> test $ TransactionModifier "" ["pong" `post` usd 2]
|
-- >>> test $ TransactionModifier "" ["pong" `post` usd 2]
|
||||||
-- 0000-01-01
|
-- 0000-01-01
|
||||||
-- ping $1.00
|
-- ping $1.00
|
||||||
|
|||||||
@ -13,6 +13,7 @@ module Hledger.Utils.Text
|
|||||||
-- stripbrackets,
|
-- stripbrackets,
|
||||||
textUnbracket,
|
textUnbracket,
|
||||||
wrap,
|
wrap,
|
||||||
|
textChomp,
|
||||||
-- -- quoting
|
-- -- quoting
|
||||||
quoteIfSpaced,
|
quoteIfSpaced,
|
||||||
textQuoteIfNeeded,
|
textQuoteIfNeeded,
|
||||||
@ -92,6 +93,10 @@ textElideRight width t =
|
|||||||
wrap :: Text -> Text -> Text -> Text
|
wrap :: Text -> Text -> Text -> Text
|
||||||
wrap start end x = start <> x <> end
|
wrap start end x = start <> x <> end
|
||||||
|
|
||||||
|
-- | Remove trailing newlines/carriage returns.
|
||||||
|
textChomp :: Text -> Text
|
||||||
|
textChomp = T.dropWhileEnd (`elem` ['\r', '\n'])
|
||||||
|
|
||||||
-- -- | Clip and pad a string to a minimum & maximum width, and/or left/right justify it.
|
-- -- | Clip and pad a string to a minimum & maximum width, and/or left/right justify it.
|
||||||
-- -- Works on multi-line strings too (but will rewrite non-unix line endings).
|
-- -- Works on multi-line strings too (but will rewrite non-unix line endings).
|
||||||
-- formatString :: Bool -> Maybe Int -> Maybe Int -> String -> String
|
-- formatString :: Bool -> Maybe Int -> Maybe Int -> String -> String
|
||||||
|
|||||||
@ -79,9 +79,10 @@ tsDraw UIState{aopts=UIOpts{cliopts_=copts@CliOpts{reportspec_=rspec@ReportSpec{
|
|||||||
fromMaybe (error' "TransactionScreen: expected a non-empty journal") $ -- PARTIAL: shouldn't happen
|
fromMaybe (error' "TransactionScreen: expected a non-empty journal") $ -- PARTIAL: shouldn't happen
|
||||||
reportPeriodOrJournalLastDay rspec j
|
reportPeriodOrJournalLastDay rspec j
|
||||||
|
|
||||||
render $ defaultLayout toplabel bottomlabel $ str $
|
render . defaultLayout toplabel bottomlabel . str
|
||||||
showTransactionOneLineAmounts $
|
. T.unpack . showTransactionOneLineAmounts
|
||||||
maybe t (transactionApplyValuation prices styles periodlast (rsToday rspec) t) $ value_ ropts
|
. maybe t (transactionApplyValuation prices styles periodlast (rsToday rspec) t)
|
||||||
|
$ value_ ropts
|
||||||
-- (if real_ ropts then filterTransactionPostings (Real True) else id) -- filter postings by --real
|
-- (if real_ ropts then filterTransactionPostings (Real True) else id) -- filter postings by --real
|
||||||
where
|
where
|
||||||
toplabel =
|
toplabel =
|
||||||
|
|||||||
@ -38,7 +38,7 @@
|
|||||||
#{simpleMixedAmountQuantity $ triCommodityBalance c i},
|
#{simpleMixedAmountQuantity $ triCommodityBalance c i},
|
||||||
'#{showMixedAmountWithZeroCommodity $ triCommodityAmount c i}',
|
'#{showMixedAmountWithZeroCommodity $ triCommodityAmount c i}',
|
||||||
'#{showMixedAmountWithZeroCommodity $ triCommodityBalance c i}',
|
'#{showMixedAmountWithZeroCommodity $ triCommodityBalance c i}',
|
||||||
'#{concat $ intersperse "\\n" $ lines $ showTransaction $ triOrigTransaction i}',
|
'#{concat $ intersperse "\\n" $ lines $ T.unpack $ showTransaction $ triOrigTransaction i}',
|
||||||
#{tindex $ triOrigTransaction i}
|
#{tindex $ triOrigTransaction i}
|
||||||
],
|
],
|
||||||
/* [] */
|
/* [] */
|
||||||
|
|||||||
@ -27,18 +27,19 @@ import Data.Either (isRight)
|
|||||||
import Data.Functor.Identity (Identity(..))
|
import Data.Functor.Identity (Identity(..))
|
||||||
import "base-compat-batteries" Data.List.Compat
|
import "base-compat-batteries" Data.List.Compat
|
||||||
import qualified Data.Set as S
|
import qualified Data.Set as S
|
||||||
import Data.Maybe
|
import Data.Maybe (fromJust, fromMaybe, isJust)
|
||||||
import Data.Text (Text)
|
import Data.Text (Text)
|
||||||
import qualified Data.Text as T
|
import qualified Data.Text as T
|
||||||
|
import qualified Data.Text.IO as T
|
||||||
import qualified Data.Text.Lazy as TL
|
import qualified Data.Text.Lazy as TL
|
||||||
import qualified Data.Text.Lazy.IO as TL
|
import qualified Data.Text.Lazy.IO as TL
|
||||||
import Data.Time.Calendar (Day)
|
import Data.Time.Calendar (Day)
|
||||||
import Data.Time.Format (formatTime, defaultTimeLocale, iso8601DateFormat)
|
import Data.Time.Format (formatTime, defaultTimeLocale, iso8601DateFormat)
|
||||||
import Safe (headDef, headMay, atMay)
|
import Safe (headDef, headMay, atMay)
|
||||||
import System.Console.CmdArgs.Explicit
|
import System.Console.CmdArgs.Explicit (flagNone)
|
||||||
import System.Console.Haskeline (runInputT, defaultSettings, setComplete)
|
import System.Console.Haskeline (runInputT, defaultSettings, setComplete)
|
||||||
import System.Console.Haskeline.Completion
|
import System.Console.Haskeline.Completion (CompletionFunc, completeWord, isFinished, noCompletion, simpleCompletion)
|
||||||
import System.Console.Wizard
|
import System.Console.Wizard (Wizard, defaultTo, line, output, retryMsg, linePrewritten, nonEmpty, parser, run)
|
||||||
import System.Console.Wizard.Haskeline
|
import System.Console.Wizard.Haskeline
|
||||||
import System.IO ( stderr, hPutStr, hPutStrLn )
|
import System.IO ( stderr, hPutStr, hPutStrLn )
|
||||||
import Text.Megaparsec
|
import Text.Megaparsec
|
||||||
@ -91,7 +92,7 @@ add :: CliOpts -> Journal -> IO ()
|
|||||||
add opts j
|
add opts j
|
||||||
| journalFilePath j == "-" = return ()
|
| journalFilePath j == "-" = return ()
|
||||||
| otherwise = do
|
| otherwise = do
|
||||||
hPrintf stderr "Adding transactions to journal file %s\n" (journalFilePath j)
|
hPutStrLn stderr $ "Adding transactions to journal file " <> journalFilePath j
|
||||||
showHelp
|
showHelp
|
||||||
today <- getCurrentDay
|
today <- getCurrentDay
|
||||||
let es = defEntryState{esOpts=opts
|
let es = defEntryState{esOpts=opts
|
||||||
@ -125,16 +126,16 @@ getAndAddTransactions es@EntryState{..} = (do
|
|||||||
Nothing -> error "Could not interpret the input, restarting" -- caught below causing a restart, I believe -- PARTIAL:
|
Nothing -> error "Could not interpret the input, restarting" -- caught below causing a restart, I believe -- PARTIAL:
|
||||||
Just t -> do
|
Just t -> do
|
||||||
j <- if debug_ esOpts > 0
|
j <- if debug_ esOpts > 0
|
||||||
then do hPrintf stderr "Skipping journal add due to debug mode.\n"
|
then do hPutStrLn stderr "Skipping journal add due to debug mode."
|
||||||
return esJournal
|
return esJournal
|
||||||
else do j' <- journalAddTransaction esJournal esOpts t
|
else do j' <- journalAddTransaction esJournal esOpts t
|
||||||
hPrintf stderr "Saved.\n"
|
hPutStrLn stderr "Saved."
|
||||||
return j'
|
return j'
|
||||||
hPrintf stderr "Starting the next transaction (. or ctrl-D/ctrl-C to quit)\n"
|
hPutStrLn stderr "Starting the next transaction (. or ctrl-D/ctrl-C to quit)"
|
||||||
getAndAddTransactions es{esJournal=j, esDefDate=tdate t}
|
getAndAddTransactions es{esJournal=j, esDefDate=tdate t}
|
||||||
)
|
)
|
||||||
`E.catch` (\(_::RestartTransactionException) ->
|
`E.catch` (\(_::RestartTransactionException) ->
|
||||||
hPrintf stderr "Restarting this transaction.\n" >> getAndAddTransactions es)
|
hPutStrLn stderr "Restarting this transaction." >> getAndAddTransactions es)
|
||||||
|
|
||||||
data TxnParams = TxnParams
|
data TxnParams = TxnParams
|
||||||
{ txnDate :: Day
|
{ txnDate :: Day
|
||||||
@ -182,7 +183,9 @@ confirmedTransactionWizard prevInput es@EntryState{..} stack@(currentStage : _)
|
|||||||
}
|
}
|
||||||
descAndCommentString = T.unpack $ desc <> (if T.null comment then "" else " ; " <> comment)
|
descAndCommentString = T.unpack $ desc <> (if T.null comment then "" else " ; " <> comment)
|
||||||
prevInput' = prevInput{prevDescAndCmnt=Just descAndCommentString}
|
prevInput' = prevInput{prevDescAndCmnt=Just descAndCommentString}
|
||||||
when (isJust mbaset) $ liftIO $ hPrintf stderr "Using this similar transaction for defaults:\n%s" (showTransaction $ fromJust mbaset)
|
when (isJust mbaset) . liftIO $ do
|
||||||
|
hPutStrLn stderr "Using this similar transaction for defaults:"
|
||||||
|
T.hPutStr stderr $ showTransaction (fromJust mbaset)
|
||||||
confirmedTransactionWizard prevInput' es' ((EnterNewPosting TxnParams{txnDate=date, txnCode=code, txnDesc=desc, txnCmnt=comment} Nothing) : stack)
|
confirmedTransactionWizard prevInput' es' ((EnterNewPosting TxnParams{txnDate=date, txnCode=code, txnDesc=desc, txnCmnt=comment} Nothing) : stack)
|
||||||
Nothing ->
|
Nothing ->
|
||||||
confirmedTransactionWizard prevInput es (drop 1 stack)
|
confirmedTransactionWizard prevInput es (drop 1 stack)
|
||||||
@ -241,7 +244,7 @@ confirmedTransactionWizard prevInput es@EntryState{..} stack@(currentStage : _)
|
|||||||
Nothing -> confirmedTransactionWizard prevInput es (drop 1 stack)
|
Nothing -> confirmedTransactionWizard prevInput es (drop 1 stack)
|
||||||
|
|
||||||
EndStage t -> do
|
EndStage t -> do
|
||||||
output $ showTransaction t
|
output . T.unpack $ showTransaction t
|
||||||
y <- let def = "y" in
|
y <- let def = "y" in
|
||||||
retryMsg "Please enter y or n." $
|
retryMsg "Please enter y or n." $
|
||||||
parser ((fmap (\c -> if c == '<' then Nothing else Just c)) . headMay . map toLower . strip) $
|
parser ((fmap (\c -> if c == '<' then Nothing else Just c)) . headMay . map toLower . strip) $
|
||||||
@ -305,7 +308,7 @@ accountWizard PrevInput{..} EntryState{..} = do
|
|||||||
historicalp = fmap ((!! (pnum - 1)) . (++ (repeat nullposting)) . tpostings) esSimilarTransaction
|
historicalp = fmap ((!! (pnum - 1)) . (++ (repeat nullposting)) . tpostings) esSimilarTransaction
|
||||||
historicalacct = case historicalp of Just p -> showAccountName Nothing (ptype p) (paccount p)
|
historicalacct = case historicalp of Just p -> showAccountName Nothing (ptype p) (paccount p)
|
||||||
Nothing -> ""
|
Nothing -> ""
|
||||||
def = headDef historicalacct esArgs
|
def = headDef (T.unpack historicalacct) esArgs
|
||||||
endmsg | canfinish && null def = " (or . or enter to finish this transaction)"
|
endmsg | canfinish && null def = " (or . or enter to finish this transaction)"
|
||||||
| canfinish = " (or . to finish this transaction)"
|
| canfinish = " (or . to finish this transaction)"
|
||||||
| otherwise = ""
|
| otherwise = ""
|
||||||
@ -444,7 +447,7 @@ journalAddTransaction j@Journal{jtxns=ts} opts t = do
|
|||||||
-- unelided shows all amounts explicitly, in case there's a price, cf #283
|
-- unelided shows all amounts explicitly, in case there's a price, cf #283
|
||||||
when (debug_ opts > 0) $ do
|
when (debug_ opts > 0) $ do
|
||||||
putStrLn $ printf "\nAdded transaction to %s:" f
|
putStrLn $ printf "\nAdded transaction to %s:" f
|
||||||
TL.putStrLn =<< registerFromString (T.pack $ showTransaction t)
|
TL.putStrLn =<< registerFromString (showTransaction t)
|
||||||
return j{jtxns=ts++[t]}
|
return j{jtxns=ts++[t]}
|
||||||
|
|
||||||
-- | Append a string, typically one or more transactions, to a journal
|
-- | Append a string, typically one or more transactions, to a journal
|
||||||
@ -455,15 +458,15 @@ journalAddTransaction j@Journal{jtxns=ts} opts t = do
|
|||||||
-- even if the file uses dos line endings (\r\n), which could leave
|
-- even if the file uses dos line endings (\r\n), which could leave
|
||||||
-- mixed line endings in the file. See also writeFileWithBackupIfChanged.
|
-- mixed line endings in the file. See also writeFileWithBackupIfChanged.
|
||||||
--
|
--
|
||||||
appendToJournalFileOrStdout :: FilePath -> String -> IO ()
|
appendToJournalFileOrStdout :: FilePath -> Text -> IO ()
|
||||||
appendToJournalFileOrStdout f s
|
appendToJournalFileOrStdout f s
|
||||||
| f == "-" = putStr s'
|
| f == "-" = T.putStr s'
|
||||||
| otherwise = appendFile f s'
|
| otherwise = appendFile f $ T.unpack s'
|
||||||
where s' = "\n" ++ ensureOneNewlineTerminated s
|
where s' = "\n" <> ensureOneNewlineTerminated s
|
||||||
|
|
||||||
-- | Replace a string's 0 or more terminating newlines with exactly one.
|
-- | Replace a string's 0 or more terminating newlines with exactly one.
|
||||||
ensureOneNewlineTerminated :: String -> String
|
ensureOneNewlineTerminated :: Text -> Text
|
||||||
ensureOneNewlineTerminated = (++"\n") . reverse . dropWhile (=='\n') . reverse
|
ensureOneNewlineTerminated = (<>"\n") . T.dropWhileEnd (=='\n')
|
||||||
|
|
||||||
-- | Convert a string of journal data into a register report.
|
-- | Convert a string of journal data into a register report.
|
||||||
registerFromString :: Text -> IO TL.Text
|
registerFromString :: Text -> IO TL.Text
|
||||||
|
|||||||
@ -11,7 +11,8 @@ import Control.Monad (when)
|
|||||||
import Data.Function (on)
|
import Data.Function (on)
|
||||||
import Data.List (groupBy)
|
import Data.List (groupBy)
|
||||||
import Data.Maybe
|
import Data.Maybe
|
||||||
import qualified Data.Text as T (pack)
|
import qualified Data.Text as T
|
||||||
|
import qualified Data.Text.IO as T
|
||||||
import Data.Time.Calendar
|
import Data.Time.Calendar
|
||||||
import System.Console.CmdArgs.Explicit as C
|
import System.Console.CmdArgs.Explicit as C
|
||||||
|
|
||||||
@ -152,6 +153,5 @@ close CliOpts{rawopts_=rawopts, reportspec_=rspec} j = do
|
|||||||
++ [posting{paccount=openingacct, pamount=if explicit then mapMixedAmount precise (negate totalamt) else missingmixedamt} | not interleaved]
|
++ [posting{paccount=openingacct, pamount=if explicit then mapMixedAmount precise (negate totalamt) else missingmixedamt} | not interleaved]
|
||||||
|
|
||||||
-- print them
|
-- print them
|
||||||
when closing $ putStr $ showTransaction closingtxn
|
when closing . T.putStr $ showTransaction closingtxn
|
||||||
when opening $ putStr $ showTransaction openingtxn
|
when opening . T.putStr $ showTransaction openingtxn
|
||||||
|
|
||||||
|
|||||||
@ -19,6 +19,7 @@ import Data.Maybe
|
|||||||
import Data.Time
|
import Data.Time
|
||||||
import Data.Either
|
import Data.Either
|
||||||
import qualified Data.Text as T
|
import qualified Data.Text as T
|
||||||
|
import qualified Data.Text.IO as T
|
||||||
import System.Exit
|
import System.Exit
|
||||||
|
|
||||||
import Hledger
|
import Hledger
|
||||||
@ -116,10 +117,10 @@ diff CliOpts{file_=[f1, f2], reportspec_=ReportSpec{rsQuery=Acct acctRe}} _ = do
|
|||||||
let unmatchedtxn2 = unmatchedtxns R pp2 m
|
let unmatchedtxn2 = unmatchedtxns R pp2 m
|
||||||
|
|
||||||
putStrLn "These transactions are in the first file only:\n"
|
putStrLn "These transactions are in the first file only:\n"
|
||||||
mapM_ (putStr . showTransaction) unmatchedtxn1
|
mapM_ (T.putStr . showTransaction) unmatchedtxn1
|
||||||
|
|
||||||
putStrLn "These transactions are in the second file only:\n"
|
putStrLn "These transactions are in the second file only:\n"
|
||||||
mapM_ (putStr . showTransaction) unmatchedtxn2
|
mapM_ (T.putStr . showTransaction) unmatchedtxn2
|
||||||
|
|
||||||
diff _ _ = do
|
diff _ _ = do
|
||||||
putStrLn "Please specify two input files. Usage: hledger diff -f FILE1 -f FILE2 FULLACCOUNTNAME"
|
putStrLn "Please specify two input files. Usage: hledger diff -f FILE1 -f FILE2 FULLACCOUNTNAME"
|
||||||
|
|||||||
@ -9,6 +9,7 @@ where
|
|||||||
|
|
||||||
import Control.Monad
|
import Control.Monad
|
||||||
import Data.List
|
import Data.List
|
||||||
|
import qualified Data.Text.IO as T
|
||||||
import Hledger
|
import Hledger
|
||||||
import Hledger.Cli.CliOptions
|
import Hledger.Cli.CliOptions
|
||||||
import Hledger.Cli.Commands.Add (journalAddTransaction)
|
import Hledger.Cli.Commands.Add (journalAddTransaction)
|
||||||
@ -50,7 +51,7 @@ importcmd opts@CliOpts{rawopts_=rawopts,inputopts_=iopts} j = do
|
|||||||
printf "; would import %d new transactions from %s:\n\n" (length newts) inputstr
|
printf "; would import %d new transactions from %s:\n\n" (length newts) inputstr
|
||||||
-- TODO how to force output here ?
|
-- TODO how to force output here ?
|
||||||
-- length (jtxns newj) `seq` print' opts{rawopts_=("explicit",""):rawopts} newj
|
-- length (jtxns newj) `seq` print' opts{rawopts_=("explicit",""):rawopts} newj
|
||||||
mapM_ (putStr . showTransaction) newts
|
mapM_ (T.putStr . showTransaction) newts
|
||||||
newts | catchup -> do
|
newts | catchup -> do
|
||||||
printf "marked %s as caught up, skipping %d unimported transactions\n\n" inputstr (length newts)
|
printf "marked %s as caught up, skipping %d unimported transactions\n\n" inputstr (length newts)
|
||||||
newts -> do
|
newts -> do
|
||||||
|
|||||||
@ -19,6 +19,7 @@ import Data.Maybe (isJust)
|
|||||||
import Data.Text (Text)
|
import Data.Text (Text)
|
||||||
import Data.List (intersperse)
|
import Data.List (intersperse)
|
||||||
import qualified Data.Text as T
|
import qualified Data.Text as T
|
||||||
|
import qualified Data.Text.IO as T
|
||||||
import qualified Data.Text.Lazy as TL
|
import qualified Data.Text.Lazy as TL
|
||||||
import qualified Data.Text.Lazy.Builder as TB
|
import qualified Data.Text.Lazy.Builder as TB
|
||||||
import System.Console.CmdArgs.Explicit
|
import System.Console.CmdArgs.Explicit
|
||||||
@ -65,7 +66,7 @@ printEntries opts@CliOpts{reportspec_=rspec} j =
|
|||||||
| otherwise = error' $ unsupportedOutputFormatError fmt -- PARTIAL:
|
| otherwise = error' $ unsupportedOutputFormatError fmt -- PARTIAL:
|
||||||
|
|
||||||
entriesReportAsText :: CliOpts -> EntriesReport -> TL.Text
|
entriesReportAsText :: CliOpts -> EntriesReport -> TL.Text
|
||||||
entriesReportAsText opts = TB.toLazyText . foldMap (TB.fromString . showTransaction . whichtxn)
|
entriesReportAsText opts = TB.toLazyText . foldMap (TB.fromText . showTransaction . whichtxn)
|
||||||
where
|
where
|
||||||
whichtxn
|
whichtxn
|
||||||
-- With -x, use the fully-inferred txn with all amounts & txn prices explicit.
|
-- With -x, use the fully-inferred txn with all amounts & txn prices explicit.
|
||||||
@ -176,8 +177,8 @@ postingToCSV p =
|
|||||||
where
|
where
|
||||||
Mixed amounts = pamount p
|
Mixed amounts = pamount p
|
||||||
status = show $ pstatus p
|
status = show $ pstatus p
|
||||||
account = showAccountName Nothing (ptype p) (paccount p)
|
account = T.unpack $ showAccountName Nothing (ptype p) (paccount p)
|
||||||
comment = chomp $ strip $ T.unpack $ pcomment p
|
comment = T.unpack . textChomp . T.strip $ pcomment p
|
||||||
|
|
||||||
-- --match
|
-- --match
|
||||||
|
|
||||||
@ -187,7 +188,7 @@ printMatch :: CliOpts -> Journal -> Text -> IO ()
|
|||||||
printMatch CliOpts{reportspec_=rspec} j desc = do
|
printMatch CliOpts{reportspec_=rspec} j desc = do
|
||||||
case similarTransaction' j (rsQuery rspec) desc of
|
case similarTransaction' j (rsQuery rspec) desc of
|
||||||
Nothing -> putStrLn "no matches found."
|
Nothing -> putStrLn "no matches found."
|
||||||
Just t -> putStr $ showTransaction t
|
Just t -> T.putStr $ showTransaction t
|
||||||
|
|
||||||
where
|
where
|
||||||
-- Identify the closest recent match for this description in past transactions.
|
-- Identify the closest recent match for this description in past transactions.
|
||||||
|
|||||||
@ -13,7 +13,9 @@ import Control.Monad.Writer hiding (Any)
|
|||||||
#endif
|
#endif
|
||||||
import Data.Functor.Identity
|
import Data.Functor.Identity
|
||||||
import Data.List (sortOn, foldl')
|
import Data.List (sortOn, foldl')
|
||||||
|
import Data.Text (Text)
|
||||||
import qualified Data.Text as T
|
import qualified Data.Text as T
|
||||||
|
import qualified Data.Text.IO as T
|
||||||
import Hledger
|
import Hledger
|
||||||
import Hledger.Cli.CliOptions
|
import Hledger.Cli.CliOptions
|
||||||
import Hledger.Cli.Commands.Print
|
import Hledger.Cli.Commands.Print
|
||||||
@ -65,9 +67,9 @@ printOrDiff opts
|
|||||||
diffOutput :: Journal -> Journal -> IO ()
|
diffOutput :: Journal -> Journal -> IO ()
|
||||||
diffOutput j j' = do
|
diffOutput j j' = do
|
||||||
let changed = [(originalTransaction t, originalTransaction t') | (t, t') <- zip (jtxns j) (jtxns j'), t /= t']
|
let changed = [(originalTransaction t, originalTransaction t') | (t, t') <- zip (jtxns j) (jtxns j'), t /= t']
|
||||||
putStr $ renderPatch $ map (uncurry $ diffTxn j) changed
|
T.putStr $ renderPatch $ map (uncurry $ diffTxn j) changed
|
||||||
|
|
||||||
type Chunk = (GenericSourcePos, [DiffLine String])
|
type Chunk = (GenericSourcePos, [DiffLine Text])
|
||||||
|
|
||||||
-- XXX doctests, update needed:
|
-- XXX doctests, update needed:
|
||||||
-- >>> putStr $ renderPatch [(GenericSourcePos "a" 1 1, [D.First "x", D.Second "y"])]
|
-- >>> putStr $ renderPatch [(GenericSourcePos "a" 1 1, [D.First "x", D.Second "y"])]
|
||||||
@ -95,17 +97,17 @@ type Chunk = (GenericSourcePos, [DiffLine String])
|
|||||||
-- @@ -5,0 +5,1 @@
|
-- @@ -5,0 +5,1 @@
|
||||||
-- +z
|
-- +z
|
||||||
-- | Render list of changed lines as a unified diff
|
-- | Render list of changed lines as a unified diff
|
||||||
renderPatch :: [Chunk] -> String
|
renderPatch :: [Chunk] -> Text
|
||||||
renderPatch = go Nothing . sortOn fst where
|
renderPatch = go Nothing . sortOn fst where
|
||||||
go _ [] = ""
|
go _ [] = ""
|
||||||
go Nothing cs@((sourceFilePath -> fp, _):_) = fileHeader fp ++ go (Just (fp, 0)) cs
|
go Nothing cs@((sourceFilePath -> fp, _):_) = fileHeader fp <> go (Just (fp, 0)) cs
|
||||||
go (Just (fp, _)) cs@((sourceFilePath -> fp', _):_) | fp /= fp' = go Nothing cs
|
go (Just (fp, _)) cs@((sourceFilePath -> fp', _):_) | fp /= fp' = go Nothing cs
|
||||||
go (Just (fp, offs)) ((sourceFirstLine -> lineno, diffs):cs) = chunkHeader ++ chunk ++ go (Just (fp, offs + adds - dels)) cs
|
go (Just (fp, offs)) ((sourceFirstLine -> lineno, diffs):cs) = chunkHeader <> chunk <> go (Just (fp, offs + adds - dels)) cs
|
||||||
where
|
where
|
||||||
chunkHeader = printf "@@ -%d,%d +%d,%d @@\n" lineno dels (lineno+offs) adds where
|
chunkHeader = T.pack $ printf "@@ -%d,%d +%d,%d @@\n" lineno dels (lineno+offs) adds where
|
||||||
(dels, adds) = foldl' countDiff (0, 0) diffs
|
(dels, adds) = foldl' countDiff (0, 0) diffs
|
||||||
chunk = concatMap renderLine diffs
|
chunk = foldMap renderLine diffs
|
||||||
fileHeader fp = printf "--- %s\n+++ %s\n" fp fp
|
fileHeader fp = "--- " <> T.pack fp <> "\n+++ " <> T.pack fp <> "\n"
|
||||||
|
|
||||||
countDiff (dels, adds) = \case
|
countDiff (dels, adds) = \case
|
||||||
Del _ -> (dels + 1, adds)
|
Del _ -> (dels + 1, adds)
|
||||||
@ -113,9 +115,9 @@ renderPatch = go Nothing . sortOn fst where
|
|||||||
Ctx _ -> (dels + 1, adds + 1)
|
Ctx _ -> (dels + 1, adds + 1)
|
||||||
|
|
||||||
renderLine = \case
|
renderLine = \case
|
||||||
Del s -> '-' : s ++ "\n"
|
Del s -> "-" <> s <> "\n"
|
||||||
Add s -> '+' : s ++ "\n"
|
Add s -> "+" <> s <> "\n"
|
||||||
Ctx s -> ' ' : s ++ "\n"
|
Ctx s -> " " <> s <> "\n"
|
||||||
|
|
||||||
diffTxn :: Journal -> Transaction -> Transaction -> Chunk
|
diffTxn :: Journal -> Transaction -> Transaction -> Chunk
|
||||||
diffTxn j t t' =
|
diffTxn j t t' =
|
||||||
@ -124,18 +126,18 @@ diffTxn j t t' =
|
|||||||
-- TODO: use range and produce two chunks: one removes part of
|
-- TODO: use range and produce two chunks: one removes part of
|
||||||
-- original file, other adds transaction to new file with
|
-- original file, other adds transaction to new file with
|
||||||
-- suffix .ledger (generated). I.e. move transaction from one file to another.
|
-- suffix .ledger (generated). I.e. move transaction from one file to another.
|
||||||
diffs :: [DiffLine String]
|
diffs :: [DiffLine Text]
|
||||||
diffs = concat . map (traverse showPostingLines . mapDiff) $ D.getDiff (tpostings t) (tpostings t')
|
diffs = concat . map (traverse showPostingLines . mapDiff) $ D.getDiff (tpostings t) (tpostings t')
|
||||||
pos@(JournalSourcePos fp (line, line')) -> (pos, diffs) where
|
pos@(JournalSourcePos fp (line, line')) -> (pos, diffs) where
|
||||||
-- We do diff for original lines vs generated ones. Often leads
|
-- We do diff for original lines vs generated ones. Often leads
|
||||||
-- to big diff because of re-format effect.
|
-- to big diff because of re-format effect.
|
||||||
diffs :: [DiffLine String]
|
diffs :: [DiffLine Text]
|
||||||
diffs = map mapDiff $ D.getDiff source changed'
|
diffs = map mapDiff $ D.getDiff source changed'
|
||||||
source | Just contents <- lookup fp $ jfiles j = map T.unpack . drop (line-1) . take line' $ T.lines contents
|
source | Just contents <- lookup fp $ jfiles j = drop (line-1) . take line' $ T.lines contents
|
||||||
| otherwise = []
|
| otherwise = []
|
||||||
changed = lines $ showTransaction t'
|
changed = T.lines $ showTransaction t'
|
||||||
changed' | null changed = changed
|
changed' | null changed = changed
|
||||||
| null $ last changed = init changed
|
| T.null $ last changed = init changed
|
||||||
| otherwise = changed
|
| otherwise = changed
|
||||||
|
|
||||||
data DiffLine a = Del a | Add a | Ctx a
|
data DiffLine a = Del a | Add a | Ctx a
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user