agda2lagda-0.2021.6.1: src/Markup.hs
-- | Detect markup in the text items.
--
-- A line of text consisting only of "=" (and at least 2) is a heading.
-- A line of text consisting only of "-" (and at least 2) is a subheading.
module Markup
( markup, Item(..), TextItem(..)
) where
import Util
import qualified LexicalStructure as L
-- | A more refined version of 'LexicalStructure.Item'.
data Item
= CodeItem String -- ^ Same.
| CommItem String -- ^ Same.
| TextItem TextItem
data TextItem
= Paragraph Paragraph -- ^ A non-empty list of non-whitespace lines.
| Heading Level String -- ^ A heading at the given level (1,2).
| Itemize [Paragraph] -- ^ A bulleted list (non-nested).
type Level = Int
-- | Entrypoint: Process a lexed file.
markup :: [L.Item] -> [Item]
markup = concatMap processText . gobbleTrailingBlockCommentClosers
where
processText = \case
L.CommItem s -> [CommItem s]
L.CodeItem s -> [CodeItem s]
L.TextItem s ->
map TextItem
. groupBullets
. map detectMarkup
$ paragraphs s
-- | A paragraph is a non-empty list of non-empty lines.
type Paragraph = [String]
groupBullets :: [TextItem] -> [TextItem]
groupBullets = map joinItems . groupBy (\ a b -> all (isJust . isItemize) [a,b])
where
isItemize = \case
Itemize ps -> Just ps
_ -> Nothing
joinItems :: [TextItem] -> TextItem
joinItems is =
-- In each group...
case mapMaybe isItemize is of
-- ...either it is not Itemize
[] -> head is
-- or all are Itemize.
pps -> Itemize $ concat pps
-- | If the last line of a paragraph is just dashes or equal signs, we are a heading.
detectMarkup :: Paragraph -> TextItem
detectMarkup ls@(l1:ls1)
| Just s <- bulleted l1 = Itemize [s:ls1]
| all (== '=') l0 = Heading 1 $ unwords $ map trimLeft ls0
| all (== '-') l0 = Heading 2 $ unwords $ map trimLeft ls0
| otherwise = Paragraph ls
where
(ls0, l) = initLast ls -- safe!
l0 = trimLeft l
-- | If the line starts with @*@, return the rest.
bulleted :: String -> Maybe String
bulleted l =
case trimLeft l of
'*':' ':s -> Just s
_ -> Nothing
-- | Split a text into paragraphs, dropping extra empty lines in between.
-- Preserves indentation.
paragraphs :: String -> [Paragraph]
paragraphs = mapMaybe filterBlank . groupBy emptyOrNot . lines
where
emptyLine = null . trimLeft
emptyOrNot = (==) `on` emptyLine
filterBlank ls
| all emptyLine ls = Nothing
| otherwise = Just ls
-- | Text lines at the end of file consisting solely of words "-}" are
-- discarded, to accommodate for a hack that makes it easy to comment
-- out everything to the end of the file.
-- https://github.com/agda/agda/issues/4953#issuecomment-702720296
gobbleTrailingBlockCommentClosers :: [L.Item] -> [L.Item]
gobbleTrailingBlockCommentClosers = updateLast $ \case
L.TextItem s -> L.TextItem $ reverse $ loop $ reverse s
i -> i
where
loop = \case
-- Drop trailing spaces.
c:cs | isSpace c -> loop cs
-- Drop trailing block comment closers.
'}':'-':cs -> loop cs
-- Keep rest.
s -> s