Terminal Utilities
Modules containing utilities for enhancing terminal programs. Use of this
library only makes sense for a simple CLI program that does not use ncurses
or similar terminal graphic library.
See morimekta.net/utils for procedures on releases.
Argument Parser
The ArgParser
interface, and some associated interfaces builds up a simple but
powerful CLI argument parser. It does not hold to the parsed arguments itself,
but uses closures / callbacks to put or update the values into whatever holding
structure you choose.
import net.morimekta.terminal.args.ArgException;
import java.util.ArrayList;
import java.util.concurrent.atomic.AtomicBoolean;
import static net.morimekta.terminal.args.ArgParser.argParser;
import static net.morimekta.terminal.args.ArgHelp.argHelp;
import static net.morimekta.terminal.args.Flag.flagShort;
import static net.morimekta.terminal.args.Argument.argument;
class SimpleApp {
public static void main(String[] args) {
try {
var help = new AtomicBoolean();
var lines = new ArrayList<String>();
var parser = argParser("simple", "0.0.1", "Test Application")
.add(flagShort("h?", "Show help", help::set))
.add(argument("print", "Strings to print one on each line", lines::add))
.parse(args);
if (help.get()) {
argHelp(e.getParser()).printHelp(System.out);
return;
}
// run the actual program...
lines.forEach(System.out::println);
} catch (ArgException e) {
if (e.getParser() != null) {
// Invalid argument.
argHelp(e.getParser()).printPreamble(System.err);
System.err.println();
System.err.println(e.getMessage());
} else {
// Invalid parser setup.
e.printStackTrace();
}
System.exit(1);
}
}
}
ArgParser
: Main type of the argument parsing. This class takes care of parsing the arguments, and has a builder for building the argument parser. The builder can add options or arguments, and change to aSubCommandSet
builder, which can add subCommands.Option
: Options are the default way of setting flag values. Wherever you want a--arg value
pair in the argument parsing, this is the way. It has two naming options:short
andlong
(or both), where there can be one singlelong
name, which MUST start with--
. There can be multiple short names, using one char each. The purpose of the multi-keyed is mainly for[--help | -h | -?]
(which has then 2 separate short char names).Flag
: Flags are pure boolean options, so they do not accept any value. Flags may have anegateName
long name, and a singlenegateShortName
char.Property
: Properties are generickey
+value
options, which is stored into a map-like consumer using aPutter
implementation.
Argument
: Arguments follows after options, and are handled as string values. There can be multiple arguments and the first argument that 'accepts' something will get it, so the order of the arguments are important.ArgHelp
: Helper to display usage, error preambles etc for an argument parser.ValueParser
: Helper to parse values into other values, including validation that the value is usable. Contains both simple parsers for e.g.int
andlong
types, but also for input files, directories and enum value matching.
Sub Command Set
In addition to options and argument, you can specify sub-commands for an
ArgParser
. Sub-commands will have some instance class of their own, and will
be applied via a setter if arguments are parsed properly. This is defined via
the SubCommandDef
generic type, and it is up to the developer to set what it
is.
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicReference;
import static net.morimekta.terminal.args.ArgHelp.argHelp;
import static net.morimekta.terminal.args.ArgParser.argParser;
import static net.morimekta.terminal.args.Flag.flagShort;
import static net.morimekta.terminal.args.Option.optionShort;
import static net.morimekta.terminal.args.SubCommand.subCommand;
class SimpleApp {
public static class Cmd implements Runnable {
private AtomicInteger i = new AtomicInteger();
public Cmd(ArgParser.Builder parser) {
parser.add(optionShort("i", "Integer", i::set));
}
public void run() {
System.out.println("i = " + i.get());
}
}
public static void main(String[] args) {
var help = new AtomicBoolean();
var cmd = new AtomicReference<>();
var parser = argParser("simple", "0.0.1", "Test Application")
.add(flagShort("h?", "Show help", help::set))
.withSubCommands("cmd", "Do something", cmd::set)
.add(subCommand("foo", "Foo!!!1!", Cmd::new))
.parse(args);
if (help.get()) {
argHelp(e.getParser()).printHelp(System.out);
return;
}
// validate after help, the sub-command is by default mandatory, so this will
// throw an `ArgException` if no command was set.
parser.validate();
// run the actual program...
cmd.get().run();
}
}
SubCommandSet
: This is an internal type, that behaves as an argument, but MUST be placed last. The only way to instantiate it normally is to use thewithSubCommands
method on theArgParser.Builder
. This will stop allowing options and arguments, and instead allowing sub-commands. Sub-commands are argument like commands that have sub-argument-parsers of their own.SubCommand
: Definition of a sub-command. It requires an instance factory for the specific sub-command that will be called immediately with a sub-command specific argument parser builder. It can then internally instantiate its ownArgParser
using the same types and methods as above.
Terminal Handling
The terminal handling is a way to interact with the terminal in a more uniform
manner, without needing ncurses
or similar. Alternative libraries are
Laterna. This library is fully java, but
uses some unix tty
CLI bindings to e.g. get terminal size.
import net.morimekta.terminal.Terminal;
class Test {
public static void main(String... args) {
try (var terminal = new Terminal()) {
terminal.info("Starting");
terminal.pressToContinue("Continue?");
terminal.warn("Not sure about this...");
if (terminal.confirm("Are you really sure?", false)) {
terminal.fatal("Noooo!");
throw new UnsupportedOperationException();
} else {
terminal.error("Told you so.");
}
}
}
}
With output like this:
[info] Starting
Continue?
[warn] Not sure about this...
Are you really sure? [y/N]: Yes
[FATAL] Noooo!
Terminal
: Handle terminal state and does simple logging, input and output.LineBuffer
: Utility that manages a set of lines that are printed to the terminal. Will manage which line is where and can jump up to replace any existing line in the buffer.LinePrinter
: Interface for a simple line-based printer and logger. TheTerminal
is a line printer implementation, and can also provide a print stream that behaves mostly as a line printer, and ensures correct line handling for RAW vs COOKED mode.
Terminal Input
Various utilities for handling input form the user.
InputConfirmation
: Get a simple yes / no confirmation from user.InputLine
: Read a text input from terminal with inline edit capability.InputPassword
: Same asInputLine
, but hides the text (replaced with a fill char).InputSelection
: Show a list of selection lines, and allow the user to use arrows or numeric shortcuts to select from it, or to do some action on the selections.
Progress
Utility showing progress on some ongoing task (or tasks).
class MyTest {
public static void main(String... args) {
// ...
}
}
Giving output like this:
Title: [##################################### ] 73% v /
Title: [###################################################] 100% v @ 0.9 s
Progress
: Progress state used to pass current state to the progress line or progress manager classes.ProgressLine
: Display a single progress bar with spinner until the task is complete.ProgressManager
: Manages a set of progress entries, starting each when previous tasks are done, and displaying a continuous set of progress lines with logging above them all.Spinner
: Interface to generate progress status lines.DefaultSpinners
: A set of default spinners using ASCII or simple unicode block characters.