2e7b14dc0e26479b05c498626da7203c77bba20d
[pve-common.git] / src / PVE / CLIHandler.pm
1 package PVE::CLIHandler;
2
3 use strict;
4 use warnings;
5
6 use PVE::Exception qw(raise raise_param_exc);
7 use PVE::RESTHandler;
8 use PVE::PodParser;
9
10 use base qw(PVE::RESTHandler);
11
12 my $cmddef;
13 my $exename;
14
15 my $expand_command_name = sub {
16     my ($def, $cmd) = @_;
17
18     if (!$def->{$cmd}) {
19         my $expanded;
20         for my $k (keys(%$def)) {
21             if ($k =~ m/^$cmd/) {
22                 if ($expanded) {
23                     $expanded = undef; # more than one match
24                     last;
25                 } else {
26                     $expanded = $k;
27                 }
28             }
29         }
30         $cmd = $expanded if $expanded;
31     }
32     return $cmd;
33 };
34
35 __PACKAGE__->register_method ({
36     name => 'help', 
37     path => 'help',
38     method => 'GET',
39     description => "Get help about specified command.",
40     parameters => {
41         additionalProperties => 0,
42         properties => {
43             cmd => {
44                 description => "Command name",
45                 type => 'string',
46                 optional => 1,
47             },
48             verbose => {
49                 description => "Verbose output format.",
50                 type => 'boolean',
51                 optional => 1,
52             },
53         },
54     },
55     returns => { type => 'null' },
56     
57     code => sub {
58         my ($param) = @_;
59
60         die "not initialized" if !($cmddef && $exename);
61
62         my $cmd = $param->{cmd};
63
64         my $verbose = defined($cmd) && $cmd; 
65         $verbose = $param->{verbose} if defined($param->{verbose});
66
67         if (!$cmd) {
68             if ($verbose) {
69                 print_usage_verbose();
70             } else {            
71                 print_usage_short(\*STDOUT);
72             }
73             return undef;
74         }
75
76         $cmd = &$expand_command_name($cmddef, $cmd);
77
78         my ($class, $name, $arg_param, $uri_param) = @{$cmddef->{$cmd} || []};
79
80         raise_param_exc({ cmd => "no such command '$cmd'"}) if !$class;
81
82
83         my $str = $class->usage_str($name, "$exename $cmd", $arg_param, $uri_param, $verbose ? 'full' : 'short');
84         if ($verbose) {
85             print "$str\n";
86         } else {
87             print "USAGE: $str\n";
88         }
89
90         return undef;
91
92     }});
93
94 sub print_pod_manpage {
95     my ($podfn) = @_;
96
97     die "not initialized" if !($cmddef && $exename);
98     die "no pod file specified" if !$podfn;
99
100     my $synopsis = "";
101     
102     $synopsis .= " $exename <COMMAND> [ARGS] [OPTIONS]\n\n";
103
104     my $style = 'full'; # or should we use 'short'?
105     my $oldclass;
106     foreach my $cmd (sorted_commands()) {
107         my ($class, $name, $arg_param, $uri_param) = @{$cmddef->{$cmd}};
108         my $str = $class->usage_str($name, "$exename $cmd", $arg_param, 
109                                     $uri_param, $style);
110         $str =~ s/^USAGE: //;
111
112         $synopsis .= "\n" if $oldclass && $oldclass ne $class;
113         $str =~ s/\n/\n /g;
114         $synopsis .= " $str\n\n";
115         $oldclass = $class;
116     }
117
118     $synopsis .= "\n";
119
120     my $parser = PVE::PodParser->new();
121     $parser->{include}->{synopsis} = $synopsis;
122     $parser->parse_from_file($podfn);
123 }
124
125 sub print_usage_verbose {
126
127     die "not initialized" if !($cmddef && $exename);
128
129     print "USAGE: $exename <COMMAND> [ARGS] [OPTIONS]\n\n";
130
131     foreach my $cmd (sort keys %$cmddef) {
132         my ($class, $name, $arg_param, $uri_param) = @{$cmddef->{$cmd}};
133         my $str = $class->usage_str($name, "$exename $cmd", $arg_param, $uri_param, 'full');
134         print "$str\n\n";
135     }
136 }
137
138 sub sorted_commands {   
139     return sort { ($cmddef->{$a}->[0] cmp $cmddef->{$b}->[0]) || ($a cmp $b)} keys %$cmddef;
140 }
141
142 sub print_usage_short {
143     my ($fd, $msg) = @_;
144
145     die "not initialized" if !($cmddef && $exename);
146
147     print $fd "ERROR: $msg\n" if $msg;
148     print $fd "USAGE: $exename <COMMAND> [ARGS] [OPTIONS]\n";
149
150     my $oldclass;
151     foreach my $cmd (sorted_commands()) {
152         my ($class, $name, $arg_param, $uri_param) = @{$cmddef->{$cmd}};
153         my $str = $class->usage_str($name, "$exename $cmd", $arg_param, $uri_param, 'short');
154         print $fd "\n" if $oldclass && $oldclass ne $class;
155         print $fd "       $str";
156         $oldclass = $class;
157     }
158 }
159
160 sub handle_cmd {
161     my ($def, $cmdname, $cmd, $args, $pwcallback, $podfn, $preparefunc) = @_;
162
163     $cmddef = $def;
164     $exename = $cmdname;
165
166     $cmddef->{help} = [ __PACKAGE__, 'help', ['cmd'] ];
167
168     if (!$cmd) { 
169         print_usage_short (\*STDERR, "no command specified");
170         exit (-1);
171     } elsif ($cmd eq 'verifyapi') {
172         PVE::RESTHandler::validate_method_schemas();
173         return;
174     } elsif ($cmd eq 'printmanpod') {
175         print_pod_manpage($podfn);
176         return;
177     }
178
179     &$preparefunc() if $preparefunc;
180
181     $cmd = &$expand_command_name($cmddef, $cmd);
182
183     my ($class, $name, $arg_param, $uri_param, $outsub) = @{$cmddef->{$cmd} || []};
184
185     if (!$class) {
186         print_usage_short (\*STDERR, "unknown command '$cmd'");
187         exit (-1);
188     }
189
190     my $prefix = "$exename $cmd";
191     my $res = $class->cli_handler($prefix, $name, \@ARGV, $arg_param, $uri_param, $pwcallback);
192
193     &$outsub($res) if $outsub;
194 }
195
196 sub handle_simple_cmd {
197     my ($def, $args, $pwcallback, $podfn) = @_;
198
199     my ($class, $name, $arg_param, $uri_param, $outsub) = @{$def};
200     die "no class specified" if !$class;
201
202     if (scalar(@$args) == 1) {
203         if ($args->[0] eq 'help') {
204             my $str = "USAGE: $name help\n";
205             $str .= $class->usage_str($name, $name, $arg_param, $uri_param, 'long');
206             print STDERR "$str\n\n";
207             return;
208         } elsif ($args->[0] eq 'verifyapi') {
209             PVE::RESTHandler::validate_method_schemas();
210             return;
211         } elsif ($args->[0] eq 'printmanpod') {
212             my $synopsis = " $name help\n\n";
213             my $str = $class->usage_str($name, $name, $arg_param, $uri_param, 'long');
214             $str =~ s/^USAGE://;
215             $str =~ s/\n/\n /g;
216             $synopsis .= $str;
217
218             my $parser = PVE::PodParser->new();
219             $parser->{include}->{synopsis} = $synopsis;
220             $parser->parse_from_file($podfn);
221             return;
222         }
223     }
224
225     my $res = $class->cli_handler($name, $name, \@ARGV, $arg_param, $uri_param, $pwcallback);
226
227     &$outsub($res) if $outsub;
228 }
229
230 1;