CLI style#

This guide helps ensure that command-line interface (CLI) utilities in Pigweed behave in a reasonably consistent manner. This applies to build tools, test programs, etc.

The Pigweed CLI style guide is based on the Command Line Interface Guidelines (CLIG), and mostly defers to it. The CLIG applies to Pigweed except as noted. This document covers key guidelines and Pigweed specifics.

As most Pigweed CLIs are not expected to be full-fledged user interfaces like git or docker—but rather a collection of smaller tools—this guide focuses on specific basics rather than broad philosophy or advanced topics. The Pigweed CLI style guide only applies to utilities included in Pigweed itself; projects which use Pigweed are free to conform to this, or any other guide as they see fit.

Examples#

The following programs demonstrate conformance to Pigweed’s CLI style guide rules:

Exit codes#

Programs must exit with a zero exit code on success and nonzero on failure.

If multiple non-zero exit codes are used, they should be clearly documented and treated as a stable API. If appropriate, consider using pw_status codes.

Note

In no case should a program return -1 from main(). The exit code is essentially a uint8_t so returning -1 would result in an exit status of 255.

Tip

Avoid exit codes 126 and above because those conflict with exit codes returned by some shells. This can create ambiguity when a tool is called via a wrapper script. See https://tldp.org/LDP/abs/html/exitcodes.html.

Program output#

Following these guidelines ensures that the output of a program can be properly used in a shell pipeline (e.g. pw_foo | grep foo) or otherwise consumed by another program or script.

See CLIG:Guidelines Output.

Standard output#

The main output of a program should be written to standard out (stdout).

#include <iostream>

int main() {
  std::cout << "foo: " << foo() << std::endl;
  std::cout << "bar: " << bar() << std::endl;
}
#include <stdio.h>

int main() {
  printf("foo: %d\n", foo());
  printf("bar: %d\n", bar());
}
def main() -> int:
    print("foo:", foo())
    print("bar:", bar())
    return 0

Standard error#

Debug logs, error messages, etc. should be written to standard error (stderr).

#include <iostream>

int main() {
  if (!InitFoo()) {
    std::cerr << "Error: failed to initialize foo!" << std::endl;
    return 2;
  }
  // ...
}
#include <stdio.h>

int main() {
  if (!InitFoo()) {
    fprintf(stderr, "Error: failed to initialize foo!\n");
    return 2;
  }
  // ...
}
import sys

def main() -> int:
    if not init_foo():
      print("Error: failed to initialize foo!", file=sys.stderr)
      return 2
    # ...

Logging#

It is recommended to use pw_log for logging, including PW_LOG_DEBUG for debug messages, and PW_LOG_ERROR for all error conditions.

Warning

Currently there is no preconfigured pw_log backend which sends log messages to stderr. See issue #329747262.

This can be achieved by using pw_log_basic and calling SetOutput() as follows:

pw::log_basic::SetOutput([](std::string_view log) {
  std::cerr << log << std::endl;
});

Warning

Currently there is no mechanism for setting the pw_log level at runtime. (E.g. via --verbose or --quiet options). See issue #329755001.

Exceptions:

  • Informative messages which should be written to stderr, but which form a core part of the user interface, can be written directly to stderr rather than via pw_log. You can do this for usage text, for example.

Arguments and flags#

See CLIG:Guidelines Arguments and flags.

  • Prefer flags over (positional) arguments. This leads to a more verbose, but much more extensible interface.

    Yes: Using flags to clarify inputs

    $ pw_foo --symbols=symbols.txt --config=config.json --passes=7 \
             --bin-out=output.bin --map-out=output.map
    

    No: Using a lot of positional arguments

    $ pw_foo symbols.txt config.json 7 output.bin output.map
    
  • Prefer subcommands (which are naturally mutually exclusive) over mutually exclusive flags.

    Yes: Using subcommands to specify actions

    $ pw_foo get --key abc
    $ pw_foo set --key abc
    

    No: Using mutually-exclusive flags

    $ pw_foo --get-key abc
    $ pw_foo --set-key abc
    $ pw_foo --get-key abc --set-key abc  # Error
    
  • Show usage or help text when no subcommand or arguments are provided. Display full help text by default unless it is longer than 24 lines, in which case, show abbreviated usage text. Show full help text if --help is given.