User interaction
Progress tracking
Shell operations yield progress bars by default when they take too long. Progress operations are represented as ProgressReport objects and
passed to a ProgressReport.Tracker. The global tracker is accessed via progressTracker.
There are utilities to help you work with and generate these events.
Tracking progress through a list
| for (file in ls("/").withProgress(progressTracker)) {
echo(file)
Thread.sleep(1000)
}
|

Customize the output
| for (file in ls("/").withProgress(progressTracker, ProgressReport.create("Iterating over files"))) {
echo(file)
Thread.sleep(1000)
}
|
Send progress events manually
| // Expect 500 units of work
var report = ProgressReport.create("My operation", 500).immutableReport()
while (!report.complete) {
progressTracker.accept(report)
Thread.sleep(100)
report = report.withIncremented(1)
}
|
Spinner
For indefinite tasks where no progress can be measured:
| progressTracker.indeterminate("My message") {
Thread.sleep(5000)
}
|
Process a file with progress tracking
1
2
3
4
5
6
7
8
9
10
11
12
13
14 | // Write a million lines of text.
write("bigfile.txt", buildString { for (i in 1..1_000_000) appendLine("Line $i") })
// Process lines whilst tracking progress.
path("bigfile.txt").inputStreamWithProgress(progressTracker).bufferedReader().use { reader ->
reader.lineSequence().forEachIndexed { lineNumber, line ->
if ("Line 12345" in line)
echo(line)
// Slow down a bit so the progress tracker is visible.
if (lineNumber.mod(1000) == 0)
Thread.sleep(10)
}
}
|

Prompting
| val answer: String = prompt("Password?", hideInput = true)
|
There are also promptPrefix, promptSuffix, default parameters, and validateResponse which lets you quickly check if the user input
is OK, repeating the question if not.
Warning the user
Automatically deduplicated warnings:
User errors
throw UserError("A message") to end the script in a way that indicates a user error. The exit code is set appropriately. Other kinds of
exceptions will print a message indicating that the script has crashed.
Use check to assert internal invariants and verify to assert things that are potentially user errors:
| @Option(names = ["--foo-path"])
var foo: Path? = null
// If --foo-path is specified, verify it's a .txt file and then assert (crash) if it's empty.
val textLines: List<String>? = foo?.let { foo ->
verify(foo.name.endsWith(".txt"), "If --foo-path is specified it must be a .txt file")
readLines(foo).also { check(it.isNotEmpty()) { "File $foo should not be empty" } }
}
|
Path assertions
| val dir: Path = path("subdir").verifyIsExistingDirectory()
val file: Path = path("subdir/file.txt").verifyIsExistingFile()
|
If a path assertion fails the user is shown the contents of the nearest directory and a spelling error suggestion.
