2025-02-17 20:53:19 +01:00
|
|
|
/* Arg_parser - POSIX/GNU command-line argument parser. (C version)
|
2025-02-17 20:53:44 +01:00
|
|
|
Copyright (C) 2006-2024 Antonio Diaz Diaz.
|
2025-02-17 18:33:31 +01:00
|
|
|
|
2025-02-17 20:49:14 +01:00
|
|
|
This library is free software. Redistribution and use in source and
|
|
|
|
binary forms, with or without modification, are permitted provided
|
|
|
|
that the following conditions are met:
|
2025-02-17 20:40:18 +01:00
|
|
|
|
2025-02-17 20:49:14 +01:00
|
|
|
1. Redistributions of source code must retain the above copyright
|
|
|
|
notice, this list of conditions, and the following disclaimer.
|
2025-02-17 20:40:18 +01:00
|
|
|
|
2025-02-17 20:49:14 +01:00
|
|
|
2. Redistributions in binary form must reproduce the above copyright
|
|
|
|
notice, this list of conditions, and the following disclaimer in the
|
|
|
|
documentation and/or other materials provided with the distribution.
|
2025-02-17 18:33:31 +01:00
|
|
|
|
2025-02-17 20:49:14 +01:00
|
|
|
This library is distributed in the hope that it will be useful,
|
|
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
2025-02-17 18:33:31 +01:00
|
|
|
*/
|
|
|
|
|
|
|
|
#include <stdlib.h>
|
|
|
|
#include <string.h>
|
|
|
|
|
|
|
|
#include "carg_parser.h"
|
|
|
|
|
|
|
|
|
2025-02-17 20:05:47 +01:00
|
|
|
/* assure at least a minimum size for buffer 'buf' */
|
2025-02-17 18:33:31 +01:00
|
|
|
static void * ap_resize_buffer( void * buf, const int min_size )
|
|
|
|
{
|
|
|
|
if( buf ) buf = realloc( buf, min_size );
|
|
|
|
else buf = malloc( min_size );
|
|
|
|
return buf;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2025-02-17 20:54:18 +01:00
|
|
|
static char push_back_record( Arg_parser * const ap, const int code,
|
2025-02-17 20:51:10 +01:00
|
|
|
const char * const long_name,
|
|
|
|
const char * const argument )
|
2025-02-17 18:33:31 +01:00
|
|
|
{
|
2025-02-17 20:54:18 +01:00
|
|
|
ap_Record * p;
|
2025-02-17 19:17:21 +01:00
|
|
|
void * tmp = ap_resize_buffer( ap->data,
|
2025-02-17 20:54:18 +01:00
|
|
|
( ap->data_size + 1 ) * sizeof (ap_Record) );
|
2025-02-17 18:33:31 +01:00
|
|
|
if( !tmp ) return 0;
|
2025-02-17 20:54:18 +01:00
|
|
|
ap->data = (ap_Record *)tmp;
|
2025-02-17 18:33:31 +01:00
|
|
|
p = &(ap->data[ap->data_size]);
|
|
|
|
p->code = code;
|
2025-02-17 20:51:10 +01:00
|
|
|
if( long_name )
|
|
|
|
{
|
|
|
|
const int len = strlen( long_name );
|
|
|
|
p->parsed_name = (char *)malloc( len + 2 + 1 );
|
|
|
|
if( !p->parsed_name ) return 0;
|
|
|
|
p->parsed_name[0] = p->parsed_name[1] = '-';
|
|
|
|
strncpy( p->parsed_name + 2, long_name, len + 1 );
|
|
|
|
}
|
|
|
|
else if( code > 0 && code < 256 )
|
|
|
|
{
|
|
|
|
p->parsed_name = (char *)malloc( 2 + 1 );
|
|
|
|
if( !p->parsed_name ) return 0;
|
|
|
|
p->parsed_name[0] = '-'; p->parsed_name[1] = code; p->parsed_name[2] = 0;
|
|
|
|
}
|
|
|
|
else p->parsed_name = 0;
|
|
|
|
if( argument )
|
|
|
|
{
|
|
|
|
const int len = strlen( argument );
|
|
|
|
p->argument = (char *)malloc( len + 1 );
|
|
|
|
if( !p->argument ) { free( p->parsed_name ); return 0; }
|
|
|
|
strncpy( p->argument, argument, len + 1 );
|
|
|
|
}
|
|
|
|
else p->argument = 0;
|
2025-02-17 18:33:31 +01:00
|
|
|
++ap->data_size;
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2025-02-17 20:54:18 +01:00
|
|
|
static char add_error( Arg_parser * const ap, const char * const msg )
|
2025-02-17 18:33:31 +01:00
|
|
|
{
|
|
|
|
const int len = strlen( msg );
|
|
|
|
void * tmp = ap_resize_buffer( ap->error, ap->error_size + len + 1 );
|
|
|
|
if( !tmp ) return 0;
|
|
|
|
ap->error = (char *)tmp;
|
|
|
|
strncpy( ap->error + ap->error_size, msg, len + 1 );
|
|
|
|
ap->error_size += len;
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2025-02-17 20:54:18 +01:00
|
|
|
static void free_data( Arg_parser * const ap )
|
2025-02-17 18:33:31 +01:00
|
|
|
{
|
|
|
|
int i;
|
2025-02-17 20:51:10 +01:00
|
|
|
for( i = 0; i < ap->data_size; ++i )
|
|
|
|
{ free( ap->data[i].argument ); free( ap->data[i].parsed_name ); }
|
2025-02-17 18:33:31 +01:00
|
|
|
if( ap->data ) { free( ap->data ); ap->data = 0; }
|
|
|
|
ap->data_size = 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2025-02-17 20:51:10 +01:00
|
|
|
/* Return 0 only if out of memory. */
|
2025-02-17 20:54:18 +01:00
|
|
|
static char parse_long_option( Arg_parser * const ap,
|
2025-02-17 18:33:31 +01:00
|
|
|
const char * const opt, const char * const arg,
|
2025-02-17 20:54:18 +01:00
|
|
|
const ap_Option options[], int * const argindp )
|
2025-02-17 18:33:31 +01:00
|
|
|
{
|
2025-02-17 20:12:24 +01:00
|
|
|
unsigned len;
|
2025-02-17 20:24:33 +01:00
|
|
|
int index = -1, i;
|
2025-02-17 18:33:31 +01:00
|
|
|
char exact = 0, ambig = 0;
|
|
|
|
|
|
|
|
for( len = 0; opt[len+2] && opt[len+2] != '='; ++len ) ;
|
|
|
|
|
2025-02-17 19:14:54 +01:00
|
|
|
/* Test all long options for either exact match or abbreviated matches. */
|
2025-02-17 18:33:31 +01:00
|
|
|
for( i = 0; options[i].code != 0; ++i )
|
2025-02-17 20:51:10 +01:00
|
|
|
if( options[i].long_name &&
|
|
|
|
strncmp( options[i].long_name, &opt[2], len ) == 0 )
|
2025-02-17 18:33:31 +01:00
|
|
|
{
|
2025-02-17 20:51:10 +01:00
|
|
|
if( strlen( options[i].long_name ) == len ) /* Exact match found */
|
2025-02-17 18:33:31 +01:00
|
|
|
{ index = i; exact = 1; break; }
|
2025-02-17 19:14:54 +01:00
|
|
|
else if( index < 0 ) index = i; /* First nonexact match found */
|
2025-02-17 18:33:31 +01:00
|
|
|
else if( options[index].code != options[i].code ||
|
|
|
|
options[index].has_arg != options[i].has_arg )
|
2025-02-17 20:44:01 +01:00
|
|
|
ambig = 1; /* Second or later nonexact match found */
|
2025-02-17 18:33:31 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
if( ambig && !exact )
|
|
|
|
{
|
2025-02-17 20:05:47 +01:00
|
|
|
add_error( ap, "option '" ); add_error( ap, opt );
|
2025-02-17 18:33:31 +01:00
|
|
|
add_error( ap, "' is ambiguous" );
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
2025-02-17 19:14:54 +01:00
|
|
|
if( index < 0 ) /* nothing found */
|
2025-02-17 18:33:31 +01:00
|
|
|
{
|
2025-02-17 20:05:47 +01:00
|
|
|
add_error( ap, "unrecognized option '" ); add_error( ap, opt );
|
2025-02-17 18:33:31 +01:00
|
|
|
add_error( ap, "'" );
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
++*argindp;
|
|
|
|
|
2025-02-17 20:05:47 +01:00
|
|
|
if( opt[len+2] ) /* '--<long_option>=<argument>' syntax */
|
2025-02-17 18:33:31 +01:00
|
|
|
{
|
|
|
|
if( options[index].has_arg == ap_no )
|
|
|
|
{
|
2025-02-17 20:51:10 +01:00
|
|
|
add_error( ap, "option '--" ); add_error( ap, options[index].long_name );
|
2025-02-17 18:33:31 +01:00
|
|
|
add_error( ap, "' doesn't allow an argument" );
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
if( options[index].has_arg == ap_yes && !opt[len+3] )
|
|
|
|
{
|
2025-02-17 20:51:10 +01:00
|
|
|
add_error( ap, "option '--" ); add_error( ap, options[index].long_name );
|
2025-02-17 18:33:31 +01:00
|
|
|
add_error( ap, "' requires an argument" );
|
|
|
|
return 1;
|
|
|
|
}
|
2025-02-17 20:54:18 +01:00
|
|
|
return push_back_record( ap, options[index].code, options[index].long_name,
|
|
|
|
&opt[len+3] ); /* argument may be empty */
|
2025-02-17 18:33:31 +01:00
|
|
|
}
|
|
|
|
|
2025-02-17 20:54:18 +01:00
|
|
|
if( options[index].has_arg == ap_yes || options[index].has_arg == ap_yme )
|
2025-02-17 18:33:31 +01:00
|
|
|
{
|
2025-02-17 20:54:18 +01:00
|
|
|
if( !arg || ( options[index].has_arg == ap_yes && !arg[0] ) )
|
2025-02-17 18:33:31 +01:00
|
|
|
{
|
2025-02-17 20:51:10 +01:00
|
|
|
add_error( ap, "option '--" ); add_error( ap, options[index].long_name );
|
2025-02-17 18:33:31 +01:00
|
|
|
add_error( ap, "' requires an argument" );
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
++*argindp;
|
2025-02-17 20:54:18 +01:00
|
|
|
return push_back_record( ap, options[index].code, options[index].long_name,
|
|
|
|
arg ); /* argument may be empty */
|
2025-02-17 18:33:31 +01:00
|
|
|
}
|
|
|
|
|
2025-02-17 20:51:10 +01:00
|
|
|
return push_back_record( ap, options[index].code,
|
|
|
|
options[index].long_name, 0 );
|
2025-02-17 18:33:31 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2025-02-17 20:51:10 +01:00
|
|
|
/* Return 0 only if out of memory. */
|
2025-02-17 20:54:18 +01:00
|
|
|
static char parse_short_option( Arg_parser * const ap,
|
2025-02-17 18:33:31 +01:00
|
|
|
const char * const opt, const char * const arg,
|
2025-02-17 20:54:18 +01:00
|
|
|
const ap_Option options[], int * const argindp )
|
2025-02-17 18:33:31 +01:00
|
|
|
{
|
2025-02-17 19:14:54 +01:00
|
|
|
int cind = 1; /* character index in opt */
|
2025-02-17 18:33:31 +01:00
|
|
|
|
|
|
|
while( cind > 0 )
|
|
|
|
{
|
2025-02-17 20:24:33 +01:00
|
|
|
int index = -1, i;
|
2025-02-17 20:51:10 +01:00
|
|
|
const unsigned char c = opt[cind];
|
2025-02-17 19:14:54 +01:00
|
|
|
char code_str[2];
|
2025-02-17 20:51:10 +01:00
|
|
|
code_str[0] = c; code_str[1] = 0;
|
2025-02-17 18:33:31 +01:00
|
|
|
|
2025-02-17 20:51:10 +01:00
|
|
|
if( c != 0 )
|
2025-02-17 18:33:31 +01:00
|
|
|
for( i = 0; options[i].code; ++i )
|
2025-02-17 20:51:10 +01:00
|
|
|
if( c == options[i].code )
|
2025-02-17 18:33:31 +01:00
|
|
|
{ index = i; break; }
|
|
|
|
|
|
|
|
if( index < 0 )
|
|
|
|
{
|
2025-02-17 20:33:28 +01:00
|
|
|
add_error( ap, "invalid option -- '" ); add_error( ap, code_str );
|
|
|
|
add_error( ap, "'" );
|
2025-02-17 18:33:31 +01:00
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
2025-02-17 19:14:54 +01:00
|
|
|
if( opt[++cind] == 0 ) { ++*argindp; cind = 0; } /* opt finished */
|
2025-02-17 18:33:31 +01:00
|
|
|
|
|
|
|
if( options[index].has_arg != ap_no && cind > 0 && opt[cind] )
|
|
|
|
{
|
2025-02-17 20:51:10 +01:00
|
|
|
if( !push_back_record( ap, c, 0, &opt[cind] ) ) return 0;
|
2025-02-17 18:33:31 +01:00
|
|
|
++*argindp; cind = 0;
|
|
|
|
}
|
2025-02-17 20:54:18 +01:00
|
|
|
else if( options[index].has_arg == ap_yes || options[index].has_arg == ap_yme )
|
2025-02-17 18:33:31 +01:00
|
|
|
{
|
2025-02-17 20:54:18 +01:00
|
|
|
if( !arg || ( options[index].has_arg == ap_yes && !arg[0] ) )
|
2025-02-17 18:33:31 +01:00
|
|
|
{
|
2025-02-17 20:33:28 +01:00
|
|
|
add_error( ap, "option requires an argument -- '" );
|
|
|
|
add_error( ap, code_str ); add_error( ap, "'" );
|
2025-02-17 18:33:31 +01:00
|
|
|
return 1;
|
|
|
|
}
|
2025-02-17 20:54:18 +01:00
|
|
|
++*argindp; cind = 0; /* argument may be empty */
|
2025-02-17 20:51:10 +01:00
|
|
|
if( !push_back_record( ap, c, 0, arg ) ) return 0;
|
2025-02-17 18:33:31 +01:00
|
|
|
}
|
2025-02-17 20:51:10 +01:00
|
|
|
else if( !push_back_record( ap, c, 0, 0 ) ) return 0;
|
2025-02-17 18:33:31 +01:00
|
|
|
}
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2025-02-17 20:54:18 +01:00
|
|
|
char ap_init( Arg_parser * const ap,
|
2025-02-17 18:33:31 +01:00
|
|
|
const int argc, const char * const argv[],
|
2025-02-17 20:54:18 +01:00
|
|
|
const ap_Option options[], const char in_order )
|
2025-02-17 18:33:31 +01:00
|
|
|
{
|
2025-02-17 19:14:54 +01:00
|
|
|
const char ** non_options = 0; /* skipped non-options */
|
|
|
|
int non_options_size = 0; /* number of skipped non-options */
|
|
|
|
int argind = 1; /* index in argv */
|
2025-02-17 20:51:10 +01:00
|
|
|
char done = 0; /* false until success */
|
2025-02-17 18:33:31 +01:00
|
|
|
|
|
|
|
ap->data = 0;
|
|
|
|
ap->error = 0;
|
|
|
|
ap->data_size = 0;
|
|
|
|
ap->error_size = 0;
|
|
|
|
if( argc < 2 || !argv || !options ) return 1;
|
|
|
|
|
|
|
|
while( argind < argc )
|
|
|
|
{
|
|
|
|
const unsigned char ch1 = argv[argind][0];
|
2025-02-17 20:29:57 +01:00
|
|
|
const unsigned char ch2 = ch1 ? argv[argind][1] : 0;
|
2025-02-17 18:33:31 +01:00
|
|
|
|
2025-02-17 19:14:54 +01:00
|
|
|
if( ch1 == '-' && ch2 ) /* we found an option */
|
2025-02-17 18:33:31 +01:00
|
|
|
{
|
|
|
|
const char * const opt = argv[argind];
|
2025-02-17 20:29:57 +01:00
|
|
|
const char * const arg = ( argind + 1 < argc ) ? argv[argind+1] : 0;
|
2025-02-17 18:33:31 +01:00
|
|
|
if( ch2 == '-' )
|
|
|
|
{
|
2025-02-17 19:14:54 +01:00
|
|
|
if( !argv[argind][2] ) { ++argind; break; } /* we found "--" */
|
2025-02-17 20:51:10 +01:00
|
|
|
else if( !parse_long_option( ap, opt, arg, options, &argind ) ) goto out;
|
2025-02-17 18:33:31 +01:00
|
|
|
}
|
2025-02-17 20:51:10 +01:00
|
|
|
else if( !parse_short_option( ap, opt, arg, options, &argind ) ) goto out;
|
2025-02-17 18:33:31 +01:00
|
|
|
if( ap->error ) break;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2025-02-17 20:44:01 +01:00
|
|
|
if( in_order )
|
2025-02-17 20:51:10 +01:00
|
|
|
{ if( !push_back_record( ap, 0, 0, argv[argind++] ) ) goto out; }
|
2025-02-17 20:44:01 +01:00
|
|
|
else
|
2025-02-17 18:33:31 +01:00
|
|
|
{
|
|
|
|
void * tmp = ap_resize_buffer( non_options,
|
|
|
|
( non_options_size + 1 ) * sizeof *non_options );
|
2025-02-17 20:51:10 +01:00
|
|
|
if( !tmp ) goto out;
|
2025-02-17 18:33:31 +01:00
|
|
|
non_options = (const char **)tmp;
|
|
|
|
non_options[non_options_size++] = argv[argind++];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if( ap->error ) free_data( ap );
|
|
|
|
else
|
|
|
|
{
|
2025-02-17 20:51:10 +01:00
|
|
|
int i;
|
2025-02-17 18:33:31 +01:00
|
|
|
for( i = 0; i < non_options_size; ++i )
|
2025-02-17 20:51:10 +01:00
|
|
|
if( !push_back_record( ap, 0, 0, non_options[i] ) ) goto out;
|
2025-02-17 18:33:31 +01:00
|
|
|
while( argind < argc )
|
2025-02-17 20:51:10 +01:00
|
|
|
if( !push_back_record( ap, 0, 0, argv[argind++] ) ) goto out;
|
2025-02-17 18:33:31 +01:00
|
|
|
}
|
2025-02-17 20:51:10 +01:00
|
|
|
done = 1;
|
|
|
|
out: if( non_options ) free( non_options );
|
|
|
|
return done;
|
2025-02-17 18:33:31 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2025-02-17 20:54:18 +01:00
|
|
|
void ap_free( Arg_parser * const ap )
|
2025-02-17 18:33:31 +01:00
|
|
|
{
|
|
|
|
free_data( ap );
|
|
|
|
if( ap->error ) { free( ap->error ); ap->error = 0; }
|
|
|
|
ap->error_size = 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2025-02-17 20:54:18 +01:00
|
|
|
const char * ap_error( const Arg_parser * const ap ) { return ap->error; }
|
2025-02-17 18:33:31 +01:00
|
|
|
|
2025-02-17 20:54:18 +01:00
|
|
|
int ap_arguments( const Arg_parser * const ap ) { return ap->data_size; }
|
2025-02-17 18:33:31 +01:00
|
|
|
|
2025-02-17 20:54:18 +01:00
|
|
|
int ap_code( const Arg_parser * const ap, const int i )
|
2025-02-17 18:33:31 +01:00
|
|
|
{
|
2025-02-17 20:51:10 +01:00
|
|
|
if( i < 0 || i >= ap_arguments( ap ) ) return 0;
|
|
|
|
return ap->data[i].code;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2025-02-17 20:54:18 +01:00
|
|
|
const char * ap_parsed_name( const Arg_parser * const ap, const int i )
|
2025-02-17 20:51:10 +01:00
|
|
|
{
|
|
|
|
if( i < 0 || i >= ap_arguments( ap ) || !ap->data[i].parsed_name ) return "";
|
|
|
|
return ap->data[i].parsed_name;
|
2025-02-17 18:33:31 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2025-02-17 20:54:18 +01:00
|
|
|
const char * ap_argument( const Arg_parser * const ap, const int i )
|
2025-02-17 18:33:31 +01:00
|
|
|
{
|
2025-02-17 20:51:10 +01:00
|
|
|
if( i < 0 || i >= ap_arguments( ap ) || !ap->data[i].argument ) return "";
|
|
|
|
return ap->data[i].argument;
|
2025-02-17 18:33:31 +01:00
|
|
|
}
|