Document the usage and methods of scala.sys.process.

From the first international scaladoc marathon.

   Contributed by: Daniel Sobral

git-svn-id: http://lampsvn.epfl.ch/svn-repos/scala/scala/trunk@25599 5e8d7ff9-d8ef-0310-90f0-a4852d11357a
This commit is contained in:
Joshua.Suereth 2011-08-31 19:16:18 +00:00
parent 0b2544fa4b
commit 47d321c78e
6 changed files with 316 additions and 11 deletions

View File

@ -14,6 +14,12 @@ import java.io.{ BufferedReader, InputStreamReader, FilterInputStream, FilterOut
import java.util.concurrent.LinkedBlockingQueue
import scala.collection.immutable.Stream
/**
* This object contains factories for [[scala.sys.process.ProcessIO]],
* which can be used to control the I/O of a [[scala.sys.process.Process]]
* when a [[scala.sys.process.ProcessBuilder]] is started with the `run`
* command.
*/
object BasicIO {
final val BufferSize = 8192
final val Newline = props("line.separator")

View File

@ -14,6 +14,19 @@ import ProcessBuilder._
/** Represents a process that is running or has finished running.
* It may be a compound process with several underlying native processes (such as 'a #&& b`).
*
* This trait is often not used directly, though its companion object contains
* factories for [[scala.sys.process.ProcessBuilder]], the main component of this
* package.
*
* It is used directly when calling the method `run` on a `ProcessBuilder`,
* which makes the process run in the background. The methods provided on `Process`
* make it possible for one to block until the process exits and get the exit value,
* or destroy the process altogether.
*
* Presently, one cannot poll the `Process` to see if it has finished.
*
* @see [[scala.sys.process.ProcessBuilder]]
*/
trait Process {
/** Blocks until this process exits and returns the exit code.*/
@ -25,20 +38,52 @@ trait Process {
/** Methods for constructing simple commands that can then be combined. */
object Process extends ProcessImpl with ProcessCreation { }
/** Factories for creating [[scala.sys.process.ProcessBuilder]]. They can be
* found on and used through [[scala.sys.process.Process]]'s companion object.
*/
trait ProcessCreation {
/** Create a [[scala.sys.process.ProcessBuilder]] from a `String`, including the
* parameters.
*
* @example {{{ apply("cat file.txt") }}}
*/
def apply(command: String): ProcessBuilder = apply(command, None)
/** Create a [[scala.sys.process.ProcessBuilder]] from a sequence of `String`,
* where the head is the command and each element of the tail is a parameter.
*
* @example {{{ apply("cat" :: files) }}}
*/
def apply(command: Seq[String]): ProcessBuilder = apply(command, None)
/** Create a [[scala.sys.process.ProcessBuilder]] from a command represented by a `String`,
* and a sequence of `String` representing the arguments.
*
* @example {{{ apply("cat", files) }}}
*/
def apply(command: String, arguments: Seq[String]): ProcessBuilder = apply(command +: arguments, None)
/** create ProcessBuilder with working dir set to File and extra environment variables */
/** Create a [[scala.sys.process.ProcessBuilder]] with working dir set to `File` and extra
* environment variables.
*
* @example {{{ apply("java", new java.ioFile("/opt/app"), "CLASSPATH" -> "library.jar") }}}
*/
def apply(command: String, cwd: File, extraEnv: (String, String)*): ProcessBuilder =
apply(command, Some(cwd), extraEnv: _*)
/** create ProcessBuilder with working dir set to File and extra environment variables */
/** Create a [[scala.sys.process.ProcessBuilder]] with working dir set to `File` and extra
* environment variables.
*
* @example {{{ apply("java" :: javaArgs, new java.ioFile("/opt/app"), "CLASSPATH" -> "library.jar") }}}
*/
def apply(command: Seq[String], cwd: File, extraEnv: (String, String)*): ProcessBuilder =
apply(command, Some(cwd), extraEnv: _*)
/** create ProcessBuilder with working dir optionally set to File and extra environment variables */
/** Create a [[scala.sys.process.ProcessBuilder]] with working dir optionally set to
* `File` and extra environment variables.
*
* @example {{{ apply("java", params.get("cwd"), "CLASSPATH" -> "library.jar") }}}
*/
def apply(command: String, cwd: Option[File], extraEnv: (String, String)*): ProcessBuilder = {
apply(command.split("""\s+"""), cwd, extraEnv : _*)
// not smart to use this on windows, because CommandParser uses \ to escape ".
@ -48,7 +93,11 @@ trait ProcessCreation {
}*/
}
/** create ProcessBuilder with working dir optionally set to File and extra environment variables */
/** Create a [[scala.sys.process.ProcessBuilder]] with working dir optionally set to
* `File` and extra environment variables.
*
* @example {{{ apply("java" :: javaArgs, params.get("cwd"), "CLASSPATH" -> "library.jar") }}}
*/
def apply(command: Seq[String], cwd: Option[File], extraEnv: (String, String)*): ProcessBuilder = {
val jpb = new JProcessBuilder(command.toArray: _*)
cwd foreach (jpb directory _)
@ -56,30 +105,111 @@ trait ProcessCreation {
apply(jpb)
}
/** create a [[scala.sys.process.ProcessBuilder]] from a `java.lang.ProcessBuilder`.
*
* @example {{{
* apply((new java.lang.ProcessBuilder("ls", "-l")) directory new java.io.File(System.getProperty("user.home")))
* }}}
*/
def apply(builder: JProcessBuilder): ProcessBuilder = new Simple(builder)
/** create a [[scala.sys.process.ProcessBuilder]] from a `java.io.File`. This
* `ProcessBuilder` can then be used as a `Source` or a `Sink`, so one can
* pipe things from and to it.
*/
def apply(file: File): FileBuilder = new FileImpl(file)
/** Create a [[scala.sys.process.ProcessBuilder]] from a `java.net.URL`. This
* `ProcessBuilder` can then be used as a `Source`, so that one can pipe things
* from it.
*/
def apply(url: URL): URLBuilder = new URLImpl(url)
/** Create a [[scala.sys.process.ProcessBuilder]] from a Scala XML Element.
* This can be used as a way to template strings.
*
* @example {{{
* apply(<x> {dxPath.absolutePath} --dex --output={classesDexPath.absolutePath} {classesMinJarPath.absolutePath}</x>)
* }}}
*/
def apply(command: scala.xml.Elem): ProcessBuilder = apply(command.text.trim)
/** Create a [[scala.sys.process.ProcessBuilder]] from a `Boolean`. This can be
* to force an exit value.
*/
def apply(value: Boolean): ProcessBuilder = apply(value.toString, if (value) 0 else 1)
/** Create a [[scala.sys.process.ProcessBuilder]] from a `String` name and a
* `Boolean`. This can be used to force an exit value, with the name being
* used for `toString`.
*/
def apply(name: String, exitValue: => Int): ProcessBuilder = new Dummy(name, exitValue)
/** Create a sequence of [[scala.sys.process.ProcessBuilder.Source]] from a sequence of
* something else for which there's an implicit conversion to `Source`.
*/
def applySeq[T](builders: Seq[T])(implicit convert: T => Source): Seq[Source] = builders.map(convert)
/** Create a [[scala.sys.process.ProcessBuilder]] from one or more
* [[scala.sys.process.ProcessBuilder.Source]], which can then be
* piped to something else.
*
* This will concatenate the output of all sources.
*/
def cat(file: Source, files: Source*): ProcessBuilder = cat(file +: files)
/** Create a [[scala.sys.process.ProcessBuilder]] from a non-empty sequence
* of [[scala.sys.process.ProcessBuilder.Source]], which can then be
* piped to something else.
*
* This will concatenate the output of all sources. For example:
*
* {{{
* import scala.sys.process._
* import scala.sys.process.Process.cat
* import java.net.URL
* import java.io.File
*
* val spde = new URL("http://technically.us/spde/About")
* val dispatch = new URL("http://databinder.net/dispatch/About")
* val build = new File("project/build.properties")
* cat(spde, dispatch, build) #| "grep -i scala" !
* }}}
*/
def cat(files: Seq[Source]): ProcessBuilder = {
require(files.nonEmpty)
files map (_.cat) reduceLeft (_ #&& _)
}
}
/** Provide implicit conversions for the factories offered by [[scala.sys.process.Process]]'s
* companion object. These implicits can then be used to decrease the noise in a pipeline
* of commands, making it look more shell-like. They are available through the package object
* [[scala.sys.process]].
*/
trait ProcessImplicits {
import Process._
/** Return a sequence of [[scala.sys.process.ProcessBuilder.Source]] from a sequence
* of values for which an implicit conversion to `Source` is available.
*/
implicit def buildersToProcess[T](builders: Seq[T])(implicit convert: T => Source): Seq[Source] = applySeq(builders)
/** Implicitly convert a `java.lang.ProcessBuilder` into a Scala one. */
implicit def builderToProcess(builder: JProcessBuilder): ProcessBuilder = apply(builder)
/** Implicitly convert a `java.io.File` into a [[scala.sys.process.ProcessBuilder]] */
implicit def fileToProcess(file: File): FileBuilder = apply(file)
/** Implicitly convert a `java.net.URL` into a [[scala.sys.process.ProcessBuilder]] */
implicit def urlToProcess(url: URL): URLBuilder = apply(url)
/** Implicitly convert a [[scala.xml.Elem]] into a [[scala.sys.process.ProcessBuilder]] */
implicit def xmlToProcess(command: scala.xml.Elem): ProcessBuilder = apply(command)
/** Implicitly convert a `String` into a [[scala.sys.process.ProcessBuilder]] */
implicit def stringToProcess(command: String): ProcessBuilder = apply(command)
/** Implicitly convert a sequence of `String` into a [[scala.sys.process.ProcessBuilder]] */
implicit def stringSeqToProcess(command: Seq[String]): ProcessBuilder = apply(command)
}

View File

@ -12,7 +12,66 @@ package process
import processInternal._
import ProcessBuilder._
/** Represents a runnable process. */
/** Represents a runnable process.
*
* This is the main component of this package. A `ProcessBuilder` may be composed with
* others, either concatenating their outputs or piping them from one to the next, and
* possibly with conditional execution depending on the last process exit value.
*
* Once executed, one can retrieve the output or redirect it to a
* [[scala.sys.process.ProcessLogger]], or one can get the exit value, discarding or
* redirecting the output.
*
* One creates a `ProcessBuilder` through factories provided in [[scala.sys.process.Process]]'s
* companion object, or implicit conversions based on these factories made available in the
* package object [[scala.sys.process]].
*
* Let's examine in detail one example of usage:
*
* {{{
* import scala.sys.process._
* "find src -name *.scala -exec grep null {} ;" #| "xargs test -z" #&& "echo null-free" #|| "echo null detected" !
* }}}
*
* Note that every `String` is implicitly converted into a `ProcessBuilder`
* through the implicits imported from [[scala.sys.process]]. These `ProcessBuilder` are then
* combined in three different ways.
*
* 1. `#|` pipes the output of the first command into the input of the second command. It
* mirrors a shell pipe (`|`).
* 2. `#&&` conditionally executes the second command if the previous one finished with
* exit value 0. It mirrors shell's `&&`.
* 3. `#||` conditionally executes the third command if the exit value of the previous
* command is is different than zero. It mirrors shell's `&&`.
*
* Not shown here, the equivalent of a shell's `;` would be `###`. The reason for this name is
* that `;` is a reserved token in Scala.
*
* Finally, `!` at the end executes the commands, and returns the exit value. If the output
* was desired instead, one could run that with `!!` instead.
*
* If one wishes to execute the commands in background, one can either call `run`, which
* returns a [[scala.sys.process.Process]] from which the exit value can be obtained, or
* `lines`, which returns a [scala.collection.immutable.Stream] of output lines. This throws
* an exception at the end of the `Stream` is the exit value is non-zero. To avoid exceptions,
* one can use `lines_!` instead.
*
* One can also start the commands in specific ways to further control their I/O. Using `!<` to
* start the commands will use the stdin from the current process for them. All methods can
* be used passing a [[scala.sys.process.ProcessLogger]] to capture the output, both stderr and
* stdout. And, when using `run`, one can pass a [[scala.sys.process.ProcessIO]] to control
* stdin, stdout and stderr.
*
* The stdin of a command can be redirected from a `java.io.InputStream`, a `java.io.File`, a
* `java.net.URL` or another `ProcessBuilder` through the method `#<`. Likewise, the stdout
* can be sent to a `java.io.OutputStream`, a `java.io.File` or another `ProcessBuilder` with
* the method `#>`. The method `#>>` can be used to append the output to a `java.io.File`.
* For example:
*
* {{{
* new URL("http://databinder.net/dispatch/About") #> "grep JSON" #>> new File("About_JSON") !
* }}}
*/
trait ProcessBuilder extends Source with Sink {
/** 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.*/
@ -83,40 +142,75 @@ trait ProcessBuilder extends Source with Sink {
def hasExitValue: Boolean
}
/** This object contains traits used to describe input and output sources. */
object ProcessBuilder extends ProcessBuilderImpl {
/** Used when creating [[scala.sys.process.ProcessBuilder.Source]] from an URL. */
trait URLBuilder extends Source {
}
/** Used when creating [[scala.sys.process.ProcessBuilder.Source]] and/or
* [[scala.sys.process.ProcessBuilder.Sink]] from a file.
*/
trait FileBuilder extends Sink with Source {
/** Append the contents of a `java.io.File` to this file */
def #<<(f: File): ProcessBuilder
/** Append the contents from a `java.net.URL` to this file */
def #<<(u: URL): ProcessBuilder
/** Append the contents of a `java.io.InputStream` to this file */
def #<<(i: => InputStream): ProcessBuilder
/** Append the contents of a [[scala.sys.process.ProcessBuilder]] to this file */
def #<<(p: ProcessBuilder): ProcessBuilder
}
/** Represents everything that can be used as an input to a
* [[scala.sys.process.ProcessBuilder]].
*/
trait Source {
protected def toSource: ProcessBuilder
/** Writes the output stream of this process to the given file. */
def #> (f: File): ProcessBuilder = toFile(f, false)
/** Appends the output stream of this process to the given file. */
def #>> (f: File): ProcessBuilder = toFile(f, true)
/** Writes the output stream of this process to the given OutputStream. The
* argument is call-by-name, so the stream is recreated, written, and closed each
* time this process is executed. */
* argument is call-by-name, so the stream is recreated, written, and closed each
* time this process is executed.
*/
def #>(out: => OutputStream): ProcessBuilder = #> (new OStreamBuilder(out, "<output stream>"))
/** Writes the output stream of this process to a [[scala.sys.process.ProcessBuilder]]. */
def #>(b: ProcessBuilder): ProcessBuilder = new PipedBuilder(toSource, b, false)
/** Returnes a [[scala.sys.process.ProcessBuilder]] representing this `Source`. */
def cat = toSource
private def toFile(f: File, append: Boolean) = #> (new FileOutput(f, append))
}
/** Represents everything that can receive an output from a
* [[scala.sys.process.ProcessBuilder]].
*/
trait Sink {
protected def toSink: ProcessBuilder
/** Reads the given file into the input stream of this process. */
def #< (f: File): ProcessBuilder = #< (new FileInput(f))
/** Reads the given URL into the input stream of this process. */
def #< (f: URL): ProcessBuilder = #< (new URLInput(f))
/** Reads the given InputStream into the input stream of this process. The
* argument is call-by-name, so the stream is recreated, read, and closed each
* time this process is executed. */
* argument is call-by-name, so the stream is recreated, read, and closed each
* time this process is executed.
*/
def #<(in: => InputStream): ProcessBuilder = #< (new IStreamBuilder(in, "<input stream>"))
/** Reads the output of a [[scala.sys.process.ProcessBuilder]] into the input stream of this process. */
def #<(b: ProcessBuilder): ProcessBuilder = new PipedBuilder(b, toSink, false)
}
}

View File

@ -11,7 +11,12 @@ package process
import processInternal._
/** Each method will be called in a separate thread.
/** This class is used to control the I/O of every [[scala.sys.process.ProcessBuilder]].
* Most of the time, there is no need to interact with `ProcessIO` directly. However, if
* fine control over the I/O of a `ProcessBuilder` is desired, one can use the factories
* on [[scala.sys.process.BasicIO]] stand-alone object to create one.
*
* Each method will be called in a separate thread.
* If daemonizeThreads is true, they will all be marked daemon threads.
*/
final class ProcessIO(

View File

@ -39,6 +39,7 @@ trait ProcessLogger {
def buffer[T](f: => T): T
}
/** A [[scala.sys.process.ProcessLogger]] that writes output to a file. */
class FileProcessLogger(file: File) extends ProcessLogger with Closeable with Flushable {
private val writer = (
new PrintWriter(
@ -56,9 +57,26 @@ class FileProcessLogger(file: File) extends ProcessLogger with Closeable with Fl
def flush(): Unit = writer.flush()
}
object ProcessLogger {
/** Provides factories to create [[scala.sys.process.ProcessLogger]], which
* are used to capture output of [[scala.sys.process.ProcessBuilder]] commands
* when run.
*/
object ProcessLogger {
/** Creates a [[scala.sys.process.ProcessLogger]] that redirects output to a `java.io.File`. */
def apply(file: File): FileProcessLogger = new FileProcessLogger(file)
/** Creates a [[scala.sys.process.ProcessLogger]] that sends all output, standard and error,
* to the passed function.
*/
def apply(fn: String => Unit): ProcessLogger = apply(fn, fn)
/** Creates a [[scala.sys.process.ProcessLogger]] that sends all output to the corresponding
* function.
*
* @param fout This function will receive standard outpout.
*
* @param ferr This function will receive standard error.
*/
def apply(fout: String => Unit, ferr: String => Unit): ProcessLogger =
new ProcessLogger {
def out(s: => String): Unit = fout(s)

View File

@ -11,6 +11,58 @@
// for process debugging output.
//
package scala.sys {
/**
* This package is used to create process pipelines, similar to Unix command pipelines.
*
* The key concept is that one builds a [[scala.sys.process.Process]] that will run and return an exit
* value. This `Process` is usually composed of one or more [[scala.sys.process.ProcessBuilder]], fed by a
* [[scala.sys.process.ProcessBuilder.Source]] and feeding a [[scala.sys.process.ProcessBuilder.Sink]]. A
* `ProcessBuilder` itself is both a `Source` and a `Sink`.
*
* As `ProcessBuilder`, `Sink` and `Source` are abstract, one usually creates them with `apply` methods on
* the companion object of [[scala.sys.process.Process]], or through implicit conversions available in this
* package object from `String` and other types. The pipe is composed through unix-like pipeline and I/O
* redirection operators available on [[scala.sys.process.ProcessBuilder]].
*
* The example below shows how to build and combine such commands. It searches for `null` uses in the `src`
* directory, printing a message indicating whether they were found or not. The first command pipes its
* output to the second command, whose exit value is then used to choose between the third or fourth
* commands. This same example is explained in greater detail on [[scala.sys.process.ProcessBuilder]].
*
* {{{
* import scala.sys.process._
* (
* "find src -name *.scala -exec grep null {} ;"
* #| "xargs test -z"
* #&& "echo null-free" #|| "echo null detected"
* ) !
* }}}
*
* Other implicits available here are for [[scala.sys.process.ProcessBuilder.FileBuilder]], which extends
* both `Sink` and `Source`, and for [[scala.sys.process.ProcessBuilder.URLBuilder]], which extends
* `Source` alone.
*
* One can even create a `Process` solely out of these, without running any command. For example, this will
* download from a URL to a file:
*
* {{{
* import java.io.File
* import scala.sys.process._
* new File("About.html") #> new File("About_copy.html") !
* }}}
*
* One may use a `Process` directly through `ProcessBuilder`'s `run` method, which starts the process in
* the background, and returns a `Process`. If background execution is not desired, one can get a
* `ProcessBuilder` to execute through a method such as `!`, `lines`, `run` or variations thereof. That
* will create the `Process` to execute the commands, and return either the exit value or the output, maybe
* throwing an exception.
*
* Finally, when executing a `ProcessBuilder`, one may pass a [[scala.sys.process.ProcessLogger]] to
* capture stdout and stderr of the executing processes. A `ProcessLogger` may be created through its
* companion object from functions of type `(String) => Unit`, or one might redirect it to a file, using
* [[scala.sys.process.FileProcessLogger]], which can also be created through `ProcessLogger`'s object
* companion.
*/
package object process extends ProcessImplicits {
def javaVmArguments: List[String] = {
import collection.JavaConversions._