App-shcompgen
view release on metacpan or search on metacpan
lib/App/shcompgen.pm view on Meta::CPAN
package App::shcompgen;
use 5.010001;
use strict;
use warnings;
use Log::ger;
use File::Slurper qw(read_text write_text);
use Perinci::Object;
use Perinci::Sub::Util qw(err);
our $AUTHORITY = 'cpan:PERLANCAR'; # AUTHORITY
our $DATE = '2022-10-07'; # DATE
our $DIST = 'App-shcompgen'; # DIST
our $VERSION = '0.325'; # VERSION
our %SPEC;
my $re_progname = qr/\A[A-Za-z0-9_.,:-]+\z/;
$SPEC{':package'} = {
v => 1.1,
summary => 'Generate shell completion scripts',
};
my $_complete_prog = sub {
require Complete::File;
require Complete::Program;
my %args = @_;
my $word = $args{word} // '';
if ($word =~ m!/!) {
# user might want to mention a program file (e.g. ./foo)
return {
words => Complete::File::complete_file(
word=>$word, filter=>'d|rxf'),
path_sep => '/',
};
} else {
# or user might want to mention a program in PATH
Complete::Program::complete_program(word=>$word);
}
};
our @supported_shells = qw(bash fish zsh tcsh);
our %shell_arg = (
shell => {
summary => 'Override guessing and select shell manually',
schema => ['str*', {in=>\@supported_shells}],
tags => ['common'],
cmdline_aliases => {
fish => {summary=>"Shortcut for --shell=fish", is_flag=>1, code=>sub { $_[0]{shell} = "fish" }},
zsh => {summary=>"Shortcut for --shell=zsh" , is_flag=>1, code=>sub { $_[0]{shell} = "zsh" }},
tcsh => {summary=>"Shortcut for --shell=tcsh", is_flag=>1, code=>sub { $_[0]{shell} = "tcsh" }},
},
},
);
our %common_args = (
%shell_arg,
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.
_
tags => ['common'],
},
bash_global_dir => {
summary => 'Directory to put completions scripts',
schema => ['array*', of => 'str*'],
default => ['/etc/bash/completions'],
tags => ['common'],
},
bash_per_user_dir => {
summary => 'Directory to put completions scripts',
schema => ['array*', of => 'str*'],
tags => ['common'],
},
fish_global_dir => {
summary => 'Directory to put completions scripts',
schema => ['array*', of => 'str*'],
default => ['/etc/fish/completions'],
tags => ['common'],
},
fish_per_user_dir => {
summary => 'Directory to put completions scripts',
schema => ['array*', of => 'str*'],
tags => ['common'],
},
tcsh_global_dir => {
lib/App/shcompgen.pm view on Meta::CPAN
REMOVE:
if ($which eq 'remove') {
my $comppath = _completion_script_path(%args, prog => $prog);
unless (-f $comppath) {
log_debug("Skipping %s (completion script does not exist)", $prog0);
$envres->add_result(304, "Completion does not exist", {item_id=>$prog0});
next PROG;
}
my $content;
eval { $content = read_text($comppath) };
if ($@) {
$envres->add_result(500, "Can't open '$comppath': $@", {item_id=>$prog0});
next;
};
unless ($content =~ /^# FRAGMENT id=shcompgen-header note=(.+)\b/m) {
log_debug("Skipping %s, not installed by us", $prog0);
$envres->add_result(304, "Not installed by us", {item_id=>$prog0});
next PROG;
}
if ($written_files{$comppath}) {
# not removing files we already wrote
next PROG;
}
log_info("Unlinking %s ...", $comppath);
unless (unlink $comppath) {
$envres->add_result(500, "Can't unlink '$comppath': $!",
{item_id=>$prog0});
next PROG;
}
# XXX we should only remove helper script if there are no other
# shells' completion scripts using this
while ($content =~ /^# FRAGMENT id=shcompgen-helper-\d+ path=(.+)/mg) {
my $hspath = $1;
log_info("Unlinking helper script %s ...", $1);
unless (unlink $hspath) {
$envres->add_result(500, "Can't unlink helper script '$hspath': $!",
{item_id=>$prog0});
next PROG;
}
$removed_files{$hspath}++;
}
$envres->add_result(200, "OK", {item_id=>$prog0});
$removed_files{$comppath}++;
} # remove
} # for prog0
if (keys(%written_files) || keys(%removed_files)) {
if ($args{shell} eq 'tcsh') {
my $init_script_path = _tcsh_init_script_path(%args);
my $init_script = _gen_tcsh_init_script(%args);
log_debug("Re-writing init script %s ...", $init_script_path);
write_text($init_script_path, $init_script);
}
}
$envres->as_struct;
}
$SPEC{guess_shell} = {
v => 1.1,
summary => 'Guess running shell',
args => {
},
};
sub guess_shell {
my %args = @_;
my $setdef_res = _set_args_defaults(\%args);
return $setdef_res unless $setdef_res->[0] == 200;
[200, "OK", $args{shell}];
}
$SPEC{detect_prog} = {
v => 1.1,
summary => "Detect a program",
args => {
%shell_arg,
prog => {
schema => 'str*',
completion => $_complete_prog,
req => 1,
pos => 0,
},
},
'cmdline.default_format' => 'json',
};
sub detect_prog {
require File::Which;
my %args = @_;
_set_args_defaults(\%args);
my $progname = $args{prog};
my $progpath = File::Which::which($progname);
return [404, "No such program '$progname'"] unless $progpath;
$progname =~ s!.+/!!;
_detect_prog(
prog => $progname,
progpath => $progpath,
shell => $args{shell},
);
}
$SPEC{init} = {
v => 1.1,
summary => 'Initialize shcompgen',
description => <<'_',
This subcommand creates the completion directories and initialization shell
script, as well as run `generate`.
_
args => {
%common_args,
},
};
sub init {
my %args = @_;
my $setdef_res = _set_args_defaults(\%args);
return $setdef_res unless $setdef_res->[0] == 200;
lib/App/shcompgen.pm view on Meta::CPAN
_
element_completion => sub {
# list programs in the completion scripts dir
require Complete::Util;
my %args = @_;
my $word = $args{word} // '';
my $res = list($args{args});
return unless $res->[0] == 200;
Complete::Util::complete_array_elem(
array=>$res->[2], word=>$word);
},
},
},
};
sub remove {
my %args = @_;
$args{prog} //= _all_exec_in_PATH();
_generate_or_remove('remove', %args);
}
1;
# ABSTRACT: Generate shell completion scripts
__END__
=pod
=encoding UTF-8
=head1 NAME
App::shcompgen - Generate shell completion scripts
=head1 VERSION
This document describes version 0.325 of App::shcompgen (from Perl distribution App-shcompgen), released on 2022-10-07.
=head1 FUNCTIONS
=head2 detect_prog
Usage:
detect_prog(%args) -> [$status_code, $reason, $payload, \%result_meta]
Detect a program.
This function is not exported.
Arguments ('*' denotes required arguments):
=over 4
=item * B<prog>* => I<str>
=item * B<shell> => I<str>
Override guessing and select shell manually.
=back
Returns an enveloped result (an array).
First element ($status_code) is an integer containing HTTP-like status code
(200 means OK, 4xx caller error, 5xx function error). Second element
($reason) is a string containing error message, or something like "OK" if status is
200. Third element ($payload) is the actual result, but usually not present when enveloped result is an error response ($status_code is not 2xx). Fourth
element (%result_meta) is called result metadata and is optional, a hash
that contains extra information, much like how HTTP response headers provide additional metadata.
Return value: (any)
=head2 generate
Usage:
generate(%args) -> [$status_code, $reason, $payload, \%result_meta]
Generate shell completion scripts for detectable programs.
This function is not exported.
Arguments ('*' denotes required arguments):
=over 4
=item * B<bash_global_dir> => I<array[str]> (default: ["/etc/bash/completions"])
Directory to put completions scripts.
=item * B<bash_per_user_dir> => I<array[str]>
Directory to put completions scripts.
=item * B<fish_global_dir> => I<array[str]> (default: ["/etc/fish/completions"])
Directory to put completions scripts.
=item * B<fish_per_user_dir> => I<array[str]>
Directory to put completions scripts.
=item * B<global> => I<bool>
Use global completions directory.
Shell has global (system-wide) completions directory as well as per-user. For
example, in fish the global directory is by default C</etc/fish/completions> and
the per-user directory is C<~/.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 C<--global> or C<--per-user> overrides
that and manually select which.
=item * B<helper_global_dir> => I<str> (default: "/etc/shcompgen/helpers")
Directory to put helper scripts.
=item * B<helper_per_user_dir> => I<str>
Directory to put helper scripts.
=item * B<per_option> => I<bool>
Create per-option completion script if possible.
If set to true, then attempt to create completion script that register each
option. This creates nicer completion in some shells, e.g. fish and zsh. For
example, option description can be shown.
This is possible for only some types of scripts, e.g. L<Perinci::CmdLine>-
(that does not have subcommands) or L<Getopt::Long::Descriptive>-based ones.
=item * B<prog> => I<array[str]>
Program(s) to generate completion for.
Can contain path (e.g. C<../foo>) or a plain word (C<foo>) in which case will be
searched from PATH.
=item * B<remove> => I<bool>
Remove completion for script that (now) is not detected to have completion.
The default behavior is to simply ignore existing completion script if the
program is not detected to have completion. When the C<remove> setting is
enabled, however, such existing completion script will be removed.
=item * B<replace> => I<bool>
Replace existing script.
The default behavior is to skip if an existing completion script exists.
=item * B<shell> => I<str>
Override guessing and select shell manually.
=item * B<stdout> => I<bool>
Output completion script to STDOUT.
=item * B<tcsh_global_dir> => I<array[str]> (default: ["/etc/tcsh/completions"])
Directory to put completions scripts.
=item * B<tcsh_per_user_dir> => I<array[str]>
Directory to put completions scripts.
=item * B<zsh_global_dir> => I<array[str]> (default: ["/usr/local/share/zsh/site-functions"])
Directory to put completions scripts.
=item * B<zsh_per_user_dir> => I<array[str]>
Directory to put completions scripts.
=back
Returns an enveloped result (an array).
First element ($status_code) is an integer containing HTTP-like status code
(200 means OK, 4xx caller error, 5xx function error). Second element
($reason) is a string containing error message, or something like "OK" if status is
200. Third element ($payload) is the actual result, but usually not present when enveloped result is an error response ($status_code is not 2xx). Fourth
element (%result_meta) is called result metadata and is optional, a hash
that contains extra information, much like how HTTP response headers provide additional metadata.
Return value: (any)
=head2 guess_shell
Usage:
guess_shell() -> [$status_code, $reason, $payload, \%result_meta]
Guess running shell.
This function is not exported.
No arguments.
Returns an enveloped result (an array).
First element ($status_code) is an integer containing HTTP-like status code
(200 means OK, 4xx caller error, 5xx function error). Second element
($reason) is a string containing error message, or something like "OK" if status is
200. Third element ($payload) is the actual result, but usually not present when enveloped result is an error response ($status_code is not 2xx). Fourth
element (%result_meta) is called result metadata and is optional, a hash
that contains extra information, much like how HTTP response headers provide additional metadata.
Return value: (any)
=head2 init
Usage:
init(%args) -> [$status_code, $reason, $payload, \%result_meta]
Initialize shcompgen.
This subcommand creates the completion directories and initialization shell
script, as well as run C<generate>.
This function is not exported.
Arguments ('*' denotes required arguments):
=over 4
=item * B<bash_global_dir> => I<array[str]> (default: ["/etc/bash/completions"])
Directory to put completions scripts.
=item * B<bash_per_user_dir> => I<array[str]>
Directory to put completions scripts.
=item * B<fish_global_dir> => I<array[str]> (default: ["/etc/fish/completions"])
Directory to put completions scripts.
=item * B<fish_per_user_dir> => I<array[str]>
Directory to put completions scripts.
=item * B<global> => I<bool>
Use global completions directory.
Shell has global (system-wide) completions directory as well as per-user. For
example, in fish the global directory is by default C</etc/fish/completions> and
the per-user directory is C<~/.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 C<--global> or C<--per-user> overrides
that and manually select which.
=item * B<helper_global_dir> => I<str> (default: "/etc/shcompgen/helpers")
Directory to put helper scripts.
=item * B<helper_per_user_dir> => I<str>
Directory to put helper scripts.
=item * B<per_option> => I<bool>
Create per-option completion script if possible.
If set to true, then attempt to create completion script that register each
option. This creates nicer completion in some shells, e.g. fish and zsh. For
example, option description can be shown.
This is possible for only some types of scripts, e.g. L<Perinci::CmdLine>-
(that does not have subcommands) or L<Getopt::Long::Descriptive>-based ones.
=item * B<shell> => I<str>
Override guessing and select shell manually.
=item * B<tcsh_global_dir> => I<array[str]> (default: ["/etc/tcsh/completions"])
Directory to put completions scripts.
=item * B<tcsh_per_user_dir> => I<array[str]>
Directory to put completions scripts.
=item * B<zsh_global_dir> => I<array[str]> (default: ["/usr/local/share/zsh/site-functions"])
Directory to put completions scripts.
=item * B<zsh_per_user_dir> => I<array[str]>
Directory to put completions scripts.
=back
Returns an enveloped result (an array).
First element ($status_code) is an integer containing HTTP-like status code
(200 means OK, 4xx caller error, 5xx function error). Second element
($reason) is a string containing error message, or something like "OK" if status is
200. Third element ($payload) is the actual result, but usually not present when enveloped result is an error response ($status_code is not 2xx). Fourth
element (%result_meta) is called result metadata and is optional, a hash
that contains extra information, much like how HTTP response headers provide additional metadata.
Return value: (any)
=head2 list
Usage:
list(%args) -> [$status_code, $reason, $payload, \%result_meta]
List all shell completion scripts generated by this script.
This function is not exported.
Arguments ('*' denotes required arguments):
=over 4
=item * B<bash_global_dir> => I<array[str]> (default: ["/etc/bash/completions"])
Directory to put completions scripts.
=item * B<bash_per_user_dir> => I<array[str]>
Directory to put completions scripts.
=item * B<detail> => I<bool>
=item * B<fish_global_dir> => I<array[str]> (default: ["/etc/fish/completions"])
Directory to put completions scripts.
=item * B<fish_per_user_dir> => I<array[str]>
Directory to put completions scripts.
=item * B<global> => I<bool>
Use global completions directory.
Shell has global (system-wide) completions directory as well as per-user. For
example, in fish the global directory is by default C</etc/fish/completions> and
the per-user directory is C<~/.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 C<--global> or C<--per-user> overrides
that and manually select which.
=item * B<helper_global_dir> => I<str> (default: "/etc/shcompgen/helpers")
Directory to put helper scripts.
=item * B<helper_per_user_dir> => I<str>
Directory to put helper scripts.
=item * B<per_option> => I<bool>
Create per-option completion script if possible.
If set to true, then attempt to create completion script that register each
option. This creates nicer completion in some shells, e.g. fish and zsh. For
example, option description can be shown.
This is possible for only some types of scripts, e.g. L<Perinci::CmdLine>-
(that does not have subcommands) or L<Getopt::Long::Descriptive>-based ones.
=item * B<shell> => I<str>
Override guessing and select shell manually.
=item * B<tcsh_global_dir> => I<array[str]> (default: ["/etc/tcsh/completions"])
Directory to put completions scripts.
=item * B<tcsh_per_user_dir> => I<array[str]>
Directory to put completions scripts.
=item * B<zsh_global_dir> => I<array[str]> (default: ["/usr/local/share/zsh/site-functions"])
Directory to put completions scripts.
=item * B<zsh_per_user_dir> => I<array[str]>
Directory to put completions scripts.
=back
Returns an enveloped result (an array).
First element ($status_code) is an integer containing HTTP-like status code
(200 means OK, 4xx caller error, 5xx function error). Second element
($reason) is a string containing error message, or something like "OK" if status is
200. Third element ($payload) is the actual result, but usually not present when enveloped result is an error response ($status_code is not 2xx). Fourth
element (%result_meta) is called result metadata and is optional, a hash
that contains extra information, much like how HTTP response headers provide additional metadata.
Return value: (any)
=head2 remove
Usage:
remove(%args) -> [$status_code, $reason, $payload, \%result_meta]
Remove shell completion scripts generated by this script.
This function is not exported.
Arguments ('*' denotes required arguments):
=over 4
=item * B<bash_global_dir> => I<array[str]> (default: ["/etc/bash/completions"])
Directory to put completions scripts.
=item * B<bash_per_user_dir> => I<array[str]>
Directory to put completions scripts.
=item * B<fish_global_dir> => I<array[str]> (default: ["/etc/fish/completions"])
Directory to put completions scripts.
=item * B<fish_per_user_dir> => I<array[str]>
Directory to put completions scripts.
=item * B<global> => I<bool>
Use global completions directory.
Shell has global (system-wide) completions directory as well as per-user. For
example, in fish the global directory is by default C</etc/fish/completions> and
the per-user directory is C<~/.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 C<--global> or C<--per-user> overrides
that and manually select which.
=item * B<helper_global_dir> => I<str> (default: "/etc/shcompgen/helpers")
Directory to put helper scripts.
=item * B<helper_per_user_dir> => I<str>
Directory to put helper scripts.
=item * B<per_option> => I<bool>
Create per-option completion script if possible.
If set to true, then attempt to create completion script that register each
option. This creates nicer completion in some shells, e.g. fish and zsh. For
example, option description can be shown.
This is possible for only some types of scripts, e.g. L<Perinci::CmdLine>-
(that does not have subcommands) or L<Getopt::Long::Descriptive>-based ones.
=item * B<prog> => I<array[str]>
Program(s) to remove completion script of.
Can contain path (e.g. C<../foo>) or a plain word (C<foo>) in which case will be
searched from PATH.
=item * B<shell> => I<str>
Override guessing and select shell manually.
=item * B<tcsh_global_dir> => I<array[str]> (default: ["/etc/tcsh/completions"])
Directory to put completions scripts.
=item * B<tcsh_per_user_dir> => I<array[str]>
Directory to put completions scripts.
=item * B<zsh_global_dir> => I<array[str]> (default: ["/usr/local/share/zsh/site-functions"])
Directory to put completions scripts.
=item * B<zsh_per_user_dir> => I<array[str]>
Directory to put completions scripts.
=back
Returns an enveloped result (an array).
First element ($status_code) is an integer containing HTTP-like status code
(200 means OK, 4xx caller error, 5xx function error). Second element
($reason) is a string containing error message, or something like "OK" if status is
200. Third element ($payload) is the actual result, but usually not present when enveloped result is an error response ($status_code is not 2xx). Fourth
element (%result_meta) is called result metadata and is optional, a hash
that contains extra information, much like how HTTP response headers provide additional metadata.
Return value: (any)
=head1 HOMEPAGE
Please visit the project's homepage at L<https://metacpan.org/release/App-shcompgen>.
=head1 SOURCE
Source repository is at L<https://github.com/perlancar/perl-App-shcompgen>.
=head1 AUTHOR
perlancar <perlancar@cpan.org>
=head1 CONTRIBUTING
To contribute, you can send patches by email/via RT, or send pull requests on
GitHub.
Most of the time, you don't need to build the distribution yourself. You can
simply modify the code, then test via:
% prove -l
If you want to build the distribution (e.g. to try to install it locally on your
system), you can install L<Dist::Zilla>,
L<Dist::Zilla::PluginBundle::Author::PERLANCAR>,
L<Pod::Weaver::PluginBundle::Author::PERLANCAR>, and sometimes one or two other
Dist::Zilla- and/or Pod::Weaver plugins. Any additional steps required beyond
that are considered a bug and can be reported to me.
( run in 2.290 seconds using v1.01-cache-2.11-cpan-39bf76dae61 )