package PVE::CLIHandler; use strict; use warnings; use PVE::Exception qw(raise raise_param_exc); use PVE::RESTHandler; use PVE::PodParser; use base qw(PVE::RESTHandler); my $cmddef; my $exename; my $expand_command_name = sub { my ($def, $cmd) = @_; if (!$def->{$cmd}) { my $expanded; for my $k (keys(%$def)) { if ($k =~ m/^$cmd/) { if ($expanded) { $expanded = undef; # more than one match last; } else { $expanded = $k; } } } $cmd = $expanded if $expanded; } return $cmd; }; __PACKAGE__->register_method ({ name => 'help', path => 'help', method => 'GET', description => "Get help about specified command.", parameters => { additionalProperties => 0, properties => { cmd => { description => "Command name", type => 'string', optional => 1, }, verbose => { description => "Verbose output format.", type => 'boolean', optional => 1, }, }, }, returns => { type => 'null' }, code => sub { my ($param) = @_; die "not initialized" if !($cmddef && $exename); my $cmd = $param->{cmd}; my $verbose = defined($cmd) && $cmd; $verbose = $param->{verbose} if defined($param->{verbose}); if (!$cmd) { if ($verbose) { print_usage_verbose(); } else { print_usage_short(\*STDOUT); } return undef; } $cmd = &$expand_command_name($cmddef, $cmd); my ($class, $name, $arg_param, $uri_param) = @{$cmddef->{$cmd} || []}; raise_param_exc({ cmd => "no such command '$cmd'"}) if !$class; my $str = $class->usage_str($name, "$exename $cmd", $arg_param, $uri_param, $verbose ? 'full' : 'short'); if ($verbose) { print "$str\n"; } else { print "USAGE: $str\n"; } return undef; }}); sub print_pod_manpage { my ($podfn) = @_; die "not initialized" if !($cmddef && $exename); die "no pod file specified" if !$podfn; my $synopsis = ""; $synopsis .= " $exename [ARGS] [OPTIONS]\n\n"; my $style = 'full'; # or should we use 'short'? my $oldclass; foreach my $cmd (sorted_commands()) { my ($class, $name, $arg_param, $uri_param) = @{$cmddef->{$cmd}}; my $str = $class->usage_str($name, "$exename $cmd", $arg_param, $uri_param, $style); $str =~ s/^USAGE: //; $synopsis .= "\n" if $oldclass && $oldclass ne $class; $str =~ s/\n/\n /g; $synopsis .= " $str\n\n"; $oldclass = $class; } $synopsis .= "\n"; my $parser = PVE::PodParser->new(); $parser->{include}->{synopsis} = $synopsis; $parser->parse_from_file($podfn); } sub print_usage_verbose { die "not initialized" if !($cmddef && $exename); print "USAGE: $exename [ARGS] [OPTIONS]\n\n"; foreach my $cmd (sort keys %$cmddef) { my ($class, $name, $arg_param, $uri_param) = @{$cmddef->{$cmd}}; my $str = $class->usage_str($name, "$exename $cmd", $arg_param, $uri_param, 'full'); print "$str\n\n"; } } sub sorted_commands { return sort { ($cmddef->{$a}->[0] cmp $cmddef->{$b}->[0]) || ($a cmp $b)} keys %$cmddef; } sub print_usage_short { my ($fd, $msg) = @_; die "not initialized" if !($cmddef && $exename); print $fd "ERROR: $msg\n" if $msg; print $fd "USAGE: $exename [ARGS] [OPTIONS]\n"; my $oldclass; foreach my $cmd (sorted_commands()) { my ($class, $name, $arg_param, $uri_param) = @{$cmddef->{$cmd}}; my $str = $class->usage_str($name, "$exename $cmd", $arg_param, $uri_param, 'short'); print $fd "\n" if $oldclass && $oldclass ne $class; print $fd " $str"; $oldclass = $class; } } sub handle_cmd { my ($def, $cmdname, $cmd, $args, $pwcallback, $podfn) = @_; $cmddef = $def; $exename = $cmdname; $cmddef->{help} = [ __PACKAGE__, 'help', ['cmd'] ]; if (!$cmd) { print_usage_short (\*STDERR, "no command specified"); exit (-1); } elsif ($cmd eq 'verifyapi') { PVE::RESTHandler::validate_method_schemas(); return; } elsif ($cmd eq 'printmanpod') { print_pod_manpage($podfn); return; } $cmd = &$expand_command_name($cmddef, $cmd); my ($class, $name, $arg_param, $uri_param, $outsub) = @{$cmddef->{$cmd} || []}; if (!$class) { print_usage_short (\*STDERR, "unknown command '$cmd'"); exit (-1); } my $prefix = "$exename $cmd"; my $res = $class->cli_handler($prefix, $name, \@ARGV, $arg_param, $uri_param, $pwcallback); if ($outsub) { &$outsub($res); } } 1;