Getopt.pm revision 1.5
1# $OpenBSD: Getopt.pm,v 1.5 2012/07/08 18:28:34 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
42package Option::Short;
43our @ISA = qw(Option);
44sub setup
45{
46	# short options don't make accessors
47}
48
49sub match
50{
51	my ($self, $_, $opts, $canonical, $code) = @_;
52	if (m/^\-\Q$$self\E$/) {
53		&$code($opts, $canonical, 1);
54		return 1;
55	}
56	if (m/^\-\Q$$self\E(.*)$/) {
57		unshift(@main::ARGV, "-$1");
58		&$code($opts, $canonical, 1);
59		return 1;
60	}
61	return 0;
62}
63
64package Option::ShortArg;
65our @ISA = qw(Option::Short);
66
67sub match
68{
69	my ($self, $_, $opts, $canonical, $code) = @_;
70	if (m/^\-\Q$$self\E$/) {
71		&$code($opts, $canonical, (shift @main::ARGV));
72		return 1;
73	}
74	if (m/^\-\Q$$self\E(.*)$/) {
75		&$code($opts, $canonical, $1);
76		return 1;
77	}
78	return 0;
79}
80
81package Option::Long;
82our @ISA = qw(Option);
83
84sub setup
85{
86	my ($self, $opts, $isarray) = @_;
87	$opts->add_option_accessor($$self, $isarray);
88	return $self;
89}
90
91sub match
92{
93	my ($self, $_, $opts, $canonical, $code) = @_;
94	if (m/^\-\Q$$self\E$/) {
95		&$code($opts, $canonical, 1);
96		return 1;
97	}
98	return 0;
99}
100
101package Option::LongArg;
102our @ISA = qw(Option::Long);
103
104sub match
105{
106	my ($self, $_, $opts, $canonical, $code) = @_;
107	if (m/^\-\Q$$self\E$/) {
108		if (@main::ARGV > 0) {
109			&$code($opts, $canonical, (shift @main::ARGV));
110			return 1;
111		} else {
112			die "Missing argument  for option -$$self\n";
113		}
114	}
115	if (m/^-\Q$$self\E\=(.*)$/) {
116		&$code($opts, $canonical, $1);
117		return 1;
118	}
119	return 0;
120}
121
122package Options;
123
124sub new
125{
126	my ($class, $string, $code) = @_;
127
128	my @alternates = split(/\|/, $string);
129
130	bless {alt => [map { Option->factory($_); } @alternates], code => $code}, $class;
131}
132
133sub setup
134{
135	my ($self, $allopts) = @_;
136	$self->{alt}[0]->setup($allopts);
137	return $self;
138}
139
140sub match
141{
142	my ($self, $arg, $opts) = @_;
143
144	my $canonical = ${$self->{alt}[0]};
145	for my $s (@{$self->{alt}}) {
146		if ($s->match($arg, $opts, $canonical, $self->{code})) {
147			return 1;
148		}
149	}
150	return 0;
151}
152
153# seems I spend my life rewriting option handlers, not surprisingly...
154package LT::Getopt;
155use LT::Util;
156
157
158# parsing an option 'all-static' will automatically add an
159# accessor $self->all_static   that maps to the option.
160
161sub add_option_accessor
162{
163	my ($self, $option, $isarray) = @_;
164	my $access = $option;
165	$access =~ s/^\-//;
166	$access =~ s/-/_/g;
167	my $actual = $isarray ?
168		sub {
169		    my $self = shift;
170		    return @{$self->{opt}{$option}};
171		} : sub {
172		    my $self = shift;
173		    return $self->{opt}{$option};
174		};
175	my $callpkg = ref($self);
176	unless ($self->can($access)) {
177		no strict 'refs';
178		*{$callpkg."::$access"} = $actual;
179	}
180}
181
182sub create_options
183{
184	my ($self, @l) = @_;
185	my @options = ();
186	# first pass creates accessors
187	while (my $opt = shift @l) {
188		my $isarray = ($opt =~ s/\@$//);
189		# default code or not
190		my $code;
191		if (@l > 0 && ref($l[0]) eq 'CODE') {
192			$code = shift @l;
193		} else {
194			if ($isarray) {
195				$code = sub {
196				    my ($object, $canonical, $value) = @_;
197				    push(@{$object->{opt}{$canonical}}, $value);
198				};
199			} else {
200				$code = sub {
201				    my ($object, $canonical, $value) = @_;
202				    $object->{opt}{$canonical} = $value;
203				};
204			}
205		}
206		push(@options, Options->new($opt, $code)->setup($self, $isarray));
207	}
208	return @options;
209}
210
211sub handle_options
212{
213	my ($self, @l) = @_;
214
215	my @options = $self->create_options(@l);
216
217MAINLOOP:
218	while (@main::ARGV > 0) {
219		my $_ = shift @main::ARGV;
220		if (m/^\-\-$/) {
221			last;
222		}
223		if (m/^\-/) {
224			for my $opt (@options) {
225				if ($opt->match($_, $self)) {
226					next MAINLOOP;
227				}
228			}
229			shortdie "Unknown option $_\n";
230		} else {
231			unshift(@main::ARGV, $_);
232			last;
233		}
234	}
235}
236
237sub handle_permuted_options
238{
239	my ($self, @l) = @_;
240
241	my @options = $self->create_options(@l);
242
243	my @kept = ();
244MAINLOOP2:
245	while (@main::ARGV > 0) {
246		my $_ = shift @main::ARGV;
247		if (m/^\-\-$/) {
248			next;   # XXX ?
249		}
250		if (m/^\-/) {
251			for my $opt (@options) {
252				if ($opt->match($_, $self)) {
253					next MAINLOOP2;
254				}
255			}
256		}
257		push(@kept, $_);
258	}
259	@main::ARGV = @kept;
260}
261
262sub new
263{
264	my $class = shift;
265	bless {}, $class;
266}
267
2681;
269