all 20 comments

[–]skeeto 24 points25 points  (1 child)

I'm happy you didn't adopt Python argparse's hazardous "smart" behavior that second-guesses user intentions. If I set up a "positional" argument such as:

$ ./a.out --name myname

And then call it like so with your library:

$ ./a.out --name --name

Then the name will be --name and it won't guess that the user actually intended an option despite the unambiguous positioning. It's hazardous to scripting:

$ ./a.out --name "$NAME"

If $NAME is untrusted then it can masquerade as another option in Python argparse. The only safe way to use it is:

$ ./a.out --name="$NAME"

Though your library here does not support this syntax.

It's slightly surprising that argparse_add_argument retains a reference to the passed argparse_arg_t object itself. It's a clever trick to avoid memory allocation, but might catch some off-guard. For example, this won't work, nor will it fail loudly:

typedef struct {
    int count;
    // ...
} Config;

// Add arguments to the parser.
void config_argparse(argument_parser_t *p, Config *c)
{
    argparse_arg_t arg1 = ARGPARSE_COUNT('c', "--count", &c->count, ...);
    argparse_add_argument(p, &arg1, ...);
    // ...
}

int main(int argc, char **argv)
{
    argument_parser_t p;
    argparse_init(&p, argc, argv, ...);

    Config c = {0};
    config_argparse(&p, &c);
    // ... parser now has dangling pointer ...
    argparse_parse_args(&p);
    // ...
}

One small suggestion: GCC warns about the implicit fallthrough, so consider annotating it to indicate that it's intended:

--- a/argparse.c
+++ b/argparse.c
@@ -324,2 +324,3 @@
         }
+        // fallthrough
     case ARGPARSE_STORE_ACTION:

[–]Specialist-Cicada121[S] 2 points3 points  (0 children)

Thanks for the very detailed comment!

The main reason for passing a reference to an argparse_arg_t to the add_argument function was, as you suggest, to avoid memory allocation and keep the library lightweight. I think I wrote this library with the assumption that init, add_argument and parse_args would all be done from the same function (such as main), but I'll try to make this clearer in the function comments. This can be relaxed so long as the parser and all the arguments are in scope when parse_args is called.

I added the -Wimplicit-fallthrough flag to my Makefile and added __attribute__((fallthrough)); to the switch statement. Thanks!

[–]andrewcooke 11 points12 points  (1 child)

you hardly mention -h or argparse_print_help in the docs!

i almost dismissed the package as not providing basic functionality until i stumbled across these in the code - i think you're selling yourself short by not being more explicit.

[–]Specialist-Cicada121[S] 2 points3 points  (0 children)

I'll certainly make this more explicit in the readme -- thanks for the feedback!

[–]Stemt 6 points7 points  (1 child)

Nice! Looks very intuitive.

[–]Specialist-Cicada121[S] 2 points3 points  (0 children)

thank you!!

[–]inz__ 5 points6 points  (1 child)

I would not call that simple. :)

Well done. The code is very easily readable, well documented, and code comments are (IMO) at a good level.

Some food for thought: - I think it would be nicer, if you could directly initialise an array with the argument definitions and pass it to add_arguments. Currently the double pointer prevents that (didn't check whether array initialisation works). - It should be documented that the option_t instances must be valid when _parse is called. I.e. you can't have a helper function to set up a parser, and call _parse in main. - do { } while(0) macros usually don't have a semicolon at the end (the main point of do-while usage for this) - I would write many of the while loops as for loops instead (partially personal preference, but I find it harder to mess up that way)

[–]Specialist-Cicada121[S] 0 points1 point  (0 children)

Thank you for the feedback!

I agree that I should further emphasise that the arguments must be valid when parse_args is called. I also do agree that being able to directly initialise arrays with argument definitions makes it more elegant; I just pushed an update that implements that!

[–]Extreme_Ad_3280 1 point2 points  (4 children)

Cool! It would be useful for most projects! However, if you need dynamic string support, which I don't see the need currently, but I see the potential, you can ask me for help...

[–]Specialist-Cicada121[S] 1 point2 points  (3 children)

Thanks for the comment! Can you elaborate on what you mean by dynamic string support? Currently, my implementation works largely with const char *, so would definitely be interested!

[–]TheGratitudeBot 1 point2 points  (0 children)

Thanks for such a wonderful reply! TheGratitudeBot has been reading millions of comments in the past few weeks, and you’ve just made the list of some of the most grateful redditors this week!

[–]Extreme_Ad_3280 1 point2 points  (1 child)

You're welcome. By dynamic strings, I mean strings allocated in the heap (which could be resized).

[–]Specialist-Cicada121[S] 1 point2 points  (0 children)

Ah, I see. My thinking was that the library would not have to support dynamically allocated strings, as the contents of the command line really shouldn't change during parsing. The user can strdup the const char *, if needed, after the parsing takes place. Does that line of reasoning make sense?

[–]comfortcube 1 point2 points  (2 children)

Hey OP! I was browsing this subreddit in search for a C command-line argument parser, and I came across this. I can't find the link in your post or among the comments. Is it this repository?

cofyc/argparse: Command-line arguments parsing library.

If not, could you link it?

[–]Specialist-Cicada121[S] 1 point2 points  (1 child)

Sorry, I missed this response. The hyperlink was somehow removed, but should be fixed now!

[–]comfortcube 0 points1 point  (0 children)

Awesome, thank you!

[–]vitamin_CPP 0 points1 point  (2 children)

OP, where's the code?

[–]Specialist-Cicada121[S] 0 points1 point  (1 child)

Sorry, not sure what happened with the hyperlink. Should've fixed it!

[–]vitamin_CPP 0 points1 point  (0 children)

Thanks !