Run and return the output of a process as a String with `!!` or as a (blocking) `Stream[String]` with `lines`.

This commit is contained in:
Mark Harrah 2010-04-06 19:26:49 -04:00
parent 30f3fdbfd7
commit 3d736fcf66
4 changed files with 94 additions and 14 deletions

View File

@ -1,4 +1,4 @@
Copyright (c) 2008, 2009, 2010 Steven Blundy, Josh Cough, Nathan Hamblen, Mark Harrah, David MacIver, Mikko Peltonen, Tony Sloane, Vesa Vilhonen
Copyright (c) 2008, 2009, 2010 Steven Blundy, Josh Cough, Nathan Hamblen, Mark Harrah, David MacIver, Mikko Peltonen, Tony Sloane, Seth Tisue, Vesa Vilhonen
All rights reserved.
Redistribution and use in source and binary forms, with or without

View File

@ -156,7 +156,7 @@ class xMain extends xsbti.AppMain
arguments match
{
case "" :: tail => continue(project, tail, failAction)
case x :: tail if x.startsWith(";") => continue(project, x.split(";").toList ::: arguments, failAction)
case x :: tail if x.startsWith(";") => continue(project, x.split(";").toList ::: tail, failAction)
case (ExitCommand | QuitCommand) :: _ => result( Exit(NormalExitCode) )
case RebootCommand :: tail => reload( tail )
case InteractiveCommand :: _ => continue(project, prompt(baseProject, project) :: arguments, interactiveContinue)

View File

@ -101,6 +101,28 @@ trait Process extends NotNull
/** Represents a runnable process. */
trait ProcessBuilder extends SourcePartialBuilder with SinkPartialBuilder
{
/** Starts the process represented by this builder, blocks until it exits, and returns the output as a String. Standard error is
* sent to the console. If the exit code is non-zero, an exception is thrown.*/
def !! : String
/** Starts the process represented by this builder, blocks until it exits, and returns the output as a String. Standard error is
* sent to the provided Logger. If the exit code is non-zero, an exception is thrown.*/
def !!(log: Logger) : String
/** Starts the process represented by this builder. The output is returned as a Stream that blocks when lines are not available
* but the process has not completed. Standard error is sent to the console. If the process exits with a non-zero value,
* the Stream will provide all lines up to termination and then throw an exception. */
def lines: Stream[String]
/** Starts the process represented by this builder. The output is returned as a Stream that blocks when lines are not available
* but the process has not completed. Standard error is sent to the provided Logger. If the process exits with a non-zero value,
* the Stream will provide all lines up to termination but will not throw an exception. */
def lines(log: Logger): Stream[String]
/** Starts the process represented by this builder. The output is returned as a Stream that blocks when lines are not available
* but the process has not completed. Standard error is sent to the console. If the process exits with a non-zero value,
* the Stream will provide all lines up to termination but will not throw an exception. */
def lines_! : Stream[String]
/** Starts the process represented by this builder. The output is returned as a Stream that blocks when lines are not available
* but the process has not completed. Standard error is sent to the provided Logger. If the process exits with a non-zero value,
* the Stream will provide all lines up to termination but will not throw an exception. */
def lines_!(log: Logger): Stream[String]
/** Starts the process represented by this builder, blocks until it exits, and returns the exit code. Standard output and error are
* sent to the console.*/
def ! : Int
@ -108,10 +130,10 @@ trait ProcessBuilder extends SourcePartialBuilder with SinkPartialBuilder
* sent to the given Logger.*/
def !(log: Logger): Int
/** Starts the process represented by this builder, blocks until it exits, and returns the exit code. Standard output and error are
* sent to the console. The newly started process reads from standard input of the current process if `connectInput` is true.*/
* sent to the console. The newly started process reads from standard input of the current process.*/
def !< : Int
/** Starts the process represented by this builder, blocks until it exits, and returns the exit code. Standard output and error are
* sent to the given Logger. The newly started process reads from standard input of the current process if `connectInput` is true.*/
* sent to the given Logger. The newly started process reads from standard input of the current process.*/
def !<(log: Logger) : Int
/** Starts the process represented by this builder. Standard output and error are sent to the console.*/
def run(): Process

View File

@ -43,17 +43,24 @@ private object Future
private object BasicIO
{
def apply(buffer: StringBuffer, log: Option[Logger], withIn: Boolean) = new ProcessIO(input(withIn), processFully(buffer), getErr(log))
def apply(log: Logger, withIn: Boolean) = new ProcessIO(input(withIn), processFully(log, Level.Info), processFully(log, Level.Error))
def getErr(log: Option[Logger]) = log match { case Some(lg) => processFully(lg, Level.Error); case None => toStdErr }
def ignoreOut = (i: OutputStream) => ()
val BufferSize = 8192
final val BufferSize = 8192
final val Newline = FileUtilities.Newline
def close(c: java.io.Closeable) = try { c.close() } catch { case _: java.io.IOException => () }
def processFully(log: Logger, level: Level.Value)(i: InputStream) { processFully(line => log.log(level, line))(i) }
def processFully(processLine: String => Unit)(i: InputStream)
{
val reader = new BufferedReader(new InputStreamReader(i))
processLinesFully(processLine)(reader.readLine)
}
def processFully(log: Logger, level: Level.Value): InputStream => Unit = processFully(line => log.log(level, line))
def processFully(buffer: Appendable): InputStream => Unit = processFully(appendLine(buffer))
def processFully(processLine: String => Unit): InputStream => Unit =
in =>
{
val reader = new BufferedReader(new InputStreamReader(in))
processLinesFully(processLine)(reader.readLine)
}
def processLinesFully(processLine: String => Unit)(readLine: () => String)
{
def readFully()
@ -70,12 +77,22 @@ private object BasicIO
def connectToIn(o: OutputStream) { transferFully(System.in, o) }
def input(connect: Boolean): OutputStream => Unit = if(connect) connectToIn else ignoreOut
def standard(connectInput: Boolean): ProcessIO = standard(input(connectInput))
def standard(in: OutputStream => Unit): ProcessIO = new ProcessIO(in, transferFully(_, System.out), transferFully(_, System.err))
def standard(in: OutputStream => Unit): ProcessIO = new ProcessIO(in, toStdOut, toStdErr)
def toStdErr = (in: InputStream) => transferFully(in, System.err)
def toStdOut = (in: InputStream) => transferFully(in, System.out)
def transferFully(in: InputStream, out: OutputStream): Unit =
try { transferFullyImpl(in, out) }
catch { case _: InterruptedException => () }
private[this] def appendLine(buffer: Appendable): String => Unit =
line =>
{
buffer.append(line)
buffer.append(Newline)
}
private[this] def transferFullyImpl(in: InputStream, out: OutputStream)
{
val continueCount = 1//if(in.isInstanceOf[PipedInputStream]) 1 else 0
@ -113,7 +130,31 @@ private abstract class AbstractProcessBuilder extends ProcessBuilder with SinkPa
def run(connectInput: Boolean): Process = run(BasicIO.standard(connectInput))
def run(log: Logger): Process = run(log, false)
def run(log: Logger, connectInput: Boolean): Process = run(BasicIO(log, connectInput))
private[this] def getString(log: Option[Logger], withIn: Boolean): String =
{
val buffer = new StringBuffer
val code = this ! BasicIO(buffer, log, withIn)
if(code == 0) buffer.toString else error("Nonzero exit value: " + code)
}
def !! = getString(None, false)
def !!(log: Logger) = getString(Some(log), false)
def !!< = getString(None, true)
def !!<(log: Logger) = getString(Some(log), true)
def lines: Stream[String] = lines(false, true, None)
def lines(log: Logger): Stream[String] = lines(false, true, Some(log))
def lines_! : Stream[String] = lines(false, false, None)
def lines_!(log: Logger): Stream[String] = lines(false, false, Some(log))
private[this] def lines(withInput: Boolean, nonZeroException: Boolean, log: Option[Logger]): Stream[String] =
{
val streamed = Streamed[String](nonZeroException)
val process = run(new ProcessIO(BasicIO.input(withInput), BasicIO.processFully(streamed.process), BasicIO.getErr(log)))
Spawn { streamed.done(process.exitValue()) }
streamed.stream()
}
def ! = run(false).exitValue()
def !< = run(true).exitValue()
def !(log: Logger) = runBuffered(log, false)
@ -413,3 +454,20 @@ object Uncloseable
def protect(in: InputStream): InputStream = if(in eq System.in) Uncloseable(in) else in
def protect(out: OutputStream): OutputStream = if( (out eq System.out) || (out eq System.err)) Uncloseable(out) else out
}
private object Streamed
{
def apply[T](nonzeroException: Boolean): Streamed[T] =
{
val q = new java.util.concurrent.LinkedBlockingQueue[Either[Int, T]]
def next(): Stream[T] =
q.take match
{
case Left(0) => Stream.empty
case Left(code) => if(nonzeroException) error("Nonzero exit code: " + code) else Stream.empty
case Right(s) => Stream.cons(s, next)
}
new Streamed((s: T) => q.put(Right(s)), code => q.put(Left(code)), () => next())
}
}
private final class Streamed[T](val process: T => Unit, val done: Int => Unit, val stream: () => Stream[T]) extends NotNull