aboutsummaryrefslogtreecommitdiff
path: root/tools/gui/src
diff options
context:
space:
mode:
authorJan Christopher Vogt <oss.nsp@cvogt.org>2016-10-29 15:41:46 -0400
committerGitHub <noreply@github.com>2016-10-29 15:41:46 -0400
commit9e941338e1ae9ec52e374b3331e39dec01efd587 (patch)
tree1ae9cbc1e1a53be9b0c96610e14e5abe8bb87e9c /tools/gui/src
parent358a189db9842705d5c885a4c315635cd36fdc14 (diff)
parenteaf05a398ce25011eb7649324f3e60689d8a879a (diff)
downloadcbt-9e941338e1ae9ec52e374b3331e39dec01efd587.tar.gz
cbt-9e941338e1ae9ec52e374b3331e39dec01efd587.tar.bz2
cbt-9e941338e1ae9ec52e374b3331e39dec01efd587.zip
Merge pull request #289 from tim-zh/activator
Web UI
Diffstat (limited to 'tools/gui/src')
-rw-r--r--tools/gui/src/JettyServer.scala67
-rw-r--r--tools/gui/src/Main.scala85
-rw-r--r--tools/gui/src/ProjectBuilder.scala149
3 files changed, 301 insertions, 0 deletions
diff --git a/tools/gui/src/JettyServer.scala b/tools/gui/src/JettyServer.scala
new file mode 100644
index 0000000..d6024c2
--- /dev/null
+++ b/tools/gui/src/JettyServer.scala
@@ -0,0 +1,67 @@
+import java.net.MalformedURLException
+import javax.servlet.http.{HttpServletRequest, HttpServletResponse}
+
+import org.eclipse.jetty.server.{Request, Server}
+import org.eclipse.jetty.server.handler._
+
+import scala.util.{Failure, Success, Try}
+
+abstract class JettyServer(port: Int, staticFilesUrl: String) {
+
+ private val handlerOfStatic = {
+ val handler = new ContextHandler("/")
+ val resourceHandler = new ResourceHandler
+ resourceHandler.setDirectoriesListed(true)
+ resourceHandler.setResourceBase(staticFilesUrl)
+ handler.setHandler(resourceHandler)
+ handler
+ }
+
+ private val handlerOfApi = {
+ val handlerWrapper = new ContextHandler("/api/")
+ val handler = new AbstractHandler {
+ override def handle(target: String,
+ baseRequest: Request,
+ request: HttpServletRequest,
+ response: HttpServletResponse) = {
+ response.setContentType("application/json")
+ response.setCharacterEncoding("UTF-8")
+
+ route(request.getMethod, target, request.getParameter) match {
+ case Success(result) =>
+ response.getWriter.write(result)
+ case Failure(e: MalformedURLException) =>
+ response.setStatus(HttpServletResponse.SC_NOT_FOUND)
+ response.getWriter.write(e.getMessage)
+ case Failure(e) =>
+ response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR)
+ response.getWriter.write(s"${e.getClass.getName}: ${e.getMessage}")
+ }
+
+ baseRequest.setHandled(true)
+ }
+ }
+ handlerWrapper.setHandler(handler)
+ handlerWrapper
+ }
+
+ private val server = {
+ val s = new Server(port)
+ val handlers = new HandlerCollection
+ handlers.setHandlers(Array(handlerOfStatic, handlerOfApi, new DefaultHandler))
+ s.setHandler(handlers)
+ s
+ }
+
+ def start() = {
+ server.start()
+ println(s"UI server started at localhost:$port")
+ }
+
+ def stop() = {
+ server.stop()
+ println("UI server stopped.")
+ }
+
+ def route(method: String, path: String, param: String => String): Try[String]
+}
diff --git a/tools/gui/src/Main.scala b/tools/gui/src/Main.scala
new file mode 100644
index 0000000..d7a9f7d
--- /dev/null
+++ b/tools/gui/src/Main.scala
@@ -0,0 +1,85 @@
+import java.io.{File, IOException}
+import java.net.MalformedURLException
+
+import scala.io.Source
+import scala.util.{Failure, Success, Try}
+import scalaj.http.Http
+
+object Main {
+
+ private val maven_host = "search.maven.org"
+ private val cbt_home = System.getenv("CBT_HOME")
+
+ implicit class StringExtensionMethods(str: String) {
+ def /(s: String): String = str + File.separator + s
+ }
+
+ val uiPort = 9080
+
+ def main(args: Array[String]) = launchUi(new File(args(0)), args(1))
+
+ def launchUi(projectDirectory: File, scalaMajorVersion: String): Unit = {
+ val staticBase = new File(cbt_home / "tools" / "gui" / "resources" / "web").toURI.toURL.toExternalForm
+ val server = new JettyServer(uiPort, staticBase) {
+ override def route(method: String, path: String, param: String => String) = (method, path) match {
+ case ("GET", "/cwd") =>
+ Success(s"""["$projectDirectory"]""")
+ case ("POST", "/project") =>
+ val name = param("name")
+ val defaultPackage = param("pack")
+ val dependencies = param("dependencies")
+ val flags = param("flags")
+ handleIoException {
+ new ProjectBuilder(name, defaultPackage, dependencies, flags, projectDirectory, scalaMajorVersion).build()
+ Success("[]")
+ }
+ case ("GET", "/dependency") =>
+ val query = param("query")
+ handleIoException(handleMavenBadResponse(searchDependency(query)))
+ case ("GET", "/dependency/version") =>
+ val group = param("group")
+ val artifact = param("artifact")
+ handleIoException(handleMavenBadResponse(searchDependencyVersion(group, artifact)))
+ case _ =>
+ Failure(new MalformedURLException(s"Incorrect path: $path"))
+ }
+ }
+ server.start()
+ java.awt.Desktop.getDesktop.browse(new java.net.URI(s"http://localhost:$uiPort/"))
+
+ println("Press Enter to stop UI server.")
+ while (Source.stdin.getLines().next().nonEmpty) {}
+ server.stop()
+ }
+
+ private def searchDependency(query: String) = {
+ Http(s"http://$maven_host/solrsearch/select")
+ .param("q", query)
+ .param("rows", "30")
+ .param("wt", "json")
+ .asString.body
+ }
+
+ private def searchDependencyVersion(group: String, artifact: String) = {
+ val query = s"""q=g:"$group"+AND+a:"$artifact""""
+ Http(s"http://$maven_host/solrsearch/select?" + query)
+ .param("rows", "30")
+ .param("wt", "json")
+ .param("core", "gav")
+ .asString.body
+ }
+
+ private def handleIoException(f: => Try[String]) = try f catch {
+ case e: IOException =>
+ e.printStackTrace()
+ Failure(e)
+ }
+
+ private def handleMavenBadResponse(result: String) = {
+ if (result.startsWith("{"))
+ Success(result)
+ else
+ Failure(new Exception(s"Bad response from $maven_host: $result"))
+ }
+
+}
diff --git a/tools/gui/src/ProjectBuilder.scala b/tools/gui/src/ProjectBuilder.scala
new file mode 100644
index 0000000..ceba376
--- /dev/null
+++ b/tools/gui/src/ProjectBuilder.scala
@@ -0,0 +1,149 @@
+import java.io.File
+import java.nio.file._
+
+import Main.StringExtensionMethods
+
+import scala.io.Source
+
+class ProjectBuilder(
+ name: String,
+ defaultPackage: String,
+ dependencyString: String,
+ flagsString: String,
+ location: File,
+ scalaMajorVersion: String
+) {
+
+ private val newLine = System.lineSeparator()
+ private val blankLine = newLine + newLine
+ private val templateDir = System.getenv("CBT_HOME") / "tools" / "gui" / "resources" / "template-project"
+ private val projectPath = location.getPath / name
+ private val buildDir = projectPath / "build"
+ private val mainDir = projectPath / "src" / "main" / "scala" / defaultPackage.split("\\.").reduce(_ / _)
+ private val testDir = projectPath / "src" / "test" / "scala" / defaultPackage.split("\\.").reduce(_ / _)
+
+ private val dependencies =
+ if (dependencyString.isEmpty)
+ ""
+ else {
+ def parseDependencies(input: String): Seq[Dependency] = {
+ input.split(" ").map { x =>
+ val xs = x.split("/")
+ Dependency(xs(0), xs(1), xs(2))
+ }
+ }
+
+ val list = parseDependencies(dependencyString)
+ val str = list.map(" " ++ _.serialized).mkString(s",$newLine")
+ s""" override def dependencies = {
+ | super.dependencies ++ Resolver(mavenCentral).bind(
+ |$str
+ | )
+ | }""".stripMargin
+ }
+
+ private val flags = {
+ val map = flagsString.split(" ").map { x =>
+ val Array(a, b) = x.split("/")
+ (a, b)
+ }.toMap
+ Flags(map("readme") == "true", map("dotty") == "true", map("uberJar") == "true", map("wartremover") == "true")
+ }
+
+ private val plugins = {
+ var content = ""
+ if (flags.dotty)
+ content += " with Dotty"
+ if (flags.uberJar)
+ content += " with UberJar"
+ if (flags.wartremover)
+ content += " with WartRemover"
+ content
+ }
+
+ private val buildBuildDependencies = {
+ var content = ""
+ if (flags.uberJar)
+ content += " :+ plugins.uberJar"
+ if (flags.wartremover)
+ content += " :+ plugins.wartremover"
+ content
+ }
+
+ def build(): Unit = {
+ new File(buildDir).mkdirs()
+ new File(mainDir).mkdirs()
+ new File(testDir).mkdirs()
+ addBuild()
+ if (buildBuildDependencies.nonEmpty)
+ addBuildBuild()
+ addMain()
+ addReadme(flags)
+ }
+
+ private def writeTemplate(templatePath: String, targetPath: String, replacements: (String, String, String)*) = {
+ var content = Source.fromFile(templatePath).mkString
+ for (replacement <- replacements)
+ if (replacement._1.nonEmpty)
+ content = content.replace(replacement._3, replacement._2)
+ else
+ content = content.replace(replacement._3, "")
+ write(new File(targetPath), content, StandardOpenOption.CREATE_NEW)
+ }
+
+ private def addBuild() =
+ writeTemplate(
+ templateDir / "build" / "build.scala",
+ buildDir / "build.scala",
+ (name, s""" override def projectName = "$name"$blankLine""", "##projectName##"),
+ (dependencyString, dependencies + blankLine, "##dependencies##"),
+ (plugins, plugins, "##with##")
+ )
+
+ private def addBuildBuild() =
+ writeTemplate(
+ templateDir / "build" / "build" / "build.scala",
+ buildDir / "build" / "build.scala",
+ (buildBuildDependencies, buildBuildDependencies, "##plus##")
+ )
+
+ private def addMain() =
+ writeTemplate(
+ templateDir / "src" / "main" / "scala" / "Main.scala",
+ mainDir / "Main.scala",
+ (defaultPackage, s"package $defaultPackage$blankLine", "##package##")
+ )
+
+ private def addReadme(flags: Flags) = {
+ if (flags.readme) {
+ val content =
+ if (name.nonEmpty)
+ s"# $name$blankLine"
+ else
+ ""
+ write(new File(projectPath / "readme.md"), content, StandardOpenOption.CREATE_NEW)
+ }
+ }
+
+ private case class Dependency(group: String, artifact: String, version: String) {
+ val nameVersion = """^(.+)_(\d+\.\d+)$""".r
+ val (name, scalaVersion) = artifact match {
+ case nameVersion(n, v) => (n, Some(v))
+ case str => (str, None)
+ }
+
+ def serialized =
+ if (scalaVersion.contains(scalaMajorVersion))
+ s"""ScalaDependency("$group", "$name", "$version")"""
+ else
+ s"""MavenDependency("$group", "$artifact", "$version")"""
+ }
+
+ private def write(file: File, content: String, option: OpenOption) = {
+ file.getParentFile.mkdirs()
+ Files.write(file.toPath, content.getBytes, option)
+ }
+
+ private case class Flags(readme: Boolean, dotty: Boolean, uberJar: Boolean, wartremover: Boolean)
+
+}