Merge commit 'c992cb4e42cc223f67ede0e48d7ff3f4947af0c6' as 'test/compatibility/C/uWebSockets'
This commit is contained in:
@ -0,0 +1,130 @@
|
||||
#include <map>
|
||||
#include <cstring>
|
||||
#include <fstream>
|
||||
#include <sstream>
|
||||
#include <iostream>
|
||||
#include <future>
|
||||
|
||||
/* This is just a very simple and inefficient demo of async responses,
|
||||
* please do roll your own variant or use a database or Node.js's async
|
||||
* features instead of this really bad demo */
|
||||
struct AsyncFileReader {
|
||||
private:
|
||||
/* The cache we have in memory for this file */
|
||||
std::string cache;
|
||||
int cacheOffset;
|
||||
bool hasCache;
|
||||
|
||||
/* The pending async file read (yes we only support one pending read) */
|
||||
std::function<void(std::string_view)> pendingReadCb;
|
||||
|
||||
int fileSize;
|
||||
std::string fileName;
|
||||
std::ifstream fin;
|
||||
uWS::Loop *loop;
|
||||
|
||||
public:
|
||||
/* Construct a demo async. file reader for fileName */
|
||||
AsyncFileReader(std::string fileName) : fileName(fileName) {
|
||||
fin.open(fileName, std::ios::binary);
|
||||
|
||||
// get fileSize
|
||||
fin.seekg(0, fin.end);
|
||||
fileSize = fin.tellg();
|
||||
|
||||
//std::cout << "File size is: " << fileSize << std::endl;
|
||||
|
||||
// cache up 1 mb!
|
||||
cache.resize(1024 * 1024);
|
||||
|
||||
//std::cout << "Caching 1 MB at offset = " << 0 << std::endl;
|
||||
fin.seekg(0, fin.beg);
|
||||
fin.read(cache.data(), cache.length());
|
||||
cacheOffset = 0;
|
||||
hasCache = true;
|
||||
|
||||
// get loop for thread
|
||||
|
||||
loop = uWS::Loop::get();
|
||||
}
|
||||
|
||||
/* Returns any data already cached for this offset */
|
||||
std::string_view peek(int offset) {
|
||||
/* Did we hit the cache? */
|
||||
if (hasCache && offset >= cacheOffset && ((offset - cacheOffset) < cache.length())) {
|
||||
/* Cache hit */
|
||||
//std::cout << "Cache hit!" << std::endl;
|
||||
|
||||
/*if (fileSize - offset < cache.length()) {
|
||||
std::cout << "LESS THAN WHAT WE HAVE!" << std::endl;
|
||||
}*/
|
||||
|
||||
int chunkSize = std::min<int>(fileSize - offset, cache.length() - offset + cacheOffset);
|
||||
|
||||
return std::string_view(cache.data() + offset - cacheOffset, chunkSize);
|
||||
} else {
|
||||
/* Cache miss */
|
||||
//std::cout << "Cache miss!" << std::endl;
|
||||
return std::string_view(nullptr, 0);
|
||||
}
|
||||
}
|
||||
|
||||
/* Asynchronously request more data at offset */
|
||||
void request(int offset, std::function<void(std::string_view)> cb) {
|
||||
|
||||
// in this case, what do we do?
|
||||
// we need to queue up this chunk request and callback!
|
||||
// if queue is full, either block or close the connection via abort!
|
||||
if (!hasCache) {
|
||||
// already requesting a chunk!
|
||||
std::cout << "ERROR: already requesting a chunk!" << std::endl;
|
||||
return;
|
||||
}
|
||||
|
||||
// disable cache
|
||||
hasCache = false;
|
||||
|
||||
std::async(std::launch::async, [this, cb, offset]() {
|
||||
//std::cout << "ASYNC Caching 1 MB at offset = " << offset << std::endl;
|
||||
|
||||
|
||||
|
||||
// den har stängts! öppna igen!
|
||||
if (!fin.good()) {
|
||||
fin.close();
|
||||
//std::cout << "Reopening fin!" << std::endl;
|
||||
fin.open(fileName, std::ios::binary);
|
||||
}
|
||||
fin.seekg(offset, fin.beg);
|
||||
fin.read(cache.data(), cache.length());
|
||||
|
||||
cacheOffset = offset;
|
||||
|
||||
loop->defer([this, cb, offset]() {
|
||||
|
||||
int chunkSize = std::min<int>(cache.length(), fileSize - offset);
|
||||
|
||||
// båda dessa sker, wtf?
|
||||
if (chunkSize == 0) {
|
||||
std::cout << "Zero size!?" << std::endl;
|
||||
}
|
||||
|
||||
if (chunkSize != cache.length()) {
|
||||
std::cout << "LESS THAN A CACHE 1 MB!" << std::endl;
|
||||
}
|
||||
|
||||
hasCache = true;
|
||||
cb(std::string_view(cache.data(), chunkSize));
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/* Abort any pending async. request */
|
||||
void abort() {
|
||||
|
||||
}
|
||||
|
||||
int getFileSize() {
|
||||
return fileSize;
|
||||
}
|
||||
};
|
@ -0,0 +1,84 @@
|
||||
#include <filesystem>
|
||||
|
||||
struct AsyncFileStreamer {
|
||||
|
||||
std::map<std::string_view, AsyncFileReader *> asyncFileReaders;
|
||||
std::string root;
|
||||
|
||||
AsyncFileStreamer(std::string root) : root(root) {
|
||||
// for all files in this path, init the map of AsyncFileReaders
|
||||
updateRootCache();
|
||||
}
|
||||
|
||||
void updateRootCache() {
|
||||
// todo: if the root folder changes, we want to reload the cache
|
||||
for(auto &p : std::filesystem::recursive_directory_iterator(root)) {
|
||||
std::string url = p.path().string().substr(root.length());
|
||||
if (url == "/index.html") {
|
||||
url = "/";
|
||||
}
|
||||
|
||||
char *key = new char[url.length()];
|
||||
memcpy(key, url.data(), url.length());
|
||||
asyncFileReaders[std::string_view(key, url.length())] = new AsyncFileReader(p.path().string());
|
||||
}
|
||||
}
|
||||
|
||||
template <bool SSL>
|
||||
void streamFile(uWS::HttpResponse<SSL> *res, std::string_view url) {
|
||||
auto it = asyncFileReaders.find(url);
|
||||
if (it == asyncFileReaders.end()) {
|
||||
std::cout << "Did not find file: " << url << std::endl;
|
||||
} else {
|
||||
streamFile(res, it->second);
|
||||
}
|
||||
}
|
||||
|
||||
template <bool SSL>
|
||||
static void streamFile(uWS::HttpResponse<SSL> *res, AsyncFileReader *asyncFileReader) {
|
||||
/* Peek from cache */
|
||||
std::string_view chunk = asyncFileReader->peek(res->getWriteOffset());
|
||||
if (!chunk.length() || res->tryEnd(chunk, asyncFileReader->getFileSize()).first) {
|
||||
/* Request new chunk */
|
||||
// todo: we need to abort this callback if peer closed!
|
||||
// this also means Loop::defer needs to support aborting (functions should embedd an atomic boolean abort or something)
|
||||
|
||||
// Loop::defer(f) -> integer
|
||||
// Loop::abort(integer)
|
||||
|
||||
// hmm? no?
|
||||
|
||||
// us_socket_up_ref eftersom vi delar ägandeskapet
|
||||
|
||||
if (chunk.length() < asyncFileReader->getFileSize()) {
|
||||
asyncFileReader->request(res->getWriteOffset(), [res, asyncFileReader](std::string_view chunk) {
|
||||
// check if we were closed in the mean time
|
||||
//if (us_socket_is_closed()) {
|
||||
// free it here
|
||||
//return;
|
||||
//}
|
||||
|
||||
/* We were aborted for some reason */
|
||||
if (!chunk.length()) {
|
||||
// todo: make sure to check for is_closed internally after all callbacks!
|
||||
res->close();
|
||||
} else {
|
||||
AsyncFileStreamer::streamFile(res, asyncFileReader);
|
||||
}
|
||||
});
|
||||
}
|
||||
} else {
|
||||
/* We failed writing everything, so let's continue when we can */
|
||||
res->onWritable([res, asyncFileReader](int offset) {
|
||||
|
||||
// här kan skiten avbrytas!
|
||||
|
||||
AsyncFileStreamer::streamFile(res, asyncFileReader);
|
||||
// todo: I don't really know what this is supposed to mean?
|
||||
return false;
|
||||
})->onAborted([]() {
|
||||
std::cout << "ABORTED!" << std::endl;
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
@ -0,0 +1,19 @@
|
||||
/* Middleware to fill out content-type */
|
||||
inline bool hasExt(std::string_view file, std::string_view ext) {
|
||||
if (ext.size() > file.size()) {
|
||||
return false;
|
||||
}
|
||||
return std::equal(ext.rbegin(), ext.rend(), file.rbegin());
|
||||
}
|
||||
|
||||
/* This should be a filter / middleware like app.use(handler) */
|
||||
template <bool SSL>
|
||||
uWS::HttpResponse<SSL> *serveFile(uWS::HttpResponse<SSL> *res, uWS::HttpRequest *req) {
|
||||
res->writeStatus(uWS::HTTP_200_OK);
|
||||
|
||||
if (hasExt(req->getUrl(), ".svg")) {
|
||||
res->writeHeader("Content-Type", "image/svg+xml");
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
407
test/compatibility/C/uWebSockets/examples/helpers/optparse.h
Normal file
407
test/compatibility/C/uWebSockets/examples/helpers/optparse.h
Normal file
@ -0,0 +1,407 @@
|
||||
/* Nicked from third-party https://github.com/skeeto/optparse 2018-09-24 */
|
||||
/* µWebSockets is not the origin of this software file */
|
||||
/* ------------------------------------------------------ */
|
||||
|
||||
/* Optparse --- portable, reentrant, embeddable, getopt-like option parser
|
||||
*
|
||||
* This is free and unencumbered software released into the public domain.
|
||||
*
|
||||
* To get the implementation, define OPTPARSE_IMPLEMENTATION.
|
||||
* Optionally define OPTPARSE_API to control the API's visibility
|
||||
* and/or linkage (static, __attribute__, __declspec).
|
||||
*
|
||||
* The POSIX getopt() option parser has three fatal flaws. These flaws
|
||||
* are solved by Optparse.
|
||||
*
|
||||
* 1) Parser state is stored entirely in global variables, some of
|
||||
* which are static and inaccessible. This means only one thread can
|
||||
* use getopt(). It also means it's not possible to recursively parse
|
||||
* nested sub-arguments while in the middle of argument parsing.
|
||||
* Optparse fixes this by storing all state on a local struct.
|
||||
*
|
||||
* 2) The POSIX standard provides no way to properly reset the parser.
|
||||
* This means for portable code that getopt() is only good for one
|
||||
* run, over one argv with one option string. It also means subcommand
|
||||
* options cannot be processed with getopt(). Most implementations
|
||||
* provide a method to reset the parser, but it's not portable.
|
||||
* Optparse provides an optparse_arg() function for stepping over
|
||||
* subcommands and continuing parsing of options with another option
|
||||
* string. The Optparse struct itself can be passed around to
|
||||
* subcommand handlers for additional subcommand option parsing. A
|
||||
* full reset can be achieved by with an additional optparse_init().
|
||||
*
|
||||
* 3) Error messages are printed to stderr. This can be disabled with
|
||||
* opterr, but the messages themselves are still inaccessible.
|
||||
* Optparse solves this by writing an error message in its errmsg
|
||||
* field. The downside to Optparse is that this error message will
|
||||
* always be in English rather than the current locale.
|
||||
*
|
||||
* Optparse should be familiar with anyone accustomed to getopt(), and
|
||||
* it could be a nearly drop-in replacement. The option string is the
|
||||
* same and the fields have the same names as the getopt() global
|
||||
* variables (optarg, optind, optopt).
|
||||
*
|
||||
* Optparse also supports GNU-style long options with optparse_long().
|
||||
* The interface is slightly different and simpler than getopt_long().
|
||||
*
|
||||
* By default, argv is permuted as it is parsed, moving non-option
|
||||
* arguments to the end. This can be disabled by setting the `permute`
|
||||
* field to 0 after initialization.
|
||||
*/
|
||||
#ifndef OPTPARSE_H
|
||||
#define OPTPARSE_H
|
||||
|
||||
#ifndef OPTPARSE_API
|
||||
# define OPTPARSE_API
|
||||
#endif
|
||||
|
||||
struct optparse {
|
||||
char **argv;
|
||||
int permute;
|
||||
int optind;
|
||||
int optopt;
|
||||
char *optarg;
|
||||
char errmsg[64];
|
||||
int subopt;
|
||||
};
|
||||
|
||||
enum optparse_argtype {
|
||||
OPTPARSE_NONE,
|
||||
OPTPARSE_REQUIRED,
|
||||
OPTPARSE_OPTIONAL
|
||||
};
|
||||
|
||||
struct optparse_long {
|
||||
const char *longname;
|
||||
int shortname;
|
||||
enum optparse_argtype argtype;
|
||||
};
|
||||
|
||||
/**
|
||||
* Initializes the parser state.
|
||||
*/
|
||||
OPTPARSE_API
|
||||
void optparse_init(struct optparse *options, char **argv);
|
||||
|
||||
/**
|
||||
* Read the next option in the argv array.
|
||||
* @param optstring a getopt()-formatted option string.
|
||||
* @return the next option character, -1 for done, or '?' for error
|
||||
*
|
||||
* Just like getopt(), a character followed by no colons means no
|
||||
* argument. One colon means the option has a required argument. Two
|
||||
* colons means the option takes an optional argument.
|
||||
*/
|
||||
OPTPARSE_API
|
||||
int optparse(struct optparse *options, const char *optstring);
|
||||
|
||||
/**
|
||||
* Handles GNU-style long options in addition to getopt() options.
|
||||
* This works a lot like GNU's getopt_long(). The last option in
|
||||
* longopts must be all zeros, marking the end of the array. The
|
||||
* longindex argument may be NULL.
|
||||
*/
|
||||
OPTPARSE_API
|
||||
int optparse_long(struct optparse *options,
|
||||
const struct optparse_long *longopts,
|
||||
int *longindex);
|
||||
|
||||
/**
|
||||
* Used for stepping over non-option arguments.
|
||||
* @return the next non-option argument, or NULL for no more arguments
|
||||
*
|
||||
* Argument parsing can continue with optparse() after using this
|
||||
* function. That would be used to parse the options for the
|
||||
* subcommand returned by optparse_arg(). This function allows you to
|
||||
* ignore the value of optind.
|
||||
*/
|
||||
OPTPARSE_API
|
||||
char *optparse_arg(struct optparse *options);
|
||||
|
||||
/* Implementation */
|
||||
#ifdef OPTPARSE_IMPLEMENTATION
|
||||
|
||||
#define OPTPARSE_MSG_INVALID "invalid option"
|
||||
#define OPTPARSE_MSG_MISSING "option requires an argument"
|
||||
#define OPTPARSE_MSG_TOOMANY "option takes no arguments"
|
||||
|
||||
static int
|
||||
optparse_error(struct optparse *options, const char *msg, const char *data)
|
||||
{
|
||||
unsigned p = 0;
|
||||
const char *sep = " -- '";
|
||||
while (*msg)
|
||||
options->errmsg[p++] = *msg++;
|
||||
while (*sep)
|
||||
options->errmsg[p++] = *sep++;
|
||||
while (p < sizeof(options->errmsg) - 2 && *data)
|
||||
options->errmsg[p++] = *data++;
|
||||
options->errmsg[p++] = '\'';
|
||||
options->errmsg[p++] = '\0';
|
||||
return '?';
|
||||
}
|
||||
|
||||
OPTPARSE_API
|
||||
void
|
||||
optparse_init(struct optparse *options, char **argv)
|
||||
{
|
||||
options->argv = argv;
|
||||
options->permute = 1;
|
||||
options->optind = 1;
|
||||
options->subopt = 0;
|
||||
options->optarg = 0;
|
||||
options->errmsg[0] = '\0';
|
||||
}
|
||||
|
||||
static int
|
||||
optparse_is_dashdash(const char *arg)
|
||||
{
|
||||
return arg != 0 && arg[0] == '-' && arg[1] == '-' && arg[2] == '\0';
|
||||
}
|
||||
|
||||
static int
|
||||
optparse_is_shortopt(const char *arg)
|
||||
{
|
||||
return arg != 0 && arg[0] == '-' && arg[1] != '-' && arg[1] != '\0';
|
||||
}
|
||||
|
||||
static int
|
||||
optparse_is_longopt(const char *arg)
|
||||
{
|
||||
return arg != 0 && arg[0] == '-' && arg[1] == '-' && arg[2] != '\0';
|
||||
}
|
||||
|
||||
static void
|
||||
optparse_permute(struct optparse *options, int index)
|
||||
{
|
||||
char *nonoption = options->argv[index];
|
||||
int i;
|
||||
for (i = index; i < options->optind - 1; i++)
|
||||
options->argv[i] = options->argv[i + 1];
|
||||
options->argv[options->optind - 1] = nonoption;
|
||||
}
|
||||
|
||||
static int
|
||||
optparse_argtype(const char *optstring, char c)
|
||||
{
|
||||
int count = OPTPARSE_NONE;
|
||||
if (c == ':')
|
||||
return -1;
|
||||
for (; *optstring && c != *optstring; optstring++);
|
||||
if (!*optstring)
|
||||
return -1;
|
||||
if (optstring[1] == ':')
|
||||
count += optstring[2] == ':' ? 2 : 1;
|
||||
return count;
|
||||
}
|
||||
|
||||
OPTPARSE_API
|
||||
int
|
||||
optparse(struct optparse *options, const char *optstring)
|
||||
{
|
||||
int type;
|
||||
char *next;
|
||||
char *option = options->argv[options->optind];
|
||||
options->errmsg[0] = '\0';
|
||||
options->optopt = 0;
|
||||
options->optarg = 0;
|
||||
if (option == 0) {
|
||||
return -1;
|
||||
} else if (optparse_is_dashdash(option)) {
|
||||
options->optind++; /* consume "--" */
|
||||
return -1;
|
||||
} else if (!optparse_is_shortopt(option)) {
|
||||
if (options->permute) {
|
||||
int index = options->optind++;
|
||||
int r = optparse(options, optstring);
|
||||
optparse_permute(options, index);
|
||||
options->optind--;
|
||||
return r;
|
||||
} else {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
option += options->subopt + 1;
|
||||
options->optopt = option[0];
|
||||
type = optparse_argtype(optstring, option[0]);
|
||||
next = options->argv[options->optind + 1];
|
||||
switch (type) {
|
||||
case -1: {
|
||||
char str[2] = {0, 0};
|
||||
str[0] = option[0];
|
||||
options->optind++;
|
||||
return optparse_error(options, OPTPARSE_MSG_INVALID, str);
|
||||
}
|
||||
case OPTPARSE_NONE:
|
||||
if (option[1]) {
|
||||
options->subopt++;
|
||||
} else {
|
||||
options->subopt = 0;
|
||||
options->optind++;
|
||||
}
|
||||
return option[0];
|
||||
case OPTPARSE_REQUIRED:
|
||||
options->subopt = 0;
|
||||
options->optind++;
|
||||
if (option[1]) {
|
||||
options->optarg = option + 1;
|
||||
} else if (next != 0) {
|
||||
options->optarg = next;
|
||||
options->optind++;
|
||||
} else {
|
||||
char str[2] = {0, 0};
|
||||
str[0] = option[0];
|
||||
options->optarg = 0;
|
||||
return optparse_error(options, OPTPARSE_MSG_MISSING, str);
|
||||
}
|
||||
return option[0];
|
||||
case OPTPARSE_OPTIONAL:
|
||||
options->subopt = 0;
|
||||
options->optind++;
|
||||
if (option[1])
|
||||
options->optarg = option + 1;
|
||||
else
|
||||
options->optarg = 0;
|
||||
return option[0];
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
OPTPARSE_API
|
||||
char *
|
||||
optparse_arg(struct optparse *options)
|
||||
{
|
||||
char *option = options->argv[options->optind];
|
||||
options->subopt = 0;
|
||||
if (option != 0)
|
||||
options->optind++;
|
||||
return option;
|
||||
}
|
||||
|
||||
static int
|
||||
optparse_longopts_end(const struct optparse_long *longopts, int i)
|
||||
{
|
||||
return !longopts[i].longname && !longopts[i].shortname;
|
||||
}
|
||||
|
||||
static void
|
||||
optparse_from_long(const struct optparse_long *longopts, char *optstring)
|
||||
{
|
||||
char *p = optstring;
|
||||
int i;
|
||||
for (i = 0; !optparse_longopts_end(longopts, i); i++) {
|
||||
if (longopts[i].shortname) {
|
||||
int a;
|
||||
*p++ = longopts[i].shortname;
|
||||
for (a = 0; a < (int)longopts[i].argtype; a++)
|
||||
*p++ = ':';
|
||||
}
|
||||
}
|
||||
*p = '\0';
|
||||
}
|
||||
|
||||
/* Unlike strcmp(), handles options containing "=". */
|
||||
static int
|
||||
optparse_longopts_match(const char *longname, const char *option)
|
||||
{
|
||||
const char *a = option, *n = longname;
|
||||
if (longname == 0)
|
||||
return 0;
|
||||
for (; *a && *n && *a != '='; a++, n++)
|
||||
if (*a != *n)
|
||||
return 0;
|
||||
return *n == '\0' && (*a == '\0' || *a == '=');
|
||||
}
|
||||
|
||||
/* Return the part after "=", or NULL. */
|
||||
static char *
|
||||
optparse_longopts_arg(char *option)
|
||||
{
|
||||
for (; *option && *option != '='; option++);
|
||||
if (*option == '=')
|
||||
return option + 1;
|
||||
else
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int
|
||||
optparse_long_fallback(struct optparse *options,
|
||||
const struct optparse_long *longopts,
|
||||
int *longindex)
|
||||
{
|
||||
int result;
|
||||
char optstring[96 * 3 + 1]; /* 96 ASCII printable characters */
|
||||
optparse_from_long(longopts, optstring);
|
||||
result = optparse(options, optstring);
|
||||
if (longindex != 0) {
|
||||
*longindex = -1;
|
||||
if (result != -1) {
|
||||
int i;
|
||||
for (i = 0; !optparse_longopts_end(longopts, i); i++)
|
||||
if (longopts[i].shortname == options->optopt)
|
||||
*longindex = i;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
OPTPARSE_API
|
||||
int
|
||||
optparse_long(struct optparse *options,
|
||||
const struct optparse_long *longopts,
|
||||
int *longindex)
|
||||
{
|
||||
int i;
|
||||
char *option = options->argv[options->optind];
|
||||
if (option == 0) {
|
||||
return -1;
|
||||
} else if (optparse_is_dashdash(option)) {
|
||||
options->optind++; /* consume "--" */
|
||||
return -1;
|
||||
} else if (optparse_is_shortopt(option)) {
|
||||
return optparse_long_fallback(options, longopts, longindex);
|
||||
} else if (!optparse_is_longopt(option)) {
|
||||
if (options->permute) {
|
||||
int index = options->optind++;
|
||||
int r = optparse_long(options, longopts, longindex);
|
||||
optparse_permute(options, index);
|
||||
options->optind--;
|
||||
return r;
|
||||
} else {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
/* Parse as long option. */
|
||||
options->errmsg[0] = '\0';
|
||||
options->optopt = 0;
|
||||
options->optarg = 0;
|
||||
option += 2; /* skip "--" */
|
||||
options->optind++;
|
||||
for (i = 0; !optparse_longopts_end(longopts, i); i++) {
|
||||
const char *name = longopts[i].longname;
|
||||
if (optparse_longopts_match(name, option)) {
|
||||
char *arg;
|
||||
if (longindex)
|
||||
*longindex = i;
|
||||
options->optopt = longopts[i].shortname;
|
||||
arg = optparse_longopts_arg(option);
|
||||
if (longopts[i].argtype == OPTPARSE_NONE && arg != 0) {
|
||||
return optparse_error(options, OPTPARSE_MSG_TOOMANY, name);
|
||||
} if (arg != 0) {
|
||||
options->optarg = arg;
|
||||
} else if (longopts[i].argtype == OPTPARSE_REQUIRED) {
|
||||
options->optarg = options->argv[options->optind];
|
||||
if (options->optarg == 0)
|
||||
return optparse_error(options, OPTPARSE_MSG_MISSING, name);
|
||||
else
|
||||
options->optind++;
|
||||
}
|
||||
return options->optopt;
|
||||
}
|
||||
}
|
||||
return optparse_error(options, OPTPARSE_MSG_INVALID, option);
|
||||
}
|
||||
|
||||
#endif /* OPTPARSE_IMPLEMENTATION */
|
||||
#endif /* OPTPARSE_H */
|
Reference in New Issue
Block a user