scala / bug

Scala 2 bug reports only. Please, no questions — proper bug reports only.
https://scala-lang.org
232 stars 21 forks source link

DSL for using a String as the input of a process #11668

Open jroper opened 5 years ago

jroper commented 5 years ago

Here's my exact use case, I want to write the Scala version of this shell script:

docker build -t graalvm-native-image - <<DOCKERFILE
FROM oracle/graalvm-ce:19.0.0
WORKDIR /opt/graalvm
RUN gu install native-image
ENTRYPOINT [native-image]
DOCKERFILE

Naively I thought this might work:

val dockerfile = """FROM oracle/graalvm-ce:19.0.0
WORKDIR /opt/graalvm
RUN gu install native-image
ENTRYPOINT [native-image]
"""

("docker build -t graalvm-native-image:19.0.0 -" #< dockerfile).!

But of course it doesn't work because String is executed as a command, not used as the source of input, so the error you get is Cannot run program "FROM". This works:

("docker build -t graalvm-native-image:19.0.0 -" #< 
  new java.io.ByteArrayInputStream(dockerfile.getBytes("utf-8"))).!

But is not very nice from a DSL perspective. There should be a straight forward way to pass a String as the input of a command. Here are some examples of potential APIs that could work:

(Process.echo(dockerfile) #> "docker build -t graalvm-native-image:19.0.0 -").!

("docker build -t graalvm-native-image:19.0.0 -" #<<< dockerfile).!

Similar APIs could be supplied for byte arrays.

SethTisue commented 5 years ago

does https://lihaoyi.github.io/Ammonite/#Ammonite-Shell offer anything here?

scala.sys.process is pretty neglected these days

jroper commented 5 years ago

Probably, but I don't really want a shell. scala.sys.process is used heavily by sbt and its plugins, along with in an ad-hoc manner by sbt users in their build files.

ritschwumm commented 5 years ago

how about sth like this:

implicit final class StringContextExt(peer:StringContext) {
    def stream(args:Any*):InputStream = new java.io.ByteArrayInputStream(peer.s(args:_*).getBytes("UTF-8"))
}
som-snytt commented 5 years ago

@SethTisue You mean PRs about scala.sys.process are neglected. https://github.com/scala/scala/pull/8269 It would be less neglected as the promised module.

The current unDSL (and somehow I thought it would be easier):

scala> import scala.sys.process._
import scala.sys.process._

scala> def text: java.io.OutputStream => Unit = { out => out.write("hello, world".getBytes) ; out.close() }
text: java.io.OutputStream => Unit

scala> "head -10" run BasicIO.standard(text)  // doesn't use ThreadProcess and PipedProcess
hello, worldres0: scala.sys.process.Process = scala.sys.process.ProcessImpl$SimpleProcess@47044f7d

scala> "head -10".#<<("mytext")  // would be nice, #<< is currently for redirect to file
                  ^
       error: value #<< is not a member of String

scala> new java.io.File("foo.out") #<< new java.io.ByteArrayInputStream("hello".getBytes)
res11: scala.sys.process.ProcessBuilder =  ( <input stream> #| /home/amarki/snips/foo.out )

scala> .!
res12: Int = 0

scala> "head -10" ! BasicIO.standard(text)  // should already work, it's defined for ProcessBuilderImpl but not ProcessBuilder
                                    ^
       error: type mismatch;
        found   : scala.sys.process.ProcessIO
        required: scala.sys.process.ProcessLogger

I like echo, but I also like the idea of leveraging "just read the string without more threads".

The current DSL has ! for exit value, !! for stdout.toString. Optionally add < to mean read stdin. Optionally provide a ProcessLogger to process stdout/stderr.

So one proposal would be "add << to mean read from string". It would be slightly awkward with a logger: "head -10" !!<< ("some long input", ProcessLogger(err => println(err))).

I guess it kind of works:

scala> import scala.sys.process._
import scala.sys.process._

scala> "head -10" !<< "text"
textres0: Int = 0

scala> "head -10" !!<< "text"
res1: String =
"text
"

scala> "head -10" !!<< ("text", ProcessLogger(err => println(s"darn, $err")))
res2: String =
"text
"

scala> "head -10" !!<< """Now is the time
     | for all do-gooders
     | to do Some(good)."""
res3: String =
"Now is the time
for all do-gooders
to do Some(good).
"

scala> "head -10" #> new java.io.File("junk.out") !<< "hello, world"
res4: Int = 0
som-snytt commented 5 years ago

Or same as echo, first-class process:

scala> "head -2" #<< "hello"
res0: scala.sys.process.ProcessBuilder =  ( <here hello> #| [head, -2] )

scala> .!!
res1: String =
"hello
"

scala> "head -2" #<< """goodbye
     | cruel
     | world"""
res2: scala.sys.process.ProcessBuilder =  ( <here goodb...> #| [head, -2] )

scala> .!!
res3: String =
"goodbye
cruel
"

To go full-on Mark Harrah.