ron@mlfarm.com (Ronald Florence) (03/17/91)
Submitted-by: Ronald Florence <ron@mlfarm.com> Posting-number: Volume 17, Issue 43 Archive-name: post.icn/part01 Post.icn and reply.icn provide a newsposter and mail reply agent for sites lacking sophisticated news and mail readers and posters. The command-line options may make post easier to use and more functional than the usual Usenet posters. Post may be configured to use inews, or to post to an upstream system via uux or mail. On systems with inews, the newsgroups and distributions are validated. The programs are written in Icon, and may be compiled for Unix, ms-dos, and other operating systems. Usage: post [-n newsgroups] [-s subject] [-d distribution] [-p quote-prefix] [ - | news-article] reply [quote-prefix] < news-article or mail-item With the optional argument of the name of a file containing a news-article, or the option `-' and a news-article via stdin, post generates a follow-up article. The default quote-prefix is ` > '. Both programs can be invoked as filters from news or mail readers: `|reply' will generated a mail reply to the current mail or news item; `|post -' will generate a follow up article. Ronald Florence ron@mlfarm.com -- #! /bin/sh # This is a shell archive, meaning: # 1. Remove everything above the #! /bin/sh line. # 2. Save the resulting text in a file. # 3. Execute the file with /bin/sh (not csh) to create: # Readme # post.icn # reply.icn # options.icn # post.1 # Makefile # This archive created: Thu Mar 14 15:10:36 1991 # By: Ronald Florence (Maple Lawn Farm, Stonington, CT) export PATH; PATH=/bin:/usr/bin:$PATH echo shar: "extracting 'Readme'" '(1478 characters)' if test -f 'Readme' then echo shar: "will not over-write existing file 'Readme'" else sed 's/^X//' << \SHAR_EOF > 'Readme' Xpost - news poster Xcopyright 1991 Ronald Florence Xversion 1.4, 5 March 1991 X XPost.icn and reply.icn provide a newsposter and mail reply agent for Xsites lacking sophisticated news and mail readers and posters. The Xcommand-line options may make post easier to use and more functional Xthan the usual Usenet posters. Post may be configured to use inews, Xor to post to an upstream system via uux or mail. On systems with Xinews, the newsgroups and distributions are validated. The programs Xare written in Icon, and may be compiled for Unix, ms-dos, and other Xoperating systems. X XUsage: X X post [-n newsgroups] [-s subject] [-d distribution] X [-p quote-prefix] [ - | news-article] X X reply [quote-prefix] < news-article or mail-item X XWith the optional argument of the name of a file containing a Xnews-article, or the option `-' and a news-article via stdin, post Xgenerates a follow-up article. The default quote-prefix is ` > '. XBoth programs can be invoked as filters from news or mail readers: X`|reply' will generated a mail reply to the current mail or news Xitem; `|post -' will generate a follow up article. X XConfiguration information is in the man page and the source code. X XPermission is hereby granted for unlimited non-commercial use of Xthese programs, on condition that the copyright notices are left Xintact and any modifications to the source code are noted as such. XNo warranty of any kind is implied or granted for this material. X XRonald Florence Xron@mlfarm.com SHAR_EOF if test 1478 -ne "`wc -c < 'Readme'`" then echo shar: "error transmitting 'Readme'" '(should have been 1478 characters)' fi fi echo shar: "extracting 'post.icn'" '(9015 characters)' if test -f 'post.icn' then echo shar: "will not over-write existing file 'post.icn'" else sed 's/^X//' << \SHAR_EOF > 'post.icn' X############################################################################ X# X# Name: post.icn X# X# Title: News Poster X# X# Author: Ronald Florence (ron@mlfarm.com) X# X# Date: 6 March 1991 X# X# Version: 1.4 X# X############################################################################ X# X# This program posts a news article to Usenet. Given an optional argument X# of the name of a file containing a news article, or an argument of "-" X# and a news article via stdin, post creates a follow-up article, with an X# attribution and quoted text. The newsgroups, subject, distribution, X# follow-up, and quote-prefix can be specified on the command line. X# X# usage: post [options] [article] X# -n newsgroups X# -s subject X# -d distribution X# -f followup-to X# -p quote-prefix (default ` > ') X# - read article from stdin X# X# Configuration options: mode, smarthost, editor, domain, and default X# distribution. If mode is "local", news is posted via inews; "uux", X# via rnews to a smarthost; "mail", via mail (sendnews) to a smarthost. X# X############################################################################ X# X# Link: options X# Bugs: Newsgroup validation assumes the `active' file is sorted. X# Non-Unix sites need hardcoded system information. X# X############################################################################ X Xlink options Xglobal mode, sitename, domain, tz, tmpfile, opts, console, newslib, org X Xprocedure main(arg) X # Site configuration. X mode := "local" X smarthost := "" X editor := "vi" X domain := ".UUCP" X default_distribution := "local" X X if (find("UNIX", &features) & find("pipes", &features)) then { X console := "/dev/tty" X newslib := "/usr/lib/news/" X tmpdir := "/tmp/" X logname := lookup("logname", "pr") X sitename := &host | trim(lookup("uuname -l", "pr")) X (tz := getenv("TZ")) & tz ?:= (tab(many(&letters)), tab(upto(&letters))) X # BSD passwd: `:fullname[,...]:' X # SystemV passwd: `-fullname(' X \logname & every lookup("/etc/passwd") ? { X =(logname) & { X every tab(upto(':')+1) \4 X fullname := (tab(upto('-')+1), tab(upto('(:'))) | tab(upto(',:')) X break X } X } X sigfile := getenv("HOME") || "/.signature" X } X # Non-Unix system configuration: X else { X console := "CON" # ms-dos default X newslib := "" X tmpdir := "" X logname := &null X tz := &null # hours off UT (EST = 5) X fullname := &null X sigfile := &null X sitename := getenv("HOST") | &host X } X X (\logname & \sitename & \tz & (mode == "local" | *smarthost > 0)) | X stop("post: missing system information") X X opts := options(arg, "n:s:d:f:p:h?") X \opts["h"] | \opts["?"] | arg[1] == "?" & { X write("usage: post [options] [article]") X write("\t-n newsgroups") X write("\t-s subject") X write("\t-d distribution") X write("\t-f followup-to") X write("\t-p quote-prefix (default ` > ')") X stop("\t- read article from stdin") X } X org := getenv("ORGANIZATION") | lookup(newslib || "organization") X article := open(tmpfile := tempname(tmpdir), "w") | X stop("post: cannot write temp file") X write(article, "Path: ", sitename, "!", logname) X writes(article, "From: ", logname, "@", sitename, domain) X \fullname & writes(article, " (", fullname, ")") X write(article) X # For a follow-up article, X # reply_headers() does the work. X if \arg[1] then { X inf := (arg[1] == "-" & &input) | X open(arg[1]) | (remove(tmpfile) & stop("post: cannot read " || arg[1])) X reply_headers(inf, article) X every write(article, \opts["p"] | " > ", !inf) X close(inf) X } X X else { X write(article, "Newsgroups: ", X validate(\opts["n"] | query("Newsgroups: "), "active")) X write(article, "Subject: ", \opts["s"] | query("Subject: ")) X write(article, "Distribution: ", X validate(\opts["d"] | query("Distribution: ", default_distribution), X "distributions")) X every write(article, req_headers()) X write(article, "\n") X } X close(article) X edstr := (getenv("EDITOR") | editor) || " " || tmpfile || " < " || console X system(edstr) X upto('nN', query("Are you sure you want to post this to Usenet y/n? ")) & { X if upto('yY', query("Save your draft article y/n? ")) then X stop("Your article is saved in ", tmpfile) X else { X remove(tmpfile) X stop("Posting aborted.") X } X } X # Try to append the .signature. X \sigfile & { X article := open(tmpfile, "a") X write(article, "--") X every write(article, lookup(sigfile)) X } X # For inews, we supply the headers. X # For rnews, don't force an immediate poll. X case mode of { X "local": mode := newslib || "inews -h" X "uux" : mode ||:= " - -r " || smarthost || "!rnews" X "mail" : mode ||:= " " || smarthost || "!rnews" X default: remove(tmpfile) & stop("post: bogus mode") X } X # Sendnews protocol requires an X # initial `N' on every line. X match("mail", mode) & { X outf := open(tmp2 := tempname(tmpdir), "w") X every write(outf, "N", lookup(tmpfile)) X remove(tmpfile) X rename(tmp2, tmpfile) X } X mode ||:= " < " || tmpfile X (system(mode) = 0) & write("Article posted!") X remove(tmpfile) Xend X X # Case-insensitive matches on the headers. X # Reply-to and Followup-To appear later X # than From and Newsgroups, so they take X # precedence. Query followup-to `poster'. Xprocedure reply_headers(infile, art) X X every !infile ? { X tab(match("from: " | "reply-to: ", map(&subject))) & { X if find("<") then { X fullname := (trim(tab(upto('<'))) ~== "") X address := (move(1), tab(find(">"))) X } X else { X address := trim(tab(upto('(') | 0)) X fullname := (move(1), tab(find(")"))) X } X quoter := (\fullname | address) X } X tab(match("date: ", map(&subject))) & date := tab(0) X tab(match("message-id: ", map(&subject))) & id := tab(0) X tab(match("subject: ", map(&subject))) & subject := tab(0) X tab(match("distribution: ", map(&subject))) & distribution := tab(0) X tab(match("newsgroups: " | "followup-to: ", map(&subject))) & X group := tab(0) X tab(match("references: ", map(&subject))) & refs := tab(0) X (\quoter & *&subject = 0) & { X find("poster", group) & { X write(quoter, " has requested followups by email.") X upto('yY', query("Do you want to abort this posting y/n? ")) & { X remove(tmpfile) X stop("Posting aborted.") X } X group := &null X } X write(art, "Newsgroups: ", \group | X validate(\opts["n"] | query("Newsgroups: "), "active")) X write(art, "Subject: ", \opts["s"] | \subject | query("Subject: ")) X write(art, "Distribution: ", \distribution | X validate(\opts["d"] | query("Distribution: ", default_distribution), X "distributions")) X write(art, "References: ", (\refs ||:= " ") | "", id) X every write(art, req_headers()) X write(art, "In-reply-to: ", quoter, "'s message of ", date) X write(art, "\nIn ", id, ", ", quoter, " writes:\n") X return X } X } Xend X X # Generate a unique message-id, sort X # &dateline into RFC822 format, and X # provide a timezone in lieu of UT. Xprocedure req_headers() X X uniq := "<" X &date || &clock ? while tab(upto(&digits)) do uniq ||:= tab(many(&digits)) X uniq ||:= "@" || sitename || domain || ">" X &dateline ? { X month := left((tab(find(" ")+1), tab(many(&letters))), 3) || " " X date := (tab(upto(&digits)), tab(many(&digits))) || " " || month X date ||:= (tab(upto(&digits)), right(tab(many(&digits)), 2)) X } X zone := ((tz > 0 & " -") | " +") || left(right(abs(tz), 2, "0"), 4, "0") X suspend "Message-ID: " || uniq X suspend "Date: " || date || " " || &clock || zone X \org & suspend "Organization: " || org X \opts["f"] & return "Followup-To: " || ((opts["f"] == "poster") | X validate(opts["f"], "active")) Xend X X X # Richard Goerwitz's generator. Xprocedure tempname(dir) X X every temp_name := dir || "article." || right(1 to 999,3,"0") do { X close(open(temp_name)) & next X suspend \temp_name X } Xend X X X # Read from a file or command. Xprocedure lookup(what, pipe) X X inf := open(what, (upto('p', \pipe) & "pr") | "r") | return fail X suspend !inf X close(inf) Xend X X # Query opens stdin because the system X # call to the editor redirects input. X # The optional parameter is a default. Xprocedure query(prompt, def) X static stdin X X initial stdin := open(console) X writes(prompt) X ans := read(stdin) X return (*ans = 0 & \def) | ans Xend X X # Build a sorted list. When an element X # is found, pop it and search for the next. X # Assumes the file is also sorted. Xprocedure validate(what, where) X X mode ~== "local" & return what X valid := &letters ++ '.-' ++ &digits X stuff := [] X what ? while tab(upto(valid)) do put(stuff,tab(many(valid))) X sf := open(newslib || where) | { X remove(tmpfile) X stop("post: cannot open ", newslib || where) X } X stuff := sort(stuff) X a := pop(stuff) X every !sf ? match(a) & (a := pop(stuff)) | return what X remove(tmpfile) X stop("`", a, "' is not in ", newslib || where) Xend SHAR_EOF if test 9015 -ne "`wc -c < 'post.icn'`" then echo shar: "error transmitting 'post.icn'" '(should have been 9015 characters)' fi fi echo shar: "extracting 'reply.icn'" '(3286 characters)' if test -f 'reply.icn' then echo shar: "will not over-write existing file 'reply.icn'" else sed 's/^X//' << \SHAR_EOF > 'reply.icn' X############################################################################ X# X# Name: reply.icn X# X# Title: Replies to news-articles or mail X# X# Author: Ronald Florence (ron@mlfarm.com) X# X# Date: March 8, 1991 X# X# Version: 1.4 X# X############################################################################ X# X# This program creates the appropriate headers and attribution, X# quotes a news or mail message, and uses system() calls to put the X# user in an editor and then to mail the reply. The default prefix X# for quoted text is ` > '. X# X# usage: reply [prefix] < news-article or mail-item X# X# If a smarthost is defined, Internet addresses are converted to bang X# paths (name@site.domain -> site.domain!name). The mail is routed X# to a domained smarthost as address@smarthost.domain, otherwise to X# smarthost!address. X# X# The default editor can be overridden with the EDITOR environment variable. X# X############################################################################ X Xprocedure main(arg) X X smarthost := "" X editor := "vi" X X if find("UNIX", &features) then { X console := "/dev/tty" X tmpdir := "/tmp/" X } X else if find("MS-DOS", &features) then { X console := "CON" X tmpdir := "" X } X (\console & \tmpdir) | stop("reply: missing system information") X X every tmpfile := tmpdir || "reply." || right(1 to 999,3,"0") do X close(open(tmpfile)) | break X reply := open(tmpfile, "w") | stop("reply: cannot write temp file") X X # Case-insensitive matches for headers. X every !&input ? { X tab(match("from: " | "reply-to: ", map(&subject))) & { X if find("<") then { X fullname := (trim(tab(upto('<'))) ~== "") X address := (move(1), tab(find(">"))) X } X else { X address := trim(tab(upto('(') | 0)) X fullname := (move(1), tab(find(")"))) X } X quoter := (\fullname | address) X } X tab(match("date: ", map(&subject))) & date := tab(0) X tab(match("message-id: ", map(&subject))) & id := tab(0) X match("subject: ", map(&subject)) & subject := tab(0) X match("newsgroups: ", map(&subject)) & newsgroup := tab(upto(',') | 0) X match("references: ", map(&subject)) & refs := tab(0) X (\address & *&subject = 0) & { X \subject & write(reply, subject) X \newsgroup & write(reply, newsgroup) X \refs & write(reply, refs, " ", id) X write(reply, "In-reply-to: ", quoter, "'s message of ", date); X write(reply, "\nIn ", id, ", ", quoter, " writes:\n") X break X } X } X X every write(reply, \arg[1] | " > ", !&input) X edstr := (getenv("EDITOR") | editor) || " " || tmpfile || " < " || console X system(edstr) X stdin := open(console) X writes("Send y/n? ") X upto('nN', read(stdin)) & { X writes("Save your draft reply y/n? ") X if upto('yY', read(stdin)) then X stop("Your draft reply is saved in ", tmpfile) X else { X remove(tmpfile) X stop("Reply aborted.") X } X } X X *smarthost > 0 & { X find("@", address) & address ? { X name := tab(upto('@')) X address := (move(1), tab(upto(' ') | 0)) || "!" || name X } X if find(".", smarthost) then address ||:= "@" || smarthost X else address := smarthost || "!" || address X } X mailstr := "mail " || address || " < " || tmpfile X system(mailstr) X write("Reply sent to " || address) X remove(tmpfile) Xend SHAR_EOF if test 3286 -ne "`wc -c < 'reply.icn'`" then echo shar: "error transmitting 'reply.icn'" '(should have been 3286 characters)' fi fi echo shar: "extracting 'options.icn'" '(2560 characters)' if test -f 'options.icn' then echo shar: "will not over-write existing file 'options.icn'" else sed 's/^X//' << \SHAR_EOF > 'options.icn' X############################################################################ X# X# Name: options.icn X# X# Title: Get command-line options X# X# Authors: Robert J. Alexander and Gregg M. Townsend X# X# Date: November 9, 1989 X# X############################################################################ X# X# options(arg,optstring) -- Get command line options. X# X# This procedure analyzes the -options on the command line X# invoking an Icon program. The inputs are: X# X# arg the argument list as passed to the main procedure. X# X# optstring a string of allowable option letters. If a X# letter is followed by ":" the corresponding X# option is assumed to be followed by a string of X# data, optionally separated from the letter by X# space. If instead of ":" the letter is followed X# by a "+", the parameter will converted to an X# integer; if a ".", converted to a real. If opt- X# string is omitted any letter is assumed to be X# valid and require no data. X# X# It returns a table containing the options that were specified. X# The keys are the specified option letters. The assigned values are X# the data words following the options, if any, or 1 if the option X# has no data. The table's default value is &null. X# X# If an error is detected, stop() is called with an appropriate X# error message. X# X# Options may be freely interspersed with non-option arguments. X# An argument of "-" is treated as a non-option. The special argument X# "--" terminates option processing. Non-option arguments are returned X# in the original argument list for interpretation by the caller. X# X############################################################################ X Xprocedure options(arg,optstring) X local x,i,c,otab,flist,o,p X /optstring := string(&letters) X otab := table() X flist := [] X while x := get(arg) do X x ? { X if ="-" & not pos(0) then { X if ="-" & pos(0) then break X while c := move(1) do X if i := find(c,optstring) + 1 then otab[c] := X if any(':+.',o := optstring[i]) then { X p := "" ~== tab(0) | get(arg) | X stop("No parameter following -",c) X case o of { X ":": p X "+": integer(p) | stop("-",c," needs numeric parameter") X ".": real(p) | stop("-",c," needs numeric parameter") X } X } X else 1 X else stop("Unrecognized option: -",c) X } X else put(flist,x) X } X while push(arg,pull(flist)) X return otab Xend SHAR_EOF if test 2560 -ne "`wc -c < 'options.icn'`" then echo shar: "error transmitting 'options.icn'" '(should have been 2560 characters)' fi fi echo shar: "extracting 'post.1'" '(2560 characters)' if test -f 'post.1' then echo shar: "will not over-write existing file 'post.1'" else sed 's/^X//' << \SHAR_EOF > 'post.1' X.\" post.1 version 1.4 X.\" copyright 1991 Ronald Florence (ron@mlfarm.com, 6 March 1991) X.TH POST LOCAL X.SH NAME Xpost \- news poster X.SH SYNOPSIS X.B post X[ X.BI \-n\ newsgroups X] [ X.BI \-s\ subject X] [ X.BI \-d\ distribution X] [ X.BI \-f\ followup-to X] [ X.BI \-p\ quote-prefix X] [ X.B \- X| X.I news-article X] X.SH DESCRIPTION X.I Post Xposts a news article to Usenet via inews, uux, or mail. Given an Xoptional argument of the name of a file containing a news article, or Xan argument of `\-' and a news article via stdin, X.I post Xcreates a follow-up article, with an attribution and quoted text. X.I Post Xcan be invoked as a filter from a newsreader: X.RB ` "|post \-" ' Xwould create a followup article to the current article in the Xnewsreader. X.PP XThe newsgroups, subject, distribution, follow-up, and/or the Xquote-prefix (the default is ` > ') can be specified on the command Xline. On systems using X.IR inews , Xthe newsgroups and distribution are validated in the appropriate news Xsystem files. X.SH CONFIGURATION XBefore compiling X.IR post , Xthe X.IR mode " and " smarthost Xidentifiers should be configured to suit your system. X.I Mode Xshould be `local' if you have inews, `mail' if you use the sendnews Xprotocol to post articles via mail to a smarthost, or `uux' if you Xpost articles to a smarthost via rnews. If X.I mode Xis `mail' or `uux,' X.I smarthost Xshould be set to the uucp name of your news feed. X.PP XChange X.IR domain , " editor" " and/or " default_distribution Xto suit your system. The default distribution is assigned if the user Xanswers <return> to the X.I Distribution: Xprompt. X.PP XFor non-Unix\u\s-3TM\s0\d systems, the system information X.RI ( "console, newslib, tmpdir, logname, tz, fullname, sigfile" ) Xshould be hard-coded. You may need iconx to invoke X.I post Xif your system does not support direct execution. X.PP XCompile with the distributed Makefile, and install X.I post Xin the appropriate binary directory. X.SH ENVIRONMENT XThe environment variable X.SM EDITOR Xoverrides the default editor. X.SM ORGANIZATION Xoverrides the file X.IR newslib /organization Xto specify an optional Organization header. On non-Unix\u\s-3TM\s0\d Xsystems, the environment variable X.SM HOST Xmay be used to override the Icon keyword X.I &host Xas the sitename. X.SH BUGS XThe code to validate newsgroups assumes the file X.IR newslib /active Xis sorted. X.SH AUTHOR XRonald Florence (ron\s-2@\s0mlfarm.com). The code to generate a Xtemporary file name is from Richard Goerwitz X(goer\s-2@\s0sophist.uchicago.edu). Options.icn is from the Icon XProgram Library. SHAR_EOF if test 2560 -ne "`wc -c < 'post.1'`" then echo shar: "error transmitting 'post.1'" '(should have been 2560 characters)' fi fi echo shar: "extracting 'Makefile'" '(185 characters)' if test -f 'Makefile' then echo shar: "will not over-write existing file 'Makefile'" else sed 's/^X//' << \SHAR_EOF > 'Makefile' X# Makefile for Icon X X.SUFFIXES: .icn X X.icn: X icont $< X Xall: post reply X Xoptions.u1: options.icn X icont -c options.icn X Xpost: post.icn options.u1 X icont post.icn X Xclean: X rm -f *.u[12] SHAR_EOF if test 185 -ne "`wc -c < 'Makefile'`" then echo shar: "error transmitting 'Makefile'" '(should have been 185 characters)' fi fi exit 0 # End of shell archive -- Ronald Florence ron@mlfarm.com exit 0 # Just in case... -- Kent Landfield INTERNET: kent@sparky.IMD.Sterling.COM Sterling Software, IMD UUCP: uunet!sparky!kent Phone: (402) 291-8300 FAX: (402) 291-4362 Please send comp.sources.misc-related mail to kent@uunet.uu.net.