#include "cli.hpp" #include #include #include #include #include #include #include "extern/getopt.hpp" #include "style.hpp" #include "usage.hpp" #include "util.hpp" // isBlankSpace using namespace std::literals; // Turn an at-file's contents into an argv that `getopt` can handle, appending them to `argPool`. static std::vector readAtFile(std::string const &path, std::vector &argPool, Usage usage) { std::vector argvOfs; std::filebuf file; if (!file.open(path, std::ios_base::in)) { style_Set(stderr, STYLE_RED, true); fputs("FATAL: ", stderr); style_Reset(stderr); fprintf(stderr, "Failed to open at-file \"%s\": %s\n", path.c_str(), strerror(errno)); usage.printAndExit(1); } for (;;) { int c = file.sbumpc(); // First, discard any leading blank space while (isBlankSpace(c)) { c = file.sbumpc(); } // If it's a comment, discard everything until EOL if (c == '#') { c = file.sbumpc(); while (c != EOF && !isNewline(c)) { c = file.sbumpc(); } } if (c == EOF) { return argvOfs; } else if (isNewline(c)) { continue; // Start processing the next line } // Alright, now we can parse the line do { argvOfs.push_back(argPool.size()); // Read one argument (until the next whitespace char). // We know there is one because we already have its first character in `c`. for (; c != EOF && !isWhitespace(c); c = file.sbumpc()) { argPool.push_back(c); } argPool.push_back('\0'); // Discard blank space until the next argument (candidate) while (isBlankSpace(c)) { c = file.sbumpc(); } } while (c != EOF && !isNewline(c)); // End if we reached EOL } } void cli_ParseArgs( int argc, char *argv[], char const *shortOpts, option const *longOpts, void (*parseArg)(int, char *), Usage usage ) { struct AtFileStackEntry { int parentInd; // Saved offset into parent argv std::vector argv; // This context's arg pointer vec AtFileStackEntry(int parentInd_, std::vector argv_) : parentInd(parentInd_), argv(argv_) {} }; std::vector atFileStack; int curArgc = argc; char **curArgv = argv; std::string optString = "-"s + shortOpts; // Request position arguments with a leading '-' std::vector> argPools; for (;;) { char *atFileName = nullptr; for (int ch; (ch = musl_getopt_long_only(curArgc, curArgv, optString.c_str(), longOpts)) != -1;) { if (ch == 1 && musl_optarg[0] == '@') { atFileName = &musl_optarg[1]; break; } else { parseArg(ch, musl_optarg); } } if (atFileName) { // We need to allocate a new arg pool for each at-file, so as not to invalidate pointers // previous at-files may have generated to their own arg pools. // But for the same reason, the arg pool must also outlive the at-file's stack entry! std::vector &argPool = argPools.emplace_back(); // Copy `argv[0]` for error reporting, and because option parsing skips it AtFileStackEntry &stackEntry = atFileStack.emplace_back(musl_optind, std::vector{atFileName}); // It would be nice to compute the char pointers on the fly, but reallocs don't allow // that; so we must compute the offsets after the pool is fixed std::vector offsets = readAtFile(&musl_optarg[1], argPool, usage); stackEntry.argv.reserve(offsets.size() + 2); // Avoid a bunch of reallocs for (size_t ofs : offsets) { stackEntry.argv.push_back(&argPool.data()[ofs]); } stackEntry.argv.push_back(nullptr); // Don't forget the arg vector terminator! curArgc = stackEntry.argv.size() - 1; curArgv = stackEntry.argv.data(); musl_optind = 1; // Don't use 0 because we're not scanning a different argv per se } else { if (musl_optind != curArgc) { // This happens if `--` is passed, process the remaining arg(s) as positional assume(musl_optind < curArgc); for (int i = musl_optind; i < curArgc; ++i) { parseArg(1, argv[i]); // Positional argument } } // Pop off the top stack entry, or end parsing if none if (atFileStack.empty()) { break; } // OK to restore `optind` directly, because `optpos` must be 0 right now. // (Providing 0 would be a "proper" reset, but we want to resume parsing) musl_optind = atFileStack.back().parentInd; atFileStack.pop_back(); if (atFileStack.empty()) { curArgc = argc; curArgv = argv; } else { std::vector &vec = atFileStack.back().argv; curArgc = vec.size(); curArgv = vec.data(); } } } }