diff options
author | Sergey Poznyakoff <gray@gnu.org.ua> | 2016-08-11 18:07:19 +0300 |
---|---|---|
committer | Sergey Poznyakoff <gray@gnu.org.ua> | 2016-08-11 18:08:35 +0300 |
commit | 7c0689660adad268a0a0ffc80d3916df48609632 (patch) | |
tree | ec3754ccb28e3e00f4136901b933c042833234fc /tpnotify | |
download | tpnotify-7c0689660adad268a0a0ffc80d3916df48609632.tar.gz tpnotify-7c0689660adad268a0a0ffc80d3916df48609632.tar.bz2 |
Initial commit
Diffstat (limited to 'tpnotify')
-rwxr-xr-x | tpnotify | 1003 |
1 files changed, 1003 insertions, 0 deletions
diff --git a/tpnotify b/tpnotify new file mode 100755 index 0000000..3916d61 --- /dev/null +++ b/tpnotify @@ -0,0 +1,1003 @@ +#!/usr/bin/perl +# Copyright (C) 2016 Sergey Poznyakoff <gray@gnu.org> +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 3, or (at your option) +# any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +use strict; +use Getopt::Long qw(:config gnu_getopt no_ignore_case); +use Pod::Usage; +use Pod::Man; +use WWW::Curl::Easy; +use Unix::Sysexits; +use File::Basename; +use File::Temp qw(tempdir); +use File::Path qw(remove_tree); +use IPC::Open2; +use Mail::Send; +use Mail::Message; +use Sys::Hostname;; +use Data::Dumper; + +my $progname = basename($0); +my $progdescr = "FIXME"; +our $VERSION = "1.0"; + +my $keep; # Keep temporary directory on errors +my $signature = "$ENV{HOME}/.signature"; # Signature file +my $verbose; # Verbose mode +my $dry_run; # Dry-run mode +my %mailer_args; # Mailer arguments +my $sender; # Sender email +my $fullname; # Sender real name +my $localdomain; # Local domain name +my $recipient; # Override recipient address +my @add_headers; # Additional headers + +my $template = <<'EOT'; +To: <coordinator@translationproject.org> +Subject: $package_base.pot + +Hello, + +The new $release_type version of $package_name is available at: + + $url + +$signature +EOT + ; + +my $url; # Tarball URL +my $wd; # Temporary working directory +my $filename; # Archive file name +my $topdir; # Toplevel directory from the archive +my %files; # Extracted files. Key - name under $topdir, value - pathname + # within $wd +my $package_name; # Package name; +my $package_tarname; # Package archive name +my $package_version; # Package version number +my $package_base; # Package base name +my $release_type; # Package type (alpha or stable) + +sub err { + my $msg = shift; + local %_ = @_; + + print STDERR "$progname: "; + print STDERR "$_{prefix}: " if exists $_{prefix} && defined $_{prefix}; + print STDERR "$msg\n"; +} + +sub abend { + my $code = shift; + &err; + if ($keep && $code ne EX_USAGE && $code ne EX_CONFIG) { + err("examine $wd for details"); + } + exit($code); +} + +sub info { + print "$progname: "; + print @_; + print "\n"; +} + +sub download { + my ($url) = @_; + my $curl = WWW::Curl::Easy->new; + $curl->setopt(CURLOPT_HEADER,0); + $curl->setopt(CURLOPT_URL, $url); + $filename = basename($url); + info("downloading $filename from $url") if $verbose; + open(my $fd, '>', $filename) + or abend(EX_CANTCREAT, "can't open $wd/$filename for writing: $!"); + $curl->setopt(CURLOPT_WRITEDATA, $fd); + my $retcode = $curl->perform; + if ($retcode) { + abend(EX_UNAVAILABLE, + "failed to download: ".$curl->strerror($retcode)." ".$curl->errbuf); + } else { + my $code = $curl->getinfo(CURLINFO_HTTP_CODE); + if ($code != 200) { + print STDERR "error downloading: HTTP code $code\n"; +#FIXME system("cat >&2 $filename"); + die "aborted"; + } + } + close($fd); + info("scanning $filename") if $verbose; + open($fd, '-|', "tar tf $filename") + or abend(EX_NOINPUT, "can't open $filename: $!"); + while (<$fd>) { + chomp; + unless (m#^(?<dir>.+?)/(?<file>.*)$#) { + abend(EX_DATAERR, "$filename content suspicious: member $_"); + } + if (defined($topdir)) { + unless ($+{dir} eq $topdir) { + abend(EX_DATAERR, + "$filename content suspicious: $+{dir} does not match $topdir"); + } + } else { + $topdir = $+{dir}; + } + my $f = $+{file}; + if ($f eq 'configure.ac' || $f =~ m#po/.*\.pot#) { + $files{$f} = $_; + } + } + close $fd; + info("top level directory: $topdir") if $verbose; + + # Verify available files + unless (exists($files{'configure.ac'})) { + abend(EX_DATAERR, "no configure.ac in $filename"); + } + unless (keys(%files) > 1) { + abend(EX_DATAERR, "no potfile in $filename"); + } + + my $filelist = join(' ', values(%files)); + info("extracting from $filename") if $verbose; + system("tar xf $filename $filelist"); + check_command_status("tar xf $filename $filelist"); + #print "tar xvf $filename " . ; + #print "\n"; +} + +sub check_command_status { + my $cmd = shift; + my $status = shift || $?; + + if ($status == -1) { + abend(EX_OSERR, "failed to run $cmd"); + } elsif ($status & 127) { + abend(EX_UNAVAILABLE, "$cmd exited on signal " . ($status & 127)); + } elsif (my $e = ($status >> 8)) { + abend(EX_UNAVAILABLE, "$cmd exited with status $e"); + } +} + +sub verify_potfile { + # FIXME +} + +sub verify { + my ($in, $out); + my $pid = open2($out, $in, "m4 -P - $files{'configure.ac'}") + or abend(EX_NOINPUT, "can't open $files{'configure.ac'}: $!"); + print $in <<'EOT'; +m4_divert(-1) +m4_changequote([,]) +m4_define([AC_INIT],[m4_divert(0)$1 +$2[]m4_divert(-1)]) +EOT + close $in; + waitpid($pid, 0); + check_command_status("m4"); + chomp(my @lines = <$out>); + abend(EX_DATAERR, "can't parse $files{'configure.ac'}") + unless $#lines == 1; + ($package_name, $package_version) = @lines; + $package_tarname = $package_name; + $package_tarname =~ s/GNU\s+//; + info("package $package_name, tarname $package_tarname, version $package_version") if $verbose; + $package_base = "$package_tarname-$package_version"; + + unless (defined($release_type)) { + if ($package_version =~ m/\d+\.\d+\.(\d+)/ && int($1) >= 90) { + $release_type = 'alpha'; + } else { + $release_type = 'stable'; + } + } + + if (substr($filename, 0, length($package_base)) ne $package_base) { + abend(EX_DATAERR, + "filename $filename does not begin with $package_base"); + } + if ($package_base ne $topdir) { + abend(EX_DATAERR, + "toplevel directory $topdir does not begin with $package_base"); + } + my $potfile = "po/$package_tarname.pot"; + unless ($files{$potfile}) { + abend(EX_DATAERR, "potfile $potfile not found in archive"); + } + verify_potfile; +} + +sub get_special_var { + my ($var) = @_; + if ($var eq 'signature') { + if (defined($signature)) { + if (open(my $fd, '<', $signature)) { + local $/; + my $sig = <$fd>; + close($fd); + return $sig; + } else { + err("can't open signature file $signature: $!", + prefix => 'warning'); + } + } + } elsif ($var eq 'username') { + return $fullname; + } + return undef; +} + +sub expand_template { + join("\n", map { + s/\$(signature|username)/get_special_var($1)/gex; + s/(\$[a-zA-Z_][a-zA-Z0-9_]+)/eval "$1"/gex; + $_ + } split(/\n/, $template)); +} + +sub read_mh_path { + if (open(my $fd, '<', "$ENV{HOME}/.mh_profile")) { + my $prev; + while (<$fd>) { + chomp; + if (s/^\s+//) { + $prev .= ' ' . $_; + } else { + last if $prev =~ /^Path:/; + $prev = $_; + } + } + close $fd; + return $prev if $prev =~ s/^Path:\s+//; + } + return "$ENV{HOME}/Mail"; +} + +sub notify { + my $msg = Mail::Message->read(expand_template); + $msg->head()->add('From', "\"$fullname\" <$sender>") unless $msg->get('From'); + foreach my $hdr (@add_headers) { + $msg->head()->add($hdr); + } + $msg->head()->add("X-Mailer: $progname $VERSION"); + + if ($verbose) { + info("message to send"); + $msg->print(); + print "\n"; + info("end of message"); + } + + if ($dry_run) { + info("NOT sending"); + return; + } + + info("sending message") if $verbose; + + if (my $folder = $msg->get('Fcc')) { + $msg->head()->delete('Fcc'); + + my %args = (create => 1, access => 'rw'); + + if ($folder =~ m#^/#) { + $args{type} = 'mbox'; + $args{folder} = $folder; + } elsif ($folder =~ s#mh:(?://)?(.+)#$1#) { + $args{type} = 'mh'; + if ($folder =~ m#^/#) { + $args{folder} = $folder; + } else { + $args{folder} = read_mh_path() . '/' . $folder; + } + } elsif ($folder =~ s#maildir:(?://)?(.+)#$1#) { + $args{type} = 'maildir'; + $args{folder} = $folder; + } else { + abend(EX_DATAERR, "unrecognized Fcc folder: $folder"); + } + + use Mail::Box::Manager; + my $mgr = Mail::Box::Manager->new(); + my $folder = $mgr->open(%args) + or abend(EX_CANTCREAT, "can't open folder $folder"); + $folder->addMessage($msg) + or abend(EX_CANTCREAT, "can't save message to folder $folder"); + $folder->close; + } + + $msg->send(%mailer_args); + + my $to = $recipient || $msg->get('To'); +# info("Location of $package_base.pot sent to $to"); +} + +END { + chdir("/"); + if (!($? && $keep)) { + remove_tree($wd, {error => \my $err}); + if (@$err) { + err("errors removing $wd:"); + for my $diag (@$err) { + my ($file, $message) = %$diag; + if ($file eq '') { + err($message); + } else { + err("$file: $message"); + } + } + } + } +} + +sub set_mailer { + my ($mailer, $locus) = @_; + if ($mailer =~ /sendmail:(.*)/) { + $mailer_args{via} = 'sendmail'; + $mailer_args{executable} = $1 if $1; + } elsif ($mailer =~ m#smtp://(?:(?<user>[^:]+)(?::(?<password>.+))?@)?(?<host>[^:]+)(?::(?<port>\d+))?#) { + $mailer_args{via} = 'smtp'; + $mailer_args{hostname} = $+{host}; + $mailer_args{port} = $+{port} if $+{port}; + $mailer_args{username} = $+{user} if $+{user}; + $mailer_args{password} = $+{password} if $+{password}; + } else { + err("unknown mailer spec", prefix => $locus); + return 0; + } + return 1; +} + +sub read_template_file { + my ($file, $locus) = @_; + if (open(my $fd, '<', $file)) { + local $/; + $template = <$fd>; + close($fd); + return 1; + } else { + err("can't open template file $file: $!", prefix => $locus); + return 0; + } +} + +my %kw = ( + keep => \$keep, + 'template-file' => \&read_template_file, + template => \$template, + 'signature-file' => \$signature, + mailer => \&set_mailer, + from => \$sender, + sender => \$sender, + fullname => \$fullname, + domain => \$localdomain, + to => \$recipient, + add => \@add_headers, + 'add-header' => \@add_headers +); + +sub read_config { + my $config_file = shift; + open(FILE, "<", $config_file) + or abend(EX_NOINPUT, "cannot open $config_file: $!"); + my $line = 0; + my $err; + my $key; + my $val; + my $heredoc; + my $heredoc_line; + while (<FILE>) { + ++$line; + + if ($heredoc) { + if (/^$heredoc\s*$/) { + $heredoc = undef; + } else { + $val .= $_; + next; + } + } else { + chomp; + s/^\s+//; + s/\s+$//; + s/#.*//; + next if ($_ eq ""); + + if (/^(.*?)\s*=\s*(.*)/) { + $key = $1; + $val = $2; + + if ($val =~ /<<(\w+)\s*$/) { + $heredoc = $1; + $heredoc_line = $line; + $val = ''; + next; + } + } else { + err("$config_file:$line: syntax error"); + ++$err; + } + } + + if (exists($kw{$key})) { + my $ref = $kw{$key}; + if (ref($ref) eq 'CODE') { + unless (&{$ref}($val, "$config_file:$line")) { + ++$err; + } + } elsif (ref($ref) eq 'ARRAY') { + push @{$ref}, $val; + } else { + $$ref = $val; + } + } else { + err("$config_file:$line: unrecognized keyword: '$key'"); + ++$err; + } + } + close FILE; + + abend(EX_CONFIG, "unfinished heredoc, started at line $heredoc_line") + if defined $heredoc; + abend(EX_CONFIG, "errors in config file") if $err; +} + +# +my $debug; +my $config_file = "$ENV{HOME}/.tpnotify" if -e "$ENV{HOME}/.tpnotify"; + +Getopt::Long::Configure(qw(gnu_getopt no_ignore_case pass_through)); +GetOptions("help" => sub { + pod2usage(-exitstatus => EX_OK, -verbose => 2); + }, + "h" => sub { + pod2usage(-message => "$progname: $progdescr", + -exitstatus => EX_OK); + }, + "usage" => sub { + pod2usage(-exitstatus => EX_OK, -verbose => 0); + }, + "config|c=s" => \$config_file, + "no-config|N" => sub { $config_file = undef } + ); + +read_config($config_file) if defined $config_file; + +Getopt::Long::Configure(qw(gnu_getopt no_ignore_case no_pass_through)); + +GetOptions("keep|k" => \$keep, + "alpha|A" => sub { $release_type = 'alpha' }, + "stable|S" => sub { $release_type = 'stable' }, + "template|t=s" => sub { + exit(EX_NOINPUT) unless read_template_file($_[1]) + }, + "signature|s=s" => \$signature, + "no-signature" => sub { $signature = undef }, + "verbose|v+" => \$verbose, + "dry-run|n" => \$dry_run, + "debug|d+" => \$debug, + "mailer|m=s" => sub { + exit(EX_USAGE) unless set_mailer($_[1]) + }, + "from|f=s" => \$sender, + "fullname|F=s" => \$fullname, + "domain|D=s" => \$localdomain, + "to=s" => \$recipient, + "add|a=s@" => \@add_headers +) or exit(EX_USAGE); + +++$verbose if $dry_run; +if ($debug && exists($mailer_args{via})) { + if ($mailer_args{via} eq 'sendmail') { + $mailer_args{sendmail_options} = [] + unless exists $mailer_args{sendmail_options}; + push @{$mailer_args{sendmail_options}}, + '-O', 'LogLevel=99', '-d10.100', '-d13.90', '-d11.100'; + } elsif ($mailer_args{via} eq 'smtp') { + $mailer_args{smtp_debug} = 1; + } +} + +if ($sender && exists($mailer_args{via})) { + if ($mailer_args{via} eq 'sendmail') { + $mailer_args{sendmail_options} = [] + unless exists $mailer_args{sendmail_options}; + push @{$mailer_args{sendmail_options}}, '-f', $sender; + } elsif ($mailer_args{via} eq 'smtp') { + $mailer_args{from} = $sender; + } +} + +my ($name,undef,undef,undef,undef,$comment,$gecos) = getpwuid($<); +$fullname = $gecos || $comment || $name unless defined $fullname; +$sender = $name . '@' . ($localdomain || hostname()) unless defined $sender; + +if ($recipient) { + $mailer_args{to} = $recipient; +} + +#print Dumper([\%mailer_args]); + +$url = shift; +abend(EX_USAGE, "not enough arguments") unless defined $url; +abend(EX_USAGE, "too many arguments") unless $#ARGV == -1; + +$wd = tempdir() + or abend(EX_CANTCREAT, "can't create temporary directory: $!"); +chdir($wd) or abend(EX_OSERR, "can't change to temporary directory $wd: $!"); + +download($url); +verify; +notify; + +__END__ + +=head1 NAME + +tpnotify - Notifies translationproject.org about new POT file + +=head1 SYNOPSIS + +B<tpnotify> +[B<-ANSdnkv>] +[B<-D> I<DOMAIN>] +[B<-F> I<NAME>] +[B<-a> I<HDR>B<:>I<VALUE> +[B<-c> I<FILE>] +[B<-f> I<FROM>] +[B<-m> I<SPEC>] +[B<-s> I<FILE>] +[B<-t> I<FILE>] +[B<--add=>I<HDR>:I<VAL>] +[B<--alpha>] +[B<--config=>I<FILE>] +[B<--debug>] +[B<--domain=>I<DOMAIN>] +[B<--dry-run>] +[B<--from=>I<EMAIL>] +[B<--fullname=>I<NAME>] +[B<--keep>] +[B<--mailer=>I<SPEC>] +[B<--no-config>] +[B<--no-signature>] +[B<--signature=>I<FILE>] +[B<--stable>] +[B<--template=>I<FILE>] +[B<--to=>I<EMAIL>] +[B<--verbose>] +I<URL> + +B<tpnotify> +[B<-h>] +[B<--help>] +[B<--usage>] + +=head1 DESCRIPTION + +Notifies the coordinator of the I<Translation Project> about new +POT file available at I<URL>. The URL must point to a tarball of +a package registered at TP (I<http://translationproject.org/domain/>). +The tool works as follows: + +First of all, the indicated I<URL> is downloaded to a temporary location +on disk. The contents of the retrieved tarball is inspected. It must +contain the file F<configure.ac> in the project toplevel directory and +one or more files with the B<.pot> suffix in the F<po> subdirectory. + +These files are extracted. The F<configure.ac> is parsed in order to +determine the package name and version (from the B<AC_INIT> statement). +The canonical package name is formed by concatenating the package name +(with the eventual B<GNU> prefix stripped), a dash, and the version +number. The name of the POT file is constructed by appending the +B<.pot> suffix to the base name, This file is looked up in the B<po> +subdirectory. + +When this initial stage is through, the message template is expanded. +See the B<TEMPLATE> section below, for a detailed discussion of this +stage. The resulting email message is then sent. Unless the B<--to> +option is given, the recipients are obtained from the headers B<To:>, +B<Cc:>, and B<Bcc:> of the formatted message. The B<--to> option supplies +the recipient email to be used instead of those. + +The B<Fcc:> header can be used to save the copy of the message being sent +in a mailbox. If its value is an absolute or relative pathname, it is assumed +to be a mailbox in traditional B<UNIX> format (relative pathnames are +expanded relative to the user home directory). Otherwise, it is a B<folder +url>: + +=over 8 + +=item B<mbox:>[B<//>]I<PATHNAME> + +UNIX mailbox located at I<PATHNAME> (relative or absolute). + +=item B<mh:>[B<//>]I<PATHNAME> + +MH mailbox at I<PATHNAME>. Relative pathnames are resolved by prepending +the value of the B<Path> header from the F<~/.mh_profile> file. If not +defined, the B<~/Mail> directory is assumed. + +=item B<maildir:>[B<//>]I<PATHNAME> + +Maildir folder at I<PATHNAME>. Relative pathnames are relative to the +current user's home directory. + +=back + +Additional configuration is supplied in configuration file and command line. +The latter overrides the former. See the section B<CONFIGURATION> for a +detailed discussion of the configuration file format. + +The B<-v> (B<--verbose>) command line option instructs the tool to verbosely +list each step being executed. Additionally, the B<-d> (B<--debug>) option +enables a detailed printout of debugging information describing the mail +sending process. + +The B<-n> (B<--dry-run>) option causes the program to verbosely print what +is being done (as ig given the B<--verbose> option), but not actually send +the constructed message. + +=head1 CONFIGURATION + +The default configration file is named B<.tpnotify> and is located in the +current user home directory. I's OK if it does not exist. In this case +the tool will use the built-in defaults. Two command line options are +provided to alter that behavior. The B<-c I<FILE>> (B<--config=>I<FILE>) +option causes the program to read configuration file from I<FILE>. It +is an error if that file does not exist or is unreadable. The B<--no-config> +option instructs B<tpnotify> to ignore the configuration file. + +When reading the configuration file, empty lines and lines starting with +a hash sign (B<#>) are ignored. Remaining lines must contain valid +configuration statements, + +A statement consists of a keyword, followed by an equals sign and a value. +Arbitrary amount of white space are allowed at the beginning and end of +line, as well as around the equals sign. Multiline values can be entered +using the familiar I<here-document> syntax. A here-document is +introduced by the B<<<> marker followed by arbitrary word. The lines following +that construct are collected and concatenated until a line is found that +contains only that word alone. The word must appear at the beginning of +the line (no leading whitespace allowed). However, whitespace is allowed +between the word and end of line. For example + + template = <<EOF + To: <coordinator@translationproject.org> + Subject: $package_base.pot + + $url + EOF + +The valid statements are as follows: + +=over 8 + +=item B<keep=>B<1> | B<0> + +If an error occurs, don't remove the temporary directory. This allows the +user to get more insight into the reasons of the failure. + +See also the B<--keep> option. + +=item B<template-file=>I<FILE> + +Name of the file to read template from. See also the B<--template> command +line option. + +=item B<template=>I<TEXT> + +Template for the message. The I<TEXT> is normally a here document. +See the B<TEMPLATE> section for a description of its format. + +=item B<signature-file=>I<FILE> + +Read signature from the given file. See also the B<--signature> command line +option. + +=item B<mailer=>I<SPEC> + +Sets mailer. The argument is a mailer specification as discussed in the +description of the B<--mailer> command line option below. + +=item B<sender=>I<EMAIL> + +Sets the sender email address. See also the B<--sender> option. + +=item B<fullname=>I<STRING> + +Sets the real name of the recipient. See the B<--fullname> command +line option. + +=item B<domain=>I<DOMAIN> + +Sets the sender domain name. This is used when creating the +sender email address to be used in the B<From:> header. See also the +B<--domain> command line option. + +=item B<to=>I<EMAIL> + +Sets the recipient address to be used instead of the emails from +B<To:>, B<Cc:>, and B<Bcc:> headers of the constructed message. + +=item B<add=>I<HDR>B<:>I<VAL> + +Adds the given header to the message. See also the B<--add> command line +option. + +=back + +An example of the configuration file follows: + + # Example configuration file for tpnotify + # + mailer = smtp://127.0.0.1:24 + from = gray@gnu.org + template = <<EOT + To: <coordinator@translationproject.org> + Subject: $package_base.pot + + The new $release_type version of $package_name is available at: + + $url + + $signature + EOT + +=head1 TEMPLATE + +The template is an email message conforming to the B<RFC-2822>. It +can (and, actually must) contain variables that will be replaced with +their actual values during the expansion. The variables are: + +=over 8 + +=item $sender + +Sender email address. It is constructed by concatenating the login name of +the invoking user, the B<@> sign, and the local host name. If the B<--domain> +command line option, or B<domain> configuration statement is used, its value +is substituted instead of the local host name. If the B<--from> option or +B<sender> configuration statement is used, its value overrides the constructed +one. + +=item $fullname + +Full real name of the sender. It can be supplied by the B<--fullname> command +line option or B<fullname> configuration statement. If neither of these is +present, the name is obtained from the B<Gecos> line in the B<passwd> entry of +the invoking user. + +=item $localdomain + +Local domain name. It is the value of the command line option B<--domain>. If +not present, the value of the configuration statemeot B<domain> is used. If +that is not present either, then the local host name is used instead. + +=item $signature + +Contents of the I<signature file>. The file location is given by the command +line option B<--signature> or the configuration statement B<--signature-file>. + +=item $url + +I<URL> of the tarball as supplied in the command line. + +=item $filename + +Name of the tarball. It is the last pathname component from I<URL>. + +=item $topdir + +Project toplevel directory as seen in the downloaded tarball. + +=item $package_name; + +Full package name. E.g. B<GNU dico>. + +=item $package_tarname + +Package I<tarname>. It is the B<$package_name> value with the eventual +B<GNU> prefix stripped off. + +=item $package_version + +Package version number. + +=item $package_base + +Package base name, constructed as a concatenation of values of the +B<$package_tarname> and B<$package_version>, separated by a dash. + +=item $release_type + +Package type: B<alpha>, or B<stable>. Unless supplied with the corresponding +command line option, it is determined by analyzing B<$package_number>. The +type is B<alpha>, if the number contains three numeric parts, separated by +dots, and the value of the last part is greater than or equal to 90. Otherwise, +the type is B<stable>. + +=back + +=head1 OPTIONS + +=over 8 + +=item B<--no-config> + +Don't read configuration file. + +=item B<--no-signature> + +Don't read signature file. The B<$signature> template variable will be +undefined. + +=item B<--to=>I<EMAIL> + +Send message to I<EMAIL>, instead of the addresses specified in the message +itself. + +=item B<-A>, B<--alpha> + +Assume I<URL> is an alpha release. + +=item B<-D>, B<--domain=>I<DOMAIN> + +Use I<DOMAIN> as the sender domain. This is used when creating the +sender email address to be used in the B<From:> header. It is constructed +by concatenating the login name of the invoking user, the B<@> sign, and the +local host name (or I<DOMAIN>, if set). + +=item B<-F>, B<--fullname=>I<NAME> + +Set the full real name of the sender. It is used to construct the B<From:> +header. When using B<sendmail> mailer, it will also be passed with the B<-F> +option to B<sendmail>. + +=item B<-S>, B<--stable> + +Assume I<URL> is a stable release. + +=item B<-a>, B<--add=>I<HDR>B<:>I<VAL> + +Append the given header to the message. + +=item B<-c>, B<--config=>I<FILE> + +Read configuration from I<FILE>, instead of F<~/.tpnotify>. + +=item B<-d>, B<--debug> + +Debug the mail sending transaction. + +=item B<-f>, B<--from=>I<EMAIL> + +Sets the sender email address. Unless this option is supplied, the email +address of the sender of the message will be constructed by concatenating +the login name of the invoking user, the B<@> sign, and the +local host name (or the local domain, if set via the B<--domain> option). + +=item B<-k>, B<--keep> + +If an error occurs, don't remove the temporary directory. This allows the +user to get more insight into the reasons of the failure. + +=item B<-m>, B<--mailer=>I<SPEC> + +Sets the mailer. The I<SPEC> is one of the following: + +=over 4 + +=item B<sendmail:> + +Use the default sendmail binary for sending. + +=item B<sendmail:>F<program> + +Use the sendmail-compatible program. F<program> is the absolute pathname +of the program. + +=item B<smtp://>[I<USER>[B<:>I<PASS>]B<@>]I<HOSTNAME>[B<:>I<PORT>] + +Send mail using SMTP. I<HOSTNAME> is the hostname or IP address of the mail +relay to use. I<PORT>, if supplied, is the port number to use instead of the +default 25. Optional I<USER> and I<PASS> provide credentials, if the relay +requires authentication. + +=back + +=item B<-n>, B<--dry-run> + +Don't actually send the message. Verbosely print what is being done (see +the B<--verbose> option) and display the content of the message that whould +have been sent. + +=item B<-s>, B<--signature=>I<FILE> + +Read signature from I<FILE>. The content of the file is available as the +value of the B<$signature> template variable. + +=item B<-t>, B<--template=>I<FILE> + +Read template from I<FILE>. See the section B<TEMPLATE> for its format. + +=item B<-v>, B<--verbose> + +Verbosely print what is being done. + +=back + +The following options are informative. They cause the program to print +the required piece of information and exit. The remaining options and +arguments are silently ignored. + +=over 8 + +=item B<-h> + +Produce a short help summary. + +=item B<--help> + +Print a detailed manual. + +=item B<--usage> + +Display a short command line usage summary. + +=back + +=head1 EXIT CODE + +=over 4 + +=item B<0> + +Success + +=item B<64> + +Command line usage error. + +=item B<65> + +Downloaded archive contains invalid data. See the error messages for details. + +=item B<66> + +Required input file cannot be opened. + +=item B<69> + +Subprocess exited with error status or on signal. + +=item B<71> + +Failed to run subprocess, or failed to change the directory. + +=item B<73> + +Required output file cannot be created or written. + +=item B<78> + +Configuration error. + +=back + +=head1 AUTHOR + +Sergey Poznyakoff <gray@gnu.org> + +=cut |