diff options
author | Sergey Poznyakoff <gray@gnu.org> | 2020-04-04 11:30:58 +0300 |
---|---|---|
committer | Sergey Poznyakoff <gray@gnu.org> | 2020-04-04 11:30:58 +0300 |
commit | c5d52ce8690a445fff4619d610c647ca0e3c25e5 (patch) | |
tree | d02c9dc4880dd1ee322de9b62e5a48fcbecc121a | |
parent | 7c010899445784b494b4b839c61867f9fe320294 (diff) | |
download | makeredirect-c5d52ce8690a445fff4619d610c647ca0e3c25e5.tar.gz makeredirect-c5d52ce8690a445fff4619d610c647ca0e3c25e5.tar.bz2 |
Add documentation.
* Makefile.PL: Get abstract from the makeredirect script.
* lib/MakeRedirect.pm (getopt): New method.
* lib/MakeRedirect/Output.pm (getopt)
(pod_usage_msg): New method.
* lib/MakeRedirect/Output/help.pm: New module.
* lib/MakeRedirect/Output/list.pm: Imlement getopt.
Change output format to match the expected input of vmod_dict.
Add POD.
* lib/MakeRedirect/Output/rewrite.pm: Add POD.
Implement getopt.
* lib/MakeRedirect/URI.pm (canonical): Fix return in case if
scheme is undefined.
* makeredirect: Call getopt after creating a MakeRedirect object.
Add documentation.
* t/03_list.t: Fix expected output.
* t/04_rewrite.t: Rename the 'mono' option to 'monolithic'.
-rw-r--r-- | Makefile.PL | 3 | ||||
-rw-r--r-- | lib/MakeRedirect.pm | 5 | ||||
-rw-r--r-- | lib/MakeRedirect/Output.pm | 70 | ||||
-rw-r--r-- | lib/MakeRedirect/Output/help.pm | 125 | ||||
-rw-r--r-- | lib/MakeRedirect/Output/list.pm | 75 | ||||
-rw-r--r-- | lib/MakeRedirect/Output/rewrite.pm | 109 | ||||
-rw-r--r-- | lib/MakeRedirect/URI.pm | 7 | ||||
-rwxr-xr-x[-rw-r--r--] | makeredirect | 56 | ||||
-rw-r--r-- | t/03_list.t | 9 | ||||
-rw-r--r-- | t/04_rewrite.t | 2 |
10 files changed, 433 insertions, 28 deletions
diff --git a/Makefile.PL b/Makefile.PL index 4c5d55b..47b5509 100644 --- a/Makefile.PL +++ b/Makefile.PL @@ -7,8 +7,7 @@ use File::Basename; WriteMakefile( NAME => 'makeredirect', - ABSTRACT => 'FIXME', -# ABSTRACT_FROM => 'lib/MakeRedirect.pm', + ABSTRACT_FROM => 'makeredirect', VERSION_FROM => 'lib/MakeRedirect.pm', AUTHOR => 'Sergey Poznyakoff <gray@gnu.org>', LICENSE => 'gpl_3', diff --git a/lib/MakeRedirect.pm b/lib/MakeRedirect.pm index cfda7e9..fefbd71 100644 --- a/lib/MakeRedirect.pm +++ b/lib/MakeRedirect.pm @@ -35,6 +35,11 @@ sub scheme { $self->{scheme}; } +sub getopt { + my $self = shift; + $self->{output}->getopt; +} + sub output { my $self = shift; $self->{output}->open; diff --git a/lib/MakeRedirect/Output.pm b/lib/MakeRedirect/Output.pm index 81cf8ec..f7941c3 100644 --- a/lib/MakeRedirect/Output.pm +++ b/lib/MakeRedirect/Output.pm @@ -10,6 +10,10 @@ use strict; use warnings; use Carp; use File::Spec; +use Getopt::Long qw(:config gnu_getopt no_ignore_case require_order); +use Pod::Man; +use Pod::Usage; +use Pod::Find qw(pod_where); sub new { my $class = shift; @@ -67,5 +71,71 @@ sub printf { my $fh = $self->fh; CORE::printf $fh @_; } + +sub getopt { + my $self = shift; + my %opts = @_; + + $opts{'shorthelp|?'} = sub { + pod2usage(-message => $self->pod_usage_msg, + -input => pod_where({-inc => 1}, ref($self)), + -exitstatus => 0) + }; + $opts{help} = sub { + pod2usage(-exitstatus => 0, + -verbose => 2, + -input => pod_where({-inc => 1}, ref($self))) + }; + $opts{usage} = sub { + pod2usage(-exitstatus => 0, + -verbose => 0, + -input => pod_where({-inc => 1}, ref($self))) + }; + + my %optdef; + foreach my $k (keys %opts) { + if (ref($opts{$k}) eq 'CODE') { + $optdef{$k} = sub { &{$opts{$k}}($self, @_ ) } + } elsif (ref($opts{$k})) { + $optdef{$k} = $opts{$k}; + } else { + $optdef{$k} = \$self->{$opts{$k}}; + } + } + GetOptions(%optdef) or die "command line parsing error"; +} + +sub pod_usage_msg { + my ($self, $input) = @_; + my %args; + + my $msg = ""; + + CORE::open my $fd, '>', \$msg; + + if (defined($input)) { + $args{-input} = $input; + } else { + if (my $r = ref($self)) { + $self = $r; + } + $args{-input} = pod_where({-inc => 1}, $self); + } + + pod2usage(-verbose => 99, + -sections => 'NAME', + -output => $fd, + -exitval => 'NOEXIT', + %args); + + my @a = split /\n/, $msg; + if ($#a < 1) { + croak "missing or malformed NAME section in " . $args{-input} // $0; + } + $msg = $a[1]; + $msg =~ s/^\s+//; + $msg =~ s/ - /: /; + return $msg; +} 1; diff --git a/lib/MakeRedirect/Output/help.pm b/lib/MakeRedirect/Output/help.pm new file mode 100644 index 0000000..0606bb9 --- /dev/null +++ b/lib/MakeRedirect/Output/help.pm @@ -0,0 +1,125 @@ +package MakeRedirect::Output::help; + +=head1 NAME + +help - prints available modules with short descriptions + +=head1 SYNOPSIS + +B<makeredirect help> [I<NAME>] + +=head1 DESCRIPTION + +Without arguments, prints a list of available B<makeredirect> modules +with short descriptions. + +Given a single argument I<NAME>, looks up a module of that name and, +if found, displays its manual page. + +=head1 OPTIONS + +=over 4 + +=item B<-?> + +Produce a short help text and exit. + +=item B<--help> + +Display the manual page and exit. + +=item B<--usage> + +Display a short command line usage summary and exit. + +=back + +=head1 SEE ALSO + +B<makeredirect>(1), +L<MakeRedirect>. + +=cut + +use strict; +use warnings; +use Carp; +use File::Basename; +use File::Spec; +use Pod::Usage; +use Pod::Find qw(pod_where); +use parent 'MakeRedirect::Output'; + +sub new { + my $class = shift; + my @classpath = split(/::/, $class); + pop @classpath; + bless { classpath => \@classpath }, $class +} + +sub classpath { @{shift->{classpath}} } + +sub module_list { + my $self = shift; + @{$self->{module_list} //= + [sort { $a->[0] cmp $b->[0] } + map { + my $name = basename($_); + my $filename = File::Spec->catfile($self->classpath, $name); + if (exists($INC{$filename})) { + () + } else { + eval { + require $filename; + }; + $name =~ s/\.pm$//; + $@ ? () : [$name, $_]; + } + } + map { glob File::Spec->catfile($_, $self->classpath, '*.pm') } @INC]} +} + +sub getopt { + my $self = shift; + + $self->SUPER::getopt(@_); + if (@ARGV) { + my $name = shift @ARGV; + if (@ARGV) { + pod2usage(-exitstatus => 1, + -verbose => 0, + -output => \*STDERR); + } + my ($module) = grep { $_->[0] eq $name } $self->module_list; + if ($module) { + pod2usage(-verbose => 2, + -input => $module->[1], + -exitstatus => 0) + } else { + print "Unknown module $name\n"; + } + } + + print "Available modules are:\n\n"; + + foreach my $mod ($self->module_list) { + my $s; + CORE::open(my $fh, '>', \$s); + pod2usage(-input => $mod->[1], + -output => $fh, + -verbose => 99, + -sections => ['NAME'], + -exitstatus => 'NOEXIT'); + CORE::close $fh; + my (undef,$descr) = split("\n", $s||''); + unless ($descr) { + $descr = ' ' . $mod->[0] + } + print "$descr\n"; + } + print "\nRun \"makeredirect help NAME\" to obtain help for a particular module.\n"; + exit(0) +} + +1; + diff --git a/lib/MakeRedirect/Output/list.pm b/lib/MakeRedirect/Output/list.pm index 0af2d33..792c4b0 100644 --- a/lib/MakeRedirect/Output/list.pm +++ b/lib/MakeRedirect/Output/list.pm @@ -3,6 +3,7 @@ use parent 'MakeRedirect::Output'; use strict; use warnings; use Carp; +use String::Escape qw( unprintable ); sub new { my $class = shift; @@ -15,15 +16,85 @@ sub new { $self; } +sub getopt { + my $self = shift; + $self->SUPER::getopt( + 'd1=s' => sub { + my ($self,undef,$val) = @_; + $self->{d1} = unprintable($val); + }, + 'd2=s' => sub { + my ($self,undef,$val) = @_; + $self->{d2} = unprintable($val); + } + ); +} + sub ruleset { my ($self, $host, $rules) = @_; - $self->print("host=$host", $self->{d1}); foreach my $key (sort { $b cmp $a } keys %{$rules}) { my $r = $rules->{$key}; - $self->print($r->src, $self->{d2}, $r->dst, $self->{d1}); + my $src = $r->src->clone; + $src->unset_scheme; + $self->print($src, $self->{d2}, $r->dst, $self->{d1}); } } 1; +=head1 NAME + +list - create a list of redirects in simple dictionary format + +=head1 SYNOPSIS + +B<makeredirect list> +[B<--d1> I<DELIM1>] +[B<--d2> I<DELIM2>] +I<FILE> ... + +=head1 DESCRIPTION + +Converts a CSV file with source-destination URL pairs to a set of equivalent +I<dictionary> pairs. Each pair consists of sourse and destination URLs with +the scheme stripped, separated by the I<DELIM1> string (a single space by +default) and terminated by the I<DELIM2> (a I<newline> character by default). + +The resulting output can be used as input for B<vmod_dict>(3) module for +B<Varnish Cache>. + +=head2 OPTIONS + +=over 4 + +=item B<--d1> I<DELIM1> + +Sets the value of the field delimiter. + +=item B<--d2> I<DELIM2> + +Sets the value of the record terminating delimiter. + +=item B<-?> + +Produce a short help text and exit. + +=item B<--help> + +Display the manual page and exit. + +=item B<--usage> + +Display a short command line usage summary and exit. + +=back + +=head1 SEE ALSO + +B<makeredirect>(1), +L<MakeRedirect>, +B<vmod_dict>(3), +L<https://www.gnu.org.ua/software/vmod-dict>. + +=cut diff --git a/lib/MakeRedirect/Output/rewrite.pm b/lib/MakeRedirect/Output/rewrite.pm index 784c438..aa5aaa3 100644 --- a/lib/MakeRedirect/Output/rewrite.pm +++ b/lib/MakeRedirect/Output/rewrite.pm @@ -9,15 +9,26 @@ use MakeRedirect::URI::SRC; sub new { my $class = shift; local %_ = @_; - my $mono = delete $_{mono}; + my $monolithic = delete $_{monolithic}; my $self = $class->SUPER::new(%_); - $self->{mono} = $mono || (!$self->{fh} && !$self->{file}); + $self->{monolithic} = $monolithic || (!$self->{fh} && !$self->{file}); $self } +sub getopt { + my $self = shift; + $self->SUPER::getopt( + 'm|monolithic' => 'monolithic', + 'o|output' => 'file' + ); + if (!$self->{fh} && !$self->{file}) { + $self->{monolithic} = 1; + } +} + sub open { my $self = shift; - if ($self->{mono}) { + if ($self->{monolithic}) { return $self->SUPER::open(@_); } } @@ -34,7 +45,7 @@ sub ruleset { $self->need_close($fh); } - if (!$self->{mono} && $host ne '') { + if (!$self->{monolithic} && $host ne '') { $self->print("# Host $host\n"); } foreach my $key (sort { $b cmp $a } keys %{$rules}) { @@ -45,7 +56,7 @@ sub ruleset { $src->path('(.*)'); $dst->path('$1'); } - if ($self->{mono} && $host ne '') { + if ($self->{monolithic} && $host ne '') { $self->printf("RewriteCond %%{HTTP_HOST} =%s [NC]\n", $host); } if ($src->query) { @@ -62,4 +73,90 @@ sub ruleset { 1; - +=head1 NAME + +rewrite - generate mod_rewrite rules + +=head1 SYNOPSIS + +B<makeredirect> +B<rewrite> +[B<-m>] +[B<--monolithic>] +I<FILE> ... + +=head1 DESCRIPTION + +Converts a CSV file with source-destination URL pairs to a set of equivalent +B<mod_rewrite> rules. The output can be a I<monolithic> single set of rewrite +rules or multiple sets of rules scattered among several output files named by +the B<ServerName> they refer to. + +In the I<monolithic> output, each line of the source CSV file that contains +a domain name in the column 1 produces a B<RewriteRule> directive preceded by +a B<RewriteCond> that limits its scope to the given domain name only. For +example, the input line + + http://example.com/login,/ + +will produce two lines: + + RewriteCond %{HTTP_HOST} =example.com [NC] + RewriteRule "^/login" / [L,R=301] + +whereas the line + + /logout,/exit + +produces + + RewriteRule "^/logout" /exit [L,R=301] + +The monolithic mode is assumed in the following cases: + +=over 4 + +=item 1. The output file name is not given. + +If the B<--output> option is not supplied, the rules are printed to the +standard output. + +=item 2. If the B<--monolithic> (B<-m>) option is given. + +=back + +Otherwise B<rewrite> operates in I<split> mode. In split mode, all rules +for a specific domain are grouped together and saved in a file named by the +domain name with the C<.conf> suffix. The argument to the B<--output> (B<-o>) +option supplies the name of the directory where these files will be stored. +The output directory must exist. + +=head1 OPTIONS + +=over 4 + +=item B<-m>, B<--monolithic> + +Enable monolithic mode. + +=item B<-?> + +Produce a short help text and exit. + +=item B<--help> + +Display the manual page and exit. + +=item B<--usage> + +Display a short command line usage summary and exit. + +=back + +=head1 SEE ALSO + +B<makeredirect>(1), +L<MakeRedirect>. + +=cut + diff --git a/lib/MakeRedirect/URI.pm b/lib/MakeRedirect/URI.pm index 9e0a574..4db1a85 100644 --- a/lib/MakeRedirect/URI.pm +++ b/lib/MakeRedirect/URI.pm @@ -58,11 +58,16 @@ my @attributes = qw(scheme host path query); } } +sub scheme_prefix { + my ($self) = @_; + $self->{scheme} ? ($self->scheme . '://') : ''; +} + sub canonical { my ($self) = @_; my $s; if ($self->host) { - $s = $self->scheme . '://' . $self->host . ($self->{path} || '/'); + $s = $self->scheme_prefix . $self->host . ($self->{path} || '/'); } else { $s = $self->path; } diff --git a/makeredirect b/makeredirect index 9c9abb9..0dabd01 100644..100755 --- a/makeredirect +++ b/makeredirect @@ -5,24 +5,60 @@ eval 'exec perl -x -S $0 ${1+"$@"}' use strict; use warnings; use Getopt::Long qw(:config gnu_getopt no_ignore_case require_order); +use Pod::Man; +use Pod::Usage; use MakeRedirect; my $output; -my $modname = 'verbose'; my %args; -GetOptions('skip|s=n' => sub { $args{skip} = $_[1] }, +my $skiplines; +GetOptions('shorthelp|?' => sub { + pod2usage(-message => 'create redirect rules from CSV', + -exitstatus => 0) + }, + 'help' => sub { + pod2usage(-exitstatus => 0, + -verbose => 2) + }, + 'usage' => sub { + pod2usage(-exitstatus => 0, + -verbose => 0) + }, + 'skip|s=n' => \$skiplines, 'output|o=s' => sub { $args{file} = $_[1] }, - 'module|m=s' => \$modname, - 'argument|a=s' => sub { - my ($k,$v) = split /=/, $_[1], 2; - $args{$k} = $v || 1; - } - ) - or die; + ) or die; +my $modname = shift || die "module name missing\n"; my $mr = new MakeRedirect($modname, %args); +$mr->getopt; foreach my $f (@ARGV) { - $mr->read($f); + $mr->read($f, skip => $skiplines); } $mr->output; +=head1 NAME + +makeredirect - create URL redirect rules from CSV + +=head1 SYNOPSIS + +B<makeredirect> +[B<-o> I<FILE>] +[B<-s> I<N>] +[B<--output=>I<FILE>] +[B<--split=>I<N>] +I<module> +[I<MODOPT>] +I<FILE> ... + +=head1 DESCRIPTION + +Converts CSV input files to a set of redirect rules for HTTP servers in +desired format. + +The format is defined by the I<module> argument, which supplies the name +of an I<output module>. Optional I<MODOPT> are options for the module. + +=head1 OPTIONS + +=cut diff --git a/t/03_list.t b/t/03_list.t index 78a8492..4117157 100644 --- a/t/03_list.t +++ b/t/03_list.t @@ -8,13 +8,10 @@ use_ok 'TestRedirect'; my $t = new TestRedirect('list'); is_deeply([$t->errors],[]); is $t->result,<<EOT; -host= /foo /bar -host=example.com -https://example.com/source/subdir /subdir -https://example.com/source /dest -host=example.org -http://example.org/foobar /baz +example.com/source/subdir /subdir +example.com/source /dest +example.org/foobar /baz EOT ; __DATA__ diff --git a/t/04_rewrite.t b/t/04_rewrite.t index 5b20701..e1816a0 100644 --- a/t/04_rewrite.t +++ b/t/04_rewrite.t @@ -5,7 +5,7 @@ use warnings; use Test::More tests => 3; use_ok 'TestRedirect'; -my $t = new TestRedirect('rewrite', mono => 1); +my $t = new TestRedirect('rewrite', monolithic => 1); is_deeply([$t->errors],[]); is $t->result,<<EOT; RewriteRule "^/foo" /bar [L,R=301,NE] |