CLI Guidelines

Resource centered api

Every new command group starts with the resource name like repo. Commands should be defined as singular. For repository commands it is repo and not repos. You may set aliases to make your command more convenient to use.

@CommandLine.Command(name = "repo")

Subcommands

Subcommands are action centered and can look like scm repo create x/y.

The RepositoryCreateCommand is a subcommand of RepositoryCommand and must be explicitly annotated.

@ParentCommand(value = RepositoryCommand.class)

Parameters and options

Every required field for a command must be a parameter. All other fields have to be options.

scm repo create git namespace/name --init --description="test"

The repository type and namespace/name must be set, so they must be annotated as parameters. The other fields like init and description are optional and are therefore annotated as options.

  @CommandLine.Parameters
  private String type;
  @CommandLine.Parameters
  private String repository;
  @CommandLine.Option(names = "--description")
  private String description;
  @CommandLine.Option(names = "--contact")
  private String contact;
  @CommandLine.Option(names = "--init")
  private boolean init;

Templating

Commands which return large texts or much content should allow templating. This can be achieved by using the TemplateRenderer. If you inject the TemplateRenderer you must annotate it as a Mixin:

  @CommandLine.Mixin
  private final TemplateRenderer templateRenderer;

Table

Besides "loose" templates, you can use a table-like template to render your output. For this purpose use the TemplateRender and create table first. Then add your table headers and rows.

      Table table = templateRenderer.createTable();
      table.addHeader("repoName", "repoType", "repoUrl");
      for (RepositoryCommandDto dto : dtos) {
        table.addRow(dto.getNamespace() + "/" + dto.getName(), dto.getType(), dto.getUrl());
      }
      templateRenderer.renderToStdout(TABLE_TEMPLATE, ImmutableMap.of("rows", table, "repos", dtos));

Result

    NAME                   TYPE URL 
    scmadmin/nice_repo     git  http://localhost:8081/scm/repo/scmadmin/nice_repo    

Key/Value Table

To create a two column (key-value-style) table you can use the addKeyValueRow() method.

    Table table = createTable();
    RepositoryCommandDto dto = mapper.map(repository);
    table.addLabelValueRow("repoNamespace", dto.getNamespace());
    table.addLabelValueRow("repoName", dto.getName());
    table.addLabelValueRow("repoType", dto.getType());
    table.addLabelValueRow("repoContact", dto.getContact());
    table.addLabelValueRow("repoUrl", dto.getUrl());
    table.addLabelValueRow("repoDescription", dto.getDescription());
    renderToStdout(DETAILS_TABLE_TEMPLATE, ImmutableMap.of("rows", table, "repo", dto));

Result

Namespace: scmadmin                                        
Name     : testrepo                                        
Type     : git     

I18n

The CLI client commands should support multiple languages. This can be done by using translation keys in the related resource bundles. By default, we support English and German translations.

Example

  static final String DEFAULT_TEMPLATE = String.join("\n",
    "{{repo.namespace}}/{{repo.name}}",
    "{{i18n.repoDescription}}: {{repo.description}}",
    "{{i18n.repoType}}: {{repo.type}}",
    "{{i18n.repoContact}}: {{repo.contact}}"
  );

The variables starting with i18n are translations from the resource bundles. The fields starting with repo are context related model data from the repository we are currently accessing.

Error handling

There are different options on how to handle errors. You can use the TemplateRender and print the errors or exception messages to stderr channel.

However, you also can throw an exception directly inside your execution. These exceptions will be handled by the CliExceptionHandler and will be printed to the stderr channel based on a specific template.

Validation

The CLI commands support Java bean validation. If you want to use this validation you have to inject the CommandValidator and call validator.validate() in the first line of the command execution. Then you can simply annotate your fields with validation annotations.

Example

  @Email
  @CommandLine.Option(names = {"--contact", "-c"})
  private String contact;
  @Inject
  public MyCommand(CommandValidator validator) {
    this.validator = validator;
  }

@Override
public void run() {
  validator.validate();
  ...
}