Hook to switch the linter binaries in Emacs Lisp according to virtual environment

The name of the pictureThe name of the pictureThe name of the pictureClash Royale CLAN TAG#URR8PPP





.everyoneloves__top-leaderboard:empty,.everyoneloves__mid-leaderboard:empty margin-bottom:0;







up vote
4
down vote

favorite












Problem



I use Emacs for Python development along with several linters. When I activate a Python virtual environment (venv) from within Emacs, I would like to set the linter binaries according to the following rules:



  1. If the venv has a particular linter installed, use it

  2. If the venv does not have a particular linter, use the one in a pre-specified, default venv

  3. If the linter does not exist in either the active venv or the default venv, set the linter binary to nil

For example, let's say I activate a venv called my_venv that has pylint installed, but no flake8. flake8 is however installed in my default linters venv. After activating my_venv, the linters that will be used are



  • pylint -- my_venv

  • flake8 -- linters

Purpose



The reason for implementing this is that I develop Python on multiple machines that share a single, version-controlled init.el file. I do not want to guarantee that I have the same Python binaries and venvs across these machines; this implementation helps decouple my Emacs setup from the Python venvs that are present on a machine.



Additional info



  • The code will go inside my init.el file

  • I use flycheck as the interface between Emacs and the linters

  • I use pyvenv for Python virtual environments in Emacs

Code



(defvar linter-execs '((flycheck-python-flake8-executable "bin/flake8")
(flycheck-python-pylint-executable "bin/pylint")
(flycheck-python-pycompile-executable "bin/python")))
(defvar default-linter-venv-path (concat (getenv "WORKON_HOME") "/linters/"))

(defun switch-linters ()
"Switch linter executables to those in the current venv.

If the venv does not have any linter packages, then they will be
set to those in the `default-linter-venv-path` venv. If these do
not exist, then no linter will be set."
(dolist (exec linter-execs)
(let ((venv-linter-bin (concat pyvenv-virtual-env (nth 1 exec)))
(default-linter-bin (concat default-linter-venv-path (nth 1 exec)))
(flycheck-var (nth 0 exec)))
(cond ((file-exists-p venv-linter-bin)
(set flycheck-var venv-linter-bin))
((file-exists-p default-linter-bin)
(set flycheck-var default-linter-bin))
(t (set flycheck-var nil))))))

(add-hook 'pyvenv-post-activate-hooks 'switch-linters)


Explanation




  • linter-execs is a list of two-element lists. The first element of an entry is the flycheck variable that contains the path of a linter binary. The second element is the relative path of the binary from within the venv.

  • The default linter venv is $WORKON_HOME/linters


  • switch-linters is the call-back function attached to pyvenv-post-activate-hooks

  • The conditional checks for the presence of the linter binary files, first in the current venv and next in the default venv. Failing these, it sets the binary to nil in the line (t (set flycheck-var nil))

Specific questions



  • Is this idiomatic elisp, or is it too "Pythonic"?

  • Is linter-execs the proper way to implement a list of tuples in elisp?









share|improve this question







New contributor




kmdouglass is a new contributor to this site. Take care in asking for clarification, commenting, and answering.
Check out our Code of Conduct.

























    up vote
    4
    down vote

    favorite












    Problem



    I use Emacs for Python development along with several linters. When I activate a Python virtual environment (venv) from within Emacs, I would like to set the linter binaries according to the following rules:



    1. If the venv has a particular linter installed, use it

    2. If the venv does not have a particular linter, use the one in a pre-specified, default venv

    3. If the linter does not exist in either the active venv or the default venv, set the linter binary to nil

    For example, let's say I activate a venv called my_venv that has pylint installed, but no flake8. flake8 is however installed in my default linters venv. After activating my_venv, the linters that will be used are



    • pylint -- my_venv

    • flake8 -- linters

    Purpose



    The reason for implementing this is that I develop Python on multiple machines that share a single, version-controlled init.el file. I do not want to guarantee that I have the same Python binaries and venvs across these machines; this implementation helps decouple my Emacs setup from the Python venvs that are present on a machine.



    Additional info



    • The code will go inside my init.el file

    • I use flycheck as the interface between Emacs and the linters

    • I use pyvenv for Python virtual environments in Emacs

    Code



    (defvar linter-execs '((flycheck-python-flake8-executable "bin/flake8")
    (flycheck-python-pylint-executable "bin/pylint")
    (flycheck-python-pycompile-executable "bin/python")))
    (defvar default-linter-venv-path (concat (getenv "WORKON_HOME") "/linters/"))

    (defun switch-linters ()
    "Switch linter executables to those in the current venv.

    If the venv does not have any linter packages, then they will be
    set to those in the `default-linter-venv-path` venv. If these do
    not exist, then no linter will be set."
    (dolist (exec linter-execs)
    (let ((venv-linter-bin (concat pyvenv-virtual-env (nth 1 exec)))
    (default-linter-bin (concat default-linter-venv-path (nth 1 exec)))
    (flycheck-var (nth 0 exec)))
    (cond ((file-exists-p venv-linter-bin)
    (set flycheck-var venv-linter-bin))
    ((file-exists-p default-linter-bin)
    (set flycheck-var default-linter-bin))
    (t (set flycheck-var nil))))))

    (add-hook 'pyvenv-post-activate-hooks 'switch-linters)


    Explanation




    • linter-execs is a list of two-element lists. The first element of an entry is the flycheck variable that contains the path of a linter binary. The second element is the relative path of the binary from within the venv.

    • The default linter venv is $WORKON_HOME/linters


    • switch-linters is the call-back function attached to pyvenv-post-activate-hooks

    • The conditional checks for the presence of the linter binary files, first in the current venv and next in the default venv. Failing these, it sets the binary to nil in the line (t (set flycheck-var nil))

    Specific questions



    • Is this idiomatic elisp, or is it too "Pythonic"?

    • Is linter-execs the proper way to implement a list of tuples in elisp?









    share|improve this question







    New contributor




    kmdouglass is a new contributor to this site. Take care in asking for clarification, commenting, and answering.
    Check out our Code of Conduct.





















      up vote
      4
      down vote

      favorite









      up vote
      4
      down vote

      favorite











      Problem



      I use Emacs for Python development along with several linters. When I activate a Python virtual environment (venv) from within Emacs, I would like to set the linter binaries according to the following rules:



      1. If the venv has a particular linter installed, use it

      2. If the venv does not have a particular linter, use the one in a pre-specified, default venv

      3. If the linter does not exist in either the active venv or the default venv, set the linter binary to nil

      For example, let's say I activate a venv called my_venv that has pylint installed, but no flake8. flake8 is however installed in my default linters venv. After activating my_venv, the linters that will be used are



      • pylint -- my_venv

      • flake8 -- linters

      Purpose



      The reason for implementing this is that I develop Python on multiple machines that share a single, version-controlled init.el file. I do not want to guarantee that I have the same Python binaries and venvs across these machines; this implementation helps decouple my Emacs setup from the Python venvs that are present on a machine.



      Additional info



      • The code will go inside my init.el file

      • I use flycheck as the interface between Emacs and the linters

      • I use pyvenv for Python virtual environments in Emacs

      Code



      (defvar linter-execs '((flycheck-python-flake8-executable "bin/flake8")
      (flycheck-python-pylint-executable "bin/pylint")
      (flycheck-python-pycompile-executable "bin/python")))
      (defvar default-linter-venv-path (concat (getenv "WORKON_HOME") "/linters/"))

      (defun switch-linters ()
      "Switch linter executables to those in the current venv.

      If the venv does not have any linter packages, then they will be
      set to those in the `default-linter-venv-path` venv. If these do
      not exist, then no linter will be set."
      (dolist (exec linter-execs)
      (let ((venv-linter-bin (concat pyvenv-virtual-env (nth 1 exec)))
      (default-linter-bin (concat default-linter-venv-path (nth 1 exec)))
      (flycheck-var (nth 0 exec)))
      (cond ((file-exists-p venv-linter-bin)
      (set flycheck-var venv-linter-bin))
      ((file-exists-p default-linter-bin)
      (set flycheck-var default-linter-bin))
      (t (set flycheck-var nil))))))

      (add-hook 'pyvenv-post-activate-hooks 'switch-linters)


      Explanation




      • linter-execs is a list of two-element lists. The first element of an entry is the flycheck variable that contains the path of a linter binary. The second element is the relative path of the binary from within the venv.

      • The default linter venv is $WORKON_HOME/linters


      • switch-linters is the call-back function attached to pyvenv-post-activate-hooks

      • The conditional checks for the presence of the linter binary files, first in the current venv and next in the default venv. Failing these, it sets the binary to nil in the line (t (set flycheck-var nil))

      Specific questions



      • Is this idiomatic elisp, or is it too "Pythonic"?

      • Is linter-execs the proper way to implement a list of tuples in elisp?









      share|improve this question







      New contributor




      kmdouglass is a new contributor to this site. Take care in asking for clarification, commenting, and answering.
      Check out our Code of Conduct.











      Problem



      I use Emacs for Python development along with several linters. When I activate a Python virtual environment (venv) from within Emacs, I would like to set the linter binaries according to the following rules:



      1. If the venv has a particular linter installed, use it

      2. If the venv does not have a particular linter, use the one in a pre-specified, default venv

      3. If the linter does not exist in either the active venv or the default venv, set the linter binary to nil

      For example, let's say I activate a venv called my_venv that has pylint installed, but no flake8. flake8 is however installed in my default linters venv. After activating my_venv, the linters that will be used are



      • pylint -- my_venv

      • flake8 -- linters

      Purpose



      The reason for implementing this is that I develop Python on multiple machines that share a single, version-controlled init.el file. I do not want to guarantee that I have the same Python binaries and venvs across these machines; this implementation helps decouple my Emacs setup from the Python venvs that are present on a machine.



      Additional info



      • The code will go inside my init.el file

      • I use flycheck as the interface between Emacs and the linters

      • I use pyvenv for Python virtual environments in Emacs

      Code



      (defvar linter-execs '((flycheck-python-flake8-executable "bin/flake8")
      (flycheck-python-pylint-executable "bin/pylint")
      (flycheck-python-pycompile-executable "bin/python")))
      (defvar default-linter-venv-path (concat (getenv "WORKON_HOME") "/linters/"))

      (defun switch-linters ()
      "Switch linter executables to those in the current venv.

      If the venv does not have any linter packages, then they will be
      set to those in the `default-linter-venv-path` venv. If these do
      not exist, then no linter will be set."
      (dolist (exec linter-execs)
      (let ((venv-linter-bin (concat pyvenv-virtual-env (nth 1 exec)))
      (default-linter-bin (concat default-linter-venv-path (nth 1 exec)))
      (flycheck-var (nth 0 exec)))
      (cond ((file-exists-p venv-linter-bin)
      (set flycheck-var venv-linter-bin))
      ((file-exists-p default-linter-bin)
      (set flycheck-var default-linter-bin))
      (t (set flycheck-var nil))))))

      (add-hook 'pyvenv-post-activate-hooks 'switch-linters)


      Explanation




      • linter-execs is a list of two-element lists. The first element of an entry is the flycheck variable that contains the path of a linter binary. The second element is the relative path of the binary from within the venv.

      • The default linter venv is $WORKON_HOME/linters


      • switch-linters is the call-back function attached to pyvenv-post-activate-hooks

      • The conditional checks for the presence of the linter binary files, first in the current venv and next in the default venv. Failing these, it sets the binary to nil in the line (t (set flycheck-var nil))

      Specific questions



      • Is this idiomatic elisp, or is it too "Pythonic"?

      • Is linter-execs the proper way to implement a list of tuples in elisp?






      elisp






      share|improve this question







      New contributor




      kmdouglass is a new contributor to this site. Take care in asking for clarification, commenting, and answering.
      Check out our Code of Conduct.











      share|improve this question







      New contributor




      kmdouglass is a new contributor to this site. Take care in asking for clarification, commenting, and answering.
      Check out our Code of Conduct.









      share|improve this question




      share|improve this question






      New contributor




      kmdouglass is a new contributor to this site. Take care in asking for clarification, commenting, and answering.
      Check out our Code of Conduct.









      asked 4 hours ago









      kmdouglass

      212




      212




      New contributor




      kmdouglass is a new contributor to this site. Take care in asking for clarification, commenting, and answering.
      Check out our Code of Conduct.





      New contributor





      kmdouglass is a new contributor to this site. Take care in asking for clarification, commenting, and answering.
      Check out our Code of Conduct.






      kmdouglass is a new contributor to this site. Take care in asking for clarification, commenting, and answering.
      Check out our Code of Conduct.




















          1 Answer
          1






          active

          oldest

          votes

















          up vote
          3
          down vote














          1. It would be a good idea to write a docstring for the defvar'd variables like linter-execs. The explanations in the post would make an excellent start, for example:



            (defvar linter-execs '((flycheck-python-flake8-executable "bin/flake8")
            (flycheck-python-pylint-executable "bin/pylint")
            (flycheck-python-pycompile-executable "bin/python"))
            "The linter executables, as list of two-element lists. The
            first element of an entry is the flycheck variable that contains
            the path of a linter executable. The second element is the
            relative path of the executable from within the venv.")



          2. Writing (nth 0 exec) and (nth 1 exec) makes it hard to understand the meaning of these items of data. In Python you'd use tuple unpacking to give meaningful names to each element of the exec data structure, like this:



            flycheck_var, path = exec


            In Emacs Lisp you can use cl-destructuring-bind in a similar way:



            (dolist (exec linter-execs)
            (cl-destructuring-bind (flycheck-var path) exec
            (let ((venv-linter-bin (concat pyvenv-virtual-env path))
            ;; etc


            But exec comes from a list that you are looping over, so you could use the cl-loop macro instead of dolist, and cl-loop has destructuring built in:



            (cl-loop for (flycheck-var path) in linter-execs do
            (let ((venv-linter-bin (concat pyvenv-virtual-env path))
            ;; etc


            You'll need (require 'cl-macs) to use cl-destructuring-bind or cl-loop.




          3. The two directories pyvenv-virtual-env and default-linter-venv-path are treated in exactly the same way: first we do (let ((Y (concat X path)) and then (file-exists-p Y) and finally (set flycheck-var Y). This repetition could be factored out into a loop:



            (defun switch-linters ()
            "Switch linter executables to those in the current venv.

            If the venv does not have any linter packages, then they will be
            set to those in the `default-linter-venv-path` venv. If these do
            not exist, then the linter will be set to nil."
            (cl-loop with dirs = (list pyvenv-virtual-env default-linter-venv-path)
            for (flycheck-var path) in linter-execs
            do (set flycheck-var
            (cl-loop for directory in dirs
            for executable = (concat directory path)
            if (file-exists-p executable) return executable)))


          4. Instead of file-exists-p, you probably want to use file-executable-p.



          5. Looking for a file in a list of directories is built into Emacs as the function locate-file. Using this, we get:



            (defun switch-linters ()
            "Switch linter executables to those in the current venv.

            If the venv does not have any linter packages, then they will be
            set to those in the `default-linter-venv-path` venv. If these do
            not exist, then the linter will be set to nil."
            (cl-loop with dirs = (list pyvenv-virtual-env default-linter-venv-path)
            for (flycheck-var path) in linter-execs
            do (set flycheck-var (locate-file path dirs nil 'file-executable-p))))






          share|improve this answer




















            Your Answer




            StackExchange.ifUsing("editor", function ()
            return StackExchange.using("mathjaxEditing", function ()
            StackExchange.MarkdownEditor.creationCallbacks.add(function (editor, postfix)
            StackExchange.mathjaxEditing.prepareWmdForMathJax(editor, postfix, [["\$", "\$"]]);
            );
            );
            , "mathjax-editing");

            StackExchange.ifUsing("editor", function ()
            StackExchange.using("externalEditor", function ()
            StackExchange.using("snippets", function ()
            StackExchange.snippets.init();
            );
            );
            , "code-snippets");

            StackExchange.ready(function()
            var channelOptions =
            tags: "".split(" "),
            id: "196"
            ;
            initTagRenderer("".split(" "), "".split(" "), channelOptions);

            StackExchange.using("externalEditor", function()
            // Have to fire editor after snippets, if snippets enabled
            if (StackExchange.settings.snippets.snippetsEnabled)
            StackExchange.using("snippets", function()
            createEditor();
            );

            else
            createEditor();

            );

            function createEditor()
            StackExchange.prepareEditor(
            heartbeatType: 'answer',
            convertImagesToLinks: false,
            noModals: false,
            showLowRepImageUploadWarning: true,
            reputationToPostImages: null,
            bindNavPrevention: true,
            postfix: "",
            onDemand: true,
            discardSelector: ".discard-answer"
            ,immediatelyShowMarkdownHelp:true
            );



            );






            kmdouglass is a new contributor. Be nice, and check out our Code of Conduct.









             

            draft saved


            draft discarded


















            StackExchange.ready(
            function ()
            StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fcodereview.stackexchange.com%2fquestions%2f203808%2fhook-to-switch-the-linter-binaries-in-emacs-lisp-according-to-virtual-environmen%23new-answer', 'question_page');

            );

            Post as a guest






























            1 Answer
            1






            active

            oldest

            votes








            1 Answer
            1






            active

            oldest

            votes









            active

            oldest

            votes






            active

            oldest

            votes








            up vote
            3
            down vote














            1. It would be a good idea to write a docstring for the defvar'd variables like linter-execs. The explanations in the post would make an excellent start, for example:



              (defvar linter-execs '((flycheck-python-flake8-executable "bin/flake8")
              (flycheck-python-pylint-executable "bin/pylint")
              (flycheck-python-pycompile-executable "bin/python"))
              "The linter executables, as list of two-element lists. The
              first element of an entry is the flycheck variable that contains
              the path of a linter executable. The second element is the
              relative path of the executable from within the venv.")



            2. Writing (nth 0 exec) and (nth 1 exec) makes it hard to understand the meaning of these items of data. In Python you'd use tuple unpacking to give meaningful names to each element of the exec data structure, like this:



              flycheck_var, path = exec


              In Emacs Lisp you can use cl-destructuring-bind in a similar way:



              (dolist (exec linter-execs)
              (cl-destructuring-bind (flycheck-var path) exec
              (let ((venv-linter-bin (concat pyvenv-virtual-env path))
              ;; etc


              But exec comes from a list that you are looping over, so you could use the cl-loop macro instead of dolist, and cl-loop has destructuring built in:



              (cl-loop for (flycheck-var path) in linter-execs do
              (let ((venv-linter-bin (concat pyvenv-virtual-env path))
              ;; etc


              You'll need (require 'cl-macs) to use cl-destructuring-bind or cl-loop.




            3. The two directories pyvenv-virtual-env and default-linter-venv-path are treated in exactly the same way: first we do (let ((Y (concat X path)) and then (file-exists-p Y) and finally (set flycheck-var Y). This repetition could be factored out into a loop:



              (defun switch-linters ()
              "Switch linter executables to those in the current venv.

              If the venv does not have any linter packages, then they will be
              set to those in the `default-linter-venv-path` venv. If these do
              not exist, then the linter will be set to nil."
              (cl-loop with dirs = (list pyvenv-virtual-env default-linter-venv-path)
              for (flycheck-var path) in linter-execs
              do (set flycheck-var
              (cl-loop for directory in dirs
              for executable = (concat directory path)
              if (file-exists-p executable) return executable)))


            4. Instead of file-exists-p, you probably want to use file-executable-p.



            5. Looking for a file in a list of directories is built into Emacs as the function locate-file. Using this, we get:



              (defun switch-linters ()
              "Switch linter executables to those in the current venv.

              If the venv does not have any linter packages, then they will be
              set to those in the `default-linter-venv-path` venv. If these do
              not exist, then the linter will be set to nil."
              (cl-loop with dirs = (list pyvenv-virtual-env default-linter-venv-path)
              for (flycheck-var path) in linter-execs
              do (set flycheck-var (locate-file path dirs nil 'file-executable-p))))






            share|improve this answer
























              up vote
              3
              down vote














              1. It would be a good idea to write a docstring for the defvar'd variables like linter-execs. The explanations in the post would make an excellent start, for example:



                (defvar linter-execs '((flycheck-python-flake8-executable "bin/flake8")
                (flycheck-python-pylint-executable "bin/pylint")
                (flycheck-python-pycompile-executable "bin/python"))
                "The linter executables, as list of two-element lists. The
                first element of an entry is the flycheck variable that contains
                the path of a linter executable. The second element is the
                relative path of the executable from within the venv.")



              2. Writing (nth 0 exec) and (nth 1 exec) makes it hard to understand the meaning of these items of data. In Python you'd use tuple unpacking to give meaningful names to each element of the exec data structure, like this:



                flycheck_var, path = exec


                In Emacs Lisp you can use cl-destructuring-bind in a similar way:



                (dolist (exec linter-execs)
                (cl-destructuring-bind (flycheck-var path) exec
                (let ((venv-linter-bin (concat pyvenv-virtual-env path))
                ;; etc


                But exec comes from a list that you are looping over, so you could use the cl-loop macro instead of dolist, and cl-loop has destructuring built in:



                (cl-loop for (flycheck-var path) in linter-execs do
                (let ((venv-linter-bin (concat pyvenv-virtual-env path))
                ;; etc


                You'll need (require 'cl-macs) to use cl-destructuring-bind or cl-loop.




              3. The two directories pyvenv-virtual-env and default-linter-venv-path are treated in exactly the same way: first we do (let ((Y (concat X path)) and then (file-exists-p Y) and finally (set flycheck-var Y). This repetition could be factored out into a loop:



                (defun switch-linters ()
                "Switch linter executables to those in the current venv.

                If the venv does not have any linter packages, then they will be
                set to those in the `default-linter-venv-path` venv. If these do
                not exist, then the linter will be set to nil."
                (cl-loop with dirs = (list pyvenv-virtual-env default-linter-venv-path)
                for (flycheck-var path) in linter-execs
                do (set flycheck-var
                (cl-loop for directory in dirs
                for executable = (concat directory path)
                if (file-exists-p executable) return executable)))


              4. Instead of file-exists-p, you probably want to use file-executable-p.



              5. Looking for a file in a list of directories is built into Emacs as the function locate-file. Using this, we get:



                (defun switch-linters ()
                "Switch linter executables to those in the current venv.

                If the venv does not have any linter packages, then they will be
                set to those in the `default-linter-venv-path` venv. If these do
                not exist, then the linter will be set to nil."
                (cl-loop with dirs = (list pyvenv-virtual-env default-linter-venv-path)
                for (flycheck-var path) in linter-execs
                do (set flycheck-var (locate-file path dirs nil 'file-executable-p))))






              share|improve this answer






















                up vote
                3
                down vote










                up vote
                3
                down vote










                1. It would be a good idea to write a docstring for the defvar'd variables like linter-execs. The explanations in the post would make an excellent start, for example:



                  (defvar linter-execs '((flycheck-python-flake8-executable "bin/flake8")
                  (flycheck-python-pylint-executable "bin/pylint")
                  (flycheck-python-pycompile-executable "bin/python"))
                  "The linter executables, as list of two-element lists. The
                  first element of an entry is the flycheck variable that contains
                  the path of a linter executable. The second element is the
                  relative path of the executable from within the venv.")



                2. Writing (nth 0 exec) and (nth 1 exec) makes it hard to understand the meaning of these items of data. In Python you'd use tuple unpacking to give meaningful names to each element of the exec data structure, like this:



                  flycheck_var, path = exec


                  In Emacs Lisp you can use cl-destructuring-bind in a similar way:



                  (dolist (exec linter-execs)
                  (cl-destructuring-bind (flycheck-var path) exec
                  (let ((venv-linter-bin (concat pyvenv-virtual-env path))
                  ;; etc


                  But exec comes from a list that you are looping over, so you could use the cl-loop macro instead of dolist, and cl-loop has destructuring built in:



                  (cl-loop for (flycheck-var path) in linter-execs do
                  (let ((venv-linter-bin (concat pyvenv-virtual-env path))
                  ;; etc


                  You'll need (require 'cl-macs) to use cl-destructuring-bind or cl-loop.




                3. The two directories pyvenv-virtual-env and default-linter-venv-path are treated in exactly the same way: first we do (let ((Y (concat X path)) and then (file-exists-p Y) and finally (set flycheck-var Y). This repetition could be factored out into a loop:



                  (defun switch-linters ()
                  "Switch linter executables to those in the current venv.

                  If the venv does not have any linter packages, then they will be
                  set to those in the `default-linter-venv-path` venv. If these do
                  not exist, then the linter will be set to nil."
                  (cl-loop with dirs = (list pyvenv-virtual-env default-linter-venv-path)
                  for (flycheck-var path) in linter-execs
                  do (set flycheck-var
                  (cl-loop for directory in dirs
                  for executable = (concat directory path)
                  if (file-exists-p executable) return executable)))


                4. Instead of file-exists-p, you probably want to use file-executable-p.



                5. Looking for a file in a list of directories is built into Emacs as the function locate-file. Using this, we get:



                  (defun switch-linters ()
                  "Switch linter executables to those in the current venv.

                  If the venv does not have any linter packages, then they will be
                  set to those in the `default-linter-venv-path` venv. If these do
                  not exist, then the linter will be set to nil."
                  (cl-loop with dirs = (list pyvenv-virtual-env default-linter-venv-path)
                  for (flycheck-var path) in linter-execs
                  do (set flycheck-var (locate-file path dirs nil 'file-executable-p))))






                share|improve this answer













                1. It would be a good idea to write a docstring for the defvar'd variables like linter-execs. The explanations in the post would make an excellent start, for example:



                  (defvar linter-execs '((flycheck-python-flake8-executable "bin/flake8")
                  (flycheck-python-pylint-executable "bin/pylint")
                  (flycheck-python-pycompile-executable "bin/python"))
                  "The linter executables, as list of two-element lists. The
                  first element of an entry is the flycheck variable that contains
                  the path of a linter executable. The second element is the
                  relative path of the executable from within the venv.")



                2. Writing (nth 0 exec) and (nth 1 exec) makes it hard to understand the meaning of these items of data. In Python you'd use tuple unpacking to give meaningful names to each element of the exec data structure, like this:



                  flycheck_var, path = exec


                  In Emacs Lisp you can use cl-destructuring-bind in a similar way:



                  (dolist (exec linter-execs)
                  (cl-destructuring-bind (flycheck-var path) exec
                  (let ((venv-linter-bin (concat pyvenv-virtual-env path))
                  ;; etc


                  But exec comes from a list that you are looping over, so you could use the cl-loop macro instead of dolist, and cl-loop has destructuring built in:



                  (cl-loop for (flycheck-var path) in linter-execs do
                  (let ((venv-linter-bin (concat pyvenv-virtual-env path))
                  ;; etc


                  You'll need (require 'cl-macs) to use cl-destructuring-bind or cl-loop.




                3. The two directories pyvenv-virtual-env and default-linter-venv-path are treated in exactly the same way: first we do (let ((Y (concat X path)) and then (file-exists-p Y) and finally (set flycheck-var Y). This repetition could be factored out into a loop:



                  (defun switch-linters ()
                  "Switch linter executables to those in the current venv.

                  If the venv does not have any linter packages, then they will be
                  set to those in the `default-linter-venv-path` venv. If these do
                  not exist, then the linter will be set to nil."
                  (cl-loop with dirs = (list pyvenv-virtual-env default-linter-venv-path)
                  for (flycheck-var path) in linter-execs
                  do (set flycheck-var
                  (cl-loop for directory in dirs
                  for executable = (concat directory path)
                  if (file-exists-p executable) return executable)))


                4. Instead of file-exists-p, you probably want to use file-executable-p.



                5. Looking for a file in a list of directories is built into Emacs as the function locate-file. Using this, we get:



                  (defun switch-linters ()
                  "Switch linter executables to those in the current venv.

                  If the venv does not have any linter packages, then they will be
                  set to those in the `default-linter-venv-path` venv. If these do
                  not exist, then the linter will be set to nil."
                  (cl-loop with dirs = (list pyvenv-virtual-env default-linter-venv-path)
                  for (flycheck-var path) in linter-execs
                  do (set flycheck-var (locate-file path dirs nil 'file-executable-p))))







                share|improve this answer












                share|improve this answer



                share|improve this answer










                answered 1 hour ago









                Gareth Rees

                41.9k394168




                41.9k394168




















                    kmdouglass is a new contributor. Be nice, and check out our Code of Conduct.









                     

                    draft saved


                    draft discarded


















                    kmdouglass is a new contributor. Be nice, and check out our Code of Conduct.












                    kmdouglass is a new contributor. Be nice, and check out our Code of Conduct.











                    kmdouglass is a new contributor. Be nice, and check out our Code of Conduct.













                     


                    draft saved


                    draft discarded














                    StackExchange.ready(
                    function ()
                    StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fcodereview.stackexchange.com%2fquestions%2f203808%2fhook-to-switch-the-linter-binaries-in-emacs-lisp-according-to-virtual-environmen%23new-answer', 'question_page');

                    );

                    Post as a guest













































































                    Comments

                    Popular posts from this blog

                    List of Gilmore Girls characters

                    What does second last employer means? [closed]

                    One-line joke