A subprocess interface for Scala
BSD-2-CLAUSE License
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!
Although Scala standard library scala.sys.process
helps external process execution, however it does not:
Schale addresses both of the downsides:
import at.loveoneanother.schale._
println(Command("ls", "-l", "/"))
println(Shell("ls -l /"))
Command
runs any process, Shell
runs system shell interpreter. The function return values have exactly the same Schale API.
This is the only use case in which .waitFor()
call is unnecessary.
val proc = Shell("sleep 100")
proc.bg()
val exitStatus = proc.waitFor()
Both simple and interactive IO may be used on background process.
Process .destroy()
can be used any time to destory a process before its completion, even during simple or interactive IO.
val grepper = Shell("grep Scala")
grepper.input("Java API\n", "Scala API\n", "Scala Doc\n")
for (line <- grepper.stdout) { println(line) }
for (line <- grepper.stderr) { println(line) }
grepper.waitFor()
.input
, .stdout
and .stderr
may only be used once!
implicit val readTimeout = Timeout(2 seconds)
Shell("grep Scala") interact { io =>
// Stdin: write chars and strings
io ! "Java API"; io ! '\n'
io ! "Scala API\n"
io ! ProcStdinClose // EOF
// Stdout: read chars and strings
println(Await.result(io ? ProcStdoutReadChar, 2 seconds).asInstanceOf[Int].toChar)
println(Await.result(io ? ProcStdoutReadLine, 2 seconds).asInstanceOf[String])
} waitFor
interact
may be used any number of times. For reading from error output, there are ProcStderrReadChar
and ProcStderrReadLine
.
// Do not import implicit default environment
import at.loveoneanother.schale.{ Env, Command, Shell }
new Env() {
cd("/") {
println(Command("pwd")) // root
env(Map("mylang" -> "Scala")) { println(Shell("echo $mylang")) } // Scala
}
cd("/tmp") {
println(Command("pwd")) // tmp
}
env(Map("hobby" -> "gliding")) {
// Override value
env(Map("hobby" -> "programming")) { println(Shell("echo $hobby")) } // programming
}
}
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!
schale
currently uses sbt-assembly
to produce a fat jar.
First, verify build.sbt
contains the same libraries as
the codebase to integrate with.
Next, run sbt assembly
to create target/scala-X.XX/schale.jar
.
Is Shell()
cross platform?
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.
Can I cd()
to relative paths?
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()
.
Can I use Schale to interact with SSH/SFTP/SCP?
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!
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.
The following copyright notice and disclaimers apply to all files in the project repository:
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.
Schale takes advantage of advanced features and syntactic sugar offered by Scala, and brings to you:
Schale was inspired by Python third-party package "sh".