module Text.Jira.Parser.Inline
( inline
, anchor
, deleted
, emph
, entity
, inserted
, image
, linebreak
, link
, monospaced
, str
, strong
, subscript
, superscript
, symbol
, whitespace
) where
import Control.Monad (guard, void)
import Data.Char (isLetter)
import Data.Monoid ((<>), All (..))
import Data.Text (pack, singleton)
import Text.Jira.Markup
import Text.Jira.Parser.Core
import Text.Parsec
inline :: JiraParser Inline
inline = notFollowedBy' blockEnd *> choice
[ whitespace
, str
, linebreak
, link
, image
, emph
, strong
, subscript
, superscript
, deleted
, inserted
, monospaced
, anchor
, entity
, symbol
] <?> "inline"
where
blockEnd = char '{' *> choice (map string blockNames) <* char '}'
specialChars :: String
specialChars = " \n" ++ symbolChars
symbolChars :: String
symbolChars = "_+-*^~|[]{}!&\\"
linebreak :: JiraParser Inline
linebreak = Linebreak <$ try (newline <* notFollowedBy' endOfPara)
<?> "linebreak"
whitespace :: JiraParser Inline
whitespace = Space <$ skipMany1 (char ' ') <?> "whitespace"
str :: JiraParser Inline
str = Str . pack <$> (alphaNums <|> otherNonSpecialChars) <?> "string"
where
alphaNums = many1 alphaNum <* updateLastStrPos
otherNonSpecialChars = many1 (noneOf specialChars)
entity :: JiraParser Inline
entity = Entity . pack
<$> try (char '&' *> (numerical <|> named) <* char ';')
where
numerical = (:) <$> char '#' <*> many1 digit
named = many1 letter
symbol :: JiraParser Inline
symbol = Str . singleton <$> (escapedChar <|> symbolChar)
<?> "symbol"
where
escapedChar = try (char '\\' *> oneOf symbolChars)
symbolChar = do
inTablePred <- do
b <- stateInTable <$> getState
return $ if b then All . (/= '|') else mempty
inLinkPred <- do
b <- stateInLink <$> getState
return $ if b then All . (`notElem` ("]|\n" :: String)) else mempty
oneOf $ filter (getAll . (inTablePred <> inLinkPred)) symbolChars
anchor :: JiraParser Inline
anchor = Anchor . pack . filter (/= ' ')
<$> try (string "{anchor:" *> noneOf "\n" `manyTill` char '}')
image :: JiraParser Inline
image = fmap (Image . URL . pack) . try $
char '!' *> noneOf "\r\t\n" `manyTill` char '!'
link :: JiraParser Inline
link = try $ do
guard . not . stateInLink =<< getState
withStateFlag (\b st -> st { stateInLink = b }) $ do
_ <- char '['
alias <- option [] $ try (many inline <* char '|')
url <- URL . pack <$> many1 (noneOf "|] \n")
_ <- char ']'
return $ Link alias url
deleted :: JiraParser Inline
deleted = Deleted <$> ('-' `delimitingMany` inline) <?> "deleted"
emph :: JiraParser Inline
emph = Emph <$> ('_' `delimitingMany` inline) <?> "emphasis"
inserted :: JiraParser Inline
inserted = Inserted <$> ('+' `delimitingMany` inline) <?> "inserted"
monospaced :: JiraParser Inline
monospaced = Monospaced
<$> enclosed (try $ string "{{") (try $ string "}}") inline
<?> "monospaced"
strong :: JiraParser Inline
strong = Strong <$> ('*' `delimitingMany` inline) <?> "strong"
subscript :: JiraParser Inline
subscript = Subscript <$> ('~' `delimitingMany` inline) <?> "subscript"
superscript :: JiraParser Inline
superscript = Superscript <$> ('^' `delimitingMany` inline) <?> "superscript"
delimitingMany :: Char -> JiraParser a -> JiraParser [a]
delimitingMany c = enclosed (char c) (char c)
enclosed :: JiraParser opening -> JiraParser closing
-> JiraParser a
-> JiraParser [a]
enclosed opening closing parser = try $ do
guard =<< notAfterString
opening *> notFollowedBy space *> manyTill parser closing'
where
closing' = try $ closing <* lookAhead wordBoundary
wordBoundary = void (satisfy (not . isLetter)) <|> eof