PageRenderTime 98ms CodeModel.GetById 19ms RepoModel.GetById 1ms app.codeStats 1ms

/scalate-core/src/main/scala/org/fusesource/scalate/mustache/MustacheParser.scala

http://github.com/scalate/scalate
Scala | 170 lines | 96 code | 43 blank | 31 comment | 1 complexity | 235128cb6213281c841f51535a06477f MD5 | raw file
  1. /**
  2. * Copyright (C) 2009-2011 the original author or authors.
  3. * See the notice.md file distributed with this work for additional
  4. * information regarding copyright ownership.
  5. *
  6. * Licensed under the Apache License, Version 2.0 (the "License");
  7. * you may not use this file except in compliance with the License.
  8. * You may obtain a copy of the License at
  9. *
  10. * http://www.apache.org/licenses/LICENSE-2.0
  11. *
  12. * Unless required by applicable law or agreed to in writing, software
  13. * distributed under the License is distributed on an "AS IS" BASIS,
  14. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  15. * See the License for the specific language governing permissions and
  16. * limitations under the License.
  17. */
  18. package org.fusesource.scalate.mustache
  19. import util.parsing.combinator.RegexParsers
  20. import util.parsing.input.{ Positional, CharSequenceReader, Position }
  21. import org.fusesource.scalate.InvalidSyntaxException
  22. import org.fusesource.scalate.util.Log
  23. sealed abstract class Statement extends Positional
  24. /**
  25. * Is a String with positioning information
  26. */
  27. case class Text(value: String) extends Statement {
  28. def +(other: String) = Text(value + other).setPos(pos)
  29. def +(other: Text) = Text(value + other.value).setPos(pos)
  30. def replaceAll(x: String, y: String) = Text(value.replaceAll(x, y)).setPos(pos)
  31. def isWhitespace: Boolean = value.trim.length == 0
  32. override def toString = value
  33. }
  34. case class Comment(comment: Text) extends Statement
  35. case class Variable(name: Text, unescape: Boolean = false) extends Statement
  36. case class Section(name: Text, body: List[Statement]) extends Statement
  37. case class InvertSection(name: Text, body: List[Statement]) extends Statement
  38. case class Partial(name: Text) extends Statement
  39. case class SetDelimiter(open: Text, close: Text) extends Statement
  40. case class ImplicitIterator(name: String) extends Statement
  41. case class Pragma(name: Text, options: Map[String, String]) extends Statement
  42. object MustacheParser extends Log
  43. /**
  44. * Parser for the Mustache template language
  45. *
  46. * @version $Revision : 1.1 $
  47. */
  48. class MustacheParser extends RegexParsers {
  49. import MustacheParser._
  50. private[this] var _open: String = "{{"
  51. private[this] var _close: String = "}}"
  52. def parse(in: String) = {
  53. phrase(mustache)(new CharSequenceReader(in)) match {
  54. case Success(s, _) => s
  55. case NoSuccess(message, next) => throw new InvalidSyntaxException(message, next.pos);
  56. }
  57. }
  58. // Grammar
  59. //-------------------------------------------------------------------------
  60. def mustache: Parser[List[Statement]] = rep(statement | someText)
  61. def someText: Parser[Statement] = upto(open)
  62. def statement: Parser[Statement] = unescapeVariable | partial | pragma | section | invert | comment | setDelimiter | variable |
  63. failure("invalid statement")
  64. def unescapeVariable: Parser[Statement] = unescapeVariableAmp | unescapeVariableMustash
  65. def unescapeVariableAmp: Parser[Statement] = expression(operation("&") ^^ { Variable(_, true) })
  66. def unescapeVariableMustash: Parser[Statement] = expression("{" ~> trimmed <~ "}" ^^ { Variable(_, true) })
  67. def section: Parser[Statement] = positioned(nested("#") ^^ {
  68. case (name, body) => Section(name, body)
  69. })
  70. def invert: Parser[Statement] = positioned(nested("^") ^^ {
  71. case (name, body) => InvertSection(name, body)
  72. })
  73. def partial = expression(operation(">") ^^ { Partial(_) })
  74. def pragma = expression(operation("%") ~ rep(option) ^^ {
  75. case p ~ o =>
  76. val options = Map(o: _*)
  77. p match {
  78. case Text("IMPLICIT-ITERATOR") =>
  79. val name = options.getOrElse("iterator", ".")
  80. ImplicitIterator(name)
  81. case _ =>
  82. Pragma(p, options)
  83. }
  84. })
  85. def option = trimmed ~ ("=" ~> trimmed) ^^ { case n ~ v => n.value -> v.value }
  86. def comment = expression((trim("!") ~> upto(close)) ^^ { Comment(_) })
  87. def variable = expression(trimmed ^^ { Variable(_, false) })
  88. def setDelimiter = expression(("=" ~> text("""\S+""".r) <~ " ") ~ (upto("=" ~ close) <~ ("=")) ^^ {
  89. case a ~ b => SetDelimiter(a, b)
  90. }) <~ opt(whiteSpace) ^^ {
  91. case a: SetDelimiter =>
  92. _open = a.open.value
  93. _close = a.close.value
  94. debug("applying new delim '" + a)
  95. a
  96. }
  97. // Helper methods
  98. //-------------------------------------------------------------------------
  99. def open = eval(_open)
  100. def close = eval(_close)
  101. def operation(prefix: String): Parser[Text] = trim(prefix) ~> trimmed
  102. def nested(prefix: String): Parser[(Text, List[Statement])] = expression(operation(prefix) ^^ { case x => Text(x.value) }) >> {
  103. case name: Text =>
  104. opt(whiteSpace) ~> mustache <~ expression(trim("/") ~> trim(text(name.value))) <~ opt(whiteSpace) ^^ {
  105. case body => (name, body)
  106. } | error("Missing section end '" + _open + "/" + name + _close + "' for section beginning", name.pos)
  107. }
  108. override def skipWhitespace = false
  109. def expression[T <: Statement](p: Parser[T]): Parser[T] = positioned(open ~> p <~ close)
  110. def trimmed = trim(text("""(\w|\.)[^\s={}]*""".r))
  111. def trim[T](p: Parser[T]): Parser[T] = opt(whiteSpace) ~> p <~ opt(whiteSpace)
  112. def text(p1: Parser[String]): Parser[Text] = {
  113. positioned(p1 ^^ { Text(_) })
  114. }
  115. // use a parser who's implementation is generated by the supplied partial function.. Handy
  116. // for when a sub part for a larger parser is dynamically changing.
  117. def eval[T](p: => Parser[T]): Parser[T] = Parser { in => p(in) }
  118. def upto[T](p: Parser[T]): Parser[Text] = text("""\z""".r) ~ failure("end of file") ^^ { null } |
  119. rep1(not(p) ~> ".|\r|\n".r) ^^ { t => Text(t.mkString("")) }
  120. def error(message: String, pos: Position) = {
  121. throw new InvalidSyntaxException(message, pos);
  122. }
  123. def isWhitespace(statement: Statement): Boolean = statement match {
  124. case t: Text => t.isWhitespace
  125. case _ => false
  126. }
  127. }