diff --git a/src/main/scala/com/atomist/event/archive/HandlerArchiveReader.scala b/src/main/scala/com/atomist/event/archive/HandlerArchiveReader.scala index ba5193c27..0282b0c36 100644 --- a/src/main/scala/com/atomist/event/archive/HandlerArchiveReader.scala +++ b/src/main/scala/com/atomist/event/archive/HandlerArchiveReader.scala @@ -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 @@ -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)) + } } } diff --git a/src/main/scala/com/atomist/rug/kind/service/SimpleMessageBuilder.scala b/src/main/scala/com/atomist/rug/kind/service/SimpleMessageBuilder.scala index 250114a41..027d9d32d 100644 --- a/src/main/scala/com/atomist/rug/kind/service/SimpleMessageBuilder.scala +++ b/src/main/scala/com/atomist/rug/kind/service/SimpleMessageBuilder.scala @@ -1,5 +1,6 @@ package com.atomist.rug.kind.service +import java.util import java.util.Collections import com.atomist.tree.TreeNode @@ -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 diff --git a/src/main/scala/com/atomist/rug/kind/service/message.scala b/src/main/scala/com/atomist/rug/kind/service/message.scala index 551ec4145..3ceb23737 100644 --- a/src/main/scala/com/atomist/rug/kind/service/message.scala +++ b/src/main/scala/com/atomist/rug/kind/service/message.scala @@ -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 /** diff --git a/src/main/scala/com/atomist/rug/runtime/js/JavaScriptEventHandler.scala b/src/main/scala/com/atomist/rug/runtime/js/JavaScriptEventHandler.scala index 3f8fbde40..0d68a49cf 100644 --- a/src/main/scala/com/atomist/rug/runtime/js/JavaScriptEventHandler.scala +++ b/src/main/scala/com/atomist/rug/runtime/js/JavaScriptEventHandler.scala @@ -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( @@ -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 @@ -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 @@ -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(pathExpression: string, handler: (m: ContextMatch) => void): void - - handlerFunction.call("apply", args:_*) + protected def invokeHandlerFunction(e: SystemEvent, cm: ContextMatch): Object = { + handlerFunction.call("apply", cm) } } diff --git a/src/main/scala/com/atomist/rug/runtime/js/JavaScriptHandlerFinder.scala b/src/main/scala/com/atomist/rug/runtime/js/JavaScriptHandlerFinder.scala index 5f4aa090e..db5f4116d 100644 --- a/src/main/scala/com/atomist/rug/runtime/js/JavaScriptHandlerFinder.scala +++ b/src/main/scala/com/atomist/rug/runtime/js/JavaScriptHandlerFinder.scala @@ -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. @@ -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, @@ -26,4 +36,52 @@ 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 + 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) + if(name != null && description != null && handle != null && expression != null){ + 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) + } } + diff --git a/src/main/scala/com/atomist/rug/runtime/js/JavaScriptOperationFinder.scala b/src/main/scala/com/atomist/rug/runtime/js/JavaScriptOperationFinder.scala index 98aecf7f5..f5bf1dc63 100644 --- a/src/main/scala/com/atomist/rug/runtime/js/JavaScriptOperationFinder.scala +++ b/src/main/scala/com/atomist/rug/runtime/js/JavaScriptOperationFinder.scala @@ -35,7 +35,6 @@ object JavaScriptOperationFinder { */ def fromJavaScriptArchive(rugAs: ArtifactSource, context: JavaScriptContext = null): Seq[ProjectOperation] = { - val jsc: JavaScriptContext = if (context == null) new JavaScriptContext() @@ -43,8 +42,7 @@ object JavaScriptOperationFinder { context jsc.load(rugAs) - val operations = operationsFromVars(rugAs, jsc) - operations + operationsFromVars(rugAs, jsc) } diff --git a/src/main/scala/com/atomist/rug/runtime/js/interop/JavaScriptHandlerContext.scala b/src/main/scala/com/atomist/rug/runtime/js/interop/JavaScriptHandlerContext.scala new file mode 100644 index 000000000..90c5f7d5b --- /dev/null +++ b/src/main/scala/com/atomist/rug/runtime/js/interop/JavaScriptHandlerContext.scala @@ -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 + ) +} diff --git a/src/main/scala/com/atomist/rug/runtime/js/interop/NamedJavaScriptEventHandler.scala b/src/main/scala/com/atomist/rug/runtime/js/interop/NamedJavaScriptEventHandler.scala new file mode 100644 index 000000000..f29234824 --- /dev/null +++ b/src/main/scala/com/atomist/rug/runtime/js/interop/NamedJavaScriptEventHandler.scala @@ -0,0 +1,139 @@ +package com.atomist.rug.runtime.js.interop + +import com.atomist.event.{HandlerContext, SystemEvent} +import com.atomist.param.Tag +import com.atomist.rug.RugRuntimeException +import com.atomist.rug.kind.DefaultTypeRegistry +import com.atomist.rug.kind.service._ +import com.atomist.rug.runtime.js.JavaScriptEventHandler +import com.atomist.source.ArtifactSource +import com.atomist.tree.TreeNode +import com.atomist.tree.content.text.SimpleMutableContainerTreeNode +import jdk.nashorn.api.scripting.ScriptObjectMirror + +import scala.collection.JavaConverters._ + +/** + * Like super, except that we require a proper name, description, tags etc. + * and we wrap the match in an Event + */ +class NamedJavaScriptEventHandler(pathExpressionStr: String, + handlerFunction: ScriptObjectMirror, + thiz: ScriptObjectMirror, + rugAs: ArtifactSource, + ctx: JavaScriptHandlerContext, + _name: String, + _description: String, + _tags: Seq[Tag] = Nil) + extends JavaScriptEventHandler(pathExpressionStr, handlerFunction, rugAs, ctx.treeMaterializer, ctx.pathExpressionEngine) { + + override def name: String = _name + + override def tags: Seq[Tag] = _tags + + override def description: String = _description + + override def handle(e: SystemEvent, s2: ServiceSource): Unit = { + val smv = new ServicesMutableView(rugAs, s2) + val handlerContext = HandlerContext(smv) + val np = nodePreparer(handlerContext) + + val targetNode = ctx.treeMaterializer.rootNodeFor(e, pathExpression) + // Put a new artificial root above to make expression work + val root = new SimpleMutableContainerTreeNode("root", Seq(targetNode), null, null) + ctx.pathExpressionEngine.ee.evaluate(root, pathExpression, DefaultTypeRegistry, Some(np)) match { + case Right(Nil) => + case Right(matches) => + val cm = ContextMatch( + targetNode, + ctx.pathExpressionEngine.wrap(matches), + s2, + teamId = e.teamId) + dispatch(invokeHandlerFunction(e, cm)) + + case Left(failure) => + throw new RugRuntimeException(pathExpressionStr, + s"Error evaluating path expression $pathExpression: [$failure]") + } + } + + override protected def invokeHandlerFunction(e: SystemEvent, cm: ContextMatch): Object = { + handlerFunction.call(thiz, Event(cm)) + } + + /** + * Extract all messages and use messageBuilder/actionRegistry to find and dispatch actions + * @param plan + */ + def dispatch(plan: Object): Unit = { + plan match { + case o: ScriptObjectMirror => { + o.get("messages") match { + case messages: ScriptObjectMirror if messages.isArray => { + messages.values().asScala.foreach(msg => { + val m = msg.asInstanceOf[ScriptObjectMirror] + var responseMessage = ctx.messageBuilder.regarding(m.get("regarding").asInstanceOf[TreeNode]) + responseMessage =m.get("text") match { + case text: String => responseMessage.say(text) + case _ => responseMessage + } + responseMessage = m.get("channelId") match { + case c: String => responseMessage.on(c) + case _ => responseMessage + } + responseMessage = m.get("rugs") match { + case rugs: ScriptObjectMirror if rugs.isArray => { + addActions(responseMessage, rugs) + } + case _ => responseMessage + } + responseMessage.send() + }) + } + } + } + } + } + + /** + * Extract action from JS and bind to Message + * + * Beware use of var! + * + * @param msg current message + * @param rugs array of Rugs + * @return new message + */ + def addActions(msg: Message, rugs: ScriptObjectMirror) : Message = { + var responseMessage = msg + for (rug <- rugs.values().asScala) { + val r = rug.asInstanceOf[ScriptObjectMirror] + //TODO - this is a reimplementation of the @cd's label hack - but at least it's not in TS + val actionName = r.get("label") match { + case label: String => s"${r.get("name").asInstanceOf[String]}|$label" + case _ => r.get("name").asInstanceOf[String] + } + var action = responseMessage.actionRegistry.findByName(actionName) + r.get("params") match { + case params: ScriptObjectMirror => { + for (param <- params.entrySet().asScala) { + action = responseMessage.actionRegistry.bindParameter(action, param.getKey, param.getValue) + } + } + case _ => + } + responseMessage = msg.withAction(action) + } + responseMessage + } +} + + +/** + * Represents an event that drives a handler + * + * @param cm the root node in the tree + */ +case class Event(cm: ContextMatch) { + def child: TreeNode = cm.root.asInstanceOf[TreeNode] +} diff --git a/src/main/scala/com/atomist/rug/runtime/rugdsl/RugOperationSupport.scala b/src/main/scala/com/atomist/rug/runtime/rugdsl/RugOperationSupport.scala index 1cb51a16e..87ab2fd0e 100644 --- a/src/main/scala/com/atomist/rug/runtime/rugdsl/RugOperationSupport.scala +++ b/src/main/scala/com/atomist/rug/runtime/rugdsl/RugOperationSupport.scala @@ -24,7 +24,7 @@ import scala.collection.JavaConverters._ object RugOperationSupport { - val YmlFormat = DateTimeFormatter.ofPattern("MMM d yyyy") + val YmlFormat: DateTimeFormatter = DateTimeFormatter.ofPattern("MMM d yyyy") // May have been passed in via the infrastructure but couldn't be declared in Rug: Suppress // so it doesn't upset binding into JavaScript diff --git a/src/main/scala/com/atomist/rug/ts/TypeScriptInterfaceGenerator.scala b/src/main/scala/com/atomist/rug/ts/TypeScriptInterfaceGenerator.scala index 43de10280..d4d2bc3a7 100644 --- a/src/main/scala/com/atomist/rug/ts/TypeScriptInterfaceGenerator.scala +++ b/src/main/scala/com/atomist/rug/ts/TypeScriptInterfaceGenerator.scala @@ -118,24 +118,6 @@ class TypeScriptInterfaceGenerator( val typeSort: (Typed, Typed) => Boolean = (a, b) => a.name <= b.name -// DO NOT DELETE -// private def allInterfaceTypes(allTypes: Seq[Typed]): Seq[InterfaceType] = { -// val methods = allTypes.map(t => t.name -> allMethods(t)).toMap -// val duplicateMethods = methods.values.flatten.groupBy(identity).filter(_ match { -// case (_, lst) => lst.size > 1 -// }).keys.toSeq.sortWith(_.name <= _.name) -// -// val parent = if (duplicateMethods.isEmpty) None else Some( -// InterfaceType(ParentInterface, "TypeScript superinterface", duplicateMethods)) -// -// parent.toList ::: allTypes.map(t => { -// val allMethods = methods.getOrElse(t.name, Nil) -// val uniqueMethods = allMethods diff duplicateMethods -// if (allMethods.size == uniqueMethods.size) InterfaceType(t.name, t.description, allMethods) -// else InterfaceType(t.name, t.description, uniqueMethods, ParentInterface) -// }).toList -// } - private def allInterfaceTypes(allTypes: Seq[Typed]): Seq[InterfaceType] = allTypes.map(t => InterfaceType(t.name, t.description, allMethods(t))) @@ -204,15 +186,6 @@ class TypeScriptInterfaceGenerator( (types -- publishedTypeNames).toSeq.sorted } -// private def findUnpublishedTypes1(publishedTypes: Seq[Typed]): Seq[Typed] = { -// val allOperations = publishedTypes.map(_.typeInformation).collect { -// case st: StaticTypeInformation => st.operations -// }.flatten -// val types = allOperations.map(op => op.definedOn).toSet -// val publishedTypeNames = publishedTypes.map(_.name).toSet -// (types -- publishedTypes).toSeq.sorted -// } - override def modify(as: ArtifactSource, poa: ProjectOperationArguments): ModificationAttempt = { val createdFile = emitInterfaces(poa) val r = as + createdFile diff --git a/src/main/scala/com/atomist/tree/content/text/TreeNodeOperations.scala b/src/main/scala/com/atomist/tree/content/text/TreeNodeOperations.scala index a3b82cd26..31e9e0743 100644 --- a/src/main/scala/com/atomist/tree/content/text/TreeNodeOperations.scala +++ b/src/main/scala/com/atomist/tree/content/text/TreeNodeOperations.scala @@ -7,7 +7,7 @@ import org.springframework.util.ReflectionUtils import scala.collection.mutable.ListBuffer /** - * Pperations on TreeNodes such as tree pruning. + * Operations on TreeNodes such as tree pruning. */ object TreeNodeOperations { diff --git a/src/main/scala/com/atomist/tree/pathexpression/PathExpressionEngine.scala b/src/main/scala/com/atomist/tree/pathexpression/PathExpressionEngine.scala index f2fb7b50d..b0b4f45be 100644 --- a/src/main/scala/com/atomist/tree/pathexpression/PathExpressionEngine.scala +++ b/src/main/scala/com/atomist/tree/pathexpression/PathExpressionEngine.scala @@ -38,5 +38,4 @@ class PathExpressionEngine extends ExpressionEngine { //println(s"Returning $nodesToApplyNextStepTo when evaluating [$parsed] against $node") nodesToApplyNextStepTo } - } diff --git a/src/main/typescript/node_modules/@atomist/rug/operations/Handlers.ts b/src/main/typescript/node_modules/@atomist/rug/operations/Handlers.ts new file mode 100644 index 000000000..73cddda82 --- /dev/null +++ b/src/main/typescript/node_modules/@atomist/rug/operations/Handlers.ts @@ -0,0 +1,94 @@ +import {TreeNode} from "../tree/PathExpression" + +interface RugCoordinate { + group: string + artifact: string + version?: string + name: string +} + +interface Rug { + readonly name: string | RugCoordinate + readonly params: {} + readonly kind: "executor" | "generator" | "editor" +} + +abstract class Executor implements Rug { + abstract name: string + abstract params: {} + kind: "executor" +} + +abstract class Generator implements Rug { + abstract name: string + abstract params: {} + kind: "generator" +} + +abstract class Editor implements Rug { + abstract name: string + abstract params: Object + kind: "editor" +} + +interface Event { + child(): R +} + +abstract class PathExpression { + readonly expression: string + readonly kind: T +} + +interface Handler { + readonly name: string + readonly description: string + readonly expression: PathExpression + readonly tags?: string[] + handle(root: Event): Response +} + +class Response { + private messages: Message[] = []; + + public addMessage(message: Message) : this{ + this.messages.push(message) + return this; + } + + //TODO - we should add this back in when we want to invoke rugs directly without the bot + // private rugs: Rug[] + // public addExecutor(rug: Executor) { + // this.rugs.push(rug) + // return this; + // } +} + +class Message { + text: string; + channelId: string; + regarding: TreeNode; + + private rugs: Rug[] = [] + + constructor(node: TreeNode){ + this.regarding = node + } + + public addExecutor(rug: Executor): this { + this.rugs.push(rug) + return this; + } + + public addEditor(rug: Editor): this { + this.rugs.push(rug) + return this; + } + + public addGenerator(rug: Generator) : this { + this.rugs.push(rug) + return this; + } +} + +export {Handler, Event, Response, Message, Executor, Generator, Editor, PathExpression} diff --git a/src/main/typescript/node_modules/@atomist/rug/tree/PathExpression.ts b/src/main/typescript/node_modules/@atomist/rug/tree/PathExpression.ts index 739cea9fa..b4c3bafef 100644 --- a/src/main/typescript/node_modules/@atomist/rug/tree/PathExpression.ts +++ b/src/main/typescript/node_modules/@atomist/rug/tree/PathExpression.ts @@ -4,7 +4,7 @@ */ interface Match { - root(): R + root(): R matches(): N[] } diff --git a/src/test/scala/com/atomist/event/archive/HandlerArchiveReaderTest.scala b/src/test/scala/com/atomist/event/archive/HandlerArchiveReaderTest.scala index 085fb1925..077d5038b 100644 --- a/src/test/scala/com/atomist/event/archive/HandlerArchiveReaderTest.scala +++ b/src/test/scala/com/atomist/event/archive/HandlerArchiveReaderTest.scala @@ -5,6 +5,7 @@ import com.atomist.plan.TreeMaterializer import com.atomist.project.archive.{AtomistConfig, DefaultAtomistConfig} import com.atomist.rug.TestUtils import com.atomist.rug.kind.service.ConsoleMessageBuilder +import com.atomist.rug.runtime.js.interop.NamedJavaScriptEventHandlerTest import com.atomist.source.{SimpleFileBasedArtifactSource, StringFileArtifact} import com.atomist.tree.TreeNode import com.atomist.tree.pathexpression.PathExpression @@ -56,6 +57,14 @@ class HandlerArchiveReaderTest extends FlatSpec with Matchers { handlers.exists(h => h.rootNodeName == "commit") should be(true) } + it should "parse single new-style handler" in { + val har = new HandlerArchiveReader(treeMaterializer, atomistConfig) + val handlers = har.handlers("XX", TestUtils.compileWithModel(new SimpleFileBasedArtifactSource("", NamedJavaScriptEventHandlerTest.reOpenCloseIssueProgram)), None, Nil, + new ConsoleMessageBuilder("XX", null)) + handlers.size should be(1) + handlers.head.rootNodeName should be("issue") + } + object TestTreeMaterializer extends TreeMaterializer { override def rootNodeFor(e: SystemEvent, pe: PathExpression): TreeNode = ??? diff --git a/src/test/scala/com/atomist/project/archive/ProjectOperationArchiveReaderTest.scala b/src/test/scala/com/atomist/project/archive/ProjectOperationArchiveReaderTest.scala index fdcf00e70..650cf341c 100644 --- a/src/test/scala/com/atomist/project/archive/ProjectOperationArchiveReaderTest.scala +++ b/src/test/scala/com/atomist/project/archive/ProjectOperationArchiveReaderTest.scala @@ -4,6 +4,7 @@ import com.atomist.project.SimpleProjectOperationArguments import com.atomist.rug.{Import, TestUtils} import com.atomist.rug.exec.FakeServiceSource import com.atomist.rug.runtime.js.TypeScriptRugEditorTest +import com.atomist.rug.runtime.js.interop.NamedJavaScriptEventHandlerTest import com.atomist.rug.runtime.lang.js.NashornConstructorTest import com.atomist.source.{SimpleDirectoryArtifact, SimpleFileBasedArtifactSource, StringFileArtifact} import org.scalatest.{FlatSpec, Matchers} @@ -167,12 +168,12 @@ class ProjectOperationArchiveReaderTest extends FlatSpec with Matchers { val f2 = StringFileArtifact("app/Thing.js", "var Thing = {};") - val rugAs = TestUtils.addUserModel(SimpleFileBasedArtifactSource( + val rugAs = SimpleFileBasedArtifactSource( StringFileArtifact(".atomist/editors/SimpleEditor.js", NashornConstructorTest.SimpleJavascriptEditor), f1, f2 - )) + ) + TestUtils.user_model @@ -221,4 +222,29 @@ class ProjectOperationArchiveReaderTest extends FlatSpec with Matchers { result.findFile("src/from/typescript").get.content.contains("Anders") should be(true) } + it should "ignore unbound handler in the new style" in { + val apc = new ProjectOperationArchiveReader(atomistConfig) + val f1 = StringFileArtifact("package.json", "{}") + val f2 = StringFileArtifact("app/Thing.ts", "class Thing {}") + + + val rugAs = TestUtils.compileWithModel(SimpleFileBasedArtifactSource( + StringFileArtifact(".atomist/editors/SimpleGenerator.ts", + TypeScriptRugEditorTest.SimpleGenerator), + f1, + f2, + NamedJavaScriptEventHandlerTest.reOpenCloseIssueProgram + )) + val ops = apc.findOperations(rugAs, None, Nil) + ops.generators.size should be(1) + ops.generators.head.parameters.size should be(0) + val result = ops.generators.head.generate(SimpleProjectOperationArguments.Empty) + // Should preserve content from the backing archive + result.findFile(f1.path).get.content.equals(f1.content) should be(true) + result.findFile(f2.path).get.content.equals(f2.content) should be(true) + + // Should contain new contain + result.findFile("src/from/typescript").get.content.contains("Anders") should be(true) + } + } diff --git a/src/test/scala/com/atomist/rug/TestUtils.scala b/src/test/scala/com/atomist/rug/TestUtils.scala index 0d5139a6f..44aead648 100644 --- a/src/test/scala/com/atomist/rug/TestUtils.scala +++ b/src/test/scala/com/atomist/rug/TestUtils.scala @@ -2,13 +2,14 @@ package com.atomist.rug import java.io.File -import com.atomist.project.ProjectOperationArguments +import com.atomist.project.{ProjectOperationArguments, SimpleProjectOperationArguments} import com.atomist.project.archive.{AtomistConfig, DefaultAtomistConfig} import com.atomist.project.edit.{ModificationAttempt, ProjectEditor, SuccessfulModification} import com.atomist.rug.compiler.typescript.TypeScriptCompiler import com.atomist.rug.kind.DefaultTypeRegistry +import com.atomist.rug.ts.TypeScriptInterfaceGenerator import com.atomist.source.file.{FileSystemArtifactSource, FileSystemArtifactSourceIdentifier} -import com.atomist.source.ArtifactSource +import com.atomist.source.{ArtifactSource, SimpleFileBasedArtifactSource} import jdk.nashorn.api.scripting.ScriptObjectMirror import org.scalatest.Matchers @@ -24,7 +25,6 @@ object TestUtils extends Matchers { attemptModification(program, as, backingAs, poa, pipeline) match { case sm: SuccessfulModification => - // show(sm.result) sm.result } } @@ -41,18 +41,21 @@ object TestUtils extends Matchers { pe.modify(as, poa) } - // This brings in a node_modules directory that was copied there by a maven goal called copy-ts, which takes it from src/main/typescript - val user_model = new FileSystemArtifactSource(FileSystemArtifactSourceIdentifier(new File("target/.atomist"))).withPathAbove(".atomist") val compiler = new TypeScriptCompiler() - def compileWithModel(tsAs: ArtifactSource) : ArtifactSource = { - compiler.compile(addUserModel(tsAs)) + // This brings in a node_modules directory that was copied there by a maven goal called copy-ts, which takes it from src/main/typescript + val user_model: ArtifactSource = { + + val generator = new TypeScriptInterfaceGenerator + val output = generator.generate(SimpleProjectOperationArguments("", Map(generator.OutputPathParam -> "Core.ts"))) + val src = new FileSystemArtifactSource(FileSystemArtifactSourceIdentifier(new File("src/main/typescript"))) + + val compiled = compiler.compile(src.underPath("node_modules/@atomist").withPathAbove(".atomist") + output.withPathAbove(".atomist/rug/model")) + compiled.underPath(".atomist").withPathAbove(".atomist/node_modules/@atomist") } - //work around for atomist/artifact-source#16 - def addUserModel(as: ArtifactSource) : ArtifactSource = { - user_model.allFiles.foldLeft(as)((acc: ArtifactSource, fa) => { - acc + fa - }) + + def compileWithModel(tsAs: ArtifactSource) : ArtifactSource = { + compiler.compile(user_model + tsAs) } } diff --git a/src/test/scala/com/atomist/rug/runtime/HandlerTest.scala b/src/test/scala/com/atomist/rug/runtime/HandlerTest.scala index d41b36ea6..df43b9941 100644 --- a/src/test/scala/com/atomist/rug/runtime/HandlerTest.scala +++ b/src/test/scala/com/atomist/rug/runtime/HandlerTest.scala @@ -5,7 +5,7 @@ import java.util.Collections import com.atomist.rug.TestUtils import com.atomist.rug.kind.service.{ConsoleMessageBuilder, EmptyActionRegistry} import com.atomist.rug.runtime.js.JavaScriptContext -import com.atomist.rug.runtime.js.interop.{AtomistFacade, Match, jsPathExpressionEngine} +import com.atomist.rug.runtime.js.interop.{AtomistFacade, Match, NamedJavaScriptEventHandlerTest, jsPathExpressionEngine} import com.atomist.source.{SimpleFileBasedArtifactSource, StringFileArtifact} import com.atomist.tree.SimpleTerminalTreeNode import jdk.nashorn.api.scripting.ScriptObjectMirror @@ -46,6 +46,14 @@ class HandlerTest extends FlatSpec with Matchers { } } + + it should "find and invoke other style of handler" in { + + val r = TestUtils.compileWithModel(SimpleFileBasedArtifactSource(NamedJavaScriptEventHandlerTest.reOpenCloseIssueProgram)) + val jsc = new JavaScriptContext() + + jsc.load(r) + } } diff --git a/src/test/scala/com/atomist/rug/runtime/js/interop/NamedJavaScriptEventHandlerTest.scala b/src/test/scala/com/atomist/rug/runtime/js/interop/NamedJavaScriptEventHandlerTest.scala new file mode 100644 index 000000000..6ace0ec49 --- /dev/null +++ b/src/test/scala/com/atomist/rug/runtime/js/interop/NamedJavaScriptEventHandlerTest.scala @@ -0,0 +1,113 @@ +package com.atomist.rug.runtime.js.interop + +import java.util + +import com.atomist.event.SystemEvent +import com.atomist.event.archive.HandlerArchiveReader +import com.atomist.param.{ParameterValue, SimpleParameterValue} +import com.atomist.plan.TreeMaterializer +import com.atomist.project.archive.{AtomistConfig, DefaultAtomistConfig} +import com.atomist.rug.TestUtils +import com.atomist.rug.kind.service.{Action, ActionRegistry, Callback, ConsoleMessageBuilder, Rug} +import com.atomist.source.{SimpleFileBasedArtifactSource, StringFileArtifact} +import com.atomist.tree.TreeNode +import com.atomist.tree.pathexpression.PathExpression +import com.atomist.util.Visitor +import org.scalatest.{FlatSpec, Matchers} + +import scala.collection.JavaConverters._ + +object NamedJavaScriptEventHandlerTest { + val atomistConfig: AtomistConfig = DefaultAtomistConfig + val treeMaterializer: TreeMaterializer = TestTreeMaterializer + + val reOpenCloseIssueProgram = StringFileArtifact(atomistConfig.handlersRoot + "/Handler.ts", + s""" + |import {Handler, Response, Message, Event} from '@atomist/rug/operations/Handlers' + |import {OpenIssues, Issue, ReopenIssue} from '@atomist/rug/model/Issues' + | + |export let simpleHandler: Handler = { + | name: "ClosedIssueReopener", + | description: "Reopens closed issues", + | tags: ["github", "issues"], + | expression: OpenIssues, + | handle(event: Event){ + | let issue = event.child() + | return new Response() + | .addMessage(new Message(issue) + | .addExecutor(new ReopenIssue("Reopen") + | .withNumber(issue.number()) + | .withRepo(issue.repo()) + | .withOwner(issue.owner()))) + | } + |} + """.stripMargin) +} + +class NamedJavaScriptEventHandlerTest extends FlatSpec with Matchers{ + + import NamedJavaScriptEventHandlerTest._ + + + it should "extract and run a handler based on new style" in { + val har = new HandlerArchiveReader(treeMaterializer, atomistConfig) + val handlers = har.handlers("XX", TestUtils.compileWithModel(new SimpleFileBasedArtifactSource("", reOpenCloseIssueProgram)), None, Nil, + new ConsoleMessageBuilder("XX", SimpleActionRegistry)) + handlers.size should be(1) + val handler = handlers.head + handler.rootNodeName should be("issue") + handler.handle(SysEvent,null) + } +} + +object SimpleActionRegistry extends ActionRegistry { + + val rug = Rug("executor", "group", "artifact", "version", "ReopenIssue") + + override def findByName(name: String): Action = Action(name, Callback(rug), new util.ArrayList[ParameterValue]()) + + + override def bindParameter(action: Action, name: String, value: Object) = { + val params = new util.ArrayList[ParameterValue](action.parameters) + params.add(SimpleParameterValue(name,value)) + Action(action.title,action.callback,params) + } +} + +object SysEvent extends SystemEvent ("blah", "issue", 0l) + +class IssueTreeNode extends TreeNode { + /** + * Name of the node. This may vary with individual nodes: For example, + * with files. However, node names do not always need to be unique. + * + * @return name of the individual node + */ + override def nodeName: String = "issue" + + /** + * All nodes have values: Either a terminal value or the + * values built up from subnodes. + */ + override def value: String = "blah" + + + def state(): String = "closed" + + val number: Int = 10 + + val repo: String = "rug" + + val owner: String = "atomist" + + override def accept(v: Visitor, depth: Int): Unit = ??? +} + +object TestTreeMaterializer extends TreeMaterializer { + + override def rootNodeFor(e: SystemEvent, pe: PathExpression): TreeNode = new IssueTreeNode() + + override def hydrate(teamId: String, rawRootNode: TreeNode, pe: PathExpression): TreeNode = rawRootNode +} + +