Skip to content

Instantly share code, notes, and snippets.

@emctague
Last active August 21, 2020 15:47
Show Gist options
  • Save emctague/06b39824ef4f509d6e6e20df34d149c1 to your computer and use it in GitHub Desktop.
Save emctague/06b39824ef4f509d6e6e20df34d149c1 to your computer and use it in GitHub Desktop.
Mini C Argument Parser

margp

margp is a miniature argument parsing library for standard C.

It parses simple POSIX-style short and long flags.

It is licensed under the MIT license.

Supported flag formats

Short flags are provided in arguments taking the form of -[flags][VALUE], where [flags] is one or more characters, each of which represents some option. Some flags require a string value - once one of these flags is encountered, the rest of the argument is stored as its value ([VALUE]). If the argument just ends after that flag, the next argument will be used as its value instead.

Long flags are provided as arguments in the form --flag, where flag is the name of some option. If this particular option requires a string value, the next argument will be used as its value.

The special argument -- indicates that all further arguments should not be handled as flags.

Defining flags

To specify the flags your program accepts, you define a list of margp_t values. This must end in an margp_t with its type set to margpEnd.

The margp_t structure looks like this:

enum {
    margpEnd = 0,
    margpString,      
    margpFlag    
} type;

char shorthand;  
char *name;      
void *target;    
int setFlagTo;

type specifies the purpose of this option - the significance of this option will be explained soon! shorthand is a single character representing the shorthand flag for this option. name is a string specifying the longhand name for this option, without the --.

Depending on the given type, the meaning of the rest of the struct changes:

  • If type is margpString, this option will expect a string value. target should be a pointer to a char* variable, and when this option is encountered, margp will set that variable to the value it finds.

  • If type is margpFlag, this option will not expect a value. target should be a pointer to an int variable, and when this option is encountered, margp will set that variable to the value specified in setFlagTo.

  • If the type is margpEnd, this does not specify an actual option, but denotes the end of the list of options.

Example Flags Definition

margp_t flags[] = {
        // The `-h` or `--help` flag sets action to actHelp.
        { margpFlag, 'h', "help", &action, actHelp },
        
        // The `-v` or `--version` flag sets action to actVersion.
        { margpFlag, 'v', "version", &action, actVersion },
        
        // The name flag (short `-nNAME` or `-n NAME`, long `--name NAME`)
        // sets name to the passed NAME value.
        { margpString, 'n', "name", &name },
        
        // Signify end of margp list
        { margpEnd }
};

Parsing Arguments

To parse margp options, invoke int margp(margp_t *args, int argc, char **argv, int *remainArgc, char ***remainArgv).

  • args should contain your list of options, in the format specified above.
  • argc and argv should be your main function's argc and argv values.
  • remainArgc and remainArgv should point to an int and char** variable, respectively. These will be populated with any arguments that aren't parsed as options by margp. If margp finishes successfully, the variable you pointed remainArgv to will contain a pointer which needs to be freed when you are done with it.

After parsing, margp returns 1 on success, and 0 on failure. On failure, margp has already printed an error message.

Example Usage

/* Example invocations and output:
 *
 * example hello world
 *     DefaultName says: hello world
 *
 * example -nJoe hello world
 *.    Joe says: hello world
 *
 * example hello --name Joe there, world!
 *.    Joe says: hello there, world!
 *
 * example -hn Joe hello world
 *.    THIS IS THE HELP SCREEN
 *
 * example -nh Joe hello world
 *.    h says: Joe hello world
 *
 * example -nJoe -- hello --help world
 *.    Joe says: hello --help world
 */

#include "margp.h"
#include <stdio.h>

int main(int argc, char **argv)
{
    char *name = "DefaultName";
    enum { actDefault, actHelp, actVersion } action = actDefault;
    int remainArgc, result;
    char **remainArgv;
    
    // Parse arguments.
    result = margp((margp_t[]) {
        
        // The `-h` or `--help` flag sets action to actHelp.
        { margpFlag, 'h', "help", &action, actHelp },
        
        // The `-v` or `--version` flag sets action to actVersion.
        { margpFlag, 'v', "version", &action, actVersion },
        
        // The name flag (short `-nNAME` or `-n NAME`, long `--name NAME`)
        // sets name to the passed NAME value.
        { margpString, 'n', "name", &name },
        
        // Signify end of margp list
        { margpEnd }
        
    }, argc, argv, &remainArgc, &remainArgv);
    
    
    // Check margp results and print usage if it failed.
    if (!result) {
        printf("Usage: example [-hvnNAME] [--name name | --help | --version] [message]\n");
        return 0;
    }
    
    
    switch (action) {
        case actHelp:
            printf("THIS IS THE HELP SCREEN\n");
            break;
        case actVersion:
            printf("THIS IS THE VERSION SCREEN\n");
            break;
        case actDefault:
            printf("%s says: ", name);
            
            // Print all non-flag arguments!
            for (int i = 0; i < remainArgc; i++)
                printf("%s ", remainArgv[i]);
            
            printf("\n");
            break;
        default:
            break;
    }
    
    free(remainArgv);
}

Copyright

Licensed under the MIT license:

Copyright © 2020 Ethan McTague.

Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

/* margp
*
* Licensed under the MIT License.
*
* Copyright (C) 2020 Ethan McTague
* Permission is hereby granted, free of charge, to any person obtaining a copy of this
* software and associated documentation files (the "Software"), to deal in the Software
* without restriction, including without limitation the rights to use, copy, modify, merge,
* publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons
* to whom the Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all copies or
* substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
* INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
* PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
* FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
* OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
* DEALINGS IN THE SOFTWARE.
*/
#include "margp.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
/* Parse long-form argument at index `*index`. Ret. 1 on success. */
int parseLongFormArg(margp_t *args, int argc, char **argv, int *index) {
margp_t *found = NULL;
for (int j = 0; args[j].type; j++) {
if (!strcmp(argv[*index] + 2, args[j].name)) found = &args[j];
}
if (!found) {
fprintf(stderr, "Unknown argument: %s\n", argv[*index]);
return 0;
}
switch (found->type) {
case margpFlag:
*(int*)(found->target) = found->setFlagTo;
break;
case margpString:
if ((*index + 1) >= argc) {
fprintf(stderr, "Argument %s expects a value\n", argv[*index]);
return 0;
}
*(char**)(found->target) = argv[++(*index)];
break;
default:
fprintf(stderr, "Error in argument parsing: unknown argument type %d\n", found->type);
return 0;
}
return 1;
}
/* Parse short-form arguments at `*index`. Ret. 1 on success. */
int parseShortFormArg(margp_t *args, int argc, char **argv, int *index) {
for (char *arg = argv[*index] + 1; *arg; arg++) {
margp_t *found = NULL;
for (int j = 0; !found && args[j].type; j++) {
if (args[j].shorthand == *arg) found = &args[j];
}
if (!found) {
fprintf(stderr, "Unknown flag: -%c\n", *arg);
return 0;
}
switch (found->type) {
case margpFlag:
*(int*)(found->target) = found->setFlagTo;
continue;
case margpString:
if (*(arg + 1)) {
// Remainder of this arg is the string
*(char**)(found->target) = arg + 1;
} else {
// Next arg is the string
*(char**)(found->target) = argv[++(*index)];
}
return 1;
default:
fprintf(stderr, "Error in argument parsing: unknown argument type %d\n", found->type);
return 0;
}
}
return 1;
}
int margp(margp_t *args, int argc, char **argv, int *remainArgc, char ***remainArgv) {
*remainArgc = 0;
*remainArgv = malloc(argc * sizeof(char*));
for (int i = 1; i < argc; i++) {
// Arguments that are either just a dash, or don't start with a dash are not valid flags
if (!strcmp(argv[i], "-") || argv[i][0] != '-') {
(*remainArgv)[(*remainArgc)++] = argv[i];
continue;
}
// Double-dashes ends run of valid flags
if (!strcmp(argv[i], "--")) {
for (i++; i < argc; i++)
(*remainArgv)[(*remainArgc)++] = argv[i];
return 1;
}
// Long-form flag
if (argv[i][1] == '-') {
if (!parseLongFormArg(args, argc, argv, &i)) {
free(*remainArgv);
return 0;
}
continue;
}
// Short-form flags
if (!parseShortFormArg(args, argc, argv, &i)) {
free(*remainArgv);
return 0;
}
}
return 1;
}
/* margp
*
* Licensed under the MIT License.
*
* Copyright (C) 2020 Ethan McTague
* Permission is hereby granted, free of charge, to any person obtaining a copy of this
* software and associated documentation files (the "Software"), to deal in the Software
* without restriction, including without limitation the rights to use, copy, modify, merge,
* publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons
* to whom the Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all copies or
* substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
* INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
* PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
* FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
* OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
* DEALINGS IN THE SOFTWARE.
*/
#pragma once
/* Represents a flag that can be consumed by margp. */
typedef struct {
enum {
margpEnd = 0, /* End of flag list */
margpString, /* String argument - target should be a pointer to an unallocated `char*` variable,
which will be populated with the value provided for this arg on the command line, if any. */
margpFlag /* Boolean / flag argument - target should be a pointer to an `int`, which will be set to
the value in `setFlagTo` if this flag is found on the command line. */
} type;
char shorthand; /* Shorthand (single-char) name, e.g. `h`. */
char *name; /* Longform name, e.g. `help`. */
void *target; /* Target variable to be populated with flag value - dependent on type. */
int setFlagTo; /* If type is margpFlag, target is set to this value when the flag is encountered. */
} margp_t;
/* Parse incoming arguments from `argc` and `argv`.
*
* `args` contains a list of supported flags, ending w/ a flag of type `margpEnd`.
* `argc` and `argv` are your standard `main` argc, argv.
*
* `remainArgc` and `remainArgv` should point to an integer and unallocated `char**` pointer, respectively.
* They will be populated with any non-flag arguments that are consumed.
*
* Returns `1` on success, `0` on failure. Human-readable error messages are printed to stderr on failure.
*
* Multiple short-form arguments can be provided sequentially after a single dash.
* If one of these short-form flags expects a string value, however, the rest of the argument will be interpreted
* as its value. (e.g. if `-h` is a normal flag and `-v` expects a string, `-hvVALUE` will treat "VALUE" as the value for
* the `-v` flag.)
*
* If a short-form flag which expects a string value is provided with no value immediately after it within the same
* argument, the NEXT argument will be used as its value (e.g. `-v VALUE` instead of `-vVALUE`). This also applies
* in the above case of combining short-form flags (e.g. `-hv VALUE` instead of `-hvVALUE`.)
*
* The special flag `--` will signify the end of the run of flags - all arguments after a `--` will be interpreted
* as plain arguments and added to `remainArgv`.
*/
int margp(margp_t *args, int argc, char **argv, int *remainArgc, char ***remainArgv);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment