WIP -- add simple bash completion support (#3131)

* add simple bash completion support

* add "completions" subdir to meson

* start extending completion to extra args

* file complete for every arg after 1

* add completion for enums, file, ints, doubles

* docs, try to improve directory completion

though dir completion is not working correctly, I'm not sure why
This commit is contained in:
John Cupitt 2022-11-13 18:57:14 +00:00 committed by GitHub
parent 7f352b3c9e
commit e24cee4e22
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 199 additions and 2 deletions

View File

@ -18,6 +18,7 @@ master
- threadpools size dynamically with load
- operations can hint threadpool size
- support for N-colour ICC profiles
- add bash completions for "vips"
- fits load and allows many more bands
- fits write doesn't duplicate header fields
- add @wrap to vips_text()

View File

@ -83,6 +83,8 @@ libvips must have `build-essential`, `pkg-config`, `libglib2.0-dev`,
`libexpat1-dev`. See the **Dependencies** section below for a full list
of the libvips optional dependencies.
There are basic bash completions in `completions/`, see the README in there.
## Cheatsheet
```

28
completions/README.md Normal file
View File

@ -0,0 +1,28 @@
# Shell completions for vips
Basic shell completions for the `vips` program. Internally, these use the
`-c` argument to `vips` to list argument options.
## Example
```
$ vips relational<TAB>
relational relational_const
$ vips relational_const ~/pics/k2.<TAB>
~/pics/k2.avif ~/pics/k2.hdr ~/pics/k2.pdf ~/pics/k2.tif
~/pics/k2.bmp ~/pics/k2.heic ~/pics/k2.pfm ~/pics/k2.v
~/pics/k2.csv ~/pics/k2.jp2 ~/pics/k2.pgm ~/pics/k2.vips
~/pics/k2.fits ~/pics/k2.jpg ~/pics/k2.png ~/pics/k2.webp
~/pics/k2.flif ~/pics/k2.jxl ~/pics/k2.ppm
~/pics/k2.gif ~/pics/k2.pbm ~/pics/k2.ppt
$ vips relational_const ~/pics/k2.jpg x.v less<TAB>
less lesseq
$ vips relational_const ~/pics/k2.jpg x.v lesseq 12
```
## Install
### `vips-completion.bash`
Usually copy to `/etc/bash_completion.d` to install, but it depends on your
system.

View File

@ -0,0 +1,43 @@
#/usr/bin/env bash
# bash completions for the "vips" command
# copy to /etc/bash_completion.d to install
_vips_compgen_f()
{
COMPREPLY=($(compgen -f -- "${COMP_WORDS[-1]}"))
if [ ${#COMPREPLY[@]} = 1 ]; then
local LASTCHAR=' '
if [ -d "$COMPREPLY" ]; then
LASTCHAR=/
fi
COMPREPLY=$(printf %q%s "$COMPREPLY" "$LASTCHAR")
else
for ((i=0; i < ${#COMPREPLY[@]}; i++)); do
if [ -d "${COMPREPLY[$i]}" ]; then
COMPREPLY[$i]=${COMPREPLY[$i]}/
fi
done
fi
}
_vips_completions()
{
if [ ${#COMP_WORDS[@]} == "2" ]; then
COMPREPLY=($(compgen -W "$(vips -c)" "${COMP_WORDS[1]}"))
else
local args=($(vips -c ${COMP_WORDS[1]}))
local arg_type=${args[${#COMP_WORDS[@]}-3]}
if [ $arg_type == "file" ]; then
_vips_compgen_f
elif [[ $arg_type = word:* ]]; then
local options=$(echo $arg_type | sed 's/word://' | sed 's/|/ /g')
COMPREPLY=($(compgen -W "${options[@]}" "${COMP_WORDS[-1]}"))
fi
fi
}
complete -F _vips_completions vips

View File

@ -360,7 +360,6 @@ vips_operation_pspec_usage( VipsBuf *buf, GParamSpec *pspec )
vips_buf_appendf( buf, "%s", _( "max" ) );
vips_buf_appendf( buf, ": %d\n", pspec_int->maximum );
}
}
static void *

View File

@ -33,6 +33,11 @@ loaded automatically.
.B -v, --version
Show VIPS version.
.TP
.B -c NAME, --completion NAME
Print completions for
.B NAME
.SH COMMANDS
.TP
@ -41,7 +46,7 @@ Execute a named operation, for example add.
.SH EXAMPLES
Run a vips8 operation. Operation options must follow the operation name.
Run a vips operation. Operation options must follow the operation name.
$ vips insert lena.v lena2.v out.v 0 0 --background "128 0 0"

View File

@ -45,6 +45,8 @@
* 18/6/20 kleisauke
* - avoid using vips7 symbols
* - remove deprecated vips7 C++ generator
* 1/11/22
* - add "-c" flag
*/
/*
@ -170,6 +172,119 @@ parse_main_option_list( const gchar *option_name, const gchar *value,
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 );
printf( "%s\n", class->nickname );
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_nickname, (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,
@ -180,6 +295,10 @@ static GOptionEntry main_option[] = {
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 }
};