From e075b2149b5d287b30718b31bee5ba80aba3da94 Mon Sep 17 00:00:00 2001 From: Simon Legner Date: Sun, 10 Feb 2019 22:06:42 +0100 Subject: [PATCH] scripts/completion.pl: also generate fish completion file This is the renamed script formerly known as zsh.pl Closes #3545 --- .gitignore | 1 + Makefile.am | 4 +- configure.ac | 25 ++++++++ scripts/Makefile.am | 22 +++++-- scripts/completion.pl | 134 ++++++++++++++++++++++++++++++++++++++++++ scripts/zsh.pl | 92 ----------------------------- 6 files changed, 179 insertions(+), 99 deletions(-) create mode 100755 scripts/completion.pl delete mode 100755 scripts/zsh.pl diff --git a/.gitignore b/.gitignore index 8cbb1a985..9b040fea0 100644 --- a/.gitignore +++ b/.gitignore @@ -53,6 +53,7 @@ mkinstalldirs tags test-driver scripts/_curl +scripts/curl.fish curl_fuzzer curl_fuzzer_seed_corpus.zip libstandaloneengine.a diff --git a/Makefile.am b/Makefile.am index debec9aa3..ac5eca3ba 100644 --- a/Makefile.am +++ b/Makefile.am @@ -155,9 +155,9 @@ WINBUILD_DIST = winbuild/BUILD.WINDOWS.txt winbuild/gen_resp_file.bat \ winbuild/MakefileBuild.vc winbuild/Makefile.vc EXTRA_DIST = CHANGES COPYING maketgz Makefile.dist curl-config.in \ - RELEASE-NOTES buildconf libcurl.pc.in MacOSX-Framework scripts/zsh.pl \ + RELEASE-NOTES buildconf libcurl.pc.in MacOSX-Framework \ scripts/updatemanpages.pl $(CMAKE_DIST) $(VC_DIST) $(WINBUILD_DIST) \ - lib/libcurl.vers.in buildconf.bat scripts/coverage.sh + lib/libcurl.vers.in buildconf.bat scripts/coverage.sh scripts/completion.pl CLEANFILES = $(VC6_LIBDSP) $(VC6_SRCDSP) $(VC7_LIBVCPROJ) $(VC7_SRCVCPROJ) \ $(VC71_LIBVCPROJ) $(VC71_SRCVCPROJ) $(VC8_LIBVCPROJ) $(VC8_SRCVCPROJ) \ diff --git a/configure.ac b/configure.ac index ab5275a8f..bb1a68508 100755 --- a/configure.ac +++ b/configure.ac @@ -3412,6 +3412,31 @@ case "$OPT_ZSH_FPATH" in ;; esac +dnl ********************************************************************** +dnl Check for fish completion path +dnl ********************************************************************** + +OPT_FISH_FPATH=default +AC_ARG_WITH(fish-functions-dir, +AC_HELP_STRING([--with-fish-functions-dir=PATH],[Install fish completions to PATH]) +AC_HELP_STRING([--without-fish-functions-dir],[Do not install fish completions]), + [OPT_FISH_FPATH=$withval]) +case "$OPT_FISH_FPATH" in + no) + dnl --without-fish-functions-dir option used + ;; + default|yes) + dnl --with-fish-functions-dir option used without path + FISH_FUNCTIONS_DIR="$datarootdir/fish/completions" + AC_SUBST(FISH_FUNCTIONS_DIR) + ;; + *) + dnl --with-fish-functions-dir option used with path + FISH_FUNCTIONS_DIR="$withval" + AC_SUBST(FISH_FUNCTIONS_DIR) + ;; +esac + dnl ********************************************************************** dnl Back to "normal" configuring dnl ********************************************************************** diff --git a/scripts/Makefile.am b/scripts/Makefile.am index 15c08828c..297b8d29b 100644 --- a/scripts/Makefile.am +++ b/scripts/Makefile.am @@ -20,20 +20,30 @@ # ########################################################################### ZSH_FUNCTIONS_DIR = @ZSH_FUNCTIONS_DIR@ +FISH_FUNCTIONS_DIR = @FISH_FUNCTIONS_DIR@ PERL = @PERL@ ZSH_COMPLETION_FUNCTION_FILENAME = _curl +FISH_COMPLETION_FUNCTION_FILENAME = curl.fish -CLEANFILES = $(ZSH_COMPLETION_FUNCTION_FILENAME) +CLEANFILES = $(ZSH_COMPLETION_FUNCTION_FILENAME) $(FISH_COMPLETION_FUNCTION_FILENAME) -all-local: $(ZSH_COMPLETION_FUNCTION_FILENAME) +all-local: $(ZSH_COMPLETION_FUNCTION_FILENAME) $(FISH_COMPLETION_FUNCTION_FILENAME) -$(ZSH_COMPLETION_FUNCTION_FILENAME): zsh.pl +$(ZSH_COMPLETION_FUNCTION_FILENAME): completion.pl if CROSSCOMPILING @echo "NOTICE: we can't generate zsh completion when cross-compiling!" else # if not cross-compiling: - @if ! test -x "$(PERL)"; then echo "No perl: can't install zsh.pl"; exit 0; fi - $(PERL) $(srcdir)/zsh.pl $(top_builddir)/src/curl$(EXEEXT) > $@ + @if ! test -x "$(PERL)"; then echo "No perl: can't install completion.pl"; exit 0; fi + $(PERL) $(srcdir)/completion.pl --curl $(top_builddir)/src/curl$(EXEEXT) --shell zsh > $@ +endif + +$(FISH_COMPLETION_FUNCTION_FILENAME): completion.pl +if CROSSCOMPILING + @echo "NOTICE: we can't generate fish completion when cross-compiling!" +else # if not cross-compiling: + @if ! test -x "$(PERL)"; then echo "No perl: can't install completion.pl"; exit 0; fi + $(PERL) $(srcdir)/completion.pl --curl $(top_builddir)/src/curl$(EXEEXT) --shell fish > $@ endif install-data-local: @@ -41,5 +51,7 @@ if CROSSCOMPILING @echo "NOTICE: we can't install zsh completion when cross-compiling!" else # if not cross-compiling: $(MKDIR_P) $(DESTDIR)$(ZSH_FUNCTIONS_DIR) + $(MKDIR_P) $(DESTDIR)$(FISH_FUNCTIONS_DIR) $(INSTALL_DATA) $(ZSH_COMPLETION_FUNCTION_FILENAME) $(DESTDIR)$(ZSH_FUNCTIONS_DIR)/$(ZSH_COMPLETION_FUNCTION_FILENAME) + $(INSTALL_DATA) $(FISH_COMPLETION_FUNCTION_FILENAME) $(DESTDIR)$(FISH_FUNCTIONS_DIR)/$(FISH_COMPLETION_FUNCTION_FILENAME) endif diff --git a/scripts/completion.pl b/scripts/completion.pl new file mode 100755 index 000000000..1c41755b4 --- /dev/null +++ b/scripts/completion.pl @@ -0,0 +1,134 @@ +#!/usr/bin/env perl + +use strict; +use warnings; +use Getopt::Long(); +use Pod::Usage(); + +my $curl = 'curl'; +my $shell = 'zsh'; +my $help = 0; +Getopt::Long::GetOptions( + 'curl=s' => \$curl, + 'shell=s' => \$shell, + 'help' => \$help, +) or Pod::Usage::pod2usage(); +Pod::Usage::pod2usage() if $help; + +my $regex = '\s+(?:(-[^\s]+),\s)?(--[^\s]+)\s*(\<.+?\>)?\s+(.*)'; +my @opts = parse_main_opts('--help', $regex); + +if ($shell eq 'fish') { + print "# curl fish completion\n\n"; + print qq{$_ \n} foreach (@opts); +} elsif ($shell eq 'zsh') { + my $opts_str; + + $opts_str .= qq{ $_ \\\n} foreach (@opts); + chomp $opts_str; + +my $tmpl = <<"EOS"; +#compdef curl + +# curl zsh completion + +local curcontext="\$curcontext" state state_descr line +typeset -A opt_args + +local rc=1 + +_arguments -C -S \\ +$opts_str + '*:URL:_urls' && rc=0 + +return rc +EOS + + print $tmpl; +} else { + die("Unsupported shell: $shell"); +} + +sub parse_main_opts { + my ($cmd, $regex) = @_; + + my @list; + my @lines = call_curl($cmd); + + foreach my $line (@lines) { + my ($short, $long, $arg, $desc) = ($line =~ /^$regex/) or next; + + my $option = ''; + + $arg =~ s/\:/\\\:/g if defined $arg; + + $desc =~ s/'/'\\''/g if defined $desc; + $desc =~ s/\[/\\\[/g if defined $desc; + $desc =~ s/\]/\\\]/g if defined $desc; + $desc =~ s/\:/\\\:/g if defined $desc; + + if ($shell eq 'fish') { + $option .= "complete --command curl"; + $option .= " --short-option '" . strip_dash(trim($short)) . "'" + if defined $short; + $option .= " --long-option '" . strip_dash(trim($long)) . "'" + if defined $long; + $option .= " --description '" . strip_dash(trim($desc)) . "'" + if defined $desc; + } elsif ($shell eq 'zsh') { + $option .= '{' . trim($short) . ',' if defined $short; + $option .= trim($long) if defined $long; + $option .= '}' if defined $short; + $option .= '\'[' . trim($desc) . ']\'' if defined $desc; + + $option .= ":'$arg'" if defined $arg; + + $option .= ':_files' + if defined $arg and ($arg eq '' || $arg eq '' + || $arg eq ''); + } + + push @list, $option; + } + + # Sort longest first, because zsh won't complete an option listed + # after one that's a prefix of it. + @list = sort { + $a =~ /([^=]*)/; my $ma = $1; + $b =~ /([^=]*)/; my $mb = $1; + + length($mb) <=> length($ma) + } @list if $shell eq 'zsh'; + + return @list; +} + +sub trim { my $s = shift; $s =~ s/^\s+|\s+$//g; return $s }; +sub strip_dash { my $s = shift; $s =~ s/^-+//g; return $s }; + +sub call_curl { + my ($cmd) = @_; + my $output = `"$curl" $cmd`; + if ($? == -1) { + die "Could not run curl: $!"; + } elsif ((my $exit_code = $? >> 8) != 0) { + die "curl returned $exit_code with output:\n$output"; + } + return split /\n/, $output; +} + +__END__ + +=head1 NAME + +completion.pl - Generates tab-completion files for various shells + +=head1 SYNOPSIS + +completion.pl [options...] + + --curl path to curl executable + --shell zsh/fish + --help prints this help + +=cut diff --git a/scripts/zsh.pl b/scripts/zsh.pl deleted file mode 100755 index 0f9cbec7d..000000000 --- a/scripts/zsh.pl +++ /dev/null @@ -1,92 +0,0 @@ -#!/usr/bin/env perl - -# Generate ZSH completion - -use strict; -use warnings; - -my $curl = $ARGV[0] || 'curl'; - -my $regex = '\s+(?:(-[^\s]+),\s)?(--[^\s]+)\s*(\<.+?\>)?\s+(.*)'; -my @opts = parse_main_opts('--help', $regex); - -my $opts_str; - -$opts_str .= qq{ $_ \\\n} foreach (@opts); -chomp $opts_str; - -my $tmpl = <<"EOS"; -#compdef curl - -# curl zsh completion - -local curcontext="\$curcontext" state state_descr line -typeset -A opt_args - -local rc=1 - -_arguments -C -S \\ -$opts_str - '*:URL:_urls' && rc=0 - -return rc -EOS - -print $tmpl; - -sub parse_main_opts { - my ($cmd, $regex) = @_; - - my @list; - my @lines = call_curl($cmd); - - foreach my $line (@lines) { - my ($short, $long, $arg, $desc) = ($line =~ /^$regex/) or next; - - my $option = ''; - - $arg =~ s/\:/\\\:/g if defined $arg; - - $desc =~ s/'/'\\''/g if defined $desc; - $desc =~ s/\[/\\\[/g if defined $desc; - $desc =~ s/\]/\\\]/g if defined $desc; - $desc =~ s/\:/\\\:/g if defined $desc; - - $option .= '{' . trim($short) . ',' if defined $short; - $option .= trim($long) if defined $long; - $option .= '}' if defined $short; - $option .= '\'[' . trim($desc) . ']\'' if defined $desc; - - $option .= ":'$arg'" if defined $arg; - - $option .= ':_files' - if defined $arg and ($arg eq '' || $arg eq '' - || $arg eq ''); - - push @list, $option; - } - - # Sort longest first, because zsh won't complete an option listed - # after one that's a prefix of it. - @list = sort { - $a =~ /([^=]*)/; my $ma = $1; - $b =~ /([^=]*)/; my $mb = $1; - - length($mb) <=> length($ma) - } @list; - - return @list; -} - -sub trim { my $s = shift; $s =~ s/^\s+|\s+$//g; return $s }; - -sub call_curl { - my ($cmd) = @_; - my $output = `"$curl" $cmd`; - if ($? == -1) { - die "Could not run curl: $!"; - } elsif ((my $exit_code = $? >> 8) != 0) { - die "curl returned $exit_code with output:\n$output"; - } - return split /\n/, $output; -}