ð#Syntax10.Scn.FntJJMODULE Mailer; (* J. Templ Sept-90/22.04.93, Solaris-2 version *) IMPORT SYSTEM, Unix, Kernel, Oberon, TextFrames, Texts, MenuViewers, Display, Files, Fonts, In; CONST CR = 0DX; LF = 0AX; TAB = 09X; SETLKW = 7; WRLCK = 2; UNLCK = 3; mboxPermissions = {7, 8}; mailDefault = "/var/spool/mail/"; showMenu = "System.Close System.Copy System.Grow Edit.Search Mailer.Reply Mailer.Mono Edit.Store "; replyMenu = "System.Close System.Copy System.Grow Edit.Search !Mailer.Send Edit.Store "; mboxMenu = "System.Close Mailer.Show Mailer.Delete Mailer.Store "; TYPE Mail = POINTER TO MailDesc; MailDesc = RECORD next: Mail; start, len, nr: LONGINT; del: BOOLEAN END; Frame = POINTER TO FrameDesc; FrameDesc = RECORD (TextFrames.FrameDesc) mailText: Texts.Text; mailFileName: ARRAY 110 OF CHAR; mailFileLen: LONGINT; mails: Mail; END ; Flock = RECORD type, whence: INTEGER; start, len: LONGINT; sysid, pid: LONGINT; pad: ARRAY 4 OF LONGINT END ; VAR MAIL*, MBOX*: ARRAY 110 OF CHAR; (* environment variables *) nextch: CHAR; system: PROCEDURE (cmd: ARRAY OF CHAR); PROCEDURE GetWord(VAR R: Texts.Reader; VAR word: ARRAY OF CHAR); VAR i: INTEGER; BEGIN Texts.Read(R, nextch); i := 0; WHILE (i < 5) & (nextch >= " ") DO word[i] := nextch; INC(i); Texts.Read(R, nextch) END ; word[i] := 0X; END GetWord; PROCEDURE GetField(t: Texts.Text; pos: LONGINT; entry: ARRAY OF CHAR; VAR line: ARRAY OF CHAR); VAR i: INTEGER; ch: CHAR; tag: ARRAY 32 OF CHAR; R: Texts.Reader; BEGIN Texts.OpenReader(R, t, pos); line[0] := 0X; LOOP i := 0; Texts.Read(R, ch); WHILE (ch > " ") & (i < 31) DO IF ("a" <= ch) & (ch <= "z") THEN ch := CAP(ch) END ; tag[i] := ch; INC(i); Texts.Read(R, ch) END ; tag[i] := 0X; IF tag = entry THEN i := -1; REPEAT Texts.Read(R, ch); IF ch = CR THEN Texts.Read(R, ch); IF (ch # " ") & (ch # TAB) THEN ch := CR END END ; IF ch = TAB THEN ch := " " END ; IF i + 1 < LEN(line) THEN INC(i) END ; line[i] := ch; UNTIL R.eot OR (ch = CR); line[i] := 0X; EXIT ELSIF R.eot OR (ch = CR) THEN EXIT END ; REPEAT Texts.Read(R, ch) UNTIL R.eot OR (ch = CR); END END GetField; PROCEDURE SkipMail(VAR R: Texts.Reader; mail: Mail); VAR i: INTEGER; word: ARRAY 6 OF CHAR; BEGIN REPEAT WHILE (nextch # CR) & (nextch # 0X) DO Texts.Read(R, nextch) END ; mail.len := Texts.Pos(R) - mail.start; GetWord(R, word); UNTIL (word = "From ") OR R.eot END SkipMail; PROCEDURE StripFrom(VAR line: ARRAY OF CHAR); VAR i, j: INTEGER; ch: CHAR; BEGIN i := 0; ch := line[0]; WHILE ch # 0X DO IF (ch = "(") OR (ch = 22X) THEN INC(i); ch := line[i]; j := 0; WHILE (ch # ")") & (ch # 22X) & (ch # 0X) DO line[j] := line[i]; INC(i); INC(j); ch := line[i] END ; line[j] := 0X; RETURN ELSIF ch = "<" THEN IF (i > 2) & (line[i-1] = " ") THEN line[i-1] := 0X; RETURN ELSE INC(i); ch := line[i]; j := 0; WHILE (ch # ">") & (ch # 0X) DO line[j] := line[i]; INC(i); INC(j); ch := line[i] END ; line[j] := 0X; RETURN END END ; INC(i); ch := line[i] END END StripFrom; PROCEDURE GetNewsSubject(t: Texts.Text; pos: LONGINT; VAR line: ARRAY OF CHAR); VAR ch0, ch1: CHAR; R: Texts.Reader; BEGIN Texts.OpenReader(R, t, pos); Texts.Read(R, ch0); Texts.Read(R, ch1); WHILE (~R.eot) & (ch0 # CR) OR (ch1 # CR) DO ch0 := ch1; Texts.Read(R, ch1) END ; GetField(t, Texts.Pos(R), "SUBJECT:", line) END GetNewsSubject; PROCEDURE MailBoxText(T: Texts.Text; newMailPos: LONGINT; VAR MB: Texts.Text; VAR mails: Mail); VAR W: Texts.Writer; R, R1: Texts.Reader; mail: Mail; nofmails: LONGINT; word: ARRAY 6 OF CHAR; line: ARRAY 1024 OF CHAR; BEGIN Texts.OpenWriter(W); nofmails := 0; Texts.OpenReader(R, T, 0); GetWord(R, word); MB := TextFrames.Text(""); WHILE ~ R.eot DO NEW(mail); mail.start := Texts.Pos(R)-6; mail.del := FALSE; mail.nr := nofmails; Texts.WriteInt(W, nofmails, 2); IF mail.start < newMailPos THEN Texts.WriteString(W, " ") ELSE Texts.WriteString(W, " + ") END ; GetField(T, mail.start, "FROM:", line); IF line = "" THEN GetField(T, mail.start, "FROM", line) END ; StripFrom(line); Texts.WriteString(W, line); Texts.WriteString(W, " "); IF FALSE (*line = "news@inf.ethz.ch"*) THEN GetNewsSubject(T, mail.start, line) ELSE GetField(T, mail.start, "SUBJECT:", line) END ; Texts.WriteString(W, line); SkipMail(R, mail); mail^.next := mails^.next; mails^.next := mail; Texts.WriteInt(W, mail.len, 8); Texts.WriteLn(W); Texts.Insert(MB, 0, W.buf); INC(nofmails) END END MailBoxText; PROCEDURE Init; TYPE EnvVar = POINTER TO RECORD name: ARRAY 1024 OF CHAR END ; VAR i, j: INTEGER; ch: CHAR; getenv: PROCEDURE (var: ARRAY OF CHAR): EnvVar; var: EnvVar; BEGIN Kernel.dlsym(Kernel.libc, "system", SYSTEM.VAL(LONGINT, system)); Kernel.dlsym(Kernel.libc, "getenv", SYSTEM.VAL(LONGINT, getenv)); var := getenv("MAIL"); IF var # NIL THEN COPY(var.name, MAIL) ELSE MAIL := mailDefault; i := 16; j := 0; REPEAT ch := Oberon.User[j]; MAIL[i] := ch; INC(i); INC(j) UNTIL ch = 0X END ; var := getenv("MBOX"); IF var # NIL THEN COPY(var.name, MBOX) ELSE var := getenv("HOME"); IF var # NIL THEN COPY(var.name, MBOX) ELSE MBOX[0] := "."; MBOX[1] := 0X END ; i := 0; WHILE MBOX[i] # 0X DO INC(i) END ; MBOX[i] := "/"; MBOX[i+1] := "m"; MBOX[i+2] := "b"; MBOX[i+3] := "o"; MBOX[i+4] := "x"; MBOX[i+5] := 0X END END Init; (* ------------------------------- commands ------------------------------- *) PROCEDURE Menu(title, menufile, default: ARRAY OF CHAR): TextFrames.Frame; VAR M: TextFrames.Frame; T: Texts.Text; buf: Texts.Buffer; BEGIN IF Files.Old(menufile) # NIL THEN M := TextFrames.NewMenu(title, ""); NEW(T); Texts.Open(T, menufile); NEW(buf); Texts.OpenBuf(buf); Texts.Save(T, 0, T.len, buf); Texts.Append(M.text, buf) ELSE M := TextFrames.NewMenu(title, default) END ; RETURN M END Menu; PROCEDURE Lock(fd: LONGINT); VAR l: Flock; res: LONGINT; BEGIN l.type := WRLCK; l.whence := 0; l.start := 0; l.len := 0(*up to EOF*); res := Unix.Fcntl(fd, SETLKW, SYSTEM.ADR(l)) END Lock; PROCEDURE Unlock(fd: LONGINT); VAR l: Flock; res: LONGINT; BEGIN l.type := WRLCK; l.whence := 0; l.start := 0; l.len := 0(*up to EOF*); res := Unix.Fcntl(fd, UNLCK, SYSTEM.ADR(l)) END Unlock; PROCEDURE Mailbox*; VAR X, Y: INTEGER; ch: CHAR; V: MenuViewers.Viewer; mail, mbox: Files.File; mailR, mboxR: Files.Rider; mailfile: ARRAY 64 OF CHAR; F: Frame; newMailPos, res: LONGINT; BEGIN In.Open; In.Name(mailfile); newMailPos := MAX(LONGINT); IF ~In.Done THEN mail := Files.Old(MAIL); IF mail = NIL THEN RETURN END ; mbox := Files.Old(MBOX); IF mbox = NIL THEN mbox := Files.New(MBOX); Files.Register(mbox) END ; IF (mail # NIL) & (Files.Length(mail) # 0) THEN (*append mail to mbox and purge mail*) newMailPos := Files.Length(mbox); Files.Set(mboxR, mbox, Files.Length(mbox)); Files.Set(mailR, mail, 0); Unlock(mail.fd); Lock(mail.fd); Files.Read(mailR, ch); WHILE ~mailR.eof DO Files.Write(mboxR, ch); Files.Read(mailR, ch) END ; Files.Close(mbox); Files.Purge(mail); Unlock(mail.fd) END ; COPY(MBOX, mailfile) END ; NEW(F); F.mailText := TextFrames.Text(mailfile); IF F.mailText # NIL THEN F.mailFileLen := F.mailText.len; NEW(F.mails); MailBoxText(F.mailText, newMailPos, F.text, F.mails); TextFrames.Open(F, F.text, 0); Oberon.AllocateSystemViewer(Oberon.Mouse.X, X, Y); V := MenuViewers.New( Menu(mailfile, "Mailer.MailboxMenu.Text", mboxMenu), F, TextFrames.menuH, X, Y) END END Mailbox; PROCEDURE ShowMenu(i: LONGINT): TextFrames.Frame; VAR menu: TextFrames.Frame; W: Texts.Writer; BEGIN menu := Menu("", "Mailer.ShowMenu.Text", showMenu); Texts.OpenWriter(W); Texts.WriteString(W, "Message"); Texts.WriteInt(W, i, 0); Texts.WriteString(W, ".Text"); Texts.Insert(menu.text, 0, W.buf); RETURN menu END ShowMenu; PROCEDURE Show*; VAR X, Y: INTEGER; mail: Mail; S: Texts.Scanner; F: Display.Frame; B: Texts.Buffer; V: MenuViewers.Viewer; T: Texts.Text; BEGIN F := Oberon.Par.vwr.dsc.next; IF (F IS Frame) & (F(Frame).hasSel) THEN WITH F: Frame DO Oberon.AllocateUserViewer(Oberon.Mouse.X, X, Y); Texts.OpenScanner(S, F.text, F.selbeg.org); Texts.Scan(S); IF S.class = Texts.Int THEN mail := F.mails.next; WHILE (mail # NIL) & (mail.nr # S.i) DO mail := mail.next END ; IF mail # NIL THEN T := TextFrames.Text(""); NEW(B); Texts.OpenBuf(B); Texts.Save(F.mailText, mail.start, mail.start+mail.len, B); Texts.Append(T, B); V := MenuViewers.New(ShowMenu(S.i), TextFrames.NewText(T, 0), TextFrames.menuH, X, Y) END END END END END Show; PROCEDURE Delete*; VAR mail, m: Mail; S: Texts.Scanner; F: Display.Frame; n: INTEGER; R: Texts.Reader; ch: CHAR; T: Texts.Text; BEGIN F := Oberon.Par.vwr.dsc.next; IF (F IS Frame) & (F(Frame).hasSel) THEN WITH F: Frame DO Texts.OpenScanner(S, F.text, F.selbeg.org); Texts.Scan(S); IF S.class = Texts.Int THEN Texts.OpenReader(R, F.text, F.selbeg.pos); n := 1; Texts.Read(R, ch); WHILE Texts.Pos(R) < F.selend.pos -1 DO IF ch = 0DX THEN INC(n) END ; Texts.Read(R, ch) END ; mail := F.mails.next; WHILE (mail # NIL) & (mail.nr # S.i) DO mail := mail.next END ; IF mail # NIL THEN WHILE (mail # NIL) & (n > 0) DO m := F.mails; Texts.Delete(F.mailText, mail.start, mail.start+mail.len); WHILE m.next # mail DO m := m.next; DEC(m.start, mail.len) END ; m.next := mail.next; mail := mail.next; DEC(n) END ; WHILE (ch >= " ") OR (ch = 09X) DO Texts.Read(R, ch) END ; Texts.Delete(F.text, F.selbeg.org, Texts.Pos(R)) END END END END END Delete; PROCEDURE Store*; VAR mail, m: Mail; F: Display.Frame; S: Texts.Scanner; R: Texts.Reader; ch: CHAR; mbox: Files.File; mboxR: Files.Rider; W: Texts.Writer; T: Texts.Text; res: LONGINT; BEGIN F := Oberon.Par.vwr.dsc; IF (F = Oberon.Par.frame) & (F.next IS Frame) THEN Texts.OpenScanner(S, Oberon.Par.text, 0); Texts.Scan(S); mbox := Files.New(S.s); Files.Set(mboxR, mbox, 0); Texts.OpenWriter(W); Texts.WriteString(W, "Mailer.Store "); Texts.WriteString(W, S.s); Texts.WriteLn(W); Texts.Append(Oberon.Log, W.buf); Texts.OpenReader(R, F.next(Frame).mailText, 0); Texts.Read(R, ch); WHILE ~R.eot DO IF ch = CR THEN ch := LF END ; Files.Write(mboxR, ch); Texts.Read(R, ch) END ; Files.Register(mbox); res := Unix.Chmod(SYSTEM.ADR(S.s), mboxPermissions); IF F IS TextFrames.Frame THEN T := F(TextFrames.Frame).text; Texts.OpenReader(R, T, T.len-1); Texts.Read(R, ch); IF ch = "!" THEN Texts.Delete(T, T.len-1, T.len) END END END END Store; PROCEDURE Send*; VAR V: Display.Frame; temp: Files.File; S: Texts.Scanner; R: Texts.Reader; mR: Files.Rider; ch: CHAR; res: INTEGER; W: Texts.Writer; BEGIN IF Oberon.Par.frame = Oberon.Par.vwr.dsc THEN V := Oberon.Par.vwr ELSE V := Oberon.MarkedViewer() END ; IF (V # NIL) & (V IS MenuViewers.Viewer) & (V.dsc.next IS TextFrames.Frame) THEN Texts.OpenWriter(W); Texts.OpenScanner(S, V.dsc(TextFrames.Frame).text, 0); Texts.Scan(S); Texts.OpenReader(R, V.dsc.next(TextFrames.Frame).text, 0); temp := Files.New("/tmp/Mailer.Send"); Files.Set(mR, temp, 0); Texts.Read(R, ch); WHILE ~R.eot DO IF ch = CR THEN ch := LF END ; Files.Write(mR, ch); Texts.Read(R, ch) END ; Texts.WriteString(W, S.s); Texts.WriteString(W, " mailing"); Texts.WriteLn(W); Texts.Append(Oberon.Log, W.buf); Files.Register(temp); system("(/usr/lib/sendmail -oi -t < /tmp/Mailer.Send; rm /tmp/Mailer.Send) &"); END END Send; PROCEDURE Post*; VAR V: Display.Frame; temp: Files.File; S: Texts.Scanner; R: Texts.Reader; mR: Files.Rider; ch: CHAR; res: INTEGER; W: Texts.Writer; BEGIN V := Oberon.MarkedViewer(); IF (V # NIL) & (V.dsc.next IS TextFrames.Frame) THEN Texts.OpenWriter(W); Texts.OpenScanner(S, V.dsc(TextFrames.Frame).text, 0); Texts.Scan(S); Texts.OpenReader(R, V.dsc.next(TextFrames.Frame).text, 0); temp := Files.New("/tmp/Mailer.Post"); Files.Set(mR, temp, 0); Texts.Read(R, ch); WHILE ~R.eot DO IF ch = CR THEN ch := LF END ; Files.Write(mR, ch); Texts.Read(R, ch) END ; Texts.WriteString(W, S.s); Texts.WriteString(W, " posting"); Texts.WriteLn(W); Texts.Append(Oberon.Log, W.buf); Files.Register(temp); system("/usr/lib/news/inews -h < /tmp/Mailer.Post"); Files.Delete("/tmp/Mailer.Post", res) END END Post; PROCEDURE QuoteText(text: Texts.Text; beg, end: LONGINT); VAR W: Texts.Writer; R: Texts.Reader; ch: CHAR; BEGIN Texts.OpenWriter(W); REPEAT Texts.WriteString(W, "> "); Texts.Insert(text, beg, W.buf); INC(end, 2); Texts.OpenReader(R, text, beg); REPEAT Texts.Read(R, ch); INC(beg) UNTIL (ch = CR) OR (beg >= end) UNTIL beg >= end END QuoteText; PROCEDURE Quote*; VAR text: Texts.Text; beg, end, time: LONGINT; BEGIN Oberon.GetSelection(text, beg, end, time); IF time >= 0 THEN QuoteText(text, beg, end) END ; END Quote; PROCEDURE ReplyMenu(parFrame: Display.Frame): TextFrames.Frame; VAR menu: TextFrames.Frame; W: Texts.Writer; S: Texts.Scanner; BEGIN menu := Menu("", "Mailer.ReplyMenu.Text", replyMenu); Texts.OpenScanner(S, parFrame(TextFrames.Frame).text, 0); Texts.Scan(S); Texts.OpenWriter(W); Texts.WriteString(W, "Reply."); IF S.class = Texts.Name THEN Texts.WriteString(W, S.s) ELSE Texts.WriteString(W, "Text") END ; Texts.Insert(menu.text, 0, W.buf); RETURN menu END ReplyMenu; PROCEDURE Reply*; VAR X, Y: INTEGER; ch: CHAR; beg, i: LONGINT; T: Texts.Text; W: Texts.Writer; V: MenuViewers.Viewer; F: TextFrames.Frame; B: Texts.Buffer; line: ARRAY 1024 OF CHAR; BEGIN Oberon.AllocateUserViewer(Oberon.Mouse.X, X, Y); IF Oberon.Par.vwr.dsc = Oberon.Par.frame THEN F := Oberon.Par.frame.next(TextFrames.Frame); Texts.OpenWriter(W); GetField(F.text, 0, "REPLY-TO:", line); IF line = "" THEN GetField(F.text, 0, "FROM:", line) END ; Texts.WriteString(W, "To: "); Texts.WriteString(W, line); Texts.WriteLn(W); IF FALSE (*line = "oberon-news@inf.ethz.ch"*) THEN GetNewsSubject(F.text, 0, line); IF (line[0] = "R") & (line[1] = "e") & (line[2] = ":") & (line[3] = " ") THEN i := 0; WHILE line[i + 4] # 0X DO line[i] := line[i + 4]; INC(i) END ; line[i] := 0X END ELSE GetField(F.text, 0, "SUBJECT:", line) END ; IF line # "" THEN Texts.WriteString(W, "Subject: Re: "); Texts.WriteString(W, line); Texts.WriteLn(W) END ; Texts.WriteLn(W); T := TextFrames.Text(""); Texts.Append(T, W.buf); IF F.hasSel THEN beg := T.len; NEW(B); Texts.OpenBuf(B); Texts.Save(F.text, F.selbeg.pos, F.selend.pos, B); Texts.Append(T, B); QuoteText(T, beg, T.len); Texts.WriteLn(W); Texts.Append(T, W.buf) END ; V := MenuViewers.New(ReplyMenu(Oberon.Par.frame), TextFrames.NewText(T, 0), TextFrames.menuH, X, Y); Oberon.PassFocus(V); TextFrames.SetCaret(V.dsc.next(TextFrames.Frame), T.len) END END Reply; PROCEDURE Append*; VAR f: Files.File; V: Display.Frame; R0: Texts.Reader; R1: Files.Rider; ch: CHAR; to: ARRAY 64 OF CHAR; W: Texts.Writer; BEGIN In.Open; In.Name(to); V := Oberon.MarkedViewer(); IF In.Done & (V IS MenuViewers.Viewer) & (V.dsc.next IS TextFrames.Frame) THEN Texts.OpenWriter(W); Texts.WriteString(W, " appending * to "); Texts.WriteString(W, to); Texts.WriteLn(W); Texts.Append(Oberon.Log, W.buf); f := Files.Old(to); IF f = NIL THEN f := Files.New(to); Files.Register(f) END ; Files.Set(R1, f, Files.Length(f)); Texts.OpenReader(R0, V.dsc.next(TextFrames.Frame).text, 0); Texts.Read(R0, ch); WHILE ~R0.eot DO IF ch = CR THEN ch := LF END ; Files.Write(R1, ch); Texts.Read(R0, ch) END ; Files.Close(f) END END Append; PROCEDURE CutLines*; (* by R.G. *) CONST default = 80; VAR lim, beg, end, pos: LONGINT; ch: CHAR; S: Texts.Scanner; V: Display.Frame; T: Texts.Text; R: Texts.Reader; W: Texts.Writer; BEGIN Texts.OpenWriter(W); Texts.OpenScanner(S, Oberon.Par.text, Oberon.Par.pos); Texts.Scan(S); IF (S.line = 0) & (S.class = Texts.Int) & (S.i > 1) THEN lim := S.i; Texts.Scan(S) ELSE lim := default END; IF (S.line = 0) & (S.class = Texts.Char) & (S.c = "*") THEN V := Oberon.MarkedViewer() ELSE V := Oberon.Par.vwr END ; IF (V # NIL) & (V.dsc # NIL) & (V.dsc.next # NIL) & (V.dsc.next IS TextFrames.Frame) THEN T := V.dsc.next(TextFrames.Frame).text; pos := 0; beg := 0; end := lim - 1; Texts.OpenReader(R, T, 0); LOOP Texts.Read(R, ch); INC(pos); IF R.eot THEN EXIT ELSIF ch = 0DX THEN beg := pos; end := beg + lim - 1 ELSIF pos - beg = lim THEN Texts.Write(W, 0DX); Texts.Insert(T, end, W.buf); pos := end; Texts.OpenReader(R, T, pos) ELSIF (ch <= " ") OR (ch = "-") THEN end := pos ELSIF (80X <= ch) & (ch <= 85X) THEN CASE ch OF | 80X: ch := "A" | 81X: ch := "O" | 82X: ch := "U" | 83X: ch := "a" | 84X: ch := "o" | 85X: ch := "u" END; DEC(pos); Texts.Delete(T, pos, pos + 1); Texts.Write(W, ch); Texts.Write(W, "e"); Texts.Insert(T, pos, W.buf); Texts.OpenReader(R, T, pos) END END END END CutLines; PROCEDURE Mono*; VAR V: Display.Frame; T: Texts.Text; BEGIN IF Oberon.Par.frame = Oberon.Par.vwr.dsc THEN V := Oberon.Par.vwr ELSE V := Oberon.MarkedViewer() END ; IF (V # NIL) & (V IS MenuViewers.Viewer) & (V.dsc.next IS TextFrames.Frame) THEN T := V.dsc.next(TextFrames.Frame).text; Texts.ChangeLooks(T, 0, T.len, {0}, Fonts.This("Courier10.Scn.Fnt"), 0, 0) END END Mono; PROCEDURE Forward; (*proposed by Stefan Vorkoetter; I have to think about it later*) VAR V: Display.Frame; T: Texts.Text; W: Texts.Writer; line: ARRAY 1024 OF CHAR; BEGIN IF Oberon.Par.frame = Oberon.Par.vwr.dsc THEN V := Oberon.Par.vwr ELSE V := Oberon.MarkedViewer() END; IF (V # NIL) & (V IS MenuViewers.Viewer) & (V.dsc.next IS TextFrames.Frame) THEN T := V.dsc.next(TextFrames.Frame).text; GetField(T,0,"SUBJECT:",line); QuoteText(T,0,T.len); Texts.OpenWriter(W); Texts.WriteString(W,"To: "); Texts.WriteLn(W); Texts.WriteString(W,"Subject: FWD>"); Texts.WriteString(W,line); Texts.WriteLn(W); Texts.WriteLn(W); Texts.Insert(T,0,W.buf); END END Forward; BEGIN Init END Mailer.