add new function to parse command line (got it from rtl_433)
This commit is contained in:
parent
2d1bf3042b
commit
7c8849756d
2 changed files with 496 additions and 0 deletions
391
src/optparse.c
Normal file
391
src/optparse.c
Normal file
|
@ -0,0 +1,391 @@
|
||||||
|
/** @file
|
||||||
|
Option parsing functions to complement getopt.
|
||||||
|
|
||||||
|
Copyright (C) 2017 Christian Zuckschwerdt
|
||||||
|
|
||||||
|
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 2 of the License, or
|
||||||
|
(at your option) any later version.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "optparse.h"
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <limits.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
int atobv(char *arg, int def)
|
||||||
|
{
|
||||||
|
if (!arg)
|
||||||
|
return def;
|
||||||
|
if (!strcasecmp(arg, "true") || !strcasecmp(arg, "yes") || !strcasecmp(arg, "on") || !strcasecmp(arg, "enable"))
|
||||||
|
return 1;
|
||||||
|
return atoi(arg);
|
||||||
|
}
|
||||||
|
|
||||||
|
int atoiv(char *arg, int def)
|
||||||
|
{
|
||||||
|
if (!arg)
|
||||||
|
return def;
|
||||||
|
char *endptr;
|
||||||
|
int val = strtol(arg, &endptr, 10);
|
||||||
|
if (arg == endptr)
|
||||||
|
return def;
|
||||||
|
return val;
|
||||||
|
}
|
||||||
|
|
||||||
|
char *arg_param(char *arg)
|
||||||
|
{
|
||||||
|
if (!arg)
|
||||||
|
return NULL;
|
||||||
|
char *p = strchr(arg, ':');
|
||||||
|
char *c = strchr(arg, ',');
|
||||||
|
if (p && (!c || p < c))
|
||||||
|
return ++p;
|
||||||
|
else if (c)
|
||||||
|
return c;
|
||||||
|
else
|
||||||
|
return p;
|
||||||
|
}
|
||||||
|
|
||||||
|
double arg_float(const char *str, const char *error_hint)
|
||||||
|
{
|
||||||
|
if (!str) {
|
||||||
|
fprintf(stderr, "%smissing number argument\n", error_hint);
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!*str) {
|
||||||
|
fprintf(stderr, "%sempty number argument\n", error_hint);
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// allow whitespace and equals char
|
||||||
|
while (*str == ' ' || *str == '=')
|
||||||
|
++str;
|
||||||
|
|
||||||
|
char *endptr;
|
||||||
|
double val = strtod(str, &endptr);
|
||||||
|
|
||||||
|
if (str == endptr) {
|
||||||
|
fprintf(stderr, "%sinvalid number argument (%s)\n", error_hint, str);
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
return val;
|
||||||
|
}
|
||||||
|
|
||||||
|
char *hostport_param(char *param, char **host, char **port)
|
||||||
|
{
|
||||||
|
if (param && *param) {
|
||||||
|
if (param[0] == '/' && param[1] == '/') {
|
||||||
|
param += 2;
|
||||||
|
}
|
||||||
|
if (*param != ':' && *param != ',') {
|
||||||
|
*host = param;
|
||||||
|
if (*param == '[') {
|
||||||
|
(*host)++;
|
||||||
|
param = strchr(param, ']');
|
||||||
|
if (param) {
|
||||||
|
*param++ = '\0';
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
fprintf(stderr, "Malformed Ipv6 address!\n");
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
char *colon = strchr(param, ':');
|
||||||
|
char *comma = strchr(param, ',');
|
||||||
|
if (colon && (!comma || colon < comma)) {
|
||||||
|
*colon++ = '\0';
|
||||||
|
*port = colon;
|
||||||
|
}
|
||||||
|
if (comma) {
|
||||||
|
*comma++ = '\0';
|
||||||
|
return comma;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t atouint32_metric(const char *str, const char *error_hint)
|
||||||
|
{
|
||||||
|
if (!str) {
|
||||||
|
fprintf(stderr, "%smissing number argument\n", error_hint);
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!*str) {
|
||||||
|
fprintf(stderr, "%sempty number argument\n", error_hint);
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
char *endptr;
|
||||||
|
double val = strtod(str, &endptr);
|
||||||
|
|
||||||
|
if (str == endptr) {
|
||||||
|
fprintf(stderr, "%sinvalid number argument (%s)\n", error_hint, str);
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (val < 0.0) {
|
||||||
|
fprintf(stderr, "%snon-negative number argument expected (%f)\n", error_hint, val);
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// allow whitespace before suffix
|
||||||
|
while (*endptr == ' ' || *endptr == '\t')
|
||||||
|
++endptr;
|
||||||
|
|
||||||
|
switch (*endptr) {
|
||||||
|
case '\0':
|
||||||
|
break;
|
||||||
|
case 'k':
|
||||||
|
case 'K':
|
||||||
|
val *= 1e3;
|
||||||
|
break;
|
||||||
|
case 'M':
|
||||||
|
case 'm':
|
||||||
|
val *= 1e6;
|
||||||
|
break;
|
||||||
|
case 'G':
|
||||||
|
case 'g':
|
||||||
|
val *= 1e9;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
fprintf(stderr, "%sunknown number suffix (%s)\n", error_hint, endptr);
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (val > UINT32_MAX) {
|
||||||
|
fprintf(stderr, "%snumber argument too big (%f)\n", error_hint, val);
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
val += 1e-5; // rounding (e.g. 4123456789.99999)
|
||||||
|
if (val - (uint32_t)val > 2e-5) {
|
||||||
|
fprintf(stderr, "%sdecimal fraction (%f) did you forget k, M, or G suffix?\n", error_hint, val - (uint32_t)val);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (uint32_t)val;
|
||||||
|
}
|
||||||
|
|
||||||
|
int atoi_time(const char *str, const char *error_hint)
|
||||||
|
{
|
||||||
|
if (!str) {
|
||||||
|
fprintf(stderr, "%smissing time argument\n", error_hint);
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!*str) {
|
||||||
|
fprintf(stderr, "%sempty time argument\n", error_hint);
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
char *endptr = NULL;
|
||||||
|
double val = 0.0;
|
||||||
|
unsigned colons = 0;
|
||||||
|
|
||||||
|
do {
|
||||||
|
double num = strtod(str, &endptr);
|
||||||
|
|
||||||
|
if (!endptr || str == endptr) {
|
||||||
|
fprintf(stderr, "%sinvalid time argument (%s)\n", error_hint, str);
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// allow whitespace before suffix
|
||||||
|
while (*endptr == ' ' || *endptr == '\t')
|
||||||
|
++endptr;
|
||||||
|
|
||||||
|
switch (*endptr) {
|
||||||
|
case '\0':
|
||||||
|
if (colons == 0) {
|
||||||
|
// assume seconds
|
||||||
|
val += num;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
// intentional fallthrough
|
||||||
|
case ':':
|
||||||
|
++colons;
|
||||||
|
if (colons == 1)
|
||||||
|
val += num * 60 * 60;
|
||||||
|
else if (colons == 2)
|
||||||
|
val += num * 60;
|
||||||
|
else if (colons == 3)
|
||||||
|
val += num;
|
||||||
|
else {
|
||||||
|
fprintf(stderr, "%stoo many colons (use HH:MM[:SS]))\n", error_hint);
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
if (*endptr)
|
||||||
|
++endptr;
|
||||||
|
break;
|
||||||
|
case 's':
|
||||||
|
case 'S':
|
||||||
|
val += num;
|
||||||
|
++endptr;
|
||||||
|
break;
|
||||||
|
case 'm':
|
||||||
|
case 'M':
|
||||||
|
val += num * 60;
|
||||||
|
++endptr;
|
||||||
|
break;
|
||||||
|
case 'h':
|
||||||
|
case 'H':
|
||||||
|
val += num * 60 * 60;
|
||||||
|
++endptr;
|
||||||
|
break;
|
||||||
|
case 'd':
|
||||||
|
case 'D':
|
||||||
|
val += num * 60 * 60 * 24;
|
||||||
|
++endptr;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
fprintf(stderr, "%sunknown time suffix (%s)\n", error_hint, endptr);
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// chew up any remaining whitespace
|
||||||
|
while (*endptr == ' ' || *endptr == '\t')
|
||||||
|
++endptr;
|
||||||
|
str = endptr;
|
||||||
|
|
||||||
|
} while (*endptr);
|
||||||
|
|
||||||
|
if (val > INT_MAX || val < INT_MIN) {
|
||||||
|
fprintf(stderr, "%stime argument too big (%f)\n", error_hint, val);
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (val < 0) {
|
||||||
|
val -= 1e-5; // rounding (e.g. -4123456789.99999)
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
val += 1e-5; // rounding (e.g. 4123456789.99999)
|
||||||
|
}
|
||||||
|
if (val - (int)(val) > 2e-5) {
|
||||||
|
fprintf(stderr, "%sdecimal fraction (%f) did you forget m, or h suffix?\n", error_hint, val - (uint32_t)val);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (int)val;
|
||||||
|
}
|
||||||
|
|
||||||
|
char *asepc(char **stringp, char delim)
|
||||||
|
{
|
||||||
|
if (!stringp || !*stringp) return NULL;
|
||||||
|
char *s = strchr(*stringp, delim);
|
||||||
|
if (s) *s++ = '\0';
|
||||||
|
char *p = *stringp;
|
||||||
|
*stringp = s;
|
||||||
|
return p;
|
||||||
|
}
|
||||||
|
|
||||||
|
char *getkwargs(char **s, char **key, char **val)
|
||||||
|
{
|
||||||
|
char *v = asepc(s, ',');
|
||||||
|
char *k = asepc(&v, '=');
|
||||||
|
if (key) *key = k;
|
||||||
|
if (val) *val = v;
|
||||||
|
return k;
|
||||||
|
}
|
||||||
|
|
||||||
|
char *trim_ws(char *str)
|
||||||
|
{
|
||||||
|
if (!str || !*str)
|
||||||
|
return str;
|
||||||
|
while (*str == ' ' || *str == '\t' || *str == '\r' || *str == '\n')
|
||||||
|
++str;
|
||||||
|
char *e = str; // end pointer (last non ws)
|
||||||
|
char *p = str; // scanning pointer
|
||||||
|
while (*p) {
|
||||||
|
while (*p == ' ' || *p == '\t' || *p == '\r' || *p == '\n')
|
||||||
|
++p;
|
||||||
|
if (*p)
|
||||||
|
e = p++;
|
||||||
|
}
|
||||||
|
*++e = '\0';
|
||||||
|
return str;
|
||||||
|
}
|
||||||
|
|
||||||
|
char *remove_ws(char *str)
|
||||||
|
{
|
||||||
|
if (!str)
|
||||||
|
return str;
|
||||||
|
char *d = str; // dst pointer
|
||||||
|
char *s = str; // src pointer
|
||||||
|
while (*s) {
|
||||||
|
while (*s == ' ' || *s == '\t' || *s == '\r' || *s == '\n')
|
||||||
|
++s;
|
||||||
|
if (*s)
|
||||||
|
*d++ = *s++;
|
||||||
|
}
|
||||||
|
*d++ = '\0';
|
||||||
|
return str;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Unit testing
|
||||||
|
#ifdef _TEST
|
||||||
|
#define ASSERT_EQUALS(a,b) \
|
||||||
|
do { \
|
||||||
|
if ((a) == (b)) \
|
||||||
|
++passed; \
|
||||||
|
else { \
|
||||||
|
++failed; \
|
||||||
|
fprintf(stderr, "FAIL: %d <> %d\n", (a), (b)); \
|
||||||
|
} \
|
||||||
|
} while (0)
|
||||||
|
|
||||||
|
int main(void)
|
||||||
|
{
|
||||||
|
unsigned passed = 0;
|
||||||
|
unsigned failed = 0;
|
||||||
|
|
||||||
|
fprintf(stderr, "optparse:: atouint32_metric\n");
|
||||||
|
ASSERT_EQUALS(atouint32_metric("0", ""), 0);
|
||||||
|
ASSERT_EQUALS(atouint32_metric("1", ""), 1);
|
||||||
|
ASSERT_EQUALS(atouint32_metric("0.0", ""), 0);
|
||||||
|
ASSERT_EQUALS(atouint32_metric("1.0", ""), 1);
|
||||||
|
ASSERT_EQUALS(atouint32_metric("1.024k", ""), 1024);
|
||||||
|
ASSERT_EQUALS(atouint32_metric("433.92M", ""), 433920000);
|
||||||
|
ASSERT_EQUALS(atouint32_metric("433.94M", ""), 433940000);
|
||||||
|
ASSERT_EQUALS(atouint32_metric(" +1 G ", ""), 1000000000);
|
||||||
|
ASSERT_EQUALS(atouint32_metric("1e6", ""), 1000000);
|
||||||
|
|
||||||
|
fprintf(stderr, "optparse:: atoi_time\n");
|
||||||
|
ASSERT_EQUALS(atoi_time("0", ""), 0);
|
||||||
|
ASSERT_EQUALS(atoi_time("1", ""), 1);
|
||||||
|
ASSERT_EQUALS(atoi_time("0.0", ""), 0);
|
||||||
|
ASSERT_EQUALS(atoi_time("1.0", ""), 1);
|
||||||
|
ASSERT_EQUALS(atoi_time("1s", ""), 1);
|
||||||
|
ASSERT_EQUALS(atoi_time("2d", ""), 2 * 60 * 60 * 24);
|
||||||
|
ASSERT_EQUALS(atoi_time("2h", ""), 2 * 60 * 60);
|
||||||
|
ASSERT_EQUALS(atoi_time("2m", ""), 2 * 60);
|
||||||
|
ASSERT_EQUALS(atoi_time("2s", ""), 2);
|
||||||
|
ASSERT_EQUALS(atoi_time("2D", ""), 2 * 60 * 60 * 24);
|
||||||
|
ASSERT_EQUALS(atoi_time("2H", ""), 2 * 60 * 60);
|
||||||
|
ASSERT_EQUALS(atoi_time("2M", ""), 2 * 60);
|
||||||
|
ASSERT_EQUALS(atoi_time("2S", ""), 2);
|
||||||
|
ASSERT_EQUALS(atoi_time("2h3m4s", ""), 2 * 60 * 60 + 3 * 60 + 4);
|
||||||
|
ASSERT_EQUALS(atoi_time("2h 3m 4s", ""), 2 * 60 * 60 + 3 * 60 + 4);
|
||||||
|
ASSERT_EQUALS(atoi_time("2h3h 3m 4s 5", ""), 5 * 60 * 60 + 3 * 60 + 9);
|
||||||
|
ASSERT_EQUALS(atoi_time(" 2m ", ""), 2 * 60);
|
||||||
|
ASSERT_EQUALS(atoi_time("2 m", ""), 2 * 60);
|
||||||
|
ASSERT_EQUALS(atoi_time(" 2 m ", ""), 2 * 60);
|
||||||
|
ASSERT_EQUALS(atoi_time("-1m", ""), -60);
|
||||||
|
ASSERT_EQUALS(atoi_time("1h-15m", ""), 45 * 60);
|
||||||
|
|
||||||
|
ASSERT_EQUALS(atoi_time("2:3", ""), 2 * 60 * 60 + 3 * 60);
|
||||||
|
ASSERT_EQUALS(atoi_time("2:3:4", ""), 2 * 60 * 60 + 3 * 60 + 4);
|
||||||
|
ASSERT_EQUALS(atoi_time("02:03", ""), 2 * 60 * 60 + 3 * 60);
|
||||||
|
ASSERT_EQUALS(atoi_time("02:03:04", ""), 2 * 60 * 60 + 3 * 60 + 4);
|
||||||
|
ASSERT_EQUALS(atoi_time(" 2 : 3 ", ""), 2 * 60 * 60 + 3 * 60);
|
||||||
|
ASSERT_EQUALS(atoi_time(" 2 : 3 : 4 ", ""), 2 * 60 * 60 + 3 * 60 + 4);
|
||||||
|
|
||||||
|
fprintf(stderr, "optparse:: test (%u/%u) passed, (%u) failed.\n", passed, passed + failed, failed);
|
||||||
|
|
||||||
|
return failed;
|
||||||
|
}
|
||||||
|
#endif /* _TEST */
|
105
src/optparse.h
Normal file
105
src/optparse.h
Normal file
|
@ -0,0 +1,105 @@
|
||||||
|
/** @file
|
||||||
|
Option parsing functions to complement getopt.
|
||||||
|
|
||||||
|
Copyright (C) 2017 Christian Zuckschwerdt
|
||||||
|
|
||||||
|
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 2 of the License, or
|
||||||
|
(at your option) any later version.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef INCLUDE_OPTPARSE_H_
|
||||||
|
#define INCLUDE_OPTPARSE_H_
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
// makes strcasecmp() and strncasecmp() available when including optparse.h
|
||||||
|
#ifdef _MSC_VER
|
||||||
|
#include <string.h>
|
||||||
|
#define strcasecmp(s1,s2) _stricmp(s1,s2)
|
||||||
|
#define strncasecmp(s1,s2,n) _strnicmp(s1,s2,n)
|
||||||
|
#else
|
||||||
|
#include <strings.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/// Convert string to bool with fallback default.
|
||||||
|
/// Parses "true", "yes", "on", "enable" (not case-sensitive) to 1, atoi() otherwise.
|
||||||
|
int atobv(char *arg, int def);
|
||||||
|
|
||||||
|
/// Convert string to int with fallback default.
|
||||||
|
int atoiv(char *arg, int def);
|
||||||
|
|
||||||
|
/// Get the next colon or comma separated arg, NULL otherwise.
|
||||||
|
/// Returns string including comma if a comma is found first,
|
||||||
|
/// otherwise string after colon if found, NULL otherwise.
|
||||||
|
char *arg_param(char *arg);
|
||||||
|
|
||||||
|
/// Convert a string with optional leading equals char to a double.
|
||||||
|
///
|
||||||
|
/// Parse errors will fprintf(stderr, ...) and exit(1).
|
||||||
|
///
|
||||||
|
/// @param str character string to parse
|
||||||
|
/// @param error_hint prepended to error output
|
||||||
|
/// @return parsed number value
|
||||||
|
double arg_float(const char *str, const char *error_hint);
|
||||||
|
|
||||||
|
/// Parse param string to host and port.
|
||||||
|
/// E.g. ":514", "localhost", "[::1]", "127.0.0.1:514", "[::1]:514",
|
||||||
|
/// also "//localhost", "//localhost:514", "//:514".
|
||||||
|
/// Host or port are terminated at a comma, if found.
|
||||||
|
/// @return the remaining options
|
||||||
|
char *hostport_param(char *param, char **host, char **port);
|
||||||
|
|
||||||
|
/// Convert a string to an unsigned integer, uses strtod() and accepts
|
||||||
|
/// metric suffixes of 'k', 'M', and 'G' (also 'K', 'm', and 'g').
|
||||||
|
///
|
||||||
|
/// Parse errors will fprintf(stderr, ...) and exit(1).
|
||||||
|
///
|
||||||
|
/// @param str character string to parse
|
||||||
|
/// @param error_hint prepended to error output
|
||||||
|
/// @return parsed number value
|
||||||
|
uint32_t atouint32_metric(const char *str, const char *error_hint);
|
||||||
|
|
||||||
|
/// Convert a string to an integer, uses strtod() and accepts
|
||||||
|
/// time suffixes of 'd', 'h', 'm', and 's' (also 'D', 'H', 'M', and 'S'),
|
||||||
|
/// or the form hours:minutes[:seconds].
|
||||||
|
///
|
||||||
|
/// Parse errors will fprintf(stderr, ...) and exit(1).
|
||||||
|
///
|
||||||
|
/// @param str character string to parse
|
||||||
|
/// @param error_hint prepended to error output
|
||||||
|
/// @return parsed number value
|
||||||
|
int atoi_time(const char *str, const char *error_hint);
|
||||||
|
|
||||||
|
/// Similar to strsep.
|
||||||
|
///
|
||||||
|
/// @param[in,out] stringp String to parse inplace
|
||||||
|
/// @param delim the delimiter character
|
||||||
|
/// @return the original value of *stringp
|
||||||
|
char *asepc(char **stringp, char delim);
|
||||||
|
|
||||||
|
/// Parse a comma-separated list of key/value pairs into kwargs.
|
||||||
|
///
|
||||||
|
/// The input string will be modified and the pointer advanced.
|
||||||
|
/// The key and val pointers will be into the original string.
|
||||||
|
///
|
||||||
|
/// @param[in,out] s String of key=value pairs, separated by commas
|
||||||
|
/// @param[out] key keyword argument if found, NULL otherwise
|
||||||
|
/// @param[out] val value if found, NULL otherwise
|
||||||
|
/// @return the original value of *stringp (the keyword found)
|
||||||
|
char *getkwargs(char **s, char **key, char **val);
|
||||||
|
|
||||||
|
/// Trim left and right whitespace in string.
|
||||||
|
///
|
||||||
|
/// @param[in,out] str String to change inplace
|
||||||
|
/// @return the trimmed value of str
|
||||||
|
char *trim_ws(char *str);
|
||||||
|
|
||||||
|
/// Remove all whitespace from string.
|
||||||
|
///
|
||||||
|
/// @param[in,out] str String to change inplace
|
||||||
|
/// @return the stripped value of str
|
||||||
|
char *remove_ws(char *str);
|
||||||
|
|
||||||
|
#endif /* INCLUDE_OPTPARSE_H_ */
|
Loading…
Reference in a new issue