From 67784f1f3e5992d8f43ca5006f0e6d8fb19949ea Mon Sep 17 00:00:00 2001 From: simon Date: Wed, 23 Nov 2011 19:48:31 +0000 Subject: [PATCH] Considerable redesign to incorporate UI enhancements from chiark users. Now articles may be specified as a message-id, as a group and an article number separated by whitespace, or as a group and an article number separated by a colon. Also, with no arguments, nntpid will read from standard input, parse each line into an article description, and display that article. git-svn-id: svn://svn.tartarus.org/sgt/utils@9341 cda61777-01e9-0310-a592-d414129be87e --- nntpid/nntpid | 200 ++++++++++++++++++++++++++++++++++++++++-------------- nntpid/nntpid.but | 55 +++++++++++---- 2 files changed, 189 insertions(+), 66 deletions(-) diff --git a/nntpid/nntpid b/nntpid/nntpid index f1544bb..ffdde0d 100755 --- a/nntpid/nntpid +++ b/nntpid/nntpid @@ -16,17 +16,22 @@ # a 480 response from the news server; if your news server isn't paranoid # then the script will never need to look at NNTPAUTH. -# Copyright 2000,2004 Simon Tatham. All rights reserved. +# Copyright 2000,2004,2011 Simon Tatham. All rights reserved. require 5.002; use Socket; use FileHandle; $usage = - "usage: nntpid [ -v ] [ -d ] \n" . - " or: nntpid [ -v ] [ -d ] \n" . - " or: nntpid [ -v ] -a \n" . - "where: -v verbose (print interaction with news server)\n" . + "usage: nntpid [ -v ] [ -d ]
[
...] display articles\n" . + " or: nntpid [ -v ] [ -d ] display articles read from standard input\n" . + " or: nntpid [ -v ] -a dump a newsgroup in mbox format\n" . + "where:
a news article specified in one of several ways:\n" . + " - Message-Id in angle brackets\n" . + " - bare Message-Id without angle brackets\n" . + " - newsgroup and article number in separate words\n" . + " - newsgroup and article number separated by :\n" . + " -v verbose (print interaction with news server)\n" . " -d direct output (don't consider using PAGER)\n" . " -a dump all articles in group to stdout as mbox\n" . " also: nntpid --version report version number\n" . @@ -34,7 +39,7 @@ $usage = " nntpid --licence display (MIT) licence text\n"; $licence = - "nntpid is copyright 2000,2004 Simon Tatham.\n" . + "nntpid is copyright 2000,2004,2011 Simon Tatham.\n" . "\n" . "Permission is hereby granted, free of charge, to any person\n" . "obtaining a copy of this software and associated documentation files\n" . @@ -58,13 +63,13 @@ $licence = $pager = 1; $verbose = 0; -$all = 0; +$mode = 'list'; while ($ARGV[0] =~ /^-(.+)$/) { shift @ARGV; $verbose = 1, next if $1 eq "v"; $pager = 0, next if $1 eq "d"; - $all = 1, next if $1 eq "a"; + $mode = 'all', next if $1 eq "a"; if ($1 eq "-help") { print STDERR $usage; exit 0; @@ -81,19 +86,37 @@ while ($ARGV[0] =~ /^-(.+)$/) { } } -die $usage if !defined $ARGV[0]; - -if ($all) { - $group = $ARGV[0]; -} elsif (defined $ARGV[1]) { +if ($mode eq 'all') { + # -a uses completely different command-line semantics from the + # normal ones.. + die "nntpid: -a expected exactly one argument\n" if @ARGV != 1; $group = $ARGV[0]; - $mid = $ARGV[1]; +} elsif (!@ARGV) { + # We will read article ids from standard input once we've connected + # to the NNTP server. + $mode = 'stdin'; } else { - $group = "misc.misc"; - $mid = $ARGV[0]; - $mid =~ s/^$//; - $mid = "<$mid>"; + @list = (); + while (defined ($arg = shift @ARGV)) { + # See if this argument makes sense on its own. + ($group, $mid) = &parsearticle($arg); + if (defined $mid) { + push @list, $arg; + } else { + # If it doesn't, try concatenating it with a space to the next + # argument (so you can provide a group and article number in two + # successive command-line arguments). + $args = $arg . " " . $ARGV[0]; + ($group, $mid) = &parsearticle($args); + if (defined $mid) { + push @list, $args; + shift @ARGV; # and eat the second argument + } else { + # If all else fails, die in panic. + die "nntpid: argument '$arg': unable to parse\n"; + } + } + } } $ns=$ENV{'NNTPSERVER'}; @@ -106,47 +129,96 @@ $ns = inet_aton($ns); $proto = getprotobyname("tcp"); $paddr = sockaddr_in($port, $ns); -socket(S,PF_INET,SOCK_STREAM,$proto) or die "socket: $!"; -connect(S,$paddr) or die "connect: $!"; +&connect; +if ($mode eq 'all') { + # Write out the entire contents of a newsgroup in mbox format. + $numbers = &docmd("GROUP $group"); + @numbers = split / /, $numbers; + $fatal = 0; # ignore failure to retrieve any given article + for ($mid = $numbers[1]; $mid <= $numbers[2]; $mid++) { + $art = &getart("$group:$mid"); + $art =~ s/\n(>*From )/\n>$1/gs; + print "From nntpid ".(localtime)."\n".$art."\n"; + } +} elsif ($mode eq 'stdin') { + while (<>) { + chomp; + s/^\s+//; s/\s+$//; # trim whitespace + &displayarticle($_); + } +} elsif ($mode eq 'list') { + for $item (@list) { + &displayarticle($item); + } +} + +sub parsearticle { + # Article identifiers used as input to this program can be in a + # variety of formats. This function untangles one into a standard + # format, which is either (undef, message-id) or (group, article + # number). In case of parse failure, it returns (undef, undef). + my $art = shift @_; + if ($art =~ /^(.*<)?([^<>]*\@[^<>]*)(>.*)?$/) { + # Anything with an @ sign is treated as a Message-ID. We trim + # angle brackets and anything outside them. + return (undef, $2); + } elsif ($art =~ /^(\S+)(\s+|:)(\d+)$/) { + # A group name and article number separated by whitespace or a + # colon. + return ($1, $3); + } else { + # Unable to parse. + return (undef, undef); + } +} -S->autoflush(1); +sub displayarticle { + my $mid = shift @_; -$fatal = 1; # most errors need to be fatal + &connect; -&getline; -$code =~ /^2\d\d/ or die "no initial greeting from server\n"; + my $art = &getart($mid); -&docmd("MODE READER"); -# some servers require a GROUP before an ARTICLE command -$numbers = &docmd("GROUP $group"); -if ($all) { - @numbers = split / /, $numbers; - $fatal = 0; # ignore failure to retrieve any given article - for ($mid = $numbers[1]; $mid <= $numbers[2]; $mid++) { - $art = &getart($mid); - $art =~ s/\n(>*From )/\n>$1/gs; - print "From nntpid ".(localtime)."\n".$art."\n"; - } -} else { - $art = &getart($mid); - &docmd("QUIT"); - close S; - - if ($pager and -t STDOUT) { - $pagername = $ENV{"PAGER"}; - $pagername = "more" unless defined $pagername; - open PAGER, "| $pagername"; - print PAGER $art; - close PAGER; - } else { - print $art; - } + if ($pager and -t STDOUT) { + # Close the NNTP connection before invoking the pager, in case the + # user spends so long looking at the article that the server times + # us out. + &disconnect; + + $pagername = $ENV{"PAGER"}; + $pagername = "more" unless defined $pagername; + open PAGER, "| $pagername"; + print PAGER $art; + close PAGER; + } else { + print $art; + } } sub getart { - my ($mid) = @_; - $ret = &docmd("ARTICLE $mid"); + my $art = shift @_; + my $group; + my $mid; + + ($group, $mid) = &parsearticle($art); + if (!defined $mid) { + warn "unable to parse '$art'\n"; + return undef; + } elsif (defined $group) { + # This is a (group, article number) pair. + &docmd("GROUP $group"); + $ret = &docmd("ARTICLE $mid"); + } else { + # This is a Message-Id. Some NNTP servers will insist on having + # seen a GROUP command before 'ARTICLE ', + # so ensure we've sent one. + &docmd("GROUP misc.misc") unless $in_a_group; + $ret = &docmd("ARTICLE <$mid>"); + } + return undef if !defined $ret; + $in_a_group = 1; + $art = ""; while (1) { &getline; @@ -172,6 +244,30 @@ sub getline { return substr($_,4); } +sub connect { + return if $connected; + socket(S,PF_INET,SOCK_STREAM,$proto) or die "socket: $!"; + connect(S,$paddr) or die "connect: $!"; + + S->autoflush(1); + + $fatal = 1; # most errors need to be fatal + + &getline; + $code =~ /^2\d\d/ or die "no initial greeting from server\n"; + + &docmd("MODE READER"); + + $connected = 1; + $in_a_group = 0; +} + +sub disconnect { + &docmd("QUIT"); + close S; + $connected = 0; +} + sub docmd { my ($cmd) = @_; # We go at most twice round the following loop. If the first attempt diff --git a/nntpid/nntpid.but b/nntpid/nntpid.but index 934068d..ee6f026 100644 --- a/nntpid/nntpid.but +++ b/nntpid/nntpid.but @@ -8,17 +8,17 @@ \U SYNOPSIS -\c nntpid [ -v ] [ -d ] message-id -\e bbbbbb bb bb iiiiiiiiii -\c nntpid [ -v ] [ -d ] newsgroup-name article-number -\e bbbbbb bb bb iiiiiiiiiiiiii iiiiiiiiiiiiii +\c nntpid [ -v ] [ -d ] article [ article... ] +\e bbbbbb bb bb iiiiiii iiiiiii +\c nntpid [ -v ] [ -d ] +\e bbbbbb bb bb \c nntpid [ -v ] -a newsgroup-name \e bbbbbb bb bb iiiiiiiiiiiiii \U DESCRIPTION \cw{nntpid} makes a connection to a news server, retrieves one or -more articles, and displays it. +more articles, and displays them. You can specify the article you want by either: @@ -40,9 +40,9 @@ that cause unexpected behaviour in your terminal. If \cw{nntpid} detects that its standard output is not a terminal, however, it will bypass the pager and just write out the article directly. -There is a third mode of operation, enabled by the \cw{-a} option, -in which \cw{nntpid} retrieves \e{all} available articles in the -group and writes them to standard output in \cw{mbox} format. +There is an alternative mode of operation, enabled by the \cw{-a} +option, in which \cw{nntpid} retrieves \e{all} available articles in +the group and writes them to standard output in \cw{mbox} format. The location of the news server is obtained by reading the environment variable \cw{NNTPSERVER}, or failing that the file @@ -50,13 +50,40 @@ environment variable \cw{NNTPSERVER}, or failing that the file \U ARGUMENTS -If you specify one argument, \cw{nntpid} assumes it is a Message-ID. -The angle brackets that usually delimit Message-IDs are optional; -\cw{nntpid} will strip them off if it sees them, and will not -complain if it does not. +\cw{nntpid} will attempt to interpret its argument list as specifying +a series of news articles, as follows: -If you specify two arguments, \cw{nntpid} will interpret the first -as a newsgroup name, and the second as an article number. +\b An argument containing an @ sign will be parsed as a Message-ID. +The angle brackets that usually delimit Message-IDs are optional; +\cw{nntpid} will strip them off if it sees them, and will not complain +if it does not. If the angle brackets are present, anything outside +them will also be discarded. + +\b Otherwise, an argument containing whitespace or a colon will be +parsed as a group name and an article number. + +\b Otherwise, two successive arguments will be treated as a group name +and an article number. + +For example, the following invocations should all behave identically. +(Single quotes are intended to represent POSIX shell quoting, not part +of the command line as it reaches \cw{nntpid}.) + +\c $ nntpid '' misc.test 1234 +\e bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb +\c $ nntpid 'foo.bar@baz.quux' misc.test:1234 +\e bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb +\c $ nntpid 'wibble blah' 'misc.test 1234' +\e bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb + +If \cw{nntpid} is given no arguments at all, it will read from +standard input. Every line it reads will be interpreted as described +above, except that whitespace will also be trimmed from the start and +end of the line first. + +If you provide the \cw{-a} option (see below), none of the above +applies. Instead, \cw{nntpid} will expect exactly one command-line +argument, which it will treat as a newsgroup name. \U OPTIONS -- 2.11.0