pericmd 030: More on tab completion (2): Completing arguments, element_completion

Like the previous post, this blog post still focuses on tab completion, particularly on completing arguments.

Aside from option names and option values, Perinci::CmdLine can also complete arguments. In Perinci::CmdLine, command-line arguments will also be fed to function as function arguments. The function argument to which the command-line argument(s) will be fed to must be specified with the pos (for positional) property, and optionally with the greedy property. Example:

use Perinci::CmdLine::Any;

our %SPEC;
$SPEC{mycomp2} = {
    v => 1.1,
    args => {
        foo => {
            schema => 'str*',
            pos => 0,
            req => 1,
        },
        bar => {
            schema => 'str*',
            pos => 1,
        },
        baz => {
            schema => 'str*',
        },
    },
};
sub mycomp2 {
    my %args = @_;
    [200, "OK", join(
        "",
        "foo=", $args{foo}//'', " ",
        "bar=", $args{bar}//'', " ",
        "baz=", $args{baz}//'',
    )];
}

Perinci::CmdLine::Any->new(
    url => '/main/mycomp2',
)->run;

In the above program, the argument foo will map to the first
command-line argument (pos=0), bar to the second command-line argument
(pos=1), while baz does not map to any command-line argument (must be specified as command-line option, e.g. --baz val). Of course, the positional arguments can also be specified as command-line options too, although they cannot be both command-line options and arguments at the same time.

% mycomp2
ERROR 400: Missing required argument(s): foo

% mycomp2 1
foo=1 bar= baz=

% mycomp2 --foo 1
foo=1 bar= baz=

% mycomp2 1
ERROR 400: You specified option --foo but also argument #0

% mycomp2 1 2
foo=1 bar=2 baz=

% mycomp2 1 --bar 2
foo=1 bar=2 baz=

% mycomp2 1 --bar 2 2
ERROR 400: You specified option --bar but also argument #1

% mycomp2 1 2 --baz 3
foo=1 bar=2 baz=3

% mycomp2 1 2 3
ERROR 400: There are extra, unassigned elements in array: [3]

As you can see from the last example, Perinci::CmdLine will complain if there are extra arguments that are not unassigned to any function argument. What if we want a function argument to slurp all the remaining command-line arguments? We can declare a function argument as an array and set the pos property as well as set greedy property to true to express that the argument is slurpy.

use Perinci::CmdLine::Any;

our %SPEC;
$SPEC{mycomp2} = {
    v => 1.1,
    args => {
        foo => {
            schema => 'str*',
            pos => 0,
            req => 1,
        },
        bar => {
            schema => ['array*', of=>'str*'],
            pos => 1,
            greedy => 1,
        },
        baz => {
            schema => 'str*',
        },
    },
};
sub mycomp2 {
    my %args = @_;
    [200, "OK", join(
        "",
        "foo=", $args{foo}//'', " ",
        "bar=", ($args{bar} ? "[".join(",",@{$args{bar}})."]" : ''), " ",
        "baz=", $args{baz}//'',
    )];
}

Perinci::CmdLine::Any->new(
    url => '/main/mycomp2',
)->run;

When run:

% mycomp2 1
foo=1 bar= baz=

% mycomp2 1 2
foo=1 bar=[2] baz=

% mycomp2 1 2 3 4
foo=1 bar=[2,3,4] baz=

Now, since command-line arguments map to function arguments, to specify completion for it we just need to put a completion property to the metadata, just like any other argument.

#!/usr/bin/env perl

use Complete::Util qw(complete_array_elem complete_env);
use Perinci::CmdLine::Any;

our %SPEC;
$SPEC{mycomp2} = {
    v => 1.1,
    args => {
        foo => {
            schema => 'str*',
            pos => 0,
            req => 1,
            cmdline_aliases => {f=>{}},
            completion => sub {
                my %args = @_;
                complete_array_elem(
                    word  => $args{word},
                    array => [qw/apple banana blackberry blueberry/],
                ),
            },
        },
        bar => {
            schema => ['array*', of=>'str*'],
            pos => 1,
            greedy => 1,
            element_completion => sub {
                my %args = @_;
                complete_array_elem(
                    word    => $args{word},
                    array   => [qw/snakefruit durian jackfruit/],
                    exclude => $args{args}{bar},
                );
            },
        },
        baz => {
            schema => 'str*',
            completion => \&complete_env,
        },
    },
};
sub mycomp2 {
    my %args = @_;
    [200, "OK", join(
        "",
        "foo=", $args{foo}//'', " ",
        "bar=", ($args{bar} ? "[".join(",",@{$args{bar}})."]" : ''), " ",
        "baz=", $args{baz}//'',
    )];
}

Perinci::CmdLine::Any->new(
    url => '/main/mycomp2',
)->run;

Completion works when function argument is fed as command-line option (including aliases) or command-line argument. Let’s test the completion for foo (note: from this point onwards, I assume you have activated bash completion for the script, as described in the previous post pericmd 029):

% mycomp2 <tab><tab>
-\?               blackberry        --format          --no-config
apple             blueberry         -h                -v
banana            --config-path     --help            --version
--bar             --config-profile  --json            
--baz             --foo             --naked-res      

% mycomp2 b<tab>
banana      blackberry  blueberry   

% mycomp2 --foo <tab><tab>
apple       banana      blackberry  blueberry   

% mycomp2 -f <tab><tab>
apple       banana      blackberry  blueberry   

From the last program listing, you’ll see several new things. First is that the bar argument uses the element_completion property (line 27) instead of completion. This is because bar itself is an argument with type of array (of string), and we are completing the element, not the array itself:

% mycomp2 --bar <tab><tab>
durian      jackfruit   snakefruit  

% mycomp2 --bar durian --bar <tab><tab>
jackfruit   snakefruit  

% mycomp2 --bar durian --bar jackfruit --bar <tab>
% mycomp2 --bar durian --bar jackfruit --bar snakefruit _

You’ll also notice that if a bar value has been specified, the choice will be removed from the offering in completion for the subsequent --bar option value. This is because we are using the exclude option in complete_array_elem() (line 32). The $args{args} contains the function arguments that have been formed at that point.

And lastly, in line 38, you see a new function complete_env which can complete from environment variable names. Since both complete_env() and completion routine expect hash argument as well, and the only required argument is also word, we can pass the subroutine reference directly. Let’s see it in action:

% mycomp2 --baz H
HISTCONTROL     HISTIGNORE      HISTTIMEFORMAT  
HISTFILESIZE    HISTSIZE        HOME            
Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s