aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSergey Poznyakoff <gray@gnu.org>2020-04-04 11:30:58 +0300
committerSergey Poznyakoff <gray@gnu.org>2020-04-04 11:30:58 +0300
commitc5d52ce8690a445fff4619d610c647ca0e3c25e5 (patch)
treed02c9dc4880dd1ee322de9b62e5a48fcbecc121a
parent7c010899445784b494b4b839c61867f9fe320294 (diff)
downloadmakeredirect-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.PL3
-rw-r--r--lib/MakeRedirect.pm5
-rw-r--r--lib/MakeRedirect/Output.pm70
-rw-r--r--lib/MakeRedirect/Output/help.pm125
-rw-r--r--lib/MakeRedirect/Output/list.pm75
-rw-r--r--lib/MakeRedirect/Output/rewrite.pm109
-rw-r--r--lib/MakeRedirect/URI.pm7
-rwxr-xr-x[-rw-r--r--]makeredirect56
-rw-r--r--t/03_list.t9
-rw-r--r--t/04_rewrite.t2
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]

Return to:

Send suggestions and report system problems to the System administrator.