diff --git a/arrow/src/main/scala-2.12/com/opticdev/arrow/changes/ChangeGroup.scala b/arrow/src/main/scala-2.12/com/opticdev/arrow/changes/ChangeGroup.scala index 08d234ca56..de88365564 100644 --- a/arrow/src/main/scala-2.12/com/opticdev/arrow/changes/ChangeGroup.scala +++ b/arrow/src/main/scala-2.12/com/opticdev/arrow/changes/ChangeGroup.scala @@ -3,6 +3,7 @@ package com.opticdev.arrow.changes import play.api.libs.json.{JsArray, JsObject, JsValue, Json} import JsonImplicits.changeGroupFormat import com.opticdev.arrow.changes.evaluation.{BatchedChanges, Evaluation} +import com.opticdev.arrow.state.NodeKeyStore import com.opticdev.core.sourcegear.SourceGear import com.opticdev.core.sourcegear.project.OpticProject @@ -10,11 +11,11 @@ import scala.util.Try case class ChangeGroup(changes: OpticChange*) { - def evaluate(sourcegear: SourceGear, project: Option[OpticProject] = None): BatchedChanges = { + def evaluate(sourcegear: SourceGear, project: Option[OpticProject] = None)(implicit nodeKeyStore: NodeKeyStore): BatchedChanges = { Evaluation.forChangeGroup(this, sourcegear, project) } - def evaluateAndWrite(sourcegear: SourceGear, project: Option[OpticProject] = None) : Try[BatchedChanges] = Try { + def evaluateAndWrite(sourcegear: SourceGear, project: Option[OpticProject] = None)(implicit nodeKeyStore: NodeKeyStore) : Try[BatchedChanges] = Try { val evaluated = evaluate(sourcegear, project) if (evaluated.isSuccess) { evaluated.flushToDisk diff --git a/arrow/src/main/scala-2.12/com/opticdev/arrow/changes/JsonImplicits.scala b/arrow/src/main/scala-2.12/com/opticdev/arrow/changes/JsonImplicits.scala index bd9f4a73ca..c285416572 100644 --- a/arrow/src/main/scala-2.12/com/opticdev/arrow/changes/JsonImplicits.scala +++ b/arrow/src/main/scala-2.12/com/opticdev/arrow/changes/JsonImplicits.scala @@ -4,10 +4,11 @@ import com.opticdev.arrow.changes.location.{AsChildOf, InsertLocation, RawPositi import com.opticdev.arrow.graph.KnowledgeGraphImplicits.{DirectTransformation, TransformationChanges} import com.opticdev.arrow.results.ModelOption import com.opticdev.common.PackageRef +import com.opticdev.core.sourcegear.sync.FilePatch import com.opticdev.sdk.descriptions.transformation.Transformation import com.opticdev.sdk.descriptions.{Schema, SchemaRef} import play.api.libs.json._ - +import com.opticdev.common.fileFormat import scala.util.{Failure, Success, Try} object JsonImplicits { @@ -17,7 +18,6 @@ object JsonImplicits { import SchemaRef.schemaRefFormats - implicit val schemaFormat = new Format[Schema] { override def reads(json: JsValue) = { val schemaJson = json.as[JsObject] @@ -29,17 +29,6 @@ object JsonImplicits { override def writes(o: Schema) = o.toJson } - //File - implicit val fileFormat = new Format[File] { - override def reads(json: JsValue) = { - JsSuccess(Try(File(json.as[JsString].value)).get) - } - - override def writes(o: File) = { - JsString(o.pathAsString) - } - } - implicit val modelOptionsFormat = Json.format[ModelOption] //Location @@ -66,6 +55,11 @@ object JsonImplicits { //Clear Search Lines implicit val clearSearchLinesFormat = Json.format[ClearSearchLines] + //Put Update + implicit val putUpdateFormat = Json.format[PutUpdate] + //File Contents Update + implicit val fileContentsUpdateFormat = Json.format[FileContentsUpdate] + implicit val opticChangeFormat = Json.format[OpticChange] implicit val changeGroupFormat = new Format[ChangeGroup] { diff --git a/arrow/src/main/scala-2.12/com/opticdev/arrow/changes/OpticChange.scala b/arrow/src/main/scala-2.12/com/opticdev/arrow/changes/OpticChange.scala index df0d9da741..e49b5f64b8 100644 --- a/arrow/src/main/scala-2.12/com/opticdev/arrow/changes/OpticChange.scala +++ b/arrow/src/main/scala-2.12/com/opticdev/arrow/changes/OpticChange.scala @@ -9,6 +9,9 @@ import com.opticdev.sdk.descriptions.{Schema, SchemaRef} import play.api.libs.json.{JsObject, JsString, JsValue, Json} import JsonImplicits.opticChangeFormat import com.opticdev.arrow.results.ModelOption +import com.opticdev.core.sourcegear.graph.model.LinkedModelNode +import com.opticdev.core.sourcegear.sync.{FilePatch, FilePatchTrait, SyncPatch} +import com.opticdev.parsers.graph.CommonAstNode sealed trait OpticChange { def asJson = Json.toJson[OpticChange](this) @@ -47,9 +50,14 @@ case class RunTransformation(transformationChanges: TransformationChanges, override def asJson = Json.toJson[OpticChange](this) } + case class RawInsert(content: String, position: RawPosition) extends OpticChange case class ClearSearchLines(file: File, prefixPattern: String = "^\\s*\\/\\/\\/.*") extends OpticChange { import JsonImplicits.clearSearchLinesFormat val regex = prefixPattern.r -} \ No newline at end of file +} + +case class PutUpdate(id: String, newModel: JsObject) extends OpticChange + +case class FileContentsUpdate(file: File, originalFileContents: String, newFileContents: String) extends OpticChange with FilePatchTrait \ No newline at end of file diff --git a/arrow/src/main/scala-2.12/com/opticdev/arrow/changes/evaluation/Evaluation.scala b/arrow/src/main/scala-2.12/com/opticdev/arrow/changes/evaluation/Evaluation.scala index 3d03129f5d..69c9ecabad 100644 --- a/arrow/src/main/scala-2.12/com/opticdev/arrow/changes/evaluation/Evaluation.scala +++ b/arrow/src/main/scala-2.12/com/opticdev/arrow/changes/evaluation/Evaluation.scala @@ -2,19 +2,22 @@ package com.opticdev.arrow.changes.evaluation import better.files.File import com.opticdev.arrow.changes._ +import com.opticdev.arrow.state.NodeKeyStore import com.opticdev.core.sourcegear.{Render, SourceGear} -import com.opticdev.core.sourcegear.project.OpticProject +import com.opticdev.core.sourcegear.project.{OpticProject, ProjectBase} import com.opticdev.core.sourcegear.project.monitoring.FileStateMonitor import com.opticdev.marvin.common.helpers.LineOperations import com.opticdev.sdk.RenderOptions import com.opticdev.sdk.descriptions.transformation.{SingleModel, StagedNode, TransformationResult} import play.api.libs.json.JsObject -import scala.util.Try +import scala.util.{Failure, Success, Try} import com.opticdev.core.sourcegear.context.SDKObjectsResolvedImplicits._ +import com.opticdev.core.sourcegear.mutate.MutationSteps.{collectFieldChanges, combineChanges, handleChanges} +import com.opticdev.core.sourcegear.objects.annotations.{NameAnnotation, ObjectAnnotationRenderer, SourceAnnotation} object Evaluation { - def forChange(opticChange: OpticChange, sourcegear: SourceGear, project: Option[OpticProject] = None)(implicit filesStateMonitor: FileStateMonitor): ChangeResult = opticChange match { + def forChange(opticChange: OpticChange, sourcegear: SourceGear, projectOption: Option[ProjectBase] = None)(implicit filesStateMonitor: FileStateMonitor, nodeKeyStore: NodeKeyStore): ChangeResult = opticChange match { case im: InsertModel => { val stagedNode = StagedNode(im.schema.schemaRef, im.value, Some(RenderOptions( @@ -22,15 +25,10 @@ object Evaluation { ))) val renderedTry = Render.fromStagedNode(stagedNode)(sourcegear) - require(renderedTry.isSuccess, "Could not render model "+ renderedTry.failed.get.toString) - val generatedNode = (renderedTry.get._1, renderedTry.get._2) - val resolvedLocation = im.atLocation.get.resolveToLocation(sourcegear).get - val changeResult = InsertCode.atLocation(generatedNode, im.atLocation.get.file, resolvedLocation) - if (changeResult.isSuccess) { changeResult.asFileChanged.stageContentsIn(filesStateMonitor) } @@ -38,9 +36,7 @@ object Evaluation { changeResult } case rt: RunTransformation => { - val schema = sourcegear.findSchema(rt.transformationChanges.transformation.resolvedOutput(sourcegear)).get - require(rt.inputValue.isDefined, "Transformation must have an input value specified") val transformationTry = rt.transformationChanges.transformation.transformFunction.transform(rt.inputValue.get, rt.answers.getOrElse(JsObject.empty)) @@ -53,21 +49,69 @@ object Evaluation { require(schema.validate(stagedNode.value), "Result of transformation did not conform to schema "+ schema.schemaRef.full) val prefixedFlatContent = sourcegear.flatContext.prefix(rt.transformationChanges.transformation.packageId.packageId) - val generatedNode = Render.fromStagedNode(stagedNode)(sourcegear, prefixedFlatContent).get + val updatedString = if (rt.objectSelection.isDefined) { + val objName = rt.objectSelection.get + ObjectAnnotationRenderer.renderToFirstLine( + generatedNode._3.renderer.parser.get.inlineCommentPrefix, + Vector(SourceAnnotation(objName, rt.transformationChanges.transformation.transformationRef, rt.answers)), + generatedNode._2) + } else generatedNode._2 + val resolvedLocation = rt.location.get.resolveToLocation(sourcegear).get + val changeResult = InsertCode.atLocation((generatedNode._1, updatedString), rt.location.get.file, resolvedLocation) + if (changeResult.isSuccess) { + changeResult.asFileChanged.stageContentsIn(filesStateMonitor) + } + + changeResult - val changeResult = InsertCode.atLocation((generatedNode._1, generatedNode._2), rt.location.get.file, resolvedLocation) + } + case pu: PutUpdate => { + val changeResult : ChangeResult = Try { + val modelNode = nodeKeyStore.lookupId(pu.id).get + implicit val project = projectOption.get + implicit val actorCluster = project.actorCluster + implicit val sourceGearContext = modelNode.getContext.get + val file = modelNode.fileNode.get.toFile + implicit val fileContents = filesStateMonitor.contentsForFile(file).get + val changes = collectFieldChanges(modelNode, pu.newModel).filter(_.isSuccess).map(_.get) + val astChanges = handleChanges(changes) + val combined = combineChanges(astChanges) + val output = combined.toString() + + FileChanged(file, output) + } match { + case s: Success[FileChanged] => s.get + case Failure(ex) => FailedToChange(ex) + } if (changeResult.isSuccess) { changeResult.asFileChanged.stageContentsIn(filesStateMonitor) } changeResult - } + case fileContentsUpdate: FileContentsUpdate => { + + val changeResult = Try { + require(fileContentsUpdate.file.exists, s"File '${fileContentsUpdate.file}' to update must exist") + val currentContents = filesStateMonitor.contentsForFile(fileContentsUpdate.file).get + require(currentContents == fileContentsUpdate.originalFileContents, s"The contents of File '${fileContentsUpdate.file}' have changed since patch was generated. Aborted Patch.") + FileChanged(fileContentsUpdate.file, fileContentsUpdate.newFileContents) + } match { + case s: Success[FileChanged] => s.get + case Failure(ex) => FailedToChange(ex) + } + + if (changeResult.isSuccess) { + changeResult.asFileChanged.stageContentsIn(filesStateMonitor) + } + + changeResult + } //cleanup case cSL: ClearSearchLines => { @@ -94,7 +138,7 @@ object Evaluation { } } - def forChangeGroup(changeGroup: ChangeGroup, sourcegear: SourceGear, project: Option[OpticProject] = None) = { + def forChangeGroup(changeGroup: ChangeGroup, sourcegear: SourceGear, project: Option[OpticProject] = None)(implicit nodeKeyStore: NodeKeyStore) = { implicit val filesStateMonitor : FileStateMonitor = { //hook up to existing in-memory representation of files diff --git a/arrow/src/main/scala-2.12/com/opticdev/arrow/results/TransformationResult.scala b/arrow/src/main/scala-2.12/com/opticdev/arrow/results/TransformationResult.scala index aaf015ade0..ba6d2b6511 100644 --- a/arrow/src/main/scala-2.12/com/opticdev/arrow/results/TransformationResult.scala +++ b/arrow/src/main/scala-2.12/com/opticdev/arrow/results/TransformationResult.scala @@ -16,7 +16,7 @@ import com.opticdev.sdk.descriptions.transformation.Transformation import scala.util.Try import com.opticdev.core.sourcegear.context.SDKObjectsResolvedImplicits._ -case class TransformationResult(score: Int, transformationChange: TransformationChanges, context : ArrowContextBase, inputValue: Option[JsObject])(implicit sourcegear: SourceGear, project: OpticProject, knowledgeGraph: KnowledgeGraph) extends Result { +case class TransformationResult(score: Int, transformationChange: TransformationChanges, context : ArrowContextBase, inputValue: Option[JsObject], objectSelection: Option[String])(implicit sourcegear: SourceGear, project: OpticProject, knowledgeGraph: KnowledgeGraph) extends Result { override def changes : ChangeGroup = transformationChange match { case dt: DirectTransformation => { @@ -25,14 +25,14 @@ case class TransformationResult(score: Int, transformationChange: Transformation def modelOptions = project.projectGraphWrapper.query((node)=> { node.value match { - case mn: BaseModelNode => mn.schemaId == transformationChange.transformation.resolvedInput + case mn: BaseModelNode => mn.schemaId == transformationChange.transformation.resolvedInput && mn.objectRef.isDefined case _ => false } }).asInstanceOf[Set[BaseModelNode]] .map(i=> { implicit val sourceGearContext = TransformationSearch.sourceGearContext(i) val expandedValue = i.expandedValue() - ModelOption(i.id, expandedValue, ModelOption.nameFromValue(i.schemaId.id, expandedValue)) + ModelOption(i.id, expandedValue, i.objectRef.get.name) }).toSeq.sortBy(_.name) ChangeGroup(RunTransformation( @@ -43,7 +43,7 @@ case class TransformationResult(score: Int, transformationChange: Transformation if (insertLocationOption.isDefined) Seq(insertLocationOption.get) else Seq(), //@todo add all location options None, None, - None, + objectSelection, if (inputValue.isDefined) None else Some(modelOptions) )) } diff --git a/arrow/src/main/scala-2.12/com/opticdev/arrow/results/package.scala b/arrow/src/main/scala-2.12/com/opticdev/arrow/results/package.scala index 7c05c7540a..1ca171fcbd 100644 --- a/arrow/src/main/scala-2.12/com/opticdev/arrow/results/package.scala +++ b/arrow/src/main/scala-2.12/com/opticdev/arrow/results/package.scala @@ -17,19 +17,4 @@ package object results { } case class ModelOption(id: String, value: JsObject, name: String) - object ModelOption { - def nameFromValue(schemaId: String, value: JsObject): String = { - - val asStrings = value.fields.sortBy(_._1).map(i=> { - val stringValue = i._2 match { - case o: JsObject => s"{${o.fields.size} fields}" - case a: JsArray => s"{${a.value.size} items}" - case a: JsValue => a.toString() - } - s"${i._1}: ${stringValue}" - }) - - s"${schemaId}(${asStrings.mkString(", ")})" - } - } } diff --git a/arrow/src/main/scala-2.12/com/opticdev/arrow/search/TransformationSearch.scala b/arrow/src/main/scala-2.12/com/opticdev/arrow/search/TransformationSearch.scala index b01e407120..29006b98e0 100644 --- a/arrow/src/main/scala-2.12/com/opticdev/arrow/search/TransformationSearch.scala +++ b/arrow/src/main/scala-2.12/com/opticdev/arrow/search/TransformationSearch.scala @@ -2,16 +2,16 @@ package com.opticdev.arrow.search import com.opticdev.arrow.context.{ArrowContextBase, ModelContext} import com.opticdev.arrow.graph.KnowledgeGraph -import com.opticdev.arrow.results.{GearResult, Result, TransformationResult} -import com.opticdev.core.sourcegear.project.OpticProject -import com.opticdev.core.sourcegear.{CompiledLens, SGContext, SourceGear} -import me.xdrop.fuzzywuzzy.FuzzySearch import com.opticdev.arrow.graph.KnowledgeGraphImplicits._ +import com.opticdev.arrow.results.TransformationResult import com.opticdev.core.sourcegear.actors.ParseSupervisorSyncAccess -import com.opticdev.core.sourcegear.graph.model.{BaseModelNode, ModelNode} +import com.opticdev.core.sourcegear.context.SDKObjectsResolvedImplicits._ +import com.opticdev.core.sourcegear.graph.model.BaseModelNode +import com.opticdev.core.sourcegear.project.OpticProject +import com.opticdev.core.sourcegear.{SGContext, SourceGear} +import me.xdrop.fuzzywuzzy.FuzzySearch import scala.util.Try -import com.opticdev.core.sourcegear.context.SDKObjectsResolvedImplicits._ object TransformationSearch { def search(context: ArrowContextBase)(implicit sourcegear: SourceGear, project: OpticProject, knowledgeGraph: KnowledgeGraph) : Vector[TransformationResult] = @@ -25,7 +25,7 @@ object TransformationSearch { }.getOrElse(c.value) //@todo rank based on usage over time... - transformations.map(t=> TransformationResult(100, t, context, Some(inputValue))) + transformations.map(t=> TransformationResult(100, t, context, Some(inputValue), c.flatten.objectRef.map(_.name))) }) case _ => Vector() } @@ -36,6 +36,7 @@ object TransformationSearch { FuzzySearch.tokenSetPartialRatio(i.yields, query), DirectTransformation(i, i.resolvedOutput), context, + None, None ) }).toVector diff --git a/server/src/main/scala-2.12/com/opticdev/server/state/NodeKeyStore.scala b/arrow/src/main/scala-2.12/com/opticdev/arrow/state/NodeKeyStore.scala similarity index 97% rename from server/src/main/scala-2.12/com/opticdev/server/state/NodeKeyStore.scala rename to arrow/src/main/scala-2.12/com/opticdev/arrow/state/NodeKeyStore.scala index 04ac12ceab..ec970fb584 100644 --- a/server/src/main/scala-2.12/com/opticdev/server/state/NodeKeyStore.scala +++ b/arrow/src/main/scala-2.12/com/opticdev/arrow/state/NodeKeyStore.scala @@ -1,4 +1,4 @@ -package com.opticdev.server.state +package com.opticdev.arrow.state import better.files.File import com.opticdev.core.sourcegear.graph.model.LinkedModelNode diff --git a/arrow/src/test/scala-2.12/com/opticdev/arrow/ExampleSourcegears.scala b/arrow/src/test/scala-2.12/com/opticdev/arrow/ExampleSourcegears.scala index c46f504254..2e2ed8dd3c 100644 --- a/arrow/src/test/scala-2.12/com/opticdev/arrow/ExampleSourcegears.scala +++ b/arrow/src/test/scala-2.12/com/opticdev/arrow/ExampleSourcegears.scala @@ -28,9 +28,9 @@ object ExampleSourcegears { override val lensSet = new LensSet() override val schemas = Set(schemaModel, schemaRoute, schemaForm, schemaFetch) override val transformations = Set( - Transformation("Model -> Route", transformationPackage, schemaModel.schemaRef, schemaRoute.schemaRef, Transformation.emptyAskSchema, ""), - Transformation("Route -> Form", transformationPackage, schemaRoute.schemaRef, schemaForm.schemaRef, Transformation.emptyAskSchema, ""), - Transformation("Route -> Fetch", transformationPackage, schemaRoute.schemaRef, schemaFetch.schemaRef, Transformation.emptyAskSchema, "") + Transformation("Model -> Route", "m2r", transformationPackage, schemaModel.schemaRef, schemaRoute.schemaRef, Transformation.emptyAskSchema, ""), + Transformation("Route -> Form", "r2f", transformationPackage, schemaRoute.schemaRef, schemaForm.schemaRef, Transformation.emptyAskSchema, ""), + Transformation("Route -> Fetch", "r2fe", transformationPackage, schemaRoute.schemaRef, schemaFetch.schemaRef, Transformation.emptyAskSchema, "") ) override val flatContext: FlatContext = FlatContext(None, Map( "optic:test" -> FlatContext(Some(PackageRef("optic:test", "0.1.0")), Map( diff --git a/arrow/src/test/scala-2.12/com/opticdev/arrow/changes/ChangesEvaluationSpec.scala b/arrow/src/test/scala-2.12/com/opticdev/arrow/changes/ChangesEvaluationSpec.scala index 49a2b6e384..e35cb3aa55 100644 --- a/arrow/src/test/scala-2.12/com/opticdev/arrow/changes/ChangesEvaluationSpec.scala +++ b/arrow/src/test/scala-2.12/com/opticdev/arrow/changes/ChangesEvaluationSpec.scala @@ -5,6 +5,7 @@ import com.opticdev.opm.TestPackageProviders import play.api.libs.json.Json import ExampleChanges._ import better.files.File +import com.opticdev.arrow.state.NodeKeyStore import com.opticdev.core.sourcegear.project.config.ProjectFile import com.opticdev.core.sourcegear.{SGConfig, SGConstructor} import org.scalatest.{BeforeAndAfterAll, BeforeAndAfterEach} @@ -15,6 +16,8 @@ import scala.concurrent.ExecutionContext.Implicits.global class ChangesEvaluationSpec extends TestBase with TestPackageProviders with BeforeAndAfterEach { + implicit val nodeKeyStore = new NodeKeyStore + override def beforeEach(): Unit = { resetScratch super.beforeEach() @@ -39,28 +42,57 @@ class ChangesEvaluationSpec extends TestBase with TestPackageProviders with Befo } - it("Runs Transformation") { - val (changeGroup, sourcegear, expectedChange) = transformModelToRoute - val results = changeGroup.evaluateAndWrite(sourcegear) + describe("Transformations") { - assert(results.get.stagedFiles.head._2.text == expectedChange) + it("Runs Transformation") { + val (changeGroup, sourcegear, expectedChange) = transformModelToRoute + val results = changeGroup.evaluateAndWrite(sourcegear) - } + assert(results.get.stagedFiles.head._2.text == expectedChange) - it("Runs Nested Transformation") { - val (changeGroup, sourcegear, expectedChange) = nestedTransformModelToRoute - val results = changeGroup.evaluateAndWrite(sourcegear) + } - assert(results.get.stagedFiles.head._2.text == expectedChange) - } + it("Runs Nested Transformation") { + val (changeGroup, sourcegear, expectedChange) = nestedTransformModelToRoute + val results = changeGroup.evaluateAndWrite(sourcegear) - it("Runs transformation from search") { - val (changeGroup, sourcegear, expectedChange) = transformationFromSearch + assert(results.get.stagedFiles.head._2.text == expectedChange) + } - val results = changeGroup.evaluateAndWrite(sourcegear) + it("Runs transformation from search") { + val (changeGroup, sourcegear, expectedChange) = transformationFromSearch + + val results = changeGroup.evaluateAndWrite(sourcegear) + + assert(results.get.stagedFiles.head._2.text == expectedChange) + } - assert(results.get.stagedFiles.head._2.text == expectedChange) } + describe("File Contents Updates") { + it("Updates file when valid") { + val newContents = "let test = 1234" + val changeGroup = ChangeGroup(FileContentsUpdate(File("test-examples/resources/tmp/test_project/nested/firstFile.js"), "let me = \"you\"", newContents)) + val results = changeGroup.evaluateAndWrite(sourceGearContext.sourceGear) + assert(results.isSuccess) + assert(results.get.stagedFiles.size == 1) + assert(results.get.stagedFiles.head._2.text == newContents) + } + + it("Fails to update if current value is different") { + val newContents = "let test = 1234" + val changeGroup = ChangeGroup(FileContentsUpdate(File("test-examples/resources/tmp/test_project/nested/firstFile.js"), "let different = \"you\"", newContents)) + val results = changeGroup.evaluateAndWrite(sourceGearContext.sourceGear) + assert(results.isFailure) + } + + it("Fails to update if file does not exist") { + val newContents = "let test = 1234" + val changeGroup = ChangeGroup(FileContentsUpdate(File("test-examples/resources/tmp/test_project/nested/not-real-file.js"), "let different = \"you\"", newContents)) + val results = changeGroup.evaluateAndWrite(sourceGearContext.sourceGear) + assert(results.isFailure) + } + + } } diff --git a/arrow/src/test/scala-2.12/com/opticdev/arrow/changes/ExampleChanges.scala b/arrow/src/test/scala-2.12/com/opticdev/arrow/changes/ExampleChanges.scala index 9380d7c088..61049f9bc4 100644 --- a/arrow/src/test/scala-2.12/com/opticdev/arrow/changes/ExampleChanges.scala +++ b/arrow/src/test/scala-2.12/com/opticdev/arrow/changes/ExampleChanges.scala @@ -179,6 +179,7 @@ object ExampleChanges extends TestBase with TestPackageProviders { | "transformationChanges": { | "transformation": { | "yields": "Model -> Route", + | "id": "m2r", | "packageId": "optic:test-transform@latest", | "input": "optic:rest@0.1.0/model", | "output": "optic:rest@0.1.0/route", @@ -231,6 +232,7 @@ object ExampleChanges extends TestBase with TestPackageProviders { | "transformationChanges": { | "transformation": { | "yields": "Schema -> Create Route", + | "id": "s2r", | "packageId": "optic:mongoose@0.1.0", | "input": "optic:mongoose@0.1.0/schema", | "output": "optic:rest@0.1.0/route", @@ -305,6 +307,7 @@ object ExampleChanges extends TestBase with TestPackageProviders { | "transformationChanges": { | "transformation": { | "yields": "Create Route", + | "id": "cr", | "packageId": "optic:mongoose@0.1.0", | "input": "optic:mongoose@0.1.0/schema", | "output": "optic:rest@0.1.0/route", @@ -348,7 +351,7 @@ object ExampleChanges extends TestBase with TestPackageProviders { | }], | "_type": "com.opticdev.arrow.changes.RunTransformation", | "answers": {}, - | "objectSelection": "a520740e", + | "objectSelection": "Hello", | "inputValue": { | "name": "Hello", | "schema": { @@ -367,7 +370,7 @@ object ExampleChanges extends TestBase with TestPackageProviders { val changeGroup = Json.fromJson[ChangeGroup](Json.parse(changesJSON)).get - (changeGroup, sourcegear, "let first = require('second')\n\napp.get('user/:id', function (req, res) {\n req.query.id\n})\n\napp.post('/hello', function (req, res) {\n req.body.first\n req.body.last\n req.body.isAdmin\n})\n\napp.get('post/:id', function (req, res) {\n req.query.id\n})") + (changeGroup, sourcegear, "let first = require('second')\n\napp.get('user/:id', function (req, res) {\n req.query.id\n})\n\napp.post('/hello', function (req, res) { //source: Hello -> optic:mongoose/cr {}\n req.body.first\n req.body.last\n req.body.isAdmin\n})\n\napp.get('post/:id', function (req, res) {\n req.query.id\n})") } diff --git a/arrow/src/test/scala-2.12/com/opticdev/arrow/changes/JsonImplicitsSpec.scala b/arrow/src/test/scala-2.12/com/opticdev/arrow/changes/JsonImplicitsSpec.scala index 6dc62016aa..3babe80f79 100644 --- a/arrow/src/test/scala-2.12/com/opticdev/arrow/changes/JsonImplicitsSpec.scala +++ b/arrow/src/test/scala-2.12/com/opticdev/arrow/changes/JsonImplicitsSpec.scala @@ -9,14 +9,6 @@ import com.opticdev.sdk.descriptions.{Schema, SchemaRef} class JsonImplicitsSpec extends FunSpec { - it("Files toJSON & back again") { - import JsonImplicits.fileFormat - val file = File("path/to/file") - val json = Json.toJson[File](File("path/to/file")) - - assert(Json.fromJson[File](json).get == file) - } - describe("Location format") { it("Raw Position toJSON & back again") { diff --git a/arrow/src/test/scala-2.12/com/opticdev/arrow/changes/OpticChangeSpec.scala b/arrow/src/test/scala-2.12/com/opticdev/arrow/changes/OpticChangeSpec.scala index 1c12b791c7..9f26621a4b 100644 --- a/arrow/src/test/scala-2.12/com/opticdev/arrow/changes/OpticChangeSpec.scala +++ b/arrow/src/test/scala-2.12/com/opticdev/arrow/changes/OpticChangeSpec.scala @@ -2,15 +2,17 @@ package com.opticdev.arrow.changes import better.files.File import com.opticdev.arrow.changes.evaluation.Evaluation +import com.opticdev.arrow.state.NodeKeyStore import com.opticdev.core.Fixture.TestBase import com.opticdev.core.sourcegear.project.monitoring.FileStateMonitor class OpticChangeSpec extends TestBase { + implicit val nodeKeyStore = new NodeKeyStore it("Clear search change works") { val expected = "class code {\n\n}\n\nfunction code1(arg1, arg2) {\n\n}\n" - val result = Evaluation.forChange(ClearSearchLines(File("test-examples/resources/example_source/ClearSearchTest.js")), null, null)(new FileStateMonitor()) + val result = Evaluation.forChange(ClearSearchLines(File("test-examples/resources/example_source/ClearSearchTest.js")), null, null)(new FileStateMonitor(), nodeKeyStore) assert(result.isSuccess) assert(result.asFileChanged.newContents == expected) diff --git a/arrow/src/test/scala-2.12/com/opticdev/arrow/graph/GearSerializationSpec.scala b/arrow/src/test/scala-2.12/com/opticdev/arrow/graph/GearSerializationSpec.scala index da1810640a..956aeb157e 100644 --- a/arrow/src/test/scala-2.12/com/opticdev/arrow/graph/GearSerializationSpec.scala +++ b/arrow/src/test/scala-2.12/com/opticdev/arrow/graph/GearSerializationSpec.scala @@ -19,7 +19,7 @@ class GearSerializationSpec extends TestBase with TestPackageProviders { it("can turn a gear node into json") { val json = GraphSerialization.jsonFromNode(LensNode(exampleProjectSG.lensSet.listLenses.find(_.id == "parameter").get)) - assert(json == Json.parse("""{"id":"optic:express-js@0.1.0/parameter","name":null, "packageFull":"optic:express-js@0.1.0","type":"gear"}""")) + assert(json == Json.parse("""{"id":"optic:express-js@0.1.0/parameter","name": "Parameter", "packageFull":"optic:express-js@0.1.0","type":"gear"}""")) } it("can turn a schema node into json") { @@ -31,8 +31,7 @@ class GearSerializationSpec extends TestBase with TestPackageProviders { it("can serialize a basic graph") { val graph = IndexSourceGear.runFor(exampleProjectSG) val result = GraphSerialization.serialize(graph) - - assert(result == Json.parse("""{"nodes":[{"id":"optic:express-js@0.1.0/route","name":"Example Route","packageFull":"optic:express-js@0.1.0","type":"gear"},{"id":"optic:rest@0.1.0/parameter","name":"Parameter","packageFull":"optic:rest@0.1.0","type":"schema"},{"id":"optic:express-js@0.1.0/parameter","name":null,"packageFull":"optic:express-js@0.1.0","type":"gear"},{"id":"optic:rest@0.1.0/route","name":"Route","packageFull":"optic:rest@0.1.0","type":"schema"}],"edges":[{"n1":"optic:rest@0.1.0/parameter","n2":"optic:express-js@0.1.0/parameter"},{"n1":"optic:rest@0.1.0/route","n2":"optic:express-js@0.1.0/route"}]}""")) + assert(result == Json.parse("""{"nodes":[{"id":"optic:rest@0.1.0/parameter","name":"Parameter","packageFull":"optic:rest@0.1.0","type":"schema"},{"id":"optic:rest@0.1.0/response","name":"Response","packageFull":"optic:rest@0.1.0","type":"schema"},{"id":"optic:rest@0.1.0/route","name":"Route","packageFull":"optic:rest@0.1.0","type":"schema"},{"id":"optic:express-js@0.1.0/response","name":"Response","packageFull":"optic:express-js@0.1.0","type":"gear"},{"id":"optic:express-js@0.1.0/route","name":"Route","packageFull":"optic:express-js@0.1.0","type":"gear"},{"id":"optic:express-js@0.1.0/parameter","name":"Parameter","packageFull":"optic:express-js@0.1.0","type":"gear"}],"edges":[{"n1":"optic:rest@0.1.0/parameter","n2":"optic:express-js@0.1.0/parameter"},{"n1":"optic:rest@0.1.0/response","n2":"optic:express-js@0.1.0/response"},{"n1":"optic:rest@0.1.0/route","n2":"optic:express-js@0.1.0/route"}]}""")) } it("can serialize a graph with transformations") { diff --git a/arrow/src/test/scala-2.12/com/opticdev/arrow/index/IndexSourceGearSpec.scala b/arrow/src/test/scala-2.12/com/opticdev/arrow/index/IndexSourceGearSpec.scala index 56f1b1272b..c14824b61c 100644 --- a/arrow/src/test/scala-2.12/com/opticdev/arrow/index/IndexSourceGearSpec.scala +++ b/arrow/src/test/scala-2.12/com/opticdev/arrow/index/IndexSourceGearSpec.scala @@ -17,8 +17,8 @@ class IndexSourceGearSpec extends TestBase with TestPackageProviders { it("can map schema and gear connections") { val knowledgeGraph = ExampleSourcegears.exampleProjectSG.knowledgeGraph - assert(knowledgeGraph.nodes.size == 4) - assert(knowledgeGraph.size == 6) + assert(knowledgeGraph.nodes.size == 6) + assert(knowledgeGraph.size == 9) } val schemaModel = Schema(SchemaRef(Some(PackageRef("optic:test")), "model"), JsObject.empty) diff --git a/arrow/src/test/scala-2.12/com/opticdev/arrow/search/TransformationSearchSpec.scala b/arrow/src/test/scala-2.12/com/opticdev/arrow/search/TransformationSearchSpec.scala index 1a6beec730..c0e3d81b66 100644 --- a/arrow/src/test/scala-2.12/com/opticdev/arrow/search/TransformationSearchSpec.scala +++ b/arrow/src/test/scala-2.12/com/opticdev/arrow/search/TransformationSearchSpec.scala @@ -4,6 +4,7 @@ import akka.actor.ActorSystem import better.files.File import com.opticdev.arrow.context.{ModelContext, NoContext} import com.opticdev.arrow.graph.KnowledgeGraphImplicits.DirectTransformation +import com.opticdev.arrow.state.NodeKeyStore import com.opticdev.core.Fixture.TestBase import com.opticdev.core.sourcegear.graph.model.ModelNode import com.opticdev.core.sourcegear.project.StaticSGProject @@ -13,13 +14,14 @@ import play.api.libs.json.JsObject import com.opticdev.core.sourcegear.actors.ActorCluster class TransformationSearchSpec extends TestBase { + implicit val nodeKeyStore = new NodeKeyStore - implicit lazy val project = new StaticSGProject("test", File("test-examples/resources/tmp/test_project"), null)(false, new ActorCluster(ActorSystem("test"))) + implicit lazy val project = new StaticSGProject("test", File("test-examples/resources/tmp/test_project"), null)(new ActorCluster(ActorSystem("test"))) it("finds transformations when valid context is present") { import com.opticdev.arrow.ExampleSourcegears.sgWithTransformations._ - val context = ModelContext(null, null, Vector(ModelNode(schemaModel.schemaRef, JsObject.empty, 0))) + val context = ModelContext(null, null, Vector(ModelNode(schemaModel.schemaRef, JsObject.empty, null, None, None, None, "a"))) val results = TransformationSearch.search(context)(sourceGear, project, knowledgeGraph) @@ -45,11 +47,11 @@ class TransformationSearchSpec extends TestBase { it("can convert transformation result to JSON ") { import com.opticdev.arrow.ExampleSourcegears.sgWithTransformations._ - val context = ModelContext(File("/test/file"), Range(32, 42), Vector(ModelNode(schemaModel.schemaRef, JsObject.empty, 0))) + val context = ModelContext(File("/test/file"), Range(32, 42), Vector(ModelNode(schemaModel.schemaRef, JsObject.empty, null, None, None, None, "a"))) val results = TransformationSearch.search(context)(sourceGear, project, knowledgeGraph) - assert(results.head.asJson.toString() == """{"name":"Model -> Route","projectName":"test","packageId":"optic:test-transform@latest","input":"optic:test@0.1.0/model","output":"optic:test@0.1.0/route","changes":[{"transformationChanges":{"transformation":{"yields":"Model -> Route","packageId":"optic:test-transform@latest","input":"optic:test@0.1.0/model","output":"optic:test@0.1.0/route","ask":{"type":"object"},"script":""},"target":"optic:test@0.1.0/route","_type":"com.opticdev.arrow.graph.KnowledgeGraphImplicits.DirectTransformation"},"inputValue":{},"lensOptions":[],"locationOptions":[{"file":"/test/file","position":43,"_type":"com.opticdev.arrow.changes.location.AsChildOf"}],"_type":"com.opticdev.arrow.changes.RunTransformation"}]}""") + assert(results.head.asJson.toString() == """{"name":"Model -> Route","projectName":"test","packageId":"optic:test-transform@latest","input":"optic:test@0.1.0/model","output":"optic:test@0.1.0/route","changes":[{"transformationChanges":{"transformation":{"yields":"Model -> Route","id":"m2r","packageId":"optic:test-transform@latest","input":"optic:test@0.1.0/model","output":"optic:test@0.1.0/route","ask":{"type":"object"},"script":""},"target":"optic:test@0.1.0/route","_type":"com.opticdev.arrow.graph.KnowledgeGraphImplicits.DirectTransformation"},"inputValue":{},"lensOptions":[],"locationOptions":[{"file":"/test/file","position":43,"_type":"com.opticdev.arrow.changes.location.AsChildOf"}],"_type":"com.opticdev.arrow.changes.RunTransformation"}]}""") } diff --git a/server/src/test/scala-2.12/com/opticdev/server/state/NodeKeyStoreSpec.scala b/arrow/src/test/scala-2.12/com/opticdev/arrow/state/NodeKeyStoreSpec.scala similarity index 69% rename from server/src/test/scala-2.12/com/opticdev/server/state/NodeKeyStoreSpec.scala rename to arrow/src/test/scala-2.12/com/opticdev/arrow/state/NodeKeyStoreSpec.scala index 9adf1a2472..095185d9fe 100644 --- a/server/src/test/scala-2.12/com/opticdev/server/state/NodeKeyStoreSpec.scala +++ b/arrow/src/test/scala-2.12/com/opticdev/arrow/state/NodeKeyStoreSpec.scala @@ -1,12 +1,9 @@ -package com.opticdev.server.state +package com.opticdev.arrow.state import better.files.File -import com.opticdev.core.Fixture.{AkkaTestFixture, TestBase} +import com.opticdev.core.Fixture.TestBase import com.opticdev.core.sourcegear.graph.model.LinkedModelNode -import com.opticdev.core.sourcegear.project.{OpticProject, StaticSGProject} -import com.opticdev.sdk.descriptions.SchemaRef -import org.scalatest.FunSpec -import play.api.libs.json.JsObject +import com.opticdev.core.sourcegear.project.OpticProject class NodeKeyStoreSpec extends TestBase { @@ -15,7 +12,7 @@ class NodeKeyStoreSpec extends TestBase { it("can lease an id and look it up") { val nodeKeyStore = new NodeKeyStore val testFile = File("hello/world") - val id = nodeKeyStore.leaseId(testFile, LinkedModelNode(null, null, null, null, null, null)) + val id = nodeKeyStore.leaseId(testFile, LinkedModelNode(null, null, null, null, null, null, null, null, null, null)) assert(id.nonEmpty) assert(nodeKeyStore.lookupId(id).isDefined) assert(nodeKeyStore.lookupIdInFile(id, testFile).isDefined) @@ -24,7 +21,7 @@ class NodeKeyStoreSpec extends TestBase { it("can invalidate a file's ids") { val nodeKeyStore = new NodeKeyStore val testFile = File("hello/world") - val id = nodeKeyStore.leaseId(testFile, LinkedModelNode(null, null, null, null, null, null)) + val id = nodeKeyStore.leaseId(testFile, LinkedModelNode(null, null, null, null, null, null, null, null, null, null)) nodeKeyStore.invalidateFileIds(testFile) assert(nodeKeyStore.lookupId(id).isEmpty) diff --git a/build.sbt b/build.sbt index e74eaef614..ca0b09326b 100644 --- a/build.sbt +++ b/build.sbt @@ -7,7 +7,7 @@ name := "optic-core" organization := "com.opticdev" -val appVersion = "0.1.6" +val appVersion = "1.0.0" version := appVersion diff --git a/common/src/main/scala-2.12/com/opticdev/common/ObjectRef.scala b/common/src/main/scala-2.12/com/opticdev/common/ObjectRef.scala new file mode 100644 index 0000000000..bf4dd38517 --- /dev/null +++ b/common/src/main/scala-2.12/com/opticdev/common/ObjectRef.scala @@ -0,0 +1,3 @@ +package com.opticdev.common + +case class ObjectRef(name: String) diff --git a/common/src/main/scala-2.12/com/opticdev/common/Regexes.scala b/common/src/main/scala-2.12/com/opticdev/common/Regexes.scala index 123f6a7864..0127082bd2 100644 --- a/common/src/main/scala-2.12/com/opticdev/common/Regexes.scala +++ b/common/src/main/scala-2.12/com/opticdev/common/Regexes.scala @@ -6,4 +6,6 @@ object Regexes { val packageName = "^[a-z][a-z0-9-]{0,34}" val packageId = "^[a-z][a-z0-9]{1,34}:[a-z][a-z0-9-]{0,34}" + val packages = """([a-z][a-z0-9]{1,34}):([a-z][a-z0-9-]{0,34})(?:@(v?(?:0|[1-9]\d*)\.(?:0|[1-9]\d*)\.(?:0|[1-9]\d*)(?:-[\da-z-]+(?:\.[\da-z-]+)*)?(?:\+[\da-z-]+(?:\.[\da-z-]+)*)?|latest)){0,1}(?:\/([a-z][a-z0-9-]{0,34})){0,1}""" + .r("namespace", "packageId", "version", "id") } diff --git a/common/src/main/scala-2.12/com/opticdev/common/package.scala b/common/src/main/scala-2.12/com/opticdev/common/package.scala index 4e42fcff9e..71befba518 100644 --- a/common/src/main/scala-2.12/com/opticdev/common/package.scala +++ b/common/src/main/scala-2.12/com/opticdev/common/package.scala @@ -1,6 +1,7 @@ package com.opticdev - +import better.files.File import play.api.libs.json._ +import scala.util.Try package object common { @@ -24,6 +25,18 @@ package object common { } } + //File + implicit val fileFormat = new Format[File] { + override def reads(json: JsValue) = { + JsSuccess(Try(File(json.as[JsString].value)).get) + } + + override def writes(o: File) = { + JsString(o.pathAsString) + } + } + + trait SGExportable } diff --git a/common/src/main/scala-2.12/com/opticdev/common/utils/Crypto.scala b/common/src/main/scala-2.12/com/opticdev/common/utils/Crypto.scala index 998122dc30..f2edbdf30a 100644 --- a/common/src/main/scala-2.12/com/opticdev/common/utils/Crypto.scala +++ b/common/src/main/scala-2.12/com/opticdev/common/utils/Crypto.scala @@ -3,6 +3,8 @@ package com.opticdev.parsers.utils import java.security.MessageDigest object Crypto { + def createSha256Hash(contentAsString: String) : String = String.format("%064x", new java.math.BigInteger(1, java.security.MessageDigest.getInstance("SHA-256").digest(contentAsString.getBytes("UTF-8")))) + def createSha1(string: String) : String = MessageDigest.getInstance("SHA-1").digest(string.getBytes).mkString("") } diff --git a/common/src/main/scala-2.12/com/opticdev/common/utils/FileCrypto.scala b/common/src/main/scala-2.12/com/opticdev/common/utils/FileCrypto.scala deleted file mode 100644 index 06546952fa..0000000000 --- a/common/src/main/scala-2.12/com/opticdev/common/utils/FileCrypto.scala +++ /dev/null @@ -1,12 +0,0 @@ -package com.opticdev.parsers.utils - -object FileCrypto { - def hashFile(filePath: String): String = { - val lines = scala.io.Source.fromFile(filePath).mkString - Crypto.createSha1(lines) - } - - def sha256Hash(text: String) : String = String.format("%064x", new java.math.BigInteger(1, java.security.MessageDigest.getInstance("SHA-256").digest(text.getBytes("UTF-8")))) - - def hashString(raw: String) = Crypto.createSha1(raw) -} diff --git a/common/src/test/scala-2.12/com/opticdev/common/FileJsonSpec.scala b/common/src/test/scala-2.12/com/opticdev/common/FileJsonSpec.scala new file mode 100644 index 0000000000..af2c0271b1 --- /dev/null +++ b/common/src/test/scala-2.12/com/opticdev/common/FileJsonSpec.scala @@ -0,0 +1,17 @@ +package com.opticdev.common + +import better.files.File +import org.scalatest.FunSpec +import play.api.libs.json.Json + +class FileJsonSpec extends FunSpec { + + it("Files toJSON & back again") { + import com.opticdev.common.fileFormat + val file = File("path/to/file") + val json = Json.toJson[File](File("path/to/file")) + + assert(Json.fromJson[File](json).get == file) + } + +} diff --git a/config.yaml b/config.yaml index aafc3c219d..87dca14eca 100644 --- a/config.yaml +++ b/config.yaml @@ -1 +1 @@ -testParser: "server/src/main/resources/es7_2.12-0.1.3.jar" \ No newline at end of file +testParser: "server/src/main/resources/es7_2.12-1.0.0.jar" \ No newline at end of file diff --git a/core/src/main/resources/application.conf b/core/src/main/resources/application.conf index 16a6416e6b..84c95b014b 100644 --- a/core/src/main/resources/application.conf +++ b/core/src/main/resources/application.conf @@ -1,4 +1,8 @@ faddish-parse-worker-mailbox { mailbox-type = "com.opticdev.scala.akka.FaddishUnboundedMailbox" filter = "com.opticdev.core.sourcegear.actors.filters.ParseWorkerFaddishFilter" +} + +akka { + loglevel = "OFF" } \ No newline at end of file diff --git a/core/src/main/scala-2.12/com/opticdev/core/compiler/Output.scala b/core/src/main/scala-2.12/com/opticdev/core/compiler/Output.scala index 24a98258fd..1649933e19 100644 --- a/core/src/main/scala-2.12/com/opticdev/core/compiler/Output.scala +++ b/core/src/main/scala-2.12/com/opticdev/core/compiler/Output.scala @@ -11,6 +11,7 @@ import com.opticdev.core.sourcegear.variables.VariableManager import com.opticdev.opm.packages.OpticMDPackage import com.opticdev.parsers.{AstGraph, ParserBase} import com.opticdev.parsers.graph.{AstType, CommonAstNode} +import com.opticdev.parsers.rules.Rule import com.opticdev.sdk.descriptions._ import scala.util.Try @@ -35,7 +36,7 @@ case class SnippetStageOutput(astGraph: AstGraph, case class FinderStageOutput(componentFinders: Map[FinderPath, Vector[Component]], - ruleFinders: Map[FinderPath, Vector[Rule]], + ruleFinders: Map[FinderPath, Vector[RuleWithFinder]], failedFinders: Vector[FinderError]) { def hasErrors = failedFinders.nonEmpty } diff --git a/core/src/main/scala-2.12/com/opticdev/core/compiler/stages/FinderStage.scala b/core/src/main/scala-2.12/com/opticdev/core/compiler/stages/FinderStage.scala index 166b13d286..a7c3bf51b1 100644 --- a/core/src/main/scala-2.12/com/opticdev/core/compiler/stages/FinderStage.scala +++ b/core/src/main/scala-2.12/com/opticdev/core/compiler/stages/FinderStage.scala @@ -5,7 +5,7 @@ import com.opticdev.core.compiler.errors.{ErrorAccumulator, InvalidComponents} import com.opticdev.core.compiler.helpers.{FinderEvaluator, FinderPath} import com.opticdev.core.sourcegear.containers.SubContainerManager import com.opticdev.core.sourcegear.variables.VariableManager -import com.opticdev.sdk.descriptions.{CodeComponent, Lens} +import com.opticdev.sdk.descriptions.{CodeComponent, Lens, RuleWithFinder} import com.opticdev.sdk.descriptions.enums.{Literal, ObjectLiteral, Token} import com.opticdev.sdk.descriptions.finders.Finder @@ -59,13 +59,14 @@ class FinderStage(snippetStageOutput: SnippetStageOutput)(implicit val lens: Len val combinedRules = /* lens.rules ++ */ lens.components.flatMap(_.rules) ++ variableRules ++ subContainerRules - val rulePaths = combinedRules.map(r=> { + val rulePaths = combinedRules.collect { + case r: RuleWithFinder => val finderPathTry = pathForFinder(r.finder) if (finderPathTry.isFailure) { errorAccumulator.add(finderPathTry.asInstanceOf[Failure[Exception]].exception) null } else (r, finderPathTry.get) - }).filterNot(_ == null) + }.filterNot(_ == null) val componentsGrouped = finderPaths.groupBy(_._2).mapValues(_.map(_._1)) val rulesGrouped = rulePaths.groupBy(_._2).mapValues(_.map(_._1)) diff --git a/core/src/main/scala-2.12/com/opticdev/core/compiler/stages/ParserFactoryStage.scala b/core/src/main/scala-2.12/com/opticdev/core/compiler/stages/ParserFactoryStage.scala index 692b88e34e..4b3f274e7e 100644 --- a/core/src/main/scala-2.12/com/opticdev/core/compiler/stages/ParserFactoryStage.scala +++ b/core/src/main/scala-2.12/com/opticdev/core/compiler/stages/ParserFactoryStage.scala @@ -40,8 +40,6 @@ class ParserFactoryStage(snippetStage: SnippetStageOutput, finderStageOutput: Fi ) }) - implicit val ruleProvider = new RuleProvider() - ParserFactoryOutput( ParseAsModel( nodeDescription, @@ -56,7 +54,9 @@ class ParserFactoryStage(snippetStage: SnippetStageOutput, finderStageOutput: Fi listeners, variableManager, AdditionalParserInformation(snippetStage.parser.identifierNodeDesc, snippetStage.parser.blockNodeTypes.nodeTypes.toSeq), - lens.packageRef.packageId + lens.packageRef.packageId, + lens.lensRef, + lens.initialValue.getOrElse(JsObject.empty) )) } diff --git a/core/src/main/scala-2.12/com/opticdev/core/debug/DebugMarkdownProject.scala b/core/src/main/scala-2.12/com/opticdev/core/debug/DebugMarkdownProject.scala index b3b3d97e02..05e87c1d17 100644 --- a/core/src/main/scala-2.12/com/opticdev/core/debug/DebugMarkdownProject.scala +++ b/core/src/main/scala-2.12/com/opticdev/core/debug/DebugMarkdownProject.scala @@ -11,6 +11,7 @@ import com.opticdev.core.sourcegear.graph.{ProjectGraph, ProjectGraphWrapper} import com.opticdev.core.sourcegear.project.ProjectBase import com.opticdev.core.sourcegear.project.monitoring.FileStateMonitor import com.opticdev.core.sourcegear.project.status.{ProjectStatus, _} +import com.opticdev.core.sourcegear.sync.SyncPatch import com.opticdev.parsers.AstGraph import com.opticdev.sdk.descriptions.{Lens, PackageExportable} @@ -24,7 +25,7 @@ case class DebugMarkdownProject(implicit logToCli: Boolean = false, actorCluster val name: String = "_internal:DEBUG_PROJECT" - val projectActor: ActorRef = actorCluster.newProjectActor + val projectActor: ActorRef = actorCluster.newProjectActor()(project = this) protected val projectStatusInstance: ProjectStatus = new ProjectStatus(_configStatus = ValidConfig, _sourceGearStatus = Valid) val projectStatus = projectStatusInstance.immutable @@ -86,4 +87,8 @@ case class DebugMarkdownProject(implicit logToCli: Boolean = false, actorCluster filesStateMonitor.stageContents(file, contents) projectActor ? FileUpdatedInMemory(file, contents, this)(projectSourcegear) } + + override def syncPatch: Future[SyncPatch] = Future(SyncPatch(Vector(), Vector())(this)) + + override val baseDirectory: File = File("") } diff --git a/core/src/main/scala-2.12/com/opticdev/core/debug/DebugSourceGear.scala b/core/src/main/scala-2.12/com/opticdev/core/debug/DebugSourceGear.scala index 68596c275b..b85b624f7e 100644 --- a/core/src/main/scala-2.12/com/opticdev/core/debug/DebugSourceGear.scala +++ b/core/src/main/scala-2.12/com/opticdev/core/debug/DebugSourceGear.scala @@ -9,7 +9,7 @@ import com.opticdev.opm.packages.{OpticMDPackage, OpticPackage} import com.opticdev.opm.providers.ProjectKnowledgeSearchPaths import com.opticdev.parsers.{ParserBase, ParserResult} import com.opticdev.parsers.graph.{CommonAstNode, GraphBuilder, WithinFile} -import com.opticdev.sdk.descriptions.{Lens, PackageExportable, Schema, SchemaRef} +import com.opticdev.sdk.descriptions._ import com.opticdev.sdk.descriptions.transformation.Transformation import com.opticdev.sdk.markdown.MarkdownParser import play.api.libs.json.{JsObject, JsString} @@ -46,7 +46,7 @@ object DebugSourceGear extends SourceGear { Try(contents).flatMap(i => parseStringWithKnowledgePaths(i)(project, projectKnowledgeSearchPaths)) } - override def parseString(string: String)(implicit project: ProjectBase): Try[sourcegear.FileParseResults] = + override def parseString(string: String, file: File = null)(implicit project: ProjectBase): Try[sourcegear.FileParseResults] = parseStringWithKnowledgePaths(string)(project, ProjectKnowledgeSearchPaths()) def parseStringWithKnowledgePaths(string: String)(implicit project: ProjectBase, projectKnowledgeSearchPaths: ProjectKnowledgeSearchPaths): Try[sourcegear.FileParseResults] = Try { @@ -82,7 +82,7 @@ object DebugSourceGear extends SourceGear { implicit val astGraph = graphBuilder.graph def linkedModelNode[S <: PackageExportable](schemaRef: SchemaRef, node: DebugAstNode[S]): LinkedModelNode[DebugAstNode[S]] = - LinkedModelNode(schemaRef, JsObject.empty, node, Map(), Map(), null)(project) + LinkedModelNode(schemaRef, JsObject.empty, LensRef(Some(PackageRef("optic:internal")), "lens"), node, Map(), Map(), null, None, None, None)(project) val linkedModelNodes : Vector[LinkedModelNode[DebugAstNode[PackageExportable]]] = astGraph.nodes.toVector.map(_.value).collect { //for some reason the if is needed. likely type erasure diff --git a/core/src/main/scala-2.12/com/opticdev/core/debug/package.scala b/core/src/main/scala-2.12/com/opticdev/core/debug/package.scala index 872519ce91..566cd5fa51 100644 --- a/core/src/main/scala-2.12/com/opticdev/core/debug/package.scala +++ b/core/src/main/scala-2.12/com/opticdev/core/debug/package.scala @@ -9,12 +9,16 @@ import com.opticdev.sdk.descriptions.transformation.Transformation import com.opticdev.sdk.descriptions.{Lens, PackageExportable, Schema, SchemaRef} import play.api.libs.json.{JsObject, JsValue} +import scala.util.hashing.MurmurHash3 + package object debug { case class DebugAstNode[S <: PackageExportable](nodeType: AstType, range: Range, sdkObject: S)(implicit val packageContext: Context) extends WithinFile { def isSchema = sdkObject.isInstanceOf[Schema] def isLens = sdkObject.isInstanceOf[Lens] def isTransformation = sdkObject.isInstanceOf[Transformation] + + override def hash: String = Integer.toHexString(MurmurHash3.stringHash(this.toString)) } object DebugLanguageProxy { diff --git a/core/src/main/scala-2.12/com/opticdev/core/sourcegear/CompiledLens.scala b/core/src/main/scala-2.12/com/opticdev/core/sourcegear/CompiledLens.scala index e27d1d55f2..0842a06579 100644 --- a/core/src/main/scala-2.12/com/opticdev/core/sourcegear/CompiledLens.scala +++ b/core/src/main/scala-2.12/com/opticdev/core/sourcegear/CompiledLens.scala @@ -5,7 +5,7 @@ import com.opticdev.core.sourcegear.gears.rendering.RenderGear import com.opticdev.core.sourcegear.gears.parsing.{ParseAsModel, ParseGear} import com.opticdev.core.sourcegear.project.Project import com.opticdev.core.utils.UUID -import com.opticdev.parsers.AstGraph +import com.opticdev.parsers.{AstGraph, ParserBase} import com.opticdev.parsers.graph.{AstType, CommonAstNode} import com.opticdev.sdk.descriptions.{Lens, LensRef, SchemaRef} import play.api.libs.json.{Format, JsString, JsSuccess, JsValue} diff --git a/core/src/main/scala-2.12/com/opticdev/core/sourcegear/Gearset.scala b/core/src/main/scala-2.12/com/opticdev/core/sourcegear/Gearset.scala index 018aa38458..f5c535931e 100644 --- a/core/src/main/scala-2.12/com/opticdev/core/sourcegear/Gearset.scala +++ b/core/src/main/scala-2.12/com/opticdev/core/sourcegear/Gearset.scala @@ -69,7 +69,6 @@ class LensSet(initialGears: CompiledLens*) { fileAccumulator.run(astGraph, results) - import com.opticdev.core.sourcegear.graph.GraphImplicits._ FileParseResults(astGraph, astGraph.modelNodes.asInstanceOf[Vector[ModelNode]], sourceGearContext.parser, sourceGearContext.fileContents) } diff --git a/core/src/main/scala-2.12/com/opticdev/core/sourcegear/ParseCache.scala b/core/src/main/scala-2.12/com/opticdev/core/sourcegear/ParseCache.scala index 3eb4eb7b4c..9ccd800615 100644 --- a/core/src/main/scala-2.12/com/opticdev/core/sourcegear/ParseCache.scala +++ b/core/src/main/scala-2.12/com/opticdev/core/sourcegear/ParseCache.scala @@ -2,11 +2,23 @@ package com.opticdev.core.sourcegear import better.files.File import com.opticdev.core.sourcegear.graph.FileNode +import com.opticdev.core.sourcegear.graph.model.ModelNode import com.opticdev.parsers.{AstGraph, ParserBase} import scala.collection.mutable -case class CacheRecord(graph: AstGraph, parser: ParserBase, fileContents: String) +case class CacheRecord(graph: AstGraph, parser: ParserBase, fileContents: String) { + //WARNING: Negating this does not determine equality + def differentFrom(other: String) : Boolean = { + other.size != fileContents.size || + other != fileContents + } + + def asFileParseResults = { + import com.opticdev.core.sourcegear.graph.GraphImplicits._ + FileParseResults(graph, graph.modelNodes.asInstanceOf[Vector[ModelNode]], parser, fileContents) + } +} class ParseCache { @@ -20,7 +32,6 @@ class ParseCache { def add(file: FileNode, record: CacheRecord) : ParseCache = { - //remove any records with same path and different hash fileStore --= fileStore.keys.filter(_.filePath == file.filePath) //add new file record to map fileStore += file -> record @@ -41,6 +52,10 @@ class ParseCache { def get(key: FileNode): Option[CacheRecord] = fileStore.get(key) + def isCurrentForFile(fileNode: FileNode, contents: String) : Boolean = { + get(fileNode).exists(record => !record.differentFrom(contents)) + } + def clear: ParseCache = { fileStore.clear() lastNFiles.clear() diff --git a/core/src/main/scala-2.12/com/opticdev/core/sourcegear/Render.scala b/core/src/main/scala-2.12/com/opticdev/core/sourcegear/Render.scala index bbd4349cd4..519b272375 100644 --- a/core/src/main/scala-2.12/com/opticdev/core/sourcegear/Render.scala +++ b/core/src/main/scala-2.12/com/opticdev/core/sourcegear/Render.scala @@ -15,6 +15,8 @@ import play.api.libs.json._ import scala.util.{Failure, Success, Try} import com.opticdev.core.sourcegear.context.SDKObjectsResolvedImplicits._ +import com.opticdev.core.sourcegear.objects.annotations.{ObjectAnnotationRenderer, TagAnnotation} +import com.opticdev.marvin.common.helpers.LineOperations object Render { def fromStagedNode(stagedNode: StagedNode, parentVariableMapping: VariableMapping = Map.empty)(implicit sourceGear: SourceGear, context: FlatContextBase = null) : Try[(NewAstNode, String, CompiledLens)] = Try { @@ -39,10 +41,17 @@ object Render { val processedValue = processValue(stagedNode)(sourceGear, variableMapping) val result = gear.renderer.renderWithNewAstNode(processedValue, containerContents, variableMapping) - (result._1, result._2, gear) + + val stringResult = if (options.tag.isDefined) { + ObjectAnnotationRenderer.renderToFirstLine(gear.renderer.parser.get.inlineCommentPrefix, Vector(TagAnnotation(options.tag.get, gear.schemaRef)), result._2) + } else { + result._2 + } + + (result._1.withForcedContent(Some(stringResult)), stringResult, gear) } - private def resolveLens(stagedNode: StagedNode)(implicit sourceGear: SourceGear, context: FlatContextBase) : Option[CompiledLens] = { + def resolveLens(stagedNode: StagedNode)(implicit sourceGear: SourceGear, context: FlatContextBase) : Option[CompiledLens] = { val lensRefTry = Try(LensRef.fromString(stagedNode.options.get.lensId.get).get) if (lensRefTry.isSuccess) { val lensRef = lensRefTry.get diff --git a/core/src/main/scala-2.12/com/opticdev/core/sourcegear/SGConstants.scala b/core/src/main/scala-2.12/com/opticdev/core/sourcegear/SGConstants.scala index 0730589809..fdd8750db6 100644 --- a/core/src/main/scala-2.12/com/opticdev/core/sourcegear/SGConstants.scala +++ b/core/src/main/scala-2.12/com/opticdev/core/sourcegear/SGConstants.scala @@ -1,6 +1,6 @@ package com.opticdev.core.sourcegear object SGConstants { - val parseWorkers = 5 - val maxCachedFiles = 10 + val parseWorkers = 12 + val maxCachedFiles = 80 } diff --git a/core/src/main/scala-2.12/com/opticdev/core/sourcegear/SGConstructor.scala b/core/src/main/scala-2.12/com/opticdev/core/sourcegear/SGConstructor.scala index f8c024dbf0..7325e97be6 100644 --- a/core/src/main/scala-2.12/com/opticdev/core/sourcegear/SGConstructor.scala +++ b/core/src/main/scala-2.12/com/opticdev/core/sourcegear/SGConstructor.scala @@ -7,7 +7,7 @@ import com.opticdev.core.sourcegear.project.config.ProjectFile import com.opticdev.core.sourcegear.storage.SGConfigStorage import com.opticdev.opm.providers.ProjectKnowledgeSearchPaths import com.opticdev.opm.{DependencyTree, PackageManager} -import com.opticdev.parsers.{ParserBase, ParserRef} +import com.opticdev.parsers.{ParserBase, ParserRef, SourceParserManager} import com.opticdev.sdk.descriptions.Lens import scala.concurrent.ExecutionContext.Implicits.global diff --git a/core/src/main/scala-2.12/com/opticdev/core/sourcegear/SGContext.scala b/core/src/main/scala-2.12/com/opticdev/core/sourcegear/SGContext.scala index f8ab093e2d..816013d4de 100644 --- a/core/src/main/scala-2.12/com/opticdev/core/sourcegear/SGContext.scala +++ b/core/src/main/scala-2.12/com/opticdev/core/sourcegear/SGContext.scala @@ -1,5 +1,6 @@ package com.opticdev.core.sourcegear +import better.files.File import com.opticdev.core.sourcegear.accumulate.FileAccumulator import com.opticdev.core.sourcegear.actors.{ActorCluster, ParseSupervisorSyncAccess} import com.opticdev.core.sourcegear.graph.model.{BaseModelNode, ModelNode} @@ -10,7 +11,8 @@ case class SGContext(fileAccumulator: FileAccumulator, astGraph: AstGraph, parser: ParserBase, fileContents: String, - sourceGear: SourceGear + sourceGear: SourceGear, + file: File ) @@ -22,7 +24,7 @@ object SGContext { } def forRender(sourceGear: SourceGear, astGraph: AstGraph, parserRef: ParserRef): SGContext = { - SGContext(sourceGear.fileAccumulator, astGraph, sourceGear.findParser(parserRef).get, null, sourceGear) + SGContext(sourceGear.fileAccumulator, astGraph, sourceGear.findParser(parserRef).get, null, sourceGear, null) } } \ No newline at end of file diff --git a/core/src/main/scala-2.12/com/opticdev/core/sourcegear/SourceGear.scala b/core/src/main/scala-2.12/com/opticdev/core/sourcegear/SourceGear.scala index fc84146617..02f9b99fc7 100644 --- a/core/src/main/scala-2.12/com/opticdev/core/sourcegear/SourceGear.scala +++ b/core/src/main/scala-2.12/com/opticdev/core/sourcegear/SourceGear.scala @@ -10,7 +10,7 @@ import com.opticdev.marvin.common.ast.NewAstNode import com.opticdev.opm.context.{Tree, TreeContext} import com.opticdev.parsers import com.opticdev.parsers.{ParserBase, ParserRef, SourceParserManager} -import com.opticdev.sdk.descriptions.transformation.{StagedNode, Transformation} +import com.opticdev.sdk.descriptions.transformation.{StagedNode, Transformation, TransformationRef} import scala.util.{Failure, Success, Try} import scalax.collection.edge.LkDiEdge @@ -51,15 +51,27 @@ abstract class SourceGear { lensVersion.map(_._2) } + def findTransformation(transformationRef: TransformationRef): Option[Transformation] = { + + val available: Set[Transformation] = transformations.filter(trans=> + transformationRef.packageRef.map(_.packageId).contains(trans.packageId.packageId) + && trans.id.contains(transformationRef.id) + ) + + val transformationVersion = SemverHelper.findVersion(available, (l: Transformation) => l.packageId, transformationRef.packageRef.map(_.version).getOrElse("latest")) + + transformationVersion.map(_._2) + } + def findParser(parserRef: ParserRef) = parsers.find(_.languageName == parserRef.languageName) lazy val validExtensions: Set[String] = parsers.flatMap(_.fileExtensions) lazy val excludedPaths: Seq[String] = parsers.flatMap(_.excludedPaths).toSeq def parseFile(file: File) (implicit project: ProjectBase) : Try[FileParseResults] = - Try(file.contentAsString).flatMap(i=> parseString(i)) + Try(file.contentAsString).flatMap(i=> parseString(i, file)) - def parseString(string: String) (implicit project: ProjectBase) : Try[FileParseResults] = Try { + def parseString(string: String, file: File = null) (implicit project: ProjectBase) : Try[FileParseResults] = Try { val fileContents = string //@todo connect to parser list val parsedOption = SourceParserManager.parseString(fileContents, "es7") @@ -69,7 +81,7 @@ abstract class SourceGear { //@todo clean this up and have the parser return in the parse result. right now it only supports the test one // val parser = parsers.find(_.languageName == parsed.language).get - implicit val sourceGearContext = SGContext(lensSet.fileAccumulator, astGraph, SourceParserManager.installedParsers.head, fileContents, this) + implicit val sourceGearContext = SGContext(lensSet.fileAccumulator, astGraph, SourceParserManager.installedParsers.head, fileContents, this, file) lensSet.parseFromGraph(fileContents, astGraph, sourceGearContext, project) } else { throw parsedOption.failed.get diff --git a/core/src/main/scala-2.12/com/opticdev/core/sourcegear/accumulate/AccumulatorListener.scala b/core/src/main/scala-2.12/com/opticdev/core/sourcegear/accumulate/AccumulatorListener.scala index 525675db7f..fad08125df 100644 --- a/core/src/main/scala-2.12/com/opticdev/core/sourcegear/accumulate/AccumulatorListener.scala +++ b/core/src/main/scala-2.12/com/opticdev/core/sourcegear/accumulate/AccumulatorListener.scala @@ -24,9 +24,7 @@ case class MapSchemaListener(schemaComponent: SchemaComponent, mapToSchema: Sche override val schema = schemaComponent.schema override def collect(implicit astGraph: AstGraph, modelNode: BaseModelNode, sourceGearContext: SGContext): ModelField = { - println(schemaComponent.schema) val resolvedSchema = schemaComponent.resolvedSchema(packageId)(sourceGearContext.sourceGear) - println(resolvedSchema) val asModelNode : ModelNode = modelNode match { case l: LinkedModelNode[CommonAstNode] => l.flatten diff --git a/core/src/main/scala-2.12/com/opticdev/core/sourcegear/actors/ActorCluster.scala b/core/src/main/scala-2.12/com/opticdev/core/sourcegear/actors/ActorCluster.scala index fdf8671c4f..668436e03f 100644 --- a/core/src/main/scala-2.12/com/opticdev/core/sourcegear/actors/ActorCluster.scala +++ b/core/src/main/scala-2.12/com/opticdev/core/sourcegear/actors/ActorCluster.scala @@ -3,13 +3,14 @@ package com.opticdev.core.sourcegear.actors import akka.actor.{ActorRef, ActorSystem, Props} import com.opticdev._ import com.opticdev.core.sourcegear.graph.ProjectGraphWrapper +import com.opticdev.core.sourcegear.project.{OpticProject, ProjectBase} -class ActorCluster(actorSystem: ActorSystem) { +class ActorCluster(val actorSystem: ActorSystem) { val parserSupervisorRef : ActorRef = { actorSystem.actorOf(ParseSupervisorActor.props()(this)) } - def newProjectActor()(implicit logToCli : Boolean = false) = { + def newProjectActor()(implicit logToCli : Boolean = false, project: ProjectBase) = { implicit val actorCluster = this actorSystem.actorOf(ProjectActor.props(ProjectGraphWrapper.empty)) } diff --git a/core/src/main/scala-2.12/com/opticdev/core/sourcegear/actors/ParseSupervisorActor.scala b/core/src/main/scala-2.12/com/opticdev/core/sourcegear/actors/ParseSupervisorActor.scala index 327c258ec0..83714a59b2 100644 --- a/core/src/main/scala-2.12/com/opticdev/core/sourcegear/actors/ParseSupervisorActor.scala +++ b/core/src/main/scala-2.12/com/opticdev/core/sourcegear/actors/ParseSupervisorActor.scala @@ -33,9 +33,16 @@ class ParseSupervisorActor()(implicit actorCluster: ActorCluster) extends Actor override def receive: Receive = handler(new ParseCache) + //cache won't play nicely if files are in multiple projects with different sourcegears def handler(parseCache: ParseCache) : Receive = { - case request: ParserRequest => - router.route(request, sender()) + case request: ParserRequest => { + val fileNode = FileNode(request.file.pathAsString) + if (parseCache.isCurrentForFile(fileNode, request.contents)) { + sender() tell(ParseSuccessful(parseCache.get(fileNode).get.asFileParseResults, request.file, fromCache = true), request.requestingActor) + } else { + router.route(request, sender()) + } + } case Terminated(a) => router = router.removeRoutee(a) val r = context.actorOf(Props[WorkerActor]) @@ -52,7 +59,9 @@ class ParseSupervisorActor()(implicit actorCluster: ActorCluster) extends Actor record.graph, record.parser, record.fileContents, - ctxRequest.sourceGear) + ctxRequest.sourceGear, + ctxRequest.fileNode.toFile + ) ) } else { router.route(ctxRequest, sender()) @@ -74,7 +83,7 @@ object ParseSupervisorActor { } object ParseSupervisorSyncAccess { - implicit val timeout: Timeout = Timeout(2 seconds) + implicit val timeout: Timeout = Timeout(1 minute) def setCache(newCache: ParseCache) (implicit actorCluster: ActorCluster): Unit = { actorCluster.parserSupervisorRef ! SetCache(newCache) @@ -90,7 +99,7 @@ object ParseSupervisorSyncAccess { } def getContext(file: File)(implicit actorCluster: ActorCluster, sourceGear: SourceGear, project: ProjectBase): Option[SGContext] = { - val future = actorCluster.parserSupervisorRef ? GetContext(FileNode.fromFile(file)) + val future = actorCluster.parserSupervisorRef ? GetContext(FileNode(file.pathAsString)) Await.result(future, timeout.duration).asInstanceOf[Option[SGContext]] } diff --git a/core/src/main/scala-2.12/com/opticdev/core/sourcegear/actors/ProjectActor.scala b/core/src/main/scala-2.12/com/opticdev/core/sourcegear/actors/ProjectActor.scala index 8ff81f4afa..5d0e2b8394 100644 --- a/core/src/main/scala-2.12/com/opticdev/core/sourcegear/actors/ProjectActor.scala +++ b/core/src/main/scala-2.12/com/opticdev/core/sourcegear/actors/ProjectActor.scala @@ -9,6 +9,9 @@ import scala.concurrent.Await import akka.pattern.ask import akka.util.Timeout import com.opticdev.core.sourcegear.ParseCache +import com.opticdev.core.sourcegear.project.status.SyncStatus +import com.opticdev.core.sourcegear.snapshot.Snapshot +import com.opticdev.core.sourcegear.sync.DiffSyncGraph import scala.concurrent.Future import concurrent.duration._ @@ -20,28 +23,37 @@ class ProjectActor(initialGraph: ProjectGraphWrapper)(implicit logToCli: Boolean def active(graph: ProjectGraphWrapper): Receive = { //handle consequences of parsings case parsed: ParseSuccessful => { - graph.updateFile(parsed.parseResults.astGraph, parsed.file) - - if (logToCli) graph.prettyPrint else sender() ! graph + if (!parsed.fromCache) { + graph.updateFile(parsed.parseResults.astGraph, parsed.file) + } context.become(active(graph)) + sender() ! graph } - case i: ParseFailed => println("Failed to parse file "+ i.file) + case i: ParseFailed => { + graph.removeFile(i.file, ignoreExceptions = true) + context.become(active(graph)) + println("Failed to parse file "+ i.file) + sender() ! graph + } case deleted: FileDeleted => { graph.removeFile(deleted.file) - - if (logToCli) graph.prettyPrint else sender() ! graph - context.become(active(graph)) + sender() ! graph } case CurrentGraph => sender ! graph + case SetCurrentGraph(newGraph: ProjectGraph) => { + context.become(active(new ProjectGraphWrapper(newGraph)(initialGraph.project))) + sender ! Unit + } case ClearGraph => { - val emptyGraph = ProjectGraphWrapper.empty + val emptyGraph = ProjectGraphWrapper.empty()(initialGraph.project) sender ! emptyGraph context.become(active(emptyGraph)) } case NodeForId(id) => sender ! graph.nodeForId(id) + case GetSnapshot(sg, project) => sender ! Snapshot.forSourceGearAndProjectGraph(sg, graph.projectGraph, project.actorCluster.parserSupervisorRef, project) //Forward parsing requests to the cluster supervisor case created: FileCreated => actorCluster.parserSupervisorRef ! ParseFile(created.file, sender(), created.project)(created.sourceGear) diff --git a/core/src/main/scala-2.12/com/opticdev/core/sourcegear/actors/WorkerActor.scala b/core/src/main/scala-2.12/com/opticdev/core/sourcegear/actors/WorkerActor.scala index ed864cc257..58152c46b4 100644 --- a/core/src/main/scala-2.12/com/opticdev/core/sourcegear/actors/WorkerActor.scala +++ b/core/src/main/scala-2.12/com/opticdev/core/sourcegear/actors/WorkerActor.scala @@ -18,7 +18,7 @@ class WorkerActor()(implicit actorCluster: ActorCluster) extends Actor with Requ val requestingActor = parseRequest.requestingActor val result: Try[FileParseResults] = parseRequest.sourceGear.parseFile(parseRequest.file) if (result.isSuccess) { - actorCluster.parserSupervisorRef ! AddToCache(FileNode.fromFile(parseRequest.file), result.get.astGraph, result.get.parser, result.get.fileContents) + actorCluster.parserSupervisorRef ! AddToCache(FileNode(parseRequest.file.pathAsString), result.get.astGraph, result.get.parser, result.get.fileContents) sender() tell(ParseSuccessful(result.get, parseRequest.file), requestingActor) } else { sender() tell(ParseFailed(parseRequest.file), requestingActor) @@ -30,7 +30,7 @@ class WorkerActor()(implicit actorCluster: ActorCluster) extends Actor with Requ val requestingActor = parseWithContentsRequest.requestingActor val result: Try[FileParseResults] = parseWithContentsRequest.sourceGear.parseString(parseWithContentsRequest.contents) if (result.isSuccess) { - actorCluster.parserSupervisorRef ! AddToCache(FileNode.fromFile(parseWithContentsRequest.file), result.get.astGraph, result.get.parser, parseWithContentsRequest.contents) + actorCluster.parserSupervisorRef ! AddToCache(FileNode(parseWithContentsRequest.file.pathAsString), result.get.astGraph, result.get.parser, parseWithContentsRequest.contents) sender() tell(ParseSuccessful(result.get, parseWithContentsRequest.file), requestingActor) } else { sender() tell(ParseFailed(parseWithContentsRequest.file), requestingActor) @@ -43,13 +43,15 @@ class WorkerActor()(implicit actorCluster: ActorCluster) extends Actor with Requ val fileContents = project.filesStateMonitor.contentsForFile(file).get val result: Try[FileParseResults] = ctxRequest.sourceGear.parseString(fileContents) if (result.isSuccess) { - actorCluster.parserSupervisorRef ! AddToCache(FileNode.fromFile(file), result.get.astGraph, result.get.parser, result.get.fileContents) + actorCluster.parserSupervisorRef ! AddToCache(FileNode(file.pathAsString), result.get.astGraph, result.get.parser, result.get.fileContents) sender() ! Option(SGContext( ctxRequest.sourceGear.fileAccumulator, result.get.astGraph, result.get.parser, result.get.fileContents, - ctxRequest.sourceGear)) + ctxRequest.sourceGear, + ctxRequest.fileNode.toFile + )) ctxRequest.project.projectActor ! ParseSuccessful(result.get, file) } else { ctxRequest.project.projectActor ! ParseFailed(file) diff --git a/core/src/main/scala-2.12/com/opticdev/core/sourcegear/actors/package.scala b/core/src/main/scala-2.12/com/opticdev/core/sourcegear/actors/package.scala index f029869ffb..5fe33dc5ad 100644 --- a/core/src/main/scala-2.12/com/opticdev/core/sourcegear/actors/package.scala +++ b/core/src/main/scala-2.12/com/opticdev/core/sourcegear/actors/package.scala @@ -4,8 +4,8 @@ import akka.actor.{ActorRef, ActorSystem, Props} import better.files.File import com.opticdev.core.sourcegear.actors.ParseSupervisorActor import com.opticdev.core.actorSystem -import com.opticdev.core.sourcegear.graph.FileNode -import com.opticdev.core.sourcegear.project.{ProjectBase, Project} +import com.opticdev.core.sourcegear.graph.{FileNode, ProjectGraph} +import com.opticdev.core.sourcegear.project.{Project, ProjectBase} import com.opticdev.parsers._ package object actors { @@ -20,22 +20,28 @@ package object actors { //Parser Supervisor & Worker Receive sealed trait ParserRequest { val file: File + def contents: String + def requestingActor: ActorRef + } + case class ParseFile(file: File, requestingActor: ActorRef, project: ProjectBase)(implicit val sourceGear: SourceGear) extends ParserRequest { + def contents = file.contentAsString } - case class ParseFile(file: File, requestingActor: ActorRef, project: ProjectBase)(implicit val sourceGear: SourceGear) extends ParserRequest case class ParseFileWithContents(file: File, contents: String, requestingActor: ActorRef, project: ProjectBase)(implicit val sourceGear: SourceGear) extends ParserRequest //Project Receives sealed trait ParseStatus - case class ParseSuccessful(parseResults: FileParseResults, file: File) extends ParseStatus + case class ParseSuccessful(parseResults: FileParseResults, file: File, fromCache: Boolean = false) extends ParseStatus case class ParseFailed(file: File) extends ParseStatus case class FileUpdatedInMemory(file: File, contents: String, project: ProjectBase)(implicit val sourceGear: SourceGear) case class FileUpdated(file: File, project: ProjectBase)(implicit val sourceGear: SourceGear) case class FileCreated(file: File, project: ProjectBase)(implicit val sourceGear: SourceGear) case class FileDeleted(file: File, project: ProjectBase)(implicit val sourceGear: SourceGear) case object CurrentGraph + case class SetCurrentGraph(projectGraph: ProjectGraph) case object ClearGraph case class GetContext(fileNode: FileNode)(implicit val sourceGear: SourceGear, val project: ProjectBase) case class NodeForId(id: String) + case class GetSnapshot(sourceGear: SourceGear, project: ProjectBase) } diff --git a/core/src/main/scala-2.12/com/opticdev/core/sourcegear/containers/SubContainerManager.scala b/core/src/main/scala-2.12/com/opticdev/core/sourcegear/containers/SubContainerManager.scala index d1bee6b229..f28639a346 100644 --- a/core/src/main/scala-2.12/com/opticdev/core/sourcegear/containers/SubContainerManager.scala +++ b/core/src/main/scala-2.12/com/opticdev/core/sourcegear/containers/SubContainerManager.scala @@ -1,6 +1,7 @@ package com.opticdev.core.sourcegear.containers import com.opticdev.parsers.graph.path.FlatWalkablePath +import com.opticdev.parsers.rules.Rule import com.opticdev.sdk.descriptions.finders.NodeFinder import com.opticdev.sdk.descriptions.{ChildrenRule, Rule, SubContainer} diff --git a/core/src/main/scala-2.12/com/opticdev/core/sourcegear/context/SDKObjectsResolvedImplicits.scala b/core/src/main/scala-2.12/com/opticdev/core/sourcegear/context/SDKObjectsResolvedImplicits.scala index 1b91ddbfda..ea49bc3aa9 100644 --- a/core/src/main/scala-2.12/com/opticdev/core/sourcegear/context/SDKObjectsResolvedImplicits.scala +++ b/core/src/main/scala-2.12/com/opticdev/core/sourcegear/context/SDKObjectsResolvedImplicits.scala @@ -69,14 +69,12 @@ object SDKObjectsResolvedImplicits { def qualifySchema(packageRef: PackageRef, schemaRef: SchemaRef)(implicit packageContext: Context) : SchemaRef = { - val a = if (schemaRef.packageRef.isDefined) { + if (schemaRef.packageRef.isDefined) { packageContext.getPackageContext(schemaRef.packageRef.get.packageId).get .getProperty(schemaRef.id).get.asInstanceOf[Schema].schemaRef } else { SchemaRef(Some(packageRef), schemaRef.id) } - - a } } diff --git a/core/src/main/scala-2.12/com/opticdev/core/sourcegear/gears/RuleProvider.scala b/core/src/main/scala-2.12/com/opticdev/core/sourcegear/gears/RuleProvider.scala index 42eda09529..74ee91c578 100644 --- a/core/src/main/scala-2.12/com/opticdev/core/sourcegear/gears/RuleProvider.scala +++ b/core/src/main/scala-2.12/com/opticdev/core/sourcegear/gears/RuleProvider.scala @@ -1,28 +1,34 @@ package com.opticdev.core.sourcegear.gears -import com.opticdev.sdk.descriptions.enums.RuleEnums.Exact +import com.opticdev.parsers.ParserBase import com.opticdev.sdk.descriptions.{ChildrenRule, Rule} import com.opticdev.parsers.graph.AstType +import com.opticdev.parsers.rules.{AllChildrenRule, Exact, ParserChildrenRule, Rule} import com.opticdev.sdk.descriptions.Rule -class RuleProvider(defaultRules: Map[AstType, Vector[Rule]] = Map()) { +object RuleProvider { - val globalChildrenDefaultRule = ChildrenRule(null, Exact) + val globalChildrenDefaultRule = AllChildrenRule(Exact) //@todo doing this at every node may not be performant. better way possible - def applyDefaultRulesForType(rules: Vector[Rule], astType: AstType) = { - - var updatedRules: Vector[Rule] = rules + def applyDefaultRulesForType(rules: Vector[Rule], astType: AstType)(implicit parser: ParserBase) : Vector[Rule] = { //add the default rule for children only if one is not already set if (!rules.exists(_.isChildrenRule)) { - val defaultOption = defaultRules.get(astType) - if (defaultOption.isDefined && defaultOption.get.exists(_.isChildrenRule)) { - updatedRules = updatedRules ++ defaultOption.get.filter(_.isChildrenRule) + val defaultRulesForType = parser.defaultChildrenRules.get(astType) + if (defaultRulesForType.isDefined) { + rules ++ parser.defaultChildrenRules(astType) :+ globalChildrenDefaultRule + } else { + rules :+ globalChildrenDefaultRule } + } else { + rules.map { + case i: ChildrenRule => i.asParserChildrenRule + case other => other + } ++ parser.defaultChildrenRules.getOrElse(astType, Vector()) } - updatedRules } + } diff --git a/core/src/main/scala-2.12/com/opticdev/core/sourcegear/gears/helpers/RuleEvaluation.scala b/core/src/main/scala-2.12/com/opticdev/core/sourcegear/gears/helpers/RuleEvaluation.scala index 5691abcedc..454147432a 100644 --- a/core/src/main/scala-2.12/com/opticdev/core/sourcegear/gears/helpers/RuleEvaluation.scala +++ b/core/src/main/scala-2.12/com/opticdev/core/sourcegear/gears/helpers/RuleEvaluation.scala @@ -1,13 +1,14 @@ package com.opticdev.core.sourcegear.gears.helpers +import com.opticdev.core.sourcegear.gears.RuleProvider import com.opticdev.parsers.AstGraph -import com.opticdev.parsers.graph.{CommonAstNode, Child} +import com.opticdev.parsers.graph.{Child, CommonAstNode} import com.opticdev.parsers.graph.path.FlatWalkablePath import play.api.libs.json.JsObject import com.opticdev.sdk.descriptions.{ChildrenRule, PropertyRule, RawRule, VariableRule} import com.opticdev.core.sourcegear.gears.parsing.{MatchResults, NodeDescription} import com.opticdev.core.sourcegear.variables.VariableLookupTable - +import com.opticdev.parsers.rules._ import scalax.collection.edge.LkDiEdge import scalax.collection.mutable.Graph @@ -44,12 +45,42 @@ object RuleEvaluation { } } - - implicit class ChildrenRuleWithEvaluation(childrenRule: ChildrenRule)(implicit graph: AstGraph, fileContents: String) { + implicit class ParserChildrenRuleVectorWithEvaluation(rules: Vector[ParserChildrenRule])(implicit graph: AstGraph, fileContents: String) { def evaluate(node: CommonAstNode, desc: NodeDescription, currentPath: FlatWalkablePath, compareWith: (CommonAstNode, String, NodeDescription, FlatWalkablePath) => MatchResults): MatchResults = { - val childrenVecor: Vector[(CommonAstNode, String)] = node.children.map(c=> (c._2, c._1.asInstanceOf[Child].typ)) + val specificRules = rules.collect{case r: SpecificChildrenRule => r} + val allChildrenRule = rules.collectFirst{case r: AllChildrenRule => r}.getOrElse(RuleProvider.globalChildrenDefaultRule) + + val childrenVector: Vector[(CommonAstNode, String)] = node.children.map(c=> (c._2, c._1.asInstanceOf[Child].typ)) + + val matchResults = scala.collection.mutable.ListBuffer[MatchResults]() + + val specificRulesEvaluated = specificRules.foldLeft(true) { + case (b, rule) => if (!b) false else { + val results = rule.evaluate(node, childrenVector.filter(_._2 == rule.edgeType), desc.filterChildren(_.edge.typ == rule.edgeType), currentPath, compareWith) + matchResults += results + results.isMatch + } + } + + val handledEdgeTypes = specificRules.map(_.edgeType) + val allChildrenRuleEvaluation = allChildrenRule.evaluate(node, childrenVector.filterNot(child=> handledEdgeTypes.contains(child._2)), desc.filterChildren(child=> !handledEdgeTypes.contains(child.edge.typ)), currentPath, compareWith) + + if (specificRulesEvaluated && allChildrenRuleEvaluation.isMatch) { + val combinedMatchResults = matchResults.toVector :+ allChildrenRuleEvaluation + combinedMatchResults.foldLeft(combinedMatchResults.head)(_.mergeWith(_)) + } else { + MatchResults(false, None) + } + + } + + } + + implicit class ParserChildrenRuleWithEvaluation(parserChildrenRule: ParserChildrenRule)(implicit graph: AstGraph, fileContents: String) { + + def evaluate(node: CommonAstNode, childrenVector: Vector[(CommonAstNode, String)], desc: NodeDescription, currentPath: FlatWalkablePath, compareWith: (CommonAstNode, String, NodeDescription, FlatWalkablePath) => MatchResults): MatchResults = { def equality(astNodeWithType: (CommonAstNode, String), nodeDesc: NodeDescription): MatchResults = { @@ -57,17 +88,17 @@ object RuleEvaluation { } import com.opticdev.sdk.descriptions.enums.RuleEnums._ - childrenRule.ruleType match { + parserChildrenRule.rule match { case Any => ChildrenVectorComparison.any - [(CommonAstNode, String), NodeDescription](childrenVecor, desc.children, equality) + [(CommonAstNode, String), NodeDescription](childrenVector, desc.children, equality) case Exact => ChildrenVectorComparison.exact - [(CommonAstNode, String), NodeDescription](childrenVecor, desc.children, equality) + [(CommonAstNode, String), NodeDescription](childrenVector, desc.children, equality) case SamePlus => ChildrenVectorComparison.samePlus - [(CommonAstNode, String), NodeDescription](childrenVecor, desc.children, equality) + [(CommonAstNode, String), NodeDescription](childrenVector, desc.children, equality) case SameAnyOrder => ChildrenVectorComparison.sameAnyOrder - [(CommonAstNode, String), NodeDescription](childrenVecor, desc.children, equality) + [(CommonAstNode, String), NodeDescription](childrenVector, desc.children, equality) case SameAnyOrderPlus => ChildrenVectorComparison.sameAnyOrderPlus - [(CommonAstNode, String), NodeDescription](childrenVecor, desc.children, equality) + [(CommonAstNode, String), NodeDescription](childrenVector, desc.children, equality) case _ => MatchResults(false, None) } diff --git a/core/src/main/scala-2.12/com/opticdev/core/sourcegear/gears/parsing/ParseGear.scala b/core/src/main/scala-2.12/com/opticdev/core/sourcegear/gears/parsing/ParseGear.scala index 676733694f..276e4b9479 100644 --- a/core/src/main/scala-2.12/com/opticdev/core/sourcegear/gears/parsing/ParseGear.scala +++ b/core/src/main/scala-2.12/com/opticdev/core/sourcegear/gears/parsing/ParseGear.scala @@ -17,20 +17,25 @@ import scalax.collection.edge.LkDiEdge import scalax.collection.mutable.Graph import com.opticdev.core.sourcegear.gears.helpers.RuleEvaluation.RawRuleWithEvaluation import com.opticdev.core.sourcegear.gears.helpers.RuleEvaluation.VariableRuleWithEvaluation +import com.opticdev.core.sourcegear.objects.annotations.{NameAnnotation, ObjectAnnotationParser, SourceAnnotation, TagAnnotation} import com.opticdev.core.sourcegear.variables.VariableManager import scala.util.hashing.MurmurHash3 +import com.opticdev.marvin.common.helpers.LineOperations +import com.opticdev.parsers.rules.{AllChildrenRule, ParserChildrenRule, Rule} -sealed abstract class ParseGear()(implicit val ruleProvider: RuleProvider) { +sealed abstract class ParseGear() { val description : NodeDescription val components: Map[FlatWalkablePath, Vector[Component]] val containers: Map[FlatWalkablePath, SubContainer] - val rules: Map[FlatWalkablePath, Vector[Rule]] + val rules: Map[FlatWalkablePath, Vector[RuleWithFinder]] val listeners : Vector[Listener] val packageId: String + val parsingLensRef: LensRef + val additionalParserInformation : AdditionalParserInformation val variableManager : VariableManager @@ -46,6 +51,7 @@ sealed abstract class ParseGear()(implicit val ruleProvider: RuleProvider) { def matches(entryNode: CommonAstNode, extract: Boolean = false)(implicit astGraph: AstGraph, fileContents: String, sourceGearContext: SGContext, project: ProjectBase) : Option[ParseResult[CommonAstNode]] = { + implicit val parser = sourceGearContext.parser val extractableComponents = components.mapValues(_.filter(_.isInstanceOf[CodeComponent])) //new for each search instance @@ -58,7 +64,7 @@ sealed abstract class ParseGear()(implicit val ruleProvider: RuleProvider) { def compareToDescription(node: CommonAstNode, childType: String, desc: NodeDescription, currentPath: FlatWalkablePath) : MatchResults = { val componentsAtPath = extractableComponents.getOrElse(currentPath, Vector[Component]()) val expectedSubContainerAtPath = containers.get(currentPath) - val rulesAtPath = ruleProvider.applyDefaultRulesForType(rules.getOrElse(currentPath, Vector[Rule]()), node.nodeType) + val rulesAtPath = RuleProvider.applyDefaultRulesForType(rules.getOrElse(currentPath, Vector[Rule]()), node.nodeType) val isMatch = { val nodeTypesMatch = node.nodeType == desc.astType @@ -104,17 +110,17 @@ sealed abstract class ParseGear()(implicit val ruleProvider: RuleProvider) { } else Set() - import com.opticdev.core.sourcegear.gears.helpers.RuleEvaluation.ChildrenRuleWithEvaluation + import com.opticdev.core.sourcegear.gears.helpers.RuleEvaluation.ParserChildrenRuleVectorWithEvaluation - val childrenRule = rulesAtPath.find(_.isChildrenRule).getOrElse(ruleProvider.globalChildrenDefaultRule).asInstanceOf[ChildrenRule] + val childrenRules = { + val ruleVector = rulesAtPath.collect{ case i: ParserChildrenRule => i} + if (ruleVector.isEmpty) Vector(RuleProvider.globalChildrenDefaultRule) else ruleVector + } //returns final results & extractions - val childrenResults = childrenRule.evaluate(node, desc, currentPath, compareWith) + val childrenResults = childrenRules.evaluate(node, desc, currentPath, compareWith) val foundContainer: Set[SubContainerMatch] = expectedSubContainerAtPath.map(c=> Set(SubContainerMatch(c, node))).getOrElse(Set()) -// println("LOOK HERE "+ childrenResults.containers) -// println(foundContainer) - MatchResults(childrenResults.isMatch, if (childrenResults.isMatch) Some(childrenResults.extracted.getOrElse(Set()) ++ extractedFields) else None, if (childrenResults.isMatch) Some(entryNode) else None, @@ -130,7 +136,7 @@ sealed abstract class ParseGear()(implicit val ruleProvider: RuleProvider) { output(matchResults) } - def output(matchResults: MatchResults)(implicit sourceGearContext: SGContext, project: ProjectBase) : Option[ParseResult[CommonAstNode]] = None + def output(matchResults: MatchResults)(implicit sourceGearContext: SGContext, project: ProjectBase, fileContents: String) : Option[ParseResult[CommonAstNode]] = None } @@ -140,26 +146,37 @@ case class ParseAsModel(description: NodeDescription, schema: SchemaRef, components: Map[FlatWalkablePath, Vector[Component]], containers: Map[FlatWalkablePath, SubContainer], - rules: Map[FlatWalkablePath, Vector[Rule]], + rules: Map[FlatWalkablePath, Vector[RuleWithFinder]], listeners : Vector[Listener], variableManager: VariableManager = VariableManager.empty, additionalParserInformation : AdditionalParserInformation, - packageId: String - )(implicit ruleProvider: RuleProvider) extends ParseGear { + packageId: String, + parsingLensRef: LensRef, + initialValue: JsObject = JsObject.empty + ) extends ParseGear { - override def output(matchResults: MatchResults) (implicit sourceGearContext: SGContext, project: ProjectBase) : Option[ParseResult[CommonAstNode]] = { + override def output(matchResults: MatchResults) (implicit sourceGearContext: SGContext, project: ProjectBase, fileContents: String) : Option[ParseResult[CommonAstNode]] = { if (!matchResults.isMatch) return None val fields = matchResults.extracted.getOrElse(Set()) - val model = FlattenModelFields.flattenFields(fields) + val model = FlattenModelFields.flattenFields(fields, initialValue) import com.opticdev.core.sourcegear.graph.model.MappingImplicits._ val modelMapping = fields.toMapping import com.opticdev.core.sourcegear.containers.ContainerMappingImplicits._ val containerMapping = matchResults.containers.getOrElse(Set()).toMapping - val linkedModelNode = LinkedModelNode(schema, model, matchResults.baseNode.get, modelMapping, containerMapping, this) + val (objectRefOption, sourceAnnotationOption, tagAnnotation) = { + val raw = ObjectAnnotationParser.contentsToCheck(matchResults.baseNode.get) + val annotations = ObjectAnnotationParser.extract(raw, schema)(sourceGearContext.parser) + ( annotations.collectFirst { case na: NameAnnotation => na }.map(_.objectRef), + annotations.collectFirst { case sa: SourceAnnotation => sa }, + annotations.collectFirst { case ta: TagAnnotation => ta }, + ) + } + + val linkedModelNode = LinkedModelNode(schema, model, parsingLensRef, matchResults.baseNode.get, modelMapping, containerMapping, this, objectRefOption, sourceAnnotationOption, tagAnnotation) //@todo have schema validate Option(ParseResult(this, linkedModelNode, matchResults.baseNode.get)) @@ -170,14 +187,14 @@ case class ParseAsModel(description: NodeDescription, case class ParseAsContainer(description: NodeDescription, containers: Map[FlatWalkablePath, SubContainer], - rules: Map[FlatWalkablePath, Vector[Rule]], + rules: Map[FlatWalkablePath, Vector[RuleWithFinder]], variableManager: VariableManager = VariableManager.empty, additionalParserInformation : AdditionalParserInformation, - packageId: String - )(implicit ruleProvider: RuleProvider) extends ParseGear { - - override def output(matchResults: MatchResults)(implicit sourceGearContext: SGContext, project: ProjectBase): Option[ParseResult[CommonAstNode]] = { + packageId: String, + parsingLensRef: LensRef + ) extends ParseGear { + override def output(matchResults: MatchResults)(implicit sourceGearContext: SGContext, project: ProjectBase, fileContents: String): Option[ParseResult[CommonAstNode]] = { None } diff --git a/core/src/main/scala-2.12/com/opticdev/core/sourcegear/gears/parsing/package.scala b/core/src/main/scala-2.12/com/opticdev/core/sourcegear/gears/parsing/package.scala index 09b3a2f9d3..40133a06a3 100644 --- a/core/src/main/scala-2.12/com/opticdev/core/sourcegear/gears/parsing/package.scala +++ b/core/src/main/scala-2.12/com/opticdev/core/sourcegear/gears/parsing/package.scala @@ -3,8 +3,8 @@ package com.opticdev.core.sourcegear.gears import com.opticdev.core.sourcegear.containers.{SubContainerManager, SubContainerMatch} import com.opticdev.core.sourcegear.gears.helpers.ModelField import com.opticdev.parsers._ -import com.opticdev.parsers.graph.{CommonAstNode, AstType, Child} -import com.opticdev.sdk.PropertyValue +import com.opticdev.parsers.graph.{AstType, Child, CommonAstNode} +import com.opticdev.sdk.{PropertyValue, RenderOptions} import com.opticdev.sdk.descriptions.PropertyRule import play.api.libs.json.JsObject @@ -17,7 +17,43 @@ package object parsing { extracted: Option[Set[ModelField]] = None, baseNode: Option[CommonAstNode] = None, containers: Option[Set[SubContainerMatch]] = None - ) + ) { + + def mergeWith(other: MatchResults): MatchResults = MatchResults( + this.isMatch && other.isMatch, + { + if (this.extracted.isEmpty) { + other.extracted + } else if (this.extracted.isDefined && other.extracted.isEmpty) { + this.extracted + } else if (this.extracted.isDefined && other.extracted.isDefined) { + Some(this.extracted.get ++ other.extracted.get) + } else { + other.extracted + } + }, + { + if (this.baseNode.isEmpty) { + other.baseNode + } else if (this.baseNode.isDefined && other.baseNode.isEmpty) { + this.baseNode + } else { + other.baseNode + } + }, + { + if (this.containers.isEmpty) { + other.containers + } else if (this.containers.isDefined && other.containers.isEmpty) { + this.containers + } else if (this.containers.isDefined && other.containers.isDefined) { + Some(this.containers.get ++ other.containers.get) + } else { + other.containers + } + }) + + } //Serializable for Storage case class RulesDesc() @@ -55,6 +91,9 @@ package object parsing { children.flatMap(_.flatNodes) :+ this } + def filterChildren(predicate: (NodeDescription)=> Boolean) = + this.copy(children = this.children.filter(predicate)) + } diff --git a/core/src/main/scala-2.12/com/opticdev/core/sourcegear/gears/rendering/RenderGear.scala b/core/src/main/scala-2.12/com/opticdev/core/sourcegear/gears/rendering/RenderGear.scala index 60f8a8458b..38d72d46ce 100644 --- a/core/src/main/scala-2.12/com/opticdev/core/sourcegear/gears/rendering/RenderGear.scala +++ b/core/src/main/scala-2.12/com/opticdev/core/sourcegear/gears/rendering/RenderGear.scala @@ -21,6 +21,8 @@ import scala.util.Try import scala.util.hashing.MurmurHash3 import com.opticdev.marvin.common.helpers.InRangeImplicits._ import com.opticdev.core.sourcegear.context.SDKObjectsResolvedImplicits._ +import com.opticdev.core.sourcegear.graph.model.ModelNode +import scalax.collection.mutable.Graph case class RenderGear(block: String, parserRef: ParserRef, parseGear: ParseAsModel, @@ -45,6 +47,16 @@ case class RenderGear(block: String, (fileContents, astGraph, rootNode) } + def parseAndGetModel(contents: String)(implicit sourceGear: SourceGear, context: FlatContextBase = FlatContextBuilder.empty) : Try[JsObject] = parseAndGetModelWithGraph(contents).map(_._1) + + def parseAndGetModelWithGraph(contents: String)(implicit sourceGear: SourceGear, context: FlatContextBase = FlatContextBuilder.empty): Try[(JsObject, AstGraph, ModelNode)] = Try { + implicit val (fileContents, astGraph, rootNode) = parseAndGetRoot(contents) + implicit val sourceGearContext = SGContext.forRender(sourceGear, astGraph, parserRef) + val results = sourceGear.lensSet.parseFromGraph(fileContents, astGraph, sourceGearContext, null) + val model = results.modelNodes.find(_.resolveInGraph[CommonAstNode](results.astGraph).root == rootNode).get + (model.expandedValue()(SGContext.forRender(sourceGear, results.astGraph, parserRef)), results.astGraph, model) + } + def renderWithNewAstNode(value: JsObject, containersContent: ContainersContent = Map.empty, variableMapping: VariableMapping = Map.empty)(implicit sourceGear: SourceGear, context: FlatContextBase = FlatContextBuilder.empty): (NewAstNode, String) = { implicit val (fileContents, astGraph, rootNode) = parseAndGetRoot(block) diff --git a/core/src/main/scala-2.12/com/opticdev/core/sourcegear/graph/FileNode.scala b/core/src/main/scala-2.12/com/opticdev/core/sourcegear/graph/FileNode.scala index e7b22dce1c..f13d69b4a0 100644 --- a/core/src/main/scala-2.12/com/opticdev/core/sourcegear/graph/FileNode.scala +++ b/core/src/main/scala-2.12/com/opticdev/core/sourcegear/graph/FileNode.scala @@ -3,12 +3,6 @@ package com.opticdev.core.sourcegear.graph import better.files.File import com.opticdev.parsers.utils.Crypto -case class FileNode(filePath: String, lastHash: String) extends AstProjection { +case class FileNode(filePath: String) extends AstProjection { def toFile = File(filePath) -} - -object FileNode { - def fromFile(file: File) = { - FileNode(file.pathAsString, if (file.exists) Crypto.createSha1(file.contentAsString) else null) - } } \ No newline at end of file diff --git a/core/src/main/scala-2.12/com/opticdev/core/sourcegear/graph/GraphImplicits.scala b/core/src/main/scala-2.12/com/opticdev/core/sourcegear/graph/GraphImplicits.scala index 3dddfe62da..822a92fcfe 100644 --- a/core/src/main/scala-2.12/com/opticdev/core/sourcegear/graph/GraphImplicits.scala +++ b/core/src/main/scala-2.12/com/opticdev/core/sourcegear/graph/GraphImplicits.scala @@ -11,6 +11,8 @@ import scala.collection.mutable import scalax.collection.edge.LkDiEdge import scalax.collection.mutable.Graph +import scala.util.Try + object GraphImplicits { implicit class AstGraphInstance(graph: AstGraph) { @@ -31,29 +33,29 @@ object GraphImplicits { implicit class CommonAstNodeInstance(node: CommonAstNode) { - def hasParent(parent: CommonAstNode)(implicit astGraph: AstGraph) : Boolean = { + def hasParent(parent: CommonAstNode)(implicit astGraph: AstGraph) : Boolean = Try { if (parent == null) return false val dependencies = node.dependencies(astGraph).filter(_.isAstNode()).asInstanceOf[Set[CommonAstNode]] dependencies.contains(parent) || dependencies.exists(i => i.hasParent(parent)) - } + }.getOrElse(false) - def hasChild(child: CommonAstNode)(implicit astGraph: AstGraph) : Boolean = { + def hasChild(child: CommonAstNode)(implicit astGraph: AstGraph) : Boolean = Try { if (child == null) return false val dependents = node.dependents(astGraph).filter(_.isAstNode()).asInstanceOf[Set[CommonAstNode]] dependents.contains(child) || dependents.exists(i => i.hasChild(child)) - } + }.getOrElse(false) - def siblingOf(otherNode: CommonAstNode)(implicit astGraph: AstGraph): Boolean = { + def siblingOf(otherNode: CommonAstNode)(implicit astGraph: AstGraph): Boolean = Try { if (otherNode == null) return false otherNode.dependencies == node.dependencies - } + }.getOrElse(false) } implicit class BaseModelNodeInstance(modelNode: BaseModelNode) { def astRoot()(implicit astGraph: AstGraph): CommonAstNode = modelNode match { case l: LinkedModelNode[CommonAstNode] => l.root - case _ => { + case m: ModelNode => { val dependencies = modelNode.dependencies(astGraph) if (dependencies.head.isAstNode()) { dependencies.head.asInstanceOf[CommonAstNode] diff --git a/core/src/main/scala-2.12/com/opticdev/core/sourcegear/graph/ProjectGraphWrapper.scala b/core/src/main/scala-2.12/com/opticdev/core/sourcegear/graph/ProjectGraphWrapper.scala index ce56b6ed52..82c28d4956 100644 --- a/core/src/main/scala-2.12/com/opticdev/core/sourcegear/graph/ProjectGraphWrapper.scala +++ b/core/src/main/scala-2.12/com/opticdev/core/sourcegear/graph/ProjectGraphWrapper.scala @@ -4,6 +4,8 @@ import better.files.File import com.opticdev.core.debug.DebugAstNode import com.opticdev.core.sourcegear.graph.edges.{InFile, YieldsModel} import com.opticdev.core.sourcegear.graph.model.BaseModelNode +import com.opticdev.core.sourcegear.project.{OpticProject, ProjectBase} +import com.opticdev.core.sourcegear.sync.SyncGraph import com.opticdev.parsers.AstGraph import com.opticdev.parsers.graph.{CommonAstNode, CustomEdge, WithinFile} import com.opticdev.parsers.utils.Crypto @@ -18,10 +20,10 @@ import scalax.collection.mutable.Graph import scala.collection.mutable object ProjectGraphWrapper { - def empty = new ProjectGraphWrapper(Graph[AstProjection, LkDiEdge]()) + def empty()(implicit project: ProjectBase) = new ProjectGraphWrapper(Graph[AstProjection, LkDiEdge]()) } -class ProjectGraphWrapper(val projectGraph: ProjectGraph) { +class ProjectGraphWrapper(val projectGraph: ProjectGraph)(implicit val project: ProjectBase) { import GraphImplicits._ @@ -53,7 +55,7 @@ class ProjectGraphWrapper(val projectGraph: ProjectGraph) { private def astGraphToProjectGraph(astGraph: AstGraph, forFile: File): ProjectGraph = { val newProjectGraph = Graph[AstProjection, LkDiEdge]() - val fileNode = FileNode(forFile.pathAsString, Crypto.createSha1(forFile.contentAsString)) + val fileNode = FileNode(forFile.pathAsString) astGraph.edges.toVector.foreach(edge => { val fromNode = edge._1.value val toNode = edge._2.value @@ -89,7 +91,7 @@ class ProjectGraphWrapper(val projectGraph: ProjectGraph) { def query(nodeFilter: (projectGraph.NodeT) => Boolean): Set[AstProjection] = projectGraph.nodes.collect { - case n: projectGraph.NodeT if (nodeFilter(n)) => n.value + case n: projectGraph.NodeT if nodeFilter(n) => n.value }.toSet def prettyPrint = { diff --git a/core/src/main/scala-2.12/com/opticdev/core/sourcegear/graph/edges/DerivedFrom.scala b/core/src/main/scala-2.12/com/opticdev/core/sourcegear/graph/edges/DerivedFrom.scala new file mode 100644 index 0000000000..8c0f7272b4 --- /dev/null +++ b/core/src/main/scala-2.12/com/opticdev/core/sourcegear/graph/edges/DerivedFrom.scala @@ -0,0 +1,11 @@ +package com.opticdev.core.sourcegear.graph.edges + +import com.opticdev.parsers.graph.CustomEdge +import com.opticdev.sdk.descriptions.transformation.TransformationRef +import play.api.libs.json.JsObject + +import scala.util.hashing.MurmurHash3 + +case class DerivedFrom(transformationRef: TransformationRef, askAnswers: JsObject) extends CustomEdge { + def hash = Integer.toHexString(MurmurHash3.stringHash(transformationRef.toString + askAnswers.toString())) +} \ No newline at end of file diff --git a/core/src/main/scala-2.12/com/opticdev/core/sourcegear/graph/edges/InFile.scala b/core/src/main/scala-2.12/com/opticdev/core/sourcegear/graph/edges/InFile.scala index 7b685685ac..dfc7ebda6e 100644 --- a/core/src/main/scala-2.12/com/opticdev/core/sourcegear/graph/edges/InFile.scala +++ b/core/src/main/scala-2.12/com/opticdev/core/sourcegear/graph/edges/InFile.scala @@ -2,4 +2,4 @@ package com.opticdev.core.sourcegear.graph.edges import com.opticdev.parsers.graph.CustomEdge -case class InFile(atRange: Range) extends CustomEdge +case class InFile(atRange: Range) extends CustomEdge \ No newline at end of file diff --git a/core/src/main/scala-2.12/com/opticdev/core/sourcegear/graph/model/ModelNode.scala b/core/src/main/scala-2.12/com/opticdev/core/sourcegear/graph/model/ModelNode.scala index b36698feef..fd9247a93d 100644 --- a/core/src/main/scala-2.12/com/opticdev/core/sourcegear/graph/model/ModelNode.scala +++ b/core/src/main/scala-2.12/com/opticdev/core/sourcegear/graph/model/ModelNode.scala @@ -1,13 +1,15 @@ package com.opticdev.core.sourcegear.graph.model -import com.opticdev.sdk.descriptions.SchemaRef -import com.opticdev.core.sourcegear.SGContext +import com.opticdev.common.ObjectRef +import com.opticdev.sdk.descriptions.{LensRef, SchemaRef} +import com.opticdev.core.sourcegear.{AstDebugLocation, CompiledLens, SGContext} import com.opticdev.core.sourcegear.actors.ActorCluster import com.opticdev.core.sourcegear.containers.ContainerAstMapping import com.opticdev.core.sourcegear.gears.helpers.FlattenModelFields import com.opticdev.core.sourcegear.gears.parsing.ParseGear import com.opticdev.core.sourcegear.graph.edges.{ContainerRoot, YieldsModel, YieldsModelProperty, YieldsProperty} -import com.opticdev.core.sourcegear.graph.{AstProjection, FileNode} +import com.opticdev.core.sourcegear.graph.{AstProjection, FileNode, ProjectGraph} +import com.opticdev.core.sourcegear.objects.annotations.{SourceAnnotation, TagAnnotation} import com.opticdev.core.sourcegear.project.{OpticProject, Project, ProjectBase} import com.opticdev.parsers.AstGraph import com.opticdev.parsers.graph.{BaseNode, CommonAstNode, WithinFile} @@ -21,10 +23,23 @@ import scala.util.hashing.MurmurHash3 sealed abstract class BaseModelNode(implicit val project: ProjectBase) extends AstProjection { val schemaId : SchemaRef val value : JsObject + val objectRef: Option[ObjectRef] + val sourceAnnotation: Option[SourceAnnotation] + val tag: Option[TagAnnotation] + val lensRef: LensRef + + def hash: String + + def fileNode: Option[FileNode] = { + import com.opticdev.core.sourcegear.graph.GraphImplicits._ + project.projectGraph + .allPredecessorOf(this).find(_.isInstanceOf[FileNode]) + .asInstanceOf[Option[FileNode]] + } - lazy val fileNode: Option[FileNode] = { - import com.opticdev.core.sourcegear.graph.GraphImplicits._ - project.projectGraph + def fileNode(projectGraph: ProjectGraph): Option[FileNode] = { + import com.opticdev.core.sourcegear.graph.GraphImplicits._ + projectGraph .allPredecessorOf(this).find(_.isInstanceOf[FileNode]) .asInstanceOf[Option[FileNode]] } @@ -35,8 +50,9 @@ sealed abstract class BaseModelNode(implicit val project: ProjectBase) extends A val listenersOption = sourceGearContext.fileAccumulator.listeners.get(schemaId) if (listenersOption.isDefined) { - val modelFields = listenersOption.get.map(i => i.collect(sourceGearContext.astGraph, this, sourceGearContext)) - expandedValueStore = Option(FlattenModelFields.flattenFields(modelFields, value)) + val modelFields = Try(listenersOption.get.map(i => i.collect(sourceGearContext.astGraph, this, sourceGearContext))) + modelFields.failed.foreach(i=> i.printStackTrace()) + expandedValueStore = Option(FlattenModelFields.flattenFields(modelFields.get, value)) } else { expandedValueStore = Option(value) } @@ -46,31 +62,58 @@ sealed abstract class BaseModelNode(implicit val project: ProjectBase) extends A def getContext()(implicit actorCluster: ActorCluster, project: ProjectBase): Try[SGContext] = Try(SGContext.forModelNode(this).get) + def resolved()(implicit actorCluster: ActorCluster): LinkedModelNode[CommonAstNode] = this match { + case l: LinkedModelNode[CommonAstNode] => l + case m: ModelNode => m.resolve[CommonAstNode]() + } + + def flatten : ModelNode + + def includedInSync: Boolean = sourceAnnotation.isDefined || tag.isDefined || objectRef.isDefined + } -case class LinkedModelNode[N <: WithinFile](schemaId: SchemaRef, value: JsObject, root: N, modelMapping: ModelAstMapping, containerMapping: ContainerAstMapping, parseGear: ParseGear)(implicit override val project: ProjectBase) extends BaseModelNode { +case class LinkedModelNode[N <: WithinFile](schemaId: SchemaRef, value: JsObject, lensRef: LensRef, root: N, modelMapping: ModelAstMapping, containerMapping: ContainerAstMapping, parseGear: ParseGear, objectRef: Option[ObjectRef], sourceAnnotation: Option[SourceAnnotation], tag: Option[TagAnnotation])(implicit override val project: ProjectBase) extends BaseModelNode { + + //not sure this is a better approach +// def hash = Integer.toHexString( +// MurmurHash3.stringHash(schemaId.full) ^ +// MurmurHash3.stringHash(value.toString()) ^ +// MurmurHash3.stringHash(lensRef.full) ^ +// MurmurHash3.stringHash(root.hash) ^ +// MurmurHash3.mapHash(modelMapping) ^ +// MurmurHash3.mapHash(containerMapping) ^ +// MurmurHash3.stringHash(objectRef.toString) ^ +// MurmurHash3.stringHash(sourceAnnotation.toString)) + + def hash = Integer.toHexString(MurmurHash3.stringHash(root.toString + modelMapping.toString + sourceAnnotation.toString + objectRef.toString + containerMapping.toString)) + def flatten = { - val hash = MurmurHash3.stringHash(root.toString() + modelMapping.toString() + containerMapping.toString()) - ModelNode(schemaId, value, hash) + ModelNode(schemaId, value, lensRef, objectRef, sourceAnnotation, tag, hash) } override lazy val fileNode: Option[FileNode] = flatten.fileNode + + def toDebugLocation = AstDebugLocation(fileNode.map(_.filePath).getOrElse(""), root.range) } -case class ModelNode(schemaId: SchemaRef, value: JsObject, hash: Int)(implicit override val project: ProjectBase) extends BaseModelNode { +case class ModelNode(schemaId: SchemaRef, value: JsObject, lensRef: LensRef, objectRef: Option[ObjectRef], sourceAnnotation: Option[SourceAnnotation], tag: Option[TagAnnotation], hash: String)(implicit override val project: ProjectBase) extends BaseModelNode { //@todo check how stable/collision prone this is - override val id: String = Integer.toHexString(hash) + override val id: String = hash - def resolve[T <: WithinFile]()(implicit actorCluster: ActorCluster) : LinkedModelNode[T] = { - implicit val sourceGearContext = SGContext.forModelNode(this).get - implicit val astGraph = sourceGearContext.astGraph + def resolveInGraph[T <: WithinFile](graph: AstGraph) : LinkedModelNode[T] = { + implicit val astGraph = graph val labeledDependencies = astGraph.get(this).labeledDependencies val root : T = labeledDependencies.find(i=> i._1.isInstanceOf[YieldsModel] && i._1.asInstanceOf[YieldsModel].root) - .get._2.asInstanceOf[T] - + .get._2.asInstanceOf[T] val parseGear = astGraph.get(this).labeledDependencies.find(_._1.isInstanceOf[YieldsModel]).get._1.asInstanceOf[YieldsModel].withParseGear + LinkedModelNode(schemaId, value, lensRef, root, modelMapping, containerMapping, parseGear, objectRef, sourceAnnotation, tag) + } - LinkedModelNode(schemaId, value, root, modelMapping, containerMapping, parseGear) + def resolve[T <: WithinFile]()(implicit actorCluster: ActorCluster) : LinkedModelNode[T] = { + implicit val sourceGearContext = SGContext.forModelNode(this).get + implicit val astGraph = sourceGearContext.astGraph + resolveInGraph(astGraph) } def modelMapping(implicit astGraph: AstGraph) : ModelAstMapping = { @@ -101,4 +144,6 @@ case class ModelNode(schemaId: SchemaRef, value: JsObject, hash: Int)(implicit o }.toMap } + def flatten : ModelNode = this + } diff --git a/core/src/main/scala-2.12/com/opticdev/core/sourcegear/graph/package.scala b/core/src/main/scala-2.12/com/opticdev/core/sourcegear/graph/package.scala index 28442e4376..faace094a7 100644 --- a/core/src/main/scala-2.12/com/opticdev/core/sourcegear/graph/package.scala +++ b/core/src/main/scala-2.12/com/opticdev/core/sourcegear/graph/package.scala @@ -1,7 +1,7 @@ package com.opticdev.core.sourcegear +import com.opticdev.core.sourcegear.graph.model.BaseModelNode import com.opticdev.parsers.graph.BaseNode - import scalax.collection.edge.LkDiEdge import scalax.collection.mutable.Graph @@ -9,7 +9,9 @@ package object graph { trait AstProjection extends BaseNode { val id : String = null + def isModel : Boolean = this.isInstanceOf[BaseModelNode] } type ProjectGraph = Graph[AstProjection, LkDiEdge] + type SyncGraph = ProjectGraph } diff --git a/core/src/main/scala-2.12/com/opticdev/core/sourcegear/mutate/MutationSteps.scala b/core/src/main/scala-2.12/com/opticdev/core/sourcegear/mutate/MutationSteps.scala index 105aed5f86..f1abee8b02 100644 --- a/core/src/main/scala-2.12/com/opticdev/core/sourcegear/mutate/MutationSteps.scala +++ b/core/src/main/scala-2.12/com/opticdev/core/sourcegear/mutate/MutationSteps.scala @@ -108,7 +108,6 @@ object MutationSteps { case NodeMapping(node, relationship) => contents.updateRange(node.range, change.replacementString.get) } } - } } diff --git a/core/src/main/scala-2.12/com/opticdev/core/sourcegear/objects/annotations/ObjectAnnotationParser.scala b/core/src/main/scala-2.12/com/opticdev/core/sourcegear/objects/annotations/ObjectAnnotationParser.scala new file mode 100644 index 0000000000..e375f8ad96 --- /dev/null +++ b/core/src/main/scala-2.12/com/opticdev/core/sourcegear/objects/annotations/ObjectAnnotationParser.scala @@ -0,0 +1,96 @@ +package com.opticdev.core.sourcegear.objects.annotations + +import com.opticdev.common.PackageRef +import com.opticdev.marvin.common.helpers.LineOperations +import com.opticdev.parsers.ParserBase +import com.opticdev.parsers.graph.{BaseNode, CommonAstNode} +import com.opticdev.sdk.descriptions.SchemaRef +import com.opticdev.marvin.common.helpers.InRangeImplicits._ +import com.opticdev.sdk.descriptions.transformation.TransformationRef + +import scala.util.{Failure, Success, Try} +import scala.util.matching.Regex + +object ObjectAnnotationParser { + + def extract(string: String, schemaRef: SchemaRef)(implicit parserBase: ParserBase) : Set[ObjectAnnotation] = + extract(string, schemaRef, parserBase.inlineCommentPrefix) + + def extract(string: String, schemaRef: SchemaRef, inlineCommentPrefix: String) : Set[ObjectAnnotation] = { + if (string.isEmpty) return Set() + + val found = { + val lineContents = string.lines.next() + val lastComment = findAnnotationComment(inlineCommentPrefix, lineContents) + lastComment.map(i=> extractRawAnnotationsFromLine(i.substring(inlineCommentPrefix.size))) + }.map(_.map(pair=> { + pair._1 match { + case "name" => Some(NameAnnotation(pair._2.name, schemaRef)) + case "source" => pair._2 match { + case exp: ExpressionValue => { + Some(SourceAnnotation(exp.name, exp.transformationRef, exp.askJsObject)) + } + case _ => None + } + case "tag" => Some(TagAnnotation(pair._2.name, schemaRef)) + case _ => None + } + }).collect {case Some(a)=> a} + .toSet.asInstanceOf[Set[ObjectAnnotation]]) + + found.getOrElse(Set()) + } + + def extractRawAnnotationsFromLine(string: String) : Map[String, AnnotationValues] = { + if (topLevelCapture.pattern.matcher(string).matches()) { + val extracts = propertiesCapture.findAllIn(string).matchData.map { + i => + Try { + val key = i.group("key").trim + val name = i.group("name").trim + val transform = i.group("transformRef") + val askOption = Option(i.group("askJson")) + + val value = if (transform != null) { + + val namespace = i.group("namespace") + val packageName = i.group("packageName") + val version = Option(i.group("version")).getOrElse("latest") + val id = i.group("id") + + require(!Set(namespace, packageName, version, id).contains(null)) + + val transformationRef = TransformationRef(Some(PackageRef(namespace + ":" + packageName, version)), id) + + ExpressionValue(name, transformationRef, askOption) + } else { + StringValue(name) + } + + (key, value) + } + } + extracts.collect {case Success(a) => a} .toMap + } else Map.empty + } + + def contentsToCheck(node: CommonAstNode)(implicit fileContents: String) = { + val range = node.range + val startLine = LineOperations.lineOf(range.start, fileContents) + val endLine = LineOperations.lineOf(range.end, fileContents) + + if (startLine == endLine) { + val lines = fileContents.substring(range.start).lines + if (lines.nonEmpty) lines.next() else "" + } else { + fileContents.substring(node) + } + + } + + def findAnnotationComment(inlineCommentPrefix: String, contents: String) : Option[String] = { + val result = contents.lastIndexOf(inlineCommentPrefix) + if (result == -1) None else Some(contents.substring(result)) + } + +} diff --git a/core/src/main/scala-2.12/com/opticdev/core/sourcegear/objects/annotations/ObjectAnnotationRenderer.scala b/core/src/main/scala-2.12/com/opticdev/core/sourcegear/objects/annotations/ObjectAnnotationRenderer.scala new file mode 100644 index 0000000000..00a4eb1285 --- /dev/null +++ b/core/src/main/scala-2.12/com/opticdev/core/sourcegear/objects/annotations/ObjectAnnotationRenderer.scala @@ -0,0 +1,25 @@ +package com.opticdev.core.sourcegear.objects.annotations + +object ObjectAnnotationRenderer { + def render(inlineCommentPrefix: String, annotations: Vector[ObjectAnnotation]) : String = { + val inner = annotations.map(_.asString).mkString(",") + //2 spaces of padding + s" $inlineCommentPrefix$inner" + } + + def renderToFirstLine(inlineCommentPrefix: String, annotations: Vector[ObjectAnnotation], contents: String) : String = { + val result = render(inlineCommentPrefix, annotations) + + val s = contents + val lines = s.lines.toVector + val firstNonEmptyLine = lines.indexWhere(_.trim.nonEmpty) + + if (firstNonEmptyLine == -1) { + result + } else { + val newFirstLine = lines.lift(firstNonEmptyLine).getOrElse("")+result + lines.patch(firstNonEmptyLine, Vector(newFirstLine), 1).mkString("\n") + } + } + +} diff --git a/core/src/main/scala-2.12/com/opticdev/core/sourcegear/objects/annotations/package.scala b/core/src/main/scala-2.12/com/opticdev/core/sourcegear/objects/annotations/package.scala new file mode 100644 index 0000000000..1528734f1b --- /dev/null +++ b/core/src/main/scala-2.12/com/opticdev/core/sourcegear/objects/annotations/package.scala @@ -0,0 +1,42 @@ +package com.opticdev.core.sourcegear.objects + +import com.opticdev.common.ObjectRef +import com.opticdev.sdk.descriptions.SchemaRef +import play.api.libs.json.{JsObject, JsString, JsValue, Json} +import com.opticdev.common.Regexes.packages +import com.opticdev.sdk.descriptions.transformation.TransformationRef + +import scala.util.Try +import scala.util.matching.Regex + +package object annotations { + + //Annotation Value Classes + sealed trait AnnotationValues {val name: String} + case class StringValue(name: String) extends AnnotationValues + case class ExpressionValue(name: String, transformationRef: TransformationRef, askJsonRaw: Option[String]) extends AnnotationValues { + def askJsObject: Option[JsObject] = askJsonRaw.map(Json.parse).map(_.as[JsObject]) + } + + //Processed Annotation Classes + sealed trait ObjectAnnotation { + def asString: String + } + case class NameAnnotation(name: String, schemaRef: SchemaRef) extends ObjectAnnotation { + def objectRef = ObjectRef(name) + def asString = s"name: $name" + } + case class SourceAnnotation(sourceName: String, transformationRef: TransformationRef, askObject: Option[JsObject]) extends ObjectAnnotation { + def asString = s"source: $sourceName -> ${transformationRef.internalFull} ${askObject.map(_.toString()).getOrElse("")}" + def asJson: JsValue = JsString(s"$sourceName") + } + case class TagAnnotation(tag: String, schemaRef: SchemaRef) extends ObjectAnnotation { + def asString = s"tag: $tag" + } + + //Regexes + def topLevelCapture = "^(\\s*([a-z]+)\\s*:\\s*[a-zA-z \\-\\>\\{\\}\\.\\d\\@\\/\\:\\'\\\"]+)(,\\s*([a-z]+)\\s*:\\s*[a-zA-z \\-\\>\\{\\}\\.\\d\\@\\/\\:\\'\\\"]+)*".r + def propertiesCapture = s"\\s*([a-z]+)\\s*:\\s*([a-zA-z ]+)(?:\\s*->\\s*($packages)\\s*(\\{.*\\}){0,1}){0,1}" + .r("key", "name", "transformRef", "namespace", "packageName", "version", "id", "askJson") + +} diff --git a/core/src/main/scala-2.12/com/opticdev/core/sourcegear/package.scala b/core/src/main/scala-2.12/com/opticdev/core/sourcegear/package.scala index f958a59d80..0c212d22be 100644 --- a/core/src/main/scala-2.12/com/opticdev/core/sourcegear/package.scala +++ b/core/src/main/scala-2.12/com/opticdev/core/sourcegear/package.scala @@ -1,7 +1,9 @@ package com.opticdev.core import com.opticdev.core.sourcegear.graph.model.{BaseModelNode, ModelNode} +import com.opticdev.core.sourcegear.project.ProjectBase import com.opticdev.parsers.{AstGraph, ParserBase} +import play.api.libs.json.Json package object sourcegear { @@ -9,4 +11,8 @@ package object sourcegear { case class FileParseResults(astGraph: AstGraph, modelNodes: Vector[ModelNode], parser: ParserBase, fileContents: String) + case class AstDebugLocation(filePath: String, range: Range)(implicit project: ProjectBase) { + override def toString: String = s"${range.start}, ${range.end} in ${project.trimAbsoluteFilePath(filePath)}" + } + } diff --git a/core/src/main/scala-2.12/com/opticdev/core/sourcegear/project/OpticProject.scala b/core/src/main/scala-2.12/com/opticdev/core/sourcegear/project/OpticProject.scala index 600e0c191c..7a7766f7af 100644 --- a/core/src/main/scala-2.12/com/opticdev/core/sourcegear/project/OpticProject.scala +++ b/core/src/main/scala-2.12/com/opticdev/core/sourcegear/project/OpticProject.scala @@ -15,11 +15,14 @@ import com.opticdev.core.sourcegear.graph.{ProjectGraph, ProjectGraphWrapper} import com.opticdev.core.sourcegear.project.config.ProjectFile import com.opticdev.core.sourcegear.project.monitoring.{FileStateMonitor, ShouldWatch} import com.opticdev.core.sourcegear.project.status.ProjectStatus +import com.opticdev.core.sourcegear.snapshot.Snapshot +import com.opticdev.core.sourcegear.sync.{DiffSyncGraph, SyncPatch} +import com.opticdev.core.utils.ScheduledTask import com.opticdev.opm.providers.ProjectKnowledgeSearchPaths import net.jcazevedo.moultingyaml.YamlString import scala.concurrent.ExecutionContext.Implicits.global -import scala.concurrent.{Await, Future} +import scala.concurrent.{Await, Future, Promise} import scala.concurrent.duration._ import scala.util.{Failure, Success} @@ -29,7 +32,7 @@ abstract class OpticProject(val name: String, val baseDirectory: File)(implicit /* Project Actor setup */ - val projectActor: ActorRef = actorCluster.newProjectActor + val projectActor: ActorRef = actorCluster.newProjectActor()(project = this) /* Private & public declarations of the project status & info */ @@ -71,11 +74,7 @@ abstract class OpticProject(val name: String, val baseDirectory: File)(implicit projectStatusInstance.firstPassStatus = InProgress - val futures = filesToWatch.toSeq.map(i=> { - projectActor ? FileCreated(i, this) - }).map( - _.map(Success(_)).recover { case t => Failure(t) } - ) + val futures = filesToWatch.toSeq.map(i=> projectActor ? FileCreated(i, this)) Future.sequence(futures).onComplete(i=> { projectStatusInstance.firstPassStatus = Complete @@ -88,7 +87,8 @@ abstract class OpticProject(val name: String, val baseDirectory: File)(implicit implicit val sourceGear = projectSourcegear projectStatusInstance.touch filesStateMonitor.markUpdated(file) - if (shouldWatchFile(file)) projectActor ! FileCreated(file, this) + if (shouldWatchFile(file)) projectActor ! FileCreated(file, this) else + if (inProjectKnowledgeSearchPaths(file)) rereadAll } case (EventType.ENTRY_MODIFY, file) => { implicit val sourceGear = projectSourcegear @@ -97,14 +97,16 @@ abstract class OpticProject(val name: String, val baseDirectory: File)(implicit if (file.isSameFileAs(projectFile.file)) { projectFile.reload } else { - if (shouldWatchFile(file)) projectActor ! FileUpdated(file, this) + if (shouldWatchFile(file)) projectActor ! FileUpdated(file, this) else + if (inProjectKnowledgeSearchPaths(file)) rereadAll } } case (EventType.ENTRY_DELETE, file) => { implicit val sourceGear = projectSourcegear filesStateMonitor.markUpdated(file) projectStatusInstance.touch - if (shouldWatchFile(file)) projectActor ! FileDeleted(file, this) + if (shouldWatchFile(file)) projectActor ! FileDeleted(file, this) else + if (inProjectKnowledgeSearchPaths(file)) rereadAll } } @@ -113,6 +115,14 @@ abstract class OpticProject(val name: String, val baseDirectory: File)(implicit projectStatusInstance.monitoringStatus = NotWatching } +// /* Sync Monitoring */ scoped out of use in 1.0. Syncs will be triggered manually +// protected val syncMonitor = new ScheduledTask(10 seconds, ()=> { +// implicit val timeout = Timeout(2 minutes) +// val future = projectActor ? CalculateSyncStatus +// val syncStatus = Await.result(future, timeout.duration).asInstanceOf[SyncStatus] +// println("CHECKING SYNC STATUS "+ syncStatus) +// projectStatusInstance.syncStatus = syncStatus +// }, 8 seconds).start def projectGraph: ProjectGraph = { implicit val timeout = Timeout(15 seconds) @@ -142,4 +152,18 @@ abstract class OpticProject(val name: String, val baseDirectory: File)(implicit projectFile.interface.get.exclude.value.map(i=> File(i.value)) ++ projectSourcegear.excludedPaths.map(i=> File(i))) def filesToWatch : Set[File] = baseDirectory.listRecursively.toVector.filter(shouldWatchFile).toSet + + def snapshot: Future[Snapshot] = { + implicit val timeout: akka.util.Timeout = Timeout(1 minute) + (projectActor ? GetSnapshot(projectSourcegear, this)).mapTo[Future[Snapshot]].flatten + } + + def syncPatch: Future[SyncPatch] = { + implicit val timeout: akka.util.Timeout = Timeout(1 minute) + snapshot.map(snapshot=> DiffSyncGraph.calculateDiff(snapshot)(this)) + } + + def inProjectKnowledgeSearchPaths(file: File) = + file.extension(false).contains("md") && projectFile.projectKnowledgeSearchPaths.dirs.exists(searchPath=> file.isChildOf(searchPath)) + } diff --git a/core/src/main/scala-2.12/com/opticdev/core/sourcegear/project/Project.scala b/core/src/main/scala-2.12/com/opticdev/core/sourcegear/project/Project.scala index 57aa08724f..bcf99ec3a5 100644 --- a/core/src/main/scala-2.12/com/opticdev/core/sourcegear/project/Project.scala +++ b/core/src/main/scala-2.12/com/opticdev/core/sourcegear/project/Project.scala @@ -41,8 +41,9 @@ class Project(name: String, baseDirectory: File)(implicit logToCli: Boolean = fa } } - override def projectSourcegear = sourceGear - + override def projectSourcegear = synchronized { + sourceGear + } private val sourcegearchangedCallbacks = scala.collection.mutable.ListBuffer[(SourceGear)=> Unit]() override def onSourcegearChanged(callback: (SourceGear)=> Unit) : Unit = diff --git a/core/src/main/scala-2.12/com/opticdev/core/sourcegear/project/StaticSGProject.scala b/core/src/main/scala-2.12/com/opticdev/core/sourcegear/project/StaticSGProject.scala index 0acc16add1..1a7a8c9553 100644 --- a/core/src/main/scala-2.12/com/opticdev/core/sourcegear/project/StaticSGProject.scala +++ b/core/src/main/scala-2.12/com/opticdev/core/sourcegear/project/StaticSGProject.scala @@ -1,17 +1,37 @@ package com.opticdev.core.sourcegear.project +import akka.util.Timeout import better.files.File import com.opticdev.core.sourcegear.SourceGear -import com.opticdev.core.sourcegear.actors.ActorCluster +import com.opticdev.core.sourcegear.actors.{ActorCluster, SetCurrentGraph} +import com.opticdev.core.sourcegear.graph.{ProjectGraph, ProjectGraphWrapper} import com.opticdev.core.sourcegear.project.config.ProjectFile import com.opticdev.core.sourcegear.project.status._ +import scala.concurrent.duration._ +import scala.concurrent.Await +import akka.pattern.ask -class StaticSGProject(name: String, baseDirectory: File, sourceGear: SourceGear)(implicit logToCli: Boolean = false, actorCluster: ActorCluster) extends OpticProject(name, baseDirectory) { + +//@todo consider moving this class to a test suite. Has no use in prod + +class StaticSGProject(name: String, baseDirectory: File, sourceGear: SourceGear)(implicit actorCluster: ActorCluster) extends OpticProject(name, baseDirectory) { override def projectFileChanged(newPf: ProjectFile): Unit = {} override def projectSourcegear: SourceGear = sourceGear + private var projectGraphStore : ProjectGraph = ProjectGraphWrapper.empty()(project = this).projectGraph + def stageProjectGraph(projectGraph: ProjectGraph) = { + projectGraphStore = projectGraph + } + override def projectGraph: ProjectGraph = projectGraphStore + + override def projectGraphWrapper: ProjectGraphWrapper = { + new ProjectGraphWrapper(projectGraph)(project = this) + } + projectStatusInstance.configStatus = ValidConfig projectStatusInstance.sourceGearStatus = Valid +// syncMonitor.cancel() + } diff --git a/core/src/main/scala-2.12/com/opticdev/core/sourcegear/project/package.scala b/core/src/main/scala-2.12/com/opticdev/core/sourcegear/project/package.scala index 40f7d6e894..11a4829112 100644 --- a/core/src/main/scala-2.12/com/opticdev/core/sourcegear/project/package.scala +++ b/core/src/main/scala-2.12/com/opticdev/core/sourcegear/project/package.scala @@ -2,11 +2,15 @@ package com.opticdev.core.sourcegear import akka.actor.ActorRef import better.files.File +import com.opticdev.core.sourcegear.actors.ActorCluster import com.opticdev.core.sourcegear.graph.ProjectGraph import com.opticdev.core.sourcegear.project.monitoring.FileStateMonitor import com.opticdev.core.sourcegear.project.status.ImmutableProjectStatus +import com.opticdev.core.sourcegear.sync.SyncPatch import play.api.libs.json.{JsObject, JsString} +import scala.concurrent.Future + package object project { case class ProjectInfo(name: String, baseDir: String, status: ImmutableProjectStatus) { def asJson : JsObject = JsObject(Seq("name" -> JsString(name), "directory" -> JsString(baseDir), "status" -> status.asJson)) @@ -14,12 +18,25 @@ package object project { trait ProjectBase { val name: String + val baseDirectory: File val projectActor: ActorRef val projectStatus: ImmutableProjectStatus val filesStateMonitor : FileStateMonitor + val actorCluster: ActorCluster def projectSourcegear : SourceGear def projectGraph: ProjectGraph + def syncPatch: Future[SyncPatch] def shouldWatchFile(file: File) : Boolean + + def trimAbsoluteFilePath(filePath: String) = { + val split = filePath.split(baseDirectory.pathAsString) + if (split.size == 2 && split(0).isEmpty) { + split(1) + } else { + filePath + } + } + } } diff --git a/core/src/main/scala-2.12/com/opticdev/core/sourcegear/project/status/ImmutableProjectStatus.scala b/core/src/main/scala-2.12/com/opticdev/core/sourcegear/project/status/ImmutableProjectStatus.scala index 2107038877..408e60375f 100644 --- a/core/src/main/scala-2.12/com/opticdev/core/sourcegear/project/status/ImmutableProjectStatus.scala +++ b/core/src/main/scala-2.12/com/opticdev/core/sourcegear/project/status/ImmutableProjectStatus.scala @@ -10,12 +10,14 @@ class ImmutableProjectStatus(projectStatus: ProjectStatus) { def configStatus = projectStatus.configStatus def firstPassStatus = projectStatus.firstPassStatus def lastUpdate = projectStatus.lastUpdate + def syncStatus = projectStatus.syncStatus def sourcegearChanged(callback: (SourceGearStatus)=> Unit) = projectStatus.sourcegearChanged(callback) def monitoringChanged(callback: (MonitoringStatus)=> Unit) = projectStatus.monitoringChanged(callback) def configChanged(callback: (ConfigStatus)=> Unit) = projectStatus.configChanged(callback) def firstPassChanged(callback: (FirstPassStatus)=> Unit) = projectStatus.firstPassChanged(callback) + def syncStatusChanged(callback: (SyncStatus)=> Unit) = projectStatus.syncStatusChanged(callback) def statusChanged(callback: (ProjectStatusCase, ImmutableProjectStatus)=> Unit) = projectStatus.statusChanged(callback) def isValid: Boolean = projectStatus.isValid @@ -35,7 +37,8 @@ class ImmutableProjectStatus(projectStatus: ProjectStatus) { "isValid"-> JsBoolean(isValid), "isLoading"-> JsBoolean(isLoading), "hasErrors"-> JsBoolean(errors.value.nonEmpty), - "errors" -> errors + "errors" -> errors, + "syncStatus" -> JsString(syncStatus.toString) )) } diff --git a/core/src/main/scala-2.12/com/opticdev/core/sourcegear/project/status/ProjectStatus.scala b/core/src/main/scala-2.12/com/opticdev/core/sourcegear/project/status/ProjectStatus.scala index c8671b2c91..b1c8465436 100644 --- a/core/src/main/scala-2.12/com/opticdev/core/sourcegear/project/status/ProjectStatus.scala +++ b/core/src/main/scala-2.12/com/opticdev/core/sourcegear/project/status/ProjectStatus.scala @@ -10,6 +10,7 @@ class ProjectStatus(private var _loadedStatus: LoadedStatusCase = Loaded, private var _monitoringStatus: MonitoringStatus = NotWatching, private var _configStatus: ConfigStatus = ValidConfig, private var _firstPassStatus: FirstPassStatus = NotStarted, + private var _syncStatus: SyncStatus = UpToDate, private var _lastUpdate: LastUpdateDate = LastUpdateDate(Calendar.getInstance().getTime)) { //@todo consolidate replace with macros @@ -47,6 +48,13 @@ class ProjectStatus(private var _loadedStatus: LoadedStatusCase = Loaded, if (changed) notify(s) } + def syncStatus = _syncStatus + def syncStatus_=(s: SyncStatus): Unit = { + val changed = _syncStatus != s + _syncStatus = s + if (changed) notify(s) + } + def lastUpdate = _lastUpdate def lastUpdate_=(s: LastUpdateDate): Unit = { val changed = _lastUpdate != s @@ -79,6 +87,11 @@ class ProjectStatus(private var _loadedStatus: LoadedStatusCase = Loaded, firstPassChangedCallbacks = firstPassChangedCallbacks + callback } + private var syncStatusChangedCallbacks = Set[(SyncStatus)=> Unit]() + def syncStatusChanged(callback: (SyncStatus)=> Unit) = { + syncStatusChangedCallbacks = syncStatusChangedCallbacks + callback + } + private var statusChangedCallbacks = Set[(ProjectStatusCase, ImmutableProjectStatus)=> Unit]() def statusChanged(callback: (ProjectStatusCase, ImmutableProjectStatus)=> Unit) = { statusChangedCallbacks = statusChangedCallbacks + callback @@ -91,6 +104,7 @@ class ProjectStatus(private var _loadedStatus: LoadedStatusCase = Loaded, case a: MonitoringStatus => monitoringChangedCallbacks.foreach(i=> i(a)) case a: ConfigStatus => configChangedCallbacks.foreach(i=> i(a)) case a: FirstPassStatus => firstPassChangedCallbacks.foreach(i=> i(a)) + case a: SyncStatus => syncStatusChangedCallbacks.foreach(i=> i(a)) case _ => } //send to any status changed callbacks diff --git a/core/src/main/scala-2.12/com/opticdev/core/sourcegear/project/status/package.scala b/core/src/main/scala-2.12/com/opticdev/core/sourcegear/project/status/package.scala index 9c7a5aa336..039a9de509 100644 --- a/core/src/main/scala-2.12/com/opticdev/core/sourcegear/project/status/package.scala +++ b/core/src/main/scala-2.12/com/opticdev/core/sourcegear/project/status/package.scala @@ -29,6 +29,11 @@ package object status { case object InProgress extends FirstPassStatus case object Complete extends FirstPassStatus + sealed trait SyncStatus extends ProjectStatusCase + case object UpToDate extends SyncStatus + case object SyncPending extends SyncStatus + case class ErrorSyncing(error: String) extends SyncStatus + case class LastUpdateDate(time: Date) extends ProjectStatusCase } diff --git a/core/src/main/scala-2.12/com/opticdev/core/sourcegear/serialization/PickleImplicits.scala b/core/src/main/scala-2.12/com/opticdev/core/sourcegear/serialization/PickleImplicits.scala index 751a940beb..b94b0b66e1 100644 --- a/core/src/main/scala-2.12/com/opticdev/core/sourcegear/serialization/PickleImplicits.scala +++ b/core/src/main/scala-2.12/com/opticdev/core/sourcegear/serialization/PickleImplicits.scala @@ -53,7 +53,7 @@ object PickleImplicits extends PicklerHelper { } implicit val childrenRuleTypeEnumPickler = { - import com.opticdev.sdk.descriptions.enums.RuleEnums._ + import com.opticdev.parsers.rules._ compositePickler[ChildrenRuleTypeEnum] .addConcreteType[Any.type] .addConcreteType[Exact.type] @@ -129,9 +129,6 @@ object PickleImplicits extends PicklerHelper { .addConcreteType[ObjectProperty] - //@todo this should be moved within the parsers - implicit val ruleProvider = new RuleProvider() - implicit object ConmpiledLensPickler extends Pickler[CompiledLens] { override def pickle(value: CompiledLens)(implicit state: PickleState): Unit = { state.pickle(value.name) diff --git a/core/src/main/scala-2.12/com/opticdev/core/sourcegear/snapshot/Snapshot.scala b/core/src/main/scala-2.12/com/opticdev/core/sourcegear/snapshot/Snapshot.scala new file mode 100644 index 0000000000..7bf5fdaf48 --- /dev/null +++ b/core/src/main/scala-2.12/com/opticdev/core/sourcegear/snapshot/Snapshot.scala @@ -0,0 +1,71 @@ +package com.opticdev.core.sourcegear.snapshot + +import akka.actor.ActorRef +import akka.dispatch.Futures +import com.opticdev.core.sourcegear.{SGContext, SourceGear} +import com.opticdev.core.sourcegear.actors.GetContext +import com.opticdev.core.sourcegear.graph.model.{BaseModelNode, LinkedModelNode, ModelNode} +import com.opticdev.core.sourcegear.graph.{FileNode, ProjectGraph} +import com.opticdev.core.sourcegear.project.ProjectBase +import com.opticdev.parsers.graph.CommonAstNode +import play.api.libs.json.JsObject +import akka.pattern.ask +import akka.util.Timeout + +import scala.collection.mutable +import scala.concurrent.ExecutionContext.Implicits.global +import scala.concurrent.Future +import scala.concurrent.duration._ + +//A snapshot of a project's state. contains all the information required to calculate a sync patch. +case class Snapshot(projectGraph: ProjectGraph, + sourceGear: SourceGear, + linkedModelNodes: Map[ModelNode, LinkedModelNode[CommonAstNode]], + expandedValues: Map[ModelNode, JsObject], + files: Map[ModelNode, FileNode], + contextForNode: Map[ModelNode, SGContext]) + +object Snapshot { + implicit private val timeout: akka.util.Timeout = Timeout(1 minute) + + def forProject(implicit project: ProjectBase): Future[Snapshot] = + forSourceGearAndProjectGraph(project.projectSourcegear, project.projectGraph, project.actorCluster.parserSupervisorRef, project) + + /* use this if you're within an actor so you don't block it */ + def forSourceGearAndProjectGraph(implicit sourceGear: SourceGear, projectGraph: ProjectGraph, parserSupervisorRef: ActorRef, projectBase: ProjectBase): Future[Snapshot] = { + + val modelNodes = projectGraph.nodes.collect { + case a if a.value.isModel && + a.value.asInstanceOf[BaseModelNode].includedInSync => a.value.asInstanceOf[ModelNode] + } + + val files: Map[ModelNode, FileNode] = modelNodes.map{ + case mn => (mn, mn.fileNode(projectGraph).get) + }.toMap + + val contexts = modelNodes.map { + case mn => (parserSupervisorRef ? GetContext(files(mn))).mapTo[Option[SGContext]].map(context=> { + (mn, context.get) + }) + } + + Future.sequence(contexts).map(contextsResolved=> { + val contextForNode: Map[ModelNode, SGContext] = contextsResolved.toMap + + val linkedNodes: Map[ModelNode, LinkedModelNode[CommonAstNode]] = modelNodes.map { + case node : ModelNode => (node, node.resolveInGraph[CommonAstNode](contextForNode(node).astGraph)) + }.toMap + + val expandedValues: Map[ModelNode, JsObject] = modelNodes.map{ + case node : ModelNode => { + val linkedNode = linkedNodes(node) + val expandedValue = linkedNode.expandedValue()(contextForNode(node)) + (node, expandedValue) + } + }.toMap + + Snapshot(projectGraph, sourceGear, linkedNodes, expandedValues, files, contextForNode) + }) + + } +} \ No newline at end of file diff --git a/core/src/main/scala-2.12/com/opticdev/core/sourcegear/storage/GearStorage.scala b/core/src/main/scala-2.12/com/opticdev/core/sourcegear/storage/GearStorage.scala index 598ea020bb..ad42310144 100644 --- a/core/src/main/scala-2.12/com/opticdev/core/sourcegear/storage/GearStorage.scala +++ b/core/src/main/scala-2.12/com/opticdev/core/sourcegear/storage/GearStorage.scala @@ -13,8 +13,6 @@ import com.opticdev.core.sourcegear.serialization.PickleImplicits._ import scala.util.{Failure, Try} object GearStorage { - //@todo this has to come from preferences... - implicit val rulesProvider = new RuleProvider() def writeToStorage(gear: CompiledLens): File = { val file = DataDirectory.compiled / gear.id createIfNotExists(asDirectory = false) diff --git a/core/src/main/scala-2.12/com/opticdev/core/sourcegear/storage/SGConfigStorage.scala b/core/src/main/scala-2.12/com/opticdev/core/sourcegear/storage/SGConfigStorage.scala index bd1334d416..e29f0b4027 100644 --- a/core/src/main/scala-2.12/com/opticdev/core/sourcegear/storage/SGConfigStorage.scala +++ b/core/src/main/scala-2.12/com/opticdev/core/sourcegear/storage/SGConfigStorage.scala @@ -14,8 +14,6 @@ import com.opticdev.opm.DependencyTree import scala.util.{Failure, Try} object SGConfigStorage { - //@todo this has to come from preferences... - implicit val rulesProvider = new RuleProvider() def writeToStorage(sgConfig: SGConfig, projectFileHash: String): File = { val file = DataDirectory.sourcegear / projectFileHash createIfNotExists(asDirectory = false) diff --git a/core/src/main/scala-2.12/com/opticdev/core/sourcegear/sync/DiffSyncGraph.scala b/core/src/main/scala-2.12/com/opticdev/core/sourcegear/sync/DiffSyncGraph.scala new file mode 100644 index 0000000000..07a6bfb4d1 --- /dev/null +++ b/core/src/main/scala-2.12/com/opticdev/core/sourcegear/sync/DiffSyncGraph.scala @@ -0,0 +1,169 @@ +package com.opticdev.core.sourcegear.sync + +import com.opticdev.core.sourcegear.actors.ActorCluster +import com.opticdev.core.sourcegear.context.FlatContextBase +import com.opticdev.core.sourcegear.{Render, SGContext, SourceGear} +import com.opticdev.core.sourcegear.graph.ProjectGraph +import com.opticdev.core.sourcegear.graph.edges.DerivedFrom +import com.opticdev.core.sourcegear.graph.model.{BaseModelNode, ModelNode} +import com.opticdev.core.sourcegear.objects.annotations.TagAnnotation +import com.opticdev.core.sourcegear.project.ProjectBase +import com.opticdev.parsers.graph.{BaseNode, CommonAstNode} +import com.opticdev.sdk.RenderOptions +import com.opticdev.sdk.descriptions.transformation.{StagedNode, Transformation} +import jdk.internal.org.objectweb.asm.tree.analysis.SourceValue +import play.api.libs.json.{JsObject, JsString} +import scalax.collection.edge.LkDiEdge +import scalax.collection.mutable.Graph +import com.opticdev.marvin.common.helpers.InRangeImplicits._ + +import scala.concurrent.ExecutionContext.Implicits.global +import scala.collection.mutable +import scala.concurrent.Future +import scala.util.Try +import com.opticdev.core.sourcegear.graph.GraphImplicits._ +import com.opticdev.core.sourcegear.mutate.MutationSteps.{collectFieldChanges, combineChanges, handleChanges} +import com.opticdev.core.sourcegear.snapshot.Snapshot +import com.opticdev.parsers.ParserBase + +object DiffSyncGraph { + + def calculateDiff(snapshot: Snapshot)(implicit project: ProjectBase, includeNoChange: Boolean = false) : SyncPatch = { + + implicit val sourceGear = snapshot.sourceGear + + val resultsFromSyncGraph = SyncGraph.getSyncGraph(snapshot) + val projectPlusSyncGraph: Graph[BaseNode, LkDiEdge] = resultsFromSyncGraph.syncGraph.asInstanceOf[Graph[BaseNode, LkDiEdge]] + implicit val graph: Graph[BaseNode, LkDiEdge] = projectPlusSyncGraph.filter(projectPlusSyncGraph.having(edge = (e) => e.isLabeled && e.label.isInstanceOf[DerivedFrom])) + + val startingNodes= graph.nodes.collect { case n if n.dependencies.isEmpty => n.value.asInstanceOf[BaseModelNode] }.toVector + + def compareDiffAlongPath(sourceNode: BaseModelNode, predecessorDiff: Option[SyncDiff] = None) : Vector[SyncDiff] = { + val sourceValue = { + if (predecessorDiff.exists(_.newValue.isDefined)) { + predecessorDiff.get.newValue.get + } else { + snapshot.expandedValues(sourceNode.flatten) + } + } + sourceNode.labeledDependents.toVector.flatMap { + case (label: DerivedFrom, targetNode: BaseModelNode) => { + + val diff = compareNode(label, sourceNode, sourceValue, targetNode)(sourceGear, snapshot, project) + + val diffWithTrigger = diff match { + //if its parent has a trigger, take it... + case r: Replace if predecessorDiff.exists(_.isInstanceOf[Replace]) && predecessorDiff.get.asInstanceOf[Replace].trigger.isDefined => r.copy(trigger = predecessorDiff.get.asInstanceOf[Replace].trigger) + //if its the trigger set it equal to itself + case r: Replace if predecessorDiff.isEmpty || predecessorDiff.exists(_.newValue.isEmpty) => r.copy(trigger = Some(Trigger(sourceNode.objectRef.get.name, sourceNode.schemaId, r.after))) + //return self + case d=> d + } + + Vector(diffWithTrigger) ++ compareDiffAlongPath(targetNode, Some(diffWithTrigger)) + + } + case _ => Vector() ///should never be hit + } + } + SyncPatch(startingNodes.flatMap(i=> compareDiffAlongPath(i)).filterNot(i=> i.isInstanceOf[NoChange] && !includeNoChange), resultsFromSyncGraph.warnings) + } + + def compareNode(label: DerivedFrom, sourceNode: BaseModelNode, sourceValue: JsObject, targetNode: BaseModelNode)(implicit sourceGear: SourceGear, snapshot: Snapshot, project: ProjectBase) = { + import com.opticdev.core.sourcegear.graph.GraphImplicits._ + val extractValuesTry = for { + transformation <- Try(sourceGear.findTransformation(label.transformationRef).getOrElse(throw new Error(s"No Transformation with id '${label.transformationRef.full}' found"))) + transformationResult <- transformation.transformFunction.transform(sourceValue, label.askAnswers) + (currentValue, linkedModel, context) <- Try { + (snapshot.expandedValues(targetNode.flatten), snapshot.linkedModelNodes(targetNode.flatten), snapshot.contextForNode(targetNode.flatten)) + } + (expectedValue, expectedRaw) <- Try { + + val prefixedFlatContent: FlatContextBase = sourceGear.flatContext.prefix(transformation.packageId.packageId) + + val stagedNode = transformationResult.toStagedNode(Some(RenderOptions( + lensId = Some(targetNode.lensRef.full) + ))) + + implicit val sourceGearContext: SGContext = snapshot.contextForNode(targetNode.flatten) + val tagVector = sourceGearContext.astGraph.nodes.filter(_.value match { + case mn: BaseModelNode if mn.tag.isDefined && + stagedNode.tags.map(_._1).contains(mn.tag.get.tag) && + stagedNode.tagsMap(mn.tag.get.tag).schema.matchLoose(mn.schemaId) && //reduces ambiguity. need a long term fix. + linkedModel.root.hasChild(snapshot.linkedModelNodes(mn.flatten).root)(sourceGearContext.astGraph) => true + case _ => false + }).map(i=> (i.value.asInstanceOf[BaseModelNode].tag.get.tag, i.value.asInstanceOf[BaseModelNode])) + .toVector + .sortBy(t=> stagedNode.tags.indexWhere(_._1 == t)) + + val tagPatches: Seq[SyncDiff] = tagVector.collect { + case (tag, targetTagNode) => { + val tagStaged = stagedNode.tagsMap(tag) + val tagStagedValues = expectedValuesForStagedNode(tagStaged, prefixedFlatContent) + val targetTagValue = targetTagNode.expandedValue() + if (tagStagedValues._1 == targetTagValue) { + NoChange(label, targetTagNode.tag) + } else { + UpdatedTag(tag, label, targetTagNode, targetTagValue, tagStagedValues._1, null) + } + } + } + + val rawAfterTags = tagPatches.foldLeft(sourceGearContext.fileContents.substring(linkedModel.root)) { + case (current: String, ut: UpdatedTag) => + updateNodeFromRaw(stagedNode, Some(ut.modelNode.tag.get), ut.after, current)(sourceGear, prefixedFlatContent, context.parser) + + case (c, p) => c + } + + val (expectedValue, expectedRaw) = expectedValuesForStagedNode(stagedNode, prefixedFlatContent) + val newExpectedRaw = updateNodeFromRaw(stagedNode, None, expectedValue, rawAfterTags)(sourceGear, prefixedFlatContent, context.parser) + (expectedValue, newExpectedRaw) + } + } yield (expectedValue, currentValue, linkedModel, expectedRaw, context) + + if (extractValuesTry.isSuccess) { + val (expectedValue, currentValue, linkedModel, expectedRaw, context) = extractValuesTry.get + if (expectedValue == currentValue) { + NoChange(label) + } else { + Replace(label, targetNode.schemaId, currentValue, expectedValue, + RangePatch(linkedModel.root.range, expectedRaw, context.file, context.fileContents)) + } + } else { +// println(extractValuesTry.failed.get.printStackTrace()) + ErrorEvaluating(label, extractValuesTry.failed.get.getMessage, snapshot.linkedModelNodes(targetNode.flatten).toDebugLocation) + } + + } + + def expectedValuesForStagedNode(stagedNode: StagedNode, context: FlatContextBase)(implicit sourceGear: SourceGear): (JsObject, String) = { + val variables = stagedNode.options.flatMap(_.variables).getOrElse(Map.empty) + val generatedNode = Render.fromStagedNode(stagedNode, variables)(sourceGear, context).get + val value = generatedNode._3.renderer.parseAndGetModel(generatedNode._2)(sourceGear, context).get + val raw = generatedNode._2 + (value, raw) + } + + def updateNodeFromRaw(masterStagedNode: StagedNode, targetNodeOption: Option[TagAnnotation], newValue: JsObject, raw: String)(implicit sourceGear: SourceGear, context: FlatContextBase, parser: ParserBase) = { + import com.opticdev.core.sourcegear.mutate.MutationImplicits._ + val lens = Render.resolveLens(masterStagedNode).get + val (value, astGraph, modelNode) = lens.renderer.parseAndGetModelWithGraph(raw).get + + implicit val sourceGearContext = SGContext.forRender(sourceGear, astGraph, parser.parserRef) + implicit val fileContents = raw + + if (targetNodeOption.isDefined) { + val tag = targetNodeOption.get + val variables = masterStagedNode.variablesForTag(tag.tag) + val taggedModelNode = astGraph.modelNodes.find(_.tag.contains(tag)).get.asInstanceOf[ModelNode].resolveInGraph[CommonAstNode](astGraph) + val variableChanges = taggedModelNode.parseGear.variableManager.changesFromMapping(variables) + taggedModelNode.update(newValue, Some(variableChanges)) + } else { + val variableChanges = lens.parser.variableManager.changesFromMapping(masterStagedNode.options.flatMap(_.variables).getOrElse(Map.empty)) + modelNode.resolveInGraph[CommonAstNode](astGraph).update(newValue, Some(variableChanges)) + } + + } + +} diff --git a/core/src/main/scala-2.12/com/opticdev/core/sourcegear/sync/SyncGraph.scala b/core/src/main/scala-2.12/com/opticdev/core/sourcegear/sync/SyncGraph.scala new file mode 100644 index 0000000000..f01fa460be --- /dev/null +++ b/core/src/main/scala-2.12/com/opticdev/core/sourcegear/sync/SyncGraph.scala @@ -0,0 +1,87 @@ +package com.opticdev.core.sourcegear.sync + +import com.opticdev.core.sourcegear.graph.edges.DerivedFrom +import com.opticdev.core.sourcegear.graph.{AstProjection, ProjectGraph, ProjectGraphWrapper, SyncGraph} +import com.opticdev.core.sourcegear.graph.model.BaseModelNode +import com.opticdev.parsers.AstGraph +import com.opticdev.parsers.graph.BaseNode +import scalax.collection.edge.Implicits._ +import scalax.collection.edge.LkDiEdge +import scalax.collection.constrained._ +import com.opticdev.core.sourcegear.graph.GraphImplicits._ +import com.opticdev.core.sourcegear.project.{OpticProject, ProjectBase} +import com.opticdev.core.sourcegear.snapshot.Snapshot +import play.api.libs.json.JsObject +import scalax.collection.GraphPredef +import scalax.collection.constrained.constraints.Acyclic +import scalax.collection.constrained.mutable.Graph +// import scalax.collection.constrained.constraints.Acyclic + +import scala.util.Try + +//only call from a project actor + +/* +Invalid States: +1. Multiple models with the same name -> None work, warnings generated +2. Sources that don't connect to a valid model name -> no edges, warnings generated +3. Circular dependencies -> no edges, warnings generated + + */ + +object SyncGraph { + + def emptySyncGraph: SyncGraph = { + implicit val conf: Config = Acyclic + Graph() + } + + def syncGraphFromProjectGraph(pg: ProjectGraph) = pg.filter(pg.having(edge = (e) => e.isLabeled && e.label.isInstanceOf[DerivedFrom])) + + def getSyncGraph(snapshot: Snapshot) : SyncSubGraph = { + val projectGraph = snapshot.projectGraph + val syncSubgraph = emptySyncGraph + val warnings = scala.collection.mutable.ListBuffer[() => SyncWarning]() + var validTargets = 0 + + def hasName(baseNode: BaseNode) = baseNode.isInstanceOf[BaseModelNode] && baseNode.asInstanceOf[BaseModelNode].objectRef.isDefined + def hasSource(baseNode: BaseNode) = baseNode.isInstanceOf[BaseModelNode] && baseNode.asInstanceOf[BaseModelNode].sourceAnnotation.isDefined + + val pgDefinesNames = projectGraph.nodes.collect { case i if hasName(i) => i.value.asInstanceOf[BaseModelNode]} + val pgDefinesSources = projectGraph.nodes.collect { case i if hasSource(i) => i.value.asInstanceOf[BaseModelNode]} + + val allNames = { + val an = pgDefinesNames + val duplicates = an.groupBy(_.objectRef.get.name).filter(_._2.size > 1).keys + duplicates.foreach(dup=> warnings += { + () => DuplicateSourceName(dup, Try(an.map(mn=> snapshot.linkedModelNodes(mn.flatten).toDebugLocation).toVector).getOrElse(Vector())) + }) + an.filterNot(_.objectRef.exists(i=> duplicates.exists(_ == i.name))) + } + + val unifiedTargets = pgDefinesSources + unifiedTargets + .foreach(targetNode=> { + val sourceAnnotation = targetNode.sourceAnnotation.get + val sourceName = sourceAnnotation.sourceName + val sourceNodeOption = allNames.find(_.objectRef.exists(_.name == sourceName)) + + if (sourceNodeOption.isDefined) { + validTargets += 1 + val didAdd = syncSubgraph add (sourceNodeOption.get ~+#> targetNode)(DerivedFrom(sourceAnnotation.transformationRef, sourceAnnotation.askObject.getOrElse(JsObject.empty))) + if (!didAdd) { + warnings += { + () => CircularDependency(sourceName, snapshot.linkedModelNodes(targetNode.flatten).toDebugLocation) + } + } + } else { + warnings += { + () => SourceDoesNotExist(sourceName, snapshot.linkedModelNodes(targetNode.flatten).toDebugLocation) + } + } + }) + + SyncSubGraph(allNames.size, validTargets, warnings.map(_.apply()).toVector, projectGraph ++ syncSubgraph) + } + +} diff --git a/core/src/main/scala-2.12/com/opticdev/core/sourcegear/sync/SyncPatch.scala b/core/src/main/scala-2.12/com/opticdev/core/sourcegear/sync/SyncPatch.scala new file mode 100644 index 0000000000..cd0a7471b7 --- /dev/null +++ b/core/src/main/scala-2.12/com/opticdev/core/sourcegear/sync/SyncPatch.scala @@ -0,0 +1,64 @@ +package com.opticdev.core.sourcegear.sync +import com.opticdev.core.sourcegear.project.ProjectBase +import com.opticdev.core.utils.StringBuilderImplicits._ +import play.api.libs.json._ + +import scala.collection.immutable + +case class SyncPatch(changes: Vector[SyncDiff], warnings: Vector[SyncWarning])(implicit project: ProjectBase) { + def containsErrors = changes.exists(_.isError) + def noErrors = !containsErrors + + def isEmpty = changes.isEmpty + def nonEmpty = changes.nonEmpty + + def errors: Vector[ErrorEvaluating] = changes.collect { case e: ErrorEvaluating => e } + + def filePatches: Vector[FilePatch] = { + val rangePatches = changes.collect { + case a: Replace => a.rangePatch + } + + val fileRangePatches = rangePatches.groupBy(_.file) + + fileRangePatches.map { + case (file, patches) => { + val newFileContents = + patches + .sortBy(_.range.end) + .reverse + .foldLeft ( new StringBuilder(patches.head.fileContents) ) { + case (contents, change) => { + contents.updateRange(change.range, change.newRaw) + } + }.toString() + + FilePatch(patches.head.file, patches.head.fileContents, newFileContents) + } + }.toVector.filter(_.causesSourceChange) + } + + def triggers: Map[Trigger, Vector[Replace]] = { + changes.collect { case a: Replace => a }.groupBy(_.trigger.get) + } + + def asJson : JsValue = JsObject(Seq( + "projectName" -> JsString(project.name), + "warnings" -> JsArray(warnings.map(_.asJson)), + "errors" -> JsArray(errors.map(_.asJson)), + "changes" -> JsArray(filePatches.map(fp=> fp.asJson(project.trimAbsoluteFilePath(fp.file.pathAsString)))), + "triggers" -> { + JsArray(triggers.map { + case (trigger, changes) => { + import com.opticdev.core.sourcegear.sync.triggerFormat + val jsObject = Json.toJsObject(trigger) + val groupedBySchema = changes.groupBy(_.schemaRef) + val changeString = JsArray(groupedBySchema.map(i=> JsString(s"${i._2.length} ${if (i._2.length == 1) "instance" else "instances"} of ${i._1.internalFull}")).toSeq) + jsObject + ("changes" -> changeString) + } + }.toSeq) + } + )) + + +} \ No newline at end of file diff --git a/core/src/main/scala-2.12/com/opticdev/core/sourcegear/sync/package.scala b/core/src/main/scala-2.12/com/opticdev/core/sourcegear/sync/package.scala new file mode 100644 index 0000000000..8547acc86a --- /dev/null +++ b/core/src/main/scala-2.12/com/opticdev/core/sourcegear/sync/package.scala @@ -0,0 +1,78 @@ +package com.opticdev.core.sourcegear + +import better.files.File +import com.opticdev.core.sourcegear.graph.ProjectGraph +import com.opticdev.core.sourcegear.graph.edges.DerivedFrom +import play.api.libs.json._ +import scalax.collection.edge.LkDiEdge +import com.opticdev.common.fileFormat +import com.opticdev.core.sourcegear.graph.model.BaseModelNode +import com.opticdev.core.sourcegear.objects.annotations.TagAnnotation +import com.opticdev.core.sourcegear.project.ProjectBase +import com.opticdev.sdk.descriptions.SchemaRef + +package object sync { + + case class SyncSubGraph(sources: Int, targets: Int, warnings: Vector[SyncWarning], syncGraph: ProjectGraph) { + def noWarnings : Boolean = warnings.isEmpty + } + + sealed trait SyncWarning { def asJson: JsValue } + case class DuplicateSourceName(name: String, locations: Vector[AstDebugLocation]) extends SyncWarning { + override def asJson: JsValue = JsObject(Seq( + "message" -> JsString(s"Source name '$name' is defined in multiple locations. All instances will be ignored"), + "locations" -> JsArray(locations.map(i=> JsString(i.toString)) + ))) + } + + case class SourceDoesNotExist(missingSource: String, location: AstDebugLocation) extends SyncWarning { + override def asJson: JsValue = JsObject(Seq( + "message" -> JsString(s"Source '${missingSource}' was not found."), + "locations" -> JsArray(Seq(JsString(location.toString))) + )) + } + + case class CircularDependency(targeting: String, location: AstDebugLocation) extends SyncWarning { + override def asJson: JsValue = JsObject(Seq( + "message" -> JsString(s"Using '${targeting}' as a source would lead to a circular dependency. This instance will be ignored."), + "locations" -> JsArray(Seq(JsString(location.toString))) + )) + } + + implicit val triggerFormat = Json.format[Trigger] + case class Trigger(name: String, schemaRef: SchemaRef, newValue: JsObject) + + case class RangePatch(range: Range, newRaw: String, file: File, fileContents: String) + trait FilePatchTrait { + def file: File + def originalFileContents: String + def newFileContents: String + } + + implicit val filePatchFormat = Json.format[FilePatch] + case class FilePatch(file: File, originalFileContents: String, newFileContents: String) extends FilePatchTrait { + def asJson(relativePath: String) = Json.toJson[FilePatch](this).as[JsObject] + ("relativePath" -> JsString(relativePath)) + def causesSourceChange : Boolean = originalFileContents != newFileContents + } + + sealed trait SyncDiff { + val edge: DerivedFrom + def newValue : Option[JsObject] = None + def isError : Boolean = false + } + + case class NoChange(edge: DerivedFrom, tagOption: Option[TagAnnotation] = None) extends SyncDiff + case class Replace(edge: DerivedFrom, schemaRef: SchemaRef, before: JsObject, after: JsObject, rangePatch: RangePatch, trigger: Option[Trigger] = None) extends SyncDiff { override def newValue = Some(after) } + case class UpdatedTag(tag: String, edge: DerivedFrom, modelNode: BaseModelNode, before: JsObject, after: JsObject, rangePatch: RangePatch) extends SyncDiff { override def newValue = Some(after) } + case class ErrorEvaluating(edge: DerivedFrom, error: String, location: AstDebugLocation) extends SyncDiff { + override def isError: Boolean = true + private def message = s""""${error}" encountered when calculating patch""" + override def toString: String = s""""${message}. Check location $location""" + def asJson: JsValue = JsObject(Seq( + "message" -> JsString(message), + "locations" -> JsArray(Seq(JsString(location.toString))) + )) + + } + +} diff --git a/core/src/main/scala-2.12/com/opticdev/core/sourcegear/variables/VariableManager.scala b/core/src/main/scala-2.12/com/opticdev/core/sourcegear/variables/VariableManager.scala index c70ae4359b..ecd2a464a2 100644 --- a/core/src/main/scala-2.12/com/opticdev/core/sourcegear/variables/VariableManager.scala +++ b/core/src/main/scala-2.12/com/opticdev/core/sourcegear/variables/VariableManager.scala @@ -5,6 +5,7 @@ import com.opticdev.core.sourcegear.gears.parsing.NodeDescription import com.opticdev.parsers.{IdentifierNodeDesc, ParserBase} import com.opticdev.parsers.graph.{AstType, CommonAstNode} import com.opticdev.parsers.graph.path.PropertyPathWalker +import com.opticdev.parsers.rules.Rule import com.opticdev.sdk.VariableMapping import com.opticdev.sdk.descriptions.finders.NodeFinder import com.opticdev.sdk.descriptions.{PropertyRule, Rule, Variable, VariableRule} diff --git a/core/src/main/scala-2.12/com/opticdev/core/utils/ScheduledTask.scala b/core/src/main/scala-2.12/com/opticdev/core/utils/ScheduledTask.scala new file mode 100644 index 0000000000..56273b0ca6 --- /dev/null +++ b/core/src/main/scala-2.12/com/opticdev/core/utils/ScheduledTask.scala @@ -0,0 +1,15 @@ +package com.opticdev.core.utils +import com.opticdev.core.sourcegear.actors.ActorCluster + +import scala.concurrent.duration._ + +class ScheduledTask(interval: FiniteDuration, task: ()=> Unit, initial: FiniteDuration = 0 seconds)(implicit actorCluster: ActorCluster) { + def start = { + val scheduler = actorCluster.actorSystem.scheduler + implicit val executor = actorCluster.actorSystem.dispatcher + scheduler.schedule( + initialDelay = initial, + interval = interval, + runnable = () => task()) + } +} diff --git a/core/src/test/scala-2.12/com/opticdev/core/Fixture/DummyCompilerOutputs.scala b/core/src/test/scala-2.12/com/opticdev/core/Fixture/DummyCompilerOutputs.scala index e3f6895fe6..f902251cc3 100644 --- a/core/src/test/scala-2.12/com/opticdev/core/Fixture/DummyCompilerOutputs.scala +++ b/core/src/test/scala-2.12/com/opticdev/core/Fixture/DummyCompilerOutputs.scala @@ -1,18 +1,18 @@ package com.opticdev.core.Fixture +import com.opticdev.common.PackageRef import com.opticdev.core.sourcegear.gears.RuleProvider import com.opticdev.core.sourcegear.gears.parsing.{AdditionalParserInformation, NodeDescription, ParseAsModel} import com.opticdev.core.sourcegear.gears.rendering.RenderGear import com.opticdev.core.sourcegear.variables.VariableManager import com.opticdev.parsers.{IdentifierNodeDesc, ParserRef} import com.opticdev.parsers.graph.AstType -import com.opticdev.sdk.descriptions.SchemaRef +import com.opticdev.sdk.descriptions.{LensRef, SchemaRef} +import play.api.libs.json.JsObject object DummyCompilerOutputs { - private implicit val ruleProvider = new RuleProvider() - val parser = ParseAsModel( NodeDescription(AstType("test", "test"), Range(1,10), @@ -27,7 +27,9 @@ object DummyCompilerOutputs { Vector(), VariableManager.empty, AdditionalParserInformation(IdentifierNodeDesc(AstType("A", "B"), Seq()), Seq()), - "test:test" + "test:test", + LensRef(Some(PackageRef("test:test")), "place-lens"), + JsObject.empty ) val render = RenderGear( diff --git a/core/src/test/scala-2.12/com/opticdev/core/Fixture/ExampleSourcegearFixtures.scala b/core/src/test/scala-2.12/com/opticdev/core/Fixture/ExampleSourcegearFixtures.scala index 4a9ea455af..2e8a4c7b29 100644 --- a/core/src/test/scala-2.12/com/opticdev/core/Fixture/ExampleSourcegearFixtures.scala +++ b/core/src/test/scala-2.12/com/opticdev/core/Fixture/ExampleSourcegearFixtures.scala @@ -8,8 +8,8 @@ import com.opticdev.core.sourcegear.{CompiledLens, LensSet, SourceGear} import com.opticdev.parsers.{ParserBase, SourceParserManager} import com.opticdev.sdk.descriptions._ import com.opticdev.sdk.descriptions.enums.FinderEnums.{Containing, Entire, Starting} -import com.opticdev.sdk.descriptions.enums.RuleEnums.SameAnyOrderPlus -import com.opticdev.sdk.descriptions.enums.{RuleEnums, VariableEnums} +import com.opticdev.parsers.rules._ +import com.opticdev.sdk.descriptions.enums.VariableEnums import com.opticdev.sdk.descriptions.finders.StringFinder import play.api.libs.json.JsObject @@ -45,8 +45,8 @@ object ExampleSourcegearFixtures extends TestBase with GearUtils with ParserUtil ), variables = Vector(Variable("err", VariableEnums.Self), Variable("item", VariableEnums.Self)), subContainers = Vector( - SubContainer("success", Vector(), RuleEnums.Any, Vector()), - SubContainer("failure", Vector(), RuleEnums.Any, Vector()) + SubContainer("success", Vector(), Any, Vector()), + SubContainer("failure", Vector(), Any, Vector()) ) ) diff --git a/core/src/test/scala-2.12/com/opticdev/core/Fixture/TestBase.scala b/core/src/test/scala-2.12/com/opticdev/core/Fixture/TestBase.scala index 0240ab3bf5..f40cb43ea8 100644 --- a/core/src/test/scala-2.12/com/opticdev/core/Fixture/TestBase.scala +++ b/core/src/test/scala-2.12/com/opticdev/core/Fixture/TestBase.scala @@ -46,7 +46,7 @@ trait TestBase extends FunSpecLike with BeforeAndAfterAll { start - implicit val sourceGearContext = SGContext(null, null, SourceParserManager.installedParsers.head, null, null) + implicit val sourceGearContext = SGContext(null, null, SourceParserManager.installedParsers.head, null, null, null) def resetScratch = PreTest.resetScratch diff --git a/core/src/test/scala-2.12/com/opticdev/core/Fixture/compilerUtils/GearUtils.scala b/core/src/test/scala-2.12/com/opticdev/core/Fixture/compilerUtils/GearUtils.scala index ce6e001ef8..a50fad79e5 100644 --- a/core/src/test/scala-2.12/com/opticdev/core/Fixture/compilerUtils/GearUtils.scala +++ b/core/src/test/scala-2.12/com/opticdev/core/Fixture/compilerUtils/GearUtils.scala @@ -1,18 +1,22 @@ package com.opticdev.core.Fixture.compilerUtils +import com.opticdev.common.PackageRef import com.opticdev.core.compiler.Compiler.CompileWorker import com.opticdev.core.compiler.Compiler import com.opticdev.core.sourcegear.context.FlatContext import com.opticdev.parsers.ParserBase import play.api.libs.json.Json -import com.opticdev.core.sourcegear.{CompiledLens, LensSet, SourceGear} +import com.opticdev.core.sourcegear.{CompiledLens, LensSet, SGConfig, SGConstructor, SourceGear} +import com.opticdev.opm.PackageManager import com.opticdev.opm.context.{Leaf, PackageContext, PackageContextFixture, Tree} import com.opticdev.opm.packages.{OpticMDPackage, OpticPackage} +import com.opticdev.opm.providers.ProjectKnowledgeSearchPaths import com.opticdev.parsers.SourceParserManager import com.opticdev.sdk.descriptions.{Schema, SchemaRef} import scala.collection.immutable import scala.collection.mutable.ListBuffer +import scala.concurrent.{Await, Future} import scala.io.Source trait GearUtils { @@ -75,13 +79,15 @@ trait GearUtils { val schemas: Seq[(String, Schema)] = description.schemas.map(i=> (i.schemaRef.id, i)) + val t = description.transformations + val g = (lenses ++ schemas).toMap new SourceGear { override val parsers: Set[ParserBase] = SourceParserManager.installedParsers override val lensSet = outerLensSet override val schemas = compiled.schemas - override val transformations = Set() + override val transformations = t.toSet override val flatContext: FlatContext = FlatContext(None, Map( description.packageId -> FlatContext(Some(description.packageRef), g) )) @@ -89,4 +95,14 @@ trait GearUtils { } + //for debug only + def fromDependenciesList(dependencies: String*): SourceGear = { + val packages = dependencies.map(d=> PackageRef.fromString(d).get) + implicit val projectKnowledgeSearchPaths = ProjectKnowledgeSearchPaths() + val sgFuture = SGConstructor.fromDependencies(PackageManager.collectPackages(packages).get, SourceParserManager.installedParsers.map(_.parserRef)) + import scala.concurrent.duration._ + Await.result(sgFuture, 20 seconds).inflate + } + } + diff --git a/core/src/test/scala-2.12/com/opticdev/core/Fixture/compilerUtils/ParserUtils.scala b/core/src/test/scala-2.12/com/opticdev/core/Fixture/compilerUtils/ParserUtils.scala index de1cf97fd2..4fdb6971f8 100644 --- a/core/src/test/scala-2.12/com/opticdev/core/Fixture/compilerUtils/ParserUtils.scala +++ b/core/src/test/scala-2.12/com/opticdev/core/Fixture/compilerUtils/ParserUtils.scala @@ -13,9 +13,9 @@ import com.opticdev.parsers.SourceParserManager trait ParserUtils { - def parseGearFromSnippetWithComponents(block: String, components: Vector[Component], rules: Vector[Rule] = Vector(), subContainers: Vector[SubContainer] = Vector(), variables: Vector[Variable] = Vector()) : (ParseAsModel, Lens) = { + def parseGearFromSnippetWithComponents(block: String, components: Vector[Component], rules: Vector[RuleWithFinder] = Vector(), subContainers: Vector[SubContainer] = Vector(), variables: Vector[Variable] = Vector()) : (ParseAsModel, Lens) = { val snippet = Snippet("es7", block) - implicit val lens : Lens = Lens(Some("Example"), "example", BlankSchema, snippet, components, variables, subContainers, PackageRef("test:example", "0.1.1")) + implicit val lens : Lens = Lens(Some("Example"), "example", BlankSchema, snippet, components, variables, subContainers, PackageRef("test:example", "0.1.1"), None) implicit val variableManager = VariableManager(variables, SourceParserManager.installedParsers.head.identifierNodeDesc) val snippetBuilder = new SnippetStage(snippet) @@ -34,7 +34,7 @@ trait ParserUtils { def sample(block: String) : SnippetStageOutput = { val snippet = Snippet("es7", block) - implicit val lens : Lens = Lens(Some("Example"), "example", BlankSchema, snippet, Vector(), Vector(), Vector()) + implicit val lens : Lens = Lens(Some("Example"), "example", BlankSchema, snippet, Vector(), Vector(), Vector(), initialValue = None) val snippetBuilder = new SnippetStage(snippet) snippetBuilder.run } diff --git a/core/src/test/scala-2.12/com/opticdev/core/compiler/ContainerCompilerSpec.scala b/core/src/test/scala-2.12/com/opticdev/core/compiler/ContainerCompilerSpec.scala index 62283e4d3e..5774140cf0 100644 --- a/core/src/test/scala-2.12/com/opticdev/core/compiler/ContainerCompilerSpec.scala +++ b/core/src/test/scala-2.12/com/opticdev/core/compiler/ContainerCompilerSpec.scala @@ -2,7 +2,7 @@ package com.opticdev.core.compiler import com.opticdev.core.Fixture.TestBase import com.opticdev.sdk.descriptions.{Container, Snippet} -import com.opticdev.sdk.descriptions.enums.RuleEnums.SamePlus +import com.opticdev.parsers.rules._ class ContainerCompilerSpec extends TestBase { diff --git a/core/src/test/scala-2.12/com/opticdev/core/compiler/helpers/FinderEvaluationSpec.scala b/core/src/test/scala-2.12/com/opticdev/core/compiler/helpers/FinderEvaluationSpec.scala index 375c8dc824..0629f0e899 100644 --- a/core/src/test/scala-2.12/com/opticdev/core/compiler/helpers/FinderEvaluationSpec.scala +++ b/core/src/test/scala-2.12/com/opticdev/core/compiler/helpers/FinderEvaluationSpec.scala @@ -12,7 +12,7 @@ import com.opticdev.core._ class FinderEvaluationSpec extends TestBase { val block = "var hello = require('world'); var next = hello+1" - implicit val lens : Lens = Lens(Some("Example"), "example", BlankSchema, Snippet("es7", block), Vector(), Vector(), Vector(), PackageRef("test:test")) + implicit val lens : Lens = Lens(Some("Example"), "example", BlankSchema, Snippet("es7", block), Vector(), Vector(), Vector(), PackageRef("test:test"), None) val snippetBuilder = new SnippetStage(lens.snippet) val snippetStageOutput = snippetBuilder.run diff --git a/core/src/test/scala-2.12/com/opticdev/core/compiler/stages/FinderStageSpec.scala b/core/src/test/scala-2.12/com/opticdev/core/compiler/stages/FinderStageSpec.scala index 34aa791825..4b85bb26fa 100644 --- a/core/src/test/scala-2.12/com/opticdev/core/compiler/stages/FinderStageSpec.scala +++ b/core/src/test/scala-2.12/com/opticdev/core/compiler/stages/FinderStageSpec.scala @@ -18,7 +18,7 @@ class FinderStageSpec extends TestBase { implicit val lens : Lens = Lens(Some("Example"), "example", BlankSchema, snippet, Vector( CodeComponent(Seq("definedAs"), StringFinder(Entire, "hello")) - ), Vector(), Vector()) + ), Vector(), Vector(), initialValue = None) val snippetBuilder = new SnippetStage(snippet) val outputTry = Try(snippetBuilder.run) @@ -50,7 +50,7 @@ class FinderStageSpec extends TestBase { CodeComponent(Seq("definedAs"), StringFinder(Entire, "hello")), CodeComponent(Seq("firstProblem"), StringFinder(Entire, "not-anywhere")), CodeComponent(Seq("nextProblem"), StringFinder(Entire, "nowhere")) - ), Vector(), Vector()) + ), Vector(), Vector(), initialValue = None) val finderStage = new FinderStage(outputTry.get) diff --git a/core/src/test/scala-2.12/com/opticdev/core/compiler/stages/RendererFactoryStageSpec.scala b/core/src/test/scala-2.12/com/opticdev/core/compiler/stages/RendererFactoryStageSpec.scala index 889137460a..6d0d562764 100644 --- a/core/src/test/scala-2.12/com/opticdev/core/compiler/stages/RendererFactoryStageSpec.scala +++ b/core/src/test/scala-2.12/com/opticdev/core/compiler/stages/RendererFactoryStageSpec.scala @@ -15,7 +15,7 @@ import com.opticdev.core.sourcegear.project.{Project, StaticSGProject} import com.opticdev.parsers.{ParserBase, SourceParserManager} import com.opticdev.core._ import com.opticdev.core.sourcegear.context.FlatContext -import com.opticdev.sdk.descriptions.enums.RuleEnums.SameAnyOrderPlus +import com.opticdev.parsers.rules._ import com.opticdev.sdk.descriptions.enums.VariableEnums import com.opticdev.sdk.descriptions.transformation.StagedNode @@ -24,7 +24,6 @@ class RendererFactoryStageSpec extends AkkaTestFixture("RendererFactoryStageSpec it("can create an expression renderer") { val block = "my.hello.world" - implicit val ruleProvider = new RuleProvider() implicit val project = new StaticSGProject("test", File(getCurrentDirectory + "/test-examples/resources/tmp/test_project/"), sourceGear) @@ -46,7 +45,6 @@ class RendererFactoryStageSpec extends AkkaTestFixture("RendererFactoryStageSpec it("can create a simple renderer") { val block = "var hello = require('world')" - implicit val ruleProvider = new RuleProvider() implicit val project = new StaticSGProject("test", File(getCurrentDirectory + "/test-examples/resources/tmp/test_project/"), sourceGear) @@ -159,7 +157,6 @@ class RendererFactoryStageSpec extends AkkaTestFixture("RendererFactoryStageSpec it("can create a renderer that supports variables") { val block = "const variable = function thing() {}" - implicit val ruleProvider = new RuleProvider() implicit val project = new StaticSGProject("test", File(getCurrentDirectory + "/test-examples/resources/tmp/test_project/"), sourceGear) diff --git a/core/src/test/scala-2.12/com/opticdev/core/compiler/stages/SnippetStageSpec.scala b/core/src/test/scala-2.12/com/opticdev/core/compiler/stages/SnippetStageSpec.scala index a92b9c0db7..56c3d6c07b 100644 --- a/core/src/test/scala-2.12/com/opticdev/core/compiler/stages/SnippetStageSpec.scala +++ b/core/src/test/scala-2.12/com/opticdev/core/compiler/stages/SnippetStageSpec.scala @@ -13,7 +13,7 @@ import scala.util.Try class SnippetStageSpec extends TestBase with PrivateMethodTester { - implicit val lens : Lens = Lens(Some("Example"), "example", BlankSchema, null, null, null, Vector(), null) + implicit val lens : Lens = Lens(Some("Example"), "example", BlankSchema, null, null, null, Vector(), null, initialValue = None) describe("Finds the correct parser") { it("when it exists") { @@ -65,7 +65,7 @@ class SnippetStageSpec extends TestBase with PrivateMethodTester { it("for multi-node snippets") { val (enterOn, children, matchType) = parseResult("function subtract(a,b) { return a-b } function add(a,b) { return a+b }") val blockNodeTypes = SourceParserManager.parserByLanguageName("es7").get.blockNodeTypes - assert(enterOn.size == 2 && enterOn == blockNodeTypes.nodeTypes) + assert(enterOn.size == 3 && enterOn == blockNodeTypes.nodeTypes) assert(children.size == 2) assert(matchType == MatchType.Children) } @@ -99,6 +99,24 @@ class SnippetStageSpec extends TestBase with PrivateMethodTester { )) } + it("will find the parent when put in a seqence of children nodes") { + + val example = + """ + |