Skip to content
This repository has been archived by the owner on Mar 19, 2019. It is now read-only.

Commit

Permalink
Handler JS/TS programming model as per #105
Browse files Browse the repository at this point in the history
  • Loading branch information
kipz committed Jan 19, 2017
1 parent f495be2 commit 83d9b46
Show file tree
Hide file tree
Showing 21 changed files with 579 additions and 74 deletions.
8 changes: 8 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,11 @@ and this project adheres to [Semantic Versioning](http://semver.org/).

[Unreleased]: https://github.com/atomist/rug/compare/0.10.0...HEAD

### Added

- Support for a new TS (JS) Handler programming model as per
https://github.com/atomist/rug/issues/105

### Fixed

- TS generators are now passed project name as second argument as per TS contract
Expand All @@ -21,6 +26,9 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
- **BREAKING** signature of TypeScript ProjectGenerator.populate() changed: parameter
`projectName` got removed. Name of the generated project can be obtained via `project.name()`.

- Core.ts is generated and compiled on-the-fly during unit-testing so that
the build is not dependent on network or later maven phases until deployment

## [0.10.0]

[0.10.0]: https://github.com/atomist/rug/compare/0.9.0...0.10.0
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import com.atomist.rug.kind.DefaultTypeRegistry
import com.atomist.rug.kind.dynamic.{DefaultViewFinder, ViewFinder}
import com.atomist.rug.kind.service.MessageBuilder
import com.atomist.rug.runtime.js.JavaScriptHandlerFinder
import com.atomist.rug.runtime.js.interop.ModelBackedAtomistFacade
import com.atomist.rug.runtime.js.interop.{JavaScriptHandlerContext, ModelBackedAtomistFacade}
import com.atomist.rug.runtime.rugdsl.DefaultEvaluator
import com.atomist.rug.spi.TypeRegistry
import com.atomist.source.ArtifactSource
Expand Down Expand Up @@ -38,7 +38,12 @@ class HandlerArchiveReader(
messageBuilder: MessageBuilder): Seq[SystemEventHandler] = {
val atomist = new ModelBackedAtomistFacade(teamId, messageBuilder, treeMaterializer)
JavaScriptHandlerFinder.registerHandlers(rugArchive, atomist)
atomist.handlers
val handlers = atomist.handlers
if(handlers.nonEmpty){
handlers
}else{
JavaScriptHandlerFinder.fromJavaScriptArchive(rugArchive, new JavaScriptHandlerContext(teamId,treeMaterializer, messageBuilder))
}
}
}

Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.atomist.rug.kind.service

import java.util
import java.util.Collections

import com.atomist.tree.TreeNode
Expand Down Expand Up @@ -27,7 +28,7 @@ case class ImmutableMessage(
node: TreeNode = null,
message: String = null,
address: String = null,
actions: java.util.List[Action] = Collections.emptyList())
actions: java.util.List[Action] = new util.ArrayList[Action]())
extends Message {

// We use null for interop and JSON
Expand Down
4 changes: 1 addition & 3 deletions src/main/scala/com/atomist/rug/kind/service/message.scala
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
package com.atomist.rug.kind.service

import java.util

import com.atomist.param.{ParameterValue, SimpleParameterValue}
import com.atomist.param.{ParameterValue}
import com.atomist.tree.TreeNode

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -130,16 +130,16 @@ class JavaScriptContext(allowedClasses: Set[String] = Set.empty[String], atomist
def getFile(s: String): String = {
val file = artifacts.findFile(getPath + s)
if (file.isEmpty) return null
//remove these source-map comments because they seem to be breaking nashorn :/
val withoutComments = commentPattern.matcher(file.get.content).replaceAll("")
if(atomistConfig.isAtomistSource(file.get)){
//remove these source-map comments because they seem to be breaking nashorn :/
val withoutComments = commentPattern.matcher(file.get.content).replaceAll("")
//add export for those vars without them. TODO should be removed at some point once all have moved over!
val js = new StringBuilder(withoutComments)
append(varPattern, withoutComments, js)
append(letPattern, withoutComments, js)
js.toString()
}else{
file.get.content
withoutComments
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import com.atomist.rug.kind.service.{ServiceSource, ServicesMutableView}
import com.atomist.rug.runtime.js.interop.{ContextMatch, jsPathExpressionEngine}
import com.atomist.source.ArtifactSource
import com.atomist.tree.content.text.SimpleMutableContainerTreeNode
import com.atomist.tree.pathexpression.{NamedNodeTest, PathExpressionParser}
import com.atomist.tree.pathexpression.{NamedNodeTest, PathExpression, PathExpressionParser}
import jdk.nashorn.api.scripting.ScriptObjectMirror

class JavaScriptEventHandler(
Expand All @@ -27,7 +27,7 @@ class JavaScriptEventHandler(

override def description: String = name

val pathExpression = PathExpressionParser.parsePathExpression(pathExpressionStr)
val pathExpression: PathExpression = PathExpressionParser.parsePathExpression(pathExpressionStr)

override val rootNodeName: String = pathExpression.locationSteps.head.test match {
case nnt: NamedNodeTest => nnt.name
Expand All @@ -36,7 +36,7 @@ class JavaScriptEventHandler(

import com.atomist.tree.pathexpression.ExpressionEngine.NodePreparer

private def nodePreparer(hc: HandlerContext): NodePreparer = {
protected def nodePreparer(hc: HandlerContext): NodePreparer = {
case mca: ModelContextAware =>
mca.setContext(hc)
mca
Expand All @@ -53,29 +53,20 @@ class JavaScriptEventHandler(
val root = new SimpleMutableContainerTreeNode("root", Seq(targetNode), null, null)
pexe.ee.evaluate(root, pathExpression, DefaultTypeRegistry, Some(np)) match {
case Right(Nil) =>
println("Nothing to do: No nodes found")
case Right(matches) =>
val cm = ContextMatch(
targetNode,
//matches.map(m => m.asInstanceOf[Object]).asJava,
pexe.wrap(matches),
s2,
teamId = e.teamId)
invokeHandlerFunction(e, cm)
//as.persist()
case Left(failure) =>
throw new RugRuntimeException(pathExpressionStr,
s"Error evaluating path expression $pathExpression: [$failure]")
}
}

protected def invokeHandlerFunction(e: SystemEvent, cm: ContextMatch): Unit = {
//val context = new ResultsMutableView(rugAs, hc.servicesMutableView, as)
val args = Seq(cm)

// Signature is
// on<R,N>(pathExpression: string, handler: (m: ContextMatch<R,N>) => void): void

handlerFunction.call("apply", args:_*)
protected def invokeHandlerFunction(e: SystemEvent, cm: ContextMatch): Object = {
handlerFunction.call("apply", cm)
}
}
Original file line number Diff line number Diff line change
@@ -1,8 +1,18 @@
package com.atomist.rug.runtime.js

import com.atomist.event.SystemEventHandler
import com.atomist.param.Tag
import com.atomist.plan.TreeMaterializer
import com.atomist.project.archive.{AtomistConfig, DefaultAtomistConfig}
import com.atomist.rug.runtime.js.interop.AtomistFacade
import com.atomist.rug.kind.service.TeamContext
import com.atomist.rug.runtime.js.interop._
import com.atomist.source.ArtifactSource
import com.atomist.tree.pathexpression.PathExpressionEngine
import jdk.nashorn.api.scripting.ScriptObjectMirror

import scala.util.Try
import scala.collection.JavaConverters._


/**
* Finds and evaluates handlers in a Rug archive.
Expand All @@ -15,7 +25,7 @@ object JavaScriptHandlerFinder {
* @param rugAs archive to look into
* @param atomist facade to Atomist
* @return a sequence of instantiated operations backed by JavaScript
*
*/
def registerHandlers(rugAs: ArtifactSource,
atomist: AtomistFacade,
Expand All @@ -26,4 +36,51 @@ object JavaScriptHandlerFinder {
jsc.engine.put("atomist", atomist)
jsc.load(rugAs)
}

def fromJavaScriptArchive(rugAs: ArtifactSource,
ctx: JavaScriptHandlerContext,
context: JavaScriptContext = null): Seq[SystemEventHandler] = {
val jsc: JavaScriptContext =
if (context == null)
new JavaScriptContext()
else
context

jsc.load(rugAs)
handlersFromVars(rugAs, jsc, ctx)
}

private def handlersFromVars(rugAs: ArtifactSource, jsc: JavaScriptContext, ctx: JavaScriptHandlerContext): Seq[SystemEventHandler] = {
jsc.vars.foldLeft(Seq[SystemEventHandler]())((acc: Seq[SystemEventHandler], jsVar) => {
val obj = jsVar.scriptObjectMirror
if(obj.hasMember("name") && obj.hasMember("description") && obj.hasMember("handle") && obj.hasMember("expression")){
val name = obj.getMember("name").asInstanceOf[String]
val description = obj.getMember("description").asInstanceOf[String]
val handle = obj.getMember("handle").asInstanceOf[ScriptObjectMirror]
val expression = obj.getMember("expression") match {
case x: String => x
case o: ScriptObjectMirror => o.getMember("expression").asInstanceOf[String]
case _ => Nil.asInstanceOf[String]
}
val tags = readTagsFromMetadata(obj)
acc :+ new NamedJavaScriptEventHandler(expression, handle, obj, rugAs, ctx, name, description,tags)
}else{
acc
}
})
}

protected def readTagsFromMetadata(someVar: ScriptObjectMirror): Seq[Tag] = {
Try {
someVar.getMember("tags") match {
case som: ScriptObjectMirror =>
val stringValues = som.values().asScala collect {
case s: String => s
}
stringValues.map(s => Tag(s, s)).toSeq
case _ => Nil
}
}.getOrElse(Nil)
}
}

Original file line number Diff line number Diff line change
Expand Up @@ -35,16 +35,14 @@ object JavaScriptOperationFinder {
*/
def fromJavaScriptArchive(rugAs: ArtifactSource,
context: JavaScriptContext = null): Seq[ProjectOperation] = {

val jsc: JavaScriptContext =
if (context == null)
new JavaScriptContext()
else
context

jsc.load(rugAs)
val operations = operationsFromVars(rugAs, jsc)
operations
operationsFromVars(rugAs, jsc)
}


Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package com.atomist.rug.runtime.js.interop

import com.atomist.plan.TreeMaterializer
import com.atomist.rug.kind.service.{MessageBuilder, TeamContext}
import com.atomist.tree.pathexpression.PathExpressionEngine

class JavaScriptHandlerContext(val teamId: String,
_treeMaterializer: TreeMaterializer,
_messageBuilder: MessageBuilder)

extends UserModelContext with TeamContext {

val pathExpressionEngine = new jsPathExpressionEngine(teamContext = this, ee = new PathExpressionEngine)

val messageBuilder: MessageBuilder = _messageBuilder

override val treeMaterializer: TreeMaterializer = _treeMaterializer

override def registry: Map[String, Object] = Map(
"PathExpressionEngine" -> pathExpressionEngine
)
}
Loading

0 comments on commit 83d9b46

Please sign in to comment.