3
\$\begingroup\$

I'm pretty new to C, started learning it less than a month ago, the only language I had previous experience was Javascript.

Anyways, I wrote a library for dealing with CLI flags. It's not a serious project by any means, I wrote it for practice and fun.

It's composed of a source file and a header file (with declarations etc..). I'll list these functions then I'll try to describe them briefly. I didn't use array subscripting as I'm trying to gain a serious understanding of how pointers work etc...

libflagutil.c:

#include "../flagutil.h" //make sure definitions match declarations and protos etc..
#include "../butil.h" //terminal text styling
#include <stdio.h>
#include <stdbool.h>
#include <string.h>

bool flag_present(int argc, char **argv, const char *flag) {
    /* a single flag is defined as present IF:
        - an arg that exactly matches the provided `flag` string is found
        - an arg that exactly matches the provided `flag` string BUT includes an `=` (and optionally somed data)
          after it is found
    */
    bool present = false;
    for(char **cur_flag = argv; cur_flag < argv + argc; cur_flag++) {
        if(strncmp(flag, *cur_flag, strlen(flag)) == 0 && (*(*cur_flag + strlen(flag)) == '=' || *(*cur_flag + strlen(flag)) == '\0')) {
            present = true;
        }
    }

    return present;
}

const Flag *flag_get(int argc, char **argv, const char *flag) {
    /* gets the value of the provided flag. flag must begin with 2 consecutive hyphens (`--`)
       - value of the flag may be provided within the flag itself by providing an `=` and some value after it
       - OR it may be provided in an argument after the said flag
       - the function looks for the value in that order  
       - doesn't check if the flag itself exists. use flag_present prior to calling
         this function.
    */
    if(*flag != '-' || !(*(flag + 1))) {
        printf("Invalid flag structure for flag " bold("%s") " passed to get_flag.\n", flag);
        return NULL;
    }

    static Flag internal; //static buffer of last-fetched flag

    for(char **cur_flag = argv; cur_flag < argv + argc; cur_flag++) {
        if(strncmp(flag, *cur_flag, strlen(flag)) == 0) {
            char *char_after_flag = *cur_flag + strlen(flag);
            if(*char_after_flag == '=' && *(char_after_flag + 1) != '\0') {
                internal.data = char_after_flag + 1;
                internal.location = (const char **) cur_flag;
                strncpy(internal.name, flag, strlen(flag) + 1);
            } else if(cur_flag + 1 && **(cur_flag + 1) != '-') {
                internal.data = *(cur_flag + 1);
                internal.location = (const char **) cur_flag;
                strncpy(internal.name, flag, strlen(flag) + 1);
            }
        }
    }

    return &internal;

}

void flag_startswith(int argc, char **argv, const char *start_string, Flag *storage) {
    /* assumes that storage is large enough to accomodate all valid flags */
    //TODO: handle `=` within flag_string, for now function doesn't work with those flags
    for(char **flag = argv; flag < argv + argc; flag++) {
        if(**flag != '-') {
            continue;
        } //First char isn't a `-`, skip arg


        if(*(*flag + 1) == '-') {
            if(strncmp(*flag + 2, start_string, strlen(start_string)) == 0) {
                char flag_string[2 + strlen(*flag + 2) + 1];
                strcpy(flag_string, "--");
                strcpy(flag_string + 2, *flag + 2);

                *storage = *f_get(flag_string);
            } 
        } else {
            if(strncmp(*flag + 1, start_string, strlen(start_string)) == 0) {
                char flag_string[1 + strlen(*flag + 1) + 1];
                strcpy(flag_string, "-");
                strcpy(flag_string + 1, *flag + 1);

                *storage = *f_get(flag_string);
            }
        }

        storage++;
    }
}

char *flag_nextarg(signed int argc, char **argv) {
    //oversimplify fetching args from argv... (kinda useless)
    static size_t counter = 0;
    return *(argv + (counter++));
}

flagutil.h:

#include <stdio.h>
#include <stdbool.h>
#include <string.h>
#include "./butil.h"

#pragma once

/*define these yourself if you'd like to use short version but use non-"standard"
  names for main's 2 args 
*/
#ifndef FLAGUTIL_ARGC_NAME
    #define FLAGUTIL_ARGC_NAME argc
#endif

#ifndef FLAGUTIL_ARGV_NAME
    #define FLAGUTIL_ARGV_NAME argv
#endif

/* expose functions as short versions taking less arguments, for easier usage */

#define f_present(s)       flag_present(FLAGUTIL_ARGC_NAME, FLAGUTIL_ARGV_NAME, s)
#define f_get(s)           flag_get(FLAGUTIL_ARGC_NAME, FLAGUTIL_ARGV_NAME, s)
#define f_startswith(s, g) flag_startswith(FLAGUTIL_ARGC_NAME, FLAGUTIL_ARGV_NAME, s, g);
#define f_nextarg()        flag_nextarg(FLAGUTIL_ARGC_NAME, FLAGUTIL_ARGV_NAME)

struct flag {
    char name[100];
    const char *data;
    const char **location; //actual pointer within argv
};

typedef struct flag Flag;

//prototypes:
bool flag_present(int, char **, const char *);
const Flag *flag_get(int, char **, const char *);
void flag_startswith(int, char **, const char *, Flag *);
char *flag_easyArg(int, char **);

Currently all of the functions accept argc and argv as their first 2 arguments, but flagutil.h exposes these functions as macros with shorter names taking only the necessary arguments, and automatically feeding argc and argv (also defined as macros for compatibility in case of non-standard parameter names inside main).

All of the functions (except flag_startswith) take flag names in the form -flagName or --flagName.

Data is passed to flags in the form of -flagName=DATA or -flagName DATA.

bool flag_present(...): Returns true if provided flag exists within argv, false otherwise.

const Flag *flag_get(...): Returns a pointer to an internal static struct which holds data about a certain flag. The actual struct is defined in flagutil.h and holds the name of the flag, its data (if it has any) and a pointer to its location inside of argv.

void flag_startswith(...): Stores struct representations of all flags that start with start_string inside of provided array.

char *flag_nextarg(...): Pretty pointless, wrote it for fun.

I realize some of these functions are unsafe, sometimes broken etc.. but so far they get the job done and I'll be looking to improve them further. I'm looking for general advice, best practices, stuff to avoid, etc... I've tested it only with GCC and linux64 and it compiles without any warnings (-Wall, -Wextra).

Please disregard the use of bil() and bold(), they're macros for styling text inside the terminal..

\$\endgroup\$
  • 2
    \$\begingroup\$ Few more things but something eye catching is that static field. Imagine you're writing a curl clone and you have struct flag* method = flag_get(argc, argv, "X"); struct flag* body = flag_get(argc, argv, "d"); Now you want to check if user is trying to do a GET with a body: if (strncmp(method->data, "GET"...) && strlen(body->data))...it fails because both method and body points to the same data. Here I'd happily accept to return newly allocated memory (leaving the caller with its ownership unless you want to accept a struct flag* flag parameter). \$\endgroup\$ – Adriano Repetti Jul 30 '18 at 11:59
  • \$\begingroup\$ @Adriano Repetti Thanks. I usually dereference the pointer returned by flag_get and store the data in a new struct: const struct flag method = *f_get(...). Iniyially I got the idea for using a static buffer from the gmtime function in time.h. Don't know if it's good practice tho. Thanks for the advice! \$\endgroup\$ – Bogdan M. Jul 30 '18 at 12:12
  • \$\begingroup\$ It's not a bad practice but it's error-prone. You force the caller to be aware it has to make a copy and the function is not reentrant nor thread-safe (not that it matters in this case). More than that you're returning flag* which is not const and anyway, even if it was, it's a const pointer to variable data. Unless strongly required (and for a cli parser you shouldn't have any strict performance requirement) it's usually better to be on the safe side (a quick search of gmtime() on SO wil give you tons of questions related to this issue...) \$\endgroup\$ – Adriano Repetti Jul 30 '18 at 12:33
  • \$\begingroup\$ Yeah I posted a question about gmtime on SO which is how I learned about it's behavior in the first place. \$\endgroup\$ – Bogdan M. Jul 30 '18 at 12:36

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Browse other questions tagged or ask your own question.