# $OpenBSD: Getopt.pm,v 1.14 2023/07/06 08:29:26 espie Exp $ # Copyright (c) 2012 Marc Espie # # Permission to use, copy, modify, and distribute this software for any # purpose with or without fee is hereby granted, provided that the above # copyright notice and this permission notice appear in all copies. # # THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES # WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF # MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR # ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES # WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN # ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF # OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. # use v5.36; package Option; sub factory($class, $o) { if ($o =~ m/^(.)$/) { return Option::Short->new($1); } elsif ($o =~ m/^(.)\:$/) { return Option::ShortArg->new($1); } elsif ($o =~ m/^(\-?.)(?:\:\!|\!\:)$/) { return Option::LongArg0->new($1); } elsif ($o =~ m/^(\-?.)\!$/) { return Option::Long->new($1); } elsif ($o =~ m/^(\-?.*)\=$/) { return Option::LongArg->new($1); } elsif ($o =~ m/^(\-?.*)\:$/) { return Option::LongArg0->new($1); } elsif ($o =~ m/^(\-?.*)$/) { return Option::Long->new($1); } } sub new($class, $v) { bless \$v, $class; } sub setup($self, $opts, $isarray) { $opts->add_option_accessor($$self, $isarray); return $self; } package Option::Short; our @ISA = qw(Option); sub match($self, $arg, $opts, $canonical, $code) { if ($arg =~ m/^\-\Q$$self\E$/) { &$code($opts, $canonical, 1, $arg); return 1; } if ($arg =~ m/^(\-\Q$$self\E)(.*)$/) { unshift(@main::ARGV, "-$2"); &$code($opts, $canonical, 1, $1); return 1; } return 0; } package Option::ShortArg; our @ISA = qw(Option::Short); sub match($self, $arg, $opts, $canonical, $code) { if ($arg =~ m/^\-\Q$$self\E$/) { &$code($opts, $canonical, (shift @main::ARGV), $arg); return 1; } if ($arg =~ m/^(\-\Q$$self\E)(.*)$/) { &$code($opts, $canonical, $2, $1); return 1; } return 0; } package Option::Long; our @ISA = qw(Option); sub match($self, $arg, $opts, $canonical, $code) { if ($arg =~ m/^\-\Q$$self\E$/) { &$code($opts, $canonical, 1, $arg); return 1; } return 0; } package Option::LongArg0; our @ISA = qw(Option::Long); sub match($self, $arg, $opts, $canonical, $code) { if ($arg =~ m/^\-\Q$$self\E$/) { if (@main::ARGV > 0) { &$code($opts, $canonical, (shift @main::ARGV), $arg); return 1; } else { die "Missing argument for option -$$self\n"; } } return 0; } package Option::LongArg; our @ISA = qw(Option::LongArg0); sub match($self, $arg, $opts, $canonical, $code) { if ($self->SUPER::match($arg, $opts, $canonical, $code)) { return 1; } if ($arg =~ m/^(-\Q$$self\E)\=(.*)$/) { &$code($opts, $canonical, $2, $1); return 1; } return 0; } package Option::Regexp; sub new($class, $re, $code) { bless {re => $re, code => $code}, $class; } sub setup($self, $, $) { return $self; } sub match($self, $arg, $opts) { if (my @l = ($arg =~ m/^$self->{re}$/)) { &{$self->{code}}(@l); return 1; } else { return 0; } } package Options; sub new($class, $string, $code) { if (ref($string) eq 'Regexp') { return Option::Regexp->new($string, $code); } my @alternates = split(/\|/, $string); bless { alt => [map { Option->factory($_); } @alternates], code => $code }, $class; } sub setup($self, $allopts, $isarray) { $self->{alt}[0]->setup($allopts, $isarray); return $self; } sub match($self, $arg, $opts) { my $canonical = ${$self->{alt}[0]}; for my $s (@{$self->{alt}}) { if ($s->match($arg, $opts, $canonical, $self->{code})) { return 1; } } return 0; } # seems I spend my life rewriting option handlers, not surprisingly... package LT::Getopt; use LT::Util; # parsing an option 'all-static' will automatically add an # accessor $self->all_static that maps to the option. sub add_option_accessor($self, $option, $isarray) { my $access = $option; $access =~ s/^\-//; $access =~ s/-/_/g; my $actual = $isarray ? sub($self) { $self->{opt}{$option} //= []; if (wantarray) { return @{$self->{opt}{$option}}; } else { return scalar @{$self->{opt}{$option}}; } } : sub($self) { return $self->{opt}{$option}; }; my $callpkg = ref($self); unless ($self->can($access)) { no strict 'refs'; *{$callpkg."::$access"} = $actual; } } sub create_options($self, @l) { my @options = (); # first pass creates accessors push(@l, '-tag=', sub { $self->add_tag($_[2]); }); while (my $opt = shift @l) { my $isarray = ($opt =~ s/\@$//); # default code or not my $code; if (@l > 0 && ref($l[0]) eq 'CODE') { $code = shift @l; } else { if ($isarray) { $code = sub { my ($object, $canonical, $value) = @_; push(@{$object->{opt}{$canonical}}, $value); }; } else { $code = sub { my ($object, $canonical, $value) = @_; $object->{opt}{$canonical} = $value; }; } } push(@options, Options->new($opt, $code)->setup($self, $isarray)); } return @options; } sub handle_options($self, @l) { my @options = $self->create_options(@l); MAINLOOP: while (@main::ARGV > 0) { my $arg = shift @main::ARGV; if ($arg =~ m/^\-\-$/) { last; } if ($arg =~ m/^\-/) { for my $opt (@options) { if ($opt->match($arg, $self)) { next MAINLOOP; } } shortdie "Unknown option $arg\n"; } else { unshift(@main::ARGV, $arg); last; } } } sub handle_permuted_options($self, @l) { my @options = $self->create_options(@l); $self->{kept} = []; MAINLOOP2: while (@main::ARGV > 0) { my $arg = shift @main::ARGV; if ($arg =~ m/^\-\-$/) { next; # XXX ? } if ($arg =~ m/^\-/) { for my $opt (@options) { if ($opt->match($arg, $self)) { next MAINLOOP2; } } } $self->keep_for_later($arg); } @main::ARGV = @{$self->{kept}}; } sub keep_for_later($self, @args) { push(@{$self->{kept}}, @args); } sub new($class) { bless {}, $class; } 1;