But hand rolling is also hard and annoying to support if you want to support long-name options and short name optionals, out of order with potential spaces in odd places that still work as most users will expect them to along with reasonable usage-instruction documetantation.
Never do this. There are a basket of edge-cases and weirdness that you'll miss in your three-minute re-implementation that aren't worth that pain you'll have using or debugging later on.
There's a better alternative: don't have complex usage in the first place. You can get all the functionality you want by having multiple executables calling a common library.
Consider netcat. You read the man page, and try to do something following its advice. Often enough, it just doesn't work. Because it's trying to cover a range of unrelated usages through a single entrypoint.
It's easy to set up multiple exes cleanly. Have a non-executable module that contains the functions that your application requires (e.g. call it lib.rb). For each distinct usage, create a separate executable script that imports lib.rb, extracts a, b and c from the for just that usage and then call lib.usage(a, b, c).
Users will find this easy to discover. You will find it easy to maintain, even compared to dedicated parsing DSLs.
We don't special-case functions to do multiple things based on complex argument cases. We just create well-defined functions. We should think of executables in the same terms.
There are really only edge cases in usage models - I've found option libraries all fairly poor, because they expose a global view of the options, rather than an iterator view, for the most part.
If args are in a structure that can be peeked and shifted, you're in a good place for context sensitive options. It's just a lexer.
My tools tend towards this syntax:
cmd [<global opt>...] subcommand [<local opt>...]
Composing the data structure for this with option libraries is often more work than iterating over a peekable stream of words.
As always this depends. In principle I agree for example I wrote pastel-cli(https://github.com/piotrmurach/pastel-cli) that doesn't use any parser to figure out arguments. However, it's super basic, for anything more complex I would look for more powerful parsers. Lexing command input as much as it is fun can be very thorny issue.