[pve-devel] r5050 - pve-common/trunk

svn-commits at proxmox.com svn-commits at proxmox.com
Wed Aug 25 13:57:23 CEST 2010


Author: dietmar
Date: 2010-08-25 11:57:23 +0000 (Wed, 25 Aug 2010)
New Revision: 5050

Added:
   pve-common/trunk/CLIHandler.pm
Modified:
   pve-common/trunk/ChangeLog
   pve-common/trunk/Exception.pm
   pve-common/trunk/JSONSchema.pm
   pve-common/trunk/Makefile
   pve-common/trunk/RESTHandler.pm
Log:
	* Exception.pm (raise_param_exc): allow to specify usage information.

	* RESTHandler.pm (usage_str): first try to autogenerate usage information.
	(cli_handler2): experimental code used by new CLIHandler.pm

	* CLIHandler.pm: new class for command line tools like 'pvesm' -
	automatically create 'help' and usage information.



Added: pve-common/trunk/CLIHandler.pm
===================================================================
--- pve-common/trunk/CLIHandler.pm	                        (rev 0)
+++ pve-common/trunk/CLIHandler.pm	2010-08-25 11:57:23 UTC (rev 5050)
@@ -0,0 +1,84 @@
+package PVE::CLIHandler;
+
+use strict;
+use warnings;
+
+use PVE::Exception qw(raise raise_param_exc);
+use PVE::RESTHandler;
+
+use base qw(PVE::RESTHandler);
+
+my $cmddef;
+
+__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',
+	    },
+	},
+    },
+    returns => { type => 'null' },
+    
+    code => sub {
+	my ($param) = @_;
+
+	my $cmd = $param->{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, "pvesm $cmd", $arg_param, $uri_param, 'full');
+	print $str;
+
+	return undef;
+
+    }});
+
+sub print_usage_short {
+    my ($exename, $msg) = @_;
+
+    print STDERR "ERROR: $msg\n" if $msg;
+    print STDERR "USAGE: pvesm <COMMAND> [ARGS] [OPTIONS]\n";
+
+    foreach my $cmd (sort keys %$cmddef) {
+	my ($class, $name, $arg_param, $uri_param) = @{$cmddef->{$cmd}};
+	my $str = $class->usage_str($name, "pvesm $cmd", $arg_param, $uri_param, 'short');
+	print STDERR "       $str";
+    }
+}
+
+sub handle_cmd {
+    my ($def, $exename, $cmd, $args) = @_;
+
+    $cmddef = $def;
+
+    $cmddef->{help} = [ __PACKAGE__, 'help', ['cmd'] ];
+
+    if (!$cmd) { 
+	print_usage_short ($exename, "no command specified");
+	exit (-1);
+    }
+
+    my ($class, $name, $arg_param, $uri_param, $outsub) = @{$cmddef->{$cmd}} if $cmddef->{$cmd};
+
+    if (!$class) {
+	print_usage_short ($exename, "unknown command '$cmd'");
+	exit (-1);
+    }
+
+    my $prefix = "$exename $cmd";
+    my $res = $class->cli_handler2($prefix, $name, \@ARGV, $arg_param, $uri_param);
+    if ($outsub) {
+	&$outsub($res);
+    }
+}
+
+1;

Modified: pve-common/trunk/ChangeLog
===================================================================
--- pve-common/trunk/ChangeLog	2010-08-24 11:47:34 UTC (rev 5049)
+++ pve-common/trunk/ChangeLog	2010-08-25 11:57:23 UTC (rev 5050)
@@ -1,3 +1,13 @@
+2010-08-25  Proxmox Support Team  <support at proxmox.com>
+
+	* Exception.pm (raise_param_exc): allow to specify usage information.
+
+	* RESTHandler.pm (usage_str): first try to autogenerate usage information.
+	(cli_handler2): experimental code used by new CLIHandler.pm
+
+	* CLIHandler.pm: new class for command line tools like 'pvesm' -
+	automatically create 'help' and usage information.
+
 2010-08-24  Proxmox Support Team  <support at proxmox.com>
 
 	* RESTHandler.pm (handle): remove $conn parameter. We use new

Modified: pve-common/trunk/Exception.pm
===================================================================
--- pve-common/trunk/Exception.pm	2010-08-24 11:47:34 UTC (rev 5049)
+++ pve-common/trunk/Exception.pm	2010-08-25 11:57:23 UTC (rev 5050)
@@ -48,12 +48,23 @@
     die $exc;
 }
 
+sub is_param_exc {
+    my ($self) = @_;
+
+    return $self->{code} && $self->{code} eq HTTP_BAD_REQUEST;
+}
+
 sub raise_param_exc {
-    my ($errors) = @_;
+    my ($errors, $usage) = @_;
 
-    my $exc = PVE::Exception->new("Parameter verification failed.\n",
-				  code => HTTP_BAD_REQUEST,
-				  errors => $errors);
+    my $param = {
+	 code => HTTP_BAD_REQUEST,
+	 errors => $errors,
+    };
+
+    $param->{usage} = $usage if $usage;
+
+    my $exc = PVE::Exception->new("Parameter verification failed.\n", %$param);
     
     my ($pkg, $filename, $line) = caller;
 
@@ -89,6 +100,11 @@
 	}
     }
 
+    if ($self->{usage}) {
+	$msg .= $self->{usage};
+	$msg .= "\n" if $msg !~ m/\n$/;
+    }
+
     return $msg;
 }
 

Modified: pve-common/trunk/JSONSchema.pm
===================================================================
--- pve-common/trunk/JSONSchema.pm	2010-08-24 11:47:34 UTC (rev 5049)
+++ pve-common/trunk/JSONSchema.pm	2010-08-25 11:57:23 UTC (rev 5050)
@@ -650,7 +650,7 @@
     my ($schema, $args, $uri_param, $pwcallback) = @_;
 
     if (!$schema || !$schema->{properties}) {
-	die "too many arguments\n"
+	raise("too many arguments\n", code => HTTP_BAD_REQUEST)
 	    if scalar(@$args) != 0;
 	return {};
     }
@@ -671,10 +671,10 @@
     }
 
     my $opts = {};
-    die "unable to parse option\n"
+    raise("unable to parse option\n", code => HTTP_BAD_REQUEST)
 	if !Getopt::Long::GetOptionsFromArray($args, $opts, @getopt);
     
-    die "too many arguments\n"
+    raise("too many arguments\n", code => HTTP_BAD_REQUEST)
 	if scalar(@$args) != 0;
 
     if (my $pd = $schema->{properties}->{password}) {

Modified: pve-common/trunk/Makefile
===================================================================
--- pve-common/trunk/Makefile	2010-08-24 11:47:34 UTC (rev 5049)
+++ pve-common/trunk/Makefile	2010-08-25 11:57:23 UTC (rev 5050)
@@ -19,6 +19,7 @@
 
 LIB_SOURCES=			\
 	RPCEnvironment.pm	\
+	CLIHandler.pm		\
 	RESTHandler.pm		\
 	JSONSchema.pm		\
 	SafeSyslog.pm		\

Modified: pve-common/trunk/RESTHandler.pm
===================================================================
--- pve-common/trunk/RESTHandler.pm	2010-08-24 11:47:34 UTC (rev 5049)
+++ pve-common/trunk/RESTHandler.pm	2010-08-25 11:57:23 UTC (rev 5050)
@@ -3,7 +3,7 @@
 use strict;
 use warnings;
 use PVE::SafeSyslog;
-use PVE::Exception;
+use PVE::Exception qw(raise raise_param_exc);
 use PVE::JSONSchema;
 use HTTP::Status qw(:constants :is status_message);
 
@@ -194,6 +194,148 @@
     return $result;
 }
 
+sub usage_str {
+    my ($self, $name, $prefix, $arg_param, $fixed_param, $format) = @_;
+
+    # format: 'long', 'short', 'full'
+    #' long'  ... default
+    # 'short' ... command line only (one line)
+    # 'full'  ... include description
+
+    $format = 'long' if !$format;
+
+    my $info = $self->map_method_by_name($name);
+
+    my $out = '';
+
+    my $arg_hash = {};
+
+    my $args = '';
+    foreach my $p (@$arg_param) {
+	$arg_hash->{$p} = 1;
+	$args .= " " if $args;
+	$args .= "<$p>";
+    }
+
+    my $get_type_str = sub {
+	my ($phash) = @_;
+
+	my $type =  $phash->{format} || $phash->{type} || 'string';
+
+	return $type;
+    };
+
+    my $get_prop_descr = sub {
+	my ($phash, $display_name) = @_;
+ 
+	my $res = '';
+
+	my $descr = $phash->{description} || "no description available";
+	my $type =  &$get_type_str($phash);
+
+	$res .= sprintf "  %-10s %-10s   $descr\n", $display_name, "$type";
+	my $indend = "                          ";
+	if ($phash->{enum}) {
+	    $res .= sprintf "${indend}possible values: %s\n",  join ('|', @{$phash->{enum}});
+	}	
+	if ($phash->{minimum}) {
+	    $res .= "${indend}minimum: $phash->{minimum}\n";
+	}
+	if ($phash->{maximum}) {
+	    $res .= "${indend}maximum: $phash->{maximum}\n";
+	}
+
+	return $res;
+    };
+
+    my $schema = $info->{parameters};
+    my $prop = $schema->{properties};
+
+    my $argdescr = '';
+    foreach my $k (@$arg_param) {
+	next if defined($fixed_param->{$k}); # just to be sure
+
+	$argdescr .= &$get_prop_descr($prop->{$k}, "<$k>");
+    }
+
+    my $opts = '';
+    foreach my $k (keys %$prop) {
+	next if $arg_hash->{$k};
+	next if defined($fixed_param->{$k});
+
+	my $type =  &$get_type_str($prop->{$k});
+
+	$opts .= &$get_prop_descr($prop->{$k}, "-$k");
+
+	if (!$prop->{$k}->{optional}) {
+	    $args .= " " if $args;
+	    $args .= "-$k $type"
+	}
+    } 
+
+    $out .= "usage: " if $format ne 'short';
+
+    $out .= "$prefix $args";
+
+    $out .= $opts ? " [OPTIONS]\n" : "\n";
+
+    return $out if $format eq 'short';
+
+    $out .= $argdescr if $argdescr;
+
+    $out .= $opts if $opts;
+
+    if ($info->{description} && $format eq 'full') {
+	$out .= "\nDescription: $info->{description}\n";
+    }
+    return $out;
+}
+
+sub cli_handler2 {
+    my ($self, $prefix, $name, $args, $arg_param, $fixed_param, $pwcallback) = @_;
+
+    my $info = $self->map_method_by_name($name);
+
+    my $param;
+    foreach my $p (keys %$fixed_param)  {
+	$param->{$p} = $fixed_param->{$p};
+    }
+
+    foreach my $p (@$arg_param) {
+	my $v = shift @$args;
+
+	if (!defined($v)) {
+	    my $usage = $self->usage_str($name, $prefix, $arg_param, $fixed_param);
+	    raise_param_exc({ $p => "got undefined value for required argument"}, $usage); 
+	}
+
+	if ($v =~ m/^-/) {
+	    my $usage = $self->usage_str($name, $prefix, $arg_param, $fixed_param);
+	    raise_param_exc({ $p => "required argument may not start with hyphen"}, $usage); 
+	}
+
+	$param->{$p} = $v;
+    }
+
+    my $res;
+    eval {
+	my $param = PVE::JSONSchema::get_options($info->{parameters}, $args, $param, $pwcallback);
+
+	$res = $self->handle($info, $param);
+    };
+    if (my $err = $@) {
+	my $ec = ref($err);
+
+	die $err if !$ec || $ec ne "PVE::Exception" || !$err->is_param_exc();
+	
+	$err->{usage} = $self->usage_str($name, $prefix, $arg_param, $fixed_param);
+
+	die $err;
+    }
+
+    return $res;
+}
+
 sub cli_handler {
     my ($self, $name, $args, $uri_param, $pwcallback) = @_;
 



More information about the pve-devel mailing list