PeanutShellImpl.mesa
Copyright Ó 1983, 1984, 1989, 1990, 1991, 1992 Xerox Corporation. All rights reserved.
The Great Wheel of Peanut Incarnation:
Written by Alan Turing, February 1983
Last edited by Paxton on April 4, 1983 9:10 am
Last Edited by: Pausch, July 18, 1983 1:37 pm
Last Edited by: Gasbarro June 24, 1986 1:16:19 pm PDT
Willie-Sue, December 13, 1989 4:58:24 pm PST
Michael Plass, June 1, 1988 5:03:29 pm PDT
Last changed by Pavel on March 8, 1990 3:20 pm PST
Doug Wyatt, September 13, 1990 1:42 pm PDT
Jules Bloomenthal April 23, 1993 12:20 pm PDT
DIRECTORY BasicTime, CedarProcess, Commander, CommanderOps, Convert, FS, Icons, IO, Menus, PeanutProfile, PeanutWindow, PFS, PFSNames, PFSPrefixMap, Process, RefText, Rope, TextNode, ThisMachine, TiogaAccess, TiogaAccessViewers, TiogaMenuOps, TiogaOps, UserProfile, ViewerClasses, ViewerOps, ViewerTools;
PeanutShellImpl: CEDAR MONITOR
IMPORTS CedarProcess, Commander, CommanderOps, Convert, FS, IO, Menus, PeanutProfile, PeanutWindow, PFS, PFSNames, PFSPrefixMap, Process, RefText, Rope, TextNode, ThisMachine, TiogaAccess, TiogaAccessViewers, TiogaMenuOps, TiogaOps, UserProfile, ViewerOps, ViewerTools
~ BEGIN
Types and Globals
ForkableProc: TYPE ~ CedarProcess.ForkableProc;
Writer:  TYPE ~ TiogaAccess.Writer;
Reader:  TYPE ~ TiogaAccess.Reader;
Column:  TYPE ~ ViewerClasses.Column;
Viewer:  TYPE ~ ViewerClasses.Viewer;
ROPE:   TYPE ~ Rope.ROPE;
dir:   ROPE ¬ Rope.Concat[Prefix[PeanutProfile.workingDirectory], "/"];
cedarUser: ROPE ¬ NARROW[CommanderOps.GetProp[NIL, $USER]];
user:   ROPE ¬ UserProfile.Token["Peanut.user", cedarUser];
host:   ROPE ¬ UserProfile.Token["Peanut.mailMachine", ThisMachine.Name[]];
local:   BOOL ¬ Rope.Equal[host, ThisMachine.Name[]];
spool:   ROPE ¬ Rope.Cat[Prefix[UserProfile.Token[
      "Peanut.spoolDirectory", "/var/spool"]], "/", user];
mailFiles:  LIST OF ROPE; -- short names ("Active" not "Active.mail")
Support
UnixCmd: PROC [cmd: ROPE, fork: BOOL ¬ FALSE] RETURNS [reply: ROPE ¬ NIL] ~ {
IO.PutF1[debug, "command = %g\n", IO.rope[cmd]];
cmd ¬ Rope.Concat["sh1 ", cmd];
IF fork
THEN [] ¬ CedarProcess.Fork[UnixFork, cmd]
ELSE reply ¬ CommanderOps.DoCommandRope[cmd,, NIL].out;
};
UnixFork: ForkableProc ~ {[] ¬ CommanderOps.DoCommandRope[NARROW[data],, NIL]};
MakeRsh: PROC [cmd: ROPE] RETURNS [rshCmd: ROPE] ~ {
rshCmd ¬ IF local
THEN Rope.Cat["rsh -l ", user, " -n localhost ", Rope.Cat[" ", "\"", cmd, "\""]]
ELSE Rope.Cat["rsh -l ", user, " ", host, Rope.Cat[" ", "\"", cmd, "\""]];
};
Prefix: PROC [in: ROPE] RETURNS [out: ROPE] ~ {
IF Rope.Fetch[in, Rope.Length[in]-1] = '/ THEN in ¬ Rope.Substr[in, 0, Rope.Length[in]-1];
out ¬ PFS.RopeFromPath[PFSPrefixMap.Lookup[PFS.PathFromRope[in]]];
IF Rope.IsEmpty[out] THEN out ¬ in;
IF Rope.Find[out, "-vux"] = 0 THEN out ¬ Rope.Substr[out, 5];
IF Rope.Find[out, "-ux"] = 0 THEN out ¬ Rope.Substr[out, 4];
};
WriteViewer: PROC [w: Writer, v: Viewer] RETURNS [ok: BOOL] ~ {
IF (ok ¬ v.link = NIL)
THEN TiogaAccessViewers.WriteViewer[w, v]
ELSE PeanutWindow.OutputRope["\nCan't write to split viewer!"];
};
UnixMailFileToTiogaMail: PROC [mailFile: ROPE, w: Writer] RETURNS [nMsgs: INT] ~ {
nMsgs ¬ MailToWriter[FS.StreamOpen[mailFile ! FS.Error => CONTINUE], w];
};
MailToWriter: PROC [mail: IO.STREAM, w: Writer] RETURNS [nMsgs: INT ¬ 0] ~ {
Start: PROC [line, start: ROPE] RETURNS [b: BOOL] ~ {b ¬ Rope.Find[line, start, 0, FALSE] = 0};
Write: PROC [rope: ROPE, header: BOOL ¬ FALSE, format: ATOM ¬ NIL] ~ {
Format is applied to the node that is terminated by tc.endOfNode.
tc: TiogaAccess.TiogaChar ¬ [0, '\000, ALL[FALSE], format, FALSE, FALSE, 0, NIL];
IF header THEN tc.looks['b] ¬ TRUE;
FOR n: INT IN [0..Rope.Length[rope]) DO
tc.char ¬ Rope.Fetch[rope, n];
TiogaAccess.Put[w, tc];
ENDLOOP;
IF header THEN {tc.endOfNode ¬ TRUE; TiogaAccess.Put[w, tc]};
};
BriefDate: PROC [date: ROPE] RETURNS [brief: ROPE ¬ "[no date]"] ~ {
months: ARRAY BasicTime.MonthOfYear OF ROPE ¬
["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec",NIL];
up: BasicTime.Unpacked ¬ Convert.UnpackedTimeFromRope[date];
IF up.month # unspecified THEN brief ¬ Rope.Cat[Convert.RopeFromInt[up.day], "-",
months[up.month], "-", Rope.Substr[IO.PutFR1["%g", IO.int[up.year]], 2, 2]];
};
BriefFrom: PROC [f: ROPE] RETURNS [b: ROPE] ~ {b ¬ Rope.Substr[f,, Rope.SkipTo[f,, "(<"]]};
IF mail = NIL
THEN RETURN[0]
ELSE DO
ENABLE IO.EndOfStream => EXIT;
{
Type: TYPE ~ {date, from, to, cc, subject, replyTo, none};
keys: ARRAY Type OF ROPE ¬ ["Date", "From", "To", "Cc", "Subject", "ReplyTo", NIL];
fields: ARRAY Type OF ROPE ¬ ALL[NIL];
reading: Type ¬ none;
once: BOOL ¬ FALSE;
DO
Check: PROC [type: Type] RETURNS [check: BOOL] ~ {
IF NOT (check ¬ Start[line, Rope.Concat[keys[type], ":"]]) THEN RETURN;
IF reading = type OR fields[type] # NIL THEN RETURN;
reading ¬ type;
fields[reading ¬ type] ¬ Rope.Substr[line, Rope.Length[keys[type]]+2]; -- new
};
NotKey: PROC RETURNS [b: BOOL] ~ {
b ¬ Rope.Find[line, " "] < 1 OR Rope.Fetch[line, Rope.Find[line, " "]-1] # ':;
};
goodKey: BOOL ¬ FALSE;
line: ROPE ¬ IO.GetLineRope[mail];
IF Rope.Equal[line, ""] THEN EXIT; -- blank line presumed end of header
FOR type: Type IN Type DO IF (goodKey ¬ Check[type]) THEN EXIT; ENDLOOP;
IF NOT goodKey AND NotKey[] AND reading # none THEN -- some fields multi-lined
fields[reading] ¬ Rope.Cat[fields[reading], "\n", line];
ENDLOOP;
Write[Rope.Cat[BriefDate[fields[date]], "\t", BriefFrom[fields[from]], "\t", fields[subject]], TRUE, $header];
Write[Rope.Cat["Date: ", fields[date], "\n"]];
Write[Rope.Cat["From: ", fields[from], "\n"]];
Write[Rope.Cat["To: ", fields[to], "\n"]];
IF NOT Rope.IsEmpty[fields[cc]] THEN Write[Rope.Cat["Cc: ", fields[cc], "\n"]];
Write[Rope.Cat["Subject: ", fields[subject], "\n"]];
IF NOT Rope.IsEmpty[fields[replyTo]] THEN
Write[Rope.Cat["Reply-To: ", fields[replyTo], "\n"]];
DO
ENABLE IO.EndOfStream => EXIT;
index: INT ¬ IO.GetIndex[mail];
line: ROPE ¬ IO.GetLineRope[mail];
IF Start[line, "From "] THEN { -- unexpected new message header?
index2: INT ¬ IO.GetIndex[mail];
line2: ROPE ¬ IO.GetLineRope[mail];
IF Start[line2, "Return-Path: "] OR Start[line2, "Received"]
THEN {IO.SetIndex[mail, index]; EXIT} -- yes, new message
ELSE IO.SetIndex[mail, index2];
};
Write[IF once OR Rope.IsEmpty[line] OR Rope.Fetch[line] # '\n
THEN Rope.Concat["\n", line] ELSE line];
once ¬ TRUE;
ENDLOOP;
TiogaAccess.Nest[w, 1];
Write[NIL, TRUE];
TiogaAccess.Nest[w, -1];
nMsgs ¬ nMsgs+1;
};
ENDLOOP;
};
Save Mail
ByeButton: Menus.ClickProc ~ {
PeanutWindow.SaveAllMailFiles[mailFiles, mouseButton = $blue];
};
Get Mail
MailCheck: ForkableProc ~ {
WHILE PeanutWindow.peanutParent # NIL AND NOT PeanutWindow.peanutParent.destroyed DO
cmd: ROPE ¬ IF local
THEN Rope.Concat["ls ", spool]
ELSE MakeRsh[Rope.Concat["ls ", spool]];
reply: ROPE ¬ UnixCmd[cmd];
found: BOOL ¬ Rope.Find[reply, "not found"] = -1 AND Rope.Find[reply, user] # -1;
PeanutWindow.SetNewMail[found];
IF found AND PeanutProfile.automaticNewMail THEN GetMail[left];
Process.PauseMsec[15000];
ENDLOOP;
};
GetButton: Menus.ClickProc ~ {GetMail[IF mouseButton = red THEN left ELSE right]};
GetMail: PROC [column: Column] ~ {
nMsgs: INT; 
w: Writer ¬ TiogaAccess.Create[];
ok: BOOL ¬ IF local
THEN UnixCmd[Rope.Cat["mv ", spool, " ", dir, "mbox"]] = NIL
ELSE UnixCmd[MakeRsh[Rope.Cat["mv ", spool, " ", "mbox"]]] = NIL AND
 UnixCmd[Rope.Cat["rcp ", user, "@", host, Rope.Cat[":mbox ", dir, "mbox"]]] = NIL;
IF NOT ok OR (nMsgs ¬ UnixMailFileToTiogaMail[Rope.Concat[dir, "mbox"], w]) = 0
THEN PeanutWindow.OutputRope["\nNo new messages"]
ELSE {
reply: ROPE;
v: Viewer ¬ PeanutWindow.GetMailViewer["Active"];
s: ViewerTools.SelPos ¬ NEW[ViewerTools.SelPosRec ¬ [LAST[INT], 0, FALSE, after]];
PeanutWindow.OutputRope[IO.PutFR1["\n%g new message(s)", IO.int[nMsgs]]];
IF v.column # column THEN ViewerOps.ChangeColumn[v, column];
ViewerOps.OpenIcon[v];
TiogaMenuOps.AllLevels[v];
[] ¬ v.class.scroll[v, thumb, 100];
ViewerTools.SetSelection[v, s];
TiogaOps.CaretBefore[];
TiogaOps.Break[];
THROUGH [1..TextNode.Level[TiogaOps.GetCaret[].node]) DO -- make this top level
TiogaOps.UnNest[];
ENDLOOP;
TiogaOps.SetFormat["header"];
TiogaAccessViewers.WriteSelection[w];
s ¬ ViewerTools.GetSelection[v];
s.length ¬ 0;
ViewerTools.SetSelection[v, s];
reply ¬ UnixCmd[
IF local
THEN Rope.Cat["mv ", dir, "mbox ", dir, ".mbox"]
ELSE MakeRsh["mv mbox .mbox"],
TRUE];
IF reply # NIL THEN PeanutWindow.OutputRope[Rope.Concat["\nerror: ", reply]];
};
PeanutWindow.SetNewMail[FALSE];
};
Message Form
NewForm: PROC [user: ROPE, addressee, subject: ROPE ¬ NIL, column: Column ¬ left] ~ {
EndNode: PROC [format: ATOM ¬ NIL] ~ {
TiogaAccess.Put[w, [0, '\000, ALL[FALSE], format, FALSE, TRUE, 0, NIL]];
};
PutField: PROC [key, val: ROPE, format: ATOM ¬ NIL] ~ {
keyLooks: TiogaAccess.Looks ¬ ALL[FALSE];
keyLooks['b] ¬ keyLooks['s] ¬ TRUE;
PutRope[key, keyLooks];
PutRope[Rope.Cat[": ", val, "\n"]];
};
PutRope: PROC [rope: ROPE, looks: TiogaAccess.Looks ¬ ALL[FALSE]] ~ {
Action: PROC [c: CHAR] RETURNS [quit: BOOL ¬ FALSE] ~ {
tc.looks ¬ looks;
SELECT c FROM '\001, '\002 => tc.looks['r] ¬ tc.looks['t] ¬ TRUE; ENDCASE;
tc.char ¬ c;
TiogaAccess.Put[w, tc];
};
tc: TiogaAccess.TiogaChar ¬ [0, '\000, ALL[FALSE], NIL, FALSE, FALSE, 0, NIL];
[] ¬ Rope.Map[base: rope, action: Action];
};
icon: Icons.IconFlavor ¬ PeanutWindow.mailMessageIcon;
v: Viewer ¬ ViewerTools.MakeNewTextViewer[
[name: "Message", icon: icon, iconic: FALSE, column: column]];
w: Writer ~ TiogaAccess.Create[];
TiogaMenuOps.DefaultMenus[v];
Menus.InsertMenuEntry[v.menu, Menus.CreateEntry["SEND", SendButton,,,, TRUE]];
PutField["To", IF addressee = NIL THEN "\001Address\002" ELSE addressee];
EndNode[];
TiogaAccess.Nest[w, 1];
PutField["Subject", IF subject = NIL THEN "\001Topic\002" ELSE subject];
PutRope["\n"];
IF PeanutProfile.ccField THEN {PutField["Cc", "\001Copies To\002"]; PutRope["\n"]};
PutField["Reply-To", user];
PutRope["\n"];
PutRope["\001Message\002"];
EndNode[PeanutProfile.messageNodeFormat];
[] ¬ WriteViewer[w, v];
ViewerTools.SetSelection[v, NEW[ViewerTools.SelPosRec ¬ [0, 0]]];
v.class.notify[v, LIST[$NextPlaceholder]];
};
MessageButton: Menus.ClickProc ~ {NewForm[user,,, IF mouseButton=red THEN left ELSE right]};
Send Mail
ReplyButton: Menus.ClickProc ~ {
GetParent: PROC RETURNS [TiogaOps.Ref] = {
node: TiogaOps.Ref ¬ TiogaOps.GetSelection[].start.node;
root, parent: TiogaOps.Ref ¬ TiogaOps.Root[node];
IF node = NIL THEN RETURN[NIL];
DO
IF (parent ¬ TiogaOps.Parent[node]) = root THEN RETURN[node];
node ¬ parent;
ENDLOOP;
};
GetLine: PROC [key: ROPE] RETURNS [ROPE ¬ NIL] ~ {
FOR node: TiogaOps.Ref ¬ GetParent[], TiogaOps.StepForward[node] WHILE node # NIL DO
s: IO.STREAM ¬ IO.RIS[TiogaOps.GetRope[node]];
DO
line: ROPE ¬ IO.GetLineRope[s ! IO.EndOfStream => EXIT];
IF Rope.Find[line, key] # -1 THEN RETURN[line];
ENDLOOP;
ENDLOOP;
};
GetSender: PROC RETURNS [ROPE ¬ NIL] ~ {
IF (line ¬ GetLine["From:"]) # NIL THEN {
start, stop: INT ¬ Rope.Find[line, "From:"];
i0: INT ¬ Rope.Find[line, "<", start];
i1: INT ¬ Rope.Find[line, ">", start];
IF i0 > -1 AND i1 > i0 THEN RETURN[Rope.Substr[line, i0+1, i1-i0-1]];
stop ¬ Rope.SkipTo[line, start ¬ Rope.SkipOver[line, start+5, " \t"], " \t"];
RETURN[Rope.Substr[line, start, stop-start]];
};
};
GetSubject: PROC RETURNS [r: ROPE ¬ NIL] ~ {
IF (line ¬ GetLine["Subject:"]) = NIL THEN RETURN;
r ¬ Rope.Substr[line, Rope.SkipOver[line, Rope.Find[line, "Subject:"]+8, " \t"]];
IF Rope.Find[r, "Re:"] # 0 THEN r ¬ Rope.Concat["Re: ", r];
};
line, sender: ROPE ¬ GetSender[];
IF sender = NIL
THEN PeanutWindow.OutputRope["\nBad format"]
ELSE NewForm[user, sender, GetSubject[], IF mouseButton=red THEN left ELSE right];
};
Tiogaify: PROC [reader: Reader] RETURNS [w: Writer] ~ {
tc: TiogaAccess.TiogaChar ¬ [0, '\000, ALL[FALSE], NIL, FALSE, FALSE, 0, NIL];
w ¬ TiogaAccess.Create[];
WHILE NOT TiogaAccess.EndOf[reader] DO
IF (tc.char ¬ TiogaAccess.Get[reader].char) # '\n
THEN TiogaAccess.Put[w, tc]
ELSE IF NOT TiogaAccess.EndOf[reader]
THEN SELECT TiogaAccess.Peek[reader].char FROM
' , '\n, '\t => TiogaAccess.Put[w, tc];
ENDCASE => {tc.char ¬ ' ; TiogaAccess.Put[w, tc]};
ENDLOOP;
};
WritePlain: PROC [reader: Reader, fixedPitch: BOOL] RETURNS [w: Writer] ~ {
Get: PROC [op: {space, word}] RETURNS [notEnd: BOOL ¬ TRUE] ~ {
text.length ¬ 0;
DO
tc: TiogaAccess.TiogaChar ¬ TiogaAccess.Get[reader];
IF TiogaAccess.EndOf[reader] THEN RETURN[FALSE];
IF tc.endOfNode AND op = space AND foundText THEN PutChar['\n];
IF op = space
THEN SELECT tc.char FROM
' , '\t   => text ¬ RefText.InlineAppendChar[text, tc.char];
'\n   => {IF foundText THEN {text[0] ¬ '\n; text.length ¬ 1}; EXIT};
ENDCASE  => {TiogaAccess.PutBack[reader, tc]; EXIT}
ELSE SELECT tc.char FROM
' , '\t, '\n => {TiogaAccess.PutBack[reader, tc]; EXIT};
ENDCASE  => text ¬ RefText.InlineAppendChar[text, tc.char];
ENDLOOP;
};
NewLine: PROC ~ {PutChar['\n]; count ¬ 0};
PutChar: PROC [c: CHAR] ~ {tc.char ¬ c; TiogaAccess.Put[w, tc]; lastCharCR ¬ tc.char = '\n};
PutText: PROC ~ {
FOR i: INT IN [0..text.length) DO PutChar[text[i]]; ENDLOOP;
count ¬ count+text.length;
};
tc: TiogaAccess.TiogaChar ¬ [0, '\000, ALL[FALSE], NIL, FALSE, FALSE, 0, NIL];
count: INT ¬ 0;
text: REF TEXT ¬ RefText.ObtainScratch[1000];
foundText, lastCharCR: BOOL ¬ FALSE;
IF fixedPitch THEN tc.looks['f] ¬ TRUE; -- causes Tioga formatting at end of file
w ¬ TiogaAccess.Create[];
WHILE NOT TiogaAccess.EndOf[reader] DO
IF NOT Get[space] THEN EXIT;
IF count+text.length > 79 THEN NewLine[] ELSE IF text.length > 0 THEN PutText[];
IF text.length > 0 AND text[0] = '\n THEN count ¬ 0;
[] ¬ Get[word];
foundText ¬ TRUE;
IF count+text.length > 79 THEN NewLine[];
IF text.length > 0 THEN PutText[];
ENDLOOP;
IF NOT lastCharCR THEN PutChar['\n]; -- most mail handlers want CR to end message
RefText.ReleaseScratch[text];
};
GetAddressees: PROC [reader: Reader, ccToo: BOOL ¬ TRUE] RETURNS [to: ROPE ¬ NIL] ~ {
GetLine: PROC RETURNS [rope: ROPE ¬ NIL] ~ {
WHILE NOT TiogaAccess.EndOf[reader] DO
tc: TiogaAccess.TiogaChar ¬ TiogaAccess.Get[reader];
IF tc.char = '\n OR tc.endOfNode THEN EXIT;
rope ¬ Rope.Concat[rope, Rope.FromChar[tc.char]];
ENDLOOP;
};
Translate: Rope.TranslatorType ~ {RETURN[IF old = ', THEN ' ELSE old]};
cc: ROPE ¬ NIL;
foundText, doCc, doTo: BOOL ¬ FALSE;
WHILE NOT TiogaAccess.EndOf[reader] DO
line: ROPE ¬ GetLine[];
IF Rope.IsEmpty[line] AND foundText THEN EXIT;
IF NOT Rope.IsEmpty[line] THEN {
i: INT ¬ Rope.Find[line, ":"];
IF i > 0 THEN {
key: ROPE ¬ Rope.Substr[line, 0, i];
SELECT TRUE FROM
Rope.Equal[key, "To", FALSE] => {doTo ¬ TRUE; doCc ¬ FALSE};
Rope.Equal[key, "Cc", FALSE] => {doCc ¬ TRUE; doTo ¬ FALSE};
ENDCASE => EXIT;
};
IF doTo THEN to ¬ Rope.Concat[to, Rope.Substr[line, i+1]];
IF doCc THEN cc ¬ Rope.Concat[cc, Rope.Substr[line, i+1]];
foundText ¬ TRUE;
};
ENDLOOP;
to ¬ IF ccToo
THEN Rope.Translate[Rope.Cat[to, " ", cc],,, Translate]
ELSE Rope.Translate[to,,, Translate];
to ¬ Rope.Substr[to, Rope.SkipOver[to,, " \t"]];
WHILE Rope.Size[to] > 1 AND Rope.Fetch[to, Rope.Size[to]-1] = ' DO
to ¬ Rope.Substr[to,, Rope.Size[to]-1];
ENDLOOP;
TiogaAccess.SetIndex[reader, 0];
};
SendButton: Menus.ClickProc ~ {
v: Viewer ¬ ViewerTools.GetSelectedViewer[];
IF v = NIL
THEN PeanutWindow.OutputRope["\nFirst select a message viewer"]
ELSE {
reader: Reader ¬ TiogaAccessViewers.FromViewer[v];
to: ROPE ¬ GetAddressees[reader];
IF Rope.IsEmpty[to]
THEN PeanutWindow.OutputRope["\nBad message format"]
ELSE {
msend: ROPE ¬ Rope.Concat[dir, "msend"];
dot: INT ¬ Rope.Find[to, "."];
[] ¬ UnixCmd[Rope.Cat["mv ", msend, " ", dir, ".msend "]];
TiogaAccess.WriteFile[WritePlain[reader, FALSE], msend];
v.name ¬
Rope.Concat["to ", Rope.Substr[to, 0, IF dot > 0 THEN dot ELSE LAST[INT]]];
IF NOT WriteViewer[WritePlain[TiogaAccessViewers.FromViewer[v], TRUE], v]
THEN RETURN;
ViewerOps.DestroyViewer[v];
IF local
THEN [] ¬ UnixCmd[MakeRsh[Rope.Cat["cat ", msend, " | /usr/lib/sendmail ", to]]]
ELSE {
[] ¬ UnixCmd[Rope.Cat["rcp ", msend, " ", user, Rope.Cat["@", host,":msend"]]];
[] ¬ UnixCmd[MakeRsh[Rope.Concat["cat msend | /usr/lib/sendmail ", to]]];
[] ¬ UnixCmd[MakeRsh["rm msend"], TRUE];
};
PeanutWindow.OutputRope[Rope.Concat["\nMail sent to ", to]];
};
};
};
SaveMessageButton: Menus.ClickProc ~ {
v: Viewer ¬ ViewerTools.GetSelectedViewer[];
IF v = NIL
THEN PeanutWindow.OutputRope["\nFirst select a message viewer"]
ELSE {
to: ROPE ¬ GetAddressees[TiogaAccessViewers.FromViewer[v], FALSE];
IF Rope.IsEmpty[to]
THEN PeanutWindow.OutputRope["\nBad message format"]
ELSE {
Tr: Rope.TranslatorType ~ {RETURN[IF old = ' OR old = '@ THEN '← ELSE old]};
IF Rope.Equal[to, "Address"] THEN to ¬ "Message";
v.file ¬ Rope.Cat[dir, Rope.Translate[to,,, Tr], ".save"];
[] ¬ ViewerOps.SaveViewer[v];
PeanutWindow.OutputRope[Rope.Concat["\nSaved ", v.file]];
};
};
};
MakePlainButton: Menus.ClickProc ~ {Modify[makePlain]};
TiogaifyButton: Menus.ClickProc ~ {Modify[tiogaify]};
Modify: PROC [mode: {makePlain, tiogaify}] ~ {
v: Viewer ¬ ViewerTools.GetSelectedViewer[];
IF v = NIL
THEN PeanutWindow.OutputRope["\nFirst select a message viewer"]
ELSE {
sel: ViewerTools.SelPos ¬ ViewerTools.GetSelection[v];
r: Reader ¬ TiogaAccessViewers.FromViewer[v];
w: Writer ¬ IF mode = makePlain THEN WritePlain[r, TRUE] ELSE Tiogaify[r];
IF WriteViewer[w, v] THEN ViewerTools.SetSelection[v, sel];
};
};
Mail Files
MailFileButton: Menus.ClickProc ~ {
name: ROPE ~ NARROW[clientData];
IF mouseButton= yellow
THEN ViewerOps.OpenIcon[PeanutWindow.GetMailViewer[name]]
ELSE PeanutWindow.CopyMessages[name, mouseButton = blue];
};
GetMailFileList: PROC RETURNS [result: LIST OF ROPENIL] ~ {
Names: PFS.NameProc ~ {
base: ROPE ← PFSNames.ComponentRope[PFSNames.ShortName[name]];
IF base # NIL THEN tmp ← CONS[Rope.Substr[base, 0, Rope.FindBackward[base, "."]], tmp];
};
tmp: LIST OF ROPE;
pattern: PFS.PATHPFS.PathFromRope[Rope.Concat[dir, "*.mail!H"]];
PFS.EnumerateForNames[pattern, Names ! PFS.Error => {
PeanutWindow.OutputRope[IO.PutFR["PFS.Error for %g: %g\n",
IO.rope[dir], IO.rope[error.explanation]]];
CONTINUE}];
FOR l: LIST OF ROPE ← tmp, l.rest WHILE l # NIL DO result ← CONS[l.first, result]; ENDLOOP;
};
Initialization
debug: IO.STREAM;
PeanutShellCmd: ENTRY Commander.CommandProc ~ {
debug ¬ cmd.out;
PeanutWindow.Destroy[];
IF NOT PeanutWindow.Create[] THEN RETURN;
[] ¬ CedarProcess.Fork[MailCheck];
FOR l: LIST OF ROPE ¬ mailFiles ¬ GetMailFileList[], l.rest UNTIL l = NIL DO
PeanutWindow.AddButton[l.first, MailFileButton, l.first,,, l.rest = NIL];
ENDLOOP;
PeanutWindow.OutputRope["Peanut for PCedar\n"];
PeanutWindow.OutputRope["MIDDLE click mail file button to open the file\n"];
PeanutWindow.OutputRope["LEFT click to copy selected message(s)\n"];
PeanutWindow.OutputRope["RIGHT click to move selected message(s)\n"];
};
PeanutWindow.AddCommand["Get", GetButton,,,, 0];
PeanutWindow.AddCommand["Send", SendButton,,, TRUE, 0];
PeanutWindow.AddCommand["Message", MessageButton,,,, 0];
PeanutWindow.AddCommand["Reply", ReplyButton,,,, 0];
PeanutWindow.AddCommand["Bye", ByeButton,,,, 1];
PeanutWindow.AddCommand["SaveMessage", SaveMessageButton,,,, 1];
PeanutWindow.AddCommand["MakePlain", MakePlainButton,,,, 1];
PeanutWindow.AddCommand["Tiogaify", TiogaifyButton,,,, 1];
Commander.Register["PeanutShell", PeanutShellCmd, "organize mail"];
END.
..
PeanutShellUnixToTiogaCmd: Commander.CommandProc ~ {
argv: CommanderOps.ArgumentVector ¬ CommanderOps.Parse[cmd];
IF argv.argc # 4 OR NOT Rope.Equal[argv[2], "←"]
THEN RETURN[$Failure, "usage: PeanutShellUnixToTioga <tioga file> ← <unix file>"]
ELSE {
w: Writer ¬ TiogaAccess.Create[];
nMsgs: INT ¬ UnixMailFileToTiogaMail[argv[3], w];
IO.PutF1[cmd.out, "%g messages\n", IO.int[nMsgs]];
IF nMsgs > 0 THEN TiogaAccess.WriteFile[w, argv[1]];
};
};
Commander.Register["PeanutShellUnixToTioga", PeanutShellUnixToTiogaCmd, "xform Unix file"] ;
GetAddressees: PROC [reader: Reader] RETURNS [to: ROPE ¬ NIL] ~ {
GetAddressees: PROC [v: Viewer] RETURNS [to: ROPE ¬ NIL] ~ {
GetNode: PROC RETURNS [rope: ROPE ¬ NIL] ~ {
WHILE NOT TiogaAccess.EndOf[reader] DO
tc: TiogaAccess.TiogaChar ¬ TiogaAccess.Get[reader];
IF rope # NIL AND tc.endOfNode THEN EXIT;
rope ¬ Rope.Concat[rope, Rope.FromChar[tc.char]];
ENDLOOP;
};
Translate: Rope.TranslatorType ~ {RETURN[IF old = ', THEN ' ELSE old]};
GetField: PROC [key: ROPE] RETURNS [val: ROPE ¬ NIL] ~ {
start: INT ¬ Rope.Find[node, key];
IF start = -1 THEN RETURN;
start ¬ start+Rope.Length[key];
val ¬ Rope.Substr[node, start, Rope.SkipTo[node, start, "\n"]-start];
};
reader: Reader ¬ TiogaAccessViewers.FromViewer[v];
node: ROPE ¬ GetNode[];
to ¬ Rope.Translate[Rope.Cat[GetField["To: "], " ", GetField["Cc: "]],,, Translate];
to ¬ Rope.Substr[to, Rope.SkipOver[to,, " \t"]];
WHILE Rope.Size[to] > 1 AND Rope.Fetch[to, Rope.Size[to]-1] = ' DO
to ¬ Rope.Substr[to,, Rope.Size[to]-1];
ENDLOOP;
reader ← TiogaAccessViewers.FromViewer[v]; -- gross reset of reader, but SetPosition suspect
TiogaAccess.SetIndex[reader, 0]; -- was suspect (preferable to SetPosition) seems to work
};