############################################################################
#
#	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
