Skip to content

Commit

Permalink
Shebang compatibility
Browse files Browse the repository at this point in the history
When running shebang files, the system constructs a command from by
concatenating shebang line, input file name and program
arguments. This means we need a special parsing mode that would accept
only a single input, and treat everything that comes after the input
as program arguments
  • Loading branch information
tpasternak committed Nov 24, 2021
1 parent 04aad10 commit 23fb2ea
Show file tree
Hide file tree
Showing 9 changed files with 90 additions and 7 deletions.
1 change: 1 addition & 0 deletions modules/cli/src/main/scala/scala/cli/ScalaCli.scala
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ object ScalaCli extends CommandsEntryPoint {
Package,
Run,
SetupIde,
Shebang,
Test,
Update,
Version
Expand Down
2 changes: 1 addition & 1 deletion modules/cli/src/main/scala/scala/cli/commands/Run.scala
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ object Run extends ScalaCommand[RunOptions] {
inputs.workspace,
inputs.projectName,
build,
args.unparsed,
options.shared.programArgs(args),
logger,
allowExecve = allowTerminate,
exitOnError = allowTerminate,
Expand Down
13 changes: 11 additions & 2 deletions modules/cli/src/main/scala/scala/cli/commands/SharedOptions.scala
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,7 @@ final case class SharedOptions(
@Hidden
defaultForbiddenDirectories: Boolean = true,
@Hidden
forbid: List[String] = Nil
forbid: List[String] = Nil,
) {
// format: on

Expand Down Expand Up @@ -192,9 +192,18 @@ final case class SharedOptions(

lazy val coursierCache = coursier.coursierCache(logging.logger.coursierLogger)

def programArgs(args: RemainingArgs) = args.unparsed

def inputArgs(args: RemainingArgs) = args.remaining

def inputsOrExit(
args: RemainingArgs,
defaultInputs: () => Option[Inputs] = () => Inputs.default()
): Inputs = inputsOrExit0(inputArgs(args), defaultInputs)

def inputsOrExit0(
args: Seq[String],
defaultInputs: () => Option[Inputs] = () => Inputs.default()
): Inputs = {
val download: String => Either[String, Array[Byte]] = { url =>
val artifact = Artifact(url).withChanging(true)
Expand All @@ -209,7 +218,7 @@ final case class SharedOptions(
.map(os.Path(_, Os.pwd))
.map(Inputs.ResourceDirectory(_))
val inputs = Inputs(
args.remaining,
args,
Os.pwd,
directories.directories,
defaultInputs = defaultInputs,
Expand Down
24 changes: 24 additions & 0 deletions modules/cli/src/main/scala/scala/cli/commands/Shebang.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package scala.cli.commands

import caseapp._

final case class ShebangOptions() {}

object ShebangOptions {
implicit lazy val parser: Parser[ShebangOptions] = Parser.derive
implicit lazy val help: Help[ShebangOptions] = Help.derive
}

object Shebang extends ScalaCommand[ShebangOptions] {
override lazy val finalHelp: Help[_] = Run.finalHelp

def run(options: ShebangOptions, args0: RemainingArgs): Unit = {
val args = args0.remaining
val firstInputPosition = for {
(_, options) <- Run.parser.detailedParse(args.drop(1), true).toOption
} yield options.remaining.size
val (p1, p2) = args.splitAt(args.size - firstInputPosition.getOrElse(0) + 2)
val trueArgs = p1 ++ List("--") ++ p2
Run.main(trueArgs.toArray)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -1049,7 +1049,7 @@ abstract class RunTestDefinitions(val scalaVersionOpt: Option[String])
val inputs = TestInputs(
Seq(
os.rel / "MyScript.scala" ->
"""#!/usr/bin/env scala-cli
"""#!/usr/bin/env -S scala-cli shebang
|object MyScript {
| def main(args: Array[String]): Unit =
| println("Hello" + args.map(" " + _).mkString)
Expand Down Expand Up @@ -1140,5 +1140,19 @@ abstract class RunTestDefinitions(val scalaVersionOpt: Option[String])
expect(p.out.text().trim == "16")
}
}

if (!Properties.isWin)
test("CLI args passed to shebang script") {
val inputs = TestInputs(
Seq(
os.rel / "f.sc" -> s"""|#!/usr/bin/env -S ${TestUtil.cli.mkString(" ")} shebang -S 2.13
|using scala $actualScalaVersion
|println(args.toList)""".stripMargin
)
)
inputs.fromRoot { root =>
os.perms.set(root / "f.sc", os.PermSet.fromString("rwx------"))
val p = os.proc("./f.sc", "1", "2", "3", "-v").call(cwd = root)
expect(p.out.text().trim == "List(1, 2, 3, -v)")
}
}
}
4 changes: 2 additions & 2 deletions website/docs/cookbooks/scala-scripts.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ Hello from Scala .*, Java .*
Alternatively, you can add a "shebang" header to your script, make it executable, and execute it directly with `scala-cli`. For example, given this script with a header that invokes `scala-cli`:

```scala title=HelloScriptSheBang.sc
#!/usr/bin/env scala-cli
#!/usr/bin/env -S scala-cli shebang

val sv = scala.util.Properties.versionNumberString

Expand All @@ -59,7 +59,7 @@ Hello from Scala .*, Java .*
You can also pass command line arguments to Scala scripts:

```scala title=ScriptArguments.sc
#!/usr/bin/env scala-cli
#!/usr/bin/env -S scala-cli shebang
println(args(1))
```

Expand Down
16 changes: 16 additions & 0 deletions website/docs/reference/cli-options.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ Aliases: `-q`

Available in commands:
- [`run`](./commands.md#run)
- [`shebang`](./commands.md#shebang)


<!-- Automatically generated, DO NOT EDIT MANUALLY -->
Expand Down Expand Up @@ -94,6 +95,7 @@ Available in commands:
- [`console` / `repl`](./commands.md#console)
- [`run`](./commands.md#run)
- [`setup-ide`](./commands.md#setup-ide)
- [`shebang`](./commands.md#shebang)
- [`test`](./commands.md#test)


Expand Down Expand Up @@ -161,6 +163,7 @@ Available in commands:
- [`package`](./commands.md#package)
- [`console` / `repl`](./commands.md#console)
- [`run`](./commands.md#run)
- [`shebang`](./commands.md#shebang)
- [`test`](./commands.md#test)


Expand All @@ -183,6 +186,7 @@ Available in commands:
- [`console` / `repl`](./commands.md#console)
- [`run`](./commands.md#run)
- [`setup-ide`](./commands.md#setup-ide)
- [`shebang`](./commands.md#shebang)
- [`test`](./commands.md#test)


Expand Down Expand Up @@ -214,6 +218,7 @@ Available in commands:
- [`console` / `repl`](./commands.md#console)
- [`run`](./commands.md#run)
- [`setup-ide`](./commands.md#setup-ide)
- [`shebang`](./commands.md#shebang)
- [`test`](./commands.md#test)


Expand Down Expand Up @@ -254,6 +259,7 @@ Available in commands:
- [`console` / `repl`](./commands.md#console)
- [`run`](./commands.md#run)
- [`setup-ide`](./commands.md#setup-ide)
- [`shebang`](./commands.md#shebang)
- [`test`](./commands.md#test)


Expand Down Expand Up @@ -331,6 +337,7 @@ Available in commands:
- [`console` / `repl`](./commands.md#console)
- [`run`](./commands.md#run)
- [`setup-ide`](./commands.md#setup-ide)
- [`shebang`](./commands.md#shebang)
- [`test`](./commands.md#test)
- [`update`](./commands.md#update)
- [`version`](./commands.md#version)
Expand Down Expand Up @@ -423,6 +430,7 @@ Binary directory
Available in commands:
- [`console` / `repl`](./commands.md#console)
- [`run`](./commands.md#run)
- [`shebang`](./commands.md#shebang)
- [`test`](./commands.md#test)


Expand Down Expand Up @@ -451,6 +459,7 @@ Available in commands:
- [`console` / `repl`](./commands.md#console)
- [`run`](./commands.md#run)
- [`setup-ide`](./commands.md#setup-ide)
- [`shebang`](./commands.md#shebang)
- [`test`](./commands.md#test)


Expand Down Expand Up @@ -494,6 +503,7 @@ Available in commands:
- [`console` / `repl`](./commands.md#console)
- [`run`](./commands.md#run)
- [`setup-ide`](./commands.md#setup-ide)
- [`shebang`](./commands.md#shebang)
- [`test`](./commands.md#test)


Expand Down Expand Up @@ -521,6 +531,7 @@ Available in commands:
- [`export`](./commands.md#export)
- [`package`](./commands.md#package)
- [`run`](./commands.md#run)
- [`shebang`](./commands.md#shebang)


<!-- Automatically generated, DO NOT EDIT MANUALLY -->
Expand Down Expand Up @@ -765,6 +776,7 @@ Available in commands:
- [`console` / `repl`](./commands.md#console)
- [`run`](./commands.md#run)
- [`setup-ide`](./commands.md#setup-ide)
- [`shebang`](./commands.md#shebang)
- [`test`](./commands.md#test)


Expand Down Expand Up @@ -808,6 +820,7 @@ Available in commands:
- [`console` / `repl`](./commands.md#console)
- [`run`](./commands.md#run)
- [`setup-ide`](./commands.md#setup-ide)
- [`shebang`](./commands.md#shebang)
- [`test`](./commands.md#test)


Expand Down Expand Up @@ -865,6 +878,7 @@ Available in commands:
- [`console` / `repl`](./commands.md#console)
- [`run`](./commands.md#run)
- [`setup-ide`](./commands.md#setup-ide)
- [`shebang`](./commands.md#shebang)
- [`test`](./commands.md#test)


Expand Down Expand Up @@ -898,6 +912,7 @@ Available in commands:
- [`console` / `repl`](./commands.md#console)
- [`run`](./commands.md#run)
- [`setup-ide`](./commands.md#setup-ide)
- [`shebang`](./commands.md#shebang)
- [`test`](./commands.md#test)


Expand Down Expand Up @@ -1002,6 +1017,7 @@ Available in commands:
- [`package`](./commands.md#package)
- [`console` / `repl`](./commands.md#console)
- [`run`](./commands.md#run)
- [`shebang`](./commands.md#shebang)
- [`test`](./commands.md#test)


Expand Down
2 changes: 2 additions & 0 deletions website/docs/reference/commands.md
Original file line number Diff line number Diff line change
Expand Up @@ -212,6 +212,8 @@ Accepts options:
- [setup IDE](./cli-options.md#setup-ide-options)
- [shared](./cli-options.md#shared-options)

## `shebang`

## `test`

Compile and test Scala code
Expand Down
17 changes: 17 additions & 0 deletions website/docs/reference/directives.md
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,17 @@ Add Java options
#### Examples
`using javaOpt -Xmx2g -Dsomething=a`

### Main class

Specify default main class

`using main-class `_main class_

`using mainClass `_main class_

#### Examples
`using main-class helloWorld`

### Platform

Set the default platform to Scala.JS or Scala Native
Expand Down Expand Up @@ -106,6 +117,12 @@ Manually add a resource directory to the class path
#### Examples
`using resource "./resources"`

### Scala Native options

Add Scala Native options

directive using usage

### Scala version

Set the default Scala version
Expand Down

0 comments on commit 23fb2ea

Please sign in to comment.