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.