/* VIPS universal main program.
 *
 * J. Cupitt, 8/4/93.
 * 12/5/06
 * 	- use GOption. g_*_prgname()
 * 16/7/06
 * 	- hmm, was broken for function name as argv1 case
 * 11/7/06
 * 	- add "all" option to -l
 * 14/7/06
 * 	- ignore "--" arguments.
 * 2/9/06
 * 	- do less init ... im_init_world() does more now
 * 18/8/06
 * 	- use IM_EXEEXT
 * 16/10/06
 * 	- add --version
 * 17/10/06
 * 	- add --swig
 * 	- cleanups
 * 	- remove --swig again, sigh
 * 	- add throw() decls to C++ to help SWIG
 * 14/1/07
 * 	- add --list packages
 * 26/2/07
 * 	- add input *VEC arg types to C++ binding
 * 17/8/08
 * 	- add --list formats
 * 29/11/08
 * 	- add --list interpolators
 * 9/2/09
 * 	- and now we just have --list packages/classes/package-name
 * 13/11/09
 * 	- drop _f postfixes, drop many postfixes
 * 24/6/10
 * 	- less chatty error messages
 * 	- oops, don't rename "copy_set" as "copy_"
 * 6/2/12
 * 	- long arg names in decls to help SWIG
 * 	- don't wrap im_remainderconst_vec()
 * 31/12/12
 * 	- parse options in two passes (thanks Haida)
 * 26/11/17
 * 	- remove throw() decls, they are now deprecated everywhere
 * 18/6/20 kleisauke
 * 	- avoid using vips7 symbols
 * 	- remove deprecated vips7 C++ generator
 * 1/11/22
 * 	- add "-c" flag
 */

/*

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

    This program 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 Lesser General Public License for more details.

    You should have received a copy of the GNU Lesser General Public License
    along with this program; if not, write to the Free Software
    Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
    02110-1301  USA

 */

/*

    These files are distributed with VIPS - http://www.vips.ecs.soton.ac.uk

 */

/*
#define DEBUG
#define DEBUG_FATAL
 */

/* Need to disable these sometimes.
#undef DEBUG_FATAL
 */

#ifdef HAVE_CONFIG_H
#include <config.h>
#endif /*HAVE_CONFIG_H*/
#include <glib/gi18n.h>

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

#define VIPS_DISABLE_DEPRECATION_WARNINGS
#include <vips/vips.h>
#include <vips/internal.h>

#if ENABLE_DEPRECATED
#include <vips/vips7compat.h>
#endif

static char *main_option_plugin = NULL;
static gboolean main_option_version;

static void *
list_class( GType type, void *user_data )
{
	VipsObjectClass *class = VIPS_OBJECT_CLASS( g_type_class_ref( type ) );
	int depth = vips_type_depth( type );

	int i;

	if( class->deprecated )
		return( NULL );
	if( VIPS_IS_OPERATION_CLASS( class ) &&
		(VIPS_OPERATION_CLASS( class )->flags & 
		 VIPS_OPERATION_DEPRECATED) )
		return( NULL ); 

	for( i = 0; i < depth * 2; i++ )
		printf( " " );
	vips_object_print_summary_class( 
		VIPS_OBJECT_CLASS( g_type_class_ref( type ) ) );

	return( NULL );
}

static void *
test_class_name( GType type, void *data )
{
	const char *name = (const char *) data;

	VipsObjectClass *class;

	/* Test the classname too.
	 */
	if( (class = VIPS_OBJECT_CLASS( g_type_class_ref( type ) )) &&
		strcmp( class->nickname, name ) == 0 ) 
		return( class ); 
	if( strcmp( G_OBJECT_CLASS_NAME( class ), name ) == 0 )
		return( class ); 

	return( NULL );
}

static gboolean
parse_main_option_list( const gchar *option_name, const gchar *value, 
	gpointer data, GError **error )
{
	VipsObjectClass *class;

	if( value &&
		(class = (VipsObjectClass *) vips_type_map_all( 
			g_type_from_name( "VipsObject" ), 
			test_class_name, (void *) value )) ) { 
		vips_type_map_all( G_TYPE_FROM_CLASS( class ), 
			list_class, NULL );
	}
	else if( value ) {
		vips_error( g_get_prgname(), 
			_( "'%s' is not the name of a vips class" ), value );
		vips_error_g( error );

		return( FALSE );
	}
	else {
		vips_type_map_all( g_type_from_name( "VipsOperation" ), 
			list_class, NULL );
	}

	exit( 0 );
}

static void *
list_operation( GType type, void *user_data )
{
	VipsObjectClass *class = VIPS_OBJECT_CLASS( g_type_class_ref( type ) );

	if( G_TYPE_IS_ABSTRACT( type ) )
		return( NULL ); 
	if( class->deprecated )
		return( NULL );
	if( VIPS_OPERATION_CLASS( class )->flags & VIPS_OPERATION_DEPRECATED )
		return( NULL ); 

	/* Complete on class names as well as nicknames -- "crop", for
	 * example, is a class name.
	 */
	printf( "%s\n", class->nickname );
	printf( "%s\n", G_OBJECT_CLASS_NAME( class ) );

	return( NULL );
}

static void *
list_operation_arg( VipsObjectClass *object_class, 
	GParamSpec *pspec, VipsArgumentClass *argument_class,
	void *_data1, void *_data2 )
{
	GType type = G_PARAM_SPEC_VALUE_TYPE( pspec );

	if( !(argument_class->flags & VIPS_ARGUMENT_CONSTRUCT) ||
		(argument_class->flags & VIPS_ARGUMENT_DEPRECATED) )
		return( NULL ); 

	/* We don't try to complete options, though maybe we should.
	 */
	if( !(argument_class->flags & VIPS_ARGUMENT_REQUIRED) )
		return( NULL ); 

	/* These are the pspecs that vips uses that have interesting values.
	 */
	if( G_IS_PARAM_SPEC_ENUM( pspec ) ) {
		GTypeClass *class = g_type_class_ref( type );

		GEnumClass *genum;
		int i; 

		/* Should be impossible, no need to warn.
		 */
		if( !class )
			return( NULL );

		genum = G_ENUM_CLASS( class );

		printf( "word:" );

		/* -1 since we always have a "last" member.
		 */
		for( i = 0; i < genum->n_values - 1; i++ ) {
			if( i > 0 )
				printf( "|" );
			printf( "%s", genum->values[i].value_nick );
		}

		printf( "\n" ); 
	}
	else if( G_IS_PARAM_SPEC_BOOLEAN( pspec ) ) 
		printf( "word:true|false\n" );
	else if( G_IS_PARAM_SPEC_DOUBLE( pspec ) ) {
		GParamSpecDouble *pspec_double = (GParamSpecDouble *) pspec;

		printf( "word:%g\n", pspec_double->default_value ); 
	}
	else if( G_IS_PARAM_SPEC_INT( pspec ) ) {
		GParamSpecInt *pspec_int = (GParamSpecInt *) pspec;

		printf( "word:%d\n", pspec_int->default_value ); 
	}
	else if( G_IS_PARAM_SPEC_OBJECT( pspec ) ) 
		/* Eg. an image input or output.
		 */
		printf( "file\n" );
	else
		/* We can offer no useful suggestion for eg. array_int etc.
		 */
		printf( "none\n" );

	return( NULL );	
}

static gboolean
parse_main_option_completion( const gchar *option_name, const gchar *value, 
	gpointer data, GError **error )
{
	VipsObjectClass *class;

	if( value &&
		(class = (VipsObjectClass *) vips_type_map_all( 
			g_type_from_name( "VipsOperation" ), 
			test_class_name, (void *) value )) )  
		vips_argument_class_map( class,
			(VipsArgumentClassMapFn) list_operation_arg, 
			NULL, NULL );
	else if( value ) {
		vips_error( g_get_prgname(), 
			_( "'%s' is not the name of a vips operation" ), 
			value );
		vips_error_g( error );

		return( FALSE );
	}
	else {
		vips_type_map_all( g_type_from_name( "VipsOperation" ), 
			list_operation, NULL );
	}

	exit( 0 );
}

static GOptionEntry main_option[] = {
	{ "list", 'l', G_OPTION_FLAG_OPTIONAL_ARG, G_OPTION_ARG_CALLBACK, 
		(GOptionArgFunc) parse_main_option_list, 
		N_( "list objects" ), 
		N_( "BASE-NAME" ) },
	{ "plugin", 'p', 0, G_OPTION_ARG_FILENAME, &main_option_plugin, 
		N_( "load PLUGIN" ), 
		N_( "PLUGIN" ) },
	{ "version", 'v', 0, G_OPTION_ARG_NONE, &main_option_version, 
		N_( "print version" ), NULL },
	{ "completion", 'c', G_OPTION_FLAG_OPTIONAL_ARG, G_OPTION_ARG_CALLBACK, 
		(GOptionArgFunc) parse_main_option_completion, 
		N_( "print completions" ), 
		N_( "BASE-NAME" ) },
	{ NULL }
};

#if ENABLE_DEPRECATED
typedef void *(*map_name_fn)( im_function * );

/* Loop over a package.
 */
static void *
map_package( im_package *pack, map_name_fn fn )
{
	int i;
	void *result;

	for( i = 0; i < pack->nfuncs; i++ ) 
		if( (result = fn( pack->table[i] )) )
			return( result );

	return( NULL );
}

/* Apply a function to a vips operation, or map over a package of operations.
 */
static void *
map_name( const char *name, map_name_fn fn )
{
	im_package *pack;
	im_function *func;

	if( !name || strcmp( name, "all" ) == 0 ) 
		/* Do all packages.
		 */
		im_map_packages( (VSListMap2Fn) map_package, fn );
	else if( (pack = im_find_package( name )) )
		/* Do one package.
		 */
		map_package( pack, fn );
	else if( (func = im_find_function( name )) )
		/* Do a single function.
		 */
		fn( func );
	else {
		vips_error( "map_name", 
			_( "no package or function \"%s\"" ), name );
		return( fn );
	}

	return( NULL );
}

static void *
list_package( im_package *pack )
{
	printf( "%-20s - %d operations\n", pack->name, pack->nfuncs );
	
	return( NULL );
}

static void *
list_function( im_function *func )
{
	printf( "%-20s - %s\n", func->name, _( func->desc ) );
	
	return( NULL );
}
#endif

static int
print_list( int argc, char **argv )
{
#if ENABLE_DEPRECATED
	if( !argv[0] || strcmp( argv[0], "packages" ) == 0 ) 
		im_map_packages( (VSListMap2Fn) list_package, NULL );
	else if( strcmp( argv[0], "classes" ) == 0 ) 
#else
	if( !argv[0] || strcmp( argv[0], "classes" ) == 0 )
#endif
		vips_type_map_all( g_type_from_name( "VipsObject" ), 
			list_class, NULL );
	else if( g_type_from_name( argv[0] ) &&
		g_type_is_a( g_type_from_name( argv[0] ), VIPS_TYPE_OBJECT ) ) {
		vips_type_map_all( g_type_from_name( argv[0] ), 
			list_class, NULL );
	}
	else {
#if ENABLE_DEPRECATED
		if( map_name( argv[0], list_function ) )
			vips_error_exit( "unknown package \"%s\"", argv[0] ); 
#else
		vips_error_exit( "unknown operation \"%s\"", argv[0] );
#endif
	}

	return( 0 );
}

#if ENABLE_DEPRECATED
/* Print "ln -s" lines for this package.
 */
static void *
print_links_package( im_package *pack )
{
	int i;

	for( i = 0; i < pack->nfuncs; i++ ) 
		printf( "rm -f %s" IM_EXEEXT "; "
			"ln -s vips" IM_EXEEXT " %s" IM_EXEEXT "\n", 
			pack->table[i]->name, pack->table[i]->name );

	return( NULL );
}

/* Print "ln -s" lines for this package.
 */
static int
print_links( int argc, char **argv )
{
	im_map_packages( (VSListMap2Fn) print_links_package, NULL );

	return( 0 );
}

/* Does a function have any printing output?
 */
static int
has_print( im_function *fn )
{
	int i;

	for( i = 0; i < fn->argc; i++ )
		if( fn->argv[i].print )
			return( -1 );

	return( 0 );
}
#endif

static int
isvips( const char *name )
{
	/* If we're running uninstalled we get the lt- prefix.
	 */
	if( vips_isprefix( "lt-", name ) ) 
		name += 3;

	return( vips_isprefix( "vips", name ) );
}

#if ENABLE_DEPRECATED
/* Print a usage string from an im_function descriptor.
 */
static void
usage( im_function *fn )
{
	int i;
	im_package *pack = im_package_of_function( fn->name );

	/* Don't print the prgname if we're being run as a symlink.
	 */
	fprintf( stderr, "usage: " );
	if( isvips( g_get_prgname() ) ) 
		fprintf( stderr, "%s ", g_get_prgname() );
	fprintf( stderr, "%s ", fn->name ); 

	/* Print args requiring command-line input.
	 */
	for( i = 0; i < fn->argc; i++ )
		if( fn->argv[i].desc->flags & IM_TYPE_ARG )
			fprintf( stderr, "%s ", fn->argv[i].name );

	/* Print types of command line args.
	 */
	fprintf( stderr, "\nwhere:\n" );
	for( i = 0; i < fn->argc; i++ )
		if( fn->argv[i].desc->flags & IM_TYPE_ARG )
			fprintf( stderr, "\t%s is of type \"%s\"\n", 
				fn->argv[i].name, fn->argv[i].desc->type );

	/* Print output print args.
	 */
	if( has_print( fn ) ) {
		fprintf( stderr, "prints:\n" );
		for( i = 0; i < fn->argc; i++ )
			if( fn->argv[i].print ) 
				fprintf( stderr, "\t%s of type \"%s\"\n", 
					fn->argv[i].name, 
					fn->argv[i].desc->type );
	}

	/* Print description of this function, and package it comes from.
	 */
	fprintf( stderr, "%s", _( fn->desc ) );
	if( pack )
		fprintf( stderr, ", from package \"%s\"", pack->name );
	fprintf( stderr, "\n" );

	/* Print any flags this function has.
	 */
	fprintf( stderr, "flags: " );
	if( fn->flags & IM_FN_PIO )
		fprintf( stderr, "(PIO function) " );
	else
		fprintf( stderr, "(WIO function) " );
	if( fn->flags & IM_FN_TRANSFORM )
		fprintf( stderr, "(coordinate transformer) " );
	else
		fprintf( stderr, "(no coordinate transformation) " );
	if( fn->flags & IM_FN_PTOP )
		fprintf( stderr, "(point-to-point operation) " );
	else
		fprintf( stderr, "(area operation) " );
	if( fn->flags & IM_FN_NOCACHE )
		fprintf( stderr, "(nocache operation) " );
	else
		fprintf( stderr, "(result can be cached) " );

	fprintf( stderr, "\n" );
}
#endif

static int
print_help( int argc, char **argv ) 
{
	return( 0 );
}

/* All our built-in actions.
 */

typedef int (*Action)( int argc, char **argv );

typedef struct _ActionEntry {
	char *name;
	char *description;
	GOptionEntry *group;
	Action action;
} ActionEntry;

static GOptionEntry empty_options[] = {
	{ NULL }
};

static ActionEntry actions[] = {
#if ENABLE_DEPRECATED
	{ "list", N_( "list classes|packages|all|package-name|operation-name" ),
#else
	{ "list", N_( "list classes|all|operation-name" ),
#endif
		&empty_options[0], print_list },
#if ENABLE_DEPRECATED
	{ "links", N_( "generate links for vips/bin" ),
		&empty_options[0], print_links },
#endif
	{ "help", N_( "list possible actions" ),
		&empty_options[0], print_help },
};

static void
parse_options( GOptionContext *context, int *argc, char **argv )
{
	char txt[1024];
	VipsBuf buf = VIPS_BUF_STATIC( txt );
	GError *error = NULL;
	int i, j;

#ifdef DEBUG
	printf( "parse_options:\n" );
	for( i = 0; i < *argc; i++ )
		printf( "%d) %s\n", i, argv[i] );
#endif /*DEBUG*/

	vips_buf_appendf( &buf, "%7s - %s\n", 
		"OPER", _( "execute vips operation OPER" ) );
	g_option_context_set_summary( context, vips_buf_all( &buf ) );

#ifdef G_OS_WIN32
	if( !g_option_context_parse_strv( context, &argv, &error ) ) 
#else /*!G_OS_WIN32*/
	if( !g_option_context_parse( context, argc, &argv, &error ) ) 
#endif /*G_OS_WIN32*/
	{
		if( error ) {
			fprintf( stderr, "%s\n", error->message );
			g_error_free( error );
		}

		vips_error_exit( NULL );
	}

	/* On Windows, argc will not have been updated by
	 * g_option_context_parse_strv().
	 */
	for( *argc = 0; argv[*argc]; (*argc)++ )
		;

	/* Remove any "--" argument. If one of our arguments is a negative
	 * number, the user will need to have added the "--" flag to stop
	 * GOption parsing. But "--" is still passed down to us and we need to
	 * ignore it.
	 */
	for( i = 1; i < *argc; i++ )
		if( strcmp( argv[i], "--" ) == 0 ) {
			for( j = i; j < *argc; j++ )
				argv[j] = argv[j + 1];

			*argc -= 1;
		}
}

static GOptionGroup *
add_operation_group( GOptionContext *context, VipsOperation *user_data )
{
	GOptionGroup *group;

	group = g_option_group_new( "operation", 
		_( "Operation" ), _( "Operation help" ), user_data, NULL );
	g_option_group_set_translation_domain( group, GETTEXT_PACKAGE );
	g_option_context_add_group( context, group );

	return( group );
}

/* VIPS universal main program. 
 */
int
main( int argc, char **argv )
{
	char *action;
	GOptionContext *context;
	GOptionGroup *main_group;
	GOptionGroup *group;
	VipsOperation *operation;
#if ENABLE_DEPRECATED
	im_function *fn;
#endif
	int i, j;
	gboolean handled;

	GError *error = NULL;

	if( VIPS_INIT( argv[0] ) )
		vips_error_exit( NULL );

#ifdef ENABLE_NLS
	textdomain( GETTEXT_PACKAGE );
#endif /* ENABLE_NLS */
	setlocale( LC_ALL, "" );

{
	char *basename;

	basename = g_path_get_basename( argv[0] );
	g_set_prgname( basename );
	g_free( basename );
}

	/* On Windows, argv is ascii-only .. use this to get a utf-8 version of
	 * the args.
	 */
#ifdef G_OS_WIN32
	argv = g_win32_get_command_line();
#endif /*G_OS_WIN32*/

#ifdef DEBUG_FATAL
	/* Set masks for debugging ... stop on any problem. 
	 */
	g_log_set_always_fatal(
		G_LOG_FLAG_RECURSION |
		G_LOG_FLAG_FATAL |
		G_LOG_LEVEL_ERROR |
		G_LOG_LEVEL_CRITICAL |
		G_LOG_LEVEL_WARNING );
#endif /*!DEBUG_FATAL*/

	context = g_option_context_new( _( "[ACTION] [OPTIONS] [PARAMETERS] - "
		"VIPS driver program" ) );

	/* Add and parse the outermost options: the ones this program uses.
	 * For example, we need
	 * to be able to spot that in the case of "--plugin ./poop.plg" we
	 * must remove two args.
	 */
	main_group = g_option_group_new( NULL, NULL, NULL, NULL, NULL );
	g_option_group_add_entries( main_group, main_option );
	vips_add_option_entries( main_group ); 
	g_option_group_set_translation_domain( main_group, GETTEXT_PACKAGE );
	g_option_context_set_main_group( context, main_group );

	/* We add more options later, for example as options to vips8
	 * operations. Ignore any unknown options in this first parse.
	 */
	g_option_context_set_ignore_unknown_options( context, TRUE );

	/* "vips" with no arguments does "vips --help".
	 */
	if( argc == 1 ) { 
		char *help;

		help = g_option_context_get_help( context, TRUE, NULL );
		printf( "%s", help );
		g_free( help );

		exit( 0 );
	}

	/* Also disable help output: we want to be able to display full help
	 * in a second pass after all options have been created.
	 */
	g_option_context_set_help_enabled( context, FALSE );

#ifdef G_OS_WIN32
	if( !g_option_context_parse_strv( context, &argv, &error ) ) 
#else /*!G_OS_WIN32*/
	if( !g_option_context_parse( context, &argc, &argv, &error ) ) 
#endif /*G_OS_WIN32*/
	{
		if( error ) {
			fprintf( stderr, "%s\n", error->message );
			g_error_free( error );
		}

		vips_error_exit( NULL );
	}

	/* On Windows, argc will not have been updated by
	 * g_option_context_parse_strv().
	 */
	for( argc = 0; argv[argc]; argc++ )
		;

	if( main_option_plugin ) {
#if ENABLE_MODULES
#if ENABLE_DEPRECATED
		if( !im_load_plugin( main_option_plugin ) )
			vips_error_exit( NULL );
#else /*!ENABLE_DEPRECATED*/
		GModule *module;

		module = g_module_open( main_option_plugin, G_MODULE_BIND_LAZY );
		if( !module ) {
			vips_error_exit( _( "unable to load \"%s\" -- %s" ),
				main_option_plugin, g_module_error() );
		}
#endif /*ENABLE_DEPRECATED*/
#else /*!ENABLE_MODULES*/
		g_warning( "%s", _( "plugin load disabled: "
			"libvips built without modules support" ) );
#endif /*ENABLE_MODULES*/
	}

	if( main_option_version ) 
		printf( "vips-%s\n", vips_version_string() );

	/* Reenable help and unknown option detection ready for the second
	 * option parse.
	 */
	g_option_context_set_ignore_unknown_options( context, FALSE );
	g_option_context_set_help_enabled( context, TRUE );

	/* Try to find our action.
	 */
	handled = FALSE;
	action = NULL;

	/* Should we try to run the thing we are named as?
	 */
	if( !isvips( g_get_prgname() ) ) 
		action = argv[0];

	if( !action ) {
		/* Look for the first non-option argument, if any, and make 
		 * that our action. The parse above will have removed most of
		 * them, but --help (for example) could still remain. 
		 */
		for( i = 1; i < argc; i++ )
			if( argv[i][0] != '-' ) {
				action = argv[i];

				/* Remove the action from argv.
				 */
				for( j = i; j < argc; j++ )
					argv[j] = argv[j + 1];
				argc -= 1;

				break;
			}
	}

	/* Could be one of our built-in actions.
	 */
	if( action ) 
		for( i = 0; i < VIPS_NUMBER( actions ); i++ )
			if( strcmp( action, actions[i].name ) == 0 ) {
				group = add_operation_group( context, NULL );
				g_option_group_add_entries( group, 
					actions[i].group );
				parse_options( context, &argc, argv );

				if( actions[i].action( argc - 1, argv + 1 ) ) 
					vips_error_exit( "%s", action );

				handled = TRUE;
				break;
			}

#if ENABLE_DEPRECATED
	/* Could be a vips7 im_function. We need to test for vips7 first,
	 * since we don't want to use the vips7 compat wrappers in vips8
	 * unless we have to. They don't support all args types.
	 */
	if( action && 
		!handled && 
		(fn = im_find_function( action )) ) {
		if( im_run_command( action, argc - 1, argv + 1 ) ) {
			if( argc == 1 ) 
				usage( fn );
			else
				vips_error_exit( NULL );
		}

		handled = TRUE;
	}

	/* im_find_function() set an error msg.
	 */
	if( action &&
		!handled )
		vips_error_clear();
#endif

	/* Could be a vips8 VipsOperation.
	 */
	if( action && 
		!handled && 
		(operation = vips_operation_new( action )) ) {
		group = add_operation_group( context, operation );
		vips_call_options( group, operation );
		parse_options( context, &argc, argv );

		if( vips_call_argv( operation, argc - 1, argv + 1 ) ) {
			if( argc == 1 ) 
				vips_operation_class_print_usage( 
					VIPS_OPERATION_GET_CLASS( operation ) );

			vips_object_unref_outputs( VIPS_OBJECT( operation ) );
			g_object_unref( operation );

			if( argc == 1 )
				/* We don't exit with an error for something
				 * like "vips fitsload" failing, we use it to
				 * decide if an optional component has been
				 * configured. If we've been built without
				 * fits support, fitsload will fail to find
				 * the operation and we'll error with "unknown
				 * action" below.
				 */
				exit( 0 );
			else
				vips_error_exit( NULL );
		}

		vips_object_unref_outputs( VIPS_OBJECT( operation ) );
		g_object_unref( operation );

		handled = TRUE;
	}

	/* vips_operation_new() sets an error msg for unknown operation.
	 */
	if( action &&
		!handled )
		vips_error_clear();

	if( action && 
		!handled ) {
		vips_error_exit( _( "unknown action \"%s\"" ), action );
	}

	/* Still not handled? We may not have called parse_options(), so
	 * --help args may not have been processed.
	 */
	if( !handled )
		parse_options( context, &argc, argv );

	g_option_context_free( context );

#ifdef G_OS_WIN32
	g_strfreev( argv ); 
#endif /*G_OS_WIN32*/

	vips_shutdown();

	return( 0 );
}
