mirror of
https://github.com/fish-shell/fish-shell.git
synced 2025-01-19 09:22:47 +08:00
implement argparse
builtin
We've needed a fishy way to parse flags and arguments given to scripts and functions for a very long time. In particular a manner that provides the same behavior implemented by builtin commands. The long term goal is to support DocOpt. But since it is unclear when that will happen so this implements a `argparse` command. So named as homage to the excellent Python module of the same name. Fixes #4190
This commit is contained in:
parent
b88cacf03e
commit
277999adef
|
@ -1,6 +1,7 @@
|
|||
# fish 2.7b1
|
||||
|
||||
## Notable fixes and improvements
|
||||
- A new `argparse` command is available to allow fish script to parse arguments with the same behavior as builtin commands. This also includes the `fish_opt` helper command. (#4190).
|
||||
- The `COLUMNS` and `LINES` env vars are now correctly set the first time `fish_prompt` is run (#4141).
|
||||
- New `status is-breakpoint` command that is true when a prompt is displayed in response to a `breakpoint` command (#1310).
|
||||
- Invalid array indexes are now silently ignored (#826, #4127).
|
||||
|
|
89
Makefile.in
89
Makefile.in
|
@ -99,21 +99,24 @@ HAVE_DOXYGEN=@HAVE_DOXYGEN@
|
|||
# All objects that the system needs to build fish, except fish.o
|
||||
#
|
||||
FISH_OBJS := obj/autoload.o obj/builtin.o obj/builtin_bg.o obj/builtin_bind.o obj/builtin_block.o \
|
||||
obj/builtin_builtin.o obj/builtin_cd.o obj/builtin_command.o obj/builtin_commandline.o \
|
||||
obj/builtin_complete.o obj/builtin_contains.o obj/builtin_disown.o obj/builtin_echo.o \
|
||||
obj/builtin_emit.o obj/builtin_exit.o obj/builtin_fg.o obj/builtin_function.o \
|
||||
obj/builtin_functions.o obj/builtin_history.o obj/builtin_jobs.o obj/builtin_printf.o \
|
||||
obj/builtin_pwd.o obj/builtin_random.o obj/builtin_read.o obj/builtin_realpath.o \
|
||||
obj/builtin_return.o obj/builtin_set.o obj/builtin_set_color.o obj/builtin_source.o \
|
||||
obj/builtin_status.o obj/builtin_string.o obj/builtin_test.o obj/builtin_ulimit.o \
|
||||
obj/color.o obj/common.o obj/complete.o obj/env.o obj/env_universal_common.o obj/event.o \
|
||||
obj/exec.o obj/expand.o obj/fallback.o obj/fish_version.o obj/function.o obj/highlight.o \
|
||||
obj/history.o obj/input.o obj/input_common.o obj/intern.o obj/io.o obj/iothread.o \
|
||||
obj/kill.o obj/output.o obj/pager.o obj/parse_execution.o obj/parse_productions.o \
|
||||
obj/parse_tree.o obj/parse_util.o obj/parser.o obj/parser_keywords.o obj/path.o \
|
||||
obj/postfork.o obj/proc.o obj/reader.o obj/sanity.o obj/screen.o obj/signal.o \
|
||||
obj/tokenizer.o obj/utf8.o obj/util.o obj/wcstringutil.o obj/wgetopt.o obj/wildcard.o \
|
||||
obj/wutil.o
|
||||
obj/builtin_builtin.o obj/builtin_cd.o obj/builtin_command.o \
|
||||
obj/builtin_commandline.o obj/builtin_complete.o obj/builtin_contains.o \
|
||||
obj/builtin_disown.o obj/builtin_echo.o obj/builtin_emit.o \
|
||||
obj/builtin_exit.o obj/builtin_fg.o obj/builtin_function.o \
|
||||
obj/builtin_functions.o obj/builtin_argparse.o obj/builtin_history.o \
|
||||
obj/builtin_jobs.o obj/builtin_printf.o obj/builtin_pwd.o \
|
||||
obj/builtin_random.o obj/builtin_read.o obj/builtin_realpath.o \
|
||||
obj/builtin_return.o obj/builtin_set.o obj/builtin_set_color.o \
|
||||
obj/builtin_source.o obj/builtin_status.o obj/builtin_string.o \
|
||||
obj/builtin_test.o obj/builtin_ulimit.o obj/color.o obj/common.o \
|
||||
obj/complete.o obj/env.o obj/env_universal_common.o obj/event.o obj/exec.o \
|
||||
obj/expand.o obj/fallback.o obj/fish_version.o obj/function.o obj/highlight.o \
|
||||
obj/history.o obj/input.o obj/input_common.o obj/intern.o obj/io.o \
|
||||
obj/iothread.o obj/kill.o obj/output.o obj/pager.o obj/parse_execution.o \
|
||||
obj/parse_productions.o obj/parse_tree.o obj/parse_util.o obj/parser.o \
|
||||
obj/parser_keywords.o obj/path.o obj/postfork.o obj/proc.o obj/reader.o \
|
||||
obj/sanity.o obj/screen.o obj/signal.o obj/tokenizer.o obj/utf8.o obj/util.o \
|
||||
obj/wcstringutil.o obj/wgetopt.o obj/wildcard.o obj/wutil.o
|
||||
|
||||
FISH_INDENT_OBJS := obj/fish_indent.o obj/print_help.o $(FISH_OBJS)
|
||||
|
||||
|
@ -956,10 +959,10 @@ obj/builtin.o: src/builtin_command.h src/builtin_commandline.h
|
|||
obj/builtin.o: src/builtin_complete.h src/builtin_contains.h
|
||||
obj/builtin.o: src/builtin_disown.h src/builtin_echo.h src/builtin_emit.h
|
||||
obj/builtin.o: src/builtin_exit.h src/builtin_fg.h src/builtin_functions.h
|
||||
obj/builtin.o: src/builtin_history.h src/builtin_jobs.h src/builtin_printf.h
|
||||
obj/builtin.o: src/builtin_pwd.h src/builtin_random.h src/builtin_read.h
|
||||
obj/builtin.o: src/builtin_realpath.h src/builtin_return.h src/builtin_set.h
|
||||
obj/builtin.o: src/builtin_set_color.h src/builtin_source.h
|
||||
obj/builtin.o: src/builtin_argparse.h src/builtin_history.h src/builtin_jobs.h
|
||||
obj/builtin.o: src/builtin_printf.h src/builtin_pwd.h src/builtin_random.h
|
||||
obj/builtin.o: src/builtin_read.h src/builtin_realpath.h src/builtin_return.h
|
||||
obj/builtin.o: src/builtin_set.h src/builtin_set_color.h src/builtin_source.h
|
||||
obj/builtin.o: src/builtin_status.h src/builtin_string.h src/builtin_test.h
|
||||
obj/builtin.o: src/builtin_ulimit.h src/complete.h src/exec.h src/intern.h
|
||||
obj/builtin.o: src/io.h src/parse_constants.h src/parse_util.h
|
||||
|
@ -985,7 +988,7 @@ obj/builtin_cd.o: config.h src/builtin.h src/common.h src/fallback.h
|
|||
obj/builtin_cd.o: src/signal.h src/builtin_cd.h src/env.h src/io.h
|
||||
obj/builtin_cd.o: src/parser.h src/event.h src/expand.h src/parse_constants.h
|
||||
obj/builtin_cd.o: src/parse_tree.h src/tokenizer.h src/proc.h src/path.h
|
||||
obj/builtin_cd.o: src/wgetopt.h src/wutil.h
|
||||
obj/builtin_cd.o: src/wutil.h
|
||||
obj/builtin_command.o: config.h src/builtin.h src/common.h src/fallback.h
|
||||
obj/builtin_command.o: src/signal.h src/builtin_command.h src/io.h src/path.h
|
||||
obj/builtin_command.o: src/env.h src/wgetopt.h src/wutil.h
|
||||
|
@ -1009,13 +1012,13 @@ obj/builtin_disown.o: config.h src/signal.h src/builtin.h src/common.h
|
|||
obj/builtin_disown.o: src/fallback.h src/builtin_disown.h src/io.h
|
||||
obj/builtin_disown.o: src/parser.h src/event.h src/expand.h
|
||||
obj/builtin_disown.o: src/parse_constants.h src/parse_tree.h src/tokenizer.h
|
||||
obj/builtin_disown.o: src/proc.h src/wgetopt.h src/wutil.h
|
||||
obj/builtin_disown.o: src/proc.h src/wutil.h
|
||||
obj/builtin_echo.o: config.h src/builtin.h src/common.h src/fallback.h
|
||||
obj/builtin_echo.o: src/signal.h src/builtin_echo.h src/io.h src/wgetopt.h
|
||||
obj/builtin_echo.o: src/wutil.h
|
||||
obj/builtin_emit.o: config.h src/builtin.h src/common.h src/fallback.h
|
||||
obj/builtin_emit.o: src/signal.h src/builtin_emit.h src/event.h src/io.h
|
||||
obj/builtin_emit.o: src/wgetopt.h src/wutil.h
|
||||
obj/builtin_emit.o: src/wutil.h
|
||||
obj/builtin_exit.o: config.h src/builtin.h src/common.h src/fallback.h
|
||||
obj/builtin_exit.o: src/signal.h src/builtin_exit.h src/io.h src/proc.h
|
||||
obj/builtin_exit.o: src/parse_tree.h src/parse_constants.h src/tokenizer.h
|
||||
|
@ -1038,6 +1041,10 @@ obj/builtin_functions.o: src/event.h src/function.h src/io.h
|
|||
obj/builtin_functions.o: src/parser_keywords.h src/proc.h src/parse_tree.h
|
||||
obj/builtin_functions.o: src/parse_constants.h src/tokenizer.h src/wgetopt.h
|
||||
obj/builtin_functions.o: src/wutil.h
|
||||
obj/builtin_argparse.o: config.h src/builtin.h src/common.h src/fallback.h
|
||||
obj/builtin_argparse.o: src/signal.h src/builtin_argparse.h src/env.h
|
||||
obj/builtin_argparse.o: src/expand.h src/parse_constants.h src/io.h
|
||||
obj/builtin_argparse.o: src/wgetopt.h src/wutil.h
|
||||
obj/builtin_history.o: config.h src/builtin.h src/common.h src/fallback.h
|
||||
obj/builtin_history.o: src/signal.h src/builtin_history.h src/history.h
|
||||
obj/builtin_history.o: src/wutil.h src/io.h src/reader.h src/complete.h
|
||||
|
@ -1050,17 +1057,15 @@ obj/builtin_jobs.o: src/wutil.h
|
|||
obj/builtin_printf.o: config.h src/builtin.h src/common.h src/fallback.h
|
||||
obj/builtin_printf.o: src/signal.h src/io.h src/wutil.h
|
||||
obj/builtin_pwd.o: config.h src/builtin.h src/common.h src/fallback.h
|
||||
obj/builtin_pwd.o: src/signal.h src/builtin_pwd.h src/event.h src/io.h
|
||||
obj/builtin_pwd.o: src/wgetopt.h src/wutil.h
|
||||
obj/builtin_pwd.o: src/signal.h src/builtin_pwd.h src/io.h src/wutil.h
|
||||
obj/builtin_random.o: config.h src/builtin.h src/common.h src/fallback.h
|
||||
obj/builtin_random.o: src/signal.h src/builtin_random.h src/io.h
|
||||
obj/builtin_random.o: src/wgetopt.h src/wutil.h
|
||||
obj/builtin_random.o: src/signal.h src/builtin_random.h src/io.h src/wutil.h
|
||||
obj/builtin_read.o: config.h src/builtin.h src/common.h src/fallback.h
|
||||
obj/builtin_read.o: src/signal.h src/builtin_read.h src/complete.h src/env.h
|
||||
obj/builtin_read.o: src/event.h src/expand.h src/parse_constants.h
|
||||
obj/builtin_read.o: src/highlight.h src/color.h src/io.h src/proc.h
|
||||
obj/builtin_read.o: src/parse_tree.h src/tokenizer.h src/reader.h
|
||||
obj/builtin_read.o: src/wcstringutil.h src/wgetopt.h src/wutil.h
|
||||
obj/builtin_read.o: src/highlight.h src/color.h src/history.h src/wutil.h
|
||||
obj/builtin_read.o: src/io.h src/proc.h src/parse_tree.h src/tokenizer.h
|
||||
obj/builtin_read.o: src/reader.h src/wcstringutil.h src/wgetopt.h
|
||||
obj/builtin_realpath.o: config.h src/builtin.h src/common.h src/fallback.h
|
||||
obj/builtin_realpath.o: src/signal.h src/builtin_realpath.h src/io.h
|
||||
obj/builtin_realpath.o: src/wutil.h
|
||||
|
@ -1081,7 +1086,7 @@ obj/builtin_source.o: src/signal.h src/builtin_source.h src/env.h
|
|||
obj/builtin_source.o: src/intern.h src/io.h src/parser.h src/event.h
|
||||
obj/builtin_source.o: src/expand.h src/parse_constants.h src/parse_tree.h
|
||||
obj/builtin_source.o: src/tokenizer.h src/proc.h src/reader.h src/complete.h
|
||||
obj/builtin_source.o: src/highlight.h src/color.h src/wgetopt.h src/wutil.h
|
||||
obj/builtin_source.o: src/highlight.h src/color.h src/wutil.h
|
||||
obj/builtin_status.o: config.h src/builtin.h src/common.h src/fallback.h
|
||||
obj/builtin_status.o: src/signal.h src/builtin_status.h src/io.h src/parser.h
|
||||
obj/builtin_status.o: src/event.h src/expand.h src/parse_constants.h
|
||||
|
@ -1143,11 +1148,11 @@ obj/fish_indent.o: src/signal.h src/env.h src/fish_version.h src/highlight.h
|
|||
obj/fish_indent.o: src/output.h src/parse_constants.h src/parse_tree.h
|
||||
obj/fish_indent.o: src/tokenizer.h src/print_help.h src/wutil.h
|
||||
obj/fish_key_reader.o: config.h src/signal.h src/common.h src/fallback.h
|
||||
obj/fish_key_reader.o: src/env.h src/input.h src/builtin_bind.h
|
||||
obj/fish_key_reader.o: src/input_common.h src/print_help.h src/proc.h
|
||||
obj/fish_key_reader.o: src/io.h src/parse_tree.h src/parse_constants.h
|
||||
obj/fish_key_reader.o: src/tokenizer.h src/reader.h src/complete.h
|
||||
obj/fish_key_reader.o: src/highlight.h src/color.h src/wutil.h
|
||||
obj/fish_key_reader.o: src/env.h src/fish_version.h src/input.h
|
||||
obj/fish_key_reader.o: src/builtin_bind.h src/input_common.h src/print_help.h
|
||||
obj/fish_key_reader.o: src/proc.h src/io.h src/parse_tree.h
|
||||
obj/fish_key_reader.o: src/parse_constants.h src/tokenizer.h src/reader.h
|
||||
obj/fish_key_reader.o: src/complete.h src/highlight.h src/color.h src/wutil.h
|
||||
obj/fish_tests.o: config.h src/signal.h src/builtin.h src/common.h
|
||||
obj/fish_tests.o: src/fallback.h src/color.h src/complete.h src/env.h
|
||||
obj/fish_tests.o: src/env_universal_common.h src/wutil.h src/event.h
|
||||
|
@ -1195,13 +1200,13 @@ obj/pager.o: config.h src/common.h src/fallback.h src/signal.h src/complete.h
|
|||
obj/pager.o: src/highlight.h src/color.h src/env.h src/pager.h src/reader.h
|
||||
obj/pager.o: src/parse_constants.h src/screen.h src/util.h src/wutil.h
|
||||
obj/parse_execution.o: config.h src/builtin.h src/common.h src/fallback.h
|
||||
obj/parse_execution.o: src/signal.h src/complete.h src/env.h src/event.h
|
||||
obj/parse_execution.o: src/exec.h src/expand.h src/parse_constants.h
|
||||
obj/parse_execution.o: src/function.h src/io.h src/parse_execution.h
|
||||
obj/parse_execution.o: src/parse_tree.h src/tokenizer.h src/proc.h
|
||||
obj/parse_execution.o: src/parse_util.h src/parser.h src/path.h src/reader.h
|
||||
obj/parse_execution.o: src/highlight.h src/color.h src/util.h src/wildcard.h
|
||||
obj/parse_execution.o: src/wutil.h
|
||||
obj/parse_execution.o: src/signal.h src/builtin_function.h src/complete.h
|
||||
obj/parse_execution.o: src/env.h src/event.h src/exec.h src/expand.h
|
||||
obj/parse_execution.o: src/parse_constants.h src/function.h src/io.h
|
||||
obj/parse_execution.o: src/parse_execution.h src/parse_tree.h src/tokenizer.h
|
||||
obj/parse_execution.o: src/proc.h src/parse_util.h src/parser.h src/path.h
|
||||
obj/parse_execution.o: src/reader.h src/highlight.h src/color.h src/util.h
|
||||
obj/parse_execution.o: src/wildcard.h src/wutil.h
|
||||
obj/parse_productions.o: config.h src/common.h src/fallback.h src/signal.h
|
||||
obj/parse_productions.o: src/parse_constants.h src/parse_productions.h
|
||||
obj/parse_productions.o: src/parse_tree.h src/tokenizer.h
|
||||
|
|
|
@ -647,6 +647,8 @@
|
|||
63A2C0E81CC5F9FB00973404 /* pcre2_find_bracket.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = pcre2_find_bracket.c; sourceTree = "<group>"; };
|
||||
9C7A55721DCD71330049C25D /* fish_key_reader */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = fish_key_reader; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
9C7A557C1DCD717C0049C25D /* fish_key_reader.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = fish_key_reader.cpp; sourceTree = "<group>"; };
|
||||
CBB772591F11F93F00780A21 /* builtin_argparse.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = builtin_argparse.cpp; sourceTree = "<group>"; };
|
||||
CBB7725A1F11F93F00780A21 /* builtin_argparse.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = builtin_argparse.h; sourceTree = "<group>"; };
|
||||
D00769421990137800CA4627 /* fish_tests */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = fish_tests; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
D00F63F019137E9D00FCCDEC /* fish_version.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = fish_version.cpp; sourceTree = "<group>"; };
|
||||
D01A2D23169B730A00767098 /* man1 */ = {isa = PBXFileReference; lastKnownFileType = text; name = man1; path = pages_for_manpath/man1; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
|
@ -984,6 +986,8 @@
|
|||
D0D02A91159845EF008E62BD /* Sources */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
CBB772591F11F93F00780A21 /* builtin_argparse.cpp */,
|
||||
CBB7725A1F11F93F00780A21 /* builtin_argparse.h */,
|
||||
9C7A557C1DCD717C0049C25D /* fish_key_reader.cpp */,
|
||||
4E142D731B56B5D7008783C8 /* config.h */,
|
||||
D0C6FCCB14CFA4B7004CE8AD /* autoload.h */,
|
||||
|
|
|
@ -28,6 +28,7 @@
|
|||
#include <string>
|
||||
|
||||
#include "builtin.h"
|
||||
#include "builtin_argparse.h"
|
||||
#include "builtin_bg.h"
|
||||
#include "builtin_bind.h"
|
||||
#include "builtin_block.h"
|
||||
|
@ -406,6 +407,7 @@ int builtin_false(parser_t &parser, io_streams_t &streams, wchar_t **argv) {
|
|||
static const builtin_data_t builtin_datas[] = {
|
||||
{L"[", &builtin_test, N_(L"Test a condition")},
|
||||
{L"and", &builtin_generic, N_(L"Execute command if previous command suceeded")},
|
||||
{L"argparse", &builtin_argparse, N_(L"Parse options in fish script")},
|
||||
{L"begin", &builtin_generic, N_(L"Create a block of code")},
|
||||
{L"bg", &builtin_bg, N_(L"Send job to background")},
|
||||
{L"bind", &builtin_bind, N_(L"Handle fish key bindings")},
|
||||
|
|
354
src/builtin_argparse.cpp
Normal file
354
src/builtin_argparse.cpp
Normal file
|
@ -0,0 +1,354 @@
|
|||
// Implementation of the argparse builtin.
|
||||
//
|
||||
// See issue #4190 for the rationale behind the original behavior of this builtin.
|
||||
#include "config.h" // IWYU pragma: keep
|
||||
|
||||
#if 0
|
||||
#include <malloc/malloc.h>
|
||||
#endif
|
||||
|
||||
#include <stddef.h>
|
||||
#include <wchar.h>
|
||||
|
||||
#include <functional>
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <type_traits>
|
||||
#include <utility>
|
||||
|
||||
#include "builtin.h"
|
||||
#include "builtin_argparse.h"
|
||||
#include "common.h"
|
||||
#include "env.h"
|
||||
#include "fallback.h" // IWYU pragma: keep
|
||||
#include "io.h"
|
||||
#include "wgetopt.h" // IWYU pragma: keep
|
||||
#include "wutil.h" // IWYU pragma: keep
|
||||
|
||||
class parser_t;
|
||||
|
||||
#define BUILTIN_ERR_INVALID_OPT_SPEC _(L"%ls: Invalid option spec '%ls' at char '%lc'\n")
|
||||
|
||||
class option_spec_t {
|
||||
public:
|
||||
wchar_t short_flag;
|
||||
wcstring long_flag;
|
||||
wcstring_list_t vals;
|
||||
bool short_flag_valid;
|
||||
int num_allowed;
|
||||
int num_seen;
|
||||
|
||||
option_spec_t(wchar_t s)
|
||||
: short_flag(s), long_flag(), vals(), short_flag_valid(true), num_allowed(0), num_seen(0) {}
|
||||
};
|
||||
|
||||
class argparse_cmd_opts_t {
|
||||
public:
|
||||
bool print_help = false;
|
||||
bool require_order = false;
|
||||
wcstring name = L"argparse";
|
||||
wcstring_list_t argv;
|
||||
struct std::map<wchar_t, option_spec_t *> options;
|
||||
|
||||
~argparse_cmd_opts_t() {
|
||||
for (auto it : options) {
|
||||
delete it.second;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
static const wchar_t *short_options = L"+:hn:r";
|
||||
static const struct woption long_options[] = {{L"require-order", no_argument, NULL, 'r'},
|
||||
{L"name", required_argument, NULL, 'n'},
|
||||
{L"help", no_argument, NULL, 'h'},
|
||||
{NULL, 0, NULL, 0}};
|
||||
|
||||
static bool parse_flag_modifiers(argparse_cmd_opts_t &opts, option_spec_t *opt_spec,
|
||||
const wcstring &option_spec, const wchar_t *s,
|
||||
io_streams_t &streams) {
|
||||
if (*s == '+') {
|
||||
opt_spec->num_allowed = 2; // mandatory arg and can appear more than once
|
||||
s++;
|
||||
} else if (*s == ':') {
|
||||
s++;
|
||||
if (*s == ':') {
|
||||
opt_spec->num_allowed = -1; // optional arg
|
||||
s++;
|
||||
} else {
|
||||
opt_spec->num_allowed = 1; // mandatory arg and can appear only once
|
||||
}
|
||||
}
|
||||
|
||||
if (*s) {
|
||||
streams.err.append_format(BUILTIN_ERR_INVALID_OPT_SPEC, opts.name.c_str(),
|
||||
option_spec.c_str(), *s);
|
||||
return false;
|
||||
}
|
||||
|
||||
opts.options.emplace(opt_spec->short_flag, opt_spec);
|
||||
return true;
|
||||
}
|
||||
|
||||
// This parses an option spec string into a struct option_spec.
|
||||
static bool parse_option_spec(argparse_cmd_opts_t &opts, wcstring option_spec,
|
||||
io_streams_t &streams) {
|
||||
if (option_spec.empty()) {
|
||||
streams.err.append_format(_(L"%s: An option spec must have a short flag letter\n"),
|
||||
opts.name.c_str());
|
||||
return false;
|
||||
}
|
||||
|
||||
const wchar_t *s = option_spec.c_str();
|
||||
option_spec_t *opt_spec = new option_spec_t(*s++);
|
||||
|
||||
if (*s == '/') {
|
||||
s++; // the struct is initialized assuming short_flag_valid should be true
|
||||
} else if (*s == '-') {
|
||||
opt_spec->short_flag_valid = false;
|
||||
s++;
|
||||
} else {
|
||||
// Long flag name not allowed if second char isn't '/' or '-' so just check for
|
||||
// behavior modifier chars.
|
||||
return parse_flag_modifiers(opts, opt_spec, option_spec, s, streams);
|
||||
}
|
||||
|
||||
const wchar_t *e = s;
|
||||
while (*e && *e != '+' && *e != ':') e++;
|
||||
if (e == s) {
|
||||
streams.err.append_format(BUILTIN_ERR_INVALID_OPT_SPEC, opts.name.c_str(),
|
||||
option_spec.c_str(), *(s - 1));
|
||||
return false;
|
||||
}
|
||||
|
||||
opt_spec->long_flag = wcstring(s, e - s);
|
||||
return parse_flag_modifiers(opts, opt_spec, option_spec, e, streams);
|
||||
}
|
||||
|
||||
static int parse_cmd_opts(argparse_cmd_opts_t &opts, int *optind, //!OCLINT(high ncss method)
|
||||
int argc, wchar_t **argv, parser_t &parser, io_streams_t &streams) {
|
||||
wchar_t *cmd = argv[0];
|
||||
int opt;
|
||||
wgetopter_t w;
|
||||
while ((opt = w.wgetopt_long(argc, argv, short_options, long_options, NULL)) != -1) {
|
||||
switch (opt) {
|
||||
case 'n': {
|
||||
opts.name = w.woptarg;
|
||||
break;
|
||||
}
|
||||
case 'r': {
|
||||
opts.require_order = true;
|
||||
break;
|
||||
}
|
||||
case 'h': {
|
||||
opts.print_help = true;
|
||||
break;
|
||||
}
|
||||
case ':': {
|
||||
builtin_missing_argument(parser, streams, cmd, argv[w.woptind - 1]);
|
||||
return STATUS_INVALID_ARGS;
|
||||
}
|
||||
case '?': {
|
||||
builtin_unknown_option(parser, streams, cmd, argv[w.woptind - 1]);
|
||||
return STATUS_INVALID_ARGS;
|
||||
}
|
||||
default: {
|
||||
DIE("unexpected retval from wgetopt_long");
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
*optind = w.woptind;
|
||||
return STATUS_CMD_OK;
|
||||
}
|
||||
|
||||
static void populate_option_strings(
|
||||
argparse_cmd_opts_t &opts, wcstring &short_options,
|
||||
std::unique_ptr<woption, std::function<void(woption *)>> &long_options) {
|
||||
int i = 0;
|
||||
for (auto it : opts.options) {
|
||||
option_spec_t *opt_spec = it.second;
|
||||
if (opt_spec->short_flag_valid) short_options.push_back(opt_spec->short_flag);
|
||||
|
||||
int arg_type = no_argument;
|
||||
if (opt_spec->num_allowed == -1) {
|
||||
arg_type = optional_argument;
|
||||
if (opt_spec->short_flag_valid) short_options.append(L"::");
|
||||
} else if (opt_spec->num_allowed > 0) {
|
||||
arg_type = required_argument;
|
||||
if (opt_spec->short_flag_valid) short_options.append(L":");
|
||||
}
|
||||
|
||||
if (!opt_spec->long_flag.empty()) {
|
||||
long_options.get()[i++] = {opt_spec->long_flag.c_str(), arg_type, NULL,
|
||||
opt_spec->short_flag};
|
||||
}
|
||||
}
|
||||
long_options.get()[i] = {NULL, 0, NULL, 0};
|
||||
}
|
||||
|
||||
// This function mimics the `wgetopt_long()` usage found elsewhere in our other builtin commands.
|
||||
// It's different in that the short and long option structures are constructed dynamically based on
|
||||
// arguments provided to the `argparse` command.
|
||||
static int argparse_parse_args(argparse_cmd_opts_t &opts, const wcstring_list_t &args,
|
||||
parser_t &parser, io_streams_t &streams) {
|
||||
if (args.empty()) return STATUS_CMD_OK;
|
||||
|
||||
wcstring short_options = opts.require_order ? L"+:" : L":";
|
||||
int nflags = opts.options.size();
|
||||
// This assumes every option has a long flag. Which is the worst case and isn't worth optimizing
|
||||
// since the number of options is always quite small. Thus the size of the array will never be
|
||||
// much larger than the minimum size required for all the long options.
|
||||
auto long_options = std::unique_ptr<woption, std::function<void(woption *)>>(
|
||||
new woption[nflags + 1], [](woption *p) { delete[] p; });
|
||||
populate_option_strings(opts, short_options, long_options);
|
||||
|
||||
const wchar_t *cmd = opts.name.c_str();
|
||||
int argc = args.size();
|
||||
|
||||
// This is awful but we need to convert our wcstring_list_t to a <wchar_t **> that can be passed
|
||||
// to w.wgetopt_long(). Furthermore, because we're dynamically allocating the array of pointers
|
||||
// we need to ensure the memory for the data structure is freed when we leave this scope.
|
||||
null_terminated_array_t<wchar_t> argv_container(args);
|
||||
auto argv = (wchar_t **)argv_container.get();
|
||||
|
||||
int opt;
|
||||
wgetopter_t w;
|
||||
auto long_opts = long_options.get();
|
||||
while ((opt = w.wgetopt_long(argc, argv, short_options.c_str(), long_opts, NULL)) != -1) {
|
||||
switch (opt) {
|
||||
case ':': {
|
||||
builtin_missing_argument(parser, streams, cmd, argv[w.woptind - 1]);
|
||||
return STATUS_INVALID_ARGS;
|
||||
}
|
||||
case '?': {
|
||||
builtin_unknown_option(parser, streams, cmd, argv[w.woptind - 1]);
|
||||
return STATUS_INVALID_ARGS;
|
||||
}
|
||||
default: {
|
||||
auto found = opts.options.find(opt);
|
||||
assert(found != opts.options.end());
|
||||
|
||||
option_spec_t *opt_spec = found->second;
|
||||
opt_spec->num_seen++;
|
||||
if (opt_spec->num_allowed == 0) {
|
||||
assert(!w.woptarg);
|
||||
} else if (opt_spec->num_allowed == -1 || opt_spec->num_allowed == 1) {
|
||||
// This is subtle. We're depending on `wgetopt_long()` to report that a
|
||||
// mandatory value is missing if `opt_spec->num_allowed == 1` and thus return
|
||||
// ':' so that we don't take this branch if the mandatory arg is missing.
|
||||
// Otherwise we can treat the optional and mandatory arg cases the same. That
|
||||
// is, store the arg as the only value for the flag. Even if we've seen earlier
|
||||
// instances of the flag.
|
||||
opt_spec->vals.clear();
|
||||
if (w.woptarg) {
|
||||
opt_spec->vals.push_back(w.woptarg);
|
||||
}
|
||||
} else {
|
||||
assert(w.woptarg);
|
||||
opt_spec->vals.push_back(w.woptarg);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Add a count for how many times we saw each boolean flag but only if we saw the flag at least
|
||||
// once.
|
||||
for (auto it : opts.options) {
|
||||
auto opt_spec = it.second;
|
||||
if (opt_spec->num_allowed != 0 || opt_spec->num_seen == 0) continue;
|
||||
wchar_t count[20];
|
||||
swprintf(count, sizeof count / sizeof count[0], L"%d", opt_spec->num_seen);
|
||||
opt_spec->vals.push_back(wcstring(count));
|
||||
}
|
||||
|
||||
for (int i = w.woptind; argv[i]; i++) opts.argv.push_back(argv[i]);
|
||||
return STATUS_CMD_OK;
|
||||
}
|
||||
|
||||
/// The argparse builtin. This is explicitly not compatible with the BSD or GNU version of this
|
||||
/// command. That's because fish doesn't have the weird quoting problems of POSIX shells. So we
|
||||
/// don't need to support flags like `--unquoted`. Similarly we don't want to support introducing
|
||||
/// long options with a single dash so we don't support the `--alternative` flag. That `getopt` is
|
||||
/// an external command also means its output has to be in a form that can be eval'd. Because our
|
||||
/// version is a builtin it can directly set variables local to the current scope (e.g., a
|
||||
/// function). It doesn't need to write anything to stdout that then needs to be eval'd.
|
||||
int builtin_argparse(parser_t &parser, io_streams_t &streams, wchar_t **argv) {
|
||||
const wchar_t *cmd = argv[0];
|
||||
int argc = builtin_count_args(argv);
|
||||
argparse_cmd_opts_t opts;
|
||||
|
||||
int optind;
|
||||
int retval = parse_cmd_opts(opts, &optind, argc, argv, parser, streams);
|
||||
if (retval != STATUS_CMD_OK) return retval;
|
||||
|
||||
if (opts.print_help) {
|
||||
builtin_print_help(parser, streams, cmd, streams.out);
|
||||
return STATUS_CMD_OK;
|
||||
}
|
||||
|
||||
if (optind == argc) {
|
||||
streams.err.append_format(BUILTIN_ERR_ARG_COUNT1, cmd, 2, 0);
|
||||
return STATUS_INVALID_ARGS;
|
||||
}
|
||||
|
||||
while (true) {
|
||||
if (wcscmp(L"--", argv[optind]) == 0) {
|
||||
optind++;
|
||||
break;
|
||||
}
|
||||
|
||||
if (!parse_option_spec(opts, argv[optind], streams)) {
|
||||
return STATUS_CMD_ERROR;
|
||||
}
|
||||
|
||||
if (++optind == argc) {
|
||||
streams.err.append_format(BUILTIN_ERR_ARG_COUNT1, cmd, 2, 0);
|
||||
return STATUS_INVALID_ARGS;
|
||||
}
|
||||
}
|
||||
|
||||
#if 0
|
||||
auto stats = mstats();
|
||||
fwprintf(stderr, L"WTF %d bytes_total %lu chunks_used %lu bytes_used %lu chunks_free %lu bytes_free %lu\n", __LINE__, stats.bytes_total, stats.chunks_used, stats.bytes_used, stats.chunks_free);
|
||||
#endif
|
||||
|
||||
wcstring_list_t args;
|
||||
args.push_back(opts.name);
|
||||
while (optind < argc) args.push_back(argv[optind++]);
|
||||
|
||||
retval = argparse_parse_args(opts, args, parser, streams);
|
||||
if (retval != STATUS_CMD_OK) return retval;
|
||||
|
||||
for (auto it : opts.options) {
|
||||
option_spec_t *opt_spec = it.second;
|
||||
if (!opt_spec->num_seen) continue;
|
||||
|
||||
wcstring var_name_prefix = L"_flag_";
|
||||
auto val = list_to_array_val(opt_spec->vals);
|
||||
env_set(var_name_prefix + opt_spec->short_flag, *val == ENV_NULL ? NULL : val->c_str(),
|
||||
ENV_LOCAL);
|
||||
if (!opt_spec->long_flag.empty()) {
|
||||
// We do a simple replacement of all non alphanum chars rather than calling
|
||||
// escape_string(long_flag, 0, STRING_STYLE_VAR).
|
||||
wcstring long_flag = opt_spec->long_flag;
|
||||
for (size_t pos = 0; pos < long_flag.size(); pos++) {
|
||||
if (!iswalnum(long_flag[pos])) long_flag[pos] = L'_';
|
||||
}
|
||||
env_set(var_name_prefix + long_flag, *val == ENV_NULL ? NULL : val->c_str(),
|
||||
ENV_LOCAL);
|
||||
}
|
||||
}
|
||||
|
||||
auto val = list_to_array_val(opts.argv);
|
||||
env_set(L"argv", *val == ENV_NULL ? NULL : val->c_str(), ENV_LOCAL);
|
||||
|
||||
#if 0
|
||||
stats = mstats();
|
||||
fwprintf(stderr, L"WTF %d bytes_total %lu chunks_used %lu bytes_used %lu chunks_free %lu bytes_free %lu\n", __LINE__, stats.bytes_total, stats.chunks_used, stats.bytes_used, stats.chunks_free);
|
||||
#endif
|
||||
|
||||
return retval;
|
||||
}
|
9
src/builtin_argparse.h
Normal file
9
src/builtin_argparse.h
Normal file
|
@ -0,0 +1,9 @@
|
|||
// Prototypes for executing builtin_getopt function.
|
||||
#ifndef FISH_BUILTIN_ARGPARSE_H
|
||||
#define FISH_BUILTIN_ARGPARSE_H
|
||||
|
||||
class parser_t;
|
||||
struct io_streams_t;
|
||||
|
||||
int builtin_argparse(parser_t &parser, io_streams_t &streams, wchar_t **argv);
|
||||
#endif
|
Loading…
Reference in New Issue
Block a user