http://qs321.pair.com?node_id=11141013


in reply to Getopt - Validate arguments before processing

I do not subscribe to your logic. It's best to first parse all arguments and have a flag that all checks have passed, and then act on each argument. Just like haukex in Re: Getopt - Validate arguments before processing (updated) suggests. It's cumbersome but from what I understand you are asking for the impossible: parse the arguments as they are entered by the user, do some destructive actions (e.g. --erase-all-files) in your first() et al. and then realise that the last argument does not validate and you need to restore original state. How can you rollback that? Only if you create the actions and you only commit them when all validates, eventually.

Perhaps you can compromise with having Getopt::Long processing your options in a specific and strict order, independently on how the user types them on the command line. In this way, you can achieve having only one "destructive" option at the end. In my code, the getopt_in_order() will take an @ARGV, the Getopt spec and the order you want the options to be processed and return you back a new @ARGV which will be in that order. You then simply call Getopt::Long::GetOptionsFromArray(\@newARGV, %getopt_spec).

I am sure this will be of little help to your problem but hey, I always wanted such feature from Getopt::Long.

use Getopt::Long qw(GetOptionsFromArray); use Test::More; my %getopt_spec = ( 'first=s{2}' => 1, 'second=i{3}' => 2, 'third' => 3, ); my @testARGV = ( ['--second', '1', '2', '3', '--first', 'ahah', 'and spaces', '--th +ird'], ['--first', 'ahah', 'and spaces', '--second', '1', '2', '3'], ['--first', 'ahah', 'and spaces', '--third'], ['--third', '--first', 'ahah', 'and spaces', '--second', '1', '2', + '3'], ['--third'], ['--first', 'ahah', 'and spaces'], ['--second', '1', '2', '3'], [] ); my @expectedARGV = ( ['--first', 'ahah', 'and spaces', '--second', '1', '2', '3', '--th +ird'], ['--first', 'ahah', 'and spaces', '--second', '1', '2', '3'], ['--first', 'ahah', 'and spaces', '--third'], ['--first', 'ahah', 'and spaces', '--second', '1', '2', '3', '--th +ird'], ['--third'], ['--first', 'ahah', 'and spaces'], ['--second', '1', '2', '3'], [] ); for my $i (0..@testARGV-1){ my $testar = $testARGV[$i]; my $expear = $expectedARGV[$i]; # make a copy of it for printing diags because getopt_in_order() w +ill destroy it my $copy_testar = [ @$testar ]; my $newargv = getopt_in_order(\%getopt_spec, $testar); ok(defined $newargv, "getopt_in_order() called for '@$copy_testar' +."); is_deeply($newargv, $expear, "got (@$newargv) and expected (@$expe +ar) for '@$copy_testar'"); } done_testing; # returns the ordered @$an_argv as per the @$options_order # or undef on failure # WARNING: $an_argv will be destroyed on return sub getopt_in_order { # by bliako for https://perlmonks.org/?node_id=11140961 # 01/Feb/2022 my ( # a hashref keyed on getopt specs $getopt_spec, # a hash of option names with the index you want them processe +d, e.g. 'first' => 1 #$options_order, # arrayref to @ARGV or its copy, this will be destroyed by Get +opt $an_argv ) = @_; my %options_order = map { (split('=', $_))[0] => $getopt_spec->{$_ +}-1 } keys %$getopt_spec; my %getopt_spec; my @tmpARGV; for my $aspec (keys %$getopt_spec){ $getopt_spec{$aspec} = sub { my $k = shift; my $expects_args = @_ && ($aspec =~ /^.+?=.+?$/); my $idx = $options_order{$k}; if( exists($tmpARGV[$idx]) && defined($tmpARGV[$idx]) ){ push @{$tmpARGV[$idx]}, @_ if $expects_args } else { if( $expects_args ){ $tmpARGV[$idx] = [ '--'.$k, @_ ] +} else { $tmpARGV[$idx] = [ '--'.$k ] } } } } if( ! GetOptionsFromArray($an_argv, %getopt_spec) ){ print STDERR "getopt_in_order() : error parsing command line a +rguments.\n"; return undef } # remove undef entries e.g. because -second was not present # see https://stackoverflow.com/a/11123138 @tmpARGV = grep defined, @tmpARGV; my @newARGV; foreach my $opt (@tmpARGV){ # in correct order now and no holes push @newARGV, @$opt } return \@newARGV; }

bw, bliako