Merge branch 'documentation'
authorThomas Hochstein <thh@inter.net>
Thu, 25 Feb 2010 18:22:28 +0000 (19:22 +0100)
committerThomas Hochstein <thh@inter.net>
Thu, 25 Feb 2010 18:22:28 +0000 (19:22 +0100)
1  2 
yapfaq.pl

diff --combined yapfaq.pl
+++ b/yapfaq.pl
@@@ -54,32 -54,21 +54,32 @@@ use strict
  use Net::NNTP;
  use Date::Calc qw(Add_Delta_YM Add_Delta_Days Delta_Days Today);
  use Fcntl ':flock'; # import LOCK_* constants
 +use Getopt::Std;
  my ($TDY, $TDM, $TDD) = Today(); #TD: Today's date
  
 +my %Options;
 +getopts('hvpdt:f:', \%Options);
 +if ($Options{'h'}) {
 +  print "$0 v $Version\nUsage: $0 [-hvpd] [-t <newsgroups>] [-f <faq>]\n";
 +  exit(0);
 +};
 +my ($Faq) = $Options{'f'} if ($Options{'f'});
 +
  my @Config;
 -readconfig (\$ConfigFile, \@Config);
 +readconfig (\$ConfigFile, \@Config, \$Faq);
  
  foreach (@Config) { 
    my ($LPD,$LPM,$LPY) = (01, 01, 0001);  #LP: Last posting-date
    my ($NPY,$NPM,$NPD);                   #NP: Next posting-date
    my $SupersedeMID;
    
 -  my ($ActName,$File,$PFreq) =($$_{'name'},$$_{'file'},$$_{'posting-frequency'});
 +  my ($ActName,$File,$PFreq,$Expire) =($$_{'name'},$$_{'file'},$$_{'posting-frequency'},$$_{'expires'});
    my ($From,$Subject,$NG,$Fup2)=($$_{'from'},$$_{'subject'},$$_{'ngs'},$$_{'fup2'});
    my ($MIDF,$ReplyTo,$ExtHea)=($$_{'mid-format'},$$_{'reply-to'},$$_{'extraheader'});
    my ($Supersede)            =($$_{'supersede'});
 -    
 +
 +  next if (defined($Faq) && $ActName ne $Faq);
 +      
    if (open (FH, "<$File.cfg")) {
      while(<FH>){
        if (/##;; Lastpost:\s*(\d{1,2})\.(\d{1,2})\.(\d{2}(\d{2})?)/){
  
    $SupersedeMID = "" unless $Supersede;
  
 -  if ($PFreq =~ /(\d+)\s*([dw])/) { # Is counted in days or weeks: Use Add_Delta_Days.
 -    ($NPY,$NPM,$NPD) = Add_Delta_Days($LPY, $LPM, $LPD, (($2 eq "w")?$1 * 7: $1 * 1));
 -  } elsif ($PFreq =~ /(\d+)\s*([my])/) { #Is counted in months or years: Use Add_Delta_YM
 -    ($NPY,$NPM,$NPD) = Add_Delta_YM($LPY, $LPM, $LPD, (($2 eq "m")?(0,$1):($1,0)));
 -  }
 -    
 -  if (Delta_Days($NPY,$NPM,$NPD,$TDY,$TDM,$TDD) >= 0 ) {
 -    postfaq(\$ActName,\$File,\$From,\$Subject,\$NG,\$Fup2,\$MIDF,\$ExtHea,\$Sender,\$TDY,\$TDM,\$TDD,\$ReplyTo,\$SupersedeMID);
 +  ($NPY,$NPM,$NPD) = calcdelta ($LPY,$LPM,$LPD,$PFreq);
 +
 +  if (Delta_Days($NPY,$NPM,$NPD,$TDY,$TDM,$TDD) >= 0 or ($Options{'p'})) {
 +    if($Options{'d'}) {
 +        print "$ActName: Would be posted now (but running in simulation mode [$0 -d]).\n" if $Options{'v'};
 +      } else {
 +      postfaq(\$ActName,\$File,\$From,\$Subject,\$NG,\$Fup2,\$MIDF,\$ExtHea,\$Sender,\$TDY,\$TDM,\$TDD,\$ReplyTo,\$SupersedeMID,\$Expire);
 +      }
 +  } elsif($Options{'v'}) {
 +    print "$ActName: Nothing to do.\n";
    }
  }
  
  exit;
  
  ################################## readconfig ##################################
 -# Takes a filename and the reference to an array, which will hold hashes with
 -# the data from $File.
 +# Takes a filename, a reference to an array, which will hold hashes with
 +# the data from $File, and - optionally - the name of the (single) FAQ to post
  
  sub readconfig{
 -  my ($File, $Config) = @_;
 +  my ($File, $Config, $Faq) = @_;
    my ($LastEntry, $Error, $i) = ('','',0);
  
 +  if($Options{'v'}) {
 +    print "Reading configuration.\n";
 +  }
 +
    open FH, "<$$File" or die "$0: E: Can't open $$File: $!";
    while (<FH>) {
 +    next if (defined($$Faq) && !/^\s*=====\s*$/ && defined($$Config[$i]{'name'}) && $$Config[$i]{'name'} ne $$Faq );
      if (/^(\s*(\S+)\s*=\s*'?(.*?)'?\s*(#.*$|$)|^(.*?)'?\s*(#.*$|$))/ && not /^\s*$/) {
        $LastEntry = lc($2) if $2;
        $$Config[$i]{$LastEntry} .= $3 if $3;  
  
    #Check saved values:
    for $i (0..$i){
 +    next if (defined($$Faq) && defined($$Config[$i]{'name'}) && $$Config[$i]{'name'} ne $$Faq );
      unless($$Config[$i]{'from'} =~ /\S+\@(\S+\.)?\S{2,}\.\S{2,}/) {
        $Error .= "E: The From-header for your project \"$$Config[$i]{'name'}\" seems to be incorrect.\n"
      }
      unless($$Config[$i]{'posting-frequency'} =~ /^\s*\d+\s*[dwmy]\s*$/) {
        $Error .= "E: The Posting-frequency for your project \"$$Config[$i]{'name'}\" is invalid.\n"
      }
 +    unless($$Config[$i]{'expires'} =~ /^\s*\d+\s*[dwmy]\s*$/) {
 +      $$Config[$i]{'expires'} = '3m'; # set default: 3 month
 +        warn "$0: W: The Expires for your project \"$$Config[$i]{'name'}\" is invalid - set to 3 month.\n";
 +    }
      $Error .= "-" x 25 . "\n" if $Error;
    }
    die $Error if $Error;
  }
  
 +################################# calcdelta #################################
 +# Takes a date (year,  month and day) and a time period (1d, 1w, 1m, 1y, ...)
 +# and adds the latter to the former
 +
 +sub calcdelta {
 +  my ($Year, $Month, $Day, $Period) = @_;
 +  my ($NYear, $NMonth, $NDay);
 +
 +  if ($Period =~ /(\d+)\s*([dw])/) { # Is counted in days or weeks: Use Add_Delta_Days.
 +    ($NYear, $NMonth, $NDay) = Add_Delta_Days($Year, $Month, $Day, (($2 eq "w")?$1 * 7: $1 * 1));
 +  } elsif ($Period =~ /(\d+)\s*([my])/) { #Is counted in months or years: Use Add_Delta_YM
 +    ($NYear, $NMonth, $NDay) = Add_Delta_YM($Year, $Month, $Day, (($2 eq "m")?(0,$1):($1,0)));
 +  }
 +  return ($NYear, $NMonth, $NDay);
 +}
 +  
  ################################## postfaq ##################################
  # Takes a filename and many other vars.
  #
  # It reads the data-file $File and then posts the article.
  
  sub postfaq {
 -  my ($ActName,$File,$From,$Subject,$NG,$Fup2,$MIDF,$ExtraHeaders,$Sender,$TDY,$TDM,$TDD,$ReplyTo,$Supersedes) = @_;
 +  my ($ActName,$File,$From,$Subject,$NG,$Fup2,$MIDF,$ExtraHeaders,$Sender,$TDY,$TDM,$TDD,$ReplyTo,$Supersedes,$Expire) = @_;
    my (@Header,@Body,$MID,$InRealBody,$LastModified);
  
 +  if($Options{'v'}) {
 +    print "$$ActName: Preparing to post.\n";
 +  }
 +  
    #Prepare MID:
    $$TDM = ($$TDM < 10 && $$TDM !~ /^0/) ? "0" . $$TDM : $$TDM;
    $$TDD = ($$TDD < 10 && $$TDD !~ /^0/) ? "0" . $$TDD : $$TDD;
    my $year = (1900 + $time[5]);
    my $tz = $time[8] ? " +0200" : " +0100";
    
 -  my ($expY,$expM,$expD) = Add_Delta_YM($year, $month, $day, 0, 3);
 +  my ($expY,$expM,$expD) = calcdelta ($year,$month,$day,$$Expire);
    my $expmonthN = ("Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec")[$expM-1];
  
    my $date = "$day $monthN $year " . $hh . ":" . $mm . ":" . $ss . $tz;
    my $expdate = "$expD $expmonthN $expY $hh:$mm:$ss$tz";
 -
 + 
    #Replace %LM by the content of the news.answer-pseudo-header Last-modified:
    if ($LastModified) {
      $$Subject =~ s/\%LM/$LastModified/;
    }
  
 +  # Test mode?
 +  if($Options{'t'} and $Options{'t'} !~ /console/i) {
 +    $$NG = $Options{'t'};
 +  }
 +
    #Now create the complete Header:
    push @Header, "From: $$From\n";
    push @Header, "Newsgroups: $$NG\n";
  
    my @Article = ($UsePGP)?@{signpgp(\@Header, \@Body)}:(@Header, "\n", @Body);
    
 +  if($Options{'v'}) {
 +    print "$$ActName: Posting article ...\n";
 +  }
    post(\@Article);
  
 +  if($Options{'v'}) {
 +    print "$$ActName: Save status information.\n";
 +  }
 +
    open (FH, ">$$File.cfg") or die "$0: E: Can't open $$File.cfg: $!";
    print FH "##;; Lastpost: $day.$month.$year\n";
    print FH "##;; LastMID: $MID\n";
  sub post {
    my ($ArticleR) = @_;
  
 +  # Test mode?
 +  if(defined($Options{'t'}) and $Options{'t'} =~ /console/i) {
 +    print "\n-----BEGIN--------------------------------------------------\n";
 +      print @$ArticleR;
 +    print "\n------END---------------------------------------------------\n";
 +      return;
 +  }
 +
    my $NewsConnection = Net::NNTP->new($NNTPServer, Reader => 1)
      or die "$0: E: Can't connect to news server '$NNTPServer'!\n";
  
@@@ -476,3 -413,329 +476,329 @@@ sub signpgp 
    my @pgpmessage = (@pgphead, "\n", @pgpbody);
    return \@pgpmessage;
  }
+ __END__
+ ################################ Documentation #################################
+ =head1 NAME
+ yapfaq - Post Usenet FAQs I<(yet another postfaq)>
+ =head1 SYNOPSIS
+ B<yapfaq> [B<-hvpd>] [B<-t> I<newsgroups> | CONSOLE] [B<-f> I<project name>]
+ =head1 REQUIREMENTS
+ =over 2
+ =item -
+ Perl 5.8 or later
+ =item -
+ Net::NNTP
+ =item -
+ Date::Calc
+ =item -
+ Getopt::Std
+ =back
+ Furthermore you need access to a news server to actually post FAQs.
+ =head1 DESCRIPTION
+ B<yapfaq> posts (one or more) FAQs to Usenet with a certain posting
+ frequency (every n days, weeks, months or years), adding all necessary
+ headers as defined in its config file (by default F<yapfaq.cfg>).
+ =head2 Configuration
+ F<yapfaq.cfg> consists of one or more blocks, separated by C<=====> on
+ a single line, each containing the configuration for one FAQ as a set
+ of definitions in the form of I<param = value>.
+ =over 4
+ =item B<Name> = I<project name>
+ A name referring to your FAQ, also used for generation of a Message-ID.
+ This value must be set.
+ =item B<File> = I<file name>
+ A file containing the message body of your FAQ and all pseudo headers
+ (subheaders in the news.answers style).
+ This value must be set.
+ =item B<Posting-frequency> = I<time period>
+ The posting frequency defines how often your FAQ will be posted.
+ B<yapfaq> will only post your FAQ if this period of time has passed
+ since the last posting.
+ You can declare that time period either in I<B<d>ays> or I<B<w>weeks>
+ or I<B<m>onths> or I<B<y>ears>.
+ This value must be set.
+ =item B<Expires> = I<time period>
+ The period of time after which your message will expire. An Expires
+ header will be calculated adding this time period to today's date.
+ You can declare this  time period either in I<B<d>ays> or I<B<w>weeks>
+ or I<B<m>onths> or I<B<y>ears>.
+ This setting is optional; the default  is 3 months.
+ =item B<From> = I<author>
+ The author of your FAQ as it will appear in the From header of the
+ message.
+ This value must be set.
+ =item B<Subject> = I<subject>
+ The title of your FAQ as it will appear in the Subject header of the
+ message.
+ You may use the special string C<%LM> which will be replaced with
+ the contents of the Last-Modified subheader in your I<File>.
+ This value must be set.
+ =item B<NGs> = I<newsgroups>
+ A comma-separated list of newsgroup(s) to post your FAQ to as it will
+ appear in the Newsgroups header of the message.
+ This value must be set.
+ =item B<Fup2> = I<newsgroup | poster>
+ A comma-separated list of newsgroup(s) or the special string I<poster>
+ as it will appear in the Followup-To header of the message.
+ This setting is optional.
+ =item B<MID-Format> = I<pattern>
+ A pattern from which the message ID is generated as it will appear in
+ the Message-ID header of the message.
+ You may use the special strings C<%n> for the I<Name> of your project,
+ C<%d> for the date the message is posted, C<%m> for the month and
+ C<%y> for the year, respectively.
+ This value must be set.
+ =item B<Supersede> = I<yes>
+ Add Supersedes header to the message containing the Message-ID header
+ of the last posting.
+ This setting is optional; you should set it to yes or leave it out.
+ =item B<ExtraHeader> = I<additional headers>
+ The contents of I<ExtraHeader> is added verbatim to the headers of
+ your message so you can add custom headers like Approved.
+ This setting is optional.
+ =back
+ =head2 Example configuration file
+     # name of your project
+     Name = 'testpost'
+     
+     # file to post (complete body and pseudo-headers)
+     # ($File.cfg contains data on last posting and last MID)
+     File = 'test.txt'
+     
+     # how often your project should be posted
+     # use (d)ay OR (w)eek OR (m)onth OR (y)ear
+     Posting-frequency = '1d'
+     
+     # time period after which the posting should expire
+     # use (d)ay OR (w)eek OR (m)onth OR (y)ear
+     Expires = '3m'
+     
+     # header "From:"
+     From = 'test@domain.invalid'
+     
+     # header "Subject:"
+     # (may contain "%LM" which will be replaced by the contents of the
+     #  Last-Modified pseudo header).
+     Subject = 'test noreply ignore'
+     
+     # comma-separated list of newsgroup(s) to post to
+     # (header "Newsgroups:")
+     NGs = 'de.test'
+     
+     # header "Followup-To:"
+     Fup2 = 'poster'
+     
+     # Message-ID ("%n" is $Name)
+     MID-Format = '<%n-%d.%m.%y@domain.invalid>'
+     
+     # Supersede last posting?
+     Supersede = yes
+     
+     # extra headers (appended verbatim)
+     # use this for custom headers like "Approved:"
+     ExtraHeader = 'Approved: moderator@domain.invalid
+     X-Header: Some text'
+     
+     # other projects may follow separated with "====="
+     =====
+     
+     Name = 'othertest'
+     File = 'test.txt'
+     Posting-frequency = '2m'
+     From = 'My Name <my.name@domain.invalid>'
+     Subject = 'Test of yapfag <%LM>'
+     NGs = 'de.test,de.alt.test'
+     Fup2 = 'de.test'
+     MID-Format = '<%n-%m.%y@domain.invalid>'
+     Supersede = yes
+ Information about the last post and about how to form message IDs for
+ posts is stored in a file named F<I<project name>.cfg> which will be
+ generated if it does not exist. Each of those status files will
+ contain two lines, the first being the date of the last time the FAQ
+ was posted and the second being the message ID of that incarnation.
+ =head1 OPTIONS
+ =over 3
+ =item B<-h> (help)
+ Print out version and usage information on B<yapfaq> and exit.
+ =item B<-v> (verbose)
+ Print out status information while running to STDOUT.
+ =item B<-p> (post unconditionally)
+ Post (all) FAQs unconditionally ignoring the posting frequency setting.
+ You may want to use this with the B<-f> option (see below).
+ =item B<-d> (dry run)
+ Start B<yapfaq> in simulation mode, i.e. don't post anything and don't
+ update any status information.
+ =item B<-t> I<newsgroup(s) | CONSOLE> (test)
+ Don't post to the newsgroups defined in F<yqpfaq.cfg>, but to the
+ newsgroups given after B<-t> as a comma-separated list or print the
+ FAQs to STDOUT separated by lines of dashes if the special string
+ C<CONSOLE> is given.  This can be used to preview what B<yapfaq> would
+ do without embarassing yourself on Usenet.  The status files are not
+ updated when this option is given.
+ You may want to use this with the B<-f> option (see below).
+ =item B<-f> I<project name>
+ Just deal with one FAQ only.
+ By default B<yapfaq> will work on all FAQs that are defined in
+ F<yapfaq.cfg>, check whether they are due for posting and - if they
+ are - post them. Consequently when the B<-p> option is set all FAQs
+ will be posted unconditionally. That may not be what you want to
+ achieve, so you can limit the operation of B<yapfaq> to the named FAQ
+ only.
+ =back
+ =head1 EXAMPLES
+ Post all FAQs that are due for posting:
+     yapfaq
+ Do a dry run, showing which FAQs would be posted:
+     yapfaq -dv
+ Do a test run and print on STDOUT what the FAQ I<myfaq> would look
+ like when posted, regardless whether it is due for posting or not:
+     yapfaq -pt CONSOLE -f myfaq
+ Do a "real" test run and post the FAQ I<myfaq> to I<de.test>, but only
+ if it is due:
+     yapfaq -t de.test -f myfaq
+ =head1 ENVIRONMENT
+ There are no special environment variables used by B<yapfaq>.
+ =head1 FILES
+ =over 4
+ =item F<yapfaq.pl>
+ The script itself.
+ =item F<yapfaq.cfg>
+ Configuration file for B<yapfaq>.
+ =item F<*.cfg>
+ Status data on FAQs.
+ The status files will be created on successful posting if they don't
+ already exist. The first line of the file will be the date of the last
+ time the FAQ was posted and the second line will be the message ID of
+ the last post of that FAQ.
+ =back
+ =head1 BUGS
+ Many, I'm sure.
+ =head1 SEE ALSO
+ L<http://th-h.de/download/scripts.php> will have the current
+ version of this program.
+ =head1 AUTHOR
+ Thomas Hochstein <thh@inter.net>
+ Original author (until version 0.5b from 2003):
+ Marc Brockschmidt <marc@marcbrockschmidt.de>
+ =head1 COPYRIGHT AND LICENSE
+ Copyright (c) 2003 Marc Brockschmidt <marc@marcbrockschmidt.de>
+ Copyright (c) 2010 Thomas Hochstein <thh@inter.net>
+ This program is free software; you may redistribute it and/or modify it
+ under the same terms as Perl itself.
+ =cut
This page took 0.017552 seconds and 4 git commands to generate.