Terminal Utilities

Morimekta Docs Pipeline Coverage
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 a SubCommandSet 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 and long (or both), where there can be one single long 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 a negateName long name, and a single negateShortName char.
    • Property: Properties are generic key + value options, which is stored into a map-like consumer using a Putter 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 and long 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 the withSubCommands method on the ArgParser.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 own ArgParser 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. The Terminal 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 as InputLine, 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.