#!/bin/sh
#
# emacsc(1) - a wrapper of emacsclient(1)
#
# Copyright (c) 2012-2026 Akinori MUSHA
#
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions
# are met:
# 1. Redistributions of source code must retain the above copyright
#    notice, this list of conditions and the following disclaimer.
# 2. Redistributions in binary form must reproduce the above copyright
#    notice, this list of conditions and the following disclaimer in the
#    documentation and/or other materials provided with the distribution.
#
# THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
# ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
# SUCH DAMAGE.
#
# See https://github.com/knu/emacsc for the latest information.

VERSION=1.6.20260125

usage () {
    cat <<EOF >&2
emacsc version $VERSION

usage: $(basename "$0") [-cdgknpw] [-s NAME] [-e EXPR | -x EXPR | -f FUNC] [-C DIR] [FILE..]

    -h, --help      show this help
    -d, --daemon    run Emacs as daemon and quit
    -k, --kill      kill Emacs daemon
    -g, --no-tty    do not prefer tty
    -c, --create-frame
                    create a new frame
    -n, --no-wait
                    do not wait and return immediately
    -w, --wait      (no-op, for compatibility)
    -p, --print
                    print the evaluated result to stdout in string form
                    typically used with -e and -x
    -s, --socket-name=NAME
                    specify the file name of the socket file name for
                    communication (default: \${EMACS_SERVER_NAME:-server})
    -e, --eval=EXPR
                    evaluate the Lisp expression EXPR and print the
                    result without a frame opened
    -x, --execute=EXPR
                    interactively execute the Lisp expression EXPR
    -f, --funcall=FUNC
                    interactively call the Lisp function FUNC
    -C, --chdir, --directory=DIR
                    change to directory DIR before running code
    --version
                    show version number and exit

Default options can be put in the environment variable EMACSCOPT.

This command is a wrapper of emacsclient(1) for use within a terminal.
It adds the -t option so that Emacs opens a new frame on the current
terminal, making the command itself suitable as a value for EDITOR.

A byte-compiled initialization file is automatically removed before
running Emacs if outdated, i.e. older than the original file.

In order for -x and -f to work, emacsc.el must be loaded in your Emacs
initialization file.

    (require 'emacsc)
EOF
}

: ${ALTERNATE_EDITOR=}
export ALTERNATE_EDITOR

unset expr func create_frame quiet interactive void args ec_args dir print tmpfile
tty=t
dir=.

if [ -n "${EMACS_SERVER_NAME:+t}" ]; then
    set -- -s "$EMACS_SERVER_NAME" "$@"
fi

if [ -n "${EMACSCOPT+t}" ]; then
    set -- $EMACSCOPT "$@"
fi

while getopts cdgknps:e:x:f:C:wh-: opt; do
    if [ "$opt" = - ]; then
        case "$OPTARG" in
            *\=*)
                opt="${OPTARG%%=*}"
                OPTARG="${OPTARG#*=}"
                ;;
            *)
                opt="$OPTARG"
                unset OPTARG
                ;;
        esac
    fi

    case "$opt" in
        d|daemon)
            quiet=t
            expr="t"
            ;;
        k|kill)
            expr="(kill-emacs)"
            quiet=t
            ALTERNATE_EDITOR=false
            ;;
        e|eval)
            expr="$OPTARG"
            unset tty
            ;;
        p|print)
            print=t
            void=t
            ;;
        x|execute)
            expr="$OPTARG"
            interactive=t
            void=t
            ;;
        f|funcall)
            func="$OPTARG"
            interactive=t
            void=t
            ;;
        C|chdir|directory)
            dir="$OPTARG"
            ;;
        c|create-frame)
            create_frame=t
            ;;
        g|no-tty)
            unset tty
            ;;
        n|no-wait)
            args="$args -n"
            ;;
        w|wait)
            # no-op for compatibility
            ;;
        s|socket-name)
            ec_args="$ec_args -s $OPTARG"
            ;;
        h|help)
            usage
            exit
            ;;
        version)
            echo "emacsc version $VERSION"
            exit
            ;;
        ??*)
            echo "$0: illegal long option -- $opt" >&2
            usage
            exit 64
            ;;
        *)
            usage
            exit 64
            ;;
    esac
done

shift $((OPTIND-1))

if [ $# -gt 0 ]; then
    interactive=t
fi

for el in "$HOME"/.emacs.d/init.el "$HOME"/.emacs.el  "$HOME"/.emacs; do
    if [ -f "$el" ]; then
        [ "$el" -nt "${el%.el}.elc" ] && rm -f "${el%.el}.elc"
        break
    fi
done

if [ -n "${func+t}" ]; then
    # Calling a function via call-interactively sometimes crashes the
    # session and makes minibuffer unusable, so avoid using it if
    # possible.
    expr="\
(let\
 ((func (function $func)))\
 (if (zerop (car (func-arity func)))\
     (funcall func)\
   (call-interactively func)))"
fi

if [ -d "$dir" ]; then
    dir=$(cd "$dir" && pwd)
else
    echo "$0: not a directory: $dir"
    exit 1
fi

if [ -n "${expr+t}" ]; then
    qdir="$(sed 's/[\\"]/\\&/g' <<EOF
$dir
EOF
    )"

    if [ -n "$print" ]; then
        tmpfile=$(mktemp -t emacsc.XXXXXX)

        cleanup () {
            if [ -f "$tmpfile" ]; then
                rm -f -- "$tmpfile"
            fi
        }

        trap "cleanup" 1 2 3 15

        qtmpfile="$(sed 's/[\\"]/\\&/g' <<EOF
$tmpfile
EOF
        )"
        printer="(with-temp-file \"$qtmpfile\" (insert (format \"%s\" val)))${interactive+" (if (frame-parameter nil 'client) (delete-frame))"}"
    else
        printer=
    fi
    set -- -e "${void+@}\
(let*\
 ((default-directory \"$qdir\")\
  (inhibit-quit t)\
  (val))\
 (or (with-local-quit (setq val $expr) t)\
     (setq quit-flag nil))\
 $printer\
 val)" "$@"
fi

if [ -n "${args+t}" ]; then
    set -- $args "$@"
fi

if [ -n "${interactive+t}" ]; then
    create_frame=t
fi

ec="emacsclient $ec_args"

if [ -n "${quiet+t}" ]; then
    exec >/dev/null 2>&1
    unset tty create_frame
else
    case "$($ec -e window-system)" in
        w32)
            unset tty
            ;;
        ''|nil)
            if [ -n "${INSIDE_EMACS+t}" ]; then
                unset tty create_frame
            fi
            ;;
        *)
            if [ -n "${INSIDE_EMACS+t}" ]; then
                unset tty
            fi
    esac
fi

if [ -n "${tty+t}" ]; then
    set -- -t "$@"
    # emacsclient(1) calls ttyname() on stdout
    if [ ! -t 1 ]; then
        : ${TTY:=`tty 2>/dev/null`}
        : ${TTY:=/dev/tty}
        exec 3>&1 >$TTY
    fi
elif [ -n "${create_frame+t}" ]; then
    set -- -c "$@"
fi

if [ -n "$print" ]; then
    if $ec "$@"; then
        cat "$tmpfile" >&3
        ret=0
    else
        ret=$?
    fi
    cleanup
    exit $ret
fi

exec $ec "$@"
