rtl_433/src/optparse.c

474 lines
12 KiB
C

/** @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 tls_param(tls_opts_t *tls_opts, char const *key, char const *val)
{
if (!tls_opts || !key || !*key)
return 1;
else if (!strcasecmp(key, "tls_cert"))
tls_opts->tls_cert = val;
else if (!strcasecmp(key, "tls_key"))
tls_opts->tls_key = val;
else if (!strcasecmp(key, "tls_ca_cert"))
tls_opts->tls_ca_cert = val;
else if (!strcasecmp(key, "tls_cipher_suites"))
tls_opts->tls_cipher_suites = val;
else if (!strcasecmp(key, "tls_server_name"))
tls_opts->tls_server_name = val;
else if (!strcasecmp(key, "tls_psk_identity"))
tls_opts->tls_psk_identity = val;
else if (!strcasecmp(key, "tls_psk_key"))
tls_opts->tls_psk_key = val;
else
return 1;
return 0;
}
int atobv(char const *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 const *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 const *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(char const *str, char const *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 const **host, char const **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(char const *str, char const *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(char const *str, char const *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
#if defined __has_attribute
#if __has_attribute(fallthrough)
__attribute__((fallthrough));
#endif
#endif
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;
}
static char *achrb(char *s, int c, int b)
{
for (; s && *s && *s != b; ++s)
if (*s == c) return (char *)s;
return NULL;
}
char *asepcb(char **stringp, char delim, char stop)
{
if (!stringp || !*stringp) return NULL;
char *s = achrb(*stringp, delim, stop);
if (s) *s++ = '\0';
char *p = *stringp;
*stringp = s;
return p;
}
int kwargs_match(char const *s, char const *key, char const **val)
{
if (!key || !*key) {
return 0; // no match
}
size_t len = strlen(key);
// check prefix match
if (strncmp(s, key, len)) {
return 0; // no match
}
s += len;
// skip whitespace after match
while (*s == ' ' || *s == '\t')
++s;
if (*s == '\0' || * s == ',') {
if (val)
*val = NULL;
return 1; // match with no arg
}
if (*s == '=') {
if (val)
*val = s + 1;
return 1; // match with arg
}
return 0; // no exact match
}
char const *kwargs_skip(char const *s)
{
// skip to the next comma if possible
while (s && *s && *s != ',')
++s;
// skip comma and whitespace if possible
while (s && *s && (*s == ',' || *s == ' ' || *s == '\t'))
++s;
return s;
}
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);
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 */