Refactor to abstract listening for command line

This commit is contained in:
Eugene Yokota 2016-03-02 11:49:17 +01:00
parent 649dc0ce3c
commit f9dd8b73b7
11 changed files with 112 additions and 19 deletions

View File

@ -176,12 +176,11 @@ lazy val actionsProj = (project in file("main-actions")).
// General command support and core commands not specific to a build system
lazy val commandProj = (project in file("main-command")).
dependsOn(serverProj).
settings(
testedBaseSettings,
name := "Command",
libraryDependencies ++= Seq(launcherInterface, compilerInterface,
sbtIO, utilLogging, utilCompletion, compilerClasspath)
sbtIO, utilLogging, utilCompletion, compilerClasspath, json4s, json4sNative) // to transitively get json4s)
)
// Fixes scope=Scope for Setting (core defined in collectionProj) to define the settings system used in build definitions
@ -194,15 +193,9 @@ lazy val mainSettingsProj = (project in file("main-settings")).
utilLogging, sbtIO, utilCompletion, compilerClasspath, libraryManagement)
)
lazy val serverProj = (project in mainPath / "server").
settings(
baseSettings,
libraryDependencies ++= Seq(json4s, json4sNative) // to transitively get json4s
)
// The main integration project for sbt. It brings all of the Projsystems together, configures them, and provides for overriding conventions.
lazy val mainProj = (project in file("main")).
dependsOn(actionsProj, mainSettingsProj, runProj, commandProj, serverProj).
dependsOn(actionsProj, mainSettingsProj, runProj, commandProj).
settings(
testedBaseSettings,
name := "Main",
@ -251,7 +244,7 @@ lazy val myProvided = config("provided") intransitive
def allProjects = Seq(
testingProj, testAgentProj, taskProj, stdTaskProj, runProj,
scriptedSbtProj, scriptedPluginProj,
actionsProj, commandProj, mainSettingsProj, serverProj, mainProj, sbtProj, bundledLauncherProj)
actionsProj, commandProj, mainSettingsProj, mainProj, sbtProj, bundledLauncherProj)
def projectsWithMyProvided = allProjects.map(p => p.copy(configurations = (p.configurations.filter(_ != Provided)) :+ myProvided))
lazy val nonRoots = projectsWithMyProvided.map(p => LocalProject(p.id))

View File

@ -149,6 +149,9 @@ object BasicCommandStrings {
def Shell = "shell"
def ShellDetailed = "Provides an interactive prompt from which commands can be run."
def Server = "server"
def ServerDetailed = "Provides a network server and an interactive prompt from which commands can be run."
def StashOnFailure = "sbtStashOnFailure"
def PopOnFailure = "sbtPopOnFailure"

View File

@ -13,14 +13,16 @@ import BasicCommandStrings._
import CommandUtil._
import BasicKeys._
import sbt.server.Server
import java.io.File
import sbt.io.IO
import java.util.concurrent.ConcurrentLinkedQueue
import java.util.concurrent.atomic.AtomicBoolean
import scala.util.control.NonFatal
import scala.annotation.tailrec
object BasicCommands {
lazy val allBasicCommands = Seq(nop, ignore, help, completionsCommand, multi, ifLast, append, setOnFailure, clearOnFailure, stashOnFailure, popOnFailure, reboot, call, early, exit, continuous, history, shell, read, alias) ++ compatCommands
lazy val allBasicCommands = Seq(nop, ignore, help, completionsCommand, multi, ifLast, append, setOnFailure, clearOnFailure, stashOnFailure, popOnFailure, reboot, call, early, exit, continuous, history, shell, server, read, alias) ++ compatCommands
def nop = Command.custom(s => success(() => s))
def ignore = Command.command(FailureWall)(idFun)
@ -180,9 +182,6 @@ object BasicCommands {
}
def shell = Command.command(Shell, Help.more(Shell, ShellDetailed)) { s =>
// TODO hook it in, start in the right place, shutdown on termination
val server = Server.start("127.0.0.1", 12700)
val history = (s get historyPath) getOrElse Some(new File(s.baseDir, ".history"))
val prompt = (s get shellPrompt) match { case Some(pf) => pf(s); case None => "> " }
val reader = new FullReader(history, s.combinedParser)
@ -195,6 +194,32 @@ object BasicCommands {
}
}
var askingAlready = false
val commandListers = Seq(new ConsoleListener(), new NetworkListener())
val commandQueue: ConcurrentLinkedQueue[Option[String]] = new ConcurrentLinkedQueue()
@tailrec def blockUntilNextCommand: Option[String] =
Option(commandQueue.poll) match {
case Some(x) => x
case None =>
Thread.sleep(50)
blockUntilNextCommand
}
def server = Command.command(Server, Help.more(Server, ServerDetailed)) { s =>
if (!askingAlready) {
commandListers foreach { x =>
x.run(commandQueue, CommandStatus(s, true))
}
}
blockUntilNextCommand match {
case Some(line) =>
// tell listern to be inactive .
val newState = s.copy(onFailure = Some(Server), remainingCommands = line +: Server +: s.remainingCommands).setInteractive(true)
if (line.trim.isEmpty) newState else newState.clearGlobalLog
case None => s.setInteractive(false)
}
}
def read = Command.make(ReadCommand, Help.more(ReadCommand, ReadDetailed))(s => applyEffect(readParser(s))(doRead(s)))
def readParser(s: State) =
{

View File

@ -0,0 +1,12 @@
package sbt
import java.util.concurrent.ConcurrentLinkedQueue
import java.util.concurrent.atomic.AtomicBoolean
trait CommandListener {
// represents a loop that keeps asking an IO device for String input
def run(queue: ConcurrentLinkedQueue[Option[String]],
status: CommandStatus): Unit
def shutdown(): Unit
def setStatus(status: CommandStatus): Unit
}

View File

@ -0,0 +1,3 @@
package sbt
case class CommandStatus(state: State, canEnter: Boolean)

View File

@ -0,0 +1,31 @@
package sbt
import java.util.concurrent.ConcurrentLinkedQueue
import java.util.concurrent.atomic.{ AtomicBoolean, AtomicReference }
class ConsoleListener extends CommandListener {
// val history = (s get historyPath) getOrElse Some(new File(s.baseDir, ".history"))
// val prompt = (s get shellPrompt) match { case Some(pf) => pf(s); case None => "> " }
// val reader = new FullReader(history, s.combinedParser)
// val line = reader.readLine(prompt)
// line match {
// case Some(line) =>
// val newState = s.copy(onFailure = Some(Shell), remainingCommands = line +: Shell +: s.remainingCommands).setInteractive(true)
// if (line.trim.isEmpty) newState else newState.clearGlobalLog
// case None => s.setInteractive(false)
// }
def run(queue: ConcurrentLinkedQueue[Option[String]],
status: CommandStatus): Unit =
{
// spawn thread and loop
}
def shutdown(): Unit =
{
// interrupt and kill the thread
}
def setStatus(status: CommandStatus): Unit = ???
}

View File

@ -0,0 +1,22 @@
package sbt
import java.util.concurrent.ConcurrentLinkedQueue
import java.util.concurrent.atomic.AtomicBoolean
import server.Server
class NetworkListener extends CommandListener {
def run(queue: ConcurrentLinkedQueue[Option[String]],
status: CommandStatus): Unit =
{
val server = Server.start("127.0.0.1", 12700)
// spawn thread and loop
}
def shutdown(): Unit =
{
// interrupt and kill the thread
}
def setStatus(status: CommandStatus): Unit = ???
}

View File

@ -1,7 +1,8 @@
/*
* Copyright (C) 2016 Lightbend Inc. <http://www.typesafe.com>
*/
package sbt.server
package sbt
package server
import java.net.{ SocketTimeoutException, Socket }
import java.util.concurrent.atomic.AtomicBoolean

View File

@ -1,7 +1,8 @@
/*
* Copyright (C) 2016 Lightbend Inc. <http://www.typesafe.com>
*/
package sbt.server
package sbt
package server
import org.json4s.JsonAST.{ JArray, JString }
import org.json4s._

View File

@ -1,7 +1,8 @@
/*
* Copyright (C) 2016 Lightbend Inc. <http://www.typesafe.com>
*/
package sbt.server
package sbt
package server
import java.net.{ SocketTimeoutException, InetAddress, ServerSocket }
import java.util.concurrent.ConcurrentLinkedQueue

View File

@ -1,7 +1,8 @@
/*
* Copyright (C) 2016 Lightbend Inc. <http://www.typesafe.com>
*/
package sbt.server
package sbt
package server
trait Event