Thomas Flucke преди 6 години
ревизия
1c01f84035
променени са 5 файла, в които са добавени 121 реда и са изтрити 0 реда
  1. 5 0
      build.sbt
  2. 1 0
      project/build.properties
  3. 20 0
      src/name/tflucke/webroutes/APIRoute.scala
  4. 23 0
      src/name/tflucke/webroutes/RouteDef.scala
  5. 72 0
      src/name/tflucke/webroutes/WebRoutesPlugin.scala

+ 5 - 0
build.sbt

@@ -0,0 +1,5 @@
+lazy val root = (project in file("."))
+  .enablePlugins(SbtPlugin)
+  .settings(
+    name := "web-routes"
+  )

+ 1 - 0
project/build.properties

@@ -0,0 +1 @@
+sbt.version=1.3.10

+ 20 - 0
src/name/tflucke/webroutes/APIRoute.scala

@@ -0,0 +1,20 @@
+package name.tflucke.webroutes
+
+import play.api.libs.json.{Json,JsValue}
+import org.scalajs.dom.ext.Ajax.InputData
+import scala.concurrent.Future
+import scala.concurrent.ExecutionContext.Implicits.global
+import scala.util.Try
+import org.scalajs.dom.XMLHttpRequest
+import org.scalajs.dom.ext.Ajax
+
+abstract class APIRoute[T](val method: String, val url: String, format: String) {
+
+  protected def convert(json: JsValue): T
+
+  def apply(timeout: Int = 0, headers: Map[String, String] = Map.empty, withCredentials: Boolean = false) : Future[T] = {
+    Ajax(method, url, null, timeout, headers, withCredentials, format).transform(
+      (x: Try[XMLHttpRequest]) => Try(convert(Json.parse(x.get.responseText)))
+    )
+  }
+}

+ 23 - 0
src/name/tflucke/webroutes/RouteDef.scala

@@ -0,0 +1,23 @@
+package name.tflucke.webroutes
+
+case class RouteDef(
+  method: String,
+  url: String,
+  pack: String,
+  controller: String,
+  fn: String,
+  args: Array[String],
+  retType: String = "Any",
+  retFormat: String = "json"
+) {
+  def interpolatedUrl: String = {
+    ":(?<symbol>[^/]+)".r.replaceAllIn(url, (matc) => "\\${${symbol}}")
+  }
+
+  override def toString: String = s"""
+def ${fn}(${args.mkString(", ")}) : APIRoute[${retType}] = new APIRoute[${retType}](\"${method}\", s\"${interpolatedUrl}\", \"${retFormat}\") {
+    import play.api.libs.json.{JsValue,Reads}
+    protected def convert(json: JsValue): ${retType} = json.as[${retType}]
+}
+"""
+}

+ 72 - 0
src/name/tflucke/webroutes/WebRoutesPlugin.scala

@@ -0,0 +1,72 @@
+package name.tflucke.webroutes
+
+import sbt._
+import Keys._
+
+object WebRoutesPlugin extends AutoPlugin {
+  val routePattern = ("\\s*#\\s*Shared\\s+Route\\s*\n" +
+    "((\\s*#\\s*\\w+\\s*:\\s*[^\\s]+\\s*\n)*)" +
+    "\\s*(GET|PUT|POST|DELETE)\\s+(/[^\\s]*)+\\s+@?([\\.\\w]+.controllers).([\\w]+).([\\w]+)\\s*(\\((\\w+\\s*:\\s*\\w+\\s*)?(,\\s*\\w+\\s*:\\s*\\w+)?\\))?").r
+
+  object autoImport {
+    val generateJsRoutes = taskKey[Seq[File]]("Generate scala client routes objects")
+    val scalaJsManagedDir = settingKey[File]("Output directory to put generated files")
+    lazy val baseWebRouteSettings: Seq[Def.Setting[_]] = Seq(
+      scalaJsManagedDir := Compile / resourceManaged,
+      generateJsRoutes := {
+        println("Generating Scala Web route objects...")
+        generateJsRoutes.inputFileChanges.modified.flatMap((routesFile: java.nio.file.Path) => {
+          import scala.io.Source
+
+          val routeGroups = (for (route <- routePattern.findAllMatchIn(Source.fromFile(routesFile.toFile).mkString)) yield {
+            val propPattern = "\\s*#\\s*(\\w+)\\s*:\\s*([^\\s]+)\\s*\n".r
+            val props = propPattern.findAllMatchIn(route.group(1)).map(m => (m.group(1), m.group(2)))
+            def getProp(name: String): Option[String] = props.find(_._1.equalsIgnoreCase(name)).map(_._2)
+            val method = route.group(3)
+            val path = route.group(4)
+            val pack = route.group(5)
+            val obj = route.group(6)
+            val function = route.group(7)
+            val args = (Option(route.group(9)).getOrElse("") + Option(route.group(10)).getOrElse("")).split(",")
+            RouteDef(method, path, pack, obj, function, args,
+              getProp("type") getOrElse "Any",
+              "text")//getProp("mime") getOrElse "json"
+          }).toList.groupBy[(String, String)]((route) => (route.pack, route.controller))
+          val outFiles = routeGroups.map(tuple => {
+            val ((pack, controller), routes) = tuple
+            val content = s"""package ${pack}
+object ${controller} {
+${routes.mkString("\n")}
+}
+"""
+            val outFile = makeControllerFile(pack, controller, scalaJsManagedDir.value)
+            IO.write(outFile, content)
+            outFile
+          }).toSeq
+          outFiles
+        })
+      }
+    )
+  }
+  import autoImport._
+
+  override val projectSettings =
+    inConfig(Compile)(baseWebRouteSettings) ++
+    inConfig(Test)(baseWebRouteSettings)
+
+  def makeControllerFile(path: String, controller: String, baseDir: File): File = {
+    val pattern = "\\.?([^.]+)(.*)".r
+    path match {
+      case pattern(head, rest) => {
+        val nextDir = new File(baseDir, head)
+        if (!nextDir.exists) {
+          nextDir.mkdir()
+        }
+        makeControllerFile(rest, controller, nextDir)
+      }
+      case _ => {
+        new File(baseDir, s"${controller}.scala")
+      }
+    }
+  }
+}