Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 15 additions & 19 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,28 +20,31 @@ srp
Prerequisite: jdk11+

## TOC
<!-- generated with:
markdown-toc --maxdepth 3 README.md|tail -n +4
-->
- [Benefits over / comparison with](#benefits-over--comparison-with)
* [Regular Scala REPL](#regular-scala-repl)
* [Ammonite](#ammonite)
* [scala-cli](#scala-cli)
- [Prerequisite for all of the below: run `sbt stage` or download the latest release](#prerequisite-for-all-of-the-below-run-sbt-stage-or-download-the-latest-release)
- [REPL](#repl)
- [Usage](#usage)
* [run with defaults](#run-with-defaults)
* [execute code at the start with `--runBefore`](#execute-code-at-the-start-with---runbefore)
* [`--predef`: code that is compiled but not executed](#--predef-code-that-is-compiled-but-not-executed)
* [`--predef`: add source files to the classpath](#--predef-add-source-files-to-the-classpath)
* [Operators: Redirect to file, pipe to external command](#operators-redirect-to-file-pipe-to-external-command)
* [Add dependencies with maven coordinates](#add-dependencies-with-maven-coordinates)
* [Importing additional script files interactively](#importing-additional-script-files-interactively)
* [Adding classpath entries](#adding-classpath-entries)
- [REPL](#repl)
* [Rendering of output](#rendering-of-output)
* [Exiting the REPL](#exiting-the-repl)
* [customize prompt, greeting and exit code](#customize-prompt-greeting-and-exit-code)
* [Looking up the current terminal width](#looking-up-the-current-terminal-width)
- [Scripting](#scripting)
* [Simple "Hello world" script](#simple-hello-world-script)
* [Predef file(s) used in script](#predef-files-used-in-script)
* [Importing files / scripts](#importing-files--scripts)
* [Dependencies](#dependencies)
* [Importing other files / scripts with `using file` directive](#importing-other-files--scripts-with-using-file-directive)
* [Dependencies with `using dep` directive](#dependencies-with-using-dep-directive)
* [@main entrypoints](#main-entrypoints)
* [multiple @main entrypoints](#multiple-main-entrypoints)
* [named parameters](#named-parameters)
Expand All @@ -64,6 +67,7 @@ Prerequisite: jdk11+
- [Fineprint](#fineprint)



## Benefits over / comparison with

### Regular Scala REPL
Expand Down Expand Up @@ -93,7 +97,8 @@ scala-cli wraps and invokes the regular Scala REPL (by default; or optionally Am

## Prerequisite for all of the below: run `sbt stage` or download the latest release

## REPL
## Usage
The below features are all demonstrated using the REPL but also work when running scripts.

### run with defaults
```bash
Expand Down Expand Up @@ -265,6 +270,8 @@ println(new Foo().foo)' > myScript.sc
./srp --script myScript.sc
```

## REPL

### Rendering of output

Unlike the stock Scala REPL, srp does _not_ truncate the output by default. You can optionally specify the maxHeight parameter though:
Expand Down Expand Up @@ -327,18 +334,7 @@ echo 'println("Hello!")' > test-simple.sc
cat out.txt # prints 'i was here'
```

### Predef file(s) used in script
```bash
echo 'val foo = "Hello, predef file"' > test-predef-file.sc
echo 'println(foo)' > test-predef.sc
```

```bash
./srp --script test-predef.sc --predef test-predef-file.sc
```
To import multiple scripts, you can specify this parameter multiple times.

### Importing files / scripts
### Importing other files / scripts with `using file` directive
```bash
echo 'val foo = 42' > foo.sc

Expand All @@ -348,7 +344,7 @@ println(foo)' > test.sc
./srp --script test.sc
```

### Dependencies
### Dependencies with `using dep` directive
Dependencies can be added via `//> using dep` syntax (like in scala-cli).

```bash
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,13 +36,11 @@ object NonForkingScriptRunner {
commandArgs ++ parameterArgs
}

if (config.runBefore.nonEmpty)
println(s"[WARNING] ScriptingDriver does not support `runBefore` code, the given ${config.runBefore.size} statements will be ignored")

val verboseEnabled = replpp.verboseEnabled(config)
new ScriptingDriver(
compilerArgs = replpp.compilerArgs(config) :+ "-nowarn",
predefFiles = allPredefFiles(config),
runBeforeSourceLines = config.runBefore,
scriptFile = scriptFile,
scriptArgs = scriptArgs.toArray,
verbose = verboseEnabled
Expand Down
9 changes: 7 additions & 2 deletions core/src/main/scala/replpp/scripting/ScriptingDriver.scala
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,13 @@ import scala.util.{Failure, Try}
* Main difference: we don't (need to) recursively look for main method entry points in the entire classpath,
* because we have a fixed class and method name that ScriptRunner uses when it embeds the script and predef code.
* */
class ScriptingDriver(compilerArgs: Array[String], predefFiles: Seq[Path], scriptFile: Path, scriptArgs: Array[String], verbose: Boolean) {
private val wrappingResult = WrapForMainArgs(Files.readString(scriptFile))
class ScriptingDriver(compilerArgs: Array[String],
predefFiles: Seq[Path],
runBeforeSourceLines: Seq[String],
scriptFile: Path,
scriptArgs: Array[String],
verbose: Boolean) {
private val wrappingResult = WrapForMainArgs(Files.readString(scriptFile), runBeforeSourceLines)
private val wrappedScript = Files.createTempFile("wrapped-script", ".sc")
private val tempFiles = Seq.newBuilder[Path]
private var executed = false
Expand Down
36 changes: 22 additions & 14 deletions core/src/main/scala/replpp/scripting/WrapForMainArgs.scala
Original file line number Diff line number Diff line change
Expand Up @@ -7,21 +7,36 @@ object WrapForMainArgs {
/** linesBeforeWrappedCode: allows us to adjust line numbers in error reporting... */
case class WrappingResult(fullScript: String, linesBeforeWrappedCode: Int)

def apply(scriptCode: String): WrappingResult = {
var linesBeforeWrappedCode = 0
def apply(scriptCode: String, runBeforeAsSourceLines: Seq[String]): WrappingResult = {
val runBeforeCode = runBeforeAsSourceLines.mkString("\n")

val wrapperCodeStart =
s"""import replpp.shaded.mainargs
|import mainargs.main // intentionally shadow any potentially given @main
|
|// ScriptingDriver expects an object with a predefined name and a main entrypoint method
|object ${ScriptingDriver.MainClassName} {
|// runBeforeCode START
|$runBeforeCode
|// runBeforeCode END
|""".stripMargin

var linesBeforeWrappedCode = 0 // to adjust line number reporting

val mainImpl =
if (scriptCode.contains("@main")) {
if (scriptCode.contains("@main"))
scriptCode
} else {
linesBeforeWrappedCode += 1
else {
linesBeforeWrappedCode += 1 // because we added the following line _before_ the wrapped script code
s"""@main def _execMain(): Unit = {
|$scriptCode
|}""".stripMargin
}

linesBeforeWrappedCode += codeBefore.lines().count().toInt
linesBeforeWrappedCode += wrapperCodeStart.lines().count().toInt
linesBeforeWrappedCode += 1 // for the line break after $wrapperCodeStart
val fullScript =
s"""$codeBefore
s"""$wrapperCodeStart
|$mainImpl
|
| def ${ScriptingDriver.MainMethodName}(args: Array[String]): Unit = {
Expand All @@ -33,11 +48,4 @@ object WrapForMainArgs {
WrappingResult(fullScript, linesBeforeWrappedCode)
}

private val codeBefore =
s"""import replpp.shaded.mainargs
|import mainargs.main // intentionally shadow any potentially given @main
|
|// ScriptingDriver expects an object with a predefined name and a main entrypoint method
|object ${ScriptingDriver.MainClassName} {""".stripMargin

}
55 changes: 52 additions & 3 deletions core/src/test/scala/replpp/scripting/ScriptRunnerTests.scala
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,34 @@ class ScriptRunnerTests extends AnyWordSpec with Matchers {
}.get shouldBe "iwashere-predefFile"
}

"runBeforeCode" in {
execTest { testOutputPath =>
TestSetup(
s"""import java.nio.file.*
|val string = MaxValue + ";" + fromRunBeforeCode
|Files.writeString(Path.of("$testOutputPath"), string)""".stripMargin,
adaptConfig = _.copy(runBefore = List(
"import Byte.MaxValue",
"val fromRunBeforeCode = \"iwashere-runBeforeCode\""
))
)
}.get shouldBe "127;iwashere-runBeforeCode"
}

"predefFiles and runBeforeCode" in {
execTest { testOutputPath =>
val predefFile = os.temp("""val bar = "iwashere-predefFile"""").toNIO
TestSetup(
s"""import java.nio.file.*
|val string = MinValue + ";" + bar
|Files.writeString(Path.of("$testOutputPath"), string)""".stripMargin,
adaptConfig = _.copy(
predefFiles = List(predefFile),
runBefore = List("import Byte.MinValue"))
)
}.get shouldBe "-128;iwashere-predefFile"
}

"additional dependencies" in {
execTest { testOutputPath =>
TestSetup(
Expand Down Expand Up @@ -204,7 +232,7 @@ class ScriptRunnerTests extends AnyWordSpec with Matchers {
// TODO: this isn't the case yet: note: if we intercepted the stdout/stderr, we could/should observe that the error is reported in line 1
}

"error is in imported file" in {
"error in imported file" in {
ensureErrors { () =>
val additionalScript = os.temp()
os.write.over(additionalScript,
Expand All @@ -220,12 +248,12 @@ class ScriptRunnerTests extends AnyWordSpec with Matchers {
// note: if we intercepted the stdout/stderr, we could/should observe that the error is reported in line 2
}

"error is in predef file" in {
"error in predef file" in {
ensureErrors { () =>
val predefFile = os.temp()
os.write.over(predefFile,
s"""val foo = 42
|val thisWillNotCompile: Int = "because we need an Int"
|val thisWillNotCompile: Int = "because this a String and not an Int"
|""".stripMargin)
TestSetup(
"val bar = 34".stripMargin,
Expand All @@ -235,6 +263,27 @@ class ScriptRunnerTests extends AnyWordSpec with Matchers {
// note: if we intercepted the stdout/stderr, we could/should observe that the error is reported in line 2
}

"error in runBeforeCode" in {
ensureErrors { () =>
TestSetup(
"val bar = 34".stripMargin,
adaptConfig = _.copy(runBefore = Seq("val thisWillNotCompile: Int = \"because this a String and not an Int\""))
)
}
}

"error in script with given runBeforeCode" in {
ensureErrors { () =>
TestSetup(
s"""val foo = 42
|val thisWillNotCompile: Int = "because this a String and not an Int"
|""".stripMargin,
adaptConfig = _.copy(runBefore = Seq("val bar = 34"))
)
// note: if we intercepted the stdout/stderr, we could/should observe that the error is reported in line 2
}
}

}
}

Expand Down
Loading