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