;;; chemacs.el --- -*- lexical-binding: t; -*- ;;; Commentary: ;; :::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: ;; ;; ___ ___ ___ ___ ___ ___ ___ ;; / /\ /__/\ / /\ /__/\ / /\ / /\ / /\ ;; / /:/ \ \:\ / /:/_ | |::\ / /::\ / /:/ / /:/_ ;; / /:/ \__\:\ / /:/ /\ | |:|:\ / /:/\:\ / /:/ / /:/ /\ ;; / /:/ ___ ___ / /::\ / /:/ /:/_ __|__|:|\:\ / /:/~/::\ / /:/ ___ / /:/ /::\ ;; /__/:/ / /\ /__/\ /:/\:\ /__/:/ /:/ /\ /__/::::| \:\ /__/:/ /:/\:\ /__/:/ / /\ /__/:/ /:/\:\ ;; \ \:\ / /:/ \ \:\/:/__\/ \ \:\/:/ /:/ \ \:\~~\__\/ \ \:\/:/__\/ \ \:\ / /:/ \ \:\/:/~/:/ ;; \ \:\ /:/ \ \::/ \ \::/ /:/ \ \:\ \ \::/ \ \:\ /:/ \ \2.0 /:/ ;; \ \:\/:/ \ \:\ \ \:\/:/ \ \:\ \ \:\ \ \:\/:/ \__\/ /:/ ;; \ \::/ \ \:\ \ \::/ \ \:\ \ \:\ \ \::/ /__/:/ ;; \__\/ \__\/ \__\/ \__\/ \__\/ \__\/ \__\/ ;; ;; :::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: ;; ;; Chemacs - Emacs Profile Switcher ;; ;; See README.md for instructions. ;; NOTE Don't require any libraries in this file. When emacs loads a library that ;; is byte compiled, it may start native-compiling it, so if we require anything ;; here, native compilation can start before the user has had a chance to configure ;; it in their init files. ;;; Code: (defvar chemacs-version "2.0") (defvar config-home (or (getenv "XDG_CONFIG_HOME") "~/.config")) (defvar chemacs-profiles-paths (list "~/.emacs-profiles.el" (format "%s/%s" config-home "chemacs/profiles.el" ))) (defvar chemacs-default-profile-paths (list "~/.emacs-profile" (format "%s/%s" config-home "chemacs/profile"))) ;; Copy `seq' library's `seq-filter' to avoid requiring it, see note above. (defun chemacs--seq-filter (pred sequence) (let ((exclude (make-symbol "exclude"))) (delq exclude (mapcar (lambda (elt) (if (funcall pred elt) elt exclude)) sequence)))) (defvar chemacs-profiles-path (or (car (chemacs--seq-filter #'file-exists-p chemacs-profiles-paths)) (car chemacs-profiles-paths))) (defvar chemacs-default-profile-path (or (car (chemacs--seq-filter #'file-exists-p chemacs-default-profile-paths)) (car chemacs-default-profile-paths))) (defun chemacs-handle-command-line (args) (when args ;; Handle either --with-profile profilename or ;; --with-profile=profilename (let ((s (split-string (car args) "="))) (cond ((equal (car args) "--with-profile") ;; This is just a no-op so Emacs knows --with-profile ;; is a valid option. If we wait for ;; command-switch-alist to be processed then ;; after-init-hook has already run. (add-to-list 'command-switch-alist '("--with-profile" . (lambda (_) (pop command-line-args-left)))) (cadr args)) ;; Similar handling for `--with-profile=profilename' ((equal (car s) "--with-profile") (add-to-list 'command-switch-alist `(,(car args) . (lambda (_)))) (mapconcat 'identity (cdr s) "=")) (t (chemacs-handle-command-line (cdr args))))))) (defvar chemacs--with-profile-value (let* ((value (chemacs-handle-command-line command-line-args)) (read-value (read value))) (when value (if (listp read-value) read-value value)))) (defvar chemacs-literal-profile-provided (and chemacs--with-profile-value (listp chemacs--with-profile-value))) (unless (or (file-exists-p chemacs-profiles-path) (and chemacs--with-profile-value (listp chemacs--with-profile-value))) (error "[chemacs] %s does not exist." chemacs-profiles-path)) (defvar chemacs-default-profile-name (if (file-exists-p chemacs-default-profile-path) (with-temp-buffer (insert-file-contents chemacs-default-profile-path) (goto-char (point-min)) ;; (buffer-string)) (symbol-name (read (current-buffer)) )) "default")) (defvar chemacs-profile-name (if (and chemacs--with-profile-value (stringp chemacs--with-profile-value)) chemacs--with-profile-value chemacs-default-profile-name)) (defvar chemacs-profile (if (and chemacs--with-profile-value (listp chemacs--with-profile-value)) chemacs--with-profile-value (let ((profiles (with-temp-buffer (insert-file-contents chemacs-profiles-path) (goto-char (point-min)) (read (current-buffer))))) (cdr (assoc chemacs-profile-name profiles))))) (unless chemacs-profile (error "No profile `%s' in %s" chemacs-profile-name chemacs-profiles-path)) (defun chemacs-profile-get (key &optional default) (alist-get key chemacs-profile default)) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; (setq user-emacs-directory (file-name-as-directory (chemacs-profile-get 'user-emacs-directory))) ;; Allow multiple profiles to each run their server ;; use `emacsclient -s profile_name' to connect (let ((name (chemacs-profile-get 'server-name))) (when name (setq server-name name))) ;; Set environment variables, these are visible to init-file with ;; getenv (mapcar (lambda (env) (setenv (car env) (cdr env))) (chemacs-profile-get 'env)) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; (defun chemacs-load-user-early-init () (let ((early-init-file (expand-file-name "early-init.el" user-emacs-directory))) (load early-init-file t t))) (defun chemacs-load-user-init () (when (chemacs-profile-get 'straight-p) (chemacs-load-straight)) (let ((init-file (expand-file-name "init.el" user-emacs-directory))) (setq package-user-dir (expand-file-name "elpa" user-emacs-directory)) (load init-file t t) ;; Prevent customize from changing ~/.emacs (this file), but if ;; init.el has set a value for custom-file then don't touch it. (let ((chemacs-custom-file (chemacs-profile-get 'custom-file init-file))) (when (not custom-file) (setq custom-file chemacs-custom-file) (unless (equal custom-file init-file) (unless (file-exists-p custom-file) (with-temp-buffer (write-file custom-file))) (load custom-file)))))) (defun chemacs-load-straight () (defvar bootstrap-version) (let ((bootstrap-file (expand-file-name "straight/repos/straight.el/bootstrap.el" user-emacs-directory)) (bootstrap-version 5)) (unless (file-exists-p bootstrap-file) (with-current-buffer (url-retrieve-synchronously "https://raw.githubusercontent.com/raxod502/straight.el/develop/install.el" 'silent 'inhibit-cookies) (goto-char (point-max)) (eval-print-last-sexp))) (load bootstrap-file nil 'nomessage))) (provide 'chemacs)