├── project
└── plugins.sbt
├── src
├── main
│ └── scala
│ │ └── at
│ │ └── loveoneanother
│ │ └── schale
│ │ ├── Main.scala
│ │ ├── Command.scala
│ │ ├── ProcControl.scala
│ │ ├── package.scala
│ │ ├── Shell.scala
│ │ ├── Env.scala
│ │ └── Proc.scala
└── test
│ └── scala
│ └── at
│ └── loveoneanother
│ └── schale
│ └── ProcTest.scala
├── .gitignore
├── .project
├── .classpath
├── LICENSE
└── README.md
/project/plugins.sbt:
--------------------------------------------------------------------------------
1 | addSbtPlugin("com.eed3si9n" % "sbt-assembly" % "0.9.2")
2 |
--------------------------------------------------------------------------------
/src/main/scala/at/loveoneanother/schale/Main.scala:
--------------------------------------------------------------------------------
1 | package at.loveoneanother.schale
2 |
3 | object Main {
4 | def main(args: Array[String]): Unit = {}
5 | }
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | *.class
2 | *.log
3 |
4 | # sbt specific
5 | dist/*
6 | target/
7 | lib_managed/
8 | src_managed/
9 | project/boot/
10 | project/plugins/project/
11 |
12 | # Scala-IDE specific
13 | .scala_dependencies
14 | /bin
15 | .cache
16 |
--------------------------------------------------------------------------------
/src/main/scala/at/loveoneanother/schale/Command.scala:
--------------------------------------------------------------------------------
1 | package at.loveoneanother.schale
2 |
3 | /**
4 | * Run a single program in default current working directory and default environment.
5 | */
6 | object Command {
7 | def apply(args: String*)(implicit env: Env): Proc = new Proc(args: _*)(env)
8 | }
--------------------------------------------------------------------------------
/src/main/scala/at/loveoneanother/schale/ProcControl.scala:
--------------------------------------------------------------------------------
1 | package at.loveoneanother.schale
2 |
3 | abstract class ProcControl
4 | // Output control
5 | case object ProcStdoutReadLine
6 | case object ProcStdoutReadChar
7 | case object ProcStderrReadLine
8 | case object ProcStderrReadChar
9 | // Input control
10 | case object ProcStdinFlush
11 | case object ProcStdinClose
--------------------------------------------------------------------------------
/.project:
--------------------------------------------------------------------------------
1 |
2 |
3 | schale
4 |
5 |
6 |
7 |
8 |
9 | org.scala-ide.sdt.core.scalabuilder
10 |
11 |
12 |
13 |
14 |
15 | org.scala-ide.sdt.core.scalanature
16 | org.eclipse.jdt.core.javanature
17 |
18 |
19 |
--------------------------------------------------------------------------------
/src/main/scala/at/loveoneanother/schale/package.scala:
--------------------------------------------------------------------------------
1 | package at.loveoneanother
2 |
3 | import com.typesafe.config.ConfigFactory
4 |
5 | import akka.actor.ActorSystem
6 | import at.loveoneanother.schale.Env
7 |
8 | package object schale {
9 | implicit val defaultEnv = new Env(Map(), System getProperty "user.dir")
10 |
11 | /** The actor system is responsible for monitoring process interaction actors. */
12 | implicit lazy val actorSystem: ActorSystem = ActorSystem("schale", ConfigFactory.parseString("akka.daemonic=on"))
13 | }
--------------------------------------------------------------------------------
/.classpath:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/src/main/scala/at/loveoneanother/schale/Shell.scala:
--------------------------------------------------------------------------------
1 | package at.loveoneanother.schale
2 |
3 | /**
4 | * Call operating system's script interpreter to run a script.
5 | */
6 | object Shell {
7 |
8 | /**
9 | * Run a script using operating system's script interpreter. Currently only support *nix operating systems.
10 | */
11 | def apply(script: String, interpreter: String = "/bin/sh")(implicit env: Env): Proc =
12 | System.getProperty("os.name").toLowerCase match {
13 | case os if os.contains("nux") || os.contains("sun") || os.contains("mac") => new Proc(interpreter, "-c", script)(env)
14 | case _ => throw new UnsupportedOperationException("Interpret does not work in your OS")
15 | }
16 | }
--------------------------------------------------------------------------------
/src/main/scala/at/loveoneanother/schale/Env.scala:
--------------------------------------------------------------------------------
1 | package at.loveoneanother.schale
2 |
3 | import java.io.File
4 |
5 | /**
6 | * Extra (or override) environment variables for running script or command.
7 | */
8 | class Env(var vars: Map[String, String] = Map(), var pwd: String = System getProperty "user.dir") {
9 | implicit var self = this
10 |
11 | override def toString() = pwd + (vars toString)
12 |
13 | /**
14 | * Change current working directory in this environment.
15 | */
16 | def cd(newPwd: String)(fun: => Unit) {
17 | val oldPwd = pwd
18 | pwd = newPwd
19 | try {
20 | fun
21 | } finally {
22 | pwd = oldPwd
23 | }
24 | }
25 |
26 | /**
27 | * Give more environment variables to this environment.
28 | */
29 | def env(extra: Map[String, String])(fun: => Unit) {
30 | val oldVars = vars
31 | vars ++= extra
32 | try {
33 | fun
34 | } finally {
35 | vars = oldVars
36 | }
37 | }
38 |
39 | def applyTo(pb: ProcessBuilder) {
40 | val env = pb.environment()
41 | vars foreach { kv => env.put(kv._1, kv._2) }
42 | pb.directory(new File(pwd))
43 | }
44 | }
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Copyright (c) 2013, Howard Guo
2 | All rights reserved.
3 |
4 | Redistribution and use in source and binary forms, with or without modification,
5 | are permitted provided that the following conditions are met:
6 |
7 | Redistributions of source code must retain the above copyright notice, this
8 | list of conditions and the following disclaimer.
9 |
10 | Redistributions in binary form must reproduce the above copyright notice, this
11 | list of conditions and the following disclaimer in the documentation and/or
12 | other materials provided with the distribution.
13 |
14 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
15 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
16 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
17 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
18 | ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
19 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
20 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
21 | ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
22 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
23 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
--------------------------------------------------------------------------------
/src/main/scala/at/loveoneanother/schale/Proc.scala:
--------------------------------------------------------------------------------
1 | package at.loveoneanother.schale
2 |
3 | import java.io.BufferedReader
4 | import java.io.BufferedWriter
5 | import java.io.InputStreamReader
6 | import java.io.OutputStreamWriter
7 |
8 | import scala.util.control.Breaks.break
9 | import scala.util.control.Breaks.breakable
10 |
11 | import akka.actor.ActorDSL.Act
12 | import akka.actor.ActorDSL.actor
13 | import akka.actor.ActorRef
14 | import akka.actor.actorRef2Scala
15 | /**
16 | * An operating system process.
17 | */
18 | class Proc(args: String*)(env: Env) extends Traversable[String] {
19 | protected val pb = new ProcessBuilder(args: _*)
20 | protected var proc: Process = null
21 | protected var inputWriter: BufferedWriter = null
22 | protected var outputReader: BufferedReader = null
23 | protected var errorReader: BufferedReader = null
24 |
25 | /**
26 | * Start process and traverse lines in standard output.
27 | */
28 | def stdout = new Traversable[String] {
29 | def foreach[U](fun: String => U) {
30 | startProc()
31 | collectOutput(fun, outputReader)
32 | }
33 | }
34 |
35 | /**
36 | * Start process and traverse lines in standard error.
37 | */
38 | def stderr = new Traversable[String] {
39 | def foreach[U](fun: String => U) {
40 | startProc()
41 | collectOutput(fun, errorReader)
42 | }
43 | }
44 |
45 | /**
46 | * Start process and traverse lines in both standard output and error.
47 | */
48 | def foreach[U](fun: String => U) {
49 | pb.redirectErrorStream(true)
50 | startProc()
51 | collectOutput(fun, outputReader)
52 | }
53 |
54 | /**
55 | * Start process and return all output in standard output and error.
56 | */
57 | override def toString = {
58 | val output = (collect { case s: String => s }).mkString(String format "%n")
59 | waitFor()
60 | output
61 | }
62 |
63 | /**
64 | * Feed data to standard input.
65 | */
66 | def input(lines: String*) = {
67 | startProc()
68 | try {
69 | for (s <- lines) {
70 | inputWriter.write(s)
71 | }
72 | } finally {
73 | inputWriter.close()
74 | }
75 | this
76 | }
77 |
78 | /**
79 | * Wait for process to finish and return its exit code.
80 | * If process has not been started, it will be started and then waited.
81 | */
82 | def waitFor(): Int = {
83 | if (proc == null)
84 | startProc()
85 | try {
86 | if (inputWriter != null)
87 | inputWriter.close()
88 | // there is no need to close output readers
89 | } finally {
90 | }
91 | proc.waitFor()
92 | }
93 |
94 | /**
95 | * Start this process in background (does not block main thread).
96 | */
97 | def bg() = this.startProc()
98 |
99 | /**
100 | * Destroy the process and return its exit value.
101 | * If process has not been started, an IllegalStateException is thrown.
102 | */
103 | def destroy(): Int = proc match {
104 | case null => throw new IllegalStateException("Process has not started")
105 | case _ => proc.destroy(); waitFor()
106 | }
107 |
108 | /**
109 | * Start this process in background and commence interactive IO with it.
110 | */
111 | def interact(fun: ActorRef => Unit) = {
112 | startProc()
113 | fun(actor(new Act {
114 | become {
115 | // Output control
116 | case ProcStdoutReadLine =>
117 | sender ! outputReader.readLine()
118 | case ProcStdoutReadChar =>
119 | sender ! outputReader.read()
120 | case ProcStderrReadLine =>
121 | sender ! errorReader.readLine()
122 | case ProcStderrReadChar =>
123 | sender ! errorReader.read()
124 | // Input control
125 | case s: String =>
126 | inputWriter.write(s)
127 | case c: Char =>
128 | inputWriter.write(c.toInt)
129 | case ProcStdinFlush =>
130 | inputWriter.flush()
131 | case ProcStdinClose =>
132 | inputWriter.close(); inputWriter = null
133 | }
134 | }))
135 | this
136 | }
137 |
138 | /**
139 | * Start the process.
140 | */
141 | protected def startProc() {
142 | if (proc == null) {
143 | env.applyTo(pb)
144 | proc = pb.start()
145 | inputWriter = new BufferedWriter(new OutputStreamWriter(proc getOutputStream))
146 | outputReader = new BufferedReader(new InputStreamReader(proc getInputStream))
147 | errorReader = new BufferedReader(new InputStreamReader(proc getErrorStream))
148 | }
149 | }
150 |
151 | /**
152 | * Collect process output from the reader, and feel them to the function.
153 | */
154 | protected def collectOutput[U](fun: String => U, reader: BufferedReader) {
155 | try {
156 | breakable {
157 | while (true) {
158 | val line = reader.readLine()
159 | if (line == null)
160 | break
161 | fun(line)
162 | }
163 | }
164 | } finally {
165 | reader close
166 | }
167 | }
168 | }
--------------------------------------------------------------------------------
/src/test/scala/at/loveoneanother/schale/ProcTest.scala:
--------------------------------------------------------------------------------
1 | package test.at.loveoneanother.schale
2 |
3 | import java.io.IOException
4 |
5 | import scala.concurrent.Await
6 | import scala.concurrent.ExecutionContext.Implicits.global
7 | import scala.concurrent.duration.DurationInt
8 | import scala.util.Failure
9 | import scala.util.Success
10 |
11 | import org.scalatest.FunSuite
12 |
13 | import akka.actor.actorRef2Scala
14 | import akka.pattern.ask
15 | import akka.util.Timeout
16 | import at.loveoneanother.schale.Command
17 | import at.loveoneanother.schale.Env
18 | import at.loveoneanother.schale.ProcStderrReadChar
19 | import at.loveoneanother.schale.ProcStdinClose
20 | import at.loveoneanother.schale.ProcStdinFlush
21 | import at.loveoneanother.schale.ProcStdoutReadLine
22 | import at.loveoneanother.schale.Shell
23 |
24 | class ProcTest extends FunSuite {
25 | test("run process and use exit status") {
26 | expectResult(0) { Command("echo", "a").waitFor() }
27 | }
28 |
29 | test("run process without IO") {
30 | Command("echo", "a").waitFor()
31 | intercept[IOException] {
32 | Command("does not exist").waitFor()
33 | }
34 | }
35 |
36 | test("single command and collect stdout/stderr") {
37 | expectResult("a") { Command("echo", "a").toString() }
38 | }
39 |
40 | test("consume stdout") {
41 | for (line <- Command("echo", "a").stdout)
42 | expectResult("a") { line }
43 | }
44 |
45 | test("consume stderr") {
46 | for (line <- Command("echo", "a").stderr)
47 | expectResult("") { line }
48 | }
49 |
50 | test("consume stdout and stderr") {
51 | for (line <- Command("echo", "a"))
52 | expectResult("a") { line }
53 | }
54 |
55 | test("feed to stdin") {
56 | expectResult(String.format("a%nb")) {
57 | (Command("cat").input(String.format("a%n"), String.format("b%n"))).toString()
58 | }
59 | }
60 |
61 | test("feed to stdin and consume stdout/stderr") {
62 | for (line <- Command("cat").input("a"))
63 | expectResult("a") { line }
64 | }
65 |
66 | test("interpret and collect stdout/stderr") {
67 | expectResult(String.format("a")) {
68 | Shell("echo a").toString()
69 | }
70 | }
71 |
72 | test("interpret using other interpreter") {
73 | expectResult(String.format("a")) {
74 | Shell("echo a", "/bin/ksh").toString()
75 | }
76 | }
77 |
78 | test("interpret and feed to stdin, then consume stdout/stderr") {
79 | for (line <- Shell("cat").input("a")) {
80 | expectResult("a") { line }
81 | }
82 | }
83 |
84 | test("interpret and use exit status") {
85 | val interpreter = Shell("cat").input("a")
86 | for (line <- interpreter) {
87 | expectResult("a") { line }
88 | }
89 | expectResult(0) { interpreter.waitFor() }
90 | }
91 |
92 | test("destroy process") {
93 | val proc = Shell("sleep 100")
94 | intercept[IllegalStateException] {
95 | proc.destroy()
96 | }
97 | proc.bg()
98 | expectResult(143) { proc.destroy() }
99 | }
100 |
101 | test("run in specified cwd") {
102 | new Env(pwd = "/") {
103 | expectResult("/") { Command("pwd").toString }
104 | expectResult("/") { Shell("pwd").toString }
105 | }
106 | }
107 |
108 | test("run in specified environment") {
109 | new Env(Map("newvar" -> "a")) {
110 | expectResult("a") { Shell("echo $newvar").toString }
111 | }
112 | }
113 |
114 | test("combine both env and pwd") {
115 | new Env(Map("newvar" -> "a")) {
116 | expectResult("a") { Shell("echo $newvar").toString }
117 | cd("/") {
118 | expectResult("a") { Shell("echo $newvar").toString }
119 | expectResult("/") { Command("pwd").toString }
120 | }
121 |
122 | // MacOS redirects /tmp to /private/tmp as canonical path
123 | val tmpPath = new java.io.File(System.getProperty("java.io.tmpdir")).getCanonicalPath
124 | val tmpdir = tmpPath.toString
125 | cd(tmpdir) {
126 | expectResult("a") { Shell("echo $newvar").toString }
127 | expectResult(tmpdir) { Command("pwd").toString }
128 | env(Map("newvar2" -> "b")) {
129 | expectResult(tmpdir) { Command("pwd").toString }
130 | cd("/") {
131 | expectResult("a") { Shell("echo $newvar").toString }
132 | expectResult("b") { Shell("echo $newvar2").toString }
133 | expectResult("/") { Command("pwd").toString }
134 | }
135 | }
136 | }
137 | }
138 | }
139 |
140 | test("interactive IO") {
141 | val proc = Command("grep", "a")
142 | proc interact { io =>
143 | import scala.concurrent.ExecutionContext.Implicits.global
144 | import at.loveoneanother.schale.actorSystem
145 | implicit val timeout = Timeout(2 seconds)
146 |
147 | io ! "a"
148 | io ! ProcStdinFlush
149 | io ! ProcStdinClose
150 | val future = io ? ProcStdoutReadLine
151 | future onComplete {
152 | case Success(line) => {
153 | expectResult("a") { line }
154 | expectResult(0) { proc.waitFor() }
155 | }
156 | case Failure(e) =>
157 | ProcTest.this.fail("cannot read proc output")
158 | }
159 | Await.result(future, 4 seconds)
160 | }
161 | }
162 |
163 | test("interactive IO (read character from stderr)") {
164 | val proc = Shell("grep a 1>&2")
165 | proc interact { io =>
166 | import scala.concurrent.ExecutionContext.Implicits.global
167 | import at.loveoneanother.schale.actorSystem
168 | implicit val timeout = Timeout(2 seconds)
169 |
170 | io ! "a"
171 | io ! ProcStdinFlush
172 | io ! ProcStdinClose
173 | val future = io ? ProcStderrReadChar
174 | future onComplete {
175 | case Success(char) => {
176 | expectResult('a') { char }
177 | expectResult(0) { proc.waitFor() }
178 | }
179 | case Failure(e) =>
180 | ProcTest.this.fail("cannot read proc output")
181 | }
182 | Await.result(future, 4 seconds)
183 | }
184 | }
185 |
186 | test("IO redirect to file using shell") {
187 | new java.io.File("/tmp/schale_test").delete()
188 | Shell("echo a | grep a > /tmp/schale_test").waitFor()
189 | expectResult(true) { new java.io.File("/tmp/schale_test").exists() }
190 | }
191 | }
192 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | Schale
2 | ------
3 |
4 | Schale is a subprocess interface for Scala. Make all your system programs easily available to Scala, call those programs and interact with their input and output!
5 |
6 | ## Why use Schale?
7 |
8 | Although Scala standard library `scala.sys.process` helps external process execution, however it does not:
9 |
10 | - Manage interactive input/output
11 | - Manage process environment (variables and working directory)
12 |
13 | Schale addresses both of the downsides:
14 |
15 | - It supports both asynchronous and synchronous interactive IO by exposing an Akka actor, eliminating the necessity of manual stream management.
16 | - Process environment can be very easily managed and prepared in a hierarchical fashion, reduce code redundancy and improve readability.
17 |
18 | ## Usage
19 |
20 | ### A gentle start
21 |
22 | import at.loveoneanother.schale._
23 | println(Command("ls", "-l", "/"))
24 | println(Shell("ls -l /"))
25 |
26 | `Command` runs any process, `Shell` runs system shell interpreter. The function return values have exactly the same Schale API.
27 |
28 | This is the only use case in which `.waitFor()` call is unnecessary.
29 |
30 | ### Run in background
31 |
32 | val proc = Shell("sleep 100")
33 | proc.bg()
34 | val exitStatus = proc.waitFor()
35 |
36 | Both simple and interactive IO may be used on background process.
37 |
38 | Process `.destroy()` can be used any time to destory a process before its completion, even during simple or interactive IO.
39 |
40 | ### Simple IO
41 |
42 | val grepper = Shell("grep Scala")
43 | grepper.input("Java API\n", "Scala API\n", "Scala Doc\n")
44 | for (line <- grepper.stdout) { println(line) }
45 | for (line <- grepper.stderr) { println(line) }
46 | grepper.waitFor()
47 |
48 | `.input`, `.stdout` and `.stderr` may only be used once!
49 |
50 | ### Advanced (interactive) IO
51 |
52 | implicit val readTimeout = Timeout(2 seconds)
53 | Shell("grep Scala") interact { io =>
54 | // Stdin: write chars and strings
55 | io ! "Java API"; io ! '\n'
56 | io ! "Scala API\n"
57 | io ! ProcStdinClose // EOF
58 |
59 | // Stdout: read chars and strings
60 | println(Await.result(io ? ProcStdoutReadChar, 2 seconds).asInstanceOf[Int].toChar)
61 | println(Await.result(io ? ProcStdoutReadLine, 2 seconds).asInstanceOf[String])
62 | } waitFor
63 |
64 | `interact` may be used any number of times. For reading from error output, there are `ProcStderrReadChar` and `ProcStderrReadLine`.
65 |
66 | ### Process environment
67 |
68 | // Do not import implicit default environment
69 | import at.loveoneanother.schale.{ Env, Command, Shell }
70 | new Env() {
71 | cd("/") {
72 | println(Command("pwd")) // root
73 | env(Map("mylang" -> "Scala")) { println(Shell("echo $mylang")) } // Scala
74 | }
75 | cd("/tmp") {
76 | println(Command("pwd")) // tmp
77 | }
78 | env(Map("hobby" -> "gliding")) {
79 | // Override value
80 | env(Map("hobby" -> "programming")) { println(Shell("echo $hobby")) } // programming
81 | }
82 | }
83 |
84 | Start from a `new Env()` object, use `cd()` to change directory and `env()` to add/override variables, __stack__ those calls to easily manage hierarchical process environment!
85 |
86 | ## Installation
87 | `schale` currently uses [sbt-assembly](https://github.com/sbt/sbt-assembly)
88 | to produce a fat jar.
89 | First, verify `build.sbt` contains the same libraries as
90 | the codebase to integrate with.
91 | Next, run `sbt assembly` to create `target/scala-X.XX/schale.jar`.
92 |
93 | ## Some notes
94 |
95 | __Is `Shell()` cross platform?__
96 |
97 | `Comamnd()` is cross platform, however `Shell("script")` assumes \*nix OS platform and the availability of `/bin/sh`. Alternative call `Shell("script", "interpreter path")` uses the specified interpreter, but still cannot be guaranteed to run cross-platform.
98 |
99 | __Can I `cd()` to relative paths?__
100 |
101 | In a process environment, `cd()` call only accepts absolute path, this due to JVM's inability to determine whether a path is relative or absolute. You may find `java.nio.file.Paths` easy to work with `cd()`.
102 |
103 | __Can I use Schale to interact with SSH/SFTP/SCP?__
104 |
105 | JVM cannot interact with TTY and Schale itself is not a terminal emulator, therefore it cannot be used on programs which require advanced terminal features, such as SSH/SFTP/SCP. Sorry!
106 |
107 | ## Version History
108 |
109 |
110 |
111 | | Version |
112 | Branch |
113 | Release Date |
114 | Comment |
115 |
116 |
117 | | Alpha |
118 | alpha |
119 | 2013-08-10 |
120 | First release |
121 |
122 |
123 |
124 | ## Contact and License
125 |
126 | You may want to check out [Issues] section for future plans, and please feel very free to contact [Howard] if you have any feedback / questions. I also have [Twitter] and [blog], please check them out as well.
127 |
128 | The following copyright notice and disclaimers apply to all files in the project repository:
129 |
130 | Copyright (c) 2013, Howard Guo
131 | All rights reserved.
132 |
133 | Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
134 | - Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
135 | - Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
136 |
137 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
138 |
139 |
140 | ## Project Background
141 |
142 | Subprocess management was traditionally carried out by using `Runtime.getRuntime().exec` series of calls. Although JDK introduced `ProcessBuilder` later on, but process building and IO interactivity could still be cumbersome.
143 |
144 | Schale takes advantage of advanced features and syntactic sugar offered by Scala, and brings to you:
145 |
146 | - Easy process creation
147 | - Background process management
148 | - Simplified process input/output interface
149 | - Advanced, interactive, non-blocking process IO
150 | - Hierarchical process environment (variables, working directory) management
151 |
152 | Schale was inspired by Python third-party package "sh".
153 |
154 | [Howard]: mailto:guohouzuo@gmail.com
155 | [Twitter]: https://twitter.com/hzguo
156 | [blog]: http://allstarnix.blogspot.com.au
157 | [Issues]: https://github.com/HouzuoGuo/schale/issues
158 |
--------------------------------------------------------------------------------