git.iko.soy/typeable/pandoc
on master help sign in
Templates: Fail informatively on template syntax errors.

With the move from parsec to attoparsec, we lost good error reporting. In fact, since we weren't testing for end of input, malformed templates would fail silently. Here we revert back to Parsec for better error messages.

commite1cf47efa0029a385c39053c9f441ade29d7a991 authorJohn MacFarlane <jgm@berkeley.edu> dateSun 1 Jun 2014, 23:45 (12 years ago) parent0bd8d5f
2 files changed+38−33
pandoc.cabal−1 src/Text/Pandoc/Templates.hs+38−32
pandoc.cabal−1
@@ -232,7 +232,6 @@ Library
232232 temporary >= 1.1 && < 1.3,
233233 blaze-html >= 0.5 && < 0.8,
234234 blaze-markup >= 0.5.1 && < 0.7,
235− attoparsec >= 0.10 && < 0.12,
236235 yaml >= 0.8.8.2 && < 0.9,
237236 scientific >= 0.2 && < 0.4,
238237 vector >= 0.10 && < 0.11,
src/Text/Pandoc/Templates.hs+38−32
@@ -96,8 +96,8 @@ module Text.Pandoc.Templates ( renderTemplate
9696import Data.Char (isAlphaNum)
9797import Control.Monad (guard, when)
9898import Data.Aeson (ToJSON(..), Value(..))
99−import qualified Data.Attoparsec.Text as A
100−import Data.Attoparsec.Text (Parser)
99+import qualified Text.Parsec as P
100+import Text.Parsec.Text (Parser)
101101import Control.Applicative
102102import qualified Data.Text as T
103103import Data.Text (Text)
@@ -172,7 +172,10 @@ renderTemplate :: (ToJSON a, TemplateTarget b) => Template -> a -> b
172172renderTemplate (Template f) context = toTarget $ f $ toJSON context
173173
174174compileTemplate :: Text -> Either String Template
175−compileTemplate template = A.parseOnly pTemplate template
175+compileTemplate template =
176+ case P.parse (pTemplate <* P.eof) "template" template of
177+ Left e -> Left (show e)
178+ Right x -> Right x
176179
177180-- | Like 'renderTemplate', but compiles the template first,
178181-- raising an error if compilation fails.
@@ -230,7 +233,7 @@ replaceVar _ _ old = old
230233
231234pTemplate :: Parser Template
232235pTemplate = do
233− sp <- A.option mempty pInitialSpace
236+ sp <- P.option mempty pInitialSpace
234237 rest <- mconcat <$> many (pConditional <|>
235238 pFor <|>
236239 pNewline <|>
@@ -239,40 +242,43 @@ pTemplate = do
239242 pEscapedDollar)
240243 return $ sp <> rest
241244
245+takeWhile1 :: (Char -> Bool) -> Parser Text
246+takeWhile1 f = T.pack <$> P.many1 (P.satisfy f)
247+
242248pLit :: Parser Template
243−pLit = lit <$> A.takeWhile1 (\x -> x /='$' && x /= '\n')
249+pLit = lit <$> takeWhile1 (\x -> x /='$' && x /= '\n')
244250
245251pNewline :: Parser Template
246252pNewline = do
247− A.char '\n'
248− sp <- A.option mempty pInitialSpace
253+ P.char '\n'
254+ sp <- P.option mempty pInitialSpace
249255 return $ lit "\n" <> sp
250256
251257pInitialSpace :: Parser Template
252258pInitialSpace = do
253− sps <- A.takeWhile1 (==' ')
259+ sps <- takeWhile1 (==' ')
254260 let indentVar = if T.null sps
255261 then id
256262 else indent (T.length sps)
257− v <- A.option mempty $ indentVar <$> pVar
263+ v <- P.option mempty $ indentVar <$> pVar
258264 return $ lit sps <> v
259265
260266pEscapedDollar :: Parser Template
261−pEscapedDollar = lit "$" <$ A.string "$$"
267+pEscapedDollar = lit "$" <$ P.try (P.string "$$")
262268
263269pVar :: Parser Template
264−pVar = var <$> (A.char '$' *> pIdent <* A.char '$')
270+pVar = var <$> (P.try $ P.char '$' *> pIdent <* P.char '$')
265271
266272pIdent :: Parser [Text]
267273pIdent = do
268274 first <- pIdentPart
269− rest <- many (A.char '.' *> pIdentPart)
275+ rest <- many (P.char '.' *> pIdentPart)
270276 return (first:rest)
271277
272278pIdentPart :: Parser Text
273−pIdentPart = do
274− first <- A.letter
275− rest <- A.takeWhile (\c -> isAlphaNum c || c == '_' || c == '-')
279+pIdentPart = P.try $ do
280+ first <- P.letter
281+ rest <- T.pack <$> P.many (P.satisfy (\c -> isAlphaNum c || c == '_' || c == '-'))
276282 let id' = T.singleton first <> rest
277283 guard $ id' `notElem` reservedWords
278284 return id'
@@ -281,38 +287,38 @@ reservedWords :: [Text]
281287reservedWords = ["else","endif","for","endfor","sep"]
282288
283289skipEndline :: Parser ()
284−skipEndline = A.skipWhile (`elem` " \t") >> A.char '\n' >> return ()
290+skipEndline = P.try $ P.skipMany (P.satisfy (`elem` " \t")) >> P.char '\n' >> return ()
285291
286292pConditional :: Parser Template
287293pConditional = do
288− A.string "$if("
294+ P.try $ P.string "$if("
289295 id' <- pIdent
290− A.string ")$"
296+ P.string ")$"
291297 -- if newline after the "if", then a newline after "endif" will be swallowed
292− multiline <- A.option False (True <$ skipEndline)
298+ multiline <- P.option False (True <$ skipEndline)
293299 ifContents <- pTemplate
294− elseContents <- A.option mempty $
295− do A.string "$else$"
296− when multiline $ A.option () skipEndline
300+ elseContents <- P.option mempty $ P.try $
301+ do P.string "$else$"
302+ when multiline $ P.option () skipEndline
297303 pTemplate
298− A.string "$endif$"
299− when multiline $ A.option () skipEndline
304+ P.string "$endif$"
305+ when multiline $ P.option () skipEndline
300306 return $ cond id' ifContents elseContents
301307
302308pFor :: Parser Template
303309pFor = do
304− A.string "$for("
310+ P.try $ P.string "$for("
305311 id' <- pIdent
306− A.string ")$"
312+ P.string ")$"
307313 -- if newline after the "for", then a newline after "endfor" will be swallowed
308− multiline <- A.option False $ skipEndline >> return True
314+ multiline <- P.option False $ skipEndline >> return True
309315 contents <- pTemplate
310− sep <- A.option mempty $
311− do A.string "$sep$"
312− when multiline $ A.option () skipEndline
316+ sep <- P.option mempty $
317+ do P.try $ P.string "$sep$"
318+ when multiline $ P.option () skipEndline
313319 pTemplate
314− A.string "$endfor$"
315− when multiline $ A.option () skipEndline
320+ P.string "$endfor$"
321+ when multiline $ P.option () skipEndline
316322 return $ iter id' contents sep
317323
318324indent :: Int -> Template -> Template