ref: 8130672a9e99e52ac95d3bc11d1adff7d3ffaccc
	author: sl <sl@gaff>
	date: Thu Aug 21 21:58:15 EDT 2025
	
initial import
--- /dev/null
+++ b/README
@@ -1,0 +1,65 @@
+mother
+======
+
+Mother is an [rc(1)](http://man.9front.org/1/rc) script that provides
+an experience similar to
+[nedmail(1)](http://man.9front.org/1/nedmail).
+
+Download it [here](http://only9fans.com/sl/mother/HEAD/info.html).
+
+help
+----
+
+ usage: mother [ -d ] [ -f mbox ] [ -p msg ] [ -r ]
+
+ Commands are of the form [<range>] <command> [args]
+ <range> := <addr> | <addr>,<addr>
+ <command> :=
+ a reply to sender and recipients
+ b print the next ten headers
+ d mark for deletion
+ e ... enter message (args passed to upas/marshal)
+ g/regexp/cmd grep headlines for regexp and run cmd on matches
+ h print message headline (,h for all)
+ help print this help message
+ m ... forward mail to address(es)
+ mb ... change to specified mailbox
+ p print the processed message
+ P print the raw message
+ q quit
+ r reply to message
+ s ... store message in specified mailbox
+ u remove deletion mark
+ y synchronize with mail box
+ " print message in quoted form, suitable for reply
+ |cmd pipe the processed message to a command
+ ||cmd pipe the raw message to a command
+ !cmd run a command
+
+extensions
+----------
+
+A script may be used in conjunction with
+[faces(1)](http://man.9front.org/1/faces) and the
+[plumber(4)](http://man.9front.org/4/plumber) to open individual
+messages (or the entire mailbox) by clicking mb1 on a face in the
+[faces(1)](http://man.9front.org/1/faces) window. Each click produces
+a new window containing the selected message.
+
+The script:
+[facemother](https://plan9.stanleylieber.com/rc/facemother) <- read
+and modify as needed before using
+
+The plumber rule:
+
+ # faces -> new mail window for message
+ type is text
+ data matches '[a-zA-Z¡-0-9_\-./]+'
+ data matches '/mail/fs/[a-zA-Z¡-0-9/]+/[0-9]+'
+ plumb to showmail
+ plumb start window -r 646 117 1366 676 facemother $0
+
+screenshots
+-----------
+
+
--- /dev/null
+++ b/facemother
@@ -1,0 +1,19 @@
+#!/bin/rc
+# 2018-02-15T21:12:06-0500
+# helper program for launching http://plan9.stanleylieber.com/mother from faces(1).
+rfork en
+
+# plumber rules for faces(1)
+#type is text
+#data matches '[a-zA-Z¡-0-9_\-./]+'
+#data matches '/mail/fs/[a-zA-Z¡-0-9/]+/[0-9]+'
+#plumb to showmail
+#plumb start window -r 583 206 1303 1200 facemother $0
+
+# open and print only the indicated message
+msg=`{sed -n 19p $1/info}+upas/fs -f /mail/box/sl/mbox/$msg
+mother -p 1
+
+# use existing mailbox, print the indicated message
+#mother -p `{basename $1}binary files /dev/null b/img/ello.co.png differ
--- /dev/null
+++ b/index.md
@@ -1,0 +1,65 @@
+mother
+======
+
+Mother is an [rc(1)](http://man.9front.org/1/rc) script that provides
+an experience similar to
+[nedmail(1)](http://man.9front.org/1/nedmail).
+
+Download it [here](http://only9fans.com/sl/mother/HEAD/info.html).
+
+help
+----
+
+ usage: mother [ -d ] [ -f mbox ] [ -p msg ] [ -r ]
+
+ Commands are of the form [<range>] <command> [args]
+ <range> := <addr> | <addr>,<addr>
+ <command> :=
+ a reply to sender and recipients
+ b print the next ten headers
+ d mark for deletion
+ e ... enter message (args passed to upas/marshal)
+ g/regexp/cmd grep headlines for regexp and run cmd on matches
+ h print message headline (,h for all)
+ help print this help message
+ m ... forward mail to address(es)
+ mb ... change to specified mailbox
+ p print the processed message
+ P print the raw message
+ q quit
+ r reply to message
+ s ... store message in specified mailbox
+ u remove deletion mark
+ y synchronize with mail box
+ " print message in quoted form, suitable for reply
+ |cmd pipe the processed message to a command
+ ||cmd pipe the raw message to a command
+ !cmd run a command
+
+extensions
+----------
+
+A script may be used in conjunction with
+[faces(1)](http://man.9front.org/1/faces) and the
+[plumber(4)](http://man.9front.org/4/plumber) to open individual
+messages (or the entire mailbox) by clicking mb1 on a face in the
+[faces(1)](http://man.9front.org/1/faces) window. Each click produces
+a new window containing the selected message.
+
+The script:
+[facemother](https://plan9.stanleylieber.com/rc/facemother) <- read
+and modify as needed before using
+
+The plumber rule:
+
+ # faces -> new mail window for message
+ type is text
+ data matches '[a-zA-Z¡-0-9_\-./]+'
+ data matches '/mail/fs/[a-zA-Z¡-0-9/]+/[0-9]+'
+ plumb to showmail
+ plumb start window -r 646 117 1366 676 facemother $0
+
+screenshots
+-----------
+
+
--- /dev/null
+++ b/mkfile
@@ -1,0 +1,9 @@
+web:V:
+ cp README index.md
+ if(test -f mother.tgz)
+ rm mother.tgz
+ cd ..
+ tar zcvf /tmp/mother.tgz mother
+ cd mother
+ cp /tmp/mother.tgz ../src/
+ rm /tmp/mother.tgz
--- /dev/null
+++ b/mother
@@ -1,0 +1,504 @@
+#!/bin/rc
+# 2022-11-16T18:11:20-05:00
+# Mother wants to talk to you.
+# Similar to nedmail. Use with 9front or nupas/fs (creates mdir format files on save).
+# BONUS: Helper program for use with faces(1): http://plan9.stanleylieber.com/mother/facemother
+rfork en
+ramfs -p
+argv0=$0
+if(~ $#editor 0)
+ editor=hold
+if(~ $#pager 0)
+ pager=cat
+mb=mbox
+msg=()
+sort=-r
+fn d{+	if(test $1 -le $#rposts && test -d $rposts($1)){+ flag +D $1 &&
+ dposts=($dposts $1) ||
+ echo !delete $1 failed
+ }
+ if not
+ echo !address
+}
+fn deldposts{+
+
+ ndel=()
+	for(i in $dposts){+		if(test $i -le $#rposts && test -d $rposts($i)){+ echo delete $mb $rposts($i) >/mail/fs/ctl &&
+ echo !deleted $i &&
+ ndel=($ndel $i) ||
+ echo !delete $i failed
+ }
+ }
+ echo !$#ndel messages deleted
+
+
+}
+fn e{+ >/tmp/e &&
+ eval $editor /tmp/e &&
+ yn send &&
+ if(~ $yn y)
+ /bin/upas/marshal $* </tmp/e
+}
+fn fakefile{+	if(! ~ $#file 0 || ~ $disp file inline){+ file='(file,'$"file')'
+		fake=`{+ if(~ $type *gif *GIF)
+ echo body.gif
+ if not if(~ $type *jpeg *JPEG *jpg *JPG)
+ echo body.jpg
+ if not if(~ $type *png *PNG)
+ echo body.png
+ if not if(~ $file *.*)
+				echo body.^`{echo $file | sed 's/(^.*\.|\)$)//g'}+ if not
+ echo body
+ }
+		filepath=`{echo -n /mail/fs/$mb/^$1^/$fake}+		size=`{du $1^/$fake}+ echo !--- $1 $type $size(1) $file [$filepath]
+ }
+}
+fn flag{+	if(~ $1 [\+][aDdfrSs] [-][aDdfrSs]){+ echo $1 >$rposts($2)^/flags &&
+ puth $2 ||
+ echo !address
+ }
+}
+fn fmtd{+	date=`{read}+	switch($date(2)){+ case Jan; mo=1
+ case Feb; mo=2
+ case Mar; mo=3
+ case Apr; mo=4
+ case May; mo=5
+ case Jun; mo=6
+ case Jul; mo=7
+ case Aug; mo=8
+ case Sep; mo=9
+ case Oct; mo=10
+ case Nov; mo=11
+ case Dec; mo=12
+ }
+	switch($date(3)){+ case [0-9]
+ da=0^$date(3)
+ case *
+ da=$date(3)
+ }
+	switch($date(6)){+	case `{date | awk '{print $6;}'}+		ti=`{echo $date(4) | awk '{print substr($0,0,5);}'}+ case *
+ ti=$date(6)
+ }
+ echo $mo/$da $ti
+}
+fn geth{+	for(i in $*){+		flags=`{sed -n 18p $rposts($i)^/info | sed 's/-//g'}+		mime=`{+			if(~ `{sed -n 7p $rposts($i)^/info} multipart*)+ echo H
+ }
+		size=`{sed -n 17p $rposts($i)^/info}+		date=`{sed -n 5p $rposts($i)^/info | fmtd}+		from=`{sed -n 1p $rposts($i)^/info}+		subject=`{sed -n 6p $rposts($i)^/info | awk '{print substr($0,0,50);}'}+ # Unicode 00a0 divides the message number from the headline.
+ # Command input ignores everything after the unicode 00a0.
+ # These lines may be selected and sent to the prompt
+ # in order to print the indicated message.
+ echo ' '$"i' '$"mime' '$"flags' '$"size' '$"date' '$"from' '$"subject
+ }
+}
+fn getposts{ ls | grep -e '^[0-9]+$' | sort -n $sort }+fn getr{+	switch($*){+ case ,; echo $posts
+	case ,*;	seq 1 `{echo $* | sed 's/,//g'}+	case *,;	seq `{echo $* | sed 's/,//g'} $posts($#posts)+	case *,*;	seq `{echo $* | sed 's/,/ /g'}+ case *; echo $*
+ }
+}
+fn h{ sed -n $1^p /tmp/h }+fn m{+	if(test $1 -le $#rposts && test -f $rposts($1)^/info){+		subject=`{sed -n 6p $rposts($1)^/info}+ if(! ~ $subject FWD:* Fwd:* fwd:*)
+ subject=(Fwd: $subject)
+ e -s $"subject -A $rposts($1)^/raw $*(2-) &&
+ flag +a $1
+ }
+ if not
+ echo !address
+}
+fn mb{+ mb=$1
+	if(test -d /mail/box/$user/$mb || test -d $mb){+		if(! ~ $mb mbox){+ if(~ $mb /mail/fs/*)
+				mb=`{basename $mb}+ if not if(~ $mb /*)
+				echo open $mb `{basename $mb} >/mail/fs/ctl+ if not
+ echo open /mail/box/$user/$mb $mb >/mail/fs/ctl
+			mb=`{basename $mb}+ }
+ cd /mail/fs/$mb
+ dposts=()
+ y
+ post=$posts(1)
+ prompt=$post
+ }
+ if not
+ echo !^$mb does not exist
+}
+fn printhelp{+echo 'Commands are of the form [<range>] <command> [args]
+<range> := <addr> | <addr>','<addr>
+<command> :=
+a reply to sender and recipients
+b print the next ten headers
+d mark for deletion
+e ... enter message (args passed to upas/marshal)
+g/regexp/cmd grep headlines for regexp and run cmd on matches
+h print message headline (,h for all)
+help print this help message
+m ... forward mail to address(es)
+mb ... change to specified mailbox
+p print the processed message
+P print the raw message
+q quit
+r reply to message
+s ... store message in specified mailbox
+u remove deletion mark
+y synchronize with mail box
+" print message in quoted form, suitable for reply
+|cmd pipe the processed message to a command
+||cmd pipe the raw message to a command
+!cmd run a command'
+}
+fn pp{+if(test $1 -le $#rposts && test -f $rposts($1)^/header){+{ # Avoid stutter by dumping everything into a file first.+ cat $rposts($1)^/header
+ echo
+	if(test -d $rposts($1)^/1){+		parts=`{ls -p $rposts($1) | grep -e '^[0-9]+'}+ body=1/body
+ if(test -f $rposts($1)^/1/1/body)
+ body=1/1/body
+ }
+	if not{+ parts=()
+ body=body
+ }
+	type=`{file -m $rposts($1)^/$body}+ if(~ $type text/plain)
+ cat $rposts($1)^/$body
+	if not if(~ $type text/html){+ hcmd=(htmlfmt -l60 -cutf8 -a $rposts($1)^/$body)
+ echo !/bin/^$"hcmd
+ eval $hcmd
+ echo
+		echo !--- $rposts($1) $type `{du $rposts($1)^/$body | awk '{print $1}'} [file:///mail/fs/$mb/^$rposts($1)^/$body]	# plumb to browser+ }
+	if not{+		disp=`{sed -n 8p $rposts($1)^/info}+		file=`{sed -n 9p $rposts($1)^/info}+ fakefile $rposts($1)
+ }
+ echo
+	if(! ~ $#parts 0){+ if(! ~ $#parts 1)
+ parts=$parts(2-)
+		for(j in $parts){+			type=`{file -m $rposts($1)^/$j/body}+			disp=`{sed -n 8p $rposts($1)^/$j/info}+			file=`{sed -n 9p $rposts($1)^/$j/info}+ fakefile $rposts($1)^/$j
+ }
+ parts=()
+ }
+} >/tmp/p
+ eval $pager /tmp/p
+ go=1
+ r=$1
+ post=$1
+ prompt=$1
+ flag +s $1
+}
+if not
+ echo !address
+}
+fn P{+	if(test $1 -le $#rposts && test -f $rposts($1)^/rawunix){+ eval $pager $rposts($1)^/rawunix
+ go=1
+ r=$1
+ post=$1
+ prompt=$1
+ flag +s $1
+ }
+ if not
+ echo !address
+}
+fn puth{+	flags=`{sed -n 18p $rposts($1)^/info | sed 's/-//g'}+	mime=`{+		if(~ `{sed -n 7p $rposts($1)^/info} multipart*)+ echo H
+ }
+	size=`{sed -n 17p $rposts($1)^/info}+	date=`{sed -n 5p $rposts($1)^/info | fmtd}+	from=`{sed -n 1p $rposts($1)^/info}+	subject=`{sed -n 6p $rposts($1)^/info | awk '{print substr($0,0,50);}'}+	{+ echo $1
+ echo c
+ # REMEMBER: Unicode 00a0 divides the message number from the headline.
+ echo ' '^$1^' '$"mime' '$"flags' '$"size' '$"date' '$"from' '$"subject
+ echo .
+ echo w
+ echo q
+ } | sam -d /tmp/h >/dev/null >[2=1]
+}
+fn r{+	if(test $1 -le $#rposts && test -f $rposts($1)^/info){+		subject=`{sed -n 6p $rposts($1)^/info}+ if(! ~ $subject RE:* Re:* re:*)
+ subject=(Re: $subject)
+		e -R $rposts($1) -s $"subject $*(2-) `{sed -n 4p $rposts($1)^/info} &&+ flag +a $1
+ }
+ if not
+ echo !address
+}
+fn s{+	if(test $1 -le $#rposts && test -f $rposts($1)^/raw){+ if(! test -d /mail/box/$user/$2)
+ echo create $2 >/mail/fs/ctl
+ /bin/upas/mbappend $2 $rposts($1)^/raw &&
+ flag +S $1 &&
+ echo !saved in $2
+ }
+ if not
+ echo !address
+}
+fn u{+ if(test $1 -le $#rposts && test -d $rposts($1))
+ flag -D $1 || echo !undelete $1 failed
+ if not
+ echo !address
+	dposts=`{grep -v $1 <{for(j in $dposts){ echo $j }}}+}
+fn y{+ go=()
+ r=$post
+	if(! ~ $#dposts 0){+ deldposts
+ dposts=()
+ }
+	if(! ~ $q 1){+		rposts=`{getposts}+		posts=`{seq 1 $#rposts}+ post=$posts(1)
+ prompt=$post
+ geth $posts >/tmp/h
+ if(~ $#msg 0)
+ echo $#posts messages
+ }
+}
+fn yn{+ echo
+ echo -n $* ' (y, n) '
+	yn=`{read}+	switch($yn){+ case y n
+ ;
+ case *
+ yn
+ }
+}
+fn '"' { pager=cat pp $1 | sed 1d | sed 's/^/> /g' | sed 's/^> >/>>/g' }+fn usage{+ echo usage: $argv0 [ -b ] [ -d ] [ -f mbox ] [ -p msg ] [ -r ] >[1=2]
+ exit usage
+}
+while(~ $1 -*){+	switch($1){+ case -b; biff=-b
+ case -d; debug=1
+ case -f; mb=$2; shift
+ case -p; msg=$2; shift
+ case -r; sort=(); shift
+ case *; usage
+ }
+ shift
+}
+if(! ~ $#* 0)
+ usage
+if(! test -f /mail/fs/ctl)
+ /bin/upas/fs $biff #>[2]/dev/null
+if(! test -d /mail/box/$user/$mb && ! test -d $mb){+ echo !^$mb does not exist
+ exit $mb^' does not exist'
+}
+mb $mb
+if(! ~ $#msg 0){+	for(i in `{seq 1 $#rposts})+ if(~ $msg $rposts($i))
+ msg=$posts($i)
+ pp $msg
+}
+while(){+ echo -n $"prompt': '
+ # Command input ignores everything after unicode 00a0.
+	rcmd=`{read | sed 's/[ ].*$//g' | sed 's/^([0-9]+)?(,)?([0-9]+)?/& /g'}+	switch($rcmd){+ case ,* [0-9]*
+		r=`{getr $rcmd(1)}+ cmd=$rcmd(2-)
+ if(~ $#cmd 0)
+ cmd=p
+ case *
+ r=$post
+ cmd=$rcmd
+ }
+	switch($cmd){+ case a a' '*
+ for(i in $r)
+			r $i $cmd(2-) `{sed -n 2,3p $rposts($i)^/info | sort -n | uniq}+ post=$r($#r)
+ prompt=$post
+ case b
+		r=`{seq $r(1) `{echo $r(1)^+10|bc}}+ if(test $r($#r) -gt $posts($#posts))
+			r=`{seq $r(1) $posts($#posts)}+		if(! ~ $#r 0 && test $r(1) -le $posts($#posts)){+ sed -n $r(1)^,$r($#r)^p /tmp/h
+ post=$r($#r)
+ prompt=$post
+ }
+ if not
+ echo !address
+ case d
+ for(i in $r)
+ d $i
+ post=$r($#r)
+ prompt=$post
+ case e' '*
+ e $cmd(2-)
+ case g/*
+		regexp=`{echo $cmd | awk -F '/' '{print $2;}'} # BUG: / is stripped from regexp and cmd.+		cmd=`{echo $cmd | awk -F '/' '{$1=""; $2=""; print;}'}+		r=`{</tmp/h grep -e $"regexp | sed 's/ .*$//g'} # Strip everything after unicode 00a0.+ if(~ $#cmd 0)
+ cmd=p
+		if(~ $cmd d h p P u '"'){+ for(i in $r)
+ eval $cmd $i
+ }
+		if not if(~ $cmd a' '* m' '* r' '* s' '*){+ for(i in $r)
+ eval $cmd(1) $i $cmd(2-)
+ }
+ if not
+ echo !illegal command
+ post=$r($#r)
+ prompt=$post
+ case h
+		{+ for(i in $r)
+ h $i
+ } | eval $pager
+ post=$r($#r)
+ prompt=$post
+ case help
+ printhelp
+ case m' '*
+ for(i in $r)
+ m $i $cmd(2-)
+ post=$r($#r)
+ prompt=$post
+ case mb' '*
+ mb $cmd(2-)
+ case p
+ for(i in $r)
+ pp $i
+ case P
+ for(i in $r)
+ P $i
+ case q
+ q=1 y # BUP STOP
+ exit ''
+ case r r' '*
+ for(i in $r)
+ r $i $cmd(2-)
+ post=$r($#r)
+ prompt=$post
+ case s' '*
+
+
+ for(i in $r)
+ s $i $cmd(2-)
+
+
+ post=$r($#r)
+ prompt=$post
+ case u
+
+
+ for(i in $r)
+ u $i
+
+
+ post=$r($#r)
+ prompt=$post
+ case y
+ y
+ case '"'
+ for(i in $r)
+ '"' $i | eval $pager
+ post=$r($#r)
+ prompt=$post
+ case '?'
+ echo dposts: $dposts
+ echo rposts: $rposts
+ echo posts: $posts
+ echo post: $post
+ echo r: $r
+ if(! ~ $#mlpath 0)
+			echo mdir: $mlpath^/^`{sed -n 19p $r/info}+ if(! ~ $#msg 0)
+ echo msg: $msg
+ case '|'*
+ for(i in $r)
+			pager=cat pp $i | eval `{echo $cmd | sed 's/^.//'}+ case '||'*
+ for(i in $r)
+			pager=cat P $i | eval `{echo $cmd | sed 's/^..//'}+ case '!'*
+ for(i in $r)
+			eval `{echo $cmd | sed 's/^.//'}+ case *
+ if(~ $post $posts(1) && ~ $#go 0)
+ pp $post
+		if not if(! ~ $post $posts($#posts)){+			post=`{echo $post^+1 | bc}+ if(test $post -gt $posts($#posts))
+ post=$posts($#posts)
+ pp $post
+ }
+ }
+}
--
⑨