/*********************************************************************
 *   Copyright 1993, University Corporation for Atmospheric Research
 *   See netcdf/README file for copying and redistribution conditions.
 *   $Header: /upc/share/CVS/netcdf-3/ncdump/ncdump.c,v 1.44 2007/02/16 15:16:08 ed Exp $
 *********************************************************************/

#include <config.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <stdlib.h>

#include <netcdf.h>
#include "ncdump.h"
#include "dumplib.h"
#include "vardata.h"

#define int64_t long long
#define uint64_t unsigned long long

static void usage(void);
static char* name_path(const char* path);
static const char* type_name(nc_type  type);
static void tztrim(char* ss);
static void pr_att_string(size_t len, const char* string);
static void pr_att_vals(nc_type  type, size_t len, const double* vals);
static void pr_att(int ncid, int varid, const char *varname, int ia);
static void pr_attx(int ncid, int varid, int ia);
static void do_ncdump(const char* path, fspec_t* specp);
static void do_ncdumpx(const char* path, fspec_t* specp);
static void do_nckind(const char* path);
static void make_lvars(char* optarg, fspec_t* fspecp);
static void set_sigdigs( const char* optarg);
static void set_precision( const char *optarg);
int main(int argc, char** argv);

#define	STREQ(a, b)	(*(a) == *(b) && strcmp((a), (b)) == 0)

char *progname;

static void
usage(void)
{
#define USAGE   "\
  [-c]             Coordinate variable data and header information\n\
  [-h]             Header information only, no data\n\
  [-v var1[,...]]  Data for variable(s) <var1>,... only\n\
  [-b [c|f]]       Brief annotations for C or Fortran indices in data\n\
  [-f [c|f]]       Full annotations for C or Fortran indices in data\n\
  [-l len]         Line length maximum in data section (default 80)\n\
  [-n name]        Name for netCDF (default derived from file name)\n\
  [-p n[,n]]       Display floating-point values with less precision\n\
  [-x]             Output XML (NcML) instead of CDL\n\
  [-k]             Output kind of netCDF file\n\
  file             Name of netCDF file\n"

    (void) fprintf(stderr,
		   "%s [-c|-h] [-v ...] [[-b|-f] [c|f]] [-l len] [-n name] [-p n[,n]] [-x] [-k] file\n%s",
		   progname,
		   USAGE);
    
    (void) fprintf(stderr,
                 "netcdf library version %s\n",
                 nc_inq_libvers());
}


/* 
 * convert pathname of netcdf file into name for cdl unit, by taking 
 * last component of path and stripping off any extension.
 */
static char *
name_path(const char *path)
{
    const char *cp;
    char *new;
    char *sp;

#ifdef vms
#define FILE_DELIMITER ']'
#endif    
#ifdef MSDOS
#define FILE_DELIMITER '\\'
#endif    
#ifndef FILE_DELIMITER /* default to unix */
#define FILE_DELIMITER '/'
#endif
    cp = strrchr(path, FILE_DELIMITER);
    if (cp == 0)		/* no delimiter */
      cp = path;
    else			/* skip delimeter */
      cp++;
    new = (char *) emalloc((unsigned) (strlen(cp)+1));
    (void) strcpy(new, cp);	/* copy last component of path */
    if ((sp = strrchr(new, '.')) != NULL)
      *sp = '\0';		/* strip off any extension */
    return new;
}


static const char *
type_name(nc_type type)
{
    switch (type) {
      case NC_BYTE:
	return "byte";
      case NC_CHAR:
	return "char";
      case NC_SHORT:
	return "short";
      case NC_INT:
	return "int";
      case NC_FLOAT:
	return "float";
      case NC_DOUBLE:
	return "double";
#ifdef USE_NETCDF4
      case NC_UBYTE:
	return "ubyte";
      case NC_USHORT:
	return "ushort";
      case NC_UINT:
	return "uint";
      case NC_INT64:
	return "long";
      case NC_UINT64:
	return "ulong";
      case NC_STRING:
	return "string";
      case NC_VLEN:
	return "vlen";
      case NC_OPAQUE:
	return "opaque";
      case NC_COMPOUND:
	return "compound";
#endif
      default:
	error("type_name: bad type %d", type);
	return "bogus";
    }
}


/*
 * Remove trailing zeros (after decimal point) but not trailing decimal
 * point from ss, a string representation of a floating-point number that
 * might include an exponent part.
 */
static void
tztrim(char *ss)
{
    char *cp, *ep;
    
    cp = ss;
    if (*cp == '-')
      cp++;
    while(isdigit((int)*cp) || *cp == '.')
      cp++;
    if (*--cp == '.')
      return;
    ep = cp+1;
    while (*cp == '0')
      cp--;
    cp++;
    if (cp == ep)
      return;
    while (*ep)
      *cp++ = *ep++;
    *cp = '\0';
    return;
}


/* 
 * Emit kind of netCDF file
 */
static void 
do_nckind(const char *path)
{
    int nc_status;
    int ncid;
    int nc_kind;
    char *kind_str;

    nc_status = nc_open(path, NC_NOWRITE, &ncid);
    if (nc_status != NC_NOERR) {
	error("%s: %s", path, nc_strerror(nc_status));
    }
  
   /*nc_set_log_level(3);*/
    
    NC_CHECK( nc_inq_format(ncid, &nc_kind) );
    switch(nc_kind) {
    case NC_FORMAT_CLASSIC:
	kind_str = "classic";
	break;
    case NC_FORMAT_64BIT:
	kind_str = "64-bit-offset";
	break;
    case NC_FORMAT_NETCDF4:
	kind_str = "hdf5";
	break;
    case NC_FORMAT_NETCDF4_CLASSIC:
	kind_str = "hdf5-nc3";
	break;
    default:
	error("unrecognized format: %s", path);
	break;
    }
    Printf ("%s\n", kind_str);

    NC_CHECK( nc_close(ncid) );
}


/* 
 * Emit initial line of output for NcML
 */
static void 
pr_initx(int ncid, const char *path)
{
    Printf("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<netcdf xmlns=\"http://www.unidata.ucar.edu/namespaces/netcdf/ncml-2.2\" location=\"%s\">\n", 
	   path);
}


/*
 * Print attribute string, for text attributes.
 */
static void
pr_att_string(
     size_t len,
     const char *string
     )
{
    int iel;
    const char *cp;
    const char *sp;
    unsigned char uc;

    cp = string;
    Printf ("\"");
    /* adjust len so trailing nulls don't get printed */
    sp = cp + len - 1;
    while (len != 0 && *sp-- == '\0')
	len--;
    for (iel = 0; iel < len; iel++)
	switch (uc = *cp++ & 0377) {
	case '\b':
	    Printf ("\\b");
	    break;
	case '\f':
	    Printf ("\\f");
	    break;
	case '\n':		/* generate linebreaks after new-lines */
	    Printf ("\\n\",\n\t\t\t\"");
	    break;
	case '\r':
	    Printf ("\\r");
	    break;
	case '\t':
	    Printf ("\\t");
	    break;
	case '\v':
	    Printf ("\\v");
	    break;
	case '\\':
	    Printf ("\\\\");
	    break;
	case '\'':
	    Printf ("\\'");
	    break;
	case '\"':
	    Printf ("\\\"");
	    break;
	default:
	    if (iscntrl(uc))
	        Printf ("\\%03o",uc);
	    else
	        Printf ("%c",uc);
	    break;
	}
    Printf ("\"");

}


/*
 * Print list of attribute values, for numeric attributes.  Attribute values
 * must be printed with explicit type tags, because CDL doesn't have explicit
 * syntax to declare an attribute type.
 */
static void
pr_att_vals(
     nc_type type,
     size_t len,
     const double *vals
     )
{
    int iel;
    signed char sc;
    short ss;
    int ii;
    char gps[30];
    float ff;
    double dd;
#ifdef USE_NETCDF4
    unsigned char uc;
    unsigned short us;
    unsigned int ui;
    int64_t i64;
    uint64_t ui64;
#endif /* USE_NETCDF4 */
    if (len == 0)
	return;
    for (iel = 0; iel < len-1; iel++) {
	switch (type) {
	case NC_BYTE:
	    sc = (signed char) vals[iel] & 0377;
	    Printf ("%db, ", sc);
	    break;
	case NC_SHORT:
	    ss = vals[iel];
	    Printf ("%ds, ", ss);
	    break;
	case NC_INT:
	    ii = (int) vals[iel];
	    Printf ("%d, ", ii);
	    break;
	case NC_FLOAT:
	    ff = vals[iel];
	    (void) sprintf(gps, float_att_fmt, ff);
	    tztrim(gps);	/* trim trailing 0's after '.' */
	    Printf ("%s, ", gps);
	    break;
	case NC_DOUBLE:
	    dd = vals[iel];
	    (void) sprintf(gps, double_att_fmt, dd);
	    tztrim(gps);
	    Printf ("%s, ", gps);
	    break;
#ifdef USE_NETCDF4
	case NC_UBYTE:
	    uc = vals[iel];
	    Printf ("%udub, ", uc);
	    break;
	case NC_USHORT:
	    us = vals[iel];
	    Printf ("%huus, ", us);
	    break;
	case NC_UINT:
	    ui = vals[iel];
	    Printf ("%u, ", ui);
	    break;
	case NC_INT64:
	    i64 = vals[iel];
	    Printf ("%lldL, ", i64);
	    break;
	case NC_UINT64:
	    ui64 = vals[iel];
	    Printf ("%lluUL, ", ui64);
	    break;
#endif
	default:
	    error("pr_att_vals: bad type");
	}
    }
    switch (type) {
    case NC_BYTE:
	sc = (signed char) vals[iel] & 0377;
	Printf ("%db", sc);
	break;
    case NC_SHORT:
	ss = vals[iel];
	Printf ("%ds", ss);
	break;
    case NC_INT:
	ii = (int) vals[iel];
	Printf ("%d", ii);
	break;
    case NC_FLOAT:
	ff = vals[iel];
	(void) sprintf(gps, float_att_fmt, ff);
	tztrim(gps);
	Printf ("%s", gps);
	break;
    case NC_DOUBLE:
	dd = vals[iel];
	(void) sprintf(gps, double_att_fmt, dd);
	tztrim(gps);
	Printf ("%s", gps);
	break;
#ifdef USE_NETCDF4
	case NC_UBYTE:
	    uc = vals[iel];
	    Printf ("%udub", uc);
	    break;
	case NC_USHORT:
	    us = vals[iel];
	    Printf ("%huus", us);
	    break;
	case NC_UINT:
	    ui = vals[iel];
	    Printf ("%u", ui);
	    break;
	case NC_INT64:
	    i64 = vals[iel];
	    Printf ("%lldL", i64);
	    break;
	case NC_UINT64:
	    ui64 = vals[iel];
	    Printf ("%lluUL", ui64);
	    break;
#endif
    default:
	error("pr_att_vals: bad type");
    }
}

#ifndef HAVE_STRLCAT
/*	$OpenBSD: strlcat.c,v 1.12 2005/03/30 20:13:52 otto Exp $	*/

/*
 * Copyright (c) 1998 Todd C. Miller <Todd.Miller@courtesan.com>
 *
 * Permission to use, copy, modify, and distribute this software for any
 * purpose with or without fee is hereby granted, provided that the above
 * copyright notice and this permission notice appear in all copies.
 *
 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 */

/*
 * Appends src to string dst of size siz (unlike strncat, siz is the
 * full size of dst, not space left).  At most siz-1 characters
 * will be copied.  Always NUL terminates (unless siz <= strlen(dst)).
 * Returns strlen(src) + MIN(siz, strlen(initial dst)).
 * If retval >= siz, truncation occurred.
 */
size_t
strlcat(char *dst, const char *src, size_t siz)
{
	char *d = dst;
	const char *s = src;
	size_t n = siz;
	size_t dlen;

	/* Find the end of dst and adjust bytes left but don't go past end */
	while (n-- != 0 && *d != '\0')
		d++;
	dlen = d - dst;
	n = siz - dlen;

	if (n == 0)
		return(dlen + strlen(s));
	while (*s != '\0') {
		if (n != 1) {
			*d++ = *s;
			n--;
		}
		s++;
	}
	*d = '\0';

	return(dlen + (s - src));	/* count does not include NUL */
}
#endif /* ! HAVE_STRLCAT */


/*
 * Print list of numeric attribute values to string for use in NcML output.
 * Unlike CDL, NcML makes type explicit, so con't need type suffixes.
 */
static void
pr_att_valsx(
     nc_type type,
     size_t len,
     const double *vals,
     char *attvals,		/* returned string */
     size_t attvalslen		/* size of attvals buffer, assumed
				   large enough to hold all len
				   blank-separated values */
     )
{
    int iel;
    float ff;
    double dd;
    int ii;
#ifdef USE_NETCDF4
    unsigned int ui;
    int64_t i64;
    uint64_t ui64;
#endif /* USE_NETCDF4 */

    attvals[0]='\0';
    if (len == 0)
	return;
    for (iel = 0; iel < len; iel++) {
	char gps[50];
	switch (type) {
	case NC_BYTE:
	case NC_SHORT:
	case NC_INT:
	    ii = vals[iel];
	    (void) sprintf(gps, "%d", ii);
	    (void) strlcat(attvals, gps, attvalslen);
	    (void) strlcat(attvals, iel < len-1 ? " " : "", attvalslen);
	    break;
#ifdef USE_NETCDF4
	case NC_UBYTE:
	case NC_USHORT:
	case NC_UINT:
	    ui = vals[iel];
	    (void) sprintf(gps, "%u", ui);
	    (void) strlcat(attvals, gps, attvalslen);
	    (void) strlcat(attvals, iel < len-1 ? " " : "", attvalslen);
	    break;
	case NC_INT64:
	    /* TODO, can't really store a 64-bit int in a double so need different function */
	    i64 = vals[iel];
	    (void) sprintf(gps, "%lld", i64);
	    (void) strlcat(attvals, gps, attvalslen);
	    (void) strlcat(attvals, iel < len-1 ? " " : "", attvalslen);
	    break;
	case NC_UINT64:
	    /* TODO, can't really store a 64-bit int in a double so need different function */
	    ui64 = vals[iel];
	    (void) sprintf(gps, "%llu", ui64);
	    (void) strlcat(attvals, gps, attvalslen);
	    (void) strlcat(attvals, iel < len-1 ? " " : "", attvalslen);
	    break;
#endif /* USE_NETCDF4 */
	case NC_FLOAT:
	    ff = vals[iel];
	    (void) sprintf(gps, float_attx_fmt, ff);
	    tztrim(gps);	/* trim trailing 0's after '.' */
	    (void) strlcat(attvals, gps, attvalslen);
	    (void) strlcat(attvals, iel < len-1 ? " " : "", attvalslen);
	    break;
	case NC_DOUBLE:
	    dd = vals[iel];
	    (void) sprintf(gps, double_att_fmt, dd);
	    tztrim(gps);	/* trim trailing 0's after '.' */
	    (void) strlcat(attvals, gps, attvalslen);
	    (void) strlcat(attvals, iel < len-1 ? " " : "", attvalslen);
	    break;
	default:
	    error("pr_att_valsx: bad type");
	}
    }
}

#ifdef USE_NETCDF4
/* Print the contents of a VLEN data type. */
static void
pr_vlen (nc_type base_type, nc_vlen_t *vdata)
{
   int v;

   printf("\t[");
   switch (base_type)
   {
      case NC_BYTE:
      case NC_CHAR:
	 break;
      case NC_SHORT:
	 break;
      case NC_INT:
	 for (v = 0; v < vdata->len ; v++)
	    printf("%d, ", ((int *)vdata->p)[v]);
	 break;
      case NC_FLOAT:
	 break;
      case NC_DOUBLE:
	 break;
      case NC_UBYTE:
	 break;
      case NC_USHORT:
	 break;
      case NC_UINT:
	 break;
      case NC_INT64:
	 break;
      case NC_UINT64:
	 break;
      case NC_STRING:
	 break;
      default:
	 printf("Unknown VLEN base type!");
   }
   printf("]\n");

}

/* Print the contents of a Compound data type. */
static int
pr_cmp (int ncid, nc_type typeid, void *data)
{
   char name[NC_MAX_NAME + 1], field_name[NC_MAX_NAME + 1];
   size_t size, nfields, offset;
   nc_type field_type;
   int ndims, dim_sizes[NC_MAX_DIMS];
   
   int f;
   int ret;

   if ((ret = nc_inq_compound(ncid, typeid, name, &size, &nfields)))
      return ret;

   for (f = 0; f < nfields; f++)
   {

      if ((ret = nc_inq_compound_field(ncid, typeid, f, field_name, &offset, 
				       &field_type, &ndims, dim_sizes)))
	 return ret;
      printf("\t%s: ", field_name);

#define MAX_FORMAT_LEN 5
#define NTYPES 12
/*      char format[NTYPES][MAX_FORMAT_LEN] = {"%d ","%d ","%d ","%d ","%ld ","%f ","%g ",
	"%d ","%d ","%d ","%ld","%ld","%s "};*/

/*      if (field_type <= NC_STRING)
      {
	 if (ndims)
	 {
	    for (v = 0; v < vdata->len ; v++)
	       printf(format[field_type], ((int *)vdata->p)[v]);
	 }
	 else
	 {
	    switch (field_type) {
	       case NC_BYTE:
		  printf("%d ", *((signed char *)data)++);
		  break;
	       case NC_CHAR:
		  printf("%d ", *((unsigned char *)data)++);
		  break;
	       case NC_SHORT:
		  printf("%d ", *((short *)data)++);
		  break;
	       case NC_INT:
		  printf("%d ", *((int *)data)++);
		  break;
	       case NC_FLOAT:
		  printf("%f ", *((float *)data)++);
		  break;
	       case NC_DOUBLE:
		  printf("%g ", *((double *)data)++);
		  break;
#ifdef USE_NETCDF4
	       case NC_UBYTE:
		  printf("%d ", *((unsigned char *)data)++);
		  break;
	       case NC_USHORT:
		  printf("%d ", *((unsigned short *)data)++);
		  break;
	       case NC_UINT:
		  printf("%d ", *((unsigned int *)data)++);
		  break;
	       case NC_INT64:
		  printf("%d ", *((signed long long *)data)++);
		  break;
	       case NC_UINT64:
		  printf("%d ", *((unsigned long long *)data)++);
		  break;
	       case NC_STRING:
		  printf("%d ", *((unsigned long long *)data)++);
		  break;
	       case NC_VLEN:
		  return "vlen";
	       case NC_OPAQUE:
		  return "opaque";
	       case NC_COMPOUND:
	return "compound";
#endif
      default:
	error("type_name: bad type %d", type);
	return "bogus";
    }
*/

   }
   return NC_NOERR;
}
#endif /* USE_NETCDF4 */

static void
pr_att(
    int ncid,
    int varid,
    const char *varname,
    int ia
    )
{
    ncatt_t att;			/* attribute */
	    
    NC_CHECK( nc_inq_attname(ncid, varid, ia, att.name) );

    Printf ("\t\t%s:%s = ", varname, att.name);

    NC_CHECK( nc_inq_att(ncid, varid, att.name, &att.type, &att.len) );

    if (att.len == 0) {	/* show 0-length attributes as empty strings */
	att.type = NC_CHAR;
    }
    if (att.type == NC_CHAR)
    {
       att.string = (char *) emalloc(att.len + 1);
       NC_CHECK( nc_get_att_text(ncid, varid, att.name, att.string ) );
       pr_att_string(att.len, att.string);
       free(att.string);
    }
#ifdef USE_NETCDF4
    else if (att.type <= NC_UINT64)
#else
    else if (att.type <= NC_DOUBLE)
#endif
    {
	att.vals = (double *) emalloc((att.len + 1) * sizeof(double));
	NC_CHECK( nc_get_att_double(ncid, varid, att.name, att.vals ) );
	pr_att_vals(att.type, att.len, att.vals);
	free(att.vals);
    }
#ifdef USE_NETCDF4
    else if (att.type == NC_STRING)
    {
       printf("string type!\n");
    }
    else /* User-defined type. */
    {
       char type_name[NC_MAX_NAME + 1];
       size_t type_size, nfields;
       nc_type base_nc_type;
       int class, i;
       void *data;
       nc_vlen_t *vdata;

       NC_CHECK( nc_inq_user_type(ncid, att.type,  type_name, &type_size, &base_nc_type,
				  &nfields, &class));
       switch(class)
       {
	  case NC_VLEN:
	     data = malloc(att.len * sizeof(nc_vlen_t));
	     break;
	  case NC_OPAQUE:
	     break;
	  case NC_ENUM:
	     break;
	  case NC_COMPOUND:
	     break;
	  default:
	     error("unrecognized class of user defined type: %d", class);
       }

       NC_CHECK( nc_get_att(ncid, varid, att.name, data));

       switch(class)
       {
	  case NC_VLEN:
	     vdata = data;
	     for (i = 0; i < att.len; i++)
	     {
		pr_vlen(base_nc_type, &(vdata[i]));
		nc_free_vlen(vdata[i]);
	     }
	     free(vdata);
	     break;
	  case NC_OPAQUE:
	     for (i = 0; i < att.len; i++)
		;
	     break;
	  case NC_ENUM:
	     for (i = 0; i < att.len; i++)
		;
	     break;
	  case NC_COMPOUND:
	     for (i = 0; i < att.len; i++)
	     {
/*		pr_cmp(att.type, *data);
		data += type_size;*/
	     }
       
	     break;
	  default:
	     error("unrecognized class of user defined type: %d", class);
       }
       

    }
#endif

    Printf (" ;\n");
}


static void
pr_attx(
    int ncid,
    int varid,
    int ia
    )
{
    ncatt_t att;			/* attribute */
    char *attvals;
    int attvalslen = 0;

    NC_CHECK( nc_inq_attname(ncid, varid, ia, att.name) );
    NC_CHECK( nc_inq_att(ncid, varid, att.name, &att.type, &att.len) );

    /* Put attribute values into a single string, with blanks in between */

    switch (att.type) {
    case NC_CHAR:
#ifdef USE_NETCDF4
	/* fall through */
    case NC_STRING:
#endif
	attvals = (char *) emalloc(att.len + 1);
	NC_CHECK( nc_get_att_text(ncid, varid, att.name, attvals ) );
	break;
#ifdef USE_NETCDF4
	/* TODO
	   case NC_VLEN:
	   case NC_OPAQUE:
	   case NC_COMPOUND:
	*/
#endif
    default:
	att.vals = (double *) emalloc((att.len + 1) * sizeof(double));
	NC_CHECK( nc_get_att_double(ncid, varid, att.name, att.vals ) );
	attvalslen = 20*att.len; /* max 20 chars for each value and blank separator */
	attvals = (char *) emalloc(attvalslen + 1);
	pr_att_valsx(att.type, att.len, att.vals, attvals, attvalslen);
	free(att.vals); 
	break;
    }

    Printf ("%s  <attribute name=\"%s\" type=\"%s\" value=\"%s\" />\n", 
	    varid != NC_GLOBAL ? "  " : "", 
	    att.name, 
	    att.type == NC_CHAR ? "String" : type_name(att.type), 
	    attvals);
    free (attvals);
}


/* Print optional NcML attribute for a variable's shape */
static void
pr_shape(ncvar_t* varp, ncdim_t *dims)
{
    char *shape;
    int shapelen = 0;
    int id;

    if (varp->ndims == 0)
	return;
    for (id = 0; id < varp->ndims; id++) {
	shapelen += strlen(dims[varp->dims[id]].name) + 1;
    }
    shape = (char *) emalloc(shapelen);
    shape[0] = '\0';
    for (id = 0; id < varp->ndims; id++) {
	strlcat(shape, dims[varp->dims[id]].name, shapelen);
	strlcat(shape, id < varp->ndims-1 ? " " : "", shapelen);
    }
    Printf (" shape=\"%s\"", shape);
    free(shape);
}

/* Recursively dump the contents of a group. (Recall that only
 * netcdf-4 format files can have groups. On all other formats, there
 * is just a root group, so recursion will not take place.) */
static void
do_ncdump_rec(int ncid, const char *path, fspec_t* specp)
{
   int ndims;			/* number of dimensions */
   int nvars;			/* number of variables */
   int ngatts;			/* number of global attributes */
   int xdimid;			/* id of unlimited dimension */
   int varid;			/* variable id */
   ncdim_t *dims;		/* dimensions */
   size_t *vdims=0;	   /* dimension sizes for a single variable */
   ncvar_t var;			/* variable */
   ncatt_t att;			/* attribute */
   int id;			/* dimension number per variable */
   int ia;			/* attribute number */
   int iv;			/* variable number */
   vnode* vlist = 0;		/* list for vars specified with -v option */
   int nc_status;		/* return from netcdf calls */
#ifdef USE_NETCDF4
   int *dimids_grp;	       /* dimids of the dims in this group. */
   int varids_grp[NC_MAX_VARS]; /* varids of the vars in this group. */
   int d_grp, ndims_grp;
   int v_grp, nvars_grp;
   char dim_name[NC_MAX_NAME + 1];
   size_t len;
   int ntypes, *typeids;
#else
   int dimid;			/* dimension id */
#endif /* USE_NETCDF4 */

   /*
    * If any vars were specified with -v option, get list of associated
    * variable ids
    */
   if (specp->nlvars > 0) {
      vlist = newvlist();	/* list for vars specified with -v option */
      for (iv=0; iv < specp->nlvars; iv++) {
	 NC_CHECK( nc_inq_varid(ncid, specp->lvars[iv], &varid) );
	 varadd(vlist, varid);
      }
   }

#ifdef USE_NETCDF4
   /* Are there any user defined types in this group? */
   NC_CHECK( nc_inq_typeids(ncid, &ntypes, NULL) );
   if (ntypes)
   {
      int t;

      if (!(typeids = malloc(ntypes * sizeof(int))))
      {
	 Printf("Out of memory!\n");
	 return;
      }
      NC_CHECK( nc_inq_typeids(ncid, &ntypes, typeids) );
      for (t = 0; t < ntypes; t++)
      {
	 char type_name[NC_MAX_NAME + 1];
	 size_t type_nfields, type_size;
	 nc_type base_nc_type;
	 int f, type_class;

	 Printf("type:\n");
	 NC_CHECK( nc_inq_user_type(ncid, typeids[t], type_name, &type_size, &base_nc_type, 
				    &type_nfields, &type_class) );
	 switch(type_class)
	 {
	    case NC_VLEN:
	       printf("\tVLEN typeid: %d name: %s base type: %d;\n", typeids[t], 
		      type_name, base_nc_type);
	       break;
	    case NC_OPAQUE:
	       printf("\tOPAQUE typeid: %d name: %s size: %d;\n", typeids[t], 
		      type_name, (int)type_size);
	       break;
	    case NC_ENUM:
	       printf("\tENUM typeid: %d name: %s base type: %d;\n", typeids[t], 
		      type_name, base_nc_type);
	       for (f = 0; f < type_nfields; f++)
	       {
		  long long member_value;
		  char member_name[NC_MAX_NAME + 1];

		  NC_CHECK( nc_inq_enum_member(ncid, typeids[t], f, member_name, &member_value) );
		  printf("\t%s %lld\n", member_name, member_value);
	       }
	       break;
	    case NC_COMPOUND:
	       {
		  char field_name[NC_MAX_NAME + 1];
		  size_t field_offset;
		  nc_type field_type;
		  int field_ndims, field_dim_sizes[NC_MAX_DIMS];
		  int d;

		  printf("\tCOMPOUND typeid: %d name: %s\n", typeids[t], type_name);
		  for (f = 0; f < type_nfields; f++)
		  {
		     NC_CHECK( nc_inq_compound_field(ncid, typeids[t], f, field_name, 
						     &field_offset, &field_type, &field_ndims,
						     field_dim_sizes) );
		     printf("\t%s %d %d\n", field_name, (int)field_offset, field_type);
		     for (d = 0; d < field_ndims; d++)
			printf("\t\t%d ", field_dim_sizes[d]);
		     if (field_ndims) printf("\n");
		  }
	       }
	       break;
	    default:
	       printf("\tUnknown class of user-defined type!\n");
	       
	 }

      }
      free(typeids);
   }
#endif /* USE_NETCDF4 */

   /*
    * get number of dimensions, number of variables, number of global
    * atts, and dimension id of unlimited dimension, if any
    */
   NC_CHECK( nc_inq(ncid, &ndims, &nvars, &ngatts, &xdimid) );
   /* get dimension info */
   dims = (ncdim_t *) emalloc((ndims + 1) * sizeof(ncdim_t));
   if (ndims > 0)
       Printf ("dimensions:\n");

#ifdef USE_NETCDF4
   /* In netCDF-4 files, dimids will not be sequential because they
    * may be defined in various groups, and we are only looking at one
    * group at a time. */

   /* Find the number of dimids defined in this group. */
   NC_CHECK( nc_inq_ndims(ncid, &ndims_grp) );
   dimids_grp = (int *)emalloc((ndims_grp + 1) * sizeof(int));
   
   /* Find the group ids. */
   NC_CHECK( nc_inq_dimids(ncid, 0, dimids_grp, 0) );
    
   /* For each dimension defined in this group, learn, and print out
    * info. */
   for (d_grp = 0; d_grp < ndims_grp; d_grp++)
   {
      NC_CHECK( nc_inq_dim(ncid, dimids_grp[d_grp], dims[d_grp].name, &dims[d_grp].size) );
      if (dimids_grp[d_grp] == xdimid)
	 Printf ("\t%s = %s ; // (%u currently)\n",dims[d_grp].name,
		 "UNLIMITED", (unsigned int)dims[d_grp].size);
      else
	 Printf ("\t%s = %u ;\n", dims[d_grp].name, (unsigned int)dims[d_grp].size);
   }
   if(dimids_grp)
       free(dimids_grp);
#else /* not using netCDF-4 */
   for (dimid = 0; dimid < ndims; dimid++) {
      NC_CHECK( nc_inq_dim(ncid, dimid, dims[dimid].name, &dims[dimid].size) );
      if (dimid == xdimid)
	 Printf ("\t%s = %s ; // (%u currently)\n",dims[dimid].name,
		 "UNLIMITED", (unsigned int)dims[dimid].size);
      else
	 Printf ("\t%s = %u ;\n", dims[dimid].name, (unsigned int)dims[dimid].size);
   }
#endif /* USE_NETCDF4 */

   if (nvars > 0)
      Printf ("variables:\n");
#ifdef USE_NETCDF4
   /* In netCDF-4 files, varids will not be sequentially numberer
    * because they may be defined in various groups, and we are only
    * looking at one group at a time. */

   /* Find the number of varids defined in this group, and their ids. */
   NC_CHECK( nc_inq_varids(ncid, &nvars_grp, varids_grp) );
    
   /* For each var defined in this group, learn, and print out
    * info. */
   for (v_grp = 0; v_grp < nvars_grp; v_grp++)
   {
      /* Learn about the var and its dimension ids. */
      NC_CHECK( nc_inq_varndims(ncid, varids_grp[v_grp], &var.ndims) );
      var.dims = (int *) emalloc((var.ndims + 1) * sizeof(int));
      NC_CHECK( nc_inq_var(ncid, varids_grp[v_grp], var.name, &var.type, 0,
			   var.dims, &var.natts) );

      /* Display the var info for the user. */
      Printf ("\t%s %s", type_name(var.type), var.name);
      if (var.ndims > 0)
	 Printf ("(");
      for (id = 0; id < var.ndims; id++) 
      {
	 /* This dim may be in a parent group, so let's look up the
	  * name. */
	 NC_CHECK( nc_inq_dimname(ncid, var.dims[id], dim_name) );
	 Printf ("%s%s",
		 dim_name,
		 id < var.ndims-1 ? ", " : ")");
      }
      Printf (" ;\n");

      /* get variable attributes */
      for (ia = 0; ia < var.natts; ia++)
	 pr_att(ncid, varids_grp[v_grp], var.name, ia); /* print ia-th attribute */
      free(var.dims);
   }
#else /* not using netCDF-4 */
   /* get variable info, with variable attributes */
   for (varid = 0; varid < nvars; varid++) {
      NC_CHECK( nc_inq_varndims(ncid, varid, &var.ndims) );
      var.dims = (int *) emalloc((var.ndims + 1) * sizeof(int));
      NC_CHECK( nc_inq_var(ncid, varid, var.name, &var.type, 0,
			   var.dims, &var.natts) );
      Printf ("\t%s %s", type_name(var.type), var.name);
      if (var.ndims > 0)
	 Printf ("(");
      for (id = 0; id < var.ndims; id++) {
	 Printf ("%s%s",
		 dims[var.dims[id]].name,
		 id < var.ndims-1 ? ", " : ")");
      }
      Printf (" ;\n");
      free(var.dims);

      /* get variable attributes */
      for (ia = 0; ia < var.natts; ia++)
	 pr_att(ncid, varid, var.name, ia); /* print ia-th attribute */
   }
#endif /* USE_NETCDF4 */

   /* get global attributes */
   if (ngatts > 0)
      Printf ("\n// global attributes:\n");
   for (ia = 0; ia < ngatts; ia++)
      pr_att(ncid, NC_GLOBAL, "", ia); /* print ia-th global attribute */
    
   if (! specp->header_only) {
      if (nvars > 0) {
	 Printf ("data:\n");
      }
#ifdef USE_NETCDF4
      /* output variable data */
      for (v_grp = 0; v_grp < nvars_grp; v_grp++)
      {
	 varid = varids_grp[v_grp];
	 /* if var list specified, test for membership */
	 if (specp->nlvars > 0 && ! varmember(vlist, varid))
	    continue;
	 NC_CHECK( nc_inq_varndims(ncid, varid, &var.ndims) );
	 var.dims = (int *) emalloc((var.ndims + 1) * sizeof(int));
	 NC_CHECK( nc_inq_var(ncid, varid, var.name, &var.type, 0,
			      var.dims, &var.natts) );

	 /* If coords-only option specified, don't get data for
	  * non-coordinate vars */
	 if (specp->coord_vals && !iscoordvar(ncid,varid)) {
	    continue;
	 }

	 /* Don't get data for record variables if no records have
	  * been written yet */
	 if (isrecvar(ncid, varid) && dims[xdimid].size == 0) {
	    continue;
	 }
		
	 /* Collect variable's dim sizes */
	 if (vdims) {
	     free(vdims);
	     vdims = 0;
	 }
	 vdims = (size_t *) emalloc((var.ndims + 1) * sizeof(size_t));
	 for (id = 0; id < var.ndims; id++)
	 {
	    NC_CHECK( nc_inq_dimlen(ncid, var.dims[id], &len) );
	    vdims[id] = len;
	 }
	 var.has_fillval = 1; /* by default, but turn off for bytes */
	    
	 /* get _FillValue attribute */
	 nc_status = nc_inq_att(ncid,varid,_FillValue,&att.type,&att.len);
	 if(nc_status == NC_NOERR &&
	    att.type == var.type && att.len == 1) {
	    if(var.type == NC_CHAR) {
	       char fillc;
	       NC_CHECK( nc_get_att_text(ncid, varid, _FillValue,
					 &fillc ) );
	       var.fillval = fillc;
	    } else {
	       NC_CHECK( nc_get_att_double(ncid, varid, _FillValue,
					   &var.fillval) );
	    }
	 } else {
	    switch (var.type) {
	       case NC_BYTE:
		  /* don't do default fill-values for bytes, too risky */
		  var.has_fillval = 0;
		  break;
	       case NC_CHAR:
		  var.fillval = NC_FILL_CHAR;
		  break;
	       case NC_SHORT:
		  var.fillval = NC_FILL_SHORT;
		  break;
	       case NC_INT:
		  var.fillval = NC_FILL_INT;
		  break;
	       case NC_FLOAT:
		  var.fillval = NC_FILL_FLOAT;
		  break;
	       case NC_DOUBLE:
		  var.fillval = NC_FILL_DOUBLE;
		  break;
	       default:
		  var.has_fillval = 0;
		  break;
	    }
	 }
	 if (vardata(&var, vdims, ncid, varid, specp) == -1) {
	    error("can't output data for variable %s", var.name);
	    NC_CHECK(
	       nc_close(ncid) );
	    if (vlist)
	       free(vlist);
	    return;
	 }
      }
#else /* not using netCDF-4 */
      /* output variable data */
      for (varid = 0; varid < nvars; varid++) {
	 /* if var list specified, test for membership */
	 if (specp->nlvars > 0 && ! varmember(vlist, varid))
	    continue;
	 NC_CHECK( nc_inq_varndims(ncid, varid, &var.ndims) );
	 var.dims = (int *) emalloc((var.ndims + 1) * sizeof(int));
	 NC_CHECK( nc_inq_var(ncid, varid, var.name, &var.type, 0,
			      var.dims, &var.natts) );

	 /* If coords-only option specified, don't get data for
	  * non-coordinate vars */
	 if (specp->coord_vals && !iscoordvar(ncid,varid)) {
	    continue;
	 }

	 /* Don't get data for record variables if no records have
	  * been written yet */
	 if (isrecvar(ncid, varid) && dims[xdimid].size == 0) {
	    continue;
	 }
		
	 /* Collect variable's dim sizes */
	 if (vdims) {
	     free(vdims);
	     vdims = 0;
	 }
	 vdims = (size_t *) emalloc((var.ndims + 1) * sizeof(size_t));
	 for (id = 0; id < var.ndims; id++)
	     vdims[id] = dims[var.dims[id]].size;
	 for (id = 0; id < var.ndims; id++)
	    vdims[id] = dims[var.dims[id]].size;
	 var.has_fillval = 1; /* by default, but turn off for bytes */
	    
	 /* get _FillValue attribute */
	 nc_status = nc_inq_att(ncid,varid,_FillValue,&att.type,&att.len);
	 if(nc_status == NC_NOERR &&
	    att.type == var.type && att.len == 1) {
	    if(var.type == NC_CHAR) {
	       char fillc;
	       NC_CHECK( nc_get_att_text(ncid, varid, _FillValue,
					 &fillc ) );
	       var.fillval = fillc;
	    } else {
	       NC_CHECK( nc_get_att_double(ncid, varid, _FillValue,
					   &var.fillval) );
	    }
	 } else {
	    switch (var.type) {
	       case NC_BYTE:
		  /* don't do default fill-values for bytes, too risky */
		  var.has_fillval = 0;
		  break;
	       case NC_CHAR:
		  var.fillval = NC_FILL_CHAR;
		  break;
	       case NC_SHORT:
		  var.fillval = NC_FILL_SHORT;
		  break;
	       case NC_INT:
		  var.fillval = NC_FILL_INT;
		  break;
	       case NC_FLOAT:
		  var.fillval = NC_FILL_FLOAT;
		  break;
	       case NC_DOUBLE:
		  var.fillval = NC_FILL_DOUBLE;
		  break;
	       default:
		  break;
	    }
	 }
	 if (vardata(&var, vdims, ncid, varid, specp) == -1) {
	    error("can't output data for variable %s", var.name);
	    NC_CHECK(
	       nc_close(ncid) );
	    if (vlist)
	       free(vlist);
	    return;
	 }
      }
#endif /* USE_NETCDF4 */
      if (vdims) {
	  free(vdims);
	  vdims = 0;
      }
   }
    
#ifdef USE_NETCDF4
   /* For netCDF-4 compiles, check to see if the file has any
    * groups. If it does, this function is called recursively on each
    * of them. */
   {
      int g, numgrps, *ncids, format;
      char group_name[NC_MAX_NAME + 1];

      /* Only netCDF-4 files have groups. */
      if ((nc_status = nc_inq_format(ncid, &format)))
	 error("can't get format"); 

      if (format == NC_FORMAT_NETCDF4)
      {
	 /* See how many groups there are. */
	 if ((nc_status = nc_inq_grps(ncid, &numgrps, NULL))) 
	    error("can't read groups"); 
	 
	 /* Allocate memory to hold the list of group ids. */
	 if (!(ncids = malloc(numgrps * sizeof(int))))
	    error("out of memory");
	 
	 /* Get the list of group ids. */
	 nc_status = nc_inq_grps(ncid, NULL, ncids);
	 
	 /* Call this function for each group. */
	 for (g = 0; g < numgrps; g++)
	 {
	    if (nc_inq_grpname(ncids[g], group_name))
	       error("can't find group's name");	       
	    Printf ("\ngroup: %s {\n", group_name);
	    do_ncdump_rec(ncids[g], NULL, specp);
	 }
	 
	 free(ncids);
      }
   }
#endif /* USE_NETCDF4 */

   Printf ("}\n");
   if (vlist)
      free(vlist);
   if (dims)
      free(dims);
}


static void
do_ncdump(const char *path, fspec_t* specp)
{
   int nc_status;
   int ncid;
  
   /*nc_set_log_level(3);*/

   nc_status = nc_open(path, NC_NOWRITE, &ncid);
   if (nc_status != NC_NOERR) {
      error("%s: %s", path, nc_strerror(nc_status));
   }

   /* output initial line */
   Printf ("netcdf %s {\n", specp->name);

   do_ncdump_rec(ncid, path, specp);
   NC_CHECK( nc_close(ncid) );
}


static void
do_ncdumpx(const char *path, fspec_t* specp)
{
    int ndims;			/* number of dimensions */
    int nvars;			/* number of variables */
    int ngatts;			/* number of global attributes */
    int xdimid;			/* id of unlimited dimension */
    int dimid;			/* dimension id */
    int varid;			/* variable id */
    ncdim_t *dims;		/* dimensions */
    ncvar_t var;		/* variable */
    int ia;			/* attribute number */
    int iv;			/* variable number */
    int ncid;			/* netCDF id */
    vnode* vlist = 0;		/* list for vars specified with -v option */
    int nc_status;		/* return from netcdf calls */

    nc_status = nc_open(path, NC_NOWRITE, &ncid);
    if (nc_status != NC_NOERR) {
	error("%s: %s", path, nc_strerror(nc_status));
    }
    /*
     * If any vars were specified with -v option, get list of associated
     * variable ids
     */
    if (specp->nlvars > 0) {
	vlist = newvlist();	/* list for vars specified with -v option */
	for (iv=0; iv < specp->nlvars; iv++) {
	    NC_CHECK( nc_inq_varid(ncid, specp->lvars[iv], &varid) );
	    varadd(vlist, varid);
	}
    }

    /* output initial line */
    pr_initx(ncid, path);

    /*
     * get number of dimensions, number of variables, number of global
     * atts, and dimension id of unlimited dimension, if any
     */
    NC_CHECK( nc_inq(ncid, &ndims, &nvars, &ngatts, &xdimid) );
    /* get dimension info */
    dims = (ncdim_t *) emalloc((ndims + 1) * sizeof(ncdim_t));
    for (dimid = 0; dimid < ndims; dimid++) {
	NC_CHECK( nc_inq_dim(ncid, dimid, dims[dimid].name, &dims[dimid].size) );
	if (dimid == xdimid)
  	  Printf("  <dimension name=\"%s\" length=\"%d\" isUnlimited=\"true\" />\n", 
		 dims[dimid].name, (int)dims[dimid].size);
	else
	  Printf ("  <dimension name=\"%s\" length=\"%d\" />\n", 
		  dims[dimid].name, (int)dims[dimid].size);
    }

    /* get global attributes */
    for (ia = 0; ia < ngatts; ia++)
	pr_attx(ncid, NC_GLOBAL, ia); /* print ia-th global attribute */

    /* get variable info, with variable attributes */
    for (varid = 0; varid < nvars; varid++) {
	NC_CHECK( nc_inq_varndims(ncid, varid, &var.ndims) );
	var.dims = (int *) emalloc((var.ndims + 1) * sizeof(int));
	NC_CHECK( nc_inq_var(ncid, varid, var.name, &var.type, 0,
			     var.dims, &var.natts) );
	Printf ("  <variable name=\"%s\"", var.name);
	pr_shape(&var, dims);

	/* handle one-line variable elements that aren't containers
	   for attributes or data values, since they need to be
	   rendered as <variable ... /> instead of <variable ..>
	   ... </variable> */
	if (var.natts == 0) {
	    if (
		/* header-only specified */
		(specp->header_only) ||
		/* list of variables specified and this variable not in list */
		(specp->nlvars > 0 && !varmember(vlist, varid))	||
		/* coordinate vars only and this is not a coordinate variable */
		(specp->coord_vals && !iscoordvar(ncid, varid)) ||
		/* this is a record variable, but no records have been written */
		(isrecvar(ncid,varid) && dims[xdimid].size == 0)
		) {
		Printf (" type=\"%s\" />\n", type_name(var.type));
		continue;
	    }
	}

	/* else nest attributes values, data values in <variable> ... </variable> */
	Printf (" type=\"%s\">\n", type_name(var.type));

	/* get variable attributes */
	for (ia = 0; ia < var.natts; ia++) {
	    pr_attx(ncid, varid, ia); /* print ia-th attribute */
	}
	Printf ("  </variable>\n");
    }
    
    Printf ("</netcdf>\n");
    NC_CHECK(
	nc_close(ncid) );
    if (vlist)
	free(vlist);
    if(dims)
	free(dims);
}


static void
make_lvars(char *optarg, fspec_t* fspecp)
{
    char *cp = optarg;
    int nvars = 1;
    char ** cpp;

    /* compute number of variable names in comma-delimited list */
    fspecp->nlvars = 1;
    while (*cp++)
      if (*cp == ',')
 	nvars++;

    fspecp->lvars = (char **) emalloc(nvars * sizeof(char*));

    cpp = fspecp->lvars;
    /* copy variable names into list */
    for (cp = strtok(optarg, ",");
	 cp != NULL;
	 cp = strtok((char *) NULL, ",")) {
	
	*cpp = (char *) emalloc(strlen(cp) + 1);
	strcpy(*cpp, cp);
	cpp++;
    }
    fspecp->nlvars = nvars;
}


/*
 * Extract the significant-digits specifiers from the -d argument on the
 * command-line and update the default data formats appropriately.
 */
static void
set_sigdigs(const char *optarg)
{
    char *ptr1 = 0;
    char *ptr2 = 0;
    int flt_digits = FLT_DIGITS; /* default floating-point digits */
    int dbl_digits = DBL_DIGITS; /* default double-precision digits */

    if (optarg != 0 && (int) strlen(optarg) > 0 && optarg[0] != ',')
        flt_digits = (int)strtol(optarg, &ptr1, 10);

    if (flt_digits < 1 || flt_digits > 20) {
	error("unreasonable value for float significant digits: %d",
	      flt_digits);
    }
    if (*ptr1 == ',')
      dbl_digits = (int)strtol(ptr1+1, &ptr2, 10);
    if (ptr2 == ptr1+1 || dbl_digits < 1 || dbl_digits > 20) {
	error("unreasonable value for double significant digits: %d",
	      dbl_digits);
    }
    set_formats(flt_digits, dbl_digits);
}


/*
 * Extract the significant-digits specifiers from the -p argument on the
 * command-line, set flags so we can override C_format attributes (if any),
 * and update the default data formats appropriately.
 */
static void
set_precision(const char *optarg)
{
    char *ptr1 = 0;
    char *ptr2 = 0;
    int flt_digits = FLT_DIGITS;	/* default floating-point digits */
    int dbl_digits = DBL_DIGITS;	/* default double-precision digits */

    if (optarg != 0 && (int) strlen(optarg) > 0 && optarg[0] != ',') {
        flt_digits = (int)strtol(optarg, &ptr1, 10);
	float_precision_specified = 1;
    }

    if (flt_digits < 1 || flt_digits > 20) {
	error("unreasonable value for float significant digits: %d",
	      flt_digits);
    }
    if (*ptr1 == ',') {
	dbl_digits = (int) strtol(ptr1+1, &ptr2, 10);
	double_precision_specified = 1;
    }
    if (ptr2 == ptr1+1 || dbl_digits < 1 || dbl_digits > 20) {
	error("unreasonable value for double significant digits: %d",
	      dbl_digits);
    }
    set_formats(flt_digits, dbl_digits);
}


int
main(int argc, char *argv[])
{
    extern int optind;
    extern int opterr;
    extern char *optarg;
    static fspec_t fspec =	/* defaults, overridden on command line */
      {
	  0,			/* construct netcdf name from file name */
	  false,		/* print header info only, no data? */
	  false,		/* just print coord vars? */
	  false,		/* brief  comments in data section? */
	  false,		/* full annotations in data section?  */
	  LANG_C,		/* language conventions for indices */
	  0,			/* if -v specified, number of variables */
	  0			/* if -v specified, list of variable names */
	  };
    int c;
    int i;
    int max_len = 80;		/* default maximum line length */
    int nameopt = 0;
    boolean xml_out = false;    /* if true, output NcML instead of CDL */
    boolean kind_out = false;	/* if true, just output kind of netCDF file */

    opterr = 1;
    progname = argv[0];
    set_formats(FLT_DIGITS, DBL_DIGITS); /* default for float, double data */

    /* If the user called ncdump without arguments, print the usage
     * message and return peacefully. */
    if (argc <= 1)
    {
       usage();
#ifdef vms
    exit(EXIT_SUCCESS);
#else
    return EXIT_SUCCESS;
#endif
    }

    while ((c = getopt(argc, argv, "b:cf:hkl:n:v:d:p:x")) != EOF)
      switch(c) {
	case 'h':		/* dump header only, no data */
	  fspec.header_only = true;
	  break;
	case 'c':		/* header, data only for coordinate dims */
	  fspec.coord_vals = true;
	  break;
	case 'n':		/*
				 * provide different name than derived from
				 * file name
				 */
	  fspec.name = optarg;
	  nameopt = 1;
	  break;
	case 'b':		/* brief comments in data section */
	  fspec.brief_data_cmnts = true;
	  switch (tolower(optarg[0])) {
	    case 'c':
	      fspec.data_lang = LANG_C;
	      break;
	    case 'f':
	      fspec.data_lang = LANG_F;
	      break;
	    default:
	      error("invalid value for -b option: %s", optarg);
	  }
	  break;
	case 'f':		/* full comments in data section */
	  fspec.full_data_cmnts = true;
	  switch (tolower(optarg[0])) {
	    case 'c':
	      fspec.data_lang = LANG_C;
	      break;
	    case 'f':
	      fspec.data_lang = LANG_F;
	      break;
	    default:
	      error("invalid value for -f option: %s", optarg);
	  }
	  break;
	case 'l':		/* maximum line length */
	  max_len = (int) strtol(optarg, 0, 0);
	  if (max_len < 10) {
	      error("unreasonably small line length specified: %d", max_len);
	  }
	  break;
	case 'v':		/* variable names */
	  /* make list of names of variables specified */
	  make_lvars (optarg, &fspec);
	  break;
	case 'd':		/* specify precision for floats (old option) */
	  set_sigdigs(optarg);
	  break;
	case 'p':		/* specify precision for floats */
	  set_precision(optarg);
	  break;
        case 'x':		/* XML output (NcML) */
	  xml_out = true;
	  break;
      case 'k':			/* just output what kind of netCDF file */
	  kind_out = true;
	  break;
      case '?':
	  usage();
	  return 0;
      }

    set_max_len(max_len);
    
    argc -= optind;
    argv += optind;

    /* If no file arguments left, print usage message. */
    if (argc < 1)
    {
       usage();
       return 0;
    }

    i = 0;

    do {		
        if (!nameopt) 
	    fspec.name = name_path(argv[i]);
	if (argc > 0) {
	    if (xml_out) {
		do_ncdumpx(argv[i], &fspec);
	    } else if (kind_out) {
		do_nckind(argv[i]);
	    } else {
		do_ncdump(argv[i], &fspec);
	    }
	}
    } while (++i < argc);
#ifdef vms
    exit(EXIT_SUCCESS);
#else
    return EXIT_SUCCESS;
#endif
}


syntax highlighted by Code2HTML, v. 0.9.1