/* * params.c: Parameter file and command line parsing * * Written by: Stefan Frank * Ullrich Hafner * * This file is part of FIASCO (Fractal Image And Sequence COdec) * Copyright (C) 1994-2000 Ullrich Hafner */ /* * $Date: 2000/07/15 17:24:21 $ * $Author: hafner $ * $Revision: 5.2 $ * $State: Exp $ */ #define _DEFAULT_SOURCE 1 /* New name for SVID & BSD source defines */ #define _BSD_SOURCE 1 /* Make sure strdup() is in string.h and strcaseeq() is in nstring.h */ #define _XOPEN_SOURCE 500 /* Make sure strdup() is in string.h */ #include "config.h" #include #include #include /* strtod() on SUN sparc */ #include #include #include /* system or ../lib */ #include "pm_c_util.h" #include "nstring.h" #include "types.h" #include "macros.h" #include "bit-io.h" #include "misc.h" #include "fiasco.h" #include "binerror.h" #include "params.h" /***************************************************************************** prototypes *****************************************************************************/ static void read_parameter_file (param_t *params, FILE *file); static int get_parameter_index (const param_t *params, const char *search_string); static void set_parameter (param_t *parameter, const char *value); static void usage (const param_t *params, const char *progname, const char *synopsis, const char *comment, const char *non_opt_string, bool_t show_all_options, const char *sys_file_name, const char *usr_file_name); /***************************************************************************** public code *****************************************************************************/ int parseargs (param_t *usr_params, int argc, char **argv, const char *synopsis, const char *comment, const char *non_opt_string, const char *path, const char *sys_file_name, const char *usr_file_name) /* * Perform the command line parsing. * List of allowed parameters is given by 'usr_params'. * Command line and number of parameters are given by 'argv' and 'argc'. * 'synopsis' contains a brief description of the program and * 'comment' may contain some additional advice. * Initialization order of parameters: * 1.) Default values given by the param_t struct * 2.) System parameter-file ('path'/'sys_file_name') * 3.) User parameter-file ($HOME/'usr_file_name') * 4.) Command line parameters * 5.) Parameter-file forced by option -f (--config-file) * * Return value: * index in ARGV of the first ARGV-element that is not an option. * * Side effects: * the elements of ARGV are permuted * usr_params [].value is modified */ { extern int optind; /* index in ARGV of the 1st element that is not an option */ bool_t detailed_help = NO; /* NO if all parameters can be modified with short options too */ unsigned n1; /* number of user parameters */ unsigned n2; /* number of system parameters */ bool_t read_config_file = NO; /* will override command line */ param_t *params; /* array of user and system params */ param_t *sys_params; /* array of system parameters */ param_t detailed_sys_params [] = /* detailed system parameters */ { {"version", NULL, 'v', PFLAG, {0}, NULL, "Print program version number, then exit."}, {"verbose", "NUM", 'V', PINT, {0}, "1", "Set level of verbosity to `%s'."}, {"config", "FILE", 'f', PSTR, {0}, NULL, "Load `%s' to initialize parameters."}, {"info", NULL, 'h', PFLAG, {0}, NULL, "Print brief help, then exit."}, {"help", NULL, 'H', PFLAG, {0}, NULL, "Print detailed help, then exit."}, {NULL, NULL, 0, PSTR, {0}, NULL, NULL } }; param_t short_sys_params [] = /* short system parameters */ { {"version", NULL, 'v', PFLAG, {0}, NULL, "Print program version number, then exit."}, {"verbose", "NUM", 'V', PINT, {0}, "1", "Set level of verbosity to `%s'."}, {"config", "FILE", 'f', PSTR, {0}, NULL, "Load `%s' to initialize parameters."}, {"help", NULL, 'h', PFLAG, {0}, NULL, "Print this help, then exit."}, {NULL, NULL, 0, PSTR, {0}, NULL, NULL } }; char *sys_path; /* path to system config file */ sys_path = calloc (strlen (path) + strlen (sys_file_name) + 2, sizeof (char)); if (!sys_path) error ("Out of memory."); sprintf (sys_path, "%s/%s", path, sys_file_name); /* * Set parameters defaults */ { param_t *p; for (p = usr_params; p->name != NULL; p++) { set_parameter (p, p->default_value); if (p->optchar == '\0') detailed_help = YES; } sys_params = detailed_help ? detailed_sys_params : short_sys_params; for (p = sys_params; p->name != NULL; p++) set_parameter (p, p->default_value); } /* * Append system command line option to user parameters */ for (n1 = 0; usr_params [n1].name != NULL; n1++) ; for (n2 = 0; sys_params [n2].name != NULL; n2++) ; params = calloc (n1 + n2 + 1, sizeof (param_t)); if (!params) error ("Out of memory."); memcpy (params, usr_params, n1 * sizeof (param_t)); memcpy (params + n1, sys_params, (n2 + 1) * sizeof (param_t)); /* * Try to open the system resource file 'path'/'sys_file_name' */ { FILE *parameter_file = open_file (sys_path, NULL, READ_ACCESS); if (parameter_file == NULL) /* warning ("No system resource file found."); */ {} else { read_parameter_file (params, parameter_file); fclose (parameter_file); } } /* * Try to read user resource file $HOME/'usr_file_name' */ { FILE *parameter_file = open_file (usr_file_name, "HOME", READ_ACCESS); if (parameter_file != NULL) { read_parameter_file (params, parameter_file); fclose (parameter_file); } } /* * Parse command line options */ { extern char *optarg; /* argument of current option */ struct option *long_options; /* array of long options */ int option_index = 0; char optstr [MAXSTRLEN]; /* string containing the legitimate option characters */ int optchar; /* found option character */ /* * Build short option string for getopt_long (). */ { param_t *p; /* counter */ char *ptr_optstr; /* pointer to position in string */ ptr_optstr = optstr; for (p = params; p->name != NULL; p++) if (p->optchar != '\0') { *ptr_optstr++ = p->optchar; if (p->type == POSTR) { *ptr_optstr++ = ':'; *ptr_optstr++ = ':'; } else if (p->type != PFLAG) *ptr_optstr++ = ':'; } *ptr_optstr = '\0'; } /* * Build long option string for getopt_long (). */ { int i; long_options = calloc (n1 + n2 + 1, sizeof (struct option)); if (!long_options) error ("Out of memory."); for (i = 0; params [i].name != NULL; i++) { long_options [i].name = params [i].name; switch (params [i].type) { case PFLAG: long_options [i].has_arg = 0; break; case POSTR: long_options [i].has_arg = 2; break; case PINT: case PSTR: case PFLOAT: default: long_options [i].has_arg = 1; break; } long_options [i].has_arg = params [i].type != PFLAG; long_options [i].flag = NULL; long_options [i].val = 0; } } /* * Parse command line */ while ((optchar = getopt_long (argc, argv, optstr, long_options, &option_index)) != EOF) { int param_index = -1; switch (optchar) { case 0: param_index = option_index; break; case ':': if (detailed_help) fprintf (stderr, "Try `%s -h' or `%s --help' for " "more information.\n", argv [0], argv [0]); else fprintf (stderr, "Try `%s --help' for more information.\n", argv [0]); exit (2); break; case '?': if (detailed_help) fprintf (stderr, "Try `%s -h' or `%s --help' " "for more information.\n", argv [0], argv [0]); else fprintf (stderr, "Try `%s --help' for more information.\n", argv [0]); exit (2); break; default: { int i; for (i = 0; params [i].name != NULL; i++) if (params [i].optchar == optchar) { param_index = i; break; } } } /* * Check for system options */ if (param_index >= 0) { set_parameter (params + param_index, optarg ? optarg : ""); if (streq (params [param_index].name, "help")) usage (params, argv [0], synopsis, comment, non_opt_string, YES, sys_path, usr_file_name); else if (streq (params [param_index].name, "info")) usage (params, argv [0], synopsis, comment, non_opt_string, NO, sys_path, usr_file_name); else if (streq (params [param_index].name, "version")) { fprintf (stderr, "%s " VERSION "\n", argv [0]); { /* Kludge for standard Netpbm version announcement */ char * modified_argv[2]; int argc; modified_argv[0] = argv[0]; modified_argv[1] = (char *) "--version"; argc = 2; pm_proginit(&argc, (const char **) modified_argv); } exit (2); } else if (streq (params [param_index].name, "verbose")) fiasco_set_verbosity ( * (fiasco_verbosity_e *) parameter_value (params, "verbose")); else if (streq (params [param_index].name, "config")) read_config_file = YES; param_index = -1; /* clear index flag */ } } free (long_options); } /* * Read config-file if specified by option -f */ if (read_config_file) { char *filename; if ((filename = (char *) parameter_value (params, "config")) != NULL) { FILE *parameter_file; /* input file */ warning ("Options set in file `%s' will override" " command line options.", filename); parameter_file = open_file (filename, NULL, READ_ACCESS); if (parameter_file != NULL) { read_parameter_file (params, parameter_file); fclose (parameter_file); } else file_error (filename); } else error ("Invalid config filename."); } memcpy (usr_params, params, n1 * sizeof (param_t)); /* fill user struct */ free (sys_path); return optind; } void * parameter_value (const param_t *params, const char *name) /* * Extract value of parameter 'name.' of the given parameters 'params'. * * Return value: * value of given parameter */ { int pind = get_parameter_index (params, name); if (pind < 0) error ("Invalid parameter `%s'.", name); if (params [pind].type == PSTR || params [pind].type == POSTR) return (void *) params [pind].value.s; return (void *) &(params [pind].value); } void ask_and_set (param_t *params, const char *name, const char *msg) /* * Ask user (print given message 'msg') for missing mandatory * parameter 'name' of the given parameters 'params'. * * No return value. * * Side effects: * 'params ['name'].value' is changed */ { char answer [MAXSTRLEN]; int index = get_parameter_index (params, name); if (index < 0) error ("Invalid parameter %s.", name); if (msg) fprintf (stderr, "%s\n", msg); switch (params [index].type) { case PFLAG: /* Unusual, at least. */ warning ("Flags should be initialized and set on demand, " "not request"); case PINT: case PSTR: case POSTR: case PFLOAT: scanf (MAXSTRLEN_SCANF, answer); set_parameter (¶ms [index], answer); break; default: error ("Invalid parameter type for %s", name); } } void write_parameters (const param_t *params, FILE *output) /* * Write all parameter settings to 'output'. * * No return value. */ { int pind; if (!params || !output) error ("Parameters must be not NULL."); for (pind = 0; params [pind].name != NULL; pind++) { fprintf (output, "# %s = ", params [pind].name); switch (params [pind].type) { case PFLAG: fprintf (output, "%s\n", params [pind].value.b ? "TRUE" : "FALSE"); break; case PINT: fprintf (output, "%d\n", params [pind].value.i); break; case PFLOAT: fprintf (output, "%.4f\n", (double) params [pind].value.f); break; case PSTR: case POSTR: fprintf (output, "%s\n", params [pind].value.s); break; default: error ("Invalid type %d for parameter %s", params [pind].type, params [pind].name); } } fputc ('\n', output); } /***************************************************************************** private code *****************************************************************************/ static void set_parameter (param_t *parameter, const char *value) /* * Set value of 'parameter' to 'value'. * * No return value. * * Side effects: * 'parameter.value' is changed accordingly */ { assert (parameter); switch (parameter->type) { case PFLAG: if (value != NULL && *value != '\0') { if (strcaseeq (value, "TRUE")) parameter->value.b = YES; else if (strcaseeq (value, "FALSE")) parameter->value.b = NO; else if (strcaseeq (value, "YES")) parameter->value.b = YES; else if (strcaseeq (value, "NO")) parameter->value.b = NO; else { long int data; char *endptr; data = strtol (value, &endptr, 0); if (*endptr != '\0' || endptr == value) warning ("Invalid value `%s' converted to %d", value, (int) data); parameter->value.b = data ? YES : NO; } } else parameter->value.b = !parameter->value.b; break; case PINT: { long int data; char *endptr; data = strtol (value, &endptr, 0); if (*endptr != '\0' || endptr == value) warning ("Invalid value `%s' converted to %d", value, (int) data); parameter->value.i = data; } break; case PFLOAT: { double data; char *endptr; data = strtod (value, &endptr); if (*endptr != '\0' || endptr == value) warning ("Invalid value `%s' converted to %f", value, (double) data); parameter->value.f = data; } break; case PSTR: case POSTR: parameter->value.s = value ? strdup (value) : NULL; break; default: error ("Invalid parameter type for %s", parameter->name); } } static int get_parameter_index (const param_t *params, const char *search_string) /* * Search for parameter with name 'search_string' in parameter struct. * * Return value: * index of parameter or -1 if no matching parameter has been found */ { int n; int index = -1; assert (params && search_string); for (n = 0; params [n].name != NULL; n++) if (strcaseeq (params [n].name, search_string)) { index = n; break; } return index; } static void read_parameter_file (param_t *params, FILE *file) /* * Read parameter settings from 'file'. * * No return value. * * Side effects: * 'params [].value' are changed if specified in 'file' */ { char buffer [MAXSTRLEN]; int n = 0; assert (params && file); while (fgets (buffer, MAXSTRLEN, file) != NULL) { char *b; /* temporary variable */ char *name; /* parameter name */ char *value; /* parameter value */ int pind; /* current argument number */ b = strchr (buffer, '#'); if (b != NULL) /* Strip comments. */ *b = '\0'; b = strchr (buffer, '='); if (b == NULL) /* Strip lines that contain no '=' */ continue; *b = '\0'; /* Replace '=' by string terminator */ /* * Extract value of parameter */ for (value = b + 1; ISSPACE (*value); value++) ; /* Delete leading spaces */ for (b = value + strlen (value) - 1; b >= value && ISSPACE (*b); b--) *b = '\0'; /* Delete trailing spaces. */ /* * Extract parameter name */ for (name = buffer; ISSPACE (*name); name++) ; /* Delete leading spaces */ for (b = name + strlen (name) - 1; b >= name && ISSPACE (*b); b--) *b = '\0'; /* Delete trailing spaces. */ pind = get_parameter_index (params, name); if (pind >= 0) set_parameter (¶ms [pind], value); n++; } } static void usage (const param_t *params, const char *progname, const char *synopsis, const char *comment, const char *non_opt_string, bool_t show_all_options, const char *sys_file_name, const char *usr_file_name) /* * Generates and prints command line description from param_t struct 'params'. * 'progname' is the name of the executable, 'synopsis' a short program * description, and 'comment' some more advice. * If flag 'show_all_options' is set then print also options that are not * associated with a short option character. * 'sys_file_name' and 'usr_file_name' are filenames to parameter files. * * No return value. */ { int i; size_t width = 0; fprintf (stderr, "Usage: %s [OPTION]...%s\n", progname, non_opt_string ? non_opt_string : " "); if (synopsis != NULL) fprintf (stderr, "%s", synopsis); fprintf (stderr, "\n\n"); fprintf (stderr, "Mandatory or optional arguments to long options " "are mandatory or optional\nfor short options too. " "Default values are surrounded by {}.\n"); for (i = 0; params [i].name != NULL; i++) if (params [i].optchar != '\0' || show_all_options) { if (params [i].type == POSTR) width = MAX(width, (strlen (params [i].name) + strlen (params [i].argument_name) + 2)); else if (params [i].type != PFLAG) width = MAX(width, (strlen (params [i].name) + strlen (params [i].argument_name))); else width = MAX(width, (strlen (params [i].name)) - 1); } for (i = 0; params [i].name != NULL; i++) if (params [i].optchar != '\0' || show_all_options) { if (params [i].optchar != '\0') fprintf (stderr, " -%c, --", params [i].optchar); else fprintf (stderr, " --"); if (params [i].type == POSTR) fprintf (stderr, "%s=[%s]%-*s ", params [i].name, params [i].argument_name, (unsigned) MAX(0, (width - 2 - strlen (params [i].name) - strlen (params [i].argument_name))), ""); else if (params [i].type != PFLAG) fprintf (stderr, "%s=%-*s ", params [i].name, (unsigned)(width - strlen (params [i].name)), params [i].argument_name); else fprintf (stderr, "%-*s ", (unsigned)(width + 1), params [i].name); fprintf (stderr, params [i].use, params [i].argument_name); switch (params [i].type) { case PFLAG: break; case PINT: fprintf (stderr, "{%d}", params [i].value.i); break; case PFLOAT: fprintf (stderr, "{%.2f}", (double) params [i].value.f); break; case PSTR: case POSTR: if (params [i].value.s) fprintf (stderr, "{%s}", params [i].value.s); break; default: error ("type %d for %s invalid", params [i].type, params [i].name); } fprintf (stderr, "\n"); } fprintf (stderr, "\n"); fprintf (stderr, "Parameter initialization order:\n"); fprintf (stderr, "1.) %s\n2.) $HOME/%s\t 3.) command line\t 4.) --config=file", sys_file_name, usr_file_name); fprintf (stderr, "\n\n"); if (comment != NULL) fprintf (stderr, "%s\n", comment); exit (1); }