Skip to content

Commit 3d016cd

Browse files
authored
runBefore in scripts (#177)
1 parent dac1470 commit 3d016cd

File tree

5 files changed

+97
-41
lines changed

5 files changed

+97
-41
lines changed

README.md

Lines changed: 15 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -20,28 +20,31 @@ srp
2020
Prerequisite: jdk11+
2121

2222
## TOC
23+
<!-- generated with:
24+
markdown-toc --maxdepth 3 README.md|tail -n +4
25+
-->
2326
- [Benefits over / comparison with](#benefits-over--comparison-with)
2427
* [Regular Scala REPL](#regular-scala-repl)
2528
* [Ammonite](#ammonite)
2629
* [scala-cli](#scala-cli)
2730
- [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)
28-
- [REPL](#repl)
31+
- [Usage](#usage)
2932
* [run with defaults](#run-with-defaults)
3033
* [execute code at the start with `--runBefore`](#execute-code-at-the-start-with---runbefore)
31-
* [`--predef`: code that is compiled but not executed](#--predef-code-that-is-compiled-but-not-executed)
34+
* [`--predef`: add source files to the classpath](#--predef-add-source-files-to-the-classpath)
3235
* [Operators: Redirect to file, pipe to external command](#operators-redirect-to-file-pipe-to-external-command)
3336
* [Add dependencies with maven coordinates](#add-dependencies-with-maven-coordinates)
3437
* [Importing additional script files interactively](#importing-additional-script-files-interactively)
3538
* [Adding classpath entries](#adding-classpath-entries)
39+
- [REPL](#repl)
3640
* [Rendering of output](#rendering-of-output)
3741
* [Exiting the REPL](#exiting-the-repl)
3842
* [customize prompt, greeting and exit code](#customize-prompt-greeting-and-exit-code)
3943
* [Looking up the current terminal width](#looking-up-the-current-terminal-width)
4044
- [Scripting](#scripting)
4145
* [Simple "Hello world" script](#simple-hello-world-script)
42-
* [Predef file(s) used in script](#predef-files-used-in-script)
43-
* [Importing files / scripts](#importing-files--scripts)
44-
* [Dependencies](#dependencies)
46+
* [Importing other files / scripts with `using file` directive](#importing-other-files--scripts-with-using-file-directive)
47+
* [Dependencies with `using dep` directive](#dependencies-with-using-dep-directive)
4548
* [@main entrypoints](#main-entrypoints)
4649
* [multiple @main entrypoints](#multiple-main-entrypoints)
4750
* [named parameters](#named-parameters)
@@ -64,6 +67,7 @@ Prerequisite: jdk11+
6467
- [Fineprint](#fineprint)
6568

6669

70+
6771
## Benefits over / comparison with
6872

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

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

96-
## REPL
100+
## Usage
101+
The below features are all demonstrated using the REPL but also work when running scripts.
97102

98103
### run with defaults
99104
```bash
@@ -265,6 +270,8 @@ println(new Foo().foo)' > myScript.sc
265270
./srp --script myScript.sc
266271
```
267272

273+
## REPL
274+
268275
### Rendering of output
269276

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

330-
### Predef file(s) used in script
331-
```bash
332-
echo 'val foo = "Hello, predef file"' > test-predef-file.sc
333-
echo 'println(foo)' > test-predef.sc
334-
```
335-
336-
```bash
337-
./srp --script test-predef.sc --predef test-predef-file.sc
338-
```
339-
To import multiple scripts, you can specify this parameter multiple times.
340-
341-
### Importing files / scripts
337+
### Importing other files / scripts with `using file` directive
342338
```bash
343339
echo 'val foo = 42' > foo.sc
344340

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

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

354350
```bash

core/src/main/scala/replpp/scripting/NonForkingScriptRunner.scala

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -36,13 +36,11 @@ object NonForkingScriptRunner {
3636
commandArgs ++ parameterArgs
3737
}
3838

39-
if (config.runBefore.nonEmpty)
40-
println(s"[WARNING] ScriptingDriver does not support `runBefore` code, the given ${config.runBefore.size} statements will be ignored")
41-
4239
val verboseEnabled = replpp.verboseEnabled(config)
4340
new ScriptingDriver(
4441
compilerArgs = replpp.compilerArgs(config) :+ "-nowarn",
4542
predefFiles = allPredefFiles(config),
43+
runBeforeSourceLines = config.runBefore,
4644
scriptFile = scriptFile,
4745
scriptArgs = scriptArgs.toArray,
4846
verbose = verboseEnabled

core/src/main/scala/replpp/scripting/ScriptingDriver.scala

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,13 @@ import scala.util.{Failure, Try}
2020
* Main difference: we don't (need to) recursively look for main method entry points in the entire classpath,
2121
* because we have a fixed class and method name that ScriptRunner uses when it embeds the script and predef code.
2222
* */
23-
class ScriptingDriver(compilerArgs: Array[String], predefFiles: Seq[Path], scriptFile: Path, scriptArgs: Array[String], verbose: Boolean) {
24-
private val wrappingResult = WrapForMainArgs(Files.readString(scriptFile))
23+
class ScriptingDriver(compilerArgs: Array[String],
24+
predefFiles: Seq[Path],
25+
runBeforeSourceLines: Seq[String],
26+
scriptFile: Path,
27+
scriptArgs: Array[String],
28+
verbose: Boolean) {
29+
private val wrappingResult = WrapForMainArgs(Files.readString(scriptFile), runBeforeSourceLines)
2530
private val wrappedScript = Files.createTempFile("wrapped-script", ".sc")
2631
private val tempFiles = Seq.newBuilder[Path]
2732
private var executed = false

core/src/main/scala/replpp/scripting/WrapForMainArgs.scala

Lines changed: 22 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -7,21 +7,36 @@ object WrapForMainArgs {
77
/** linesBeforeWrappedCode: allows us to adjust line numbers in error reporting... */
88
case class WrappingResult(fullScript: String, linesBeforeWrappedCode: Int)
99

10-
def apply(scriptCode: String): WrappingResult = {
11-
var linesBeforeWrappedCode = 0
10+
def apply(scriptCode: String, runBeforeAsSourceLines: Seq[String]): WrappingResult = {
11+
val runBeforeCode = runBeforeAsSourceLines.mkString("\n")
12+
13+
val wrapperCodeStart =
14+
s"""import replpp.shaded.mainargs
15+
|import mainargs.main // intentionally shadow any potentially given @main
16+
|
17+
|// ScriptingDriver expects an object with a predefined name and a main entrypoint method
18+
|object ${ScriptingDriver.MainClassName} {
19+
|// runBeforeCode START
20+
|$runBeforeCode
21+
|// runBeforeCode END
22+
|""".stripMargin
23+
24+
var linesBeforeWrappedCode = 0 // to adjust line number reporting
25+
1226
val mainImpl =
13-
if (scriptCode.contains("@main")) {
27+
if (scriptCode.contains("@main"))
1428
scriptCode
15-
} else {
16-
linesBeforeWrappedCode += 1
29+
else {
30+
linesBeforeWrappedCode += 1 // because we added the following line _before_ the wrapped script code
1731
s"""@main def _execMain(): Unit = {
1832
|$scriptCode
1933
|}""".stripMargin
2034
}
2135

22-
linesBeforeWrappedCode += codeBefore.lines().count().toInt
36+
linesBeforeWrappedCode += wrapperCodeStart.lines().count().toInt
37+
linesBeforeWrappedCode += 1 // for the line break after $wrapperCodeStart
2338
val fullScript =
24-
s"""$codeBefore
39+
s"""$wrapperCodeStart
2540
|$mainImpl
2641
|
2742
| def ${ScriptingDriver.MainMethodName}(args: Array[String]): Unit = {
@@ -33,11 +48,4 @@ object WrapForMainArgs {
3348
WrappingResult(fullScript, linesBeforeWrappedCode)
3449
}
3550

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

core/src/test/scala/replpp/scripting/ScriptRunnerTests.scala

Lines changed: 52 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,34 @@ class ScriptRunnerTests extends AnyWordSpec with Matchers {
6666
}.get shouldBe "iwashere-predefFile"
6767
}
6868

69+
"runBeforeCode" in {
70+
execTest { testOutputPath =>
71+
TestSetup(
72+
s"""import java.nio.file.*
73+
|val string = MaxValue + ";" + fromRunBeforeCode
74+
|Files.writeString(Path.of("$testOutputPath"), string)""".stripMargin,
75+
adaptConfig = _.copy(runBefore = List(
76+
"import Byte.MaxValue",
77+
"val fromRunBeforeCode = \"iwashere-runBeforeCode\""
78+
))
79+
)
80+
}.get shouldBe "127;iwashere-runBeforeCode"
81+
}
82+
83+
"predefFiles and runBeforeCode" in {
84+
execTest { testOutputPath =>
85+
val predefFile = os.temp("""val bar = "iwashere-predefFile"""").toNIO
86+
TestSetup(
87+
s"""import java.nio.file.*
88+
|val string = MinValue + ";" + bar
89+
|Files.writeString(Path.of("$testOutputPath"), string)""".stripMargin,
90+
adaptConfig = _.copy(
91+
predefFiles = List(predefFile),
92+
runBefore = List("import Byte.MinValue"))
93+
)
94+
}.get shouldBe "-128;iwashere-predefFile"
95+
}
96+
6997
"additional dependencies" in {
7098
execTest { testOutputPath =>
7199
TestSetup(
@@ -204,7 +232,7 @@ class ScriptRunnerTests extends AnyWordSpec with Matchers {
204232
// 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
205233
}
206234

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

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

266+
"error in runBeforeCode" in {
267+
ensureErrors { () =>
268+
TestSetup(
269+
"val bar = 34".stripMargin,
270+
adaptConfig = _.copy(runBefore = Seq("val thisWillNotCompile: Int = \"because this a String and not an Int\""))
271+
)
272+
}
273+
}
274+
275+
"error in script with given runBeforeCode" in {
276+
ensureErrors { () =>
277+
TestSetup(
278+
s"""val foo = 42
279+
|val thisWillNotCompile: Int = "because this a String and not an Int"
280+
|""".stripMargin,
281+
adaptConfig = _.copy(runBefore = Seq("val bar = 34"))
282+
)
283+
// note: if we intercepted the stdout/stderr, we could/should observe that the error is reported in line 2
284+
}
285+
}
286+
238287
}
239288
}
240289

0 commit comments

Comments
 (0)