File size: 4,908 Bytes
550e603 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 | /* -*- tab-width: 4; -*- */
/* vi: set sw=2 ts=4 expandtab: */
/*
* Copyright 2017-2020 Mark Callow, github.com/MarkCallow.
* SPDX-License-Identifier: Apache-2.0
*/
/**
* @internal
* @class argparser
* @~English
*
* @brief parse a command argument list.
*
* Can't use getopt_long because it declares argv as char* const*. We
* need const char* const* when we're parsing an embedded string. Also
* Windows C library does not have getopt_long.
*
* @author Mark Callow, github.com/MarkCallow.
*/
#include <assert.h>
#include <iostream>
#include "argparser.h"
#include <regex>
using namespace std;
/*
* Construct from a string of arguments.
*/
argvector::argvector(const string& sArgs)
{
const string sep(" \t");
// std::regex does not support negative lookbehind assertions. Darn!
// RE to extract argument between string start and separator.
// - Match 0 is whole matching string including separator.
// - Match 1 is the argument.
// - Match 2 is an empty string or a backslash.
// Use negative character class as it is easiest way to handle
// utf-8 file names with non-Latin characters. $ must not be last
// in the character class or it will be taken literally not as
// end of string.
// Use raw literal to avoid excess backslashes.
regex re(R"--(^([^$\\ \t]+)(\\?)(?:[ \t]+|$))--");
size_t pos;
pos = sArgs.find_first_not_of(sep);
assert(pos != string::npos);
auto first = sArgs.begin() + pos;
auto last = sArgs.end();
bool continuation = false;
for (smatch sm; regex_search(first, last, sm, re);) {
bool needContinuation = false;
#define DEBUG_REGEX 0
#if DEBUG_REGEX
std::cout << "prefix: " << sm.prefix() << '\n';
std::cout << "suffix: " << sm.suffix() << '\n';
std::cout << "match size: " << sm.size() << '\n';
for(uint32_t i = 0; i < sm.size(); i++) {
std::cout << "match " << i << ": " << "\"" << sm.str(i) << "\"" << '\n';
}
#endif
string arg;
// All this because std::regex does not support negative
// lookbehind assertions.
arg = sm.str(1);
if (sm.str(2)[0] == '\\') {
arg += " ";
needContinuation = true;
}
if (continuation) {
this->back() += arg;
} else {
push_back(arg);
}
continuation = needContinuation;
first = sm.suffix().first;
}
}
/*
* Construct from an array of C strings
*/
argvector::argvector(int argc, const char* const* argv)
{
for (int i = 0; i < argc; i++) {
push_back(argv[i]);
}
}
/*
* Functions the same as getopt_long. See `man 3 getopt_long`.
*/
int
argparser::getopt(string* shortopts, const struct option* longopts,
int* /*longindex*/)
{
if (optind == argv.size())
return -1;
string arg;
arg = argv[optind];
if (arg[0] != '-' || (arg[0] == '-' && arg.size() == 1))
return -1;
optind++;
int retval = '?';
if (arg.compare(0, 2, "--") == 0) {
if (arg.size() == 2) return -1; // " -- " separates options and files
const struct option* opt = longopts;
while (opt->name != nullptr) {
if (arg.compare(2, string::npos, opt->name) == 0) {
retval = opt->val;
if (opt->has_arg != option::no_argument) {
if (optind >= argv.size() || (optarg = argv[optind++])[0] == '-') {
optarg.clear();
optind--;
if (opt->has_arg == option::required_argument)
retval = ':';
}
}
if (opt->flag != nullptr) {
*opt->flag = opt->val;
retval = 0;
}
break;
}
opt++;
}
} else if (shortopts != nullptr && arg.compare(0, 1, "-") == 0) {
size_t pos = shortopts->find(arg.substr(1, 1));
if (pos != string::npos) {
retval = (*shortopts)[pos];
if (pos < shortopts->length()
&& ((*shortopts)[++pos] == ':' || (*shortopts)[pos] == ';')) {
if (optind >= argv.size() || (optarg = argv[optind++])[0] == '-') {
optarg.clear();
optind--;
if ((*shortopts)[pos] == ':') // required argument
retval = ':';
}
}
}
}
return retval;
}
//================== Helper for apps' processArgs ========================
std::istream& operator >> (std::istream& stream, const skip& x)
{
std::ios_base::fmtflags f = stream.flags();
stream >> std::noskipws;
char c;
const char* text = x.text;
while (stream && *text++)
stream >> c;
stream.flags(f);
return stream;
}
|