/*LINTLIBRARY*/
/*  expression.c

    This code provides Borne Shell-like expression expansion.

    Copyright (C) 1997-1999  Richard Gooch

    This library is free software; you can redistribute it and/or
    modify it under the terms of the GNU Library General Public
    License as published by the Free Software Foundation; either
    version 2 of the License, or (at your option) any later version.

    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.  See the GNU
    Library General Public License for more details.

    You should have received a copy of the GNU Library General Public
    License along with this library; if not, write to the Free
    Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.

    Richard Gooch may be reached by email at  karma-request@atnf.csiro.au
    The postal address is:
      Richard Gooch, c/o ATNF, P. O. Box 76, Epping, N.S.W., 2121, Australia.
*/

/*  This file contains shell-like expression expansion routines.


    Written by      Richard Gooch   9-OCT-1997

    Updated by      Richard Gooch   4-DEC-1997: Added support for
  "${var:-word}" expressions.

    Updated by      Richard Gooch   17-SEP-1998: Took account of some CONST
  declarations in <r> package.

    Last updated by Richard Gooch   8-JUN-1999: Changed <st_expr_expand> to
  support getting variable values from supplied function.


*/
#include <stdio.h>
#include <ctype.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <pwd.h>
#include <karma.h>
#ifdef __KARMA__
#  include <karma_st.h>
#  include <karma_r.h>
#endif

#define BUF_SIZE 16384
#ifndef __KARMA__
#  define r_getenv getenv
#endif


/*  Private functions  */
STATIC_FUNCTION (CONST char *expand_variable,
		 (char *buffer, unsigned int length, unsigned int *out_pos,
		  CONST char *input,
		  CONST char *(*func) (CONST char *variable, void *info),
		  void *info, FILE *errfp) );
STATIC_FUNCTION (CONST char *get_variable,
		 (CONST char *variable,
		  CONST char *(*func) (CONST char *variable, void *info),
		  void *info) );


/*  Public functions follow  */

/*EXPERIMENTAL_FUNCTION*/
flag st_expr_expand (char *output, unsigned int length, CONST char *input,
		     CONST char *(*get_variable) (CONST char *variable,
						  void *info),
		     void *info, FILE *errfp)
/*  [SUMMARY] Expand an expression using Borne Shell-like unquoted rules.
    <output> The output expanded expression is written here.
    <length> The size of the output buffer.
    <input> The input expression. This may equal <<output>>.
    <get_variable> A function which will be used to get variable values. If
    this returns NULL, the environment is searched instead. If this is NULL,
    only the environment is searched.
    <info> An arbitrary pointer passed to <<get_variable>>.
    <errfp> Diagnostic messages are written here. If this is NULL the global
    stderr is used instead.
    [RETURNS] TRUE on success, else FALSE.
*/
{
    char ch;
    unsigned int len;
    unsigned int out_pos = 0;
    CONST char *env;
    CONST char *ptr;
    struct passwd *pwent;
    char buffer[BUF_SIZE], tmp[STRING_LENGTH];
    static char function_name[] = "st_expr_expand";

    if (errfp == NULL) errfp = stderr;
    if (length > BUF_SIZE) length = BUF_SIZE;
    for (; TRUE; ++input)
    {
	switch (ch = *input)
	{
	  case '$':
	    /*  Variable expansion  */
	    input = expand_variable (buffer, length, &out_pos, ++input,
				     get_variable, info, errfp);
	    if (input == NULL) return (FALSE);
	    break;
	  case '~':
	    /*  Home directory expansion  */
	    ch = input[1];
	    if ( isspace (ch) || (ch == '/') || (ch == '\0') )
	    {
		/* User's own home directory: leave separator for next time */
		if ( ( env = r_getenv ("HOME") ) == NULL )
		{
		    fprintf (errfp,
			     "%s: environment variable: \"HOME\" not found\n",
			     function_name);
		    return (FALSE);
		}
		len = strlen (env);
		if (len + out_pos >= length)
		{
		    fprintf (errfp, "%s: output buffer too small\n",
			     function_name);
		    return (FALSE);
		}
		memcpy (buffer + out_pos, env, len + 1);
		out_pos += len;
		continue;
	    }
	    /*  Someone else's home directory  */
	    for (ptr = ++input; !isspace (ch) && (ch != '/') && (ch != '\0');
		 ch = *++ptr);
	    len = ptr - input;
	    if (len >= sizeof tmp)
	    {
		fprintf (errfp, "%s: username buffer too small\n",
			 function_name);
		return (FALSE);
	    }
	    memcpy (tmp, input, len);
	    tmp[len] = '\0';
	    input = ptr - 1;
	    if ( ( pwent = getpwnam (tmp) ) == NULL )
	    {
		fprintf (errfp, "%s: error getting pwent for user: \"%s\"\n",
			 function_name, tmp);
		return (FALSE);
	    }
	    len = strlen (pwent->pw_dir);
	    if (len + out_pos >= length)
	    {
		fprintf (errfp, "%s: output buffer too small\n",function_name);
		return (FALSE);
	    }
	    memcpy (buffer + out_pos, pwent->pw_dir, len + 1);
	    out_pos += len;
	    break;
	  case '\0':
	  default:
	    if (out_pos >= length)
	    {
		fprintf (errfp, "%s: output buffer too small\n",function_name);
		return (FALSE);
	    }
	    buffer[out_pos++] = ch;
	    if (ch == '\0')
	    {
		memcpy (output, buffer, out_pos);
		return (TRUE);
	    }
	    break;
	}
    }
    return (FALSE);
}   /*  End Function st_expr_expand  */


/*  Private functions follow  */

static CONST char *expand_variable (char *buffer, unsigned int length,
				    unsigned int *out_pos, CONST char *input,
				    CONST char *(*func) (CONST char *variable,
							 void *info),
				    void *info, FILE *errfp)
/*  [SUMMARY] Expand a variable.
    <buffer> The buffer to write to.
    <length> The length of the output buffer.
    <out_pos> The current output position. This is updated.
    <input> A pointer to the input character pointer.
    <func> A function which will be used to get variable values. If this
    returns NULL, the environment is searched instead. If this is NULL, only
    the environment is searched.
    <info> An arbitrary pointer passed to <<func>>.
    <errfp> Diagnostic messages are written here.
    [RETURNS] A pointer to the end of this subexpression on success, else NULL.
*/
{
    char ch;
    int len;
    unsigned int open_braces;
    CONST char *env, *ptr;
    char tmp[STRING_LENGTH];
    static char function_name[] = "_st_expr_expand_variable";

    ch = input[0];
    if (ch == '$')
    {
	/*  Special case for "$$": PID  */
	sprintf ( tmp, "%d", (int) getpid () );
	len = strlen (tmp);
	if (len + *out_pos >= length)
	{
	    fprintf (errfp, "%s: output buffer too small\n", function_name);
	    return (NULL);
	}
	memcpy (buffer + *out_pos, tmp, len + 1);
	*out_pos += len;
	return (input);
    }
    /*  Ordinary variable expansion, possibly in braces  */
    if (ch != '{')
    {
	/*  Simple variable expansion  */
	for (ptr = input; isalnum (ch) || (ch == '_') || (ch == ':');
	     ch = *++ptr);
	len = ptr - input;
	if (len >= sizeof tmp)
	{
	    fprintf (errfp, "%s: temporary buffer too small\n", function_name);
	    return (NULL);
	}
	memcpy (tmp, input, len);
	tmp[len] = '\0';
	input = ptr - 1;
	if ( ( env = get_variable (tmp, func, info) ) == NULL )
	{
	    fprintf (errfp, "%s: variable: \"%s\" not found\n",
		     function_name, tmp);
	    return (NULL);
	}
	len = strlen (env);
	if (len + *out_pos >= length)
	{
	    fprintf (errfp, "%s: output buffer too small\n", function_name);
	    return (NULL);
	}
	memcpy (buffer + *out_pos, env, len + 1);
	*out_pos += len;
	return (input);
    }
    /*  Variable in braces: check for ':' tricks  */
    ch = *++input;
    for (ptr = input; isalnum (ch) || (ch == '_'); ch = *++ptr);
    if (ch == '}')
    {
	/*  Must be simple variable expansion with "${var}"  */
	len = ptr - input;
	if (len >= sizeof tmp)
	{
	    fprintf (errfp, "%s: temporary buffer too small\n", function_name);
	    return (NULL);
	}
	memcpy (tmp, input, len);
	tmp[len] = '\0';
	ptr = expand_variable (buffer, length, out_pos, tmp, func, info,errfp);
	if (ptr == NULL) return (NULL);
	return (input + len);
    }
    if (ch != ':')
    {
	fprintf (errfp, "%s: illegal character: '%c' in variable name\n",
		 function_name, ch);
	return (NULL);
    }
    if (ptr[1] != '-')
    {
	fprintf (errfp, "%s: illegal character: '%c' in variable name\n",
		 function_name, ptr[1]);
	return (NULL);
    }
    /*  It's that handy "${var:-word}" expression. Check if var is defined  */
    len = ptr - input;
    if (len >= sizeof tmp)
    {
	fprintf (errfp, "%s: temporary buffer too small\n", function_name);
	return (NULL);
    }
    memcpy (tmp, input, len);
    tmp[len] = '\0';
    /*  Move input pointer to ':'  */
    input = ptr;
    /*  First skip to closing brace, taking note of nested expressions  */
    ptr += 2;
    ch = ptr[0];
    for (open_braces = 1; open_braces > 0; ch = *++ptr)
    {
	switch (ch)
	{
	  case '{':
	    ++open_braces;
	    break;
	  case '}':
	    --open_braces;
	    break;
	  case '\0':
	    fprintf (errfp, "%s: closing brace not found in: \"%s\"\n",
		     function_name, input);
	    return (NULL);
	    /*break;*/
	  default:
	    break;
	}
    }
    --ptr;
    /*  At this point ptr should point to closing brace of "${var:-word}"  */
    if ( ( env = get_variable (tmp, func, info) ) != NULL )
    {
	/*  Found environment variable, so skip the input to the closing brace
	    and return the variable  */
	input = ptr;
	len = strlen (env);
	if (len + *out_pos >= length)
	{
	    fprintf (errfp, "%s: output buffer too small\n", function_name);
	    return (NULL);
	}
	memcpy (buffer + *out_pos, env, len + 1);
	*out_pos += len;
	return (input);
    }
    /*  Environment variable was not found, so process word. Advance input
	pointer to start of word in "${var:-word}"  */
    input += 2;
    len = ptr - input;
    if (len >= sizeof tmp)
    {
	fprintf (errfp, "%s: temporary buffer too small for word\n",
		 function_name);
	return (NULL);
    }
    memcpy (tmp, input, len);
    tmp[len] = '\0';
    input = ptr;
    if ( !st_expr_expand (tmp, STRING_LENGTH, tmp, func, info, errfp) )
	return (NULL);
    len = strlen (tmp);
    if (len + *out_pos >= length)
    {
	fprintf (errfp, "%s: output buffer too small\n", function_name);
	return (NULL);
    }
    memcpy (buffer + *out_pos, tmp, len + 1);
    *out_pos += len;
    return (input);
}   /*  End Function expand_variable  */

static CONST char *get_variable (CONST char *variable,
				 CONST char *(*func) (CONST char *variable,
						      void *info),
				 void *info)
/*  [SUMMARY] Get a variable from the environment or .
    <variable> The variable name.
    <func> A function which will be used to get the variable. If this returns
    NULL, the environment is searched instead. If this is NULL, only the
    environment is searched.
    [RETURNS] The value of the variable on success, else NULL.
*/
{
    CONST char *value;

    if (func != NULL)
    {
	value = (*func) (variable, info);
	if (value != NULL) return (value);
    }
    return r_getenv (variable);
}   /*  End Function get_variable  */
