summaryrefslogtreecommitdiff
path: root/ipl/progs/mr.icn
blob: 0d7f49f8824d3199bf00a42b1f5ee9e0cc43e239 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
############################################################################
#
#	File:     mr.icn
#
#	Subject:  Program to read mail
#
#	Author:   Ronald Florence
#
#	Date:     November 19, 1997
#
############################################################################
#
#   This file is in the public domain.
#
############################################################################
#
#	Version:  1.4
#
############################################################################
#
#  With no arguments, mr reads the default mail spool.  Another user,
#  a spool file, or the recipient for outgoing mail can be given as 
#  a command line argument.  Help, including the symbols used to 
#  indicate the status of mail, is available with the H command. 
#
#  Usage:  mr [recipient] [-u user] [-f spool]
#	
#  Configuration:
#
#    Editor	for replies or new mail.
#    Host  	optional upstream routing address for outgoing mail;
#		a domained Host is appended to the address, a uucp
#		Host prefixes the address.
#    Mail_cmd  	the system mailer (usually sendmail, smail, or mail).
#    print_cmd 	command to format and/or spool material for the printer
#		(for OS with pipes).  &null for ms-dos.
#    ignore 	a list of headers to hide when paging messages.  The V
#    		command views hidden headers.
#
#  Non-UNIX systems only:
#
#    non_unix_mailspool  full path of the default mailspool.
#
############################################################################
#
#  Links:  iolib, options, io
#
############################################################################

link iolib, options, io

global Host, Editor, Spool, Status, Mail_cmd

procedure main(arg)
  local i, opts, cmd, art, mailspool, print_cmd, ignore, non_unix_mailspool

				# configuration 
  Editor := "vi"
  Host := &null
  Mail_cmd := "/usr/lib/sendmail -t"
  print_cmd := "mp -F | lpr"
  ignore := ["From ", "Message-Id", "Received", "Return-path", "\tid", 
	     "Path", "Xref", "References", "X-mailer", "Errors-to", 
	     "Resent-Message-Id", "Status", "X-lines", "X-VM-Attributes"]
  non_unix_mailspool := &null

				# end of configuration

  if not "UNIX" == &features then 
      mailspool := getenv("MAILSPOOL") | \non_unix_mailspool | "DUNNO"
  opts := options(arg, "u:f:h?") 
  \opts["h"] | \opts["?"] | arg[1] == "?" & 
    stop("usage: mr [recipient] [-f spoolfile] [-u user]")
  \arg[1] & { write(); newmail(arg[1]); exit(0) }
  /mailspool := "/usr/spool/mail/" || (\opts["u"] | getenv("LOGNAME"|"USER"))
  \opts["f"] & mailspool := opts["f"] 
  i := readin(mailspool)
  headers(mailspool, i)
  repeat {
    cmd := query("\n[" || i || "/" || *Status || "]: ", " ")
    if integer(cmd) & (cmd > 0) & (cmd <= *Status) then 
	headers(mailspool, i := cmd)
    else case map(!cmd) of {
      " ":  { showart(i, ignore); i := inc(i) }
      "a":  save(query("Append to: "), i, "append")
      "d":  { Status[i] ++:= 'D'; clear_line(); i := inc(i) }
      "f":  forward(query("Forward to: "), i)
      "g":  readin(mailspool, "update") & headers(mailspool, i)
      "l":  headers(mailspool, i)
      "m":  newmail(query("Address: "))
      "p":  print(print_cmd, i)
      "q":  quit(mailspool)
      "r":  reply(i)
      "s":  save(query("Filename: "), i)
      "u":  { Status[i] --:= 'D'; clear_line(); i := inc(i) }
      "v":  showart(i, ignore, "all")
      "x":  upto('yY', query("Are you sure? ")) & exit(1) 
      "|":  pipeto(query("Command: "), i)
      "!":  { system(query("Command: ")) 
	      write() & query("Press <return> to continue") }
      "-":  { if (i -:= 1) = 0 then i := *Status; showart(i, ignore) }
      "+"|"n":  showart(i := inc(i), ignore)
      "?"|"h":  help()
      default:  clear_line() & writes("\^g")
    }
  }
end

				# Read the mail spool into a list of
				# lists and set up a status list.
procedure readin(spoolname, update)
  local sf, i, article

  Spool := []
  \update | Status := []
  sf := open(spoolname) | stop("Can't read " || spoolname)
  i := 0
  every !sf ? {
    ="From " & {
      ((i +:= 1) > 1) & put(Spool, article)
      article := []      
      (i > *Status) & put(Status, 'N')
    }
    (i > 0) & put(article, &subject)
  }
  (i > 0) & {
    put(Spool, article)
    i := 1
  }
  close(sf)
  return i
end

				# Parse messages for author & subject,
				# highlight the current message.
procedure headers(spoolname, art)
  local hlist, i, entry, author, subj

  hlist := []
  every i := 1 to *Status do {
    entry := if i = art then getval("md"|"so") else ""
    entry ||:= left(i, 3, " ") || left(Status[i], 4, " ")
    author := ""
    subj := ""
    while (*author = 0) | (*subj = 0) do !Spool[i] ? {
      ="From: " & author := tab(0)
      ="Subject: " & subj := tab(0)
      (*&subject = 0) & break
    }
    entry ||:= " [" || right(*Spool[i], 3, " ") || ":" 
    entry ||:= left(author, 17, " ") || "]  " || left(subj, 45, " ")
    (i = art) & entry ||:= getval("me"|"se")
    put(hlist, entry)
  }
  put(hlist, "")
  more(spoolname, hlist)
end

				# Check if any messages are deleted;
				# if the spool cannot be written,
				# write a temporary spool.  Rename
				# would be convenient, but won't work
				# across file systems.
procedure quit(spoolname)
  local msave, f, tfn, i

  every !Status ? { find("D") & break msave := 1 }
  \msave & {
    readin(spoolname, "update")
    (f := open(spoolname, "w")) | {
      f := open(tfn := tempname(), "w")      
      write("Cannot write " || spoolname || ".  Saving changes to " || tfn)
    }
    every i := 1 to *Status do {
      find("D", Status[i]) | every write(f, !Spool[i])
    }
  }
  exit(0)
end


procedure save(where, art, append)
  local mode, outf

  mode := if \append then "a" else "w"
  outf := open(where, mode) | { write("Can't write ", where) & fail }
  every write(outf, !Spool[art])
  Status[art] ++:= 'S'
  return close(outf)
end


procedure pipeto(cmd, art)
  static real_pipes
  local p, tfn, status

  initial real_pipes := "pipes" == &features
  p := (\real_pipes & open(cmd, "wp")) | open(tfn := tempname(), "w")
  every write(p, !Spool[art])
  if \real_pipes then return close(p) 
  else {
    cmd ||:= " < " || tfn
    status := system(cmd)
    remove(tfn)
    return status
  }
end


procedure print(cmd, art)
  local p, status
  
  if \cmd then status := pipeto(cmd, art)
  else if not "MS-DOS" == &features then 
      return write("Sorry, not configured to print messages.")
  else {
    p := open("PRN", "w")
    every write (p, !Spool[art])
    status := close(p) 
  }
  \status & { Status[art] ++:= 'P'; clear_line() }
end


				# Lots of case-insensitive parsing.
procedure reply(art)
  local tfn, fullname, address, quoter, date, id, subject, newsgroup, refs, r

  r := open(tfn := tempname(), "w")
  every !Spool[art] ? {
    tab(match("from: " | "reply-to: ", map(&subject))) & {
      if find("<") then {
	fullname := tab(upto('<'))
	address := (move(1), tab(find(">")))
      }
      else {
	address := trim(tab(upto('(') | 0))
	fullname := (move(1), tab(find(")")))
      }
      while match(" ", \fullname, *fullname) do fullname ?:= tab(-1)
      quoter := if *\fullname > 0 then fullname else address
    }
    tab(match("date: ", map(&subject))) & date := tab(0)
    tab(match("message-id: ", map(&subject))) & id := tab(0)
    match("subject: ", map(&subject)) & subject := tab(0)
    match("newsgroups: ", map(&subject)) & newsgroup := tab(upto(',') | 0)
    match("references: ", map(&subject)) & refs := tab(0)
    (\address & *&subject = 0) & {
      writes(r, "To: " || address)
      write(r, if *\fullname > 0 then " (" || fullname || ")" else "")
      \subject & write(r, subject)
      \newsgroup & write(r, newsgroup)
      \refs & write(r, refs, " ", id)
      write(r, "In-reply-to: ", quoter, "'s message of ", date);
      write(r, "\nIn ", id, ", ", quoter, " writes:\n")
      break
    }
  }
  every write(r, " > ", !Spool[art])
  send(tfn, address) & {
    Status[art] ++:= 'RO'
    Status[art] --:= 'N'
  }
end

				# Put user in an editor with a temp
				# file, query for confirmation, if
				# necessary rewrite address, and send.
procedure send(what, where)
  local edstr, mailstr, done
  static console

  initial {
    if "UNIX" == &features then console := "/dev/tty"
    else if "MS-DOS" == &features then console := "CON"
    else stop("Please configure `console' in mr.icn.")
  }
  edstr := (getenv("EDITOR") | Editor) || " " || what || " < " || console
  system(edstr)
  upto('nN', query( "Send to " || where || " y/n? ")) & {
    if upto('yY', query("Save your draft y/n? ")) then 
      clear_line() & write("Your draft is saved in " || what || "\n")
    else clear_line() & remove(what)
    fail
  }
  clear_line()
  \Host & not find(map(Host), map(where)) & upto('!@', where) & {
    find("@", where) & where ? {
      name := tab(upto('@'))
      where := (move(1), tab(upto(' ') | 0)) || "!" || name
    }
    if find(".", Host) then where ||:= "@" || Host
    else where := Host || "!" || where
  }
  mailstr := Mail_cmd || " " || where || " < " || what
  done := system(mailstr)
  remove(what)
  return done
end


procedure forward(who, art)
  local out, tfn

  out := open(tfn := tempname(), "w")
  write(out, "To: " || who)
  write(out, "Subject: FYI (forwarded mail)\n")
  write(out, "-----[begin forwarded message]-----")
  every write(out, !Spool[art])
  write(out, "------[end forwarded message]------")
  send(tfn, who) & Status[art] ++:= 'F'
end

  
procedure newmail(address)
  local out, tfn

  out := open(tfn := tempname(), "w")
  write(out, "To: " || address)
  write(out, "Subject:\n")
  return send(tfn, address)
end


procedure showart(art, noshow, eoh)
  local out

  out := []
  every !Spool[art] ? {
    /eoh := *&subject = 0
    if \eoh | not match(map(!noshow), map(&subject)) then put(out, tab(0))
  }
  more("Message " || art, out, "End of Message " || art)
  Status[art] ++:= 'O'
  Status[art] --:= 'N'
end
      

procedure help()
  local hlist, item
  static pr, sts

  initial {
    pr := ["Append message to a file",
	   "Delete message", 
	   "eXit, without saving changes", 
	   "Forward message",
	   "Get new mail",
	   "Help", 
	   "List headers",
	   "Mail to a new recipient", 
	   "Next message", 
	   "Print message", 
	   "Quit, saving changes", 
	   "Reply to message", 
	   "Save message", 
	   "Undelete message", 
	   "View all headers",
	   "| pipe message to a command",
	   "+ next message",
	   "- previous message",
	   "! execute command",
	   "# make # current message",
	   " "]
    sts := ["New", "Old", "Replied-to", "Saved", 
	    "Deleted", "Forwarded", "Printed"]
  }
  hlist := []
  every !(pr ||| sts) ? {
    item := "  "
    item ||:= tab(upto(&ucase++'!|+-#') \1) || getval("md"|"so") || 
	move(1) || getval("me"|"se") || tab(0)
    put(hlist, item)
  }
  put(hlist, "")
  more("Commands & Status Symbols", hlist)
end

				# The second parameter specifies a
				# default response if the user presses
				# <return>.
procedure query(prompt, def)
  local ans

  clear_line()
  writes(prompt)
  ans := read()
  return (*ans = 0 & \def) | ans
end

				# Increment the count, then cycle
				# through again when user reaches the
				# end of the list.
procedure inc(art)

  if (art +:= 1) > *Status then art := 1
  return art
end


procedure more(header, what, footer)
  local ans, lines

  writes(getval("cl"))
  lines := 0
  \header & {
    write(getval("us") || header || getval("ue"))
    lines +:= 1
  }
  every !what ? {
    write(tab(0))
    ((lines +:= 1 + *&subject/getval("co")) % (getval("li") - 1) = 0) & {
      writes(getval("so") || 
	     "-MORE-(", (100 > (lines - 2)*100/*what) | 100, "%)" || 
	     getval("se"))
      ans := read() & clear_line()
      upto('nNqQ', ans) & fail
    }
  }
  \footer & {
    writes(getval("so") || footer || getval("se")) 
    read() & clear_line()
  }
end

procedure clear_line()

  return writes(getval("up") || getval("ce"))
end