diff options
author | Jan Christopher Vogt <oss.nsp@cvogt.org> | 2016-10-29 15:41:46 -0400 |
---|---|---|
committer | GitHub <noreply@github.com> | 2016-10-29 15:41:46 -0400 |
commit | 9e941338e1ae9ec52e374b3331e39dec01efd587 (patch) | |
tree | 1ae9cbc1e1a53be9b0c96610e14e5abe8bb87e9c /tools/gui/src | |
parent | 358a189db9842705d5c885a4c315635cd36fdc14 (diff) | |
parent | eaf05a398ce25011eb7649324f3e60689d8a879a (diff) | |
download | cbt-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.scala | 67 | ||||
-rw-r--r-- | tools/gui/src/Main.scala | 85 | ||||
-rw-r--r-- | tools/gui/src/ProjectBuilder.scala | 149 |
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) + +} |