From e88a9c4a5f9cb2b11af64936a087890a9f0e7146 Mon Sep 17 00:00:00 2001 From: Simon Michael Date: Fri, 3 Aug 2018 19:38:55 +0100 Subject: [PATCH] lib: followingcomment: parse no comment as "", not "\n" Same-line & next-line comments of transactions, postings, etc. are now parsed a bit more precisely. Previously parsing no comment gave the same result as an empty comment (a single newline); now it gives an empty string. Also, and perhaps as a consequence of the above, when there's no same-line comment but there is a next-line comment, we'll insert an empty first line, otherwise next-line comments would get moved up to the same line when rendered. Some doctests have been added. --- hledger-lib/Hledger/Read/Common.hs | 43 +++++++++++++++++++++++------- 1 file changed, 33 insertions(+), 10 deletions(-) diff --git a/hledger-lib/Hledger/Read/Common.hs b/hledger-lib/Hledger/Read/Common.hs index bcfba22a9..d454ed26e 100644 --- a/hledger-lib/Hledger/Read/Common.hs +++ b/hledger-lib/Hledger/Read/Common.hs @@ -122,6 +122,9 @@ import Text.Megaparsec.Custom import Hledger.Data import Hledger.Utils +-- $setup +-- >>> :set -XOverloadedStrings + -- | A hledger journal reader is a triple of storage format name, a -- detector of that format, and a parser from that format to Journal. data Reader = Reader { @@ -977,26 +980,46 @@ emptyorcommentlinep = do -- until the next newline. This parser should extract the "content" from -- comments. The resulting parser returns this content plus the raw text -- of the comment itself. -followingcommentp' :: (Monoid a) => TextParser m a -> TextParser m (Text, a) +-- +-- See followingcommentp for tests. +-- +followingcommentp' :: (Monoid a, Show a) => TextParser m a -> TextParser m (Text, a) followingcommentp' contentp = do skipMany spacenonewline - sameLine <- try headerp *> match' contentp <|> pure ("", mempty) + -- there can be 0 or 1 sameLine + sameLine <- try headerp *> ((:[]) <$> match' contentp) <|> pure [] _ <- eolof - lowerLines <- many $ + -- there can be 0 or more nextLines + nextLines <- many $ try (skipSome spacenonewline *> headerp) *> match' contentp <* eolof - - let (textLines, results) = unzip $ sameLine : lowerLines - strippedCommentText = T.unlines $ map T.strip textLines - result = mconcat results - pure (strippedCommentText, result) + let + -- if there's just a next-line comment, insert an empty same-line comment + -- so the next-line comment doesn't get rendered as a same-line comment. + sameLine' | null sameLine && not (null nextLines) = [("",mempty)] + | otherwise = sameLine + (texts, contents) = unzip $ sameLine' ++ nextLines + strippedCommentText = T.unlines $ map T.strip texts + commentContent = mconcat contents + pure (strippedCommentText, commentContent) where headerp = char ';' *> skipMany spacenonewline {-# INLINABLE followingcommentp' #-} --- | Parse the text of a (possibly multiline) comment following a journal --- item. +-- | Parse the text of a (possibly multiline) comment following a journal item. +-- +-- >>> rtp followingcommentp "" -- no comment +-- Right "" +-- >>> rtp followingcommentp ";" -- just a (empty) same-line comment. newline is added +-- Right "\n" +-- >>> rtp followingcommentp "; \n" +-- Right "\n" +-- >>> rtp followingcommentp ";\n ;\n" -- a same-line and a next-line comment +-- Right "\n\n" +-- >>> rtp followingcommentp "\n ;\n" -- just a next-line comment. Insert an empty same-line comment so the next-line comment doesn't become a same-line comment. +-- Right "\n\n" +-- followingcommentp :: TextParser m Text followingcommentp = fst <$> followingcommentp' (void $ takeWhileP Nothing (/= '\n'))