update files from pve-common
authorDietmar Maurer <dietmar@proxmox.com>
Tue, 26 Jun 2018 11:44:42 +0000 (13:44 +0200)
committerDietmar Maurer <dietmar@proxmox.com>
Mon, 2 Jul 2018 07:19:53 +0000 (09:19 +0200)
Makefile
PVE/APIClient/CLIFormatter.pm [new file with mode: 0644]
PVE/APIClient/CLIHandler.pm
PVE/APIClient/JSONSchema.pm
PVE/APIClient/RESTHandler.pm

index c5fe801..e7177df 100644 (file)
--- a/Makefile
+++ b/Makefile
@@ -15,6 +15,7 @@ BASHCOMPLDIR=${DESTDIR}/usr/share/bash-completion/completions/
 PVE_COMMON_FILES=              \
        Tools.pm                \
        Syscall.pm              \
+       CLIFormatter.pm         \
        CLIHandler.pm           \
        JSONSchema.pm           \
        PTY.pm                  \
diff --git a/PVE/APIClient/CLIFormatter.pm b/PVE/APIClient/CLIFormatter.pm
new file mode 100644 (file)
index 0000000..add7a3a
--- /dev/null
@@ -0,0 +1,312 @@
+package PVE::APIClient::CLIFormatter;
+
+use strict;
+use warnings;
+use I18N::Langinfo;
+
+use PVE::APIClient::JSONSchema;
+use PVE::APIClient::PTY;
+use JSON;
+use utf8;
+use Encode;
+
+sub query_terminal_options {
+    my ($options) = @_;
+
+    $options //= {};
+
+    if (-t STDOUT) {
+       ($options->{columns}) = PVE::APIClient::PTY::tcgetsize(*STDOUT);
+    }
+
+    $options->{encoding} = I18N::Langinfo::langinfo(I18N::Langinfo::CODESET());
+
+    $options->{utf8} = 1 if $options->{encoding} eq 'UTF-8';
+
+    return $options;
+}
+
+sub data_to_text {
+    my ($data, $propdef) = @_;
+
+    return '' if !defined($data);
+
+    if (defined($propdef)) {
+       if (my $type = $propdef->{type}) {
+           if ($type eq 'boolean') {
+               return $data ? 1 : 0;
+           }
+       }
+       if (!defined($data) && defined($propdef->{default})) {
+           return "($propdef->{default})";
+       }
+       if (defined(my $renderer = $propdef->{renderer})) {
+           my $code = PVE::APIClient::JSONSchema::get_renderer($renderer);
+           die "internal error: unknown renderer '$renderer'" if !$code;
+           return $code->($data);
+       }
+    }
+
+    if (my $class = ref($data)) {
+       return to_json($data, { canonical => 1 });
+    } else {
+       return "$data";
+    }
+}
+
+# prints a formatted table with a title row.
+# $data - the data to print (array of objects)
+# $returnprops -json schema property description
+# $props_to_print - ordered list of properties to print
+# $options
+# - sort_key: can be used to sort after a column, if it isn't set we sort
+#   after the leftmost column (with no undef value in $data) this can be
+#   turned off by passing 0 as sort_key
+# - border: print with/without table header and asciiart border
+# - columns: limit output width (if > 0)
+# - utf8: use utf8 characters for table delimiters
+
+sub print_text_table {
+    my ($data, $returnprops, $props_to_print, $options) = @_;
+
+    my $sort_key = $options->{sort_key};
+    my $border = $options->{border};
+    my $columns = $options->{columns};
+    my $utf8 = $options->{utf8};
+    my $encoding = $options->{encoding} // 'UTF-8';
+
+    if (!defined($sort_key) || $sort_key eq 0) {
+       $sort_key = $props_to_print->[0];
+    }
+
+    if (defined($sort_key)) {
+       my $type = $returnprops->{$sort_key}->{type} // 'string';
+       if ($type eq 'integer' || $type eq 'number') {
+           @$data = sort { $a->{$sort_key} <=> $b->{$sort_key} } @$data;
+       } else {
+           @$data = sort { $a->{$sort_key} cmp $b->{$sort_key} } @$data;
+       }
+    }
+
+    my $colopts = {};
+
+    my $borderstring_m = '';
+    my $borderstring_b = '';
+    my $borderstring_t = '';
+    my $formatstring = '';
+
+    my $column_count = scalar(@$props_to_print);
+
+    my $tabledata = [];
+
+    foreach my $entry (@$data) {
+
+       my $height = 1;
+       my $rowdata = {};
+
+       for (my $i = 0; $i < $column_count; $i++) {
+           my $prop = $props_to_print->[$i];
+           my $propinfo = $returnprops->{$prop} // {};
+
+           my $text = data_to_text($entry->{$prop}, $propinfo);
+           my $lines = [ split(/\n/, $text) ];
+           my $linecount = scalar(@$lines);
+           $height = $linecount if $linecount > $height;
+
+           my $width = 0;
+           foreach my $line (@$lines) {
+               my $len = length($line);
+               $width = $len if $len > $width;
+           }
+
+           $rowdata->{$prop} = {
+               lines => $lines,
+               width => $width,
+           };
+       }
+
+       push @$tabledata, {
+           height => $height,
+           rowdata => $rowdata,
+       };
+    }
+
+    for (my $i = 0; $i < $column_count; $i++) {
+       my $prop = $props_to_print->[$i];
+       my $propinfo = $returnprops->{$prop} // {};
+
+       my $title = $propinfo->{title} // $prop;
+       my $cutoff = $propinfo->{print_width} // $propinfo->{maxLength};
+
+       # calculate maximal print width and cutoff
+       my $titlelen = length($title);
+
+       my $longest = $titlelen;
+       foreach my $coldata (@$tabledata) {
+           my $rowdata = $coldata->{rowdata}->{$prop};
+           $longest = $rowdata->{width} if $rowdata->{width} > $longest;
+       }
+       $cutoff = $longest if !defined($cutoff) || $cutoff > $longest;
+
+       $colopts->{$prop} = {
+           title => $title,
+           cutoff => $cutoff,
+       };
+
+       if ($border) {
+           if ($i == 0 && ($column_count == 1)) {
+               if ($utf8) {
+                   $formatstring .= "│ %-${cutoff}s │";
+                   $borderstring_t .= "┌─" . ('─' x $cutoff) . "─┐";
+                   $borderstring_m .= "├─" . ('─' x $cutoff) . "─┤";
+                   $borderstring_b .= "└─" . ('─' x $cutoff) . "─┘";
+               } else {
+                   $formatstring .= "| %-${cutoff}s |";
+                   $borderstring_m .= "+-" . ('-' x $cutoff) . "-+";
+               }
+           } elsif ($i == 0) {
+               if ($utf8) {
+                   $formatstring .= "│ %-${cutoff}s ";
+                   $borderstring_t .= "┌─" . ('─' x $cutoff) . '─';
+                   $borderstring_m .= "├─" . ('─' x $cutoff) . '─';
+                   $borderstring_b .= "└─" . ('─' x $cutoff) . '─';
+               } else {
+                   $formatstring .= "| %-${cutoff}s ";
+                   $borderstring_m .= "+-" . ('-' x $cutoff) . '-';
+               }
+           } elsif ($i == ($column_count - 1)) {
+               if ($utf8) {
+                   $formatstring .= "│ %-${cutoff}s │";
+                   $borderstring_t .= "┬─" . ('─' x $cutoff) . "─┐";
+                   $borderstring_m .= "┼─" . ('─' x $cutoff) . "─┤";
+                   $borderstring_b .= "┴─" . ('─' x $cutoff) . "─┘";
+               } else {
+                   $formatstring .= "| %-${cutoff}s |";
+                   $borderstring_m .= "+-" . ('-' x $cutoff) . "-+";
+               }
+           } else {
+               if ($utf8) {
+                   $formatstring .= "│ %-${cutoff}s ";
+                   $borderstring_t .= "┬─" . ('─' x $cutoff) . '─';
+                   $borderstring_m .= "┼─" . ('─' x $cutoff) . '─';
+                   $borderstring_b .= "┴─" . ('─' x $cutoff) . '─';
+               } else {
+                   $formatstring .= "| %-${cutoff}s ";
+                   $borderstring_m .= "+-" . ('-' x $cutoff) . '-';
+               }
+           }
+       } else {
+           # skip alignment and cutoff on last column
+           $formatstring .= ($i == ($column_count - 1)) ? "%s" : "%-${cutoff}s ";
+       }
+    }
+
+    $borderstring_t = $borderstring_m if !length($borderstring_t);
+    $borderstring_b = $borderstring_m if !length($borderstring_b);
+
+    my $writeln = sub {
+       my ($text) = @_;
+
+       if ($columns) {
+           print encode($encoding, substr($text, 0, $columns) . "\n");
+       } else {
+           print encode($encoding, $text) . "\n";
+       }
+    };
+
+    $writeln->($borderstring_t) if $border;
+    my $text = sprintf $formatstring, map { $colopts->{$_}->{title} } @$props_to_print;
+    $writeln->($text);
+
+    foreach my $coldata (@$tabledata) {
+       $writeln->($borderstring_m) if $border;
+
+       for (my $i = 0; $i < $coldata->{height}; $i++) {
+
+           $text = sprintf $formatstring, map {
+               substr($coldata->{rowdata}->{$_}->{lines}->[$i] // '', 0, $colopts->{$_}->{cutoff});
+           } @$props_to_print;
+
+           $writeln->($text);
+       }
+    }
+
+    $writeln->($borderstring_b) if $border;
+}
+
+# prints the result of an API GET call returning an array as a table.
+# takes formatting information from the results property of the call
+# if $props_to_print is provided, prints only those columns. otherwise
+# takes all fields of the results property, with a fallback
+# to all fields occuring in items of $data.
+sub print_api_list {
+    my ($data, $result_schema, $props_to_print, $options) = @_;
+
+    die "can only print object lists\n"
+       if !($result_schema->{type} eq 'array' && $result_schema->{items}->{type} eq 'object');
+
+    my $returnprops = $result_schema->{items}->{properties};
+
+    if (!defined($props_to_print)) {
+       $props_to_print = [ sort keys %$returnprops ];
+       if (!scalar(@$props_to_print)) {
+           my $all_props = {};
+           foreach my $obj (@{$data}) {
+               foreach my $key (keys %{$obj}) {
+                   $all_props->{ $key } = 1;
+               }
+           }
+           $props_to_print = [ sort keys %{$all_props} ];
+       }
+       die "unable to detect list properties\n" if !scalar(@$props_to_print);
+    }
+
+    print_text_table($data, $returnprops, $props_to_print, $options);
+}
+
+sub print_api_result {
+    my ($format, $data, $result_schema, $props_to_print, $options) = @_;
+
+    if (!defined($options)) {
+       $options = query_terminal_options({});
+    } else {
+       $options = { %$options }; # copy
+    }
+
+    return if $result_schema->{type} eq 'null';
+
+    if ($format eq 'json') {
+       # Note: we always use utf8 encoding for json format
+       print to_json($data, {utf8 => 1, allow_nonref => 1, canonical => 1, pretty => 1 });
+    } elsif ($format eq 'text' || $format eq 'plain') {
+       my $encoding = $options->{encoding} // 'UTF-8';
+       my $type = $result_schema->{type};
+       if ($type eq 'object') {
+           $props_to_print = [ sort keys %$data ] if !defined($props_to_print);
+           my $kvstore = [];
+           foreach my $key (@$props_to_print) {
+               push @$kvstore, { key => $key, value => data_to_text($data->{$key}, $result_schema->{properties}->{$key}) };
+           }
+           my $schema = { type => 'array', items => { type => 'object' }};
+           $options->{border} = $format eq 'text';
+           print_api_list($kvstore, $schema, ['key', 'value'], $options);
+       } elsif ($type eq 'array') {
+           return if !scalar(@$data);
+           my $item_type = $result_schema->{items}->{type};
+           if ($item_type eq 'object') {
+               $options->{border} = $format eq 'text';
+               print_api_list($data, $result_schema, $props_to_print, $options);
+           } else {
+               foreach my $entry (@$data) {
+                   print encode($encoding, data_to_text($entry) . "\n");
+               }
+           }
+       } else {
+           print encode($encoding, "$data\n");
+       }
+    } else {
+       die "internal error: unknown output format"; # should not happen
+    }
+}
+
+1;
index d9dd06a..d97f09a 100644 (file)
@@ -9,6 +9,7 @@ use PVE::APIClient::Exception qw(raise raise_param_exc);
 use PVE::APIClient::RESTHandler;
 use PVE::APIClient::PTY;
 
+use PVE::APIClient::CLIFormatter;
 
 use base qw(PVE::APIClient::RESTHandler);
 
@@ -67,6 +68,31 @@ sub get_standard_mapping {
     return $res;
 }
 
+my $gen_param_mapping_func = sub {
+    my ($cli_handler_class) = @_;
+
+    my $param_mapping = $cli_handler_class->can('param_mapping');
+
+    if (!$param_mapping) {
+       my $read_password = $cli_handler_class->can('read_password');
+       my $string_param_mapping = $cli_handler_class->can('string_param_file_mapping');
+
+       return $string_param_mapping if !$read_password;
+
+       $param_mapping = sub {
+           my ($name) = @_;
+
+           my $password_map = get_standard_mapping('pve-password', {
+               func => $read_password
+           });
+           my $map = $string_param_mapping ? $string_param_mapping->($name) : [];
+           return [@$map, $password_map];
+       };
+    }
+
+    return $param_mapping;
+};
+
 my $assert_initialized = sub {
     my @caller = caller;
     die "$caller[0]:$caller[2] - not initialized\n"
@@ -159,9 +185,7 @@ sub generate_usage_str {
     $separator //= '';
     $indent //= '';
 
-    my $read_password_func = $cli_handler_class->can('read_password');
-    my $param_mapping_func = $cli_handler_class->can('param_mapping') ||
-       $cli_handler_class->can('string_param_file_mapping');
+    my $param_cb = $gen_param_mapping_func->($cli_handler_class);
 
     my ($subcmd, $def, undef, undef, $cmdstr) = resolve_cmd($cmd);
     $abort->("unknown command '$cmdstr'") if !defined($def) && ref($cmd) eq 'ARRAY';
@@ -181,8 +205,7 @@ sub generate_usage_str {
                    $str .= $separator if $oldclass && $oldclass ne $class;
                    $str .= $indent;
                    $str .= $class->usage_str($name, "$prefix $cmd", $arg_param,
-                                             $fixed_param, $format,
-                                             $read_password_func, $param_mapping_func);
+                                             $fixed_param, $format, $param_cb);
                    $oldclass = $class;
 
                } elsif (defined($def->{$cmd}->{alias}) && ($format eq 'asciidoc')) {
@@ -205,8 +228,7 @@ sub generate_usage_str {
            $abort->("unknown command '$cmd'") if !$class;
 
            $str .= $indent;
-           $str .= $class->usage_str($name, $prefix, $arg_param, $fixed_param, $format,
-                                     $read_password_func, $param_mapping_func);
+           $str .= $class->usage_str($name, $prefix, $arg_param, $fixed_param, $format, $param_cb);
        }
        return $str;
     };
@@ -429,147 +451,6 @@ my $print_bash_completion = sub {
     &$print_result(@option_list);
 };
 
-sub data_to_text {
-    my ($data) = @_;
-
-    return undef if !defined($data);
-
-    if (my $class = ref($data)) {
-       return to_json($data, { utf8 => 1, canonical => 1 });
-    } else {
-       return "$data";
-    }
-}
-
-# prints a formatted table with a title row.
-# $formatopts is an array of hashes, with the following keys:
-# 'key' - key of $data element to print
-# 'title' - column title, defaults to 'key' - won't get cutoff
-# 'cutoff' - maximal (print) length of this column values, if set
-#            the last column will never be cutoff
-# 'default' - optional default value for the column
-# formatopts element order defines column order (left to right)
-# sorts the output according to the leftmost column not containing any undef
-sub print_text_table {
-    my ($formatopts, $data) = @_;
-
-    my ($formatstring, @keys, @titles, %cutoffs, %defaults, $sort_key);
-    my $last_col = $formatopts->[$#{$formatopts}];
-
-    foreach my $col ( @$formatopts ) {
-       my ($key, $title, $cutoff) = @$col{qw(key title cutoff)};
-       $title //= $key;
-
-       push @keys, $key;
-       push @titles, $title;
-       $defaults{$key} = $col->{default} // '';
-
-       # calculate maximal print width and cutoff
-       my $titlelen = length($title);
-
-       my $longest = $titlelen;
-       my $sortable = 1;
-       foreach my $entry (@$data) {
-           my $len = length(data_to_text($entry->{$key})) // 0;
-           $longest = $len if $len > $longest;
-           $sortable = 0 if !defined($entry->{$key});
-       }
-
-       $sort_key //= $key if $sortable;
-       $cutoff = (defined($cutoff) && $cutoff < $longest) ? $cutoff : $longest;
-       $cutoffs{$key} = $cutoff;
-
-       my $printalign = $cutoff > $titlelen ? '-' : '';
-       if ($col == $last_col) {
-           $formatstring .= "%${printalign}${titlelen}s\n";
-       } else {
-           $formatstring .= "%${printalign}${cutoff}s ";
-       }
-    }
-
-    printf $formatstring, @titles;
-
-    if (defined($sort_key)){
-       @$data = sort { $a->{$sort_key} cmp $b->{$sort_key} } @$data;
-    }
-    foreach my $entry (@$data) {
-        printf $formatstring, map { substr((data_to_text($entry->{$_}) // $defaults{$_}), 0 , $cutoffs{$_}) } @keys;
-    }
-}
-
-# prints the result of an API GET call returning an array as a table.
-# takes formatting information from the results property of the call
-# if $props_to_print is provided, prints only those columns. otherwise
-# takes all fields of the results property, with a fallback
-# to all fields occuring in items of $data.
-sub print_api_list {
-    my ($data, $result_schema, $props_to_print) = @_;
-
-    die "can only print object lists\n"
-       if !($result_schema->{type} eq 'array' && $result_schema->{items}->{type} eq 'object');
-
-    my $returnprops = $result_schema->{items}->{properties};
-
-    if (!defined($props_to_print)) {
-       $props_to_print = [ sort keys %$returnprops ];
-       if (!scalar(@$props_to_print)) {
-           my $all_props = {};
-           foreach my $obj (@{$data}) {
-               foreach my $key (keys %{$obj}) {
-                   $all_props->{ $key } = 1;
-               }
-           }
-           $props_to_print = [ sort keys %{$all_props} ];
-       }
-       die "unable to detect list properties\n" if !scalar(@$props_to_print);
-    }
-
-    my $formatopts = [];
-    foreach my $prop ( @$props_to_print ) {
-       my $propinfo = $returnprops->{$prop};
-       my $colopts = {
-           key => $prop,
-           title => $propinfo->{title},
-           default => $propinfo->{default},
-           cutoff => $propinfo->{print_width} // $propinfo->{maxLength},
-       };
-       push @$formatopts, $colopts;
-    }
-
-    print_text_table($formatopts, $data);
-}
-
-sub print_api_result {
-    my ($format, $data, $result_schema) = @_;
-
-    return if $result_schema->{type} eq 'null';
-
-    if ($format eq 'json') {
-       print to_json($data, {utf8 => 1, allow_nonref => 1, canonical => 1, pretty => 1 });
-    } elsif ($format eq 'text') {
-       my $type = $result_schema->{type};
-       if ($type eq 'object') {
-           foreach my $key (sort keys %$data) {
-               print $key . ": " .  data_to_text($data->{$key}) . "\n";
-           }
-       } elsif ($type eq 'array') {
-           return if !scalar(@$data);
-           my $item_type = $result_schema->{items}->{type};
-           if ($item_type eq 'object') {
-               print_api_list($data, $result_schema);
-           } else {
-               foreach my $entry (@$data) {
-                   print data_to_text($entry) . "\n";
-               }
-           }
-       } else {
-           print "$data\n";
-       }
-    } else {
-       die "internal error: unknown output format"; # should not happen
-    }
-}
-
 sub verify_api {
     my ($class) = @_;
 
@@ -639,7 +520,7 @@ sub setup_environment {
 }
 
 my $handle_cmd  = sub {
-    my ($args, $read_password_func, $preparefunc, $param_mapping_func) = @_;
+    my ($args, $preparefunc, $param_cb) = @_;
 
     $cmddef->{help} = [ __PACKAGE__, 'help', ['extra-args'] ];
 
@@ -669,7 +550,7 @@ my $handle_cmd  = sub {
     my ($class, $name, $arg_param, $uri_param, $outsub) = @{$def || []};
     $abort->("unknown command '$cmd_str'") if !$class;
 
-    my $res = $class->cli_handler($cmd_str, $name, $cmd_args, $arg_param, $uri_param, $read_password_func, $param_mapping_func);
+    my $res = $class->cli_handler($cmd_str, $name, $cmd_args, $arg_param, $uri_param, $param_cb);
 
     if (defined $outsub) {
        my $result_schema = $class->map_method_by_name($name)->{returns};
@@ -678,7 +559,7 @@ my $handle_cmd  = sub {
 };
 
 my $handle_simple_cmd = sub {
-    my ($args, $read_password_func, $preparefunc, $param_mapping_func) = @_;
+    my ($args, $preparefunc, $param_cb) = @_;
 
     my ($class, $name, $arg_param, $uri_param, $outsub) = @{$cmddef};
     die "no class specified" if !$class;
@@ -707,7 +588,7 @@ my $handle_simple_cmd = sub {
 
     &$preparefunc() if $preparefunc;
 
-    my $res = $class->cli_handler($name, $name, \@ARGV, $arg_param, $uri_param, $read_password_func, $param_mapping_func);
+    my $res = $class->cli_handler($name, $name, \@ARGV, $arg_param, $uri_param, $param_cb);
 
     if (defined $outsub) {
        my $result_schema = $class->map_method_by_name($name)->{returns};
@@ -731,9 +612,7 @@ sub run_cli_handler {
 
     my $preparefunc = $params{prepare};
 
-    my $read_password_func = $class->can('read_password');
-    my $param_mapping_func = $cli_handler_class->can('param_mapping') ||
-       $class->can('string_param_file_mapping');
+    my $param_cb = $gen_param_mapping_func->($cli_handler_class);
 
     $exename = &$get_exe_name($class);
 
@@ -743,9 +622,9 @@ sub run_cli_handler {
     $cmddef = ${"${class}::cmddef"};
 
     if (ref($cmddef) eq 'ARRAY') {
-       &$handle_simple_cmd(\@ARGV, $read_password_func, $preparefunc, $param_mapping_func);
+       $handle_simple_cmd->(\@ARGV, $preparefunc, $param_cb);
     } else {
-       &$handle_cmd(\@ARGV, $read_password_func, $preparefunc, $param_mapping_func);
+       $handle_cmd->(\@ARGV, $preparefunc, $param_cb);
     }
 
     exit 0;
index 377d6d5..dd0a2c6 100644 (file)
@@ -121,6 +121,22 @@ sub get_format {
     return $format_list->{$format};
 }
 
+my $renderer_hash = {};
+
+sub register_renderer {
+    my ($name, $code) = @_;
+
+    die "renderer '$name' already registered\n"
+       if $renderer_hash->{$name};
+
+    $renderer_hash->{$name} = $code;
+}
+
+sub get_renderer {
+    my ($name) = @_;
+    return $renderer_hash->{$name};
+}
+
 # register some common type for pve
 
 register_format('string', sub {}); # allow format => 'string-list'
@@ -1338,7 +1354,7 @@ sub method_get_child_link {
 # a way to parse command line parameters, using a 
 # schema to configure Getopt::Long
 sub get_options {
-    my ($schema, $args, $arg_param, $fixed_param, $pwcallback, $param_mapping_hash) = @_;
+    my ($schema, $args, $arg_param, $fixed_param, $param_mapping_hash) = @_;
 
     if (!$schema || !$schema->{properties}) {
        raise("too many arguments\n", code => HTTP_BAD_REQUEST)
@@ -1367,11 +1383,6 @@ sub get_options {
            # optional and call the mapping function afterwards.
            push @getopt, "$prop:s";
            push @interactive, [$prop, $mapping->{func}];
-       } elsif ($prop eq 'password' && $pwcallback) {
-           # we do not accept plain password on input line, instead
-           # we turn this into a boolean option and ask for password below
-           # using $pwcallback() (for security reasons).
-           push @getopt, "$prop";
        } elsif ($pd->{type} eq 'boolean') {
            push @getopt, "$prop:s";
        } else {
@@ -1423,14 +1434,6 @@ sub get_options {
        }
     }
 
-    if (my $pd = $schema->{properties}->{password}) {
-       if ($pd->{type} ne 'boolean' && $pwcallback) {
-           if ($opts->{password} || !$pd->{optional}) {
-               $opts->{password} = &$pwcallback(); 
-           }
-       }
-    }
-
     foreach my $entry (@interactive) {
        my ($opt, $func) = @$entry;
        my $pd = $schema->{properties}->{$opt};
index d3806c7..a93ba26 100644 (file)
@@ -432,7 +432,7 @@ sub handle {
 # $style: 'config', 'config-sub', 'arg' or 'fixed'
 # $mapdef: parameter mapping ({ desc => XXX, func => sub {...} })
 my $get_property_description = sub {
-    my ($name, $style, $phash, $format, $hidepw, $mapdef) = @_;
+    my ($name, $style, $phash, $format, $mapdef) = @_;
 
     my $res = '';
 
@@ -449,10 +449,6 @@ my $get_property_description = sub {
 
     my $type_text = PVE::APIClient::JSONSchema::schema_get_type_text($phash, $style);
 
-    if ($hidepw && $name eq 'password') {
-       $type_text = '';
-    }
-
     if ($mapdef && $phash->{type} eq 'string') {
        $type_text = $mapdef->{desc};
     }
@@ -580,10 +576,9 @@ my $compute_param_mapping_hash = sub {
 #   'short'    ... command line only (text, one line)
 #   'full'     ... text, include description
 #   'asciidoc' ... generate asciidoc for man pages (like 'full')
-# $hidepw      ... hide password option (use this if you provide a read passwork callback)
-# $param_mapping_func ... mapping for string parameters to file path parameters
+# $param_cb ... mapping for string parameters to file path parameters
 sub usage_str {
-    my ($self, $name, $prefix, $arg_param, $fixed_param, $format, $hidepw, $param_mapping_func) = @_;
+    my ($self, $name, $prefix, $arg_param, $fixed_param, $format, $param_cb) = @_;
 
     $format = 'long' if !$format;
 
@@ -616,7 +611,7 @@ sub usage_str {
     foreach my $k (@$arg_param) {
        next if defined($fixed_param->{$k}); # just to be sure
        next if !$prop->{$k}; # just to be sure
-       $argdescr .= &$get_property_description($k, 'fixed', $prop->{$k}, $format, 0);
+       $argdescr .= $get_property_description->($k, 'fixed', $prop->{$k}, $format);
     }
 
     my $idx_param = {}; # -vlan\d+ -scsi\d+
@@ -628,7 +623,13 @@ sub usage_str {
 
        my $type_text = $prop->{$k}->{type} || 'string';
 
-       next if $hidepw && ($k eq 'password') && !$prop->{$k}->{optional};
+       my $param_map = {};
+
+       if (defined($param_cb)) {
+           my $mapping = $param_cb->($name);
+           $param_map = $compute_param_mapping_hash->($mapping);
+           next if $k eq 'password' && $param_map->{$k} && !$prop->{$k}->{optional};
+       }
 
        my $base = $k;
        if ($k =~ m/^([a-z]+)(\d+)$/) {
@@ -640,11 +641,8 @@ sub usage_str {
            }
        }
 
-       my $param_mapping_hash = $compute_param_mapping_hash->(&$param_mapping_func($name))
-           if $param_mapping_func;
 
-       $opts .= &$get_property_description($base, 'arg', $prop->{$k}, $format,
-                                           $hidepw, $param_mapping_hash->{$k});
+       $opts .= $get_property_description->($base, 'arg', $prop->{$k}, $format, $param_map->{$k});
 
        if (!$prop->{$k}->{optional}) {
            $args .= " " if $args;
@@ -707,7 +705,7 @@ sub dump_properties {
            }
        }
 
-       $raw .= &$get_property_description($base, $style, $phash, $format, 0);
+       $raw .= $get_property_description->($base, $style, $phash, $format);
 
        next if $style ne 'config';
 
@@ -728,9 +726,9 @@ sub dump_properties {
 }
 
 my $replace_file_names_with_contents = sub {
-    my ($param, $param_mapping_hash) = @_;
+    my ($param, $param_map) = @_;
 
-    while (my ($k, $d) = each %$param_mapping_hash) {
+    while (my ($k, $d) = each %$param_map) {
        next if $d->{interactive}; # handled by the JSONSchema's get_options code
        $param->{$k} = $d->{func}->($param->{$k})
            if defined($param->{$k});
@@ -740,17 +738,18 @@ my $replace_file_names_with_contents = sub {
 };
 
 sub cli_handler {
-    my ($self, $prefix, $name, $args, $arg_param, $fixed_param, $read_password_func, $param_mapping_func) = @_;
+    my ($self, $prefix, $name, $args, $arg_param, $fixed_param, $param_cb) = @_;
 
     my $info = $self->map_method_by_name($name);
 
     my $res;
     eval {
-       my $param_mapping_hash = $compute_param_mapping_hash->($param_mapping_func->($name)) if $param_mapping_func;
-       my $param = PVE::APIClient::JSONSchema::get_options($info->{parameters}, $args, $arg_param, $fixed_param, $read_password_func, $param_mapping_hash);
+       my $param_map = {};
+       $param_map = $compute_param_mapping_hash->($param_cb->($name)) if $param_cb;
+       my $param = PVE::APIClient::JSONSchema::get_options($info->{parameters}, $args, $arg_param, $fixed_param, $param_map);
 
-       if (defined($param_mapping_hash)) {
-           &$replace_file_names_with_contents($param, $param_mapping_hash);
+       if (defined($param_map)) {
+           $replace_file_names_with_contents->($param, $param_map);
        }
 
        $res = $self->handle($info, $param);
@@ -760,7 +759,7 @@ sub cli_handler {
 
        die $err if !$ec || $ec ne "PVE::APIClient::Exception" || !$err->is_param_exc();
        
-       $err->{usage} = $self->usage_str($name, $prefix, $arg_param, $fixed_param, 'short', $read_password_func, $param_mapping_func);
+       $err->{usage} = $self->usage_str($name, $prefix, $arg_param, $fixed_param, 'short',  $param_cb);
 
        die $err;
     }