1#!/usr/bin/env perl 2#*************************************************************************** 3# _ _ ____ _ 4# Project ___| | | | _ \| | 5# / __| | | | |_) | | 6# | (__| |_| | _ <| |___ 7# \___|\___/|_| \_\_____| 8# 9# Copyright (C) 1998 - 2020, 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########################################################################### 23 24use strict; 25use warnings; 26use Getopt::Long(); 27use Pod::Usage(); 28 29my $curl = 'curl'; 30my $shell = 'zsh'; 31my $help = 0; 32Getopt::Long::GetOptions( 33 'curl=s' => \$curl, 34 'shell=s' => \$shell, 35 'help' => \$help, 36) or Pod::Usage::pod2usage(); 37Pod::Usage::pod2usage() if $help; 38 39my $regex = '\s+(?:(-[^\s]+),\s)?(--[^\s]+)\s*(\<.+?\>)?\s+(.*)'; 40my @opts = parse_main_opts('--help all', $regex); 41 42if ($shell eq 'fish') { 43 print "# curl fish completion\n\n"; 44 print qq{$_ \n} foreach (@opts); 45} elsif ($shell eq 'zsh') { 46 my $opts_str; 47 48 $opts_str .= qq{ $_ \\\n} foreach (@opts); 49 chomp $opts_str; 50 51my $tmpl = <<"EOS"; 52#compdef curl 53 54# curl zsh completion 55 56local curcontext="\$curcontext" state state_descr line 57typeset -A opt_args 58 59local rc=1 60 61_arguments -C -S \\ 62$opts_str 63 '*:URL:_urls' && rc=0 64 65return rc 66EOS 67 68 print $tmpl; 69} else { 70 die("Unsupported shell: $shell"); 71} 72 73sub parse_main_opts { 74 my ($cmd, $regex) = @_; 75 76 my @list; 77 my @lines = call_curl($cmd); 78 79 foreach my $line (@lines) { 80 my ($short, $long, $arg, $desc) = ($line =~ /^$regex/) or next; 81 82 my $option = ''; 83 84 $arg =~ s/\:/\\\:/g if defined $arg; 85 86 $desc =~ s/'/'\\''/g if defined $desc; 87 $desc =~ s/\[/\\\[/g if defined $desc; 88 $desc =~ s/\]/\\\]/g if defined $desc; 89 $desc =~ s/\:/\\\:/g if defined $desc; 90 91 if ($shell eq 'fish') { 92 $option .= "complete --command curl"; 93 $option .= " --short-option '" . strip_dash(trim($short)) . "'" 94 if defined $short; 95 $option .= " --long-option '" . strip_dash(trim($long)) . "'" 96 if defined $long; 97 $option .= " --description '" . strip_dash(trim($desc)) . "'" 98 if defined $desc; 99 } elsif ($shell eq 'zsh') { 100 $option .= '{' . trim($short) . ',' if defined $short; 101 $option .= trim($long) if defined $long; 102 $option .= '}' if defined $short; 103 $option .= '\'[' . trim($desc) . ']\'' if defined $desc; 104 105 $option .= ":'$arg'" if defined $arg; 106 107 $option .= ':_files' 108 if defined $arg and ($arg eq '<file>' || $arg eq '<filename>' 109 || $arg eq '<dir>'); 110 } 111 112 push @list, $option; 113 } 114 115 # Sort longest first, because zsh won't complete an option listed 116 # after one that's a prefix of it. 117 @list = sort { 118 $a =~ /([^=]*)/; my $ma = $1; 119 $b =~ /([^=]*)/; my $mb = $1; 120 121 length($mb) <=> length($ma) 122 } @list if $shell eq 'zsh'; 123 124 return @list; 125} 126 127sub trim { my $s = shift; $s =~ s/^\s+|\s+$//g; return $s }; 128sub strip_dash { my $s = shift; $s =~ s/^-+//g; return $s }; 129 130sub call_curl { 131 my ($cmd) = @_; 132 my $output = `"$curl" $cmd`; 133 if ($? == -1) { 134 die "Could not run curl: $!"; 135 } elsif ((my $exit_code = $? >> 8) != 0) { 136 die "curl returned $exit_code with output:\n$output"; 137 } 138 return split /\n/, $output; 139} 140 141__END__ 142 143=head1 NAME 144 145completion.pl - Generates tab-completion files for various shells 146 147=head1 SYNOPSIS 148 149completion.pl [options...] 150 151 --curl path to curl executable 152 --shell zsh/fish 153 --help prints this help 154 155=cut 156