get rid of argp dependency

This commit is contained in:
John
2023-10-01 14:09:38 +02:00
parent 1b9723c96f
commit 22b6f02752
20 changed files with 740 additions and 304 deletions
+1
View File
@@ -1,6 +1,7 @@
add_definitions(-Wconversion)
set(libutils_a_SOURCES
arg.h arg.cpp
log.h log.cpp
tcpsocket.h tcpsocket.cpp
thread.h thread.cpp
+346
View File
@@ -0,0 +1,346 @@
/*
* ebusd - daemon for communication with eBUS heating systems.
* Copyright (C) 2023 John Baier <ebusd@ebusd.eu>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "lib/utils/arg.h"
#include <getopt.h>
#include <memory.h>
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
namespace ebusd {
#define isAlpha(c) (((c) >= 'a' && (c) <= 'z') || ((c) >= 'A' && (c) <= 'Z'))
void calcCounts(const argDef *argDefs, int &count, int &shortCharsCount, int &shortOptsCount) {
for (const argDef *arg = argDefs; arg && arg->help; arg++) {
if (!arg->name) {
continue;
}
count++;
if (!isAlpha(arg->key)) {
continue;
}
shortCharsCount++;
shortOptsCount++;
if (arg->valueName) {
shortOptsCount++;
if (arg->flags & af_optional) {
shortOptsCount++;
}
}
}
}
void buildOpts(const argDef *argDefs, int &count, int &shortCharsCount, int &shortOptsCount, struct option *longOpts, char *shortChars, int *shortIndexes, char *shortOpts, int argDefIdx) {
struct option *opt = longOpts+count;
for (const argDef *arg = argDefs; arg && arg->help; arg++, argDefIdx++) {
if (!arg->name) {
continue;
}
opt->name = arg->name;
opt->has_arg = arg->valueName ? ((arg->flags & af_optional) ? optional_argument : required_argument) : no_argument;
opt->flag = nullptr;
opt->val = argDefIdx;
if (isAlpha(arg->key)) {
shortChars[shortCharsCount] = (char)arg->key;
shortIndexes[shortCharsCount++] = count;
shortOpts[shortOptsCount++] = (char)arg->key;
if (arg->valueName) {
shortOpts[shortOptsCount++] = ':';
if (arg->flags & af_optional) {
shortOpts[shortOptsCount++] = ':';
}
}
}
opt++;
count++;
}
}
static const argDef endArgDef = {nullptr, 0, nullptr, 0, nullptr, 0 };
static const argDef helpArgDef = {"help", '?', nullptr, 0, "Give this help list", 0 };
static const argDef helpArgDefs[] = {
helpArgDef,
endArgDef
};
static const argDef versionArgDef = {"version", 'V', nullptr, 0, "Print program version", 0 };
static const argDef versionArgDefs[] = {
versionArgDef,
endArgDef
};
int argParse(const argParseOpt *parseOpt, int argc, char **argv, int *argIndex) {
int count = 0, shortCharsCount = 0, shortOptsCount = 0;
if (!(parseOpt->flags & af_noHelp)) {
calcCounts(helpArgDefs, count, shortCharsCount, shortOptsCount);
}
if (!(parseOpt->flags & af_noVersion)) {
calcCounts(versionArgDefs, count, shortCharsCount, shortOptsCount);
}
calcCounts(parseOpt->argDefs, count, shortCharsCount, shortOptsCount);
for (const argParseChildOpt *child = parseOpt->childOpts; child && child->argDefs; child++) {
calcCounts(child->argDefs, count, shortCharsCount, shortOptsCount);
}
struct option *longOpts = (struct option*)calloc(count+1, sizeof(struct option)); // room for EOF
char *shortChars = (char*)calloc(shortCharsCount+1, sizeof(char)); // room for \0
int *shortIndexes = (int*)calloc(shortCharsCount, sizeof(int));
char *shortOpts = (char*)calloc(2+shortOptsCount+1, sizeof(char)); // room for +, :, and \0
count = 0;
shortCharsCount = 0;
shortOptsCount = 0;
shortOpts[shortOptsCount++] = '+'; // posix mode to stop at first non-option
shortOpts[shortOptsCount++] = ':'; // return ':' for missing option
if (!(parseOpt->flags & af_noHelp)) {
buildOpts(helpArgDefs, count, shortCharsCount, shortOptsCount, longOpts, shortChars, shortIndexes, shortOpts, 0xff00);
}
if (!(parseOpt->flags & af_noVersion)) {
buildOpts(versionArgDefs, count, shortCharsCount, shortOptsCount, longOpts, shortChars, shortIndexes, shortOpts, 0xff01);
}
buildOpts(parseOpt->argDefs, count, shortCharsCount, shortOptsCount, longOpts, shortChars, shortIndexes, shortOpts, 0);
int children = 0;
for (const argParseChildOpt *child = parseOpt->childOpts; child && child->argDefs; child++) {
buildOpts(child->argDefs, count, shortCharsCount, shortOptsCount, longOpts, shortChars, shortIndexes, shortOpts, 0x100*(++children));
}
optind = 1; // setting to 0 does not work
int c = 0, longIdx = -1, ret = 0;
while ((c = getopt_long(argc, argv, shortOpts, longOpts, &longIdx)) != -1) {
if (c == '?') {
// unknown option or help
if (optopt != '?') {
ret = '!';
fprintf(stderr, "invalid argument %s\n", argv[optind - 1]);
} else {
ret = c;
}
break;
}
if (c == ':') {
// missing option
fprintf(stderr, "missing argument to %s\n", argv[optind - 1]);
ret = c;
break;
}
if (isAlpha(c)) {
// short name
int idx = (int)(strchr(shortChars, c) - shortChars);
if (idx >= 0 && idx < shortCharsCount) {
longIdx = shortIndexes[idx];
} else {
longIdx = -1;
}
} else if (c >= 0 && longIdx < 0) {
longIdx = c;
}
if (longIdx < 0 || longIdx >= count) {
ret = '!'; // error
break;
}
int val = longOpts[longIdx].val;
if (val == 0xff00) { // help
ret = '?';
break;
}
if (val == 0xff01) { // version
ret = 'V';
break;
}
const argDef *argDefs;
parse_function_t parser;
if (val & 0xff00) {
const argParseChildOpt *child = parseOpt->childOpts + ((val>>8)-1);
argDefs = child->argDefs;
parser = child->parser;
} else {
argDefs = parseOpt->argDefs;
parser = parseOpt->parser;
}
const argDef *arg = argDefs + (val & 0xff);
c = parser(arg->key, optarg, parseOpt);
if (c != 0) {
ret = c;
break;
}
}
if (ret == '?') {
argHelp(parseOpt);
} else if (argIndex && optind < argc) {
*argIndex = optind;
}
free(longOpts);
free(shortChars);
free(shortOpts);
return ret;
}
#define MIN_INDENT 18
#define MAX_INDENT 29
#define MAX_BREAK 79
void wrap(const char* str, size_t pos, size_t indent) {
const char* end = strchr(str, 0);
const char* eol = strchr(str, '\n');
char buf[MAX_BREAK + 1];
bool first = true;
while (*str && str < end) {
if (!first) {
if (indent) {
printf("%*c", (int)indent, ' ');
}
pos = indent;
}
// start from max position backwards to find a break char
size_t cnt = MAX_BREAK - pos;
if (eol && eol < str) {
eol = strchr(str, '\n');
}
if (eol && eol < str + cnt) {
// EOL is before latest possible break
cnt = eol - str;
} else if (end < str + cnt) {
cnt = end - str;
}
for (; cnt > 0; cnt--) {
char ch = str[cnt];
if (ch == ' ' || ch == '\n' || ch == 0) {
// break found
buf[0] = 0;
strncat(buf, str, cnt);
printf("%s\n", buf);
str += cnt;
if (*str) {
str++;
}
break; // restart
}
}
if (cnt == 0 && *str) {
// final
printf("%s\n", str);
break;
}
first = false;
}
}
size_t calcIndent(const argDef *argDefs) {
size_t indent = 0;
for (const argDef *arg = argDefs; arg && arg->help; arg++) {
if (!arg->name) {
continue;
}
// e.g. " -d, --device=DEV Use DEV..."
size_t length = 2 + 3 + 3 + strlen(arg->name) + 2;
if (arg->valueName) {
length += 1 + strlen(arg->valueName);
if (arg->flags & af_optional) {
length += 2;
}
}
if (length > indent) {
indent = length;
if (indent > MAX_INDENT) {
return indent;
}
}
}
return indent;
}
void printArgs(const argDef *argDefs, size_t indent) {
for (const argDef *arg = argDefs; arg && arg->help; arg++) {
if (!arg->name) {
if (*arg->help) {
printf("\n %s\n", arg->help);
} else {
printf("\n");
}
continue;
}
printf(" ");
if (isAlpha(arg->key) || arg->key == '?') {
printf("-%c,", arg->key);
} else {
printf(" ");
}
printf(" --%s", arg->name);
size_t taken = 2 + 3 + 3 + strlen(arg->name);
if (arg->valueName) {
taken += 1 + strlen(arg->valueName);
if (arg->flags & af_optional) {
printf("[=%s]", arg->valueName);
taken += 2;
} else {
printf("=%s", arg->valueName);
}
}
if (taken > indent) {
printf(" ");
wrap(arg->help, taken+1, indent);
} else {
printf("%*c", (int)(indent - taken), ' ');
wrap(arg->help, indent, indent);
}
}
}
void argHelp(const argParseOpt *parseOpt) {
size_t indent = calcIndent(parseOpt->argDefs);
if (indent < MAX_INDENT) {
for (const argParseChildOpt *child = parseOpt->childOpts; child && child->argDefs; child++) {
size_t childIndent = calcIndent(child->argDefs);
if (childIndent > indent) {
indent = childIndent;
if (indent > MAX_INDENT) {
break;
}
}
}
}
if (indent > MAX_INDENT) {
indent = MAX_INDENT;
} else if (indent < MIN_INDENT) {
indent = MIN_INDENT;
}
printf("Usage: %s [OPTION...] %s\n",
parseOpt->name,
parseOpt->positional ? parseOpt->positional : ""
);
wrap(parseOpt->help, 0, 0);
printArgs(parseOpt->argDefs, indent);
for (const argParseChildOpt *child = parseOpt->childOpts; child && child->argDefs; child++) {
printArgs(child->argDefs, indent);
}
if (!(parseOpt->flags & (af_noHelp|af_noVersion))) {
printf("\n");
if (!(parseOpt->flags & af_noHelp)) {
printArgs(helpArgDefs, indent);
}
if (!(parseOpt->flags & af_noVersion)) {
printArgs(versionArgDefs, indent);
}
}
if (parseOpt->suffix) {
printf("\n");
wrap(parseOpt->suffix, 0, 0);
}
fflush(stdout);
}
} // namespace ebusd
+97
View File
@@ -0,0 +1,97 @@
/*
* ebusd - daemon for communication with eBUS heating systems.
* Copyright (C) 2023 John Baier <ebusd@ebusd.eu>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef LIB_UTILS_ARGS_H_
#define LIB_UTILS_ARGS_H_
namespace ebusd {
/** \file lib/utils/args.h */
/** the available arg flags. */
enum ArgFlag {
af_optional = 1<<0, //!< optional argument value
af_noHelp = 1<<1, //!< do not include -?/--help option
af_noVersion = 1<<2, //!< do not include -V/--version option
};
/** Definition of a single argument. */
typedef struct argDef {
const char* name; //!< the (long) name of the argument, or nullptr for a group header
int key; //!< the argument key, also used as short name if alphabetic or the question mark
const char* valueName; //!< the optional argument value name
int flags; //!< flags for the argument, bit combination of @a ArgFlag
const char* help; //!< help text (mandatory)
int unused; //!< currently unused (kept for compatibility to argp)
} argDef;
struct argParseOpt;
/**
* Function to be called for each argument.
* @param key the argument key as defined.
* @param arg the argument value, or nullptr.
* @param parseOpt ppointer to the @a argParseOpt structure.
* @return 0 on success, non-zero otherwise.
*/
typedef int (*parse_function_t)(int key, char *arg, const struct argParseOpt *parseOpt);
/** Options for child definitions. */
typedef struct argParseChildOpt {
const argDef *argDefs; //!< pointer to the argument defintions (last one needs to have nullptr help as end sign)
parse_function_t parser; //!< parse function to use
} argParseChildOpt;
/** Options to pass to @a argParse(). */
typedef struct argParseOpt {
const argDef *argDefs; //!< pointer to the argument defintions (last one needs to have nullptr help as end sign)
parse_function_t parser; //!< parse function to use
int flags; //!< flags for the parser, bit combination of @a ArgFlag
const char* name; //!< name of the program parsed
const char* positional; //!< help text for optional positional argument
const char* help; //!< help text for the program (second line of help output)
const char* suffix; //!< optional help suffix text
const argParseChildOpt *childOpts; //!< optional child definitions
void* userArg; //!< optional user argument
} argParseOpt;
/**
* Parse the arguments given in @a argv.
* @param parseOpt pointer to the @a argParseOpt structure.
* @param argc the argument count (including the full program name in index 0).
* @param argv the argument values (including the full program name in index 0).
* @param argIndex optional pointer for storing the index to the first non-argument found in argv.
* @return 0 on success, '!' for an invalid argument value, ':' for a missing argument value,
* '?' when "-?" was given, or the result of the parse function if non-zero.
*/
int argParse(const argParseOpt *parseOpt, int argc, char **argv, int *argIndex);
/**
* Print the help text.
* @param parseOpt ppointer to the @a argParseOpt structure.
*/
void argHelp(const argParseOpt *parseOpt);
/**
* Convenience macro to print an error message to stderr.
*/
#define argParseError(argParseOpt, message) fprintf(stderr, "%s\n", message);
} // namespace ebusd
#endif // LIB_UTILS_ARGS_H_