#!/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 ] && { sh "`$EMACS -Q --batch --eval '(princ temporary-file-directory)'`/doom.sh" "$0" "$@" && true; __DOOMCODE=$?; }
:; exit $__DOOMCODE

;; The garbage collector isn't as important during CLI ops. A higher threshold
;; makes it 15-30% faster, but set it too high and we risk runaway 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. Use the EMACSDIR envvar to change this.
(setq user-emacs-directory
      (if (getenv-internal "EMACSDIR")
          (file-name-as-directory (expand-file-name (getenv-internal "EMACSDIR")))
        (expand-file-name
         "../" (file-name-directory (file-truename load-file-name)))))


;;
;;; Sanity checks

(when (version< emacs-version "27.1")
  (error
   (concat "Detected Emacs " emacs-version " (at " (car command-line-args) ").\n\n"
           "Doom only supports Emacs 27.1 and newer. A guide to install a newer version\n"
           "of Emacs can be found at:\n\n  "
           (format "https://doomemacs.org/docs/getting_started.org#%s\n"
                   (cond ((eq system-type 'darwin) "on-macos")
                         ((memq system-type '(cygwin windows-nt ms-dos)) "on-windows")
                         ("on-linux")))
           "Aborting...")))

(unless (file-readable-p (expand-file-name "core/core.el" user-emacs-directory))
  (error
   (concat
    "Couldn't find or read '"
    (abbreviate-file-name
     (expand-file-name "core/core.el" user-emacs-directory))
    "'.\n\n"
    "Are you sure Doom Emacs is correctly installed?\n\n"
    (when (file-symlink-p load-file-name)
      (concat "This error can occur if you've symlinked the 'doom' script, which Doom does not\n"
              "support. Consider symlinking its parent directory instead or explicitly set the\n"
              "EMACSDIR environment variable, e.g.\n\n  "
              (if (string-match-p "/fish$" (getenv "SHELL"))
                  "env EMACSDIR=~/.emacs.d doom"
                "EMACSDIR=~/.emacs.d doom sync"))
      "\n\n")
    "Aborting...")))

(when (equal (user-real-uid) 0)
  ;; If ~/.emacs.d is owned by root, assume the user genuinely wants root to be
  ;; their primary user.
  (unless (= 0 (file-attribute-user-id (file-attributes user-emacs-directory)))
    (error
     (concat
      "Do not run this script as root. It will cause file permissions errors later.\n\n"
      "To carry on anyway, change the owner of your Emacs config to root:\n\n"
      "  chown root:root -R " (abbreviate-file-name user-emacs-directory) "\n\n"
      "Aborting..."))))


;;
;;; Let 'er rip!

;; HACK Load `cl' and site files manually to prevent polluting logs and stdout
;;      with deprecation and/or file load messages.
(let ((inhibit-message t))
  (require 'cl)
  (unless site-run-file
    (let ((site-run-file "site-start")
          (verbose (or (getenv "DEBUG") init-file-debug))
          (tail load-path)
          (lispdir (expand-file-name "../lisp" data-directory))
          dir)
      (while tail
        (setq dir (car tail))
        (let ((default-directory dir))
          (load (expand-file-name "subdirs.el") t (not verbose) t))
        (or (string-prefix-p lispdir dir)
            (let ((default-directory dir))
              (load (expand-file-name "leim-list.el") t (not verbose) t)))
        (setq tail (cdr tail)))
      (load site-run-file t (not verbose)))))

;; 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)

(kill-emacs
 ;; Process the arguments passed to this script. `doom-cli-execute' should
 ;; return one of two things: a cons cell whose CAR is t, and CDR is the
 ;; command's return value OR one of: a keyword, command string, or command
 ;; list.
 (pcase (apply #'doom-cli-execute :doom (cdr (member "--" argv)))

   ;; If a CLI command returns an integer, treat it as an exit code.
   ((and (app car-safe `t) code)
    (if (integerp (cdr code))
        (cdr code)))

   ;; CLI commands can do (throw 'exit SHELL-COMMAND) to run something after
   ;; this session ends. e.g.
   ;;
   ;;   (throw 'exit "$@") or (throw 'exit :restart)
   ;;     This reruns the current command with the same arguments.
   ;;   (throw 'exit "$@ -h -c")
   ;;     This reruns the current command with two new switches.
   ;;   (throw 'exit "emacs -nw FILE")
   ;;     Opens Emacs on FILE
   ;;   (throw 'exit t) or (throw 'exit nil)
   ;;     A safe way to simply abort back to the shell with exit code 0
   ;;   (throw 'exit 42)
   ;;     Abort to shell with an explicit exit code (as a more abrupt
   ;;     alternative to having the CLI command return 42).
   ;;
   ;; How this works: the command is written to a temporary shell script which
   ;; is executed after this session ends (see the shebang lines of this file).
   ;; It's done this way because Emacs' batch library lacks an implementation of
   ;; the exec system call.
   (command
    (cond
     ((integerp command)
      command)
     ((booleanp command)
      0)
     ((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)
                        ((listp 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
                                                   (get 'process-environment 'initial-value)
                                                   :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 #o600)
        ;; Error code 128 is special: it means run the post-script after this
        ;; session ends.
        128))))))
