Re: [DRAFT PATCH] modified notmuch-emacs-mua

Subject: Re: [DRAFT PATCH] modified notmuch-emacs-mua

Date: Sat, 01 Nov 2014 10:51:51 +0200

To: Jani Nikula, notmuch@notmuchmail.org, david@tethera.net, jrollins@finestructure.net

Cc:

From: Tomi Ollila


On Wed, Oct 29 2014, Jani Nikula <jani@nikula.org> wrote:

Thanks for quick response. 

There are only one (and half) thing that needs to be resolved before
first inclusion, the desire for more versatile options can be discussed
later...

> On Fri, 11 Jul 2014, Tomi Ollila <tomi.ollila@iki.fi> wrote:
>> Highlights:
>>
>> * notmuch-emacs-mua without arguments runs (notmuch-hello)
>>
>> * runs emacs(1) in case emacsclient(1) fails to connect to running emacs
>>
>> * takes -nw option
>>
>> * handles mailto:
>>
>> * --from option when sending non-mailto: way
>>
>> * -i includes file --body[= ]string inserts string
>> ---
>>  notmuch-emacs-mua | 200 ++++++++++++++++++++++++++++++++++++++++++++++++++++++
>>  1 file changed, 200 insertions(+)
>>  create mode 100755 notmuch-emacs-mua
>>
>> diff --git a/notmuch-emacs-mua b/notmuch-emacs-mua
>> new file mode 100755
>> index 0000000..b1696f7
>> --- /dev/null
>> +++ b/notmuch-emacs-mua
>> @@ -0,0 +1,200 @@
>> +#!/usr/bin/env bash
>> +# -*- mode: shell-script; sh-basic-offset: 4; tab-width: 8 -*-
>> +#
>> +# notmuch-emacs-mua - start composing a mail on the command line
>> +#
>> +# Copyright © 2014 Jani Nikula
>> +#
>> +# This program is free software: you can redistribute it and/or modify
>> +# it under the terms of the GNU General Public License as published by
>> +# the Free Software Foundation, either version 3 of the License, or
>> +# (at your option) any later version.
>> +#
>> +# This program is distributed in the hope that it will be useful,
>> +# but WITHOUT ANY WARRANTY; without even the implied warranty of
>> +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
>> +# GNU General Public License for more details.
>> +#
>> +# You should have received a copy of the GNU General Public License
>> +# along with this program.  If not, see http://www.gnu.org/licenses/ .
>> +#
>> +# Authors: Jani Nikula <jani@nikula.org>
>> +#          Tomi Ollila <tomi.ollila@iki.fi>
>> +#
>> +
>> +set -eu
>> +
>> +# "expand" '\' to '\\' & '"' to '\"'
>> +escape_optarg ()
>> +{
>> +    OPTARG=${OPTARG//\\/\\\\}; OPTARG=${OPTARG//\"/\\\"}
>> +}
>> +
>> +# ditto, in case there is '\n' sequence in the source, otherwise
>> +# "expand" only trailing '\'s to '\\'s
>> +escape_body_optarg ()
>> +{
>> +    case ${OPTARG} in
>> +	*'\"'*) OPTARG=${OPTARG//\\/\\\\} ;;
>> +	*'\') OPTARG=$( printf %s "${OPTARG}" | sed 's/\(\\*\)$/\1\1/' )
>> +    esac
>> +    OPTARG=${OPTARG//\"/\\\"}
>> +}
>> +
>> +unset ALTERNATE_EDITOR
>> +exec_mua ()
>> +{
>> +    if "${EMACSCLIENT:=emacsclient}" --eval t >/dev/null 2>&1
>> +    then
>> +	emacs=$EMACSCLIENT
>> +	# close stdout in case no -nw (and no --print)
>> +	test -n "$W$X" || exec >/dev/null
>> +    else
>> +	emacs=${EMACS:-emacs}
>> +    fi
>> +    ${X:-exec} "$emacs" $W --eval "$*"
>> +    exit
>> +    ${X:-exec "$emacs" $W --eval} "$*"
>> +}
>> +
>> +X=
>> +W=
>> +
>> +SUBJECT= TO= CC= BCC= BODY= FROM= IB=
>> +
>> +while
>> +    # first, handle "long" options which cannot be handled by getopts
>> +    case ${1-} in
>> +	-nw)
>> +	    W=-nw
>> +	    shift
>> +	    continue
>> +	    ;;
>
> How about generalizing this into letting the user pass arguments after a
> "--" separator to emacs(client)? Would that work?

That would technically work and be very easy to do. I don't know whether
this is good option -- and such can be added later if decided.

> Alternatively, why support -nw which is neither part of the mutt
> interface I was originally aiming at emulating nor conforms to notmuch
> style? Just go with --nw or the more verbose --no-window-system (which
> is also compatible with emacs).

The original mutt interface is there -- as this is notmuch-*emacs*-mua
this could also have the original emacs -nw option (--nw came later, and
is supported by the loop).

But, what is the option is not important, as long as there is at least one
emacs-compatible option to have this feature (--nw). (for now, Let's see
whether I get irritated that notmuch-emacs-mua -nw does not work -- as I
still have ESC g bound to goto-line as I cannot get used to ESC g g ;)

>> +	mailto:*)
>> +	    oIFS=$IFS; IFS=; OPTARG="$*" IFS=$oIFS
>> +	    escape_optarg
>> +	    exec_mua "(progn (require 'notmuch) (browse-url-mail \"$OPTARG\"))"
>> +	    exit
>> +    esac
>
> Why does mailto: need to be handled here? I think you could either make
> the usage have a special mailto: case where you only have mailto: at $1,
> or you handle mailto: as a positional parameter at the end.

To handle this here is probably a slip as i started using notmuch-emacs-mua
as a drop-in replacement from notmuch-emacs-mailto (where I added
possibility to do -nw).

I agree that it is better to just check first arg for mailto: prefix
and if matches, work accordingly. 

> With these, you could drop this one extra case here, and go back to the
> more natural "while getopts" argument parsing loop.

I agree to convert to "while getopts" loop which *apparently* feels more
natural to most of the developers ;D

>> +
>> +    getopts :s:c:b:i:h opt
>> +do
>> +    # Handle errors and long options.
>> +    case ${opt} in
>> +	:)
>> +	    echo "$0: short option '-${OPTARG}' requires an argument." >&2
>> +	    exit 1
>> +	    ;;
>> +	\?)
>> +	    opt=$1
>> +	    if [[ ${OPTARG} != '-' ]]; then
>> +		echo "$0: unknown short option '-${OPTARG}'." >&2
>> +		exit 1
>> +	    fi
>> +
>> +	    case ${opt} in
>> +		# Long options with arguments.
>> +		--subject=*|--to=*|--cc=*|--bcc=*|--body=*|--from=*)
>> +		    OPTARG=${opt#--*=}
>> +		    opt=${opt%%=*}
>> +		    ;;
>> +		# Long options with argument in next arg.
>> +		--subject  |--to  |--cc  |--bcc  |--body  |--from  )
>
> This may be git-ish, but I don't think we should support this in
> notmuch.

This is also emacs-ish, tar-ish, whatnot-ish (with notable exception of
google-chrome). 

But we can go with only supporting --longarg=val option now, and bikeshed
that more if anyone wants to -- From some reason when command starts with
'notmuch' I tend to write = to separate long option with arg (and have
always done so with this script when not testing), although SPC is easier
to press that shift-0 (which produces '=' on finnish keyboard ;)

>> +		    if [[ $# < 2 ]]; then
>> +			echo "$0: option '${opt}' requires an argument." >&2
>> +			exit 1
>> +		    fi
>> +		    OPTARG=$2
>> +		    OPTIND=$((OPTIND + 1))
>> +		    ;;
>> +		# Long options without arguments.
>> +		--help|--nw|--print)
>> +		    ;;
>> +		*)
>> +		    echo "$0: unknown long option '${opt}', or argument mismatch." >&2
>> +		    exit 1
>> +		    ;;
>> +	    esac
>> +	    # getopts does not do this for what it considers errors.
>> +	    OPTIND=$((OPTIND + 1))
>> +	    ;;
>> +    esac
>> +
>> +    case ${opt} in
>> +	--help|h)
>> +	    exec man notmuch-emacs-mua
>> +	    ;;
>> +	--from)
>> +	    escape_optarg
>> +	    FROM=${OPTARG}
>> +	    ;;
>> +	--subject|s)
>> +	    escape_optarg
>> +	    SUBJECT=${SUBJECT:+$SUBJECT }${OPTARG}
>> +	    ;;
>> +	--to)
>> +	    escape_optarg
>> +	    TO=${TO:+$TO, }${OPTARG}
>> +	    ;;
>> +	--cc|c)
>> +	    escape_optarg
>> +	    CC=${CC:+$CC, }${OPTARG}
>> +	    ;;
>> +	--bcc|b)
>> +	    escape_optarg
>> +	    BCC=${BCC:+$BCC, }${OPTARG}
>> +	    ;;
>> +	i)
>> +	    escape_optarg
>> +	    if [[ ! -f ${OPTARG} ]]; then
>> +	        echo "$0: '${OPTARG}': no such file" >&2
>> +		exit 1
>> +	    fi
>> +	    IB=${IB}$'\n'"  (insert-file \"${OPTARG}\")"
>
> This needs the (cd \"${PWD}\") bit because --body given here may be
> relative to the script, while emacs likely has a totally different cwd.

Yes, the drop of that was accidental and not intentional. We could also
escape $PWD too, in case user does:

mkdir '.")(shell-command "/bin/rm -rf ~'; cd ...

although that is probably not accidental... well have to think for
cases user's PWD contains just one '"' for some reason..

>> +	    IB=${IB}$'\n'"  (if /= (point) (line-beginning-position) (insert \"\\n\"))"
>> +	    ;;
>> +	--body)
>> +	    escape_body_optarg
>> +	    IB=${IB}$'\n'"  (insert \"${OPTARG}\\n\")"
>
> Why should --body and -i be different?

Now, this is the one that is important to get decided now!

The -i is different from other options as it loads a file to be added to
the mail buffer, when all other options put the option value to the buffer.
an analogous option to that would IMO be --insert (or --include). 

Also, I desire an option to write mail content lines from command line
option directly. To me --body (which is not mutt(1) option) to work
as adding the variable to mail buffer is more consistent with the other
options -- and we could add this --insert as long option to -i.

Anyway, whatever the option to be able to insert body content from command
line is desired -- if there is any better choice :)

I also think that s/escape_body_optarg/escape_optarg/ (and dropping 
escape_body_optarg) should be done. escape_body_optarg would have
left e.g. "\n"s there and let emacs add newlines there. But this is
some hidden emacs-specific magic and might not be compatible with
e.g. notmuch-vim-mua (provided that someone(tm) will write such an utility).

>> +	    ;;
>> +	--nw)
>> +	    W=-nw

I could change this to W=--nw :)

>> +	    ;;
>> +	--print)
>> +	    X=echo
>> +	    ;;
>> +	*)
>> +	    # We should never end up here.
>> +	    echo "$0: internal error (option '${opt}')." >&2
>> +	    exit 1
>> +	    ;;
>> +    esac
>> +
>> +    shift $((OPTIND - 1))
>> +    OPTIND=1
>> +done
>> +
>> +# Positional parameters.
>> +for arg; do
>> +    arg=${arg//\\/\\\\}; arg=${arg//\"/\\\"}
>> +    TO=${TO:+$TO, }${arg}
>> +done
>> +
>> +NL=$'\n'
>> +ELISP="\
>> +${CC:+$NL  (message-goto-cc) (insert \"$CC\")}\
>> +${BCC:+$NL  (message-goto-bcc) (insert \"$BCC\")}\
>> +${IB:+$NL  (message-goto-body)$IB}"
>> +
>> +if [[ $TO == '' && $SUBJECT == '' && $ELISP == '' ]]
>> +then
>> +    exec_mua "(progn (require 'notmuch) (notmuch-hello))"
>> +else
>> +    [[ $FROM != '' ]] && OH="(list (cons 'From \"$FROM\"))" || OH=nil
>> +    [[ $TO != '' ]] && TO=\"$TO\" || TO=nil
>> +    [[ $SUBJECT != '' ]] && SUBJECT=\"$SUBJECT\" || SUBJECT=nil
>> +    exec_mua "$NL(progn (require 'notmuch)
>> +  (notmuch-mua-mail $TO $SUBJECT
>> +	$OH nil (notmuch-mua-get-switch-function))\
>> +$ELISP$NL  (set-buffer-modified-p nil) (message-goto-to))"
>
> I think the newlines with $NL hurt readability in the script more than
> they help readability in the output... which is generally interpreted by
> emacs which doesn't really care about the newlines! ;)

This is half thing left to be decided now. The $NL:s make --print -output
(very!) pretty. 

I can make $NL be empty in case --print is not given -- and and another
variable to contain 0/2 spaces depending on --print value to make emacs
and human viewer happy... >;)

I personally don't think those 4 $NL:s make the readability of the script
much worse -- but I can turn those to ${NL}:s if that help. It seems
your original code uses those more and I have not -- not because of a
style issue but because I am just so accustomed to do so... That said
I take more care of those in future versions...

>
> BR,
> Jani.

Tomi

>
>
>> +fi
>> -- 
>> 1.9.0

Thread: