#!/usr/bin/env sh
:; set -e # -*- mode: emacs-lisp; lexical-binding: t -*-
:; case "$EMACS" in *term*) EMACS=emacs ;; *) EMACS="${EMACS:-emacs}" ;; esac
:; $EMACS --version >/dev/null 2>&1 || { >&2 echo "Can't find emacs in your PATH"; exit 1; }
:; $EMACS --no-site-file --script "$0" -- "$@" || __DOOMCODE=$?
:; [ "${__DOOMCODE:-0}" -eq 128 ] && { "`$EMACS -Q --batch --eval '(princ temporary-file-directory)'`/doom.sh" "$0" "$@" && true; __DOOMCODE=$?; }
:; exit $__DOOMCODE

;; The garbage collector isn't important during CLI ops. A higher threshold
;; makes it 15-30% faster, but set it too high and we risk spiralling memory
;; usage in longer sessions.
(setq gc-cons-threshold 134217728)   ; 128mb

;; Prioritize non-byte-compiled source files in non-interactive sessions to
;; prevent loading stale byte-code.
(setq load-prefer-newer t)

;; Ensure Doom runs out of this file's parent directory, where Doom is
;; presumably installed. EMACSDIR is set in the shell script preamble earlier in
;; this file.
(setq user-emacs-directory
      (if (getenv "EMACSDIR")
          (file-name-as-directory (expand-file-name (getenv "EMACSDIR")))
        (expand-file-name
         "../" (file-name-directory (file-truename load-file-name)))))

;; Handle some potential issues early
(when (version< emacs-version "26.1")
  (error (concat "Detected Emacs %s (at %s).\n\n"
                 "Doom only supports Emacs 26.1 and newer. 27.1 is highly recommended. A guide\n"
                 "to install a newer version of Emacs can be found at:\n\n  "
                 (cond ((eq system-type 'darwin)
                        "https://github.com/hlissner/doom-emacs/blob/develop/docs/getting_started.org#on-macos")
                       ((memq system-type '(cygwin windows-nt ms-dos))
                        "https://github.com/hlissner/doom-emacs/blob/develop/docs/getting_started.org#on-windows")
                       ("https://github.com/hlissner/doom-emacs/blob/develop/docs/getting_started.org#on-linux"))
                 "Aborting...")
         emacs-version
         (car command-line-args)))

(unless (file-exists-p (expand-file-name "core/core.el" user-emacs-directory))
  (error (concat "Couldn't find Doom Emacs in %S.\n\n"
                 "This is likely because this script (or its parent directory) is a symlink.\n"
                 "If you must use a symlink, you'll need to specify an EMACSDIR so Doom knows\n"
                 "where to find itself. e.g.\n\n    "
                 (if (string-match-p "/fish$" (getenv "SHELL"))
                     "env EMACSDIR=~/.emacs.d doom"
                   "EMACSDIR=~/.emacs.d doom sync")
                 "\n\n"
                 "Aborting...")
         (abbreviate-file-name (file-truename user-emacs-directory))
         (abbreviate-file-name load-file-name)))

(when (and (equal (user-real-uid) 0)
           (not (file-in-directory-p user-emacs-directory "/root")))
  (error (concat "This script is running as root. This likely wasn't intentional and\n"
                 "will cause file permissions errors later if this Doom install is\n"
                 "ever used on a non-root account.\n\n"
                 "Aborting...")))

;; Load the heart of the beast and its CLI processing library
(load (expand-file-name "core/core.el" user-emacs-directory) nil t)
(require 'core-cli)

;; Use our own home-grown debugger to display and log errors + backtraces.
;; Control over its formatting is important, because Emacs produces
;; difficult-to-read debug information otherwise. By making its errors more
;; presentable (and storing them somewhere users can access later) we go a long
;; way toward making it easier for users to write better bug reports.
(setq debugger #'doom-cli--debugger
      debug-on-error t
      debug-ignored-errors nil)

;; HACK Load `cl' and site files manually to prevent polluting logs and stdout
;;      with deprecation and/or file load messages.
(quiet! (if EMACS27+ (require 'cl))
        (load "site-start" t t))

(kill-emacs
 (pcase
     (catch 'exit
       ;; Process the arguments passed to this script. `doom-cli-execute' should
       ;; return a boolean, integer (error code) or throw an 'exit event, which
       ;; we handle specially.
       (apply #'doom-cli-execute :doom (cdr (member "--" argv))))
   ;; Any non-zero integer is treated as an error code.
   ((and (pred integerp) code) code)
   ;; If, instead, we were given a list or string, copy these as shell script
   ;; commands to a temp script file which this script will execute after this
   ;; session finishes. Also accepts special keywords, like `:restart', to rerun
   ;; the current command.
   ((and (or (pred consp)
             (pred stringp)
             (pred keywordp))
         command)
    (let ((script (expand-file-name "doom.sh" temporary-file-directory))
          (coding-system-for-write 'utf-8-unix)
          (coding-system-for-read  'utf-8-unix))
      (with-temp-file script
        (insert "#!/usr/bin/env sh\n"
                "_postscript() {\n"
                "  rm -f " (shell-quote-argument script) "\n  "
                (cond ((eq command :restart)
                       "$@")
                      ((stringp command)
                       command)
                      ((string-join
                        (if (listp (car-safe command))
                            (cl-loop for line in (doom-enlist command)
                                     collect (mapconcat #'shell-quote-argument (remq nil line) " "))
                          (list (mapconcat #'shell-quote-argument (remq nil command) " ")))
                        "\n  ")))
                "\n}\n"
                (save-match-data
                  (cl-loop for env
                           in (cl-set-difference process-environment
                                                 doom--initial-process-environment
                                                 :test #'equal)
                           if (string-match "^\\([a-zA-Z0-9_]+\\)=\\(.+\\)$" env)
                           concat (format "%s=%s \\\n"
                                          (match-string 1 env)
                                          (shell-quote-argument (match-string 2 env)))))
                (format "PATH=\"%s%s$PATH\" \\\n" (concat doom-emacs-dir "bin/") path-separator)
                "_postscript $@\n"))
      (set-file-modes script #o700))
    ;; Error code 128 is special: it means run the post-script after this
    ;; session ends.
    128)
   ;; Anything else (e.g. booleans) is treated as a successful run. Yes, a `nil'
   ;; indicates a successful run too!
   (_ 0)))
