/scalate-core/src/main/scala/org/fusesource/scalate/filter/CoffeeScriptFilter.scala

http://github.com/scalate/scalate · Scala · 139 lines · 78 code · 14 blank · 47 comment · 3 complexity · 28744b9d097d10b6c8d7185faf26c67a 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
  19. package filter
  20. import java.util.concurrent.atomic.AtomicBoolean
  21. import javax.script.ScriptException
  22. import org.fusesource.scalate.support.RenderHelper
  23. import org.fusesource.scalate.util.Log
  24. import tv.cntt.rhinocoffeescript.Compiler
  25. /**
  26. * Surrounds the filtered text with <script> and CDATA tags.
  27. *
  28. * <p>Useful for including inline Javascript.</p>
  29. *
  30. * @author <a href="http://hiramchirino.com">Hiram Chirino</a>
  31. */
  32. object CoffeeScriptFilter extends Filter with Log {
  33. /**
  34. * Server side compilation of coffeescript is enabled by default. Disable this flag
  35. * if you want to disable it (for example to avoid the optional dependency on rhino)
  36. */
  37. val serverSideCompile = true
  38. protected val warnedMissingRhino = new AtomicBoolean()
  39. def filter(context: RenderContext, content: String) = {
  40. def clientSideCompile: String = {
  41. context.attributes("REQUIRES_COFFEE_SCRIPT_JS") = "true"
  42. """<script type='text/coffeescript'>
  43. | //<![CDATA[
  44. | """.stripMargin + RenderHelper.indent(" ", content) + """
  45. | //]]>
  46. |</script>""".stripMargin
  47. }
  48. def missingRhino(e: Throwable): String = {
  49. // we don't have rhino on the classpath
  50. // so lets do client side compilation
  51. if (warnedMissingRhino.compareAndSet(false, true)) {
  52. warn("No Rhino on the classpath: " + e + ". Using client side CoffeeScript compile", e)
  53. }
  54. clientSideCompile
  55. }
  56. if (serverSideCompile) {
  57. try {
  58. CoffeeScriptCompiler.compile(content, Some(context.currentTemplate)).fold({
  59. error =>
  60. warn("Could not compile coffeescript: " + error, error)
  61. throw new CompilerException(error.message, Nil)
  62. }, {
  63. coffee =>
  64. """<script type='text/javascript'>
  65. | //<![CDATA[
  66. | """.stripMargin + RenderHelper.indent(" ", coffee) + """
  67. | //]]>
  68. |</script>""".stripMargin
  69. })
  70. } catch {
  71. case e: NoClassDefFoundError => missingRhino(e)
  72. case e: ClassNotFoundException => missingRhino(e)
  73. }
  74. } else {
  75. clientSideCompile
  76. }
  77. }
  78. }
  79. /**
  80. * Compiles a .coffee file into JS on the server side
  81. */
  82. object CoffeeScriptPipeline extends Filter with Log {
  83. /**
  84. * Installs the coffeescript pipeline
  85. */
  86. def apply(engine: TemplateEngine): Unit = {
  87. engine.pipelines += "coffee" -> List(NoLayoutFilter(this, "text/javascript"))
  88. engine.templateExtensionsFor("js") += "coffee"
  89. }
  90. def filter(context: RenderContext, content: String) = {
  91. CoffeeScriptCompiler.compile(content, Some(context.currentTemplate)).fold({
  92. error =>
  93. warn("Could not compile coffeescript: " + error, error)
  94. throw new CompilerException(error.message, Nil)
  95. }, {
  96. coffee => coffee
  97. })
  98. }
  99. }
  100. /**
  101. * A Scala / Rhino Coffeescript compiler.
  102. */
  103. object CoffeeScriptCompiler {
  104. /**
  105. * Compiles a string of Coffeescript code to Javascript.
  106. *
  107. * @param code the Coffeescript code
  108. * @param sourceName a descriptive name for the code unit under compilation (e.g a filename)
  109. * @param bare if true, no function wrapper will be generated
  110. * @return the compiled Javascript code
  111. */
  112. def compile(code: String, sourceName: Option[String] = None): Either[CompilationError, String] =
  113. {
  114. try {
  115. Right(Compiler.compile(code))
  116. } catch {
  117. case e: ScriptException =>
  118. val line = e.getLineNumber
  119. val column = e.getColumnNumber
  120. val message = "CoffeeScript syntax error at %d:%d".format(line, column)
  121. Left(CompilationError(sourceName, message))
  122. }
  123. }
  124. }
  125. case class CompilationError(sourceName: Option[String], message: String)