diff --git a/src/library/scala/sys/process/BasicIO.scala b/src/library/scala/sys/process/BasicIO.scala index 9c5deaccb..b8881db9f 100644 --- a/src/library/scala/sys/process/BasicIO.scala +++ b/src/library/scala/sys/process/BasicIO.scala @@ -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") diff --git a/src/library/scala/sys/process/Process.scala b/src/library/scala/sys/process/Process.scala index 73152a4a7..4e4ffb5b8 100644 --- a/src/library/scala/sys/process/Process.scala +++ b/src/library/scala/sys/process/Process.scala @@ -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( {dxPath.absolutePath} --dex --output={classesDexPath.absolutePath} {classesMinJarPath.absolutePath}) + * }}} + */ 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) } diff --git a/src/library/scala/sys/process/ProcessBuilder.scala b/src/library/scala/sys/process/ProcessBuilder.scala index 3462f865b..8b75adc97 100644 --- a/src/library/scala/sys/process/ProcessBuilder.scala +++ b/src/library/scala/sys/process/ProcessBuilder.scala @@ -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, "")) + + /** 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, "")) + + /** 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) } } diff --git a/src/library/scala/sys/process/ProcessIO.scala b/src/library/scala/sys/process/ProcessIO.scala index 041cad1ef..cd010b09b 100644 --- a/src/library/scala/sys/process/ProcessIO.scala +++ b/src/library/scala/sys/process/ProcessIO.scala @@ -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( diff --git a/src/library/scala/sys/process/ProcessLogger.scala b/src/library/scala/sys/process/ProcessLogger.scala index 91107a9a1..21da3e45f 100644 --- a/src/library/scala/sys/process/ProcessLogger.scala +++ b/src/library/scala/sys/process/ProcessLogger.scala @@ -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) diff --git a/src/library/scala/sys/process/package.scala b/src/library/scala/sys/process/package.scala index f67fbcc27..7a21a68d2 100644 --- a/src/library/scala/sys/process/package.scala +++ b/src/library/scala/sys/process/package.scala @@ -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._