pic_cli Module

A declarative command line parser.

Declare what the program accepts, parse once, then read values back by name. Nothing is printed and nothing is stopped: every failure comes back as an error_t, and even --help is reported rather than acted on, because a library has no business deciding that a process should end or where its text should go. See “The library never prints” below.

Quick start

type(cli_t) :: cli
type(error_t) :: err
integer(int64) :: seed

call cli%set_program("fairport", "Deterministic airport simulator")
call cli%add_positional("scenario", "Scenario script to run", required=.true.)
call cli%add_option("seed", "Master RNG seed", short="s", default="0")
call cli%add_flag("hash", "Print the event-log hash and exit")
call cli%parse(err)
if (.haserror. err) call err%fatal()

if (cli%help_requested()) then
   write (*, "(a)") cli%help_text()
   stop 0
end if

call cli%get("seed", seed, err)
if (cli%is_set("hash")) ...

Grammar

  • --name value and --name=value
  • -s value for an option given a short name
  • -- ends option parsing; everything after it is a positional, even if it starts with a dash
  • -h and --help are reserved: they set help_requested() and are never passed through as options

A flag takes no value. --flag=value is an error rather than a silently ignored value.

Out of scope in this version: subcommands, grouped short flags (-abc), environment variable fallbacks, and range validation.

Repeated options

The last occurrence wins, which is what a shell user expects when they edit a long command line by appending to it. The earlier values are not lost silently, though: occurrences(name) reports how many times an option was given, so a program that wants to reject a repeat, or warn about one, can.

The library never prints

help_text() returns the text. help_requested() reports the request. Neither writes to a unit, and nothing here calls stop. A library that prints has decided that the program has a terminal, that the text goes to output_unit rather than to a log or a socket, and that English is wanted. A library that stops has decided that the program has nothing left to clean up. Those are the caller’s decisions.

Testing

parse_args is the real implementation and takes the arguments as an array. parse only collects get_command_argument and forwards to it. Tests therefore never need a real command line, and every case below – including the ones that are errors – is exercised without a shell.


Uses

  • module~~pic_cli~~UsesGraph module~pic_cli pic_cli module~pic_error pic_error module~pic_cli->module~pic_error module~pic_string_type pic_string_type module~pic_cli->module~pic_string_type module~pic_tokenizer pic_tokenizer module~pic_cli->module~pic_tokenizer module~pic_types pic_types module~pic_cli->module~pic_types module~pic_error->module~pic_types module~pic_global_definitions pic_global_definitions module~pic_error->module~pic_global_definitions module~pic_string_type->module~pic_types module~pic_ascii pic_ascii module~pic_string_type->module~pic_ascii module~pic_optional_value pic_optional_value module~pic_string_type->module~pic_optional_value module~pic_tokenizer->module~pic_error module~pic_tokenizer->module~pic_string_type module~pic_tokenizer->module~pic_types module~pic_tokenizer->module~pic_ascii module~pic_strings pic_strings module~pic_tokenizer->module~pic_strings iso_c_binding iso_c_binding module~pic_types->iso_c_binding iso_fortran_env iso_fortran_env module~pic_types->iso_fortran_env module~pic_ascii->module~pic_types module~pic_global_definitions->module~pic_types module~pic_optional_value->module~pic_types module~pic_strings->module~pic_string_type module~pic_strings->module~pic_types module~pic_strings->iso_c_binding module~pic_strings->module~pic_ascii module~pic_strings->module~pic_optional_value

Variables

Type Visibility Attributes Name Initial
integer(kind=default_int), private, parameter :: INITIAL_CAPACITY = 8_default_int

Entries reserved on the first registration; grown by doubling.

integer(kind=default_int), private, parameter :: KIND_FLAG = 2_default_int

An entry that takes no value.

integer(kind=default_int), private, parameter :: KIND_OPTION = 1_default_int

An entry that takes a value.

integer(kind=default_int), private, parameter :: KIND_POSITIONAL = 3_default_int

An entry filled from the argument order rather than by name.


Derived Types

type, public ::  cli_t

A command line interface: what is accepted, and what was given.

Read more…

Components

Type Visibility Attributes Name Initial
integer(kind=default_int), private :: count = 0_default_int

Entries in use; entries may be longer.

type(entry_t), private, allocatable :: entries(:)

Declared entries, in registration order, so help_text is stable.

logical, private :: parsed = .false.

Set once a parse has completed, successfully or not.

type(string_type), private :: program_name

Name used in the usage line.

type(string_type), private :: summary

One-line description of the program.

logical, private :: wants_help = .false.

Set when -h or --help was seen.

Type-Bound Procedures

procedure, public :: add_flag => cli_add_flag
procedure, public :: add_option => cli_add_option
procedure, public :: add_positional => cli_add_positional
generic, public :: get => get_string, get_char, get_int32, get_int64, get_real_sp, get_real_dp, get_logical
procedure, public :: has => cli_has
procedure, public :: help_requested => cli_help_requested
procedure, public :: help_text => cli_help_text
procedure, public :: is_set => cli_is_set
procedure, public :: occurrences => cli_occurrences
procedure, public :: parse => cli_parse
procedure, public :: parse_args => cli_parse_args
procedure, public :: set_program => cli_set_program
procedure, private :: find => cli_find
procedure, private :: get_char => cli_get_char
procedure, private :: get_int32 => cli_get_int32
procedure, private :: get_int64 => cli_get_int64
procedure, private :: get_logical => cli_get_logical
procedure, private :: get_real_dp => cli_get_real_dp
procedure, private :: get_real_sp => cli_get_real_sp
procedure, private :: get_string => cli_get_string
procedure, private :: reserve => cli_reserve

type, private ::  entry_t

One declared option, flag or positional.

Components

Type Visibility Attributes Name Initial
type(string_type), private :: default

The declared default, kept so that a second parse_args can put value back. Without it a re-parse silently inherits the previous run’s values.

type(string_type), private :: help

One-line description, for help_text.

integer(kind=default_int), private :: kind = KIND_OPTION

One of the KIND_* codes.

type(string_type), private :: name

Long name, without the leading dashes.

logical, private :: required = .false.

Whether a missing positional is an error.

integer(kind=default_int), private :: seen = 0_default_int

How many times this entry appeared on the command line.

type(string_type), private :: short

Single-character short name, or empty if there is none.

type(string_type), private :: value

Current value: the default until parsing overwrites it.


Functions

private pure function any_of_kind(this, kind) result(r)

Whether any declared entry has this kind.

Arguments

Type IntentOptional Attributes Name
class(cli_t), intent(in) :: this
integer(kind=default_int), intent(in) :: kind

Return Value logical

private pure function cli_find(this, name) result(idx)

Index of the entry with this long name, or 0.

Read more…

Arguments

Type IntentOptional Attributes Name
class(cli_t), intent(in) :: this
character(len=*), intent(in) :: name

Long name to look for, without dashes.

Return Value integer(kind=default_int)

private pure function cli_find_short(this, short) result(idx)

Index of the entry with this short name, or 0. Empty never matches.

Arguments

Type IntentOptional Attributes Name
class(cli_t), intent(in) :: this
character(len=*), intent(in) :: short

Return Value integer(kind=default_int)

private pure function cli_has(this, name) result(r)

Whether name was declared at all.

Arguments

Type IntentOptional Attributes Name
class(cli_t), intent(in) :: this
character(len=*), intent(in) :: name

Long name to look for.

Return Value logical

private pure function cli_help_requested(this) result(r)

Whether -h or --help was given.

Read more…

Arguments

Type IntentOptional Attributes Name
class(cli_t), intent(in) :: this

Return Value logical

private function cli_help_text(this) result(text)

The help text, as a string. Nothing is printed; see the module note.

Read more…

Arguments

Type IntentOptional Attributes Name
class(cli_t), intent(in) :: this

Return Value character(len=:), allocatable

private pure function cli_is_set(this, name) result(r)

Whether name appeared on the command line.

Read more…

Arguments

Type IntentOptional Attributes Name
class(cli_t), intent(in) :: this
character(len=*), intent(in) :: name

Long name to look for.

Return Value logical

private pure function cli_occurrences(this, name) result(n)

How many times name appeared on the command line.

Read more…

Arguments

Type IntentOptional Attributes Name
class(cli_t), intent(in) :: this
character(len=*), intent(in) :: name

Long name to look for.

Return Value integer(kind=default_int)

private pure function entry_label(entry) result(label)

The left-hand column of one help line.

Arguments

Type IntentOptional Attributes Name
type(entry_t), intent(in) :: entry

Return Value character(len=:), allocatable

private pure function is_long(arg) result(r)

Whether arg is a --name form. A bare -- is not.

Arguments

Type IntentOptional Attributes Name
character(len=*), intent(in) :: arg

Return Value logical

private pure function is_short(arg) result(r)

Whether arg is a -s form. A bare - is not, and neither is a negative number, which must be able to reach a positional.

Arguments

Type IntentOptional Attributes Name
character(len=*), intent(in) :: arg

Return Value logical

private pure function looks_numeric(arg) result(r)

Whether arg is a negative number rather than an option.

Read more…

Arguments

Type IntentOptional Attributes Name
character(len=*), intent(in) :: arg

Token to classify; known to start with - and be longer than one.

Return Value logical


Subroutines

private subroutine cli_add_flag(this, name, help, short)

Declare a flag, which takes no value.

Read more…

Arguments

Type IntentOptional Attributes Name
class(cli_t), intent(inout) :: this
character(len=*), intent(in) :: name

Long name, given on the command line as --name.

character(len=*), intent(in) :: help

One-line description.

character(len=*), intent(in), optional :: short

Single-character short name.

private subroutine cli_add_option(this, name, help, short, default)

Declare an option that takes a value.

Arguments

Type IntentOptional Attributes Name
class(cli_t), intent(inout) :: this
character(len=*), intent(in) :: name

Long name, given on the command line as --name.

character(len=*), intent(in) :: help

One-line description.

character(len=*), intent(in), optional :: short

Single-character short name, given as -s.

character(len=*), intent(in), optional :: default

Value get returns when the option is absent; empty if omitted.

private subroutine cli_add_positional(this, name, help, required, default)

Declare a positional argument, filled in registration order.

Arguments

Type IntentOptional Attributes Name
class(cli_t), intent(inout) :: this
character(len=*), intent(in) :: name

Name used to read the value back, and in the usage line.

character(len=*), intent(in) :: help

One-line description.

logical, intent(in), optional :: required

Whether a parse without it is ERROR_VALIDATION; default false.

character(len=*), intent(in), optional :: default

Value get returns when the argument is absent.

private subroutine cli_get_char(this, name, value, err)

Read a value into a deferred-length character.

Arguments

Type IntentOptional Attributes Name
class(cli_t), intent(in) :: this
character(len=*), intent(in) :: name

Long name of a declared entry.

character(len=:), intent(out), allocatable :: value

The last value given, or the declared default.

type(error_t), intent(inout), optional :: err

Set to ERROR_VALIDATION when name was never declared.

private subroutine cli_get_int32(this, name, value, err)

Read a value as integer(int32), strictly.

Read more…

Arguments

Type IntentOptional Attributes Name
class(cli_t), intent(in) :: this
character(len=*), intent(in) :: name

Long name of a declared entry.

integer(kind=int32), intent(out) :: value

Parsed value; zero when the conversion failed.

type(error_t), intent(inout), optional :: err

Set on an undeclared name or an unparsable value.

private subroutine cli_get_int64(this, name, value, err)

Read a value as integer(int64), strictly.

Arguments

Type IntentOptional Attributes Name
class(cli_t), intent(in) :: this
character(len=*), intent(in) :: name

Long name of a declared entry.

integer(kind=int64), intent(out) :: value

Parsed value; zero when the conversion failed.

type(error_t), intent(inout), optional :: err

Set on an undeclared name or an unparsable value.

private subroutine cli_get_logical(this, name, value, err)

Read a value as logical.

Read more…

Arguments

Type IntentOptional Attributes Name
class(cli_t), intent(in) :: this
character(len=*), intent(in) :: name

Long name of a declared entry.

logical, intent(out) :: value

Parsed value; false when the conversion failed.

type(error_t), intent(inout), optional :: err

Set on an undeclared name or an unrecognised spelling.

private subroutine cli_get_real_dp(this, name, value, err)

Read a value as real(dp), strictly.

Arguments

Type IntentOptional Attributes Name
class(cli_t), intent(in) :: this
character(len=*), intent(in) :: name

Long name of a declared entry.

real(kind=dp), intent(out) :: value

Parsed value; zero when the conversion failed.

type(error_t), intent(inout), optional :: err

Set on an undeclared name or an unparsable value.

private subroutine cli_get_real_sp(this, name, value, err)

Read a value as real(sp), strictly.

Arguments

Type IntentOptional Attributes Name
class(cli_t), intent(in) :: this
character(len=*), intent(in) :: name

Long name of a declared entry.

real(kind=sp), intent(out) :: value

Parsed value; zero when the conversion failed.

type(error_t), intent(inout), optional :: err

Set on an undeclared name or an unparsable value.

private subroutine cli_get_string(this, name, value, err)

Read a value as a string_type.

Arguments

Type IntentOptional Attributes Name
class(cli_t), intent(in) :: this
character(len=*), intent(in) :: name

Long name of a declared entry.

type(string_type), intent(out) :: value

The last value given, or the declared default.

type(error_t), intent(inout), optional :: err

Set to ERROR_VALIDATION when name was never declared.

private subroutine cli_parse(this, err)

Parse the real command line.

Read more…

Arguments

Type IntentOptional Attributes Name
class(cli_t), intent(inout) :: this
type(error_t), intent(inout), optional :: err

Set on a parse or validation failure.

private subroutine cli_parse_args(this, args, err)

Parse an explicit argument list.

Read more…

Arguments

Type IntentOptional Attributes Name
class(cli_t), intent(inout) :: this
type(string_type), intent(in) :: args(:)

Arguments, excluding the program name.

type(error_t), intent(inout), optional :: err

Set on a parse or validation failure.

private subroutine cli_reserve(this)

Make room for one more entry, doubling the store when it is full.

Arguments

Type IntentOptional Attributes Name
class(cli_t), intent(inout) :: this

private subroutine cli_set_program(this, name, summary)

Name and one-line description, used by help_text.

Arguments

Type IntentOptional Attributes Name
class(cli_t), intent(inout) :: this
character(len=*), intent(in) :: name

Program name as it should appear in the usage line.

character(len=*), intent(in) :: summary

One-line description of what the program does.

private subroutine fail(err, code, message)

Set err if it is present, and do nothing if it is not.

Arguments

Type IntentOptional Attributes Name
type(error_t), intent(inout), optional :: err
integer(kind=default_int), intent(in) :: code
character(len=*), intent(in) :: message

private pure subroutine next_positional(this, used, idx)

Index of the next unfilled positional, advancing used.

Read more…

Arguments

Type IntentOptional Attributes Name
class(cli_t), intent(in) :: this
integer(kind=default_int), intent(inout) :: used

Positionals consumed so far; incremented when one is taken.

integer(kind=default_int), intent(out) :: idx

Index of the positional taken, or 0 when they are all filled.