0

How do you overwrite a defcustom of type: string with a function that generates a string? Below a specific set-up is detailed:

Current set-up

In a .dir-locals.el file two variables are created python-shell-virtualenv-root and python-pytest-executable.

((nil . ((eval . (let ((direnv-python ".direnv/python-3.7.2"))
                   (setq-local python-shell-virtualenv-root (expand-file-name direnv-python projectile-project-root)
                               python-pytest-executable (expand-file-name (concat direnv-python "/bin/pytest") projectile-project-root)))
         ))))

The snippet above builds the two variables using direnv-python.

Preferred set-up

Globally define the functions that build the two variables python-shell-virtualenv-root and python-pytest-executable, preferably in an init.el. Then, in the .dir-locals.el define the direnv-python variable.

Then python-shell-virtualenv-root and python-pytest-executable should be created dynamically, using the direnv-python variable.

Motivation

The logic for creating python-shell-virtualenv-root and python-pytest-executable is the same for every project. The direnv-python is project specific. I would like to only specify the later per project.

EDIT

[1] defvar should be defcustom

marc_s
  • 732,580
  • 175
  • 1,330
  • 1,459
Cor
  • 104
  • 2
  • 1
    `defvar` has no facility for type declarations. Did you perhaps mean `defcustom`? If so, please edit the question to clarify. Thx. If this is about a user option (`defcustom` variable) then try `M-x customize-option`. If the type you want isn't allowed by the variable definition (`defcustom`) then rethink your need. You can use your own `defcustom`, if you evaluate it before the usual one, but that might not be a good idea, as other code might depend on the declared types. – Drew Aug 24 '21 at 14:45
  • It should be `defcustom` indeed, updated accordingly. I think it is not possible to do this due to the type differences – Cor Aug 25 '21 at 06:55
  • As I said, *"You can use your own defcustom, if you evaluate it before the usual one, but that might not be a good idea, as other code might depend on the declared types."* IOW, the `defcustom` :type definition rules, and the first `defcustom` evaluated for the variable wins. Augment the `:type` in your own defcustom, to allow a value that's a function. – Drew Aug 25 '21 at 15:47
  • Yes, this allows me to change the `type` but - like you said - it is probably not a good idea because other code depends on it. You answered my question, but I asked the wrong question. – Cor Aug 27 '21 at 10:00
  • You can still ask the right question (as a separate question, please). – Drew Aug 27 '21 at 17:44

1 Answers1

1

@Drew answered the question in the comments. In short, you can define your own defcustom with the wanted type if you make sure it is evaluated before the already existing definition.

However, like Drew mentioned, it is probably not want you want to do, since other code expects the original type and therefore is likely to break.

Preferred set-up
To get to the preferred set-up mentioned in the question, I did the following:

;; In config.el

(defcustom python-projectile-environment-directory ".direnv/python-3.7.2"
  "The python environment within a projectile project"
  :type 'string
  :group 'python)

;; Run a hook after local vars are read
;; Source: https://stackoverflow.com/questions/5147060/how-can-i-access-directory-local-variables-in-my-major-mode-hooks
(defun run-local-vars-mode-hook ()
  "Run a hook for the major-mode after the local variables have been processed."
  (run-hooks (intern (concat (symbol-name major-mode) "-local-vars-hook"))))

(add-hook 'hack-local-variables-hook 'run-local-vars-mode-hook)

;; Set-up the python shell
(defun config/python-mode-shell-setup ()
  (message "project python environment is %s" python-projectile-environment-directory)
  (setq-local python-shell-virtualenv-root (expand-file-name python-projectile-environment-directory (projectile-project-root))
              python-pytest-executable (expand-file-name (concat python-projectile-environment-directory "/bin/pytest") (projectile-project-root))))

(add-hook 'python-mode-local-vars-hook 'config/python-mode-shell-setup)

The run-local-vars-mode-hook allows you to run a hook after the local variables are read (see mentioned link for more details). This was the missing link that allows the use of local variables in a hook.

After that the config/python-mode-shell-setup sets-up the python shell using the local variable python-projectile-environment-directory.

;; .dir-locals.el

((python-mode . ((python-projectile-environment-directory . ".direnv/python-3.7.9"))))

If that local variable is not present, the default value of defcustom ".direnv/python-3.7.2" is used.

Cor
  • 104
  • 2