From 1997a345e2d9dd534f59260d082a8708ec7f82ab Mon Sep 17 00:00:00 2001 From: SqrtMinusOne Date: Sun, 18 Jun 2023 14:35:47 +0300 Subject: [PATCH] Initial commit --- README.org | 0 micromamba.el | 217 ++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 217 insertions(+) create mode 100644 README.org create mode 100644 micromamba.el diff --git a/README.org b/README.org new file mode 100644 index 0000000..e69de29 diff --git a/micromamba.el b/micromamba.el new file mode 100644 index 0000000..51e3103 --- /dev/null +++ b/micromamba.el @@ -0,0 +1,217 @@ +;;; micromamba.el --- A simple micromamba integration for Emacs -*- lexical-binding: t -*- + +;; Copyright (C) 2023 Korytov Pavel + +;; Author: Korytov Pavel +;; Maintainer: Korytov Pavel +;; Version: 0.1.0 +;; Package-Requires: ((emacs "27.1") (pythonic "0.1.0")) +;; Homepage: https://github.com/SqrtMinusOne/micromamba.el + +;; This file is NOT part of GNU Emacs. + +;; This program is free software: you can redistribute it and/or modify +;; it under the terms of the GNU General Public License as published by +;; the Free Software Foundation, either version 3 of the License, or +;; (at your option) any later version. + +;; This program is distributed in the hope that it will be useful, +;; but WITHOUT ANY WARRANTY; without even the implied warranty of +;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +;; GNU General Public License for more details. + +;; You should have received a copy of the GNU General Public License +;; along with this program. If not, see . + +;;; Commentary: + +;; Use conda.el for full mamba. + +;;; Code: +(require 'json) +(require 'pythonic) +(require 'seq) + +(defgroup micromamba nil + "Micromamba (environment manager) integration for Emacs." + :group 'python) + +(defcustom micromamba-home "~/micromamba" + "Location of micromamba home evironment directory." + :type 'string + :group 'micromamba) + +(defcustom micromamba-executable (executable-find "micromamba") + "Path to micromamba executable." + :type 'string + :group 'micromamba) + +(defcustom micromamba-preactivate-hook nil + "Hook run before a micromamba environment is activated." + :type 'hook + :group 'micromamba) + +(defcustom micromamba-postactivate-hook nil + "Hook run after a micromamba environment is activated." + :type 'hook + :group 'micromamba) + +(defcustom micromamba-predeactivate-hook nil + "Hook run before a micromamba environment is deactivated." + :type 'hook + :group 'micromamba) + +(defcustom micromamba-postdeactivate-hook nil + "Hook run after a micromamba environment is deactivated." + :type 'hook + :group 'micromamba) + +(defvar micromamba-env-current-prefix nil + "Current activated micromamba environment.") + +(defun micromamba--call-json (&rest args) + "Call micromamba and parse the return value as JSON. + +Pass ARGS as arguments to the program." + (unless micromamba-executable + (user-error "Micromamba-executable is not set!")) + (with-temp-buffer + (apply #'call-process micromamba-executable nil t nil args) + (goto-char (point-min)) + (json-read))) + +(defun micromamba-envs () + "Get micromamba environments. + +Returns an alist with environments, where the key is the name and the +value is the prefix. Duplicate names are replaced with prefixes." + (let* ((envs + (append (alist-get 'envs (micromamba--call-json "env" "list" "--json")) nil)) + (base-env (alist-get 'base\ environment + (micromamba--call-json "info" "--json"))) + ;; I have some environments from conda with the same name :-( + (dupe-name-map (make-hash-table :test 'equal)) + (env-names + (cl-loop for env in envs + for env-name = (if (eq base-env env-name) "base" + (file-name-nondirectory env)) + do (if (gethash env-name dupe-name-map) + (puthash env-name 'dupe dupe-name-map) + (puthash env-name t dupe-name-map)) + collect env-name))) + (cl-loop for env in envs + for env-name in env-names + for name = (if (eq (gethash env-name dupe-name-map) 'dupe) + env env-name) + collect (cons name env)))) + +(defun micromamba--parse-script-buffer () + "Parse bash script buffer generated by micromamba. + +E.g. micromamba shell -s bash activate . + +Returns an alist with the following keys: +- path +- vars-unset +- vars-export +- scripts." + (let (path vars-unset vars-export scripts) + (while (not (eobp)) + (cond + ((looking-at (rx bol "export PATH='")) + (setq path + (split-string (buffer-substring-no-properties + (+ 13 (point)) (1- (point-at-eol))) + ":"))) + ((looking-at (rx bol "unset")) + (push + (buffer-substring-no-properties (+ 6 (point)) (point-at-eol)) + vars-unset) ) + ((looking-at (rx bol "export")) + (save-excursion + (let ((var-point (+ 7 (point)))) + (re-search-forward (rx "=")) + (let ((var-name (buffer-substring-no-properties var-point (1- (point)))) + (var-contents (buffer-substring-no-properties + (point) (point-at-eol)))) + (when (string-match-p (rx bos "'" (* nonl) "'" eos) var-contents) + (setq var-contents + (substring var-contents 1 (1- (length var-contents))))) + (push (cons var-name var-contents) vars-export))))) + ((looking-at (rx bol ". \"")) + (push (buffer-substring-no-properties (+ 4 (point)) + (1- (point-at-eol))) + scripts))) + (forward-line)) + `((path . ,path) + (vars-unset . ,vars-unset) + (vars-export . ,vars-export) + (scripts . ,scripts)))) + +(defun micromamba--get-activation-parameters (prefix) + "Get activation parameters for the environment PREFIX. + +The parameters value is an alist as defined by +`micromamba--parse-script-buffer'." + (with-temp-buffer + (call-process micromamba-executable nil t nil + "shell" "-s" "bash" "activate" prefix) + (goto-char (point-min)) + (micromamba--parse-script-buffer))) + +(defun micromamba--get-deactivation-parameters () + "Get deactivation parameters for the current evironment. + +The parameters value is an alist as defined by +`micromamba--parse-script-buffer'." + (with-temp-buffer + (call-process micromamba-executable nil t nil + "shell" "-s" "bash" "deactivate") + (goto-char (point-min)) + (micromamba--parse-script-buffer))) + +(defun micromamba--apply-env (parameters) + "Apply PARAMETERS to the current environment. + +The parameters value is an alist as defined by +`micromamba--parse-script-buffer'." + (setq exec-path (alist-get 'path parameters)) + (setenv "PATH" (string-join (alist-get 'path parameters) ":")) + (dolist (var-name (alist-get 'vars-unset parameters)) + (setenv var-name nil)) + (dolist (var (alist-get 'vars-export parameters)) + (setenv (car var) (cdr var))) + (setq eshell-path-env (getenv "PATH"))) + +;;;###autoload +(defun micromamba-activate (prefix) + "Switch to environment with PREFIX (path). Prompt if called interactively." + (interactive + (list (let ((envs (micromamba-envs))) + (alist-get + (completing-read "Choose a micromamba environment: " envs + nil t) + envs nil nil #'equal)))) + (micromamba-deactivate) + (setq micromamba-env-current-prefix prefix) + (run-hooks 'micromamba-preactivate-hook) + ;; conda.el was doing that, so why not? + (pythonic-activate prefix) + (setq python-shell-virtualenv-root prefix) + (micromamba--apply-env + (micromamba--get-activation-parameters prefix)) + (run-hooks 'micromamba-postactivate-hook)) + +;;;###autoload +(defun micromamba-deactivate () + "Deactivate the current environment." + (interactive) + (when (bound-and-true-p micromamba-env-current-prefix) + (run-hooks 'micromamba-predeactivate-hook) + (setq python-shell-virtualenv-root nil) + (micromamba--apply-env + (micromamba--get-deactivation-parameters)) + (setq micromamba-env-current-prefix nil) + (run-hooks 'micromamba-postdeactivate-hook))) + +;;; micromamba.el ends here