Getopt.pm revision 1.7
1# $OpenBSD: Getopt.pm,v 1.7 2012/07/09 17:53:15 espie Exp $
2
3# Copyright (c) 2012 Marc Espie <espie@openbsd.org>
4#
5# Permission to use, copy, modify, and distribute this software for any
6# purpose with or without fee is hereby granted, provided that the above
7# copyright notice and this permission notice appear in all copies.
8#
9# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
10# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
11# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
12# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
13# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
14# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
15# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
16#
17
18use strict;
19use warnings;
20
21package Option;
22sub factory
23{
24	my ($class, $_) = @_;
25	if (m/^(.)$/) {
26		return Option::Short->new($1);
27	} elsif (m/^(.)\:$/) {
28		return Option::ShortArg->new($1);
29	} elsif (m/^(\-?.*)\=$/) {
30		return Option::LongArg->new($1);
31	} elsif (m/^(\-?.*)$/) {
32		return Option::Long->new($1);
33	}
34}
35
36sub new
37{
38	my ($class, $v) = @_;
39	bless \$v, $class;
40}
41
42sub setup
43{
44	my ($self, $opts, $isarray) = @_;
45	$opts->add_option_accessor($$self, $isarray);
46	return $self;
47}
48
49package Option::Short;
50our @ISA = qw(Option);
51
52sub match
53{
54	my ($self, $_, $opts, $canonical, $code) = @_;
55	if (m/^\-\Q$$self\E$/) {
56		&$code($opts, $canonical, 1);
57		return 1;
58	}
59	if (m/^\-\Q$$self\E(.*)$/) {
60		unshift(@main::ARGV, "-$1");
61		&$code($opts, $canonical, 1);
62		return 1;
63	}
64	return 0;
65}
66
67package Option::ShortArg;
68our @ISA = qw(Option::Short);
69
70sub match
71{
72	my ($self, $_, $opts, $canonical, $code) = @_;
73	if (m/^\-\Q$$self\E$/) {
74		&$code($opts, $canonical, (shift @main::ARGV));
75		return 1;
76	}
77	if (m/^\-\Q$$self\E(.*)$/) {
78		&$code($opts, $canonical, $1);
79		return 1;
80	}
81	return 0;
82}
83
84package Option::Long;
85our @ISA = qw(Option);
86
87sub match
88{
89	my ($self, $_, $opts, $canonical, $code) = @_;
90	if (m/^\-\Q$$self\E$/) {
91		&$code($opts, $canonical, 1);
92		return 1;
93	}
94	return 0;
95}
96
97package Option::LongArg;
98our @ISA = qw(Option::Long);
99
100sub match
101{
102	my ($self, $_, $opts, $canonical, $code) = @_;
103	if (m/^\-\Q$$self\E$/) {
104		if (@main::ARGV > 0) {
105			&$code($opts, $canonical, (shift @main::ARGV));
106			return 1;
107		} else {
108			die "Missing argument  for option -$$self\n";
109		}
110	}
111	if (m/^-\Q$$self\E\=(.*)$/) {
112		&$code($opts, $canonical, $1);
113		return 1;
114	}
115	return 0;
116}
117
118package Options;
119
120sub new
121{
122	my ($class, $string, $code) = @_;
123
124	my @alternates = split(/\|/, $string);
125
126	bless {alt => [map { Option->factory($_); } @alternates], code => $code}, $class;
127}
128
129sub setup
130{
131	my ($self, $allopts, $isarray) = @_;
132	$self->{alt}[0]->setup($allopts, $isarray);
133	return $self;
134}
135
136sub match
137{
138	my ($self, $arg, $opts) = @_;
139
140	my $canonical = ${$self->{alt}[0]};
141	for my $s (@{$self->{alt}}) {
142		if ($s->match($arg, $opts, $canonical, $self->{code})) {
143			return 1;
144		}
145	}
146	return 0;
147}
148
149# seems I spend my life rewriting option handlers, not surprisingly...
150package LT::Getopt;
151use LT::Util;
152
153
154# parsing an option 'all-static' will automatically add an
155# accessor $self->all_static   that maps to the option.
156
157sub add_option_accessor
158{
159	my ($self, $option, $isarray) = @_;
160	my $access = $option;
161	$access =~ s/^\-//;
162	$access =~ s/-/_/g;
163	my $actual = $isarray ?
164		sub {
165		    my $self = shift;
166		    $self->{opt}{$option} //= [];
167		    if (wantarray) {
168			    return @{$self->{opt}{$option}};
169		    } else {
170			    return scalar @{$self->{opt}{$option}};
171		    }
172		} : sub {
173		    my $self = shift;
174		    return $self->{opt}{$option};
175		};
176	my $callpkg = ref($self);
177	unless ($self->can($access)) {
178		no strict 'refs';
179		*{$callpkg."::$access"} = $actual;
180	}
181}
182
183sub create_options
184{
185	my ($self, @l) = @_;
186	my @options = ();
187	# first pass creates accessors
188	while (my $opt = shift @l) {
189		my $isarray = ($opt =~ s/\@$//);
190		# default code or not
191		my $code;
192		if (@l > 0 && ref($l[0]) eq 'CODE') {
193			$code = shift @l;
194		} else {
195			if ($isarray) {
196				$code = sub {
197				    my ($object, $canonical, $value) = @_;
198				    push(@{$object->{opt}{$canonical}}, $value);
199				};
200			} else {
201				$code = sub {
202				    my ($object, $canonical, $value) = @_;
203				    $object->{opt}{$canonical} = $value;
204				};
205			}
206		}
207		push(@options, Options->new($opt, $code)->setup($self, $isarray));
208	}
209	return @options;
210}
211
212sub handle_options
213{
214	my ($self, @l) = @_;
215
216	my @options = $self->create_options(@l);
217
218MAINLOOP:
219	while (@main::ARGV > 0) {
220		my $_ = shift @main::ARGV;
221		if (m/^\-\-$/) {
222			last;
223		}
224		if (m/^\-/) {
225			for my $opt (@options) {
226				if ($opt->match($_, $self)) {
227					next MAINLOOP;
228				}
229			}
230			shortdie "Unknown option $_\n";
231		} else {
232			unshift(@main::ARGV, $_);
233			last;
234		}
235	}
236}
237
238sub handle_permuted_options
239{
240	my ($self, @l) = @_;
241
242	my @options = $self->create_options(@l);
243
244	my @kept = ();
245MAINLOOP2:
246	while (@main::ARGV > 0) {
247		my $_ = shift @main::ARGV;
248		if (m/^\-\-$/) {
249			next;   # XXX ?
250		}
251		if (m/^\-/) {
252			for my $opt (@options) {
253				if ($opt->match($_, $self)) {
254					next MAINLOOP2;
255				}
256			}
257		}
258		push(@kept, $_);
259	}
260	@main::ARGV = @kept;
261}
262
263sub new
264{
265	my $class = shift;
266	bless {}, $class;
267}
268
2691;
270