dnl This file is part of GNU Rush.
dnl Copyright (C) 2007-2024 Sergey Poznyakoff.
dnl 
dnl GNU Rush is free software; you can redistribute it and/or modify
dnl it under the terms of the GNU General Public License as published by
dnl the Free Software Foundation; either version 3, or (at your option)
dnl any later version.
dnl
dnl GNU Rush is distributed in the hope that it will be useful,
dnl but WITHOUT ANY WARRANTY; without even the implied warranty of
dnl MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
dnl GNU General Public License for more details.
dnl
dnl You should have received a copy of the GNU General Public License
dnl along with GNU Rush.  If not, see <http://www.gnu.org/licenses/>.
divert(-1)
changequote([<,>])
changecom(/*,*/)

dnl upcase(ARGS...)
dnl Concatenate and convert ARGS to upper case.
dnl
define([<upcase>], [<translit([<$*>], [<a-z>], [<A-Z>])>])

dnl concat(ARGS...)
dnl Concatenate arguments, inserting ", " between each of pair of them.
dnl
define([<concat>],[<ifelse([<$#>],1,[<$1>],[<$1, concat(shift($@))>])>])

dnl flushleft(ARGS...)
dnl Concatenate ARGS and remove any leading whitespace
dnl
define([<flushleft>],
 [<patsubst([<concat($*)>], [<^[ 	]+>])>])

dnl chop(ARGS...)
dnl Concatenate ARGS and remove any trailing whitespace
dnl
define([<chop>],
 [<patsubst([<$*>], [<[ 	]+$>])>])

dnl escape(ARGS...)
dnl Concatenate ARGS and escape any occurrences of double-quotes with
dnl backslashes.
dnl
define([<escape>],
[<patsubst([<concat($*)>],[<[\"]>],[<\\\&>])>])

dnl prep(ARG)
dnl Prepare ARG for including in C strings: replace newlines with any amount
dnl of preceding and following whitespace by a single space character, remove
dnl leading whitespace, and escape double-quotes.
dnl
define([<prep>],
 [<escape(flushleft(patsubst([<$1>],[<[ 	]*
+[ 	]*>],[< >])))>])

dnl SHORT_OPTS
dnl Accumulator for the 3rd argument of getopt_long
dnl
define([<SHORT_OPTS>],[<>])

dnl GROUP(STRING)
dnl Begin a named group of options
dnl
define([<GROUP>],[<dnl
divert(3)
	{ NULL, NULL, 0, N_("prep([<$1>])") },
divert(-1)>]) 

define([<__GATHER_OPTIONS>],[<
define([<KEY>],ifelse([<$2>],,[<OPTION_>]upcase(patsubst($1,-,_)),'$2'))
ifelse([<$2>],,[<
divert(1)
	KEY,
divert(-1)
>])
define([<SELECTOR>],ifdef([<SELECTOR>],SELECTOR) case KEY:)
ifelse([<$1>],,,[<
divert(2)
	{ "$1", ARGTYPE, 0, KEY },
divert(-1)>])
dnl
define([<SHORT_OPTS>],SHORT_OPTS[<>]dnl
ifelse([<$2>],,,$2[<>]ifelse(ARGTYPE,[<no_argument>],,ARGTYPE,[<required_argument>],:,ARGTYPE,[<optional_argument>],::)))
dnl
ifelse([<$1>],,,dnl
[<define([<LONG_TAG>],ifelse(LONG_TAG,,[<--$1>],[<LONG_TAG; --$1>]))>])
ifelse([<$2>],,,dnl
[<define([<SHORT_TAG>],ifelse(SHORT_TAG,,[<-$2>],[<SHORT_TAG; -$2>]))>])
>])

dnl OPTION(long-opt, short-opt, [arg], [descr])
dnl Introduce a command line option. Arguments:
dnl   long-opt     Long option.  
dnl   short-opt    Short option (a single char)
dnl   (At least one of long-opt or short-opt must be present)
dnl
dnl   Optional arguments:
dnl   arg          Option argument. 
dnl   descr        Option description
dnl
dnl If arg is absent, the option does not take any arguments. If arg is
dnl enclosed in square brackets, the option takes an optional argument.
dnl Otherwise, the argument is required.
dnl
dnl If descr is not given the option will not appear in the --help and
dnl --usage outputs.
dnl
define([<OPTION>],[<
pushdef([<LONG_TAG>])
pushdef([<SHORT_TAG>])
pushdef([<ARGNAME>],[<$3>])
pushdef([<DOCSTRING>],[<prep([<$4>])>])
pushdef([<ARGTYPE>],[<ifelse([<$3>],,[<no_argument>],dnl
patsubst([<$3>],[<\[.*\]>]),,[<optional_argument>],dnl
[<required_argument>])>])
__GATHER_OPTIONS($@)
>])

dnl ALIAS(long-opt, short-opt)
dnl Declare aliases for the previous OPTION statement.
dnl   long-opt     Long option.
dnl   short-opt    Short option (a single char)
dnl   (At least one of long-opt or short-opt must be present)
dnl An OPTION statement may be followed by any number of ALIAS statements.
dnl 
define([<ALIAS>],[<
__GATHER_OPTIONS($1,$2)
>])

dnl BEGIN
dnl Start an action associated with the declared option. Must follow OPTION
dnl statement, with optional ALIAS statements in between.
dnl
define([<BEGIN>],[<
ifelse(DOCSTRING,,,[<
divert(3)
	{ "translit(dnl
ifelse(SHORT_TAG,,LONG_TAG,[<SHORT_TAG[<>]ifelse(LONG_TAG,,,; LONG_TAG)>]),
                    [<;>],[<,>])", ifelse(ARGNAME,,[<NULL, 0>],
[<ifelse(ARGTYPE,[<optional_argument>],
[<patsubst([<ARGNAME>],[<\[\(.*\)\]>],[<N_("\1"), 1>])>],[<N_("ARGNAME"), 0>])>]), N_("DOCSTRING") },
divert(-1)>])
popdef([<ARGTYPE>])
popdef([<ARGNAME>])
popdef([<DOCSTRING>])
divert(4)dnl
popdef([<LONG_TAG>])dnl
popdef([<SHORT_TAG>])dnl
	SELECTOR
          {
>])

dnl END
dnl Finish the associated action
dnl
define([<END>],[<
             break;
          }
divert(-1)
undefine([<SELECTOR>])>])

dnl GETOPT(argc, argv, [default])
dnl Emit option parsing code. Arguments:
dnl
dnl  argc        Name of the 1st argument to getopt_long.
dnl  argv        Name of the 2nd argument to getopt_long.
dnl  default     Code for the default branch
dnl
define([<GETOPT>],[<
 {
  int optchar;

ifelse([<$#>],3,opterr = 0;)
  while ((optchar = getopt_long($1, $2, "SHORT_OPTS",
                                long_options, NULL)) != EOF)
    {
      switch (optchar)
        {
        default:
	ifelse([<$#>],3,$3,[<exit(1)>]);
       
	undivert(4)
        }
    }
 }   
>])

define([<STDFUNC>],[<
divert(0)
void print_help(void);
void print_usage(void);
divert(5)
const char *program_version = [<$1>];
static char doc[] = N_("[<$3>]");
static char args_doc[] = N_("[<$4>]");
const char *program_bug_address = "<" PACKAGE_BUGREPORT ">";
		    
#define DESCRCOLUMN 30
#define RMARGIN 79
#define GROUPCOLUMN 2
#define USAGECOLUMN 13
		    
static void
indent (size_t start, size_t col)
{
  for (; start < col; start++)
    putchar (' ');
}
		    
static void
print_option_descr (const char *descr, size_t lmargin, size_t rmargin)
{
  while (*descr)
    {
      int s = 0;
      int i;
      size_t width = rmargin - lmargin;

      for (i = 0; ; i++)
	{
	  if (descr[i] == 0 || isspace (descr[i]))
	    {
	      if (i > width)
		break;
	      s = i;
	      if (descr[i] == 0)
		break;
	    }
	}
      printf ("%*.*s\n", s, s, descr);
      descr += s;
      if (*descr)
	{
	  indent (0, lmargin);
	  descr++;
	}
    }
}

void
print_help(void)
{
  unsigned i;
  
  printf ("%s %s [%s]...", _("Usage:"), [<$2>], _("[<OPTION>]"));
  if (args_doc[0])
	  printf(" %s", gettext(args_doc));
  printf("\n");
  if (doc[0])
    print_option_descr(gettext (doc), 0, RMARGIN);
  putchar ('\n');

  for (i = 0; i < sizeof (opthelp) / sizeof (opthelp[0]); i++)
    {
      unsigned n;
      if (opthelp[i].opt)
	{
	  n = printf ("  %s", opthelp[i].opt);
	  if (opthelp[i].arg)
	    {
	      char *cb, *ce;
	      if (strlen (opthelp[i].opt) == 2)
		{
		  if (!opthelp[i].is_optional)
		    {
		      putchar (' ');
		      n++;
		    }
		}
	      else
		{
		  putchar ('=');
		  n++;
		}
	      if (opthelp[i].is_optional)
		{
		  cb = "[";
		  ce = "]";
		}
	      else
		cb = ce = "";
	      n += printf ("%s%s%s", cb, gettext (opthelp[i].arg), ce);
	    }
	  if (n >= DESCRCOLUMN)
	    {
	      putchar ('\n');
	      n = 0;
	    }
	  indent (n, DESCRCOLUMN);
	  print_option_descr (gettext (opthelp[i].descr), DESCRCOLUMN, RMARGIN);
	}
      else
	{
	  if (i)
	    putchar ('\n');
	  indent (0, GROUPCOLUMN);
	  print_option_descr (gettext (opthelp[i].descr),
			      GROUPCOLUMN, RMARGIN);
	  putchar ('\n');
	}
    }
  
  putchar ('\n');
dnl **************************************************************************
dnl This string cannot be split over several lines, because this would trigger
dnl a bug in GNU M4 (version 1.4.9 and 1.4.10), which would insert #line
dnl directives between the lines.
dnl **************************************************************************
  print_option_descr (_("Mandatory or optional arguments to long options are also mandatory or optional for any corresponding short options."), 0, RMARGIN);
  putchar ('\n');
  printf (_("Report bugs to %s.\n"), program_bug_address);
}

static int
cmpidx_short(const void *a, const void *b)
{
	struct opthelp const **opta = (struct opthelp const **)a;
	struct opthelp const **optb = (struct opthelp const **)b;

	return (*opta)->opt[1] - (*optb)->opt[1];
}
  
static int
cmpidx_long(const void *a, const void *b)
{
	struct opthelp const **ap = (struct opthelp const **)a;
	struct opthelp const **bp = (struct opthelp const **)b;
	char const *opta, *optb;
	size_t lena, lenb;

	if ((*ap)->opt[1] == '-')
		opta = (*ap)->opt;
	else
		opta = (*ap)->opt + 4;
	lena = strcspn(opta, ",");
  
	if ((*bp)->opt[1] == '-')
		optb = (*bp)->opt;
	else
		optb = (*bp)->opt + 4;
	lenb = strcspn(optb, ",");
	return strncmp(opta, optb, lena > lenb ? lenb : lena);
}

void
print_usage(void)
{
	unsigned i;
	unsigned n;
	char *buf;
	size_t bufsize;
	unsigned nidx;
	struct opthelp **optidx;
	size_t optcount = sizeof(opthelp) / sizeof(opthelp[0]);
  
#define FLUSH do {				dnl
		buf[n] = 0;			dnl
		printf("%s\n", buf);		dnl
		n = USAGECOLUMN;		dnl
		memset(buf, ' ', n);		dnl
	}  while (0)
#define ADDC(c) do { if (n == RMARGIN) FLUSH; buf[n++] = c; } while (0)

	optidx = calloc(optcount, sizeof(optidx[0]));
	if (!optidx)
		abort();
	bufsize = RMARGIN + 1;
	buf = malloc(bufsize);
	if (!buf)
		abort();
	
	n = snprintf(buf, bufsize, "%s %s ", _("Usage:"), [<$2>]);
	
	/* Print a list of short options without arguments. */
	for (i = nidx = 0; i < optcount; i++)
		if (opthelp[i].opt &&
		    opthelp[i].descr &&
		    opthelp[i].opt[1] != '-' &&
		    opthelp[i].arg == NULL)
			optidx[nidx++] = opthelp + i;

	if (nidx) {
		qsort(optidx, nidx, sizeof(optidx[0]), cmpidx_short);

		ADDC('[');
		ADDC('-');
		for (i = 0; i < nidx; i++) {
			ADDC(optidx[i]->opt[1]);
		}
		ADDC(']');
	}

	/* Print a list of short options with arguments. */
	for (i = nidx = 0; i < optcount; i++) {
		if (opthelp[i].opt &&
		    opthelp[i].descr &&
		    opthelp[i].opt[1] != '-' &&
		    opthelp[i].arg)
			optidx[nidx++] = opthelp + i;
	}

	if (nidx) {
		qsort(optidx, nidx, sizeof(optidx[0]), cmpidx_short);
    
		for (i = 0; i < nidx; i++) {
			struct opthelp *opt = optidx[i];
			size_t len = 5 + strlen(opt->arg)
				       + (opt->is_optional ? 2 : 1);
      
			if (n + len > RMARGIN)
				FLUSH;
			buf[n++] = ' ';
			buf[n++] = '[';
			buf[n++] = '-';
			buf[n++] = opt->opt[1];
			if (opt->is_optional) {
				buf[n++] = '[';
				strcpy(&buf[n], opt->arg);
				n += strlen(opt->arg);
				buf[n++] = ']';
			} else {
				buf[n++] = ' ';
				strcpy(&buf[n], opt->arg);
				n += strlen(opt->arg);
			}
			buf[n++] = ']';
		}
	}
  
	/* Print a list of long options */
	for (i = nidx = 0; i < optcount; i++) {
		if (opthelp[i].opt && opthelp[i].descr
		    && (opthelp[i].opt[1] == '-' || opthelp[i].opt[2] == ','))
			optidx[nidx++] = opthelp + i;
	}

	if (nidx) {
		qsort(optidx, nidx, sizeof(optidx[0]), cmpidx_long);
	
		for (i = 0; i < nidx; i++) {
			struct opthelp *opt = optidx[i];
			size_t len;
			const char *longopt;
	  
			if (opt->opt[1] == '-')
				longopt = opt->opt;
			else if (opt->opt[2] == ',')
				longopt = opt->opt + 4;
			else
				continue;

			len = 3 + strlen(longopt)
				+ (opt->arg ? 1 + strlen(opt->arg)
				+ (opt->is_optional ? 2 : 0) : 0);
			if (n + len > RMARGIN) {
				FLUSH;
				/* Make sure we have enough buffer space if
                                   the string cannot be split */
				if (n + len > bufsize) {
					bufsize = n + len;
					buf = realloc(buf, bufsize);
					if (!buf)
						abort();
				}
			}
			buf[n++] = ' ';
			buf[n++] = '[';
			strcpy(&buf[n], longopt);
			n += strlen(longopt);
			if (opt->arg) {
				buf[n++] = '=';
				if (opt->is_optional) {
					buf[n++] = '[';
					strcpy(&buf[n], opt->arg);
					n += strlen(opt->arg);
					buf[n++] = ']';
				} else {
					strcpy(&buf[n], opt->arg);
					n += strlen(opt->arg);
				}
			}
			buf[n++] = ']';
		}
	}

	/* Print argument list */
	if (args_doc[0]) {
		char const *docstr = gettext(args_doc);
		size_t len = strlen(docstr) + 1;
		if (n + len <= RMARGIN) {
			buf[n++] = ' ';
			strcpy(buf + n, docstr);
			n += len;
		} else {
			struct wordsplit ws;

			if (wordsplit(docstr, &ws, 
				      WRDSF_SHOWERR |
				      WRDSF_NOVAR |
				      WRDSF_NOCMD |
				      WRDSF_QUOTE |
				      WRDSF_SQUEEZE_DELIMS))
				abort();

			for (i = 0; i < ws.ws_wordc; i++) {
				len = strlen(ws.ws_wordv[i]) + 1;
				if (n + len > RMARGIN) {
					FLUSH;
					/* Make sure we have enough buffer
					   space if the string cannot be
					   split */
					if (n + len > bufsize) {
						bufsize = n + len;
						buf = realloc(buf, bufsize);
						if (!buf)
							abort();
					}
				}
				buf[n++] = ' ';
				strcpy(buf + n, ws.ws_wordv[i]);
				n += len;
			}
		}	
	}
	
	FLUSH;

	free(optidx);
	free(buf);
}

const char version_etc_copyright[] =
  /* Do *not* mark this string for translation.  %s is a copyright
     symbol suitable for this locale, and %d is the copyright
     year.  */
  "Copyright %s 2008-%d Sergey Poznyakoff";
int rush_copyright_year = 2024;

void
print_version_only(const char *program_version, FILE *stream)
{
	fprintf (stream, "%s\n", program_version);
	/* TRANSLATORS: Translate "(C)" to the copyright symbol
	   (C-in-a-circle), if this symbol is available in the user's
	   locale.  Otherwise, do not translate "(C)"; leave it as-is.  */
	fprintf (stream, version_etc_copyright, _("(C)"), rush_copyright_year);
	fputc ('\n', stream);
}

void
print_version(const char *program_version, FILE *stream)
{
	print_version_only(program_version, stream);
	
dnl **************************************************************************
dnl This string cannot be split over serveal lines, because this would trigger
dnl a bug in GNU M4 (version 1.4.9 and 1.4.10), which would insert #line
dnl directives between the lines.
dnl **************************************************************************
	fputs (_("License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>\nThis is free software: you are free to change and redistribute it.\nThere is NO WARRANTY, to the extent permitted by law.\n\n"),
	       stream);
	
dnl	/* TRANSLATORS: %s denotes an author name.  */
dnl	fprintf (stream, _("Written by %s.\n"), "Sergey Poznyakoff");
}

divert(-1)
popdef([<ADDC>])
popdef([<FLUSH>])
>])

define([<OPTIONS_BEGIN>],
   [<divert(-1)
     define([<GETOPT_STYLE>],[<$1>]) 
     ifelse([<$1>],[<gnu>],
       [<STDFUNC([<$2 " (" PACKAGE_STRING ")">], [<$2>], [<$3>], [<$4>])>])
>])
  
define([<OPTIONS_END>],[<
ifelse(GETOPT_STYLE,[<gnu>],[<
         GROUP([<Other options>])
         OPTION([<help>],h,,[<Give this help list>])
         BEGIN
		print_help ();
                exit (0);
	 END
         OPTION([<usage>],,,[<Give a short usage message>])
	 BEGIN
		print_usage ();
		exit (0);
	 END
	 OPTION([<version>],,,[<Print program version>])
	 BEGIN
		/* Give version */
		print_version(program_version, stdout);
		exit (0);
         END>])
divert
/* Option codes */
enum {
	_OPTION_INIT=255,
	undivert(1)
	MAX_OPTION
};
static struct option long_options[] = {
	undivert(2)
	{0, 0, 0, 0}
};
static struct opthelp {
        const char *opt;
        const char *arg;
        int is_optional;
        const char *descr;
} opthelp[] = {
	undivert(3)
};
undivert(5)
>])

divert(0)dnl
/* -*- buffer-read-only: t -*- vi: set ro:
   THIS FILE IS GENERATED AUTOMATICALLY.  PLEASE DO NOT EDIT.
*/

