Re: [PATCH] notmuch-emacs-mua: more options, some fixes

Subject: Re: [PATCH] notmuch-emacs-mua: more options, some fixes

Date: Sat, 07 Mar 2015 16:12:43 +0200

To: Tomi Ollila, notmuch@notmuchmail.org

Cc: tomi.ollila@iki.fi

From: Jani Nikula


On Sat, 07 Mar 2015, Tomi Ollila <tomi.ollila@iki.fi> wrote:
> Two new options added:
>    --from: specify alternate From: header
>    --bodytext: possibility to give body content from command line
>
> Non-forking escape() functionality, also escaping \ (backslash) characters.
>
> Without content arguments start emacs/emacsclient and run (notmuch-hello),
> in this case without assigning anything to message-exit-actions.
>
> Point is now positioned at the end of the content given last on command
> line, unless to: or subject: is missing -- in which case point is
> positioned at one of these headers (is both missing, at to:)
>
> The -nw option is now given to emacs in case --no-window-system command
> line option is provided (--no-window-system used to work with emacsclient
> only).
>
> In case EMACS or EMACSCLIENT environment variable was defined but being
> empty (null string), use emacs/emacsclient as command instead (null string
> as executable name gives confusing error message). Also $EMACS/$EMACSCLIENT
> executable name is now quoted to *NOT* split it to separate command line
> arguments on any $IFS variables there might be.
>
> Reorganized content argument handling to combine the content of same option
> given multiple times and make it look more readable when --print option is
> used. --body option also now checks the existence of the file to be
> included and if the file included does not end with newline trailing
> newline is inserted to the message buffer.
>
> As bash supports [[ ]] command and it's special expansion (or lack thereof)
> of variables (and ==, && and || inside) the few [ ]'s there were are now
> changed to this more advanced format.

As I mentioned on IRC, I think there are too many changes here for one
patch. One change at a time! In particular because I'm not sure if all
of this is really needed... like message_goto and point placement
handling. Starts to feel like all of this would be better handled with a
dedicated function in emacs!

BR,
Jani.

> ---
>
> The changes *NOT* taken from my 2 draft patches (*)
>
> 1) no -nw hacking
>
> 2) no mailto: handling
>
> (*) id:1421776424-24304-1-git-send-email-tomi.ollila@iki.fi (later, linking
> previous)
>
>
>  doc/man1/notmuch-emacs-mua.rst |   8 ++-
>  notmuch-emacs-mua              | 123 ++++++++++++++++++++++++++++++-----------
>  2 files changed, 99 insertions(+), 32 deletions(-)
>
> diff --git a/doc/man1/notmuch-emacs-mua.rst b/doc/man1/notmuch-emacs-mua.rst
> index eb47098..29f7e0c 100644
> --- a/doc/man1/notmuch-emacs-mua.rst
> +++ b/doc/man1/notmuch-emacs-mua.rst
> @@ -22,6 +22,9 @@ Supported options for **notmuch-emacs-mua** include
>          Use emacsclient, rather than emacs. This will start
>          an emacs daemon process if necessary.
>  
> +    ``--from=``\ <from>
> +        Specify alternate sender (From) of the message.
> +
>      ``-s, --subject=``\ <subject>
>          Specify the subject of the message.
>  
> @@ -37,6 +40,9 @@ Supported options for **notmuch-emacs-mua** include
>      ``-i, --body=``\ <file>
>          Specify a file to include into the body of the message.
>  
> +    ``--bodytext=``\ <text>
> +        Specify text content to be inserted into the body of the message.
> +
>      ``--no-window-system``
>          Even if a window system is available, use the current terminal
>  
> @@ -60,4 +66,4 @@ Name of emacsclient comment to invoke
>  SEE ALSO
>  ========
>  
> -**notmuch(1)**, **emacsclient(1)**, **mutt(1)**
> +**notmuch(1)**, **emacs(1)**, **emacsclient(1)**, **mutt(1)**
> diff --git a/notmuch-emacs-mua b/notmuch-emacs-mua
> index b8cbc82..17365b9 100755
> --- a/notmuch-emacs-mua
> +++ b/notmuch-emacs-mua
> @@ -1,4 +1,5 @@
>  #!/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
>  #
> @@ -18,25 +19,47 @@
>  # 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
>  
> +# escape: "expand" '\' to '\\' & '"' to '\"'
> +# Calling convention: escape -v var "$arg" (like in bash printf).
>  escape ()
>  {
> -    echo "${1//\"/\\\"}"
> +    local arg=${3//\\/\\\\}
> +    eval $2='${arg//\"/\\\"}'
>  }
>  
> -EMACS=${EMACS-emacs}
> -EMACSCLIENT=${EMACSCLIENT-emacsclient}
> +EMACS=${EMACS:-emacs}
> +EMACSCLIENT=${EMACSCLIENT:-emacsclient}
>  
>  PRINT_ONLY=
>  USE_EMACSCLIENT=
> -CLIENT_TYPE="-c"
> +EMACSCLIENT_TYPE='-c'
> +EMACS_NW=
>  
> -# The crux of it all: construct an elisp progn and eval it.
> -ELISP="(prog1 'done (require 'notmuch) (notmuch-mua-new-mail)"
> -ELISP="${ELISP} (setq message-exit-actions (list #'save-buffers-kill-terminal))"
> +exec_mua ()
> +{
> +    if [[ -n $PRINT_ONLY ]]; then
> +	echo "$1"
> +	exit
> +    fi
> +
> +    if [[ -n $USE_EMACSCLIENT ]]; then
> +	# Evaluate the progn.
> +	exec "${EMACSCLIENT}" ${EMACSCLIENT_TYPE} -a '' --eval "$1"
> +    else
> +	exec "${EMACS}" ${EMACS_NW} --eval "$1"
> +    fi
> +    exit not reached
> +}
> +
> +SUBJECT= TO= CC= BCC= BODY= FROM=
> +
> +unset message_goto # Final elisp function to execute, when defined.
> +cddone=false
>  
>  while getopts :s:c:b:i:hC opt; do
>      # Handle errors and long options.
> @@ -47,14 +70,14 @@ while getopts :s:c:b:i:hC opt; do
>  	    ;;
>  	\?)
>  	    opt=$1
> -	    if [ "${OPTARG}" != "-" ]; then
> +	    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=*)
> +		--subject=*|--to=*|--cc=*|--bcc=*|--body=*|--from=*|--bodytext=*)
>  		    OPTARG=${opt#--*=}
>  		    opt=${opt%%=*}
>  		    ;;
> @@ -71,9 +94,7 @@ while getopts :s:c:b:i:hC opt; do
>  	    ;;
>      esac
>  
> -
> -    OPTARG="${OPTARG-none}"
> -    OPTARG="$(escape "${OPTARG}")"
> +    escape -v OPTARG "${OPTARG-}"
>  
>      case "${opt}" in
>  	--help|h)
> @@ -82,26 +103,48 @@ while getopts :s:c:b:i:hC opt; do
>  	--client|C)
>  	    USE_EMACSCLIENT="yes"
>  	    ;;
> +	--from)
> +	    FROM=${OPTARG}
> +	    ;;
>  	--subject|s)
> -	    ELISP="${ELISP} (message-goto-subject) (insert \"${OPTARG}\")"
> +	    SUBJECT=${SUBJECT:+$SUBJECT }${OPTARG}
> +	    message_goto='(message-goto-subject)'
>  	    ;;
>  	--to)
> -	    ELISP="${ELISP} (message-goto-to) (insert \"${OPTARG}, \")"
> +	    TO=${TO:+$TO, }${OPTARG}
> +	    message_goto='(message-goto-to)'
>  	    ;;
>  	--cc|c)
> -	    ELISP="${ELISP} (message-goto-cc) (insert \"${OPTARG}, \")"
> +	    CC=${CC:+$CC, }${OPTARG}
> +	    message_goto='(message-goto-cc)'
>  	    ;;
>  	--bcc|b)
> -	    ELISP="${ELISP} (message-goto-bcc) (insert \"${OPTARG}, \")"
> +	    BCC=${BCC:+$BCC, }${OPTARG}
> +	    message_goto='(message-goto-bcc)'
>  	    ;;
>  	--body|i)
> -	    ELISP="${ELISP} (message-goto-body) (cd \"${PWD}\") (insert-file \"${OPTARG}\")"
> +	    if [[ ! -f ${OPTARG} ]]; then
> +	        echo "$0: '${OPTARG}': no such file" >&2
> +		exit 1
> +	    fi
> +	    if [[ $cddone == 'false' ]]; then
> +		BODY=${BODY}$'\n'"  (cd \"${PWD}\")"
> +		cddone=true
> +	    fi
> +	    BODY=${BODY}$'\n'"  (insert-file \"${OPTARG}\")"
> +	    BODY=${BODY}$'\n'"  (if (/= (point) (line-beginning-position)) (insert \"\\n\"))"
> +	    unset message_goto
> +	    ;;
> +	--bodytext)
> +	    BODY=${BODY}$'\n'"  (insert \"${OPTARG}\\n\")"
> +	    unset message_goto
>  	    ;;
>  	--print)
>  	    PRINT_ONLY=1
>  	    ;;
>  	--no-window-system)
> -	    CLIENT_TYPE="-t"
> +	    EMACSCLIENT_TYPE='-t'
> +	    EMACS_NW='-nw'
>  	    ;;
>  	*)
>  	    # We should never end up here.
> @@ -116,21 +159,39 @@ done
>  
>  # Positional parameters.
>  for arg; do
> -    arg="$(escape "${arg}")"
> -    ELISP="${ELISP} (message-goto-to) (insert \"${arg}, \")"
> +    escape -v _arg "${arg}"
> +    TO=${TO:+$TO, }${_arg}
> +    message_goto='(message-goto-to)'
>  done
>  
> -# End progn.
> -ELISP="${ELISP})"
> +# The newlines are here for better user experience when --print option is used.
> +NL=$'\n'
> +ELISP="\
> +${CC:+$NL  (message-goto-cc) (insert \"$CC\")}\
> +${BCC:+$NL  (message-goto-bcc) (insert \"$BCC\")}\
> +${BODY:+$NL  (message-goto-body)$BODY}"
>  
> -if [ -n "$PRINT_ONLY" ]; then
> -    echo ${ELISP}
> -    exit 0
> -fi
> -
> -if [ -n "$USE_EMACSCLIENT" ]; then
> -    # Evaluate the progn.
> -    exec ${EMACSCLIENT} ${CLIENT_TYPE} -a '' --eval "${ELISP}"
> +if [[ $TO == '' && $SUBJECT == '' && $ELISP == '' ]]
> +then
> +    exec_mua "(prog1 'done (require 'notmuch) (notmuch-hello))"
>  else
> -    exec ${EMACS} --eval "${ELISP}"
> +    [[ $FROM != '' ]] && OH="(list (cons 'From \"$FROM\"))" || OH=nil
> +
> +    if [[ $SUBJECT == '' ]]; then
> +	SUBJECT=nil
> +	message_goto='(message-goto-subject)'
> +    else
> +	SUBJECT=\"$SUBJECT\"
> +    fi
> +    if [[ $TO == '' ]]; then
> +	TO=nil
> +	message_goto='(message-goto-to)'
> +    else
> +	TO=\"$TO\"
> +    fi
> +    exec_mua "(prog1 'done (require 'notmuch)
> +  (setq message-exit-actions (list #'save-buffers-kill-terminal))
> +  (notmuch-mua-mail ${TO} ${SUBJECT}
> +	${OH} nil (notmuch-mua-get-switch-function))\
> +${ELISP}${NL}  (set-buffer-modified-p nil)${message_goto+ $message_goto})"
>  fi
> -- 
> 2.1.0
>
> _______________________________________________
> notmuch mailing list
> notmuch@notmuchmail.org
> http://notmuchmail.org/mailman/listinfo/notmuch

Thread: