User Tools

Site Tools


,

Getopt but better

Основание: меня еще 10 лет назад утомили мелкие глюки и разное поведение различных libc getopt. =)

Note: не все проверки сделаны

getopt.c
/*
 * Copyright 2023 Oleg Borodin  <borodin@unix7.org>
 */
 
#include <stdbool.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <stdio.h>
 
static bool isprefixed(char* str, char* prefix) {
    if ((str == NULL) || (prefix == NULL)) {
        return false;
    }
    if (strncmp(str, prefix, strlen(prefix)) == 0) {
        return true;
    }
    return false;
}
 
static bool ismember(char elem, char* set) {
    if (set == NULL) return false;
    for(size_t i = 0; i < strlen(set); i++) {
        if (set[i] == elem) return true;
    }
    return false;
}
 
#if 0
static bool haschar(char* set, char elem) {
    if (set == NULL) return false;
    for(size_t i = 0; i < strlen(set); i++) {
        if (set[i] == elem) return true;
    }
    return false;
}
#endif
 
static char* ltrim(char* str, char* set) {
    if (str == NULL) return NULL;
    size_t pos = 0;
    size_t strsize = strlen(str);
    if (set != NULL) {
        for (pos = 0; pos < strsize; pos++) {
            if (!ismember(str[pos], set)) break;
        }
    }
    size_t memsize = strsize - pos + 1;
    char* dst = malloc(memsize);
    memset(dst, '\0', memsize);
    strcpy(dst, &(str[pos]));
    return dst;
}
 
static size_t strsplit(char* str, char*** dst, char sep, size_t max) {
    if (str == NULL) return -1;
    size_t strsize = strlen(str);
    if (dst != NULL) {
        *dst = malloc(sizeof(char*) * (max + 1));
        for (size_t i = 0; i < (max + 1); i++) {
            (*dst)[i] = NULL;
        }
    }
    size_t begin = 0;
    size_t end = 0;
    size_t num = 0;
    for (size_t i = 0; i < strsize; i++) {
        if (str[i] == sep) {
            end = i;
            size_t memsize = end - begin;
            if (dst != NULL) {
                char* word = malloc(memsize + 1);
                memset(word, '\0', memsize + 1);
                memcpy(word, &(str[begin]), end - begin);
                (*dst)[num] = word;
            }
            num++;
            begin = end + 1;
        }
        if (num == max - 1) break;
    }
    if (end < strsize + 1) {
        if (num > 1) begin = end + 1;
        end = strsize;
        if (dst != NULL) {
            size_t memsize = end - begin;
            char* word = malloc(memsize + 1);
            memset(word, '\0', memsize + 1);
            memcpy(word, &(str[begin]), end - begin);
            (*dst)[num] = word;
        }
    }
    return ++num;
}
 
static char* copystr(char* src) {
    size_t srcsize = strlen(src) + 1;
    char* dst = malloc(srcsize);
    memset(dst, '\0', srcsize);
    strcpy(dst, src);
    return dst;
}
 
static void strtolower(char* str) {
    size_t strsize = strlen(str);
    for (size_t i = 0; i < strsize; i++) {
        str[i] = tolower(str[i]);
    }
}
 
 
typedef struct {
    char*       name;
    bool        vopt;
    int         type;
    union {
        char**  sval;
        bool*   bval;
        int*    ival;
    };
} arg_t;
 
#define GETOPT_STRING   1
#define GETOPT_INTEGER  2
#define GETOPT_BOOL     3
 
 
arg_t* new_arg_string(char* name, char** val) {
    arg_t* arg = malloc(sizeof(arg_t));
    arg->name = name;
    arg->vopt = true;
    arg->sval = val;
    arg->type = GETOPT_STRING;
    return arg;
}
 
arg_t* new_arg_integer(char* name, int* val) {
    arg_t* arg = malloc(sizeof(arg_t));
    arg->name = name;
    arg->vopt = true;
    arg->ival = val;
    arg->type = GETOPT_INTEGER;
    return arg;
}
 
arg_t* new_arg_bool(char* name, bool* val) {
    arg_t* arg = malloc(sizeof(arg_t));
    arg->name = name;
    arg->vopt = false;
    arg->bval = val;
    arg->type = GETOPT_BOOL;
    return arg;
}
 
 
typedef struct {
    arg_t**     args;
    size_t      size;
    size_t      capa;
    bool        err;
    char*       errstr;
} getopt_t;
 
#define GETOPT_INITCAPA 16
 
void getopt_init(getopt_t* getopt) {
    getopt->args = malloc(sizeof(arg_t) * GETOPT_INITCAPA);
    getopt->capa = GETOPT_INITCAPA;
    getopt->size = 0;
    getopt->err = false;
    getopt->errstr = NULL;
    return;
}
 
#define RES_BIND_OK   ((int)0)
#define RES_BIND_ERR  ((int)-1)
 
int getopt_check_capa(getopt_t* getopt) {
    if (getopt->size == getopt->capa) {
        size_t newcapa = getopt->capa * 2;
        arg_t** newargs = realloc(getopt->args, newcapa);
        if (newargs == NULL) return RES_BIND_ERR;
        getopt->args = newargs;
        getopt->capa = newcapa;
    }
    return RES_BIND_OK;
}
 
 
int getopt_bind_string(getopt_t* getopt, char* name, char** val) {
    int res = 0;
    if ((res = getopt_check_capa(getopt)) != RES_BIND_OK) {
        return res;
    }
    arg_t* arg = new_arg_string(name, val);
    getopt->args[getopt->size] = arg;
    getopt->size++;
    return RES_BIND_OK;
}
 
int getopt_bind_integer(getopt_t* getopt, char* name, int* val) {
    int res = 0;
    if ((res = getopt_check_capa(getopt)) != RES_BIND_OK) {
        return res;
    }
    arg_t* arg = new_arg_integer(name, val);
    getopt->args[getopt->size] = arg;
    getopt->size++;
    return RES_BIND_OK;
}
 
 
int getopt_bind_bool(getopt_t* getopt, char* name, bool* val) {
    int res = 0;
    if ((res = getopt_check_capa(getopt)) != RES_BIND_OK) {
        return res;
    }
    arg_t* arg = new_arg_bool(name, val);
    getopt->args[getopt->size] = arg;
    getopt->size++;
    return RES_BIND_OK;
}
 
 
int getopt_parse(getopt_t* getopt, char** argv, int argc) {
 
    int shift = 0;
    while (shift < argc) {
        char* cliarg = argv[shift];
        if (isprefixed(cliarg, "--")) {
            char* scliarg = ltrim(cliarg, "-");
 
            for (size_t i = 0; i < getopt->size; i++) {
                arg_t* arg = getopt->args[i];
 
                char** subargs = NULL;
                size_t count = 2;
                size_t numsub = strsplit(scliarg, &subargs, '=', count);
 
                if (strcmp(arg->name, subargs[0]) == 0) {
                    switch(arg->type) {
                        case GETOPT_BOOL: {
                            if (numsub > 1 && arg->bval != NULL) {
                                strtolower(subargs[1]);
                                if (strcmp(subargs[1], "yes") == 0) {
                                    *(arg->bval) = true;
                                } else if (strcmp(subargs[1], "true") == 0) {
                                    *(arg->bval) = true;
                                } else if (strcmp(subargs[1], "t") == 0) {
                                    *(arg->bval) = true;
                                } else if (strcmp(subargs[1], "no") == 0) {
                                    *(arg->bval) = false;
                                } else if (strcmp(subargs[1], "false") == 0) {
                                    *(arg->bval) = false;
                                } else if (strcmp(subargs[1], "f") == 0) {
                                    *(arg->bval) = false;
                                } else {
                                    asprintf(&(getopt->errstr), "%s: invalid boolean value: %s", cliarg, subargs[1]);
                                    getopt->err = true;
                                    return -1;
                                }
                            } else {
                                *(arg->bval) = true;
                            }
                            break;
                        }
                        case GETOPT_STRING: {
                            if (numsub > 1) {
                                if (strlen(subargs[1]) == 0 && arg->vopt == true) {
                                    asprintf(&(getopt->errstr), "%s: value is necessary", cliarg);
                                    getopt->err = true;
                                    return -1;
                                }
                                *(arg->sval) = copystr(subargs[1]);
                            } else if (arg->vopt == true) {
                                asprintf(&(getopt->errstr), "%s: value is necessary", cliarg);
                                getopt->err = true;
                                return -1;
                            }
                            break;
                        }
                        case GETOPT_INTEGER: {
                            if (numsub > 1) {
                                char* endptr = NULL;
                                *(arg->ival) = (int)strtol(subargs[1], &endptr, 10);
                                if (endptr[0] != '\0') {
                                    asprintf(&(getopt->errstr), "%s: invalid integer value: %s", cliarg, subargs[1]);
                                    getopt->err = true;
                                    return -1;
                                }
                            }
                            break;
                        }
                    }
                }
                for (int i = 0; i < numsub; i++) {
                    free(subargs[i]);
                }
                free(subargs);
            }
            free(scliarg);
        }
        shift++;
    }
 
    return 0;
}
 
void getopt_destroy(getopt_t* getopt) {
    if (getopt != NULL) free(getopt->args);
    return;
}
 
int main(int argc, char **argv) {
    getopt_t getopt;
    getopt_init(&getopt);
 
    int intval = 0;
    bool boolval = false;
    char* strval = NULL;
 
    getopt_bind_integer(&getopt, "port", &intval);
    getopt_bind_bool(&getopt, "help", &boolval);
    getopt_bind_string(&getopt, "ident", &strval);
 
    int res = getopt_parse(&getopt, argv, argc);
    if (res < 0) {
        printf("error: %s\n", getopt.errstr);
    }
 
    printf("bool = %d, str = %s, int = %d\n", boolval, strval, intval);
 
    return 0;
}

Out

$ ./getopt2 --help=yes --port=12345 --ident=qwerty
bool = 1, str = qwerty, int = 12345

$ ./getopt --help=no --port=12345 --ident=qwerty
bool = 0, str = qwerty, int = 12345

$ ./getopt --help --port=12345 --ident=qwerty
bool = 1, str = qwerty, int = 12345

$ ./getopt --help --port=12345 --ident
error: --ident: value is necessary
bool = 1, str = (null), int = 12345

$ ./getopt --help=da --port=12345 --ident
error: --help=da: invalid boolean value: da
bool = 0, str = (null), int = 0

$ ./getopt --help=yes --port=x18907 --ident
error: --port=x18907: invalid integer value: x18907
bool = 1, str = (null), int = 0