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

http://github.com/scalate/scalate · Scala · 279 lines · 184 code · 44 blank · 51 comment · 15 complexity · 81786d45e63b42c266be82fe7dacab60 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 org.fusesource.scalate.RenderContext
  20. import scala.collection.JavaConverters._
  21. import java.{ lang => jl, util => ju }
  22. import xml.NodeSeq
  23. import org.fusesource.scalate.util.Log
  24. object Scope extends Log {
  25. def apply(context: RenderContext) = {
  26. context.attributeOrElse[Scope]("scope", RenderContextScope(context))
  27. }
  28. }
  29. /**
  30. * Represents a variable scope
  31. *
  32. * @version $Revision : 1.1 $
  33. */
  34. trait Scope {
  35. import Scope._
  36. def parent: Option[Scope]
  37. def context: RenderContext
  38. var implicitIterator: Option[String] = Some(".")
  39. /**
  40. * Renders the given variable name to the context
  41. */
  42. def renderVariable(name: String, unescape: Boolean): Unit = {
  43. val v = apply(name) match {
  44. case Some(a) => a
  45. case None =>
  46. parent match {
  47. case Some(p) => p.apply(name)
  48. case _ => null
  49. }
  50. }
  51. debug("renderVariable %s = %s on %s", name, v, this)
  52. renderValue(v, unescape)
  53. }
  54. def renderValue(v: Any, unescape: Boolean = false): Unit = if (unescape) {
  55. context.unescape(format(v))
  56. } else {
  57. context.escape(format(v))
  58. }
  59. /**
  60. * Returns the variable of the given name looking in this scope or parent scopes to resolve the variable
  61. */
  62. def apply(name: String): Option[Any] = {
  63. val value = localVariable(name)
  64. value match {
  65. case Some(v) => value
  66. case _ =>
  67. if (implicitIterator.isDefined && implicitIterator.get == name) {
  68. iteratorObject
  69. } else {
  70. parent match {
  71. case Some(p) => p.apply(name)
  72. case _ => None
  73. }
  74. }
  75. }
  76. }
  77. /**
  78. * Returns the current implicit iterator object
  79. */
  80. def iteratorObject: Option[Any] = None
  81. /**
  82. * Returns the variable in the local scope if it is defined
  83. */
  84. def localVariable(name: String): Option[Any]
  85. def section(name: String)(block: Scope => Unit): Unit = {
  86. apply(name) match {
  87. case Some(t) =>
  88. val v = toIterable(t, block)
  89. debug("section value " + name + " = " + v + " in " + this)
  90. v match {
  91. // TODO we have to be really careful to distinguish between collections of things
  92. // such as Seq from objects / products / Maps / partial functions which act as something to lookup names
  93. case FunctionResult(r) => renderValue(r)
  94. case s: NodeSeq => childScope(name, s)(block)
  95. case s: Seq[Any] => foreachScope(name, s)(block)
  96. case Some(a) => childScope(name, a)(block)
  97. case None =>
  98. // lets treat empty maps as being empty collections
  99. // due to bug in JSON parser returning Map() for JSON expression []
  100. case s: collection.Map[_, _] => if (!s.isEmpty) childScope(name, s)(block)
  101. // maps and so forth, treat as child scopes
  102. case a: PartialFunction[_, _] => childScope(name, a)(block)
  103. // any other traversable treat as a collection
  104. case s: Traversable[Any] => foreachScope(name, s.toIterable)(block)
  105. case true => block(this)
  106. case false =>
  107. case null =>
  108. // lets treat anything as an an object rather than a collection
  109. case a => childScope(name, a)(block)
  110. }
  111. case None => parent match {
  112. case Some(ps) => ps.section(name)(block)
  113. case None => // do nothing, no value
  114. debug("No value for " + name + " in " + this)
  115. }
  116. }
  117. }
  118. def invertedSection(name: String)(block: Scope => Unit): Unit = {
  119. apply(name) match {
  120. case Some(t) =>
  121. val v = toIterable(t, block)
  122. debug("invertedSection value " + name + " = " + v + " in " + this)
  123. v match {
  124. // TODO we have to be really careful to distinguish between collections of things
  125. // such as Seq from objects / products / Maps / partial functions which act as something to lookup names
  126. case FunctionResult(r) =>
  127. case s: NodeSeq => if (s.isEmpty) block(this)
  128. case s: Seq[_] => if (s.isEmpty) block(this)
  129. case Some(a) =>
  130. case None => block(this)
  131. case s: collection.Map[_, _] => if (s.isEmpty) block(this)
  132. // maps and so forth, treat as child scopes
  133. case a: PartialFunction[_, _] =>
  134. // any other traversible treat as a collection
  135. case s: Traversable[Any] => if (s.isEmpty) block(this)
  136. case true =>
  137. case false => block(this)
  138. case null => block(this)
  139. // lets treat anything as an an object rather than a collection
  140. case a =>
  141. }
  142. case None => block(this)
  143. }
  144. }
  145. def partial(name: String): Unit = {
  146. context.withAttributes(Map("scope" -> this)) {
  147. // TODO allow the extension to be overloaded
  148. context.include(name + ".mustache")
  149. }
  150. }
  151. def childScope(name: String, v: Any)(block: Scope => Unit): Unit = {
  152. debug("Creating scope for: " + v)
  153. val scope = createScope(name, v)
  154. block(scope)
  155. }
  156. def foreachScope[T](name: String, s: Iterable[T])(block: Scope => Unit): Unit = {
  157. for (i <- s) {
  158. debug("Creating traversable scope for: " + i)
  159. val scope = createScope(name, i)
  160. block(scope)
  161. }
  162. }
  163. def createScope(name: String, value: Any): Scope = {
  164. value match {
  165. case n: NodeSeq => new NodeScope(this, name, n)
  166. case v: scala.collection.Map[_, _] =>
  167. new MapScope(
  168. this,
  169. name,
  170. v.asInstanceOf[scala.collection.Map[String, _]])
  171. case null => new EmptyScope(this)
  172. case None => new EmptyScope(this)
  173. case v: AnyRef => new ObjectScope(this, v)
  174. case v =>
  175. warn("Unable to process value: %s", v)
  176. new EmptyScope(this)
  177. }
  178. }
  179. @deprecated(message = "use toIterable instead", since = "")
  180. def toTraversable(v: Any, block: Scope => Unit): Any = toIterable(v, block)
  181. def toIterable(v: Any, block: Scope => Unit): Any = v match {
  182. case t: Seq[_] => t
  183. case t: Array[_] => t.toSeq
  184. case t: ju.Map[_, _] => t.asScala
  185. case f: Function0[_] => toIterable(f(), block)
  186. case f: Function1[_, _] =>
  187. if (isParam1(f, classOf[Object])) {
  188. // Java lambda support since 1.8
  189. try {
  190. val f2 = f.asInstanceOf[Function1[Scope, _]]
  191. toIterable(f2(this), block)
  192. } catch {
  193. case e: Exception =>
  194. try {
  195. val f2 = f.asInstanceOf[Function1[String, _]]
  196. FunctionResult(f2(capture(block)))
  197. } catch {
  198. case e: Exception =>
  199. f
  200. }
  201. }
  202. } else {
  203. f
  204. }
  205. case c: ju.Collection[_] => c.asScala
  206. case i: ju.Iterator[_] => i.asScala
  207. case i: jl.Iterable[_] => i.asScala
  208. case _ => v
  209. }
  210. def format(v: Any): Any = v match {
  211. case f: Function0[_] => format(f())
  212. case f: Function1[_, _] if isParam1(f, classOf[Scope]) => format(f.asInstanceOf[Function1[Scope, _]](this))
  213. case f: Function1[_, _] =>
  214. try {
  215. format(f.asInstanceOf[Function1[Object, _]](this))
  216. } catch {
  217. case e: ClassCastException =>
  218. v
  219. }
  220. case _ => v
  221. }
  222. /**
  223. * Captures the output of the given block
  224. */
  225. def capture(block: Scope => Unit): String = {
  226. def body(): Unit = block(this)
  227. context.capture(body)
  228. }
  229. def isParam1[T](f: Function1[_, _], clazz: Class[T]): Boolean = {
  230. try {
  231. f.getClass.getMethod("apply", clazz)
  232. true
  233. } catch { case e: NoSuchMethodException => false }
  234. }
  235. }