Permalink
Browse files

fixes #264 to get stand alone coffee files compiling to .js files and…

… added a few example jade files using embedded or external coffeescript
  • Loading branch information...
1 parent 71398de commit 96c723cc95b69c85b67e955f69b907536426cbec @jstrachan jstrachan committed Sep 13, 2011
View
4 project/build.properties
@@ -11,6 +11,6 @@ def.scala.version=2.7.7
#def.scala.version=2.8.1.RC1
project.version=1.6-SNAPSHOT
-scala.version=2.9.0
-build.scala.versions=2.9.0
+scala.version=2.9.1
+build.scala.versions=2.9.1
project.initialize=false
View
8 samples/scalate-example/src/main/webapp/coffee/embedded.jade
@@ -0,0 +1,8 @@
+h1 Embedded CoffeeScript demo
+
+p This should demonstrate some CoffeeScript being compiled on the server to JavaScript and included inside a Jade page
+
+:coffeescript
+ alert "Hello, Coffee embedded!"
+
+p You should get an alert :)
View
7 samples/scalate-example/src/main/webapp/coffee/external.jade
@@ -0,0 +1,7 @@
+h1 External CoffeeScript demo
+
+p This should demonstrate some CoffeeScript being compiled on the server to JavaScript and referenced inside a Jade page
+
+script(src="foo.js" type="text/javascript")
+
+p You should get an alert :)
View
1 samples/scalate-example/src/main/webapp/coffee/foo.coffee
@@ -0,0 +1 @@
+alert "Hello, from included Coffee JavaScript file!"
View
10 samples/scalate-example/src/main/webapp/coffee/index.jade
@@ -0,0 +1,10 @@
+h1 CoffeeScript Examples
+
+p Here are a few samples of using CoffeeScript and SASS/SCSS with Scalate using Jade
+
+ul
+ li
+ a(href="embedded") Embedded CoffeeScript inside Jade
+ li
+ a(href="external") External CoffeeScript referenced in Jade
+
View
2 samples/scalate-example/src/main/webapp/index.jade
@@ -39,4 +39,6 @@ h2 Miscellaneous Examples
ul
li
+ a(href="coffee/index") CoffeeScript samples
+ li
a(href="tableWithLayout") Jade Table With Custom Layout
View
29 scalate-core/src/main/scala/org/fusesource/scalate/TemplateEngine.scala
@@ -177,6 +177,19 @@ class TemplateEngine(var sourceDirectories: Traversable[File] = None, var mode:
var pipelines: Map[String, List[Filter]] = Map()
+ /**
+ * Maps file extensions to possible template extensions for custom mappins such as for
+ * Map("js" -> Set("coffee"), "css" => Set("sass", "scss"))
+ */
+ var extensionToTemplateExtension: collection.mutable.Map[String, collection.mutable.Set[String]] = collection.mutable.Map()
+
+ /**
+ * Returns the mutable set of template extensions which are mapped to the given URI extension.
+ */
+ def templateExtensionsFor(extension: String): collection.mutable.Set[String] = {
+ extensionToTemplateExtension.getOrElseUpdate(extension, collection.mutable.Set())
+ }
+
private val attempt = Exception.ignoring(classOf[Throwable])
/**
@@ -187,12 +200,16 @@ class TemplateEngine(var sourceDirectories: Traversable[File] = None, var mode:
// Attempt to load all the built in filters.. Some may not load do to missing classpath
// dependencies.
- attempt( filters += "plain" -> PlainFilter )
- attempt( filters += "javascript"-> JavascriptFilter )
- attempt( filters += "coffeescript"-> CoffeeScriptFilter )
- attempt( filters += "css"-> CssFilter )
- attempt( filters += "cdata"-> CdataFilter )
- attempt( filters += "escaped"->EscapedFilter )
+ attempt(filters += "plain" -> PlainFilter)
+ attempt(filters += "javascript" -> JavascriptFilter)
+ attempt(filters += "coffeescript" -> CoffeeScriptFilter)
+ attempt(filters += "css" -> CssFilter)
+ attempt(filters += "cdata" -> CdataFilter)
+ attempt(filters += "escaped" -> EscapedFilter)
+
+ attempt{
+ CoffeeScriptPipeline(this)
+ }
var layoutStrategy: LayoutStrategy = NullLayoutStrategy
View
78 scalate-core/src/main/scala/org/fusesource/scalate/filter/CoffeeScriptFilter.scala
@@ -26,7 +26,7 @@ import java.util.concurrent.atomic.AtomicBoolean
/**
* Surrounds the filtered text with <script> and CDATA tags.
- *
+ *
* <p>Useful for including inline Javascript.</p>
*
* @author <a href="http://hiramchirino.com">Hiram Chirino</a>
@@ -62,7 +62,7 @@ object CoffeeScriptFilter extends Filter with Log {
if (serverSideCompile) {
try {
- Compiler.compile(content, Some(context.currentTemplate)).fold({
+ CoffeeScriptCompiler.compile(content, Some(context.currentTemplate)).fold({
error =>
warn("Could not compile coffeescript: " + error, error)
throw new CompilerException(error.message, Nil)
@@ -83,22 +83,48 @@ object CoffeeScriptFilter extends Filter with Log {
clientSideCompile
}
}
+}
+
+/**
+ * Compiles a .coffee file into JS on the server side
+ */
+object CoffeeScriptPipeline extends Filter with Log {
/**
- * A Scala / Rhino Coffeescript compiler.
+ * Installs the coffeescript pipeline
*/
- object Compiler {
-
- /**
- * Compiles a string of Coffeescript code to Javascript.
- *
- * @param code the Coffeescript code
- * @param sourceName a descriptive name for the code unit under compilation (e.g a filename)
- * @param bare if true, no function wrapper will be generated
- * @return the compiled Javascript code
- */
- def compile(code: String, sourceName: Option[String] = None, bare : Boolean = false)
- : Either[CompilationError, String] = withContext { ctx =>
+ def apply(engine: TemplateEngine) {
+ engine.pipelines += "coffee" -> List(NoLayoutFilter(this, "text/javascript"))
+ engine.templateExtensionsFor("js") += "coffee"
+ }
+
+ def filter(context: RenderContext, content: String) = {
+ CoffeeScriptCompiler.compile(content, Some(context.currentTemplate)).fold({
+ error =>
+ warn("Could not compile coffeescript: " + error, error)
+ throw new CompilerException(error.message, Nil)
+ }, {
+ coffee => coffee
+ })
+ }
+}
+
+/**
+ * A Scala / Rhino Coffeescript compiler.
+ */
+object CoffeeScriptCompiler {
+
+ /**
+ * Compiles a string of Coffeescript code to Javascript.
+ *
+ * @param code the Coffeescript code
+ * @param sourceName a descriptive name for the code unit under compilation (e.g a filename)
+ * @param bare if true, no function wrapper will be generated
+ * @return the compiled Javascript code
+ */
+ def compile(code: String, sourceName: Option[String] = None, bare: Boolean = false)
+ : Either[CompilationError, String] = withContext {
+ ctx =>
val scope = ctx.initStandardObjects()
ctx.evaluateReader(
scope,
@@ -113,22 +139,20 @@ object CoffeeScriptFilter extends Filter with Log {
try {
Right(compileFunc.call(ctx, scope, coffee, Array(code, opts)).asInstanceOf[String])
} catch {
- case e : JavaScriptException =>
+ case e: JavaScriptException =>
Left(CompilationError(sourceName, e.getValue.toString))
}
- }
+ }
- def withContext[T](f: Context => T): T = {
- val ctx = Context.enter()
- try {
- ctx.setOptimizationLevel(-1) // Do not compile to byte code (max 64kb methods)
- f(ctx)
- } finally {
- Context.exit()
- }
+ def withContext[T](f: Context => T): T = {
+ val ctx = Context.enter()
+ try {
+ ctx.setOptimizationLevel(-1) // Do not compile to byte code (max 64kb methods)
+ f(ctx)
+ } finally {
+ Context.exit()
}
}
-
- case class CompilationError(sourceName: Option[String], message: String)
}
+case class CompilationError(sourceName: Option[String], message: String)
View
15 scalate-core/src/main/scala/org/fusesource/scalate/filter/Filter.scala
@@ -18,6 +18,8 @@
package org.fusesource.scalate
package filter
+import servlet.ServletRenderContext
+
/**
* Represents a request to filter content.
*
@@ -34,3 +36,16 @@ trait Filter {
def filter(context: RenderContext, content: String): String
}
+/**
+ * A useful filter for wrapping other filters as a Pipeline (a top level processor of stand alone resources)
+ */
+case class NoLayoutFilter(val next: Filter, contentType: String) extends Filter {
+ def filter(context: RenderContext, content: String) = {
+ context.attributes("layout") = "" // disable the layout
+ context match {
+ case x: ServletRenderContext => x.response.setContentType(contentType)
+ case _ =>
+ }
+ next.filter(context, content)
+ }
+}
View
44 scalate-core/src/main/scala/org/fusesource/scalate/support/TemplateFinder.scala
@@ -18,6 +18,8 @@
package org.fusesource.scalate.support
import org.fusesource.scalate.TemplateEngine
+import org.fusesource.scalate.util.Files
+
/**
* A helper object to find a template from a URI using a number of possible extensions and directories
*/
@@ -29,9 +31,9 @@ class TemplateFinder(engine: TemplateEngine) {
def findTemplate(path: String): Option[String] = {
var rc = Option(engine.finderCache.get(path))
- if( rc.isEmpty ) {
+ if (rc.isEmpty) {
rc = search(path)
- if( rc.isDefined && !engine.isDevelopmentMode ) {
+ if (rc.isDefined && !engine.isDevelopmentMode) {
engine.finderCache.put(path, rc.get)
}
}
@@ -41,7 +43,7 @@ class TemplateFinder(engine: TemplateEngine) {
def search(rootOrPath: String): Option[String] = {
val path = if (rootOrPath == "/") "/index" else rootOrPath
- for( p <- hiddenPatterns ; if p.findFirstIn(path).isDefined ) {
+ for (p <- hiddenPatterns; if p.findFirstIn(path).isDefined) {
return None
}
@@ -72,16 +74,40 @@ class TemplateFinder(engine: TemplateEngine) {
// Lets try to find the template by replacing the extension
// i.e: /path/page.html -> /path/page.jade
def findReplaced(): Option[String] = {
- replacedExtensions.foreach {ext =>
- if (path.endsWith(ext)) {
- val rc = findAppended(path.stripSuffix(ext))
- if (rc != None)
- return rc
+ replacedExtensions.foreach {
+ ext =>
+ if (path.endsWith(ext)) {
+ val rc = findAppended(path.stripSuffix(ext))
+ if (rc != None)
+ return rc
+ }
+ }
+ None
+ }
+
+ // Lets try to find the template for well known template extensions
+ // i.e:
+ // /path/page.css -> List(/path/page.sass, /path/page.scss)
+ // /path/page.js -> List(/path/page.coffee)
+ def findTemplateAlias(uri: String): Option[String] = {
+ val ext = Files.extension(uri)
+ lazy val remaining = path.stripSuffix(ext)
+ if (ext.size > 0) {
+ engine.extensionToTemplateExtension.get(ext) match {
+ case Some(set) =>
+ for (base <- engine.templateDirectories; ext <- set) {
+ val path = base + remaining + ext
+ if (engine.resourceLoader.exists(path)) {
+ return Some(path)
+ }
+ }
+ None
+ case _ => None
}
}
None
}
- findDirect(path).orElse(findAppended(path).orElse(findReplaced()))
+ findDirect(path).orElse(findAppended(path).orElse(findTemplateAlias(path).orElse(findReplaced())))
}
}
View
1 scalate-core/src/test/resources/org/fusesource/scalate/filter/sample.coffee
@@ -0,0 +1 @@
+alert "Hello, Coffee!"
View
23 scalate-core/src/test/scala/org/fusesource/scalate/filter/CoffeeScriptPipelineTest.scala
@@ -0,0 +1,23 @@
+package org.fusesource.scalate.filter
+
+import org.fusesource.scalate.TemplateTestSupport
+import org.fusesource.scalate.util.ResourceLoader
+import org.fusesource.scalate.support.TemplateFinder
+
+class CoffeeScriptPipelineTest extends TemplateTestSupport {
+
+ lazy val finder = new TemplateFinder(engine)
+
+ test("coffeescript pipeline") {
+ assertUriOutputContains("/org/fusesource/scalate/filter/sample.js",
+"""(function() {
+ alert("Hello, Coffee!");
+}).call(this);
+""")
+ }
+
+ override protected def fromUri(uri: String) = {
+ val newUri = finder.findTemplate(uri).getOrElse(uri)
+ super.fromUri(newUri)
+ }
+}
View
19 scalate-jruby/src/main/scala/org/fusesource/scalate/jruby/Sass.scala
@@ -4,7 +4,7 @@ import org.fusesource.scalate.util.Log
import org.fusesource.scalate.{TemplateException, RenderContext, TemplateEngine, TemplateEngineAddOn}
import java.io.File
import org.fusesource.scalate.servlet.ServletRenderContext
-import org.fusesource.scalate.filter.{CssFilter, Pipeline, Filter}
+import org.fusesource.scalate.filter.{NoLayoutFilter, CssFilter, Pipeline, Filter}
/**
* <p>
@@ -19,28 +19,19 @@ object Sass extends TemplateEngineAddOn with Log {
if (!te.filters.contains("sass")) {
val sass = new Sass(jruby, te)
te.filters += "sass" -> Pipeline(List(sass, CssFilter))
- te.pipelines += "sass" -> List(NoLayout(sass))
+ te.pipelines += "sass" -> List(NoLayoutFilter(sass, "text/css"))
+ te.templateExtensionsFor("css") += "sass"
}
if (!te.filters.contains("scss")) {
val scss = new Scss(jruby, te)
te.filters += "scss" -> Pipeline(List(scss, CssFilter))
- te.pipelines += "scss" -> List(NoLayout(scss))
+ te.pipelines += "scss" -> List(NoLayoutFilter(scss, "text/css"))
+ te.templateExtensionsFor("css") += "scss"
}
}
}
-case class NoLayout(val next: Sass) extends Filter {
- def filter(context: RenderContext, content: String) = {
- context.attributes("layout")="" // disable the layout
- context match {
- case x:ServletRenderContext => x.response.setContentType("text/css")
- case _ =>
- }
- next.filter(context, content)
- }
-}
-
class Sass(val jruby:JRuby, val engine: TemplateEngine) extends Filter {
def syntax = "sass"

0 comments on commit 96c723c

Please sign in to comment.