sysl

Module

sysl.args

widest_label args_of cli flag help long_flag long_option option parse parse_or_exit scan scan_all short_flag short_option usage_line Arg ArgError Cli Opt Outcome Parsed Scan Display for ArgError

Import import sysl.args

Index

widest_label args_of cli flag help long_flag long_option option parse parse_or_exit scan scan_all short_flag short_option usage_line Arg ArgError Cli Opt Outcome Parsed Scan Display for ArgError

Constants

widest_label

const widest_label: usize = 22

A column the description text starts at, and the widest label that may push it out.

A label longer than this takes its description on the next line instead of moving every other description to the right, which is what one --with-a-very-long-name <PLACEHOLDER> would otherwise do to a table of short ones.

Functions

args_of

args_of(argc: i32, argv: **u8) -> []string

How a program’s arguments become a []string, written here because every line of it is ordinary sysl. What the platform hands the entry point is C’s argc and argv – a count and a vector of NUL-terminated byte runs – and what a sysl program asks for is a slice of strings, so something has to walk the one and build the other. Doing it in the library is what keeps the pair out of every sysl signature: a main(args: []string) is called with the result of this, and the two foreign types are named in one place instead of in each program that wants its arguments.

Each run’s length is found by looking for the terminator rather than by calling strlen, so the conversion asks the platform for nothing beyond the two values it was handed. The bytes are then validated and copied: a string owns what it holds, so an argument outlives the vector it came from and nothing a program does to it reaches memory the platform still owns. An argument that is not UTF-8 stops the program the way unwrap does, with the offset of the byte that made it ill-formed – 04 puts that check at the boundary, and this is one.

It is a module of its own rather than part of sysl, and the reason is what a submodule is for. Almost no program writes a call to this: a main(args: []string) is what asks for the conversion, and the entry point the compiler lays out is what makes it. A name nearly nobody writes has no business in the set every file gets for free, so a program that does want it – one handed an argv by something other than the platform – names sysl.args.args_of and says so.

It stays public for that reason and for one more: this is the only surface on which an argument vector’s failure can be reached at all, since a well-formed one is all a real process will ever hand over.

It is also why it cannot live beside the platform externs in sysl.sys. This calls print and exit, which are sysl‘s, and sysl reaches sysl.sys for its printing – putting both here would make the two modules depend on each other, which 13 s6 refuses. What is left in sys is a leaf that needs nothing, which is what a platform module should be.

cli

cli(name: string, opts: []Opt, about: string = "", version: string = "", operands: string = "") -> Cli

A description, with everything but the options and the name optional.

The defaults are what makes this readable at the call: a program with no version and no operands writes neither, and one with both names them (version = …, operands = …) rather than counting commas. reference/declarations.md § Default parameters and named arguments is what allows it, and the suffix rule is why opts comes second.

flag

flag(short: char, long: string, help: string) -> Opt

An option written both ways that takes no value.

help

help(spec: Cli) -> string

The whole help text, as --help should print it.

The description column is set by the longest label that fits, so a table of short options stays narrow and one long option does not push every description to the right; a label past widest_label takes the next line instead.

Nothing here is wrapped to a terminal width, which is a deliberate limit rather than an omission: asking how wide the terminal is means asking the platform, and this is otherwise pure string work that a program with no os capability can still call. Help text is written to fit.

The column is set in screen columns rather than in bytes, which is columns and not .len. A label is built out of text the program supplied – its long names and its <placeholder>s – so a program whose placeholder is not ASCII would otherwise have its description column go ragged by one position per accented character – and by a different amount per row, so the column comes out ragged rather than merely narrow and there is no correction to apply afterwards. That is why a format specifier’s width is the wrong thing to reach for here: it counts bytes, deliberately, so that it means what snprintf means. It costs a program that prints help the width tables, about four kilobytes; a program that never calls help reaches none of it and links none of them.

long_flag

long_flag(long: string, help: string) -> Opt

The same two, for an option with no one-letter spelling. A word that is rarely typed, or one dangerous enough to be worth spelling out (--force, --no-backup), is better without one.

long_option

long_option(long: string, arg: string, help: string) -> Opt

option

option(short: char, long: string, arg: string, help: string) -> Opt

An option written both ways that takes one.

parse

parse(spec: Cli, argv: []string) -> Result[Outcome, ArgError]

A command line read against a description.

It neither prints nor stops the program, whatever it finds – a failure comes back as an Err and a --help comes back as an Outcome, so this is the entry point a test drives and the one a program with something of its own to say about a bad option calls.

parse_or_exit

parse_or_exit(spec: Cli, argv: []string) -> Parsed

The same, doing the conventional thing with what it finds.

--help and --version are answered on standard output and the program stops successfully, because printing them was what it was asked to do. A command line that could not be read goes to standard error with the usage line and a pointer at --help, and the status is 2 – the code getopt, argp and every parser after them reserve for “you invoked me wrongly”, as against 1 for “I ran and it did not work out”.

It exits, and the name is what says so: a caller who would rather decide for itself calls parse.

scan

scan(args: []string) -> Scan

A cursor over the words a program was started with, with the zeroth left out.

It skips args[0] itself, because that word is the program’s own path and is never an argument – and forgetting to skip it is the mistake this would otherwise invite, reported as a mysterious operand that is always there. A program wanting to read a vector that is not its own, or wanting the zeroth as well, uses scan_all.

scan_all

scan_all(args: []string) -> Scan

The same over every word given, reading nothing as a program name.

This is what a program driving a sub-command’s arguments wants, or a test, or anything handed a vector that did not come from the platform.

short_flag

short_flag(short: char, help: string) -> Opt

And for an option with no whole-word spelling, which is mostly what a program keeps for compatibility with something older.

short_option

short_option(short: char, arg: string, help: string) -> Opt

usage_line

usage_line(spec: Cli) -> string

The one line a usage error is reported with.

It is separate from help deliberately, and the separation is the point: this goes to standard error when a command line could not be read, and the full help goes to standard output when --help asked for it. Answering a mistyped flag with forty lines of help buries the sentence saying what was mistyped.

Types

Arg

enum Arg
    Short(name: char)
    Long(name: string)
    Positional(value: string)

What one step of a command line turned out to be.

A short option’s name is a char rather than a byte because that is what a user typed; is a legal thing to want and costs nothing to allow. Positional covers a bare -, which by long convention names standard input and is an operand rather than an option with an empty name.

ArgError

enum ArgError
    MissingValue(option: string)
    UnexpectedValue(option: string)
    UnknownOption(option: string)
    BadValue(option: string, value: string, want: string)

Why a command line could not be read.

The first two are the scanner’s own and are about shape: a value that was asked for and is not there, and one that is there and was never asked for. The last two belong to the layer above, which is the only one that knows what the options are – they live here so that a program handling failure has one type to match on rather than one per layer.

MemberSignatureDescription
messagemessage(self) -> stringA sentence, in the terms the user wrote rather than in the parser’s.

Cli

struct Cli
    name: string
    opts: []Opt
    about: string
    version: string
    operands: string

A whole command line described: the program, what it is for, and what it takes.

MemberSignatureDescription
find_longfind_long(self, name: string) -> Option[usize]Where an option is in the table, or None for one the program does not declare.
find_shortfind_short(self, c: char) -> Option[usize]

Opt

struct Opt
    short: Option[char]
    long: Option[string]
    arg: Option[string]
    help: string

One option a program accepts.

Built through the six constructors below rather than by writing the fields, because what varies between them is which parts are absent – and a call that says flag('v', "verbose", …) reads as what it is, where Opt(Some('v'), Some("verbose"), None, …) reads as bookkeeping.

Outcome

enum Outcome
    Ready(opts: Parsed)
    HelpRequested
    VersionRequested

What a command line turned out to ask for.

--help and --version are reported rather than acted on, which is what keeps parse a function of its arguments: nothing here prints, and nothing here stops the program. A caller wanting the conventional behaviour calls parse_or_exit, which is the one that decides.

Parsed

struct Parsed
    opts: []Opt
    counts: []int
    values: []string
    positionals: []string

What a command line held, asked about by the Opt values that described it.

MemberSignatureDescription
givengiven(self, o: Opt) -> boolWhether the option was given at all.
countcount(self, o: Opt) -> intHow many times it was given, which is what -vvv is for.
valuevalue(self, o: Opt) -> Option[string]The value it was given, or None where it was not given at all.
value_orvalue_or(self, o: Opt, default: string) -> stringThe same with the program’s own default standing in, which is what most callers want.

Scan

struct Scan
    argv: []string
    at: usize
    bundle: string
    attached: Option[string]
    last: string
    rest_are_positional: bool

A cursor over a program’s arguments.

It is a value like every other struct, so a copy of one is a second cursor over the same words – which is occasionally what a program wants (looking ahead without committing) and is the reason the fields below are what they are rather than a pointer into something shared.

MemberSignatureDescription
optionoption -> stringThe option most recently reported, as it was written – -o or --output.
nextnext(*self) -> Result[Option[Arg], ArgError]The next option or operand, or None at the end of the line.
valuevalue(*self) -> Result[string, ArgError]The value belonging to the option just reported, from wherever the user put it.
restrest(*self) -> []stringEvery word that is left, read as operands whatever they look like.

Implementations

Display for ArgError

impl Display for ArgError

So that print(e) and f"$e%s" say the sentence rather than the shape of the value.

Search

Esc
to navigate to open Esc to close