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
17 changes: 17 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ Taken all these features together, `kscript` provides an easy-to-use, very flexi
- [Treat yourself a REPL with `--interactive`](#treat-yourself-a-repl-with---interactive)
- [Boostrap IDEA from a `kscript`let](#boostrap-idea-from-a-kscriptlet)
- [Deploy scripts as standalone binaries](#deploy-scripts-as-standalone-binaries)
- [Embed kscript installer within a script](#embed-kscript-installer-within-a-script)
- [FAQ](#faq)
- [Support](#support)
- [How to contribute?](#how-to-contribute)
Expand Down Expand Up @@ -427,6 +428,22 @@ The created binary will contain a compiled copy of the script, as well as all de

Just `java` is required to run these binaries.


Embed kscript installer within your script
--------------------------------------

To make a script automatically [install kscript](#installation) and its dependencies on first run if necessary, run:

```bash
kscript --add-bootstrap-header some_script.kts
```

Now `some_script.kts` can be shared and run directly on any other machine that has `bash`, without having to go through the [Installation](#installation) steps first.

Note that unlike the [`--package` option](#deploy-scripts-as-standalone-binaries) this doesn't produce a separate file, allowing the distributed script to be read and modified(including with [`kscript --idea`](#boostrap-idea-from-a-kscriptlet)) similar to what you might expect with bash/python/ruby scripts.
On the other hand this doesn't embed dependencies within the script("fat jar"), so internet connection may be required on its first run.


FAQ
---

Expand Down
41 changes: 41 additions & 0 deletions src/main/kotlin/kscript/app/Kscript.kt
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package kscript.app

import kscript.app.ShellUtils.requireInPath
import org.docopt.DocOptWrapper
import org.intellij.lang.annotations.Language
import java.io.BufferedReader
import java.io.File
import java.io.FileInputStream
Expand Down Expand Up @@ -43,6 +44,7 @@ Options:
--idea Open script in temporary Intellij session
-s --silent Suppress status logging to stderr
--package Package script and dependencies into self-dependent binary
--add-bootstrap-header Prepend bash header that installs kscript if necessary


Copyright : 2017 Holger Brandl
Expand All @@ -59,6 +61,18 @@ val KSCRIPT_CACHE_DIR = System.getenv("KSCRIPT_CACHE_DIR")?.let { File(it) }
// use lazy here prevent empty dirs for regular scripts https://github.com/holgerbrandl/kscript/issues/130
val SCRIPT_TEMP_DIR by lazy { createTempDir() }

@Language("sh")
private val BOOTSTRAP_HEADER = """
#!/bin/bash

//usr/bin/env echo '
/**** BOOTSTRAP kscript ****\'>/dev/null
command -v kscript >/dev/null 2>&1 || curl -L "https://git.io/fpVLG" | bash 1>&2
exec kscript $0 "$@"
\*** IMPORTANT: Any code including imports and annotations must come after this line ***/

""".trimIndent().lines()


fun main(args: Array<String>) {
// skip org.docopt for version and help to allow for lazy version-check
Expand Down Expand Up @@ -124,6 +138,33 @@ fun main(args: Array<String>) {
val enableSupportApi = docopt.getBoolean("text")
val (rawScript, includeContext) = prepareScript(scriptResource)

if (docopt.getBoolean("add-bootstrap-header")) {
errorIf(!rawScript.canWrite()) {
"Script file not writable: $rawScript"
}
errorIf(rawScript.parentFile == SCRIPT_TEMP_DIR) {
"Temporary script file detected: $rawScript, created from $scriptResource"
}
val scriptLines = rawScript.readLines().dropWhile {
it.startsWith("#!/") && it != "#!/bin/bash"
}

errorIf(scriptLines.getOrNull(0) == BOOTSTRAP_HEADER[0]
&& scriptLines.any { "command -v kscript >/dev/null 2>&1 || " in it }) {
val lastHeaderLine = BOOTSTRAP_HEADER.findLast { it.isNotBlank() }!!
val preexistingHeader =
scriptLines.dropLastWhile { it != lastHeaderLine }.joinToString("\n")
"Bootstrap header already detected:\n\n$preexistingHeader\n\n" +
"You can remove it to force the re-generation"
}

rawScript.writeText((BOOTSTRAP_HEADER + scriptLines).joinToString("\n"))

if (loggingEnabled) {
info("$rawScript updated")
}
quit(0)
}

// post process script (text-processing mode, custom dsl preamble, resolve includes)
// and finally resolve all includes (see https://github.com/holgerbrandl/kscript/issues/34)
Expand Down
6 changes: 6 additions & 0 deletions test/resources/echo_stdin_args.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
#!/usr/bin/env kscript

if (System.console() == null) {
print(generateSequence(::readLine).joinToString("\n") + " | ")
}
println("script ${args.toList().joinToString(" ")}")
27 changes: 27 additions & 0 deletions test/test_suite.sh
Original file line number Diff line number Diff line change
Expand Up @@ -278,3 +278,30 @@ assert_end misc
assert_raises "./gradlew test"

assert_end junit_tests


########################################################################################################################
## bootstrap header

f=/tmp/echo_stdin_args.kts
cp ${KSCRIPT_HOME}/test/resources/echo_stdin_args.kts $f

# ensure script works as is
assert 'echo stdin | '$f' --foo bar' "stdin | script --foo bar"

# add bootstrap header
assert 'kscript --add-bootstrap-header '$f ''

# ensure adding it again raises an error
assert_raises 'kscript --add-bootstrap-header '$f 1

# ensure scripts works with header, including stdin
assert 'echo stdin | '$f' --foo bar' "stdin | script --foo bar"

# ensure scripts works with header invoked with explicit `kscript`
assert 'echo stdin | kscript '$f' --foo bar' "stdin | script --foo bar"


rm $f

assert_end bootstrap_header