• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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