Merge pull request #6533 from ArjenS-LA/feat/guest-getopt-long-only

feat(libc): implement getopt_long_only
This commit is contained in:
Adrià authored and GitHub committed 2026-10-19 06:04:23 +02:00
commit 9cc0f16f6e
2 files changed
+95 -8

No files matched your search

+28 -8
View File
@@ -38,6 +38,7 @@ constexpr int OptionalArgument = 2;
constexpr int FlagPermute = 1; constexpr int FlagPermute = 1;
constexpr int FlagAllArguments = 2; constexpr int FlagAllArguments = 2;
constexpr int FlagLongOnly = 4;
constexpr int BadCharacter = '?'; constexpr int BadCharacter = '?';
constexpr int InOrder = 1; constexpr int InOrder = 1;
@@ -112,7 +113,7 @@ void PermuteArguments(int nonOptionStart, int nonOptionEnd, int optionEnd, char*
} }
} }
int ParseLongOptions(char* const* arguments, const char* options, const GuestOption* longOptions, int* index, const char* dash) { int ParseLongOptions(char* const* arguments, const char* options, const GuestOption* longOptions, int* index, const char* dash, bool shortToo, int flags) {
char* const current = g_longPlace; char* const current = g_longPlace;
int match = -1; int match = -1;
bool exactMatch = false; bool exactMatch = false;
@@ -136,8 +137,9 @@ int ParseLongOptions(char* const* arguments, const char* options, const GuestOpt
exactMatch = true; exactMatch = true;
break; break;
} }
if (shortToo && length == 1) continue;
if (match == -1) match = i; if (match == -1) match = i;
else if (longOptions[i].hasArgument != longOptions[match].hasArgument || else if ((flags & FlagLongOnly) || longOptions[i].hasArgument != longOptions[match].hasArgument ||
longOptions[i].flag != longOptions[match].flag || longOptions[i].flag != longOptions[match].flag ||
longOptions[i].value != longOptions[match].value) longOptions[i].value != longOptions[match].value)
secondPartialMatch = true; secondPartialMatch = true;
@@ -149,6 +151,10 @@ int ParseLongOptions(char* const* arguments, const char* options, const GuestOpt
return BadCharacter; return BadCharacter;
} }
if (match == -1) { if (match == -1) {
if (shortToo) {
--optind_nid_postfix;
return -1;
}
if (PrintError(options)) Warn(std::string("unrecognized option `") + dash + current + "'"); if (PrintError(options)) Warn(std::string("unrecognized option `") + dash + current + "'");
optopt_nid_postfix = 0; optopt_nid_postfix = 0;
return BadCharacter; return BadCharacter;
@@ -237,11 +243,21 @@ int GetoptInternal(int count, char* const* arguments, const char* options, const
break; break;
} }
if (longOptions && *g_longPlace == '-' && g_longPlace != arguments[optind_nid_postfix]) { if (longOptions && g_longPlace != arguments[optind_nid_postfix] &&
++g_longPlace; (*g_longPlace == '-' || (flags & FlagLongOnly))) {
const int result = ParseLongOptions(arguments, options, longOptions, index, "--"); bool shortToo = false;
g_longPlace = g_emptyMessage; const char* dash = "-";
return result; if (*g_longPlace == '-') {
++g_longPlace;
dash = "--";
} else if (*g_longPlace != ':' && std::strchr(options, *g_longPlace)) {
shortToo = true;
}
const int result = ParseLongOptions(arguments, options, longOptions, index, dash, shortToo, flags);
if (result != -1) {
g_longPlace = g_emptyMessage;
return result;
}
} }
const int character = OptionCharacter(g_longPlace++); const int character = OptionCharacter(g_longPlace++);
@@ -265,7 +281,7 @@ int GetoptInternal(int count, char* const* arguments, const char* options, const
} }
g_longPlace = Argument(arguments, optind_nid_postfix, "getopt_long"); g_longPlace = Argument(arguments, optind_nid_postfix, "getopt_long");
} }
const int result = ParseLongOptions(arguments, options, longOptions, index, "-W "); const int result = ParseLongOptions(arguments, options, longOptions, index, "-W ", false, flags);
g_longPlace = g_emptyMessage; g_longPlace = g_emptyMessage;
return result; return result;
} }
@@ -355,4 +371,8 @@ int APS5_VABI getopt_long_nid_postfix(int count, char* const* arguments, const c
return GetoptInternal(count, arguments, options, longOptions, index, FlagPermute); return GetoptInternal(count, arguments, options, longOptions, index, FlagPermute);
} }
int APS5_VABI getopt_long_only_nid_postfix(int count, char* const* arguments, const char* options, const GuestOption* longOptions, int* index) {
return GetoptInternal(count, arguments, options, longOptions, index, FlagPermute | FlagLongOnly);
}
} }
+67
View File
@@ -17,6 +17,7 @@ struct GuestOption {
extern "C" { extern "C" {
int APS5_VABI getopt_nid_postfix(int, char* const*, const char*); int APS5_VABI getopt_nid_postfix(int, char* const*, const char*);
int APS5_VABI getopt_long_nid_postfix(int, char* const*, const char*, const GuestOption*, int*); int APS5_VABI getopt_long_nid_postfix(int, char* const*, const char*, const GuestOption*, int*);
int APS5_VABI getopt_long_only_nid_postfix(int, char* const*, const char*, const GuestOption*, int*);
int APS5_VABI setenv_nid_postfix(const char*, const char*, int); int APS5_VABI setenv_nid_postfix(const char*, const char*, int);
int APS5_VABI unsetenv_nid_postfix(const char*); int APS5_VABI unsetenv_nid_postfix(const char*);
extern int opterr_nid_postfix; extern int opterr_nid_postfix;
@@ -244,6 +245,71 @@ void ShortOptions() {
ExpectText(RunGetopt("bc", permuted), "|3", "getopt does not permute"); ExpectText(RunGetopt("bc", permuted), "|3", "getopt does not permute");
} }
void LongOnlyOptions() {
int flag = 0;
const GuestOption options[] = {
{"alpha", 0, nullptr, 100},
{"alpine", 0, nullptr, 100},
{"b", 0, nullptr, 101},
{"required", 1, nullptr, 102},
{"optional", 2, nullptr, 103},
{"flag", 0, &flag, 7},
{nullptr, 0, nullptr, 0},
};
struct Case {
const char* arguments;
const char* shortOptions;
std::vector<int> results;
std::vector<std::string> values;
int finalIndex;
};
const Case cases[] = {
{"foo -alpha --alpha -b", "ab", {100, 100, 101}, {"", "", ""}, 4},
{"foo -a -acvalue", "ac:", {'a', 'a', 'c'}, {"", "", "value"}, 3},
{"foo -required=one -req two -optional=three -optional four", "", {102, 102, 103, 103}, {"one", "two", "three", ""}, 6},
{"foo operand -alpha tail -b", "ab", {100, 101}, {"", ""}, 3},
{"foo -al --al", "a", {'?', '?'}, {"", ""}, 3},
{"foo -unknown", "", {'?'}, {""}, 2},
{"foo -required", ":", {':'}, {""}, 2},
{"foo -alpha=yes", "", {'?'}, {""}, 2},
{"foo -flag", "", {0}, {""}, 2},
{"foo -- -alpha", "", {}, {}, 2},
{"foo operand -alpha", "+", {}, {}, 1},
{"foo operand -alpha", "-", {1, 100}, {"operand", ""}, 3},
{"foo -W alpha", "W;", {100}, {""}, 3},
};
for (const auto& test : cases) {
ArgumentVector arguments(test.arguments);
Reset();
opterr_nid_postfix = 0;
for (std::size_t i = 0; i < test.results.size(); ++i) {
const int result = getopt_long_only_nid_postfix(arguments.Count(), arguments.Values(), test.shortOptions, options, nullptr);
Expect(result == test.results[i], std::string("getopt_long_only result: ") + test.arguments);
ExpectText(optarg_nid_postfix ? optarg_nid_postfix : "", test.values[i], "getopt_long_only argument");
}
Expect(getopt_long_only_nid_postfix(arguments.Count(), arguments.Values(), test.shortOptions, options, nullptr) == -1,
std::string("getopt_long_only termination: ") + test.arguments);
Expect(optind_nid_postfix == test.finalIndex, std::string("getopt_long_only optind: ") + test.arguments);
}
Expect(flag == 7, "getopt_long_only flag pointer");
{
ArgumentVector arguments("foo -al");
StderrCapture capture;
Reset();
int index = -1;
Expect(getopt_long_only_nid_postfix(arguments.Count(), arguments.Values(), "a", options, &index) == '?' &&
optopt_nid_postfix == 0 && index == -1, "ambiguous aliases keep index unchanged");
ExpectText(capture.Text(), "eboot.bin: option `-al' is ambiguous\n", "single dash diagnostic");
}
ArgumentVector arguments("foo -alpha");
Reset();
int index = -1;
Expect(getopt_long_only_nid_postfix(arguments.Count(), arguments.Values(), "a", options, &index) == 100 && index == 0,
"long-only index");
optind_nid_postfix = 0;
Expect(getopt_long_only_nid_postfix(arguments.Count(), arguments.Values(), "a", options, nullptr) == 100, "long-only restart");
}
void LongOptions() { void LongOptions() {
int flag = 0; int flag = 0;
const GuestOption options[] = { const GuestOption options[] = {
@@ -364,4 +430,5 @@ int main() {
FreeBsdGetoptLongCases(); FreeBsdGetoptLongCases();
ShortOptions(); ShortOptions();
LongOptions(); LongOptions();
LongOnlyOptions();
} }