From cbc89632b93302001ad2bf4e7fdc0bfeba161ed1 Mon Sep 17 00:00:00 2001 From: sin-ack Date: Sun, 3 Apr 2022 22:33:09 +0000 Subject: [PATCH] LibCore+Base: Add way to hide ArgsParser opt.s from specific usage texts This adds the ability to hide certain options from certain help texts. `--complete` is always hidden, whereas `--help` and `--version` are hidden from Markdown help text only. Note that in all cases these three options are hidden from the short usage line. --- Base/usr/share/man/man1/UserspaceEmulator.md | 3 - Base/usr/share/man/man1/config.md | 3 - Base/usr/share/man/man1/fortune.md | 3 - Base/usr/share/man/man1/grep.md | 3 - Base/usr/share/man/man1/gunzip.md | 3 - Base/usr/share/man/man1/gzip.md | 3 - Base/usr/share/man/man1/ifconfig.md | 3 - Base/usr/share/man/man1/lsof.md | 3 - Base/usr/share/man/man1/nc.md | 3 - Base/usr/share/man/man1/netstat.md | 3 - Base/usr/share/man/man1/nl.md | 3 - Base/usr/share/man/man1/ntpquery.md | 3 - Base/usr/share/man/man1/passwd.md | 3 - Base/usr/share/man/man1/profile.md | 3 - Base/usr/share/man/man1/readelf.md | 3 - Base/usr/share/man/man1/shot.md | 3 - Base/usr/share/man/man1/sql.md | 3 - Base/usr/share/man/man1/strace.md | 3 - Base/usr/share/man/man1/tail.md | 3 - Base/usr/share/man/man1/tr.md | 3 - Base/usr/share/man/man1/traceroute.md | 3 - Base/usr/share/man/man1/tree.md | 3 - Base/usr/share/man/man1/truncate.md | 3 - Base/usr/share/man/man1/utmpupdate.md | 3 - Userland/Libraries/LibCore/ArgsParser.cpp | 68 +++++++++++--------- Userland/Libraries/LibCore/ArgsParser.h | 32 +++++---- 26 files changed, 57 insertions(+), 115 deletions(-) diff --git a/Base/usr/share/man/man1/UserspaceEmulator.md b/Base/usr/share/man/man1/UserspaceEmulator.md index 48828a9a4e..4aa661358f 100644 --- a/Base/usr/share/man/man1/UserspaceEmulator.md +++ b/Base/usr/share/man/man1/UserspaceEmulator.md @@ -10,9 +10,6 @@ $ UserspaceEmulator [--report-to-debug] [--pause] [--profile] [--profile-interva ## Options: -* `--help`: Display help message and exit -* `--version`: Print version -* `--complete`: Perform autocompletion * `--report-to-debug`: Write reports to the debug log * `-p`, `--pause`: Pause on startup * `--profile`: Generate a ProfileViewer-compatible profile diff --git a/Base/usr/share/man/man1/config.md b/Base/usr/share/man/man1/config.md index 7fe5f14a99..ed24379ba9 100644 --- a/Base/usr/share/man/man1/config.md +++ b/Base/usr/share/man/man1/config.md @@ -14,9 +14,6 @@ Show or modify values in the configuration files through ConfigServer. ## Options: -* `--help`: Display help message and exit -* `--version`: Print version -* `--complete`: Perform autocompletion * `-r`, `--remove`: Remove key ## Arguments: diff --git a/Base/usr/share/man/man1/fortune.md b/Base/usr/share/man/man1/fortune.md index a6a6b1a8d0..b6f8842168 100644 --- a/Base/usr/share/man/man1/fortune.md +++ b/Base/usr/share/man/man1/fortune.md @@ -14,9 +14,6 @@ Open a fortune cookie, receive a free quote for the day! ## Options: -* `--help`: Display help message and exit -* `--version`: Print version -* `--complete`: Perform autocompletion ## Arguments: diff --git a/Base/usr/share/man/man1/grep.md b/Base/usr/share/man/man1/grep.md index 9bf74cc752..c2152777db 100644 --- a/Base/usr/share/man/man1/grep.md +++ b/Base/usr/share/man/man1/grep.md @@ -10,9 +10,6 @@ $ grep [--recursive] [--extended-regexp] [--regexp Pattern] [-i] [--line-numbers ## Options: -* `--help`: Display help message and exit -* `--version`: Print version -* `--complete`: Perform autocompletion * `-r`, `--recursive`: Recursively scan files * `-E`, `--extended-regexp`: Extended regular expressions * `-e Pattern`, `--regexp Pattern`: Pattern diff --git a/Base/usr/share/man/man1/gunzip.md b/Base/usr/share/man/man1/gunzip.md index 9651958dca..67d6dcf3f6 100644 --- a/Base/usr/share/man/man1/gunzip.md +++ b/Base/usr/share/man/man1/gunzip.md @@ -10,9 +10,6 @@ $ gunzip [--keep] [--stdout] ## Options: -* `--help`: Display help message and exit -* `--version`: Print version -* `--complete`: Perform autocompletion * `-k`, `--keep`: Keep (don't delete) input files * `-c`, `--stdout`: Write to stdout, keep original files unchanged diff --git a/Base/usr/share/man/man1/gzip.md b/Base/usr/share/man/man1/gzip.md index 5f0a290ba0..5d68187583 100644 --- a/Base/usr/share/man/man1/gzip.md +++ b/Base/usr/share/man/man1/gzip.md @@ -10,9 +10,6 @@ $ gzip [--keep] [--stdout] [--decompress] ## Options: -* `--help`: Display help message and exit -* `--version`: Print version -* `--complete`: Perform autocompletion * `-k`, `--keep`: Keep (don't delete) input files * `-c`, `--stdout`: Write to stdout, keep original files unchanged * `-d`, `--decompress`: Decompress diff --git a/Base/usr/share/man/man1/ifconfig.md b/Base/usr/share/man/man1/ifconfig.md index e7d0dd3a0a..d00450f508 100644 --- a/Base/usr/share/man/man1/ifconfig.md +++ b/Base/usr/share/man/man1/ifconfig.md @@ -14,9 +14,6 @@ Display or modify the configuration of each network interface. ## Options: -* `--help`: Display help message and exit -* `--version`: Print version -* `--complete`: Perform autocompletion * `-i ip`, `--ipv4 ip`: Set the IP address of the selected network * `-a adapter`, `--adapter adapter`: Select a specific network adapter to configure * `-g gateway`, `--gateway gateway`: Set the default gateway of the selected network diff --git a/Base/usr/share/man/man1/lsof.md b/Base/usr/share/man/man1/lsof.md index b96c47e1e5..f96e8974bc 100644 --- a/Base/usr/share/man/man1/lsof.md +++ b/Base/usr/share/man/man1/lsof.md @@ -14,9 +14,6 @@ List open files of a processes. This can mean actual files in the file system, s ## Options: -* `--help`: Display help message and exit -* `--version`: Print version -* `--complete`: Perform autocompletion * `-p pid`: Select by PID * `-d fd`: Select by file descriptor * `-u login/UID`: Select by login/UID diff --git a/Base/usr/share/man/man1/nc.md b/Base/usr/share/man/man1/nc.md index ac4311265f..126cf42a8a 100644 --- a/Base/usr/share/man/man1/nc.md +++ b/Base/usr/share/man/man1/nc.md @@ -14,9 +14,6 @@ Network cat: Connect to network sockets as if it were a file. ## Options: -* `--help`: Display help message and exit -* `--version`: Print version -* `--complete`: Perform autocompletion * `-l`, `--listen`: Listen instead of connecting * `-v`, `--verbose`: Log everything that's happening * `-u`, `--udp`: UDP mode diff --git a/Base/usr/share/man/man1/netstat.md b/Base/usr/share/man/man1/netstat.md index 4f8e89872d..d0a10587a3 100644 --- a/Base/usr/share/man/man1/netstat.md +++ b/Base/usr/share/man/man1/netstat.md @@ -14,9 +14,6 @@ Display network connections ## Options: -* `--help`: Display help message and exit -* `--version`: Print version -* `--complete`: Perform autocompletion * `-a`, `--all`: Display both listening and non-listening sockets * `-l`, `--list`: Display only listening sockets * `-t`, `--tcp`: Display only TCP network connections diff --git a/Base/usr/share/man/man1/nl.md b/Base/usr/share/man/man1/nl.md index f019fbc07c..097a45ff8c 100644 --- a/Base/usr/share/man/man1/nl.md +++ b/Base/usr/share/man/man1/nl.md @@ -10,9 +10,6 @@ $ nl [--body-numbering style] [--increment number] [--separator string] [--start ## Options: -* `--help`: Display help message and exit -* `--version`: Print version -* `--complete`: Perform autocompletion * `-b style`, `--body-numbering style`: Line numbering style: 't' for non-empty lines, 'a' for all lines, 'n' for no lines * `-i number`, `--increment number`: Line count increment * `-s string`, `--separator string`: Separator between line numbers and lines diff --git a/Base/usr/share/man/man1/ntpquery.md b/Base/usr/share/man/man1/ntpquery.md index ce7177deda..09b5eb806a 100644 --- a/Base/usr/share/man/man1/ntpquery.md +++ b/Base/usr/share/man/man1/ntpquery.md @@ -10,9 +10,6 @@ $ ntpquery [--adjust] [--set] [--verbose] [host] ## Options: -* `--help`: Display help message and exit -* `--version`: Print version -* `--complete`: Perform autocompletion * `-a`, `--adjust`: Gradually adjust system time (requires root) * `-s`, `--set`: Immediately set system time (requires root) * `-v`, `--verbose`: Verbose output diff --git a/Base/usr/share/man/man1/passwd.md b/Base/usr/share/man/man1/passwd.md index 1ef74f95c5..091cecbdc6 100644 --- a/Base/usr/share/man/man1/passwd.md +++ b/Base/usr/share/man/man1/passwd.md @@ -14,9 +14,6 @@ Modify an account password. ## Options: -* `--help`: Display help message and exit -* `--version`: Print version -* `--complete`: Perform autocompletion * `-d`, `--delete`: Delete password * `-l`, `--lock`: Lock password * `-u`, `--unlock`: Unlock password diff --git a/Base/usr/share/man/man1/profile.md b/Base/usr/share/man/man1/profile.md index 5b3bd45b53..6d9b470572 100644 --- a/Base/usr/share/man/man1/profile.md +++ b/Base/usr/share/man/man1/profile.md @@ -10,9 +10,6 @@ $ profile [-p PID] [-a] [-e] [-d] [-f] [-w] [-c command] [-t event_type] ## Options: -* `--help`: Display help message and exit -* `--version`: Print version -* `--complete`: Perform autocompletion * `-p PID`: Target PID * `-a`: Profile all processes (super-user only), result at /proc/profile * `-e`: Enable diff --git a/Base/usr/share/man/man1/readelf.md b/Base/usr/share/man/man1/readelf.md index bfcac669ec..22fa1b8cdd 100644 --- a/Base/usr/share/man/man1/readelf.md +++ b/Base/usr/share/man/man1/readelf.md @@ -10,9 +10,6 @@ $ readelf [--all] [--file-header] [--program-headers] [--section-headers] [--hea ## Options: -* `--help`: Display help message and exit -* `--version`: Print version -* `--complete`: Perform autocompletion * `-a`, `--all`: Display all * `-h`, `--file-header`: Display ELF header * `-l`, `--program-headers`: Display program headers diff --git a/Base/usr/share/man/man1/shot.md b/Base/usr/share/man/man1/shot.md index b47d608206..638e5e386e 100644 --- a/Base/usr/share/man/man1/shot.md +++ b/Base/usr/share/man/man1/shot.md @@ -10,9 +10,6 @@ $ shot [--clipboard] [--delay seconds] [--screen index] [--region] [output] ## Options: -* `--help`: Display help message and exit -* `--version`: Print version -* `--complete`: Perform autocompletion * `-c`, `--clipboard`: Output to clipboard * `-d seconds`, `--delay seconds`: Seconds to wait before taking a screenshot * `-s index`, `--screen index`: The index of the screen (default: -1 for all screens) diff --git a/Base/usr/share/man/man1/sql.md b/Base/usr/share/man/man1/sql.md index 476d1af1c5..7db38aff55 100644 --- a/Base/usr/share/man/man1/sql.md +++ b/Base/usr/share/man/man1/sql.md @@ -14,9 +14,6 @@ This is a client for the SerenitySQL database server. ## Options: -* `--help`: Display help message and exit -* `--version`: Print version -* `--complete`: Perform autocompletion * `-d database`, `--database database`: Database to connect to * `-r file`, `--read file`: File to read * `-s file`, `--source file`: File to source diff --git a/Base/usr/share/man/man1/strace.md b/Base/usr/share/man/man1/strace.md index 557f0ed06b..9ff0b3fe51 100644 --- a/Base/usr/share/man/man1/strace.md +++ b/Base/usr/share/man/man1/strace.md @@ -14,9 +14,6 @@ Trace all syscalls and their result. ## Options: -* `--help`: Display help message and exit -* `--version`: Print version -* `--complete`: Perform autocompletion * `-p pid`, `--pid pid`: Trace the given PID * `-o output`, `--output output`: Filename to write output to * `-e exclude`, `--exclude exclude`: Comma-delimited syscalls to exclude diff --git a/Base/usr/share/man/man1/tail.md b/Base/usr/share/man/man1/tail.md index f635a6a21f..2bf4e3730e 100644 --- a/Base/usr/share/man/man1/tail.md +++ b/Base/usr/share/man/man1/tail.md @@ -14,9 +14,6 @@ Print the end ('tail') of a file. ## Options: -* `--help`: Display help message and exit -* `--version`: Print version -* `--complete`: Perform autocompletion * `-f`, `--follow`: Output data as it is written to the file * `-n number`, `--lines number`: Fetch the specified number of lines diff --git a/Base/usr/share/man/man1/tr.md b/Base/usr/share/man/man1/tr.md index d82cbf0386..a4812cb68c 100644 --- a/Base/usr/share/man/man1/tr.md +++ b/Base/usr/share/man/man1/tr.md @@ -10,9 +10,6 @@ $ tr [--complement] [--delete] [--squeeze-repeats] [to] ## Options: -* `--help`: Display help message and exit -* `--version`: Print version -* `--complete`: Perform autocompletion * `-c`, `--complement`: Take the complement of the first set * `-d`, `--delete`: Delete characters instead of replacing * `-s`, `--squeeze-repeats`: Omit repeated characters listed in the last given set from the output diff --git a/Base/usr/share/man/man1/traceroute.md b/Base/usr/share/man/man1/traceroute.md index fe925a138d..a65063ab33 100644 --- a/Base/usr/share/man/man1/traceroute.md +++ b/Base/usr/share/man/man1/traceroute.md @@ -10,9 +10,6 @@ $ traceroute [--max-hops hops] [--max-retries tries] [--timeout seconds] to the destination * `-r tries`, `--max-retries tries`: retry TTL at most times * `-t seconds`, `--timeout seconds`: wait at most for a response diff --git a/Base/usr/share/man/man1/tree.md b/Base/usr/share/man/man1/tree.md index 65231e959c..4dc290773a 100644 --- a/Base/usr/share/man/man1/tree.md +++ b/Base/usr/share/man/man1/tree.md @@ -10,9 +10,6 @@ $ tree [--all] [--only-directories] [--maximum-depth level] [directories...] ## Options: -* `--help`: Display help message and exit -* `--version`: Print version -* `--complete`: Perform autocompletion * `-a`, `--all`: Show hidden files * `-d`, `--only-directories`: Show only directories * `-L level`, `--maximum-depth level`: Maximum depth of the tree diff --git a/Base/usr/share/man/man1/truncate.md b/Base/usr/share/man/man1/truncate.md index fa28dd2fe9..9824f0607b 100644 --- a/Base/usr/share/man/man1/truncate.md +++ b/Base/usr/share/man/man1/truncate.md @@ -10,9 +10,6 @@ $ truncate [--size size] [--reference file] ## Options: -* `--help`: Display help message and exit -* `--version`: Print version -* `--complete`: Perform autocompletion * `-s size`, `--size size`: Resize the target file to (or by) this size. Prefix with + or - to expand or shrink the file, or a bare number to set the size exactly * `-r file`, `--reference file`: Resize the target file to match the size of this one diff --git a/Base/usr/share/man/man1/utmpupdate.md b/Base/usr/share/man/man1/utmpupdate.md index e89ab92af3..538afea341 100644 --- a/Base/usr/share/man/man1/utmpupdate.md +++ b/Base/usr/share/man/man1/utmpupdate.md @@ -10,9 +10,6 @@ $ utmpupdate [--create] [--delete] [--PID PID] [--from From] ## Options: -* `--help`: Display help message and exit -* `--version`: Print version -* `--complete`: Perform autocompletion * `-c`, `--create`: Create entry * `-d`, `--delete`: Delete entry * `-p PID`, `--PID PID`: PID diff --git a/Userland/Libraries/LibCore/ArgsParser.cpp b/Userland/Libraries/LibCore/ArgsParser.cpp index ced9d32548..e143a48e47 100644 --- a/Userland/Libraries/LibCore/ArgsParser.cpp +++ b/Userland/Libraries/LibCore/ArgsParser.cpp @@ -29,9 +29,9 @@ namespace Core { ArgsParser::ArgsParser() { - add_option(m_show_help, "Display help message and exit", "help", 0, true); - add_option(m_show_version, "Print version", "version", 0, true); - add_option(m_perform_autocomplete, "Perform autocompletion", "complete", 0, true); + add_option(m_show_help, "Display help message and exit", "help", 0, OptionHideMode::Markdown); + add_option(m_show_version, "Print version", "version", 0, OptionHideMode::Markdown); + add_option(m_perform_autocomplete, "Perform autocompletion", "complete", 0, OptionHideMode::CommandLineAndMarkdown); } bool ArgsParser::parse(int argc, char* const* argv, FailureBehavior failure_behavior) @@ -198,7 +198,7 @@ void ArgsParser::print_usage_terminal(FILE* file, char const* argv0) out(file, "Usage:\n\t\033[1m{}\033[0m", argv0); for (auto& opt : m_options) { - if (opt.hide_from_help_and_autocomplete) + if (opt.hide_mode != OptionHideMode::None) continue; if (opt.requires_argument) out(file, " [{} {}]", opt.name_for_display(), opt.value_name); @@ -228,6 +228,9 @@ void ArgsParser::print_usage_terminal(FILE* file, char const* argv0) if (!m_options.is_empty()) outln(file, "\nOptions:"); for (auto& opt : m_options) { + if (opt.hide_mode == OptionHideMode::CommandLineAndMarkdown) + continue; + auto print_argument = [&]() { if (opt.value_name) { if (opt.requires_argument) @@ -270,7 +273,7 @@ void ArgsParser::print_usage_markdown(FILE* file, char const* argv0) out(file, "\n## Synopsis\n\n```sh\n$ {}", argv0); for (auto& opt : m_options) { - if (opt.hide_from_help_and_autocomplete) + if (opt.hide_mode != OptionHideMode::None) continue; if (opt.requires_argument) out(file, " [{} {}]", opt.name_for_display(), opt.value_name); @@ -299,6 +302,9 @@ void ArgsParser::print_usage_markdown(FILE* file, char const* argv0) if (!m_options.is_empty()) outln(file, "\n## Options:\n"); for (auto& opt : m_options) { + if (opt.hide_mode == OptionHideMode::Markdown || opt.hide_mode == OptionHideMode::CommandLineAndMarkdown) + continue; + auto print_argument = [&]() { if (opt.value_name != nullptr) { if (opt.requires_argument) @@ -347,7 +353,7 @@ void ArgsParser::add_option(Option&& option) m_options.append(move(option)); } -void ArgsParser::add_ignored(char const* long_name, char short_name, bool hidden) +void ArgsParser::add_ignored(char const* long_name, char short_name, OptionHideMode hide_mode) { Option option { false, @@ -358,12 +364,12 @@ void ArgsParser::add_ignored(char const* long_name, char short_name, bool hidden [](char const*) { return true; }, - hidden, + hide_mode, }; add_option(move(option)); } -void ArgsParser::add_option(bool& value, char const* help_string, char const* long_name, char short_name, bool hidden) +void ArgsParser::add_option(bool& value, char const* help_string, char const* long_name, char short_name, OptionHideMode hide_mode) { Option option { false, @@ -376,12 +382,12 @@ void ArgsParser::add_option(bool& value, char const* help_string, char const* lo value = true; return true; }, - hidden, + hide_mode, }; add_option(move(option)); } -void ArgsParser::add_option(char const*& value, char const* help_string, char const* long_name, char short_name, char const* value_name, bool hidden) +void ArgsParser::add_option(char const*& value, char const* help_string, char const* long_name, char short_name, char const* value_name, OptionHideMode hide_mode) { Option option { true, @@ -393,12 +399,12 @@ void ArgsParser::add_option(char const*& value, char const* help_string, char co value = s; return true; }, - hidden, + hide_mode, }; add_option(move(option)); } -void ArgsParser::add_option(String& value, char const* help_string, char const* long_name, char short_name, char const* value_name, bool hidden) +void ArgsParser::add_option(String& value, char const* help_string, char const* long_name, char short_name, char const* value_name, OptionHideMode hide_mode) { Option option { true, @@ -410,12 +416,12 @@ void ArgsParser::add_option(String& value, char const* help_string, char const* value = s; return true; }, - hidden, + hide_mode, }; add_option(move(option)); } -void ArgsParser::add_option(StringView& value, char const* help_string, char const* long_name, char short_name, char const* value_name, bool hidden) +void ArgsParser::add_option(StringView& value, char const* help_string, char const* long_name, char short_name, char const* value_name, OptionHideMode hide_mode) { Option option { true, @@ -427,12 +433,12 @@ void ArgsParser::add_option(StringView& value, char const* help_string, char con value = s; return true; }, - hidden, + hide_mode, }; add_option(move(option)); } -void ArgsParser::add_option(int& value, char const* help_string, char const* long_name, char short_name, char const* value_name, bool hidden) +void ArgsParser::add_option(int& value, char const* help_string, char const* long_name, char short_name, char const* value_name, OptionHideMode hide_mode) { Option option { true, @@ -445,12 +451,12 @@ void ArgsParser::add_option(int& value, char const* help_string, char const* lon value = opt.value_or(0); return opt.has_value(); }, - hidden, + hide_mode, }; add_option(move(option)); } -void ArgsParser::add_option(unsigned& value, char const* help_string, char const* long_name, char short_name, char const* value_name, bool hidden) +void ArgsParser::add_option(unsigned& value, char const* help_string, char const* long_name, char short_name, char const* value_name, OptionHideMode hide_mode) { Option option { true, @@ -463,12 +469,12 @@ void ArgsParser::add_option(unsigned& value, char const* help_string, char const value = opt.value_or(0); return opt.has_value(); }, - hidden, + hide_mode, }; add_option(move(option)); } -void ArgsParser::add_option(double& value, char const* help_string, char const* long_name, char short_name, char const* value_name, bool hidden) +void ArgsParser::add_option(double& value, char const* help_string, char const* long_name, char short_name, char const* value_name, OptionHideMode hide_mode) { Option option { true, @@ -481,12 +487,12 @@ void ArgsParser::add_option(double& value, char const* help_string, char const* value = opt.value_or(0.0); return opt.has_value(); }, - hidden, + hide_mode, }; add_option(move(option)); } -void ArgsParser::add_option(Optional& value, char const* help_string, char const* long_name, char short_name, char const* value_name, bool hidden) +void ArgsParser::add_option(Optional& value, char const* help_string, char const* long_name, char short_name, char const* value_name, OptionHideMode hide_mode) { Option option { true, @@ -498,12 +504,12 @@ void ArgsParser::add_option(Optional& value, char const* help_string, ch value = convert_to_double(s); return value.has_value(); }, - hidden, + hide_mode, }; add_option(move(option)); } -void ArgsParser::add_option(Optional& value, char const* help_string, char const* long_name, char short_name, char const* value_name, bool hidden) +void ArgsParser::add_option(Optional& value, char const* help_string, char const* long_name, char short_name, char const* value_name, OptionHideMode hide_mode) { Option option { true, @@ -515,12 +521,12 @@ void ArgsParser::add_option(Optional& value, char const* help_string, ch value = AK::StringUtils::convert_to_uint(s); return value.has_value(); }, - hidden, + hide_mode, }; add_option(move(option)); } -void ArgsParser::add_option(Vector& values, char const* help_string, char const* long_name, char short_name, char const* value_name, char separator, bool hidden) +void ArgsParser::add_option(Vector& values, char const* help_string, char const* long_name, char short_name, char const* value_name, char separator, OptionHideMode hide_mode) { Option option { true, @@ -540,7 +546,7 @@ void ArgsParser::add_option(Vector& values, char const* help_string, cha return parsed_all_values; }, - hidden + hide_mode }; add_option(move(option)); @@ -718,7 +724,7 @@ void ArgsParser::autocomplete(FILE* file, StringView program_name, Span