1#!/usr/bin/env perl 2#*************************************************************************** 3# _ _ ____ _ 4# Project ___| | | | _ \| | 5# / __| | | | |_) | | 6# | (__| |_| | _ <| |___ 7# \___|\___/|_| \_\_____| 8# 9# Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al. 10# 11# This software is licensed as described in the file COPYING, which 12# you should have received as part of this distribution. The terms 13# are also available at https://curl.se/docs/copyright.html. 14# 15# You may opt to use, copy, modify, merge, publish, distribute and/or sell 16# copies of the Software, and permit persons to whom the Software is 17# furnished to do so, under the terms of the COPYING file. 18# 19# This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY 20# KIND, either express or implied. 21# 22# SPDX-License-Identifier: curl 23# 24########################################################################### 25 26use strict; 27use warnings; 28use Getopt::Long(); 29use Pod::Usage(); 30 31my $curl = 'curl'; 32my $shell = 'zsh'; 33my $help = 0; 34Getopt::Long::GetOptions( 35 'curl=s' => \$curl, 36 'shell=s' => \$shell, 37 'help' => \$help, 38) or Pod::Usage::pod2usage(); 39Pod::Usage::pod2usage() if $help; 40 41my $regex = '\s+(?:(-[^\s]+),\s)?(--[^\s]+)\s*(\<.+?\>)?\s+(.*)'; 42my @opts = parse_main_opts('--help all', $regex); 43 44if ($shell eq 'fish') { 45 print "# curl fish completion\n\n"; 46 print "# Complete file paths after @\n"; 47 print q(complete -c curl -n 'string match -qr "^@" -- (commandline -ct)' -k -xa "(printf '%s\n' -- @(__fish_complete_suffix --complete=(commandline -ct | string replace -r '^@' '') ''))"); 48 print "\n\n"; 49 print qq{$_ \n} foreach (@opts); 50} elsif ($shell eq 'zsh') { 51 my $opts_str; 52 53 $opts_str .= qq{ $_ \\\n} foreach (@opts); 54 chomp $opts_str; 55 56my $tmpl = <<"EOS"; 57#compdef curl 58 59# curl zsh completion 60 61local curcontext="\$curcontext" state state_descr line 62typeset -A opt_args 63 64local rc=1 65 66_arguments -C -S \\ 67$opts_str 68 '*:URL:_urls' && rc=0 69 70return rc 71EOS 72 73 print $tmpl; 74} else { 75 die("Unsupported shell: $shell"); 76} 77 78sub parse_main_opts { 79 my ($cmd, $regex) = @_; 80 81 my @list; 82 my @lines = call_curl($cmd); 83 84 foreach my $line (@lines) { 85 my ($short, $long, $arg, $desc) = ($line =~ /^$regex/) or next; 86 87 my $option = ''; 88 89 $arg =~ s/\:/\\\:/g if defined $arg; 90 91 $desc =~ s/'/'\\''/g if defined $desc; 92 $desc =~ s/\[/\\\[/g if defined $desc; 93 $desc =~ s/\]/\\\]/g if defined $desc; 94 $desc =~ s/\:/\\\:/g if defined $desc; 95 96 if ($shell eq 'fish') { 97 $option .= "complete --command curl"; 98 $option .= " --short-option '" . strip_dash(trim($short)) . "'" 99 if defined $short; 100 $option .= " --long-option '" . strip_dash(trim($long)) . "'" 101 if defined $long; 102 $option .= " --description '" . strip_dash(trim($desc)) . "'" 103 if defined $desc; 104 } elsif ($shell eq 'zsh') { 105 $option .= '{' . trim($short) . ',' if defined $short; 106 $option .= trim($long) if defined $long; 107 $option .= '}' if defined $short; 108 $option .= '\'[' . trim($desc) . ']\'' if defined $desc; 109 110 if (defined $arg) { 111 $option .= ":'$arg'"; 112 if ($arg =~ /<file ?(name)?>|<path>/) { 113 $option .= ':_files'; 114 } elsif ($arg =~ /<dir>/) { 115 $option .= ":'_path_files -/'"; 116 } elsif ($arg =~ /<url>/i) { 117 $option .= ':_urls'; 118 } elsif ($long =~ /ftp/ && $arg =~ /<method>/) { 119 $option .= ":'(multicwd nocwd singlecwd)'"; 120 } elsif ($arg =~ /<method>/) { 121 $option .= ":'(DELETE GET HEAD POST PUT)'"; 122 } 123 } 124 } 125 126 push @list, $option; 127 } 128 129 # Sort longest first, because zsh won't complete an option listed 130 # after one that's a prefix of it. 131 @list = sort { 132 $a =~ /([^=]*)/; my $ma = $1; 133 $b =~ /([^=]*)/; my $mb = $1; 134 135 length($mb) <=> length($ma) 136 } @list if $shell eq 'zsh'; 137 138 return @list; 139} 140 141sub trim { my $s = shift; $s =~ s/^\s+|\s+$//g; return $s }; 142sub strip_dash { my $s = shift; $s =~ s/^-+//g; return $s }; 143 144sub call_curl { 145 my ($cmd) = @_; 146 my $output = `"$curl" $cmd`; 147 if ($? == -1) { 148 die "Could not run curl: $!"; 149 } elsif ((my $exit_code = $? >> 8) != 0) { 150 die "curl returned $exit_code with output:\n$output"; 151 } 152 return split /\n/, $output; 153} 154 155__END__ 156 157=head1 NAME 158 159completion.pl - Generates tab-completion files for various shells 160 161=head1 SYNOPSIS 162 163completion.pl [options...] 164 165 --curl path to curl executable 166 --shell zsh/fish 167 --help prints this help 168 169=cut 170