PageRenderTime 23ms CodeModel.GetById 12ms app.highlight 8ms RepoModel.GetById 1ms app.codeStats 0ms

/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 */
 18package org.fusesource.scalate.mustache
 19
 20import util.parsing.combinator.RegexParsers
 21import util.parsing.input.{ Positional, CharSequenceReader, Position }
 22import org.fusesource.scalate.InvalidSyntaxException
 23import org.fusesource.scalate.util.Log
 24
 25sealed abstract class Statement extends Positional
 26
 27/**
 28 * Is a String with positioning information
 29 */
 30case class Text(value: String) extends Statement {
 31
 32  def +(other: String) = Text(value + other).setPos(pos)
 33
 34  def +(other: Text) = Text(value + other.value).setPos(pos)
 35
 36  def replaceAll(x: String, y: String) = Text(value.replaceAll(x, y)).setPos(pos)
 37
 38  def isWhitespace: Boolean = value.trim.length == 0
 39
 40  override def toString = value
 41}
 42
 43case class Comment(comment: Text) extends Statement
 44case class Variable(name: Text, unescape: Boolean = false) extends Statement
 45case class Section(name: Text, body: List[Statement]) extends Statement
 46case class InvertSection(name: Text, body: List[Statement]) extends Statement
 47case class Partial(name: Text) extends Statement
 48case class SetDelimiter(open: Text, close: Text) extends Statement
 49case class ImplicitIterator(name: String) extends Statement
 50case class Pragma(name: Text, options: Map[String, String]) extends Statement
 51
 52object MustacheParser extends Log
 53
 54/**
 55 * Parser for the Mustache template language
 56 *
 57 * @version $Revision : 1.1 $
 58 */
 59class MustacheParser extends RegexParsers {
 60
 61  import MustacheParser._
 62
 63  private[this] var _open: String = "{{"
 64  private[this] var _close: String = "}}"
 65
 66  def parse(in: String) = {
 67    phrase(mustache)(new CharSequenceReader(in)) match {
 68      case Success(s, _) => s
 69      case NoSuccess(message, next) => throw new InvalidSyntaxException(message, next.pos);
 70    }
 71  }
 72
 73  // Grammar
 74  //-------------------------------------------------------------------------
 75  def mustache: Parser[List[Statement]] = rep(statement | someText)
 76
 77  def someText: Parser[Statement] = upto(open)
 78
 79  def statement: Parser[Statement] = unescapeVariable | partial | pragma | section | invert | comment | setDelimiter | variable |
 80    failure("invalid statement")
 81
 82  def unescapeVariable: Parser[Statement] = unescapeVariableAmp | unescapeVariableMustash
 83
 84  def unescapeVariableAmp: Parser[Statement] = expression(operation("&") ^^ { Variable(_, true) })
 85
 86  def unescapeVariableMustash: Parser[Statement] = expression("{" ~> trimmed <~ "}" ^^ { Variable(_, true) })
 87
 88  def section: Parser[Statement] = positioned(nested("#") ^^ {
 89    case (name, body) => Section(name, body)
 90  })
 91
 92  def invert: Parser[Statement] = positioned(nested("^") ^^ {
 93    case (name, body) => InvertSection(name, body)
 94  })
 95
 96  def partial = expression(operation(">") ^^ { Partial(_) })
 97
 98  def pragma = expression(operation("%") ~ rep(option) ^^ {
 99    case p ~ o =>
100      val options = Map(o: _*)
101      p match {
102        case Text("IMPLICIT-ITERATOR") =>
103          val name = options.getOrElse("iterator", ".")
104          ImplicitIterator(name)
105        case _ =>
106          Pragma(p, options)
107      }
108  })
109
110  def option = trimmed ~ ("=" ~> trimmed) ^^ { case n ~ v => n.value -> v.value }
111
112  def comment = expression((trim("!") ~> upto(close)) ^^ { Comment(_) })
113
114  def variable = expression(trimmed ^^ { Variable(_, false) })
115
116  def setDelimiter = expression(("=" ~> text("""\S+""".r) <~ " ") ~ (upto("=" ~ close) <~ ("=")) ^^ {
117    case a ~ b => SetDelimiter(a, b)
118  }) <~ opt(whiteSpace) ^^ {
119    case a: SetDelimiter =>
120      _open = a.open.value
121      _close = a.close.value
122      debug("applying new delim '" + a)
123      a
124  }
125
126  // Helper methods
127  //-------------------------------------------------------------------------
128
129  def open = eval(_open)
130
131  def close = eval(_close)
132
133  def operation(prefix: String): Parser[Text] = trim(prefix) ~> trimmed
134
135  def nested(prefix: String): Parser[(Text, List[Statement])] = expression(operation(prefix) ^^ { case x => Text(x.value) }) >> {
136    case name: Text =>
137      opt(whiteSpace) ~> mustache <~ expression(trim("/") ~> trim(text(name.value))) <~ opt(whiteSpace) ^^ {
138        case body => (name, body)
139      } | error("Missing section end '" + _open + "/" + name + _close + "' for section beginning", name.pos)
140  }
141
142  override def skipWhitespace = false
143
144  def expression[T <: Statement](p: Parser[T]): Parser[T] = positioned(open ~> p <~ close)
145
146  def trimmed = trim(text("""(\w|\.)[^\s={}]*""".r))
147
148  def trim[T](p: Parser[T]): Parser[T] = opt(whiteSpace) ~> p <~ opt(whiteSpace)
149
150  def text(p1: Parser[String]): Parser[Text] = {
151    positioned(p1 ^^ { Text(_) })
152  }
153
154  // use a parser who's implementation is generated by the supplied partial function..  Handy
155  // for when a sub part for a larger parser is dynamically changing.
156  def eval[T](p: => Parser[T]): Parser[T] = Parser { in => p(in) }
157
158  def upto[T](p: Parser[T]): Parser[Text] = text("""\z""".r) ~ failure("end of file") ^^ { null } |
159    rep1(not(p) ~> ".|\r|\n".r) ^^ { t => Text(t.mkString("")) }
160
161  def error(message: String, pos: Position) = {
162    throw new InvalidSyntaxException(message, pos);
163  }
164
165  def isWhitespace(statement: Statement): Boolean = statement match {
166    case t: Text => t.isWhitespace
167    case _ => false
168  }
169
170}