[pve-devel] [RFC common 6/9] cli: factor out generate usage string

Dominik Csapak d.csapak at proxmox.com
Tue Dec 5 09:01:44 CET 2017


i am not sure if intended or not, but this patch makes a huge difference 
in how the help output is generated

previous a command like
pveum help asdf

would result in an output like this:

400 Parameter verification failed.
cmd: no such command 'asdf'
pveum help [<cmd>] [OPTIONS]

now it results in showing a verbose help for all (!) commands

this is especially weird when using tab completion

pveum help role<tab><tab>
<list of roleadd,...>
<enter>

help for all commands

On 11/06/2017 02:54 PM, Thomas Lamprecht wrote:
> reduce code reuse and prepare for sub commands
> ---
>   src/PVE/CLIHandler.pm | 137 ++++++++++++++++++++++++++++----------------------
>   1 file changed, 76 insertions(+), 61 deletions(-)
> 
> diff --git a/src/PVE/CLIHandler.pm b/src/PVE/CLIHandler.pm
> index 4e3342f..64a35c1 100644
> --- a/src/PVE/CLIHandler.pm
> +++ b/src/PVE/CLIHandler.pm
> @@ -40,6 +40,56 @@ my $complete_command_names = sub {
>       return [ sort keys %$cmddef ];
>   };
>   
> +sub generate_usage_str {
> +    my ($format, $cmd, $indent, $separator, $sortfunc) = @_;
> +
> +    $assert_initialized->();
> +    die 'format required' if !$format;
> +
> +    $sortfunc //= sub { sort keys %{$_[0]} };
> +    $separator //= '';
> +    $indent //= '';
> +
> +    my $can_read_pass = $cli_handler_class->can('read_password');
> +    my $can_str_param_fmap = $cli_handler_class->can('string_param_file_mapping');
> +
> +    my $def = $cmddef;
> +
> +    my $generate;
> +    $generate = sub {
> +	my ($indent, $separator, $def, $prefix) = @_;
> +
> +	my $str = '';
> +	if (ref($def) eq 'HASH') {
> +	    my $oldclass = undef;
> +	    foreach my $cmd (&$sortfunc($def)) {
> +
> +		if (ref($def->{$cmd}) eq 'ARRAY') {
> +		    my ($class, $name, $arg_param, $fixed_param) = @{$def->{$cmd}};
> +
> +		    $str .= $separator if $oldclass && $oldclass ne $class;
> +		    $str .= $indent;
> +		    $str .= $class->usage_str($name, "$prefix $cmd", $arg_param,
> +		                              $fixed_param, $format,
> +		                              $can_read_pass, $can_str_param_fmap);
> +		    $oldclass = $class;
> +		}
> +
> +	    }
> +	} else {
> +	    my ($class, $name, $arg_param, $fixed_param) = @$def;
> +	    $abort->("unknown command '$cmd'") if !$class;
> +
> +	    $str .= $indent;
> +	    $str .= $class->usage_str($name, $prefix, $arg_param, $fixed_param, $format,
> +	                              $can_read_pass, $can_str_param_fmap);
> +	}
> +	return $str;
> +    };
> +
> +    return $generate->($indent, $separator, $def, $exename);
> +}
> +
>   __PACKAGE__->register_method ({
>       name => 'help',
>       path => 'help',
> @@ -82,18 +132,14 @@ __PACKAGE__->register_method ({
>   	    return undef;
>   	}
>   
> -	$cmd = &$expand_command_name($cmddef, $cmd);
> +	my $str;
> +	if ($verbose) {
> +	    $str = generate_usage_str('full', $cmd, '');
> +	} else {
> +	    $str = generate_usage_str('short', $cmd, ' ' x 7);
> +	}
> +	$str =~ s/^\s+//;
>   
> -	my ($class, $name, $arg_param, $uri_param) = @{$cmddef->{$cmd} || []};
> -
> -	raise_param_exc({ cmd => "no such command '$cmd'"}) if !$class;
> -
> -	my $pwcallback = $cli_handler_class->can('read_password');
> -	my $stringfilemap = $cli_handler_class->can('string_param_file_mapping');
> -
> -	my $str = $class->usage_str($name, "$exename $cmd", $arg_param, $uri_param,
> -				    $verbose ? 'full' : 'short', $pwcallback,
> -				    $stringfilemap);
>   	if ($verbose) {
>   	    print "$str\n";
>   	} else {
> @@ -105,43 +151,22 @@ __PACKAGE__->register_method ({
>       }});
>   
>   sub print_simple_asciidoc_synopsis {
> -    my ($class, $name, $arg_param, $uri_param) = @_;
> -
>       $assert_initialized->();
>   
> -    my $pwcallback = $cli_handler_class->can('read_password');
> -    my $stringfilemap = $cli_handler_class->can('string_param_file_mapping');
> -
> -    my $synopsis = "*${name}* `help`\n\n";
> -
> -    $synopsis .= $class->usage_str($name, $name, $arg_param, $uri_param,
> -				   'asciidoc', $pwcallback, $stringfilemap);
> +    my $synopsis = "*${exename}* `help`\n\n";
> +    $synopsis .= generate_usage_str('asciidoc');
>   
>       return $synopsis;
>   }
>   
>   sub print_asciidoc_synopsis {
> -
>       $assert_initialized->();
>   
> -    my $pwcallback = $cli_handler_class->can('read_password');
> -    my $stringfilemap = $cli_handler_class->can('string_param_file_mapping');
> -
>       my $synopsis = "";
>   
>       $synopsis .= "*${exename}* `<COMMAND> [ARGS] [OPTIONS]`\n\n";
>   
> -    my $oldclass;
> -    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, 'asciidoc', $pwcallback,
> -				    $stringfilemap);
> -	$synopsis .= "\n" if $oldclass && $oldclass ne $class;
> -
> -	$synopsis .= "$str\n\n";
> -	$oldclass = $class;
> -    }
> +    $synopsis .= generate_usage_str('asciidoc');
>   
>       $synopsis .= "\n";
>   
> @@ -149,24 +174,13 @@ sub print_asciidoc_synopsis {
>   }
>   
>   sub print_usage_verbose {
> -
>       $assert_initialized->();
>   
> -    my $pwcallback = $cli_handler_class->can('read_password');
> -    my $stringfilemap = $cli_handler_class->can('string_param_file_mapping');
> -
>       print "USAGE: $exename <COMMAND> [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', $pwcallback, $stringfilemap);
> -	print "$str\n\n";
> -    }
> -}
> +    my $str = generate_usage_str('full');
>   
> -sub sorted_commands {
> -    return sort { ($cmddef->{$a}->[0] cmp $cmddef->{$b}->[0]) || ($a cmp $b)} keys %$cmddef;
> +    print "$str\n";
>   }
>   
>   sub print_usage_short {
> @@ -174,20 +188,21 @@ sub print_usage_short {
>   
>       $assert_initialized->();
>   
> -    my $pwcallback = $cli_handler_class->can('read_password');
> -    my $stringfilemap = $cli_handler_class->can('string_param_file_mapping');
> -
>       print $fd "ERROR: $msg\n" if $msg;
>       print $fd "USAGE: $exename <COMMAND> [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', $pwcallback, $stringfilemap);
> -	print $fd "\n" if $oldclass && $oldclass ne $class;
> -	print $fd "       $str";
> -	$oldclass = $class;
> -    }
> +    print {$fd} generate_usage_str('short', undef, ' ' x 7, "\n", sub {
> +	my ($h) = @_;
> +	return sort {
> +	    if (ref($h->{$a}) eq 'ARRAY' && ref($h->{$b}) eq 'ARRAY') {
> +		# $a and $b are both real commands order them by their class
> +		return $h->{$a}->[0] cmp $h->{$b}->[0];
> +	    } else {
> +		# both are from the same class
> +		return $a cmp $b;
> +	    }
> +	} keys %$h;
> +    });
>   }
>   
>   my $print_bash_completion = sub {
> @@ -338,7 +353,7 @@ sub generate_asciidoc_synopsis {
>       $cmddef = $def;
>   
>       if (ref($def) eq 'ARRAY') {
> -	print_simple_asciidoc_synopsis(@$def);
> +	print_simple_asciidoc_synopsis();
>       } else {
>   	$cmddef->{help} = [ __PACKAGE__, 'help', ['cmd'] ];
>   
> @@ -398,7 +413,7 @@ my $handle_simple_cmd = sub {
>       if (scalar(@$args) >= 1) {
>   	if ($args->[0] eq 'help') {
>   	    my $str = "USAGE: $name help\n";
> -	    $str .= $class->usage_str($name, $name, $arg_param, $uri_param, 'long', $pwcallback, $stringfilemap);
> +	    $str .= generate_usage_str('long');
>   	    print STDERR "$str\n\n";
>   	    return;
>   	} elsif ($args->[0] eq 'verifyapi') {
> 





More information about the pve-devel mailing list