#!perl

our $DATE = '2014-12-12'; # DATE
our $VERSION = '0.01'; # VERSION
# FRAGMENT id=shcompinst-nohint

use 5.010;
use strict;
use warnings;
use experimental 'smartmatch';

use App::ShellCompletionInstaller;
use Perinci::CmdLine::Any -prefer_lite=>1;

our %SPEC;

sub _all_exec_in_PATH {
    my @res;
    for my $dir (split /:/, $ENV{PATH}) {
        opendir my($dh), $dir or next;
        for my $f (readdir $dh) {
            next if $f eq '.' || $f eq '..';
            next if $f =~ /~\z/; # skip backup files
            next unless ((-f "$dir/$f") && (-x _));
            push @res, "$dir/$f";
        }
    }
    \@res;
}

my $supported_shells = [qw/bash/]; # fish tcsh zsh
my @common_args = qw(
                        shell
                        global
                        bash_global_dir bash_per_user_dir
                        fish_global_dir fish_per_user_dir
);
#                        XXX tcsh_global_dir tcsh_per_user_dir
#                        XXX zsh_global_dir zsh_per_user_dir

$SPEC{installer} = {
    v => 1.1,
    summary => 'Shell completion installer',
    description => <<'_',

This script will attempt to determine the appropriate shell tab completion
script for a program/command (by default, all programs in PATH will be
attempted) and install the completion scripts to the appropriate directory for
your shell.

See the manpage for more details on the methods and kinds of programs it can
detect.

Uninstallation and listing are also supported.

Several shells are supported.

_
    args => {
        action => {
            schema => 'str*',
            cmdline_aliases => {
                list       => { is_flag=>1, code=>sub { $_[0]{action} = 'list' } },
                l          => { is_flag=>1, code=>sub { $_[0]{action} = 'list' } },
                clean      => { is_flag=>1, code=>sub { $_[0]{action} = 'clean' } },
                uninstall  => { is_flag=>1, code=>sub { $_[0]{action} = 'uninstall' } },
                u          => { is_flag=>1, code=>sub { $_[0]{action} = 'uninstall' } },
            },
            default => 'install',
        },
        detail => {
            schema => 'bool*',
        },
        replace => {
            summary => 'When installing a completion script, replace existing',
            schema  => 'bool*',
            description => <<'_',

The default behavior is to skip installing if an existing completion script
exists.

_
        },
        args => {
            schema => ['array*' => of => 'str*'],
            pos    => 0,
            greedy => 1,
        },
        shell => {
            summary => 'Override autodetection and select shell manually',
            schema => ['str*', {in=>$supported_shells}], # XXX tcsh zsh
            description => <<'_',

The default is to look at your SHELL environment variable value. If it is
undefined, the default is `bash`.

_
        },
        global => {
            summary => 'Use global completions directory',
            schema => ['bool*'],
            cmdline_aliases => {
                per_user => {
                    is_flag => 1,
                    code    => sub { $_[0]{global} = 0 },
                    summary => 'Alias for --no-global',
                },
            },
            description => <<'_',

Shell has global (system-wide) completions directory as well as per-user. For
example, in fish the global directory is by default `/etc/fish/completions` and
the per-user directory is `~/.config/fish/completions`.

By default, if running as root, the global is chosen. And if running as normal
user, per-user directory is chosen. Using `--global` or `--per-user` overrides
that and manually select which.

_
            links => [qw/
                            bash_global_dir bash_per_user_dir
                            fish_global_dir fish_per_user_dir
                        /],
            # tcsh_global_dir tcsh_per_user_dir
            # zsh_global_dir zsh_per_user_dir
        },
        bash_global_dir => {
            summary => 'Directory to put completions scripts',
            schema  => 'str*',
            default => '/etc/bash_completion.d',
        },
        bash_per_user_dir => {
            summary => 'Directory to put completions scripts',
            schema  => 'str*',
        },
        fish_global_dir => {
            summary => 'Directory to put completions scripts',
            schema  => 'str*',
            default => '/etc/fish/completions',
        },
        fish_per_user_dir => {
            summary => 'Directory to put completions scripts',
            schema  => 'str*',
        },
        # XXX tcsh_global_dir
        # XXX tcsh_per_user_dir
        # XXX zsh_global_dir
        # XXX zsh_per_user_dir
    },
};
sub installer {
    my %args = @_;

    my $action = $args{action};
    my $args   = $args{args};

    my $shell;
    {
        $shell = $args{shell};
        if (!$shell) { ($shell = $ENV{SHELL} // '') =~ s!.+/!! }
        if (!$shell) { $shell = 'bash' }
        unless ($shell ~~ @$supported_shells) {
            return [412, "Unsupported shell '$shell'"];
        }
        $args{shell} = $shell;
    }

    my $global;
    {
        $global = $args{global} // ($> ? 0:1);
        $args{global} = $global;
    }

    {
        $args{bash_global_dir}   //= '/etc/bash_completion.d';
        $args{bash_per_user_dir} //= "$ENV{HOME}/.config/bash/completions";
        $args{fish_global_dir}   //= '/etc/fish/completions';
        $args{fish_per_user_dir} //= "$ENV{HOME}/.config/fish/completions";
        $args{tcsh_global_dir}   //= '/etc/bash_completion.d';
        $args{tcsh_per_user_dir} //= "$ENV{HOME}/.config/bash/completions";
        $args{zsh_global_dir}    //= '/etc/zsh/completions';
        $args{zsh_per_user_dir}  //= "$ENV{HOME}/.config/zsh/completions";
    }

    my %common_args;
    for (@common_args) {
        $common_args{$_} = $args{$_} if defined $args{$_};
    }

    if ($action eq 'install') {
        $args = _all_exec_in_PATH() unless $args;
        return App::ShellCompletionInstaller::_install(
            %common_args,
            progs   => $args,
            replace => $args{replace},
        );
    } elsif ($action eq 'list') {
        return App::ShellCompletionInstaller::_list(
            %common_args,
            detail  => $args{detail},
        );
    } elsif ($action eq 'clean') {
        return App::ShellCompletionInstaller::_clean(
            %common_args,
        );
    } elsif ($action eq 'uninstall') {
        $args = _all_exec_in_PATH() unless $args;
        return App::ShellCompletionInstaller::_uninstall(
            %common_args,
            progs   => $args,
        );
    }
}

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

# ABSTRACT: Shell completion installer
# PODNAME: shell-completion-installer

__END__

=pod

=encoding UTF-8

=head1 NAME

shell-completion-installer - Shell completion installer

=head1 VERSION

This document describes version 0.01 of shell-completion-installer (from Perl distribution App-ShellCompletionInstaller), released on 2014-12-12.

=head1 SYNOPSIS

Detect completion for all programs in PATH and install completion scripts for
all detectable programs:

 % shell-completion-installer

Detect some programs only, replace if previously already exists:

 % shell-completion-installer --replace prog1 prog2 ./bin/prog3

List all programs whose completion scripts are installed by us:

 % shell-completion-installer -l

Uninstall (delete completion scripts) for some programs:

 % shell-completion-installer -u prog1 prog2

Uninstall all:

 % shell-completion-installer -u

=head1 DESCRIPTION

B<NOTE: EARLY RELEASE. ONLY BASH SUPPORT HAVE BEEN ADDED. SUPPORT FOR THE OTHER
SHELLS WILL FOLLOW.>

Some shells, like bash/fish/zsh, supports tab completion for programs. They are
usually activated by issuing one or more C<complete> (zsh uses C<compctl>)
internal shell commands. The completion scripts which contain these commands are
usually put in (e.g., for fish) C</etc/fish/completion/PROGNAME.fish> (if want
to be installed globally) or C<~/.config/fish/completions/PROGNAME.fish> (if
want to be installed per-user).

This utility, B<shell-completion-installer>, can detect how to generate
completion scripts for some programs and then install the completion scripts
into the abovementioned location (the default is to per-user directory, but
under C<--global> will install to the global directory).

It can also list all completion scripts generated by it, and be instructed to
remove them again.

It supports several shells, currently: bash, fish, and zsh. Shell-specific
information can be found below.

=head2 Setup for bash

If you have installed this distribution on your system, you should have a file
called C<init-bash> somewhere in your system, e.g. in
C</usr/local/share/perl/X.Y.Z/auto/share/dist/App-ShellCompletionInstaller/init-bash>.
You then need to load this file from you C</etc/bash.bashrc> or C<~/.bashrc>,
e.g.:

 . /usr/local/share/perl/X.Y.Z/auto/share/dist/App-ShellCompletionInstaller/init-bash

B<If you are using bash-completion package>: At the time of this writing,
bash-completion (at 2.1) does not yet look at per-user completion scripts
directory (e.g. C<~/.config/bash/completions/PROGNAME>, only in
C</etc/bash_completion.d/>, so you might still want to put the above in your
C</etc/bash.bashrc> or C<~/.bashrc>. The above init script works with
bash-completion package installed.

For completeness sake, this is the content of the C<init-bash> script:

 # shell-completion-installer loader for bash. load this file from your
 # /etc/bash.bashrc or ~/.bashrc, e.g.:
 #
 #  . /path/to/share/shell-completion-installer/init-bash
 #
 
 _shcompletion_loader()
 {
     local f
     for f in ~/.config/bash/completions/"$1" /etc/bash_completion.d/"$1"; do
         if [[ -f "$f" ]]; then . "$f"; return; fi
     done
 
     # check if bash-completion is active by the existence of function
     # '_completion_loader'. if it is, delegate to the function.
     if [[ "`type -t _completion_loader`" = "function" ]]; then _completion_loader; fi
 }
 complete -D -F _shcompletion_loader

=head2 Setup for fish

No setup is necessary for fish. By default fish already looks in
C<~/.config/fish/completions/PROGNAME.fish> and
C</etc/fish/completions/PROGNAME.fish> (although configurable via
C<$fish_complete_path>) so C<shell-completion-installer> only needs to put
completion scripts there.

=head2 Setup for zsh

Depending on your configuration, by default zsh's C<FPATH> doesn't contain
per-user directory. You can put this line in your C<~/.zshrc>:

 export FPATH="~/.config/zsh/completions:$FPATH"

and create the C<~/.config/zsh/completions> directory.

=head2 Program detection

Below are the types/kinds of programs that can be detected. Expect the list to
expand as more methods are added.

=over

=item * Scripts which are tagged with hints of what completion program to use

You can put this line in a script, e.g. in a script called C<foo>:

 # FRAGMENT id=shcompinst-hint command=bar

The above line tells C<shell-completion-installer> that the script should be
completed using an external program called C<bar>. This will construct this
completion script, e.g. for bash:

 complete -C bar foo

=item * Getopt::Long::Complete-based CLI scripts

If a script like C<foo> is detected as a Perl script using
L<Getopt::Long::Complete>, we know that it can complete itself. Thus,
C<shell-completion-installer> will generate this completion script (e.g. for
bash):

 complete -C foo foo

=item * Completion programs which are tagged with hints of what programs they complete

You can create a completion script in Perl (or other language, actually), e.g.
C<_foo> and tag it with hints of what programs they complete, e.g.

 # FRAGMENT id=shcompinst-hint completer=1 for=foo,foo-this-host

This will add completion script for C<foo>:

 complete -C _foo foo

as well as for C<foo-this-host>:

 complete -C _foo foo-this-host

=item * Perinci::CmdLine-based CLI scripts

If a script like C<foo> is detected as a Perl script using L<Perinci::CmdLine>
(or its variant like L<Perinci::CmdLine::Lite> or L<Perinci::CmdLine::Any>) we
know that it can complete itself. Thus, C<shell-completion-installer> will add
this completion script e.g. for bash:

 complete -C foo foo

=item * Other methods

Other methods will be added in the future, e.g. by parsing manpage or POD, and
so on.

=back

=head2 Usage

The simplest usage would be to call the program without any argument, which will
scan all programs found in C<PATH> and add completion scripts for all
recognizable programs.

 % shell-completion-installer

Or you can add individual programs. Program names without directory (without
C</>) will be searched in C<PATH>. If you want to add program in the current
directory, use C<./progname> syntax.

 % shell-completion-installer foo ./bar/baz

If a program's completion cannot be determined, it will simply be ignored. If a
program's completion script already exists, it will also be ignored, unless
C<--replace> is specified.

=head1 OPTIONS

=head2 --global

Instead of

=head2 --shell=s, -s

Select a specific shell. By default the running shell (detected from the
C<SHELL> environment variable) is used.

=head2 --list, -l

List all programs we generate completion scripts for.

=head2 --clean

Uninstall all completion scripts generated by C<shell-completion-installer>
for all commands that are no longer found in PATH.

=head2 --uninstall, -u

Remove all completion scripts generated by us for specified programs, or (if
unspecified) all programs.

=head2 --replace

When completion script for a program already exists,
C<shell-completion-installer> will skip the program. Unless when this option is
specified, in which case it will replace the completion script with the newly
generated one.

=head1 ENVIRONMENT

=head2 DEBUG => bool

Set to true to enable debugging messages.

=head1 TODO

Support for tcsh.

=head1 SEE ALSO

L<Dist::Zilla::Plugin::Rinci::InstallCompletion>

=head1 COMPLETION

This script has shell tab completion capability with support for several shells.

=head2 bash

To activate bash completion for this script, put:

 complete -C shell-completion-installer shell-completion-installer

in your bash startup (e.g. C<~/.bashrc>). Your next shell session will then recognize tab completion for the command. Or, you can also directly execute the line above in your shell to activate immediately.

You can also install L<App::BashCompletionProg> which makes it easy to add completion for Getopt::Long::Complete-based scripts. After you install the module and put C<. ~/.bash-complete-prog> (or C<. /etc/bash-complete-prog>), you can just run C<bash-completion-prog> and the C<complete> command will be added to your C<~/.bash-completion-prog>. Your next shell session will then recognize tab completion for the command.

=head2 fish

To activate fish completion for this script, execute:

 begin; set -lx COMP_SHELL fish; set -lx COMP_MODE gen_command; shell-completion-installer; end > $HOME/.config/fish/completions/shell-completion-installer.fish

Or if you want to install globally, you can instead write the generated script to C</etc/fish/completions/shell-completion-installer.fish> or C</usr/share/fish/completions/shell-completion-installer.fish>. The exact path might be different on your system. Please check your C<fish_complete_path> variable.

=head2 tcsh

To activate tcsh completion for this script, put:

 complete shell-completion-installer 'p/*/`shell-completion-installer`/'

in your tcsh startup (e.g. C<~/.tcshrc>). Your next shell session will then recognize tab completion for the command. Or, you can also directly execute the line above in your shell to activate immediately.

=head2 zsh

To activate zsh completion for this script, put:

 _shell_completion_installer() { read -l; local cl="$REPLY"; read -ln; local cp="$REPLY"; reply=(`COMP_SHELL=zsh COMP_LINE="$cl" COMP_POINT="$cp" shell-completion-installer`) }

 compctl -K _shell_completion_installer shell-completion-installer

in your zsh startup (e.g. C<~/.zshrc>). Your next shell session will then recognize tab completion for the command. Or, you can also directly execute the line above in your shell to activate immediately.

=head1 HOMEPAGE

Please visit the project's homepage at L<https://metacpan.org/release/App-ShellCompletionInstaller>.

=head1 SOURCE

Source repository is at L<https://github.com/perlancar/perl-App-ShellCompletionInstaller>.

=head1 BUGS

Please report any bugs or feature requests on the bugtracker website L<https://rt.cpan.org/Public/Dist/Display.html?Name=App-ShellCompletionInstaller>

When submitting a bug or request, please include a test-file or a
patch to an existing test-file that illustrates the bug or desired
feature.

=head1 AUTHOR

perlancar <perlancar@cpan.org>

=head1 COPYRIGHT AND LICENSE

This software is copyright (c) 2014 by perlancar@cpan.org.

This is free software; you can redistribute it and/or modify it under
the same terms as the Perl 5 programming language system itself.

=cut
