Skip to content

Commit

Permalink
Moved to sbt 13 and updated for a lot of JavaScript support
Browse files Browse the repository at this point in the history
  • Loading branch information
dpp committed May 25, 2015
1 parent b5b83a6 commit a2828fd
Show file tree
Hide file tree
Showing 15 changed files with 476 additions and 63 deletions.
1 change: 1 addition & 0 deletions .gitignore
Expand Up @@ -54,3 +54,4 @@ lift_proto*
# Pax Runner (for easy OSGi launching)
runner

.history
29 changes: 23 additions & 6 deletions build.sbt
@@ -1,9 +1,10 @@
import AssemblyKeys._ // put this at the top of the file
// import AssemblyKeys._ // put this at the top of the file
import sbtassembly.AssemblyPlugin.autoImport._

//Project Information
name := "Hoisted"

scalaVersion := "2.11.4"
scalaVersion := "2.11.6"

scalacOptions += "-deprecation"

Expand All @@ -19,7 +20,7 @@ organization := "org.hoisted"

// seq(webSettings :_*)

// resolvers += "Scala Snapshots" at "https://oss.sonatype.org/content/repositories/snapshots/"
resolvers += "Scala Snapshots" at "https://oss.sonatype.org/content/repositories/snapshots/"

resolvers += "Scala" at "https://oss.sonatype.org/content/groups/scala-tools/"

Expand All @@ -30,7 +31,7 @@ version := "0.3-SNAPSHOT"
// crossScalaVersions in ThisBuild := Seq("2.9.2", "2.10.0") // "2.9.1-1", "2.9.1", "2.9.0-1", "2.9.0")

libraryDependencies ++= {
val liftVersion = "3.0-M4"
val liftVersion = "3.0-SNAPSHOT"
Seq(
"net.liftweb" %% "lift-webkit" % liftVersion % "compile",
"net.liftweb" %% "lift-common" % liftVersion % "compile",
Expand Down Expand Up @@ -58,9 +59,25 @@ publishTo <<= version { (v: String) =>
Some("releases" at nexus + "service/local/staging/deploy/maven2")
}

assemblySettings
// assemblySettings

jarName in assembly := "hoisted.jar"
assemblyMergeStrategy in assembly := {
case x if x == "META-INF/MANIFEST.MF" => MergeStrategy.discard
case x if x.endsWith(".class") => MergeStrategy.first
case x => MergeStrategy.concat
}

//{
// case PathList("javax", "servlet", xs @ _*) => MergeStrategy.first
// case PathList(ps @ _*) if ps.last endsWith ".html" => MergeStrategy.first
// case "application.conf" => MergeStrategy.concat
// case "unwanted.txt" => MergeStrategy.discard
// case x =>
// val oldStrategy = (assemblyMergeStrategy in assembly).value
// oldStrategy(x)
//}

assemblyJarName in assembly := "hoisted.jar"

test in assembly := {}

Expand Down
2 changes: 2 additions & 0 deletions project/assembly.sbt
@@ -0,0 +1,2 @@
addSbtPlugin("com.eed3si9n" % "sbt-assembly" % "0.13.0")

2 changes: 1 addition & 1 deletion project/plugins.sbt
Expand Up @@ -13,4 +13,4 @@ resolvers += "Web plugin repo" at "http://siasia.github.com/maven2"

resolvers += Resolver.url("artifactory", url("http://scalasbt.artifactoryonline.com/scalasbt/sbt-plugin-releases"))(Resolver.ivyStylePatterns)

addSbtPlugin("com.eed3si9n" % "sbt-assembly" % "0.9.2")
// addSbtPlugin("com.eed3si9n" % "sbt-assembly" % "0.13.0")
Binary file removed sbt-launch.jar
Binary file not shown.
Binary file removed sbt12-launch.jar
Binary file not shown.
2 changes: 1 addition & 1 deletion src/main/scala/org/hoisted/lib/EnvironmentManager.scala
Expand Up @@ -701,7 +701,7 @@ class EnvironmentManager() extends LazyLoggableWithImplicitLogger with Factory {
def syntheticFiles: List[ParsedFile] => Seq[ParsedFile] = fileset => {
val synt404 = fileset.filter(_.fileInfo.pathAndSuffix.path == List("404")) match {
case Nil => List(MarkdownFile(FileInfo(Empty, "/404.html", "404", "404.html", Some("html")),
<div>So sorry... the page you're looking for isn't found. :-(</div>, NullMetadataValue))
<div>So sorry... the page you're looking for isn't found. :-(</div>, NullMetadataValue, null))
case _ => Nil
}

Expand Down
1 change: 1 addition & 0 deletions src/main/scala/org/hoisted/lib/Hoist.scala
Expand Up @@ -23,6 +23,7 @@ object Hoist extends LazyLoggableWithImplicitLogger {
val em = new EnvironmentManager()

HoistedEnvironmentManager.doWith(em) {
JSContext.install()
val info = slurpParams(args.toList)

info.classInfo match {
Expand Down
286 changes: 286 additions & 0 deletions src/main/scala/org/hoisted/lib/JSContext.scala
@@ -0,0 +1,286 @@
package org.hoisted.lib

import net.liftweb.common.Box
import net.liftweb.common.Full
import net.liftweb.http.{LiftSession, TransientRequestVar, LiftRules}
import net.liftweb.util._
import Helpers._
import org.mozilla.javascript._
import scala.util.Success
import scala.xml.NodeSeq
import net.liftweb.actor.LAFuture
import net.liftweb.common._

/**
* You can add a JavaScript context to Lift so that
* you can run server-side JavaScript as part of Lift page
* rendering.
*
* In Boot.scala, just do `JavaScriptContext.install()`
* and you get a JavaScript execution context around
* all HTTP requests.
*/
object JSContext {

def runSourceContext(session: LiftSession, value: Any, xform: Any => NodeSeq => NodeSeq, ns: NodeSeq): NodeSeq = {
import scala.collection.JavaConversions._
value match {
case null => NodeSeq.Empty
case None => NodeSeq.Empty
case _: EmptyBox => NodeSeq.Empty
case b: Box[_] => runSourceContext(session, b.toList, xform, ns)
case b: Option[_] => runSourceContext(session, b.toList, xform, ns)
case fut: LAFuture[_] => runSourceContext(session, fut.get(5.seconds).openOr(Empty), xform, ns)
case node: scala.xml.Node => session.currentSourceContext.doWith(node)(session.processSurroundAndInclude("Source", xform(node)(ns)))
case na: org.mozilla.javascript.NativeArray =>
val len = na.getLength.toInt
val ar = new Array[Object](len)
var pos = 0
while (pos < len) {
ar(pos) = na.get(pos, na)
pos += 1
}
runSourceContext(session, ar.toList, xform, ns)
case n: java.lang.Iterable[_] => runSourceContext(session, n.iterator(), xform, ns)
case n: java.util.Iterator[_] =>
for {i <- n.toSeq; nodes <- session.currentSourceContext.doWith(i)(session.processSurroundAndInclude("Source", xform(i)(ns)))} yield nodes
case en: java.util.Enumeration[_] =>
for {i <- en.toSeq; nodes <- session.currentSourceContext.doWith(i)(session.processSurroundAndInclude("Source", xform(i)(ns)))} yield nodes
case se: scala.collection.Iterable[_] => runSourceContext(session, se.iterator, xform, ns)
case se: scala.collection.Iterator[_] =>
for {i <- se.toSeq; nodes <- session.currentSourceContext.doWith(i)(session.processSurroundAndInclude("Source", xform(i)(ns)))} yield nodes
case a: Array[_] => runSourceContext(session, a.toList, xform, ns)
case x =>
session.currentSourceContext.doWith(x)(session.processSurroundAndInclude("Source", xform(x)(ns)))
}
}

def buildXformer(session: LiftSession, xformRule: String, value: Any): NodeSeq => NodeSeq = {


def retFunc(value: Any)(ns: NodeSeq): NodeSeq = {
val func: NodeSeq => NodeSeq =
value match {
case n: scala.xml.Node => xformRule #> n
case ns: NodeSeq => xformRule #> ns
case ns: Seq[_] if !ns.exists(!_.isInstanceOf[scala.xml.Node]) => xformRule #> ns.asInstanceOf[NodeSeq]
case n: String => xformRule #> n
// case b: Bindable => xformRule #> b
case n: java.lang.Number => xformRule #> n
case d: Double => xformRule #> d
case jc: ToJsCmd => xformRule #> jc
case i: Int => xformRule #> i
case sb: StringPromotable => xformRule #> sb
case sym: Symbol => xformRule #> sym
case lng: Long => xformRule #> lng
case b: Boolean => xformRule #> b
case b: Box[_] => b match {
case Full(x) => buildXformer(session, xformRule, x)
case _ => xformRule #> Empty.asInstanceOf[Box[String]]
}
case b: Option[_] => b match {
case Some(x) => buildXformer(session, xformRule, x)
case _ => xformRule #> None.asInstanceOf[Option[String]]
}

case fut: LAFuture[_] => buildXformer(session, xformRule, fut.get(5 second))
case n: java.lang.Iterable[_] => runSourceContext(session, n.iterator(), retFunc _, _)
case n: java.util.Iterator[_] => runSourceContext(session, n, retFunc _, _)
case en: java.util.Enumeration[_] => runSourceContext(session, en, retFunc _, _)
case se: scala.collection.Iterable[_] => runSourceContext(session, se, retFunc _, _)
case se: scala.collection.Iterator[_] => runSourceContext(session, se, retFunc _, _)
case x => xformRule #> x.toString
}

func(ns)
}

retFunc(value) _
}

/**
* Hook into LiftRules to put a JavaScript
* execution loanwrapper around everything and
* also slurp in <script> tags with the data-lift-server attribute.
*/
def install() {
LiftRules.allAround.append(JSWrapper)
LiftRules.tagProcessor.prepend {
case ("script", e, session) if e.attribute("data-server-js").isDefined =>
exec(e.text)
NodeSeq.Empty
}

LiftRules.dataAttributeProcessor.append {
case ("js", value, elem, session) =>
currentScript.get.withIt(session.currentSourceContext.get) {
val (rule, v2): (NodeSeq => NodeSeq, Box[String]) =
value.roboSplit("\\#\\>") match {
case x :: Nil => (PassThru, Full(x))
// case x :: "it" :: Nil => session.buildXformer(x, Nil) -> Empty
case x :: str :: Nil =>
buildXformer(session, x, exec(str)) -> Empty
case x :: xs => buildXformer(session, x, Nil) -> Full(xs.mkString)
case _ => (PassThru, Full(value))

}


v2 match {
case Full(v22) =>
exec(v22) match {
case fut: LAFuture[_] => val ret = new LAFuture[NodeSeq]
fut.foreach(v => ret.satisfy(session.runSourceContext(v, rule, elem)))
ret

case func: Function0[_] =>
() => {
session.runSourceContext(func(), rule, elem)
}

case x => session.runSourceContext(x, rule, elem)
}

case _ => rule(elem)
}
}

}
}

object currentScript extends TransientRequestVar[JSScope]({
new JSScope
}) {
registerCleanupFunc(in => get.bye())
}

private object JSWrapper extends LoanWrapper {
def apply[T](f: => T): T = {
currentScript.get
f
}
}

/**
* Execute some JavaScript in the current context
* @param str the string to execute
* @return the value returned from the JavaScript execution
*/
def exec(str: String): AnyRef = currentScript.get.exec(str)

class JSScope {
private var initted = false
private var context: Context = null
private var scope: ScriptableObject = null

def theScope() = scope

def theContext() = context

def withIt[T](currentIt: Any)(f: => T): T = {
val cur = scope.get("it")
scope.put("it", scope, currentIt)
try {
f
} finally {
if (null eq cur) {
scope.delete("it")
} else {
scope.put("it", scope, cur)
}
}
}

def init() {
initted = true
context = Context.enter()
scope = context.initStandardObjects()
scope.put("currentFile", scope, CurrentFile)
scope.put("fileParser", scope, ParsedFile)
scope.put("help", scope, JSHelper)
}

def bye() {
if (initted) Context.exit()
}

def exec(str: String): AnyRef = synchronized {
if (!initted) init()

context.evaluateString(scope, str, "Lift", 0, null) match {
case njo: NativeJavaObject => njo.unwrap()
case x => x
}
}
}

}

object JSHelper {
def toJs(in: Any): Any = {
val scope = JSContext.currentScript.get.theScope()
val context = JSContext.currentScript.get.theContext()
def _toJs(in: Any): Any = {
in match {
case x: Seq[_] =>
val nobj = context.newArray(scope, 0).asInstanceOf[NativeArray]
x.zipWithIndex.foreach { case (v, i) => nobj.put(i, nobj, _toJs(v)) }
nobj

case x: java.util.List[_] =>
val nobj = context.newArray(scope, 0).asInstanceOf[NativeArray]
var n = 0
val it = x.iterator()
while (it.hasNext) {
nobj.put(n, nobj, _toJs(it.next()))
n += 1
}

nobj

case it: java.util.Iterator[_] =>
val nobj = context.newArray(scope, 0).asInstanceOf[NativeArray]
var n = 0
while (it.hasNext) {
nobj.put(n, nobj ,_toJs(it.next()))
n += 1
}

nobj

case m: Map[_, _] =>
val nobj = context.newObject(scope).asInstanceOf[ScriptableObject]
m.foreach { case (k, v) => nobj.defineProperty(_toJs(k).toString, _toJs(v), 0) }
nobj

case m: java.util.Map[_, _] =>
val nobj = context.newObject(scope).asInstanceOf[ScriptableObject]
val it = m.entrySet().iterator()
while (it.hasNext) {
val n = it.next()
val k = n.getKey
val v = n.getValue
nobj.defineProperty(_toJs(k).toString, _toJs(v), 0)
}
nobj

case Full(x) => _toJs(x)

case x: EmptyBox => null

case Some(x) => _toJs(x)

case None => null

case Success(x) => _toJs(x)

case _: scala.util.Failure[_] => null

case x => x
}
}

_toJs(in)

}
}

0 comments on commit a2828fd

Please sign in to comment.