Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add project.el support #73

Merged
merged 3 commits into from
Jul 4, 2024
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
111 changes: 97 additions & 14 deletions python-pytest.el
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

;; Author: wouter bolsterlee <wouter@bolsterl.ee>
;; Version: 3.3.0
;; Package-Requires: ((emacs "24.4") (dash "2.18.0") (transient "0.3.7") (projectile "0.14.0") (s "1.12.0"))
;; Package-Requires: ((emacs "24.4") (dash "2.18.0") (transient "0.3.7") (s "1.12.0"))
;; Keywords: pytest, test, python, languages, processes, tools
;; URL: https://github.com/wbolster/emacs-python-pytest
;;
Expand All @@ -25,9 +25,11 @@

(require 'dash)
(require 'transient)
(require 'projectile)
(require 's)

(require 'projectile nil t)
(require 'project nil t)

(defgroup python-pytest nil
"pytest integration"
:group 'python
Expand Down Expand Up @@ -106,6 +108,25 @@ When non-nil only ‘test_foo()’ will match, and nothing else."
(const :tag "Save current buffer" save-current)
(const :tag "Ignore" nil)))

(defcustom python-pytest-preferred-project-manager 'auto
"Override `projectile' or `project' auto-discovery to set preference if using both."
:group 'python-pytest
:type '(choice (const :tag "Projectile" projectile)
(const :tag "Project" project)
(const :tag "Automatically selected" auto))
:set (lambda (symbol value)
(cond
((and (eq value 'projectile)
(not (featurep 'projectile)))
(user-error "Projectile preferred for python-pytest.el, but not available."))
((and (eq value 'project)
(not (fboundp 'project-root)))
(user-error (concat "Project.el preferred for python-pytest.el, "
"but need a newer version of Project (28.1+) to use.")))
(t
(set-default symbol value)
value))))

(defvar python-pytest--history nil
"History for pytest invocations.")

Expand Down Expand Up @@ -508,6 +529,11 @@ When present ON-REPLACEMENT is substituted, else OFF-REPLACEMENT is appended."
:argument "--numprocesses="
:choices '("auto" "0" "1" "2" "4" "8" "16"))

(defun python-pytest--using-projectile ()
"Returns t if projectile being used for project management."
(or (eq python-pytest-preferred-project-manager 'projectile)
(and (eq python-pytest-preferred-project-manager 'auto)
(bound-and-true-p projectile-mode))))

;; python helpers

Expand Down Expand Up @@ -551,12 +577,26 @@ Example: ‘MyABCThingy.__repr__’ becomes ‘test_my_abc_thingy_repr’."

(defun python-pytest--project-name ()
"Find the project name."
(projectile-project-name))
(if (python-pytest--using-projectile)
(projectile-project-name)
(if (fboundp 'project-name)
(project-name (project-current))
;; older emacs...
(file-name-nondirectory
(directory-file-name (car (project-roots (project-current))))))))

(defun python-pytest--project-root ()
"Find the project root directory."
(let ((projectile-require-project-root nil))
(projectile-compilation-dir)))
"Find the project root directory, for project.el can manually set your own
`project-compilation-dir' variable to override `project-root' being used."
(if (python-pytest--using-projectile)
(let ((projectile-require-project-root nil))
(projectile-compilation-dir))
(or (and (bound-and-true-p project-compilation-dir)
project-compilation-dir)
(if (fboundp 'project-root)
(project-root (project-current))
;; pre-emacs "28.1"
(car (project-roots (project-current)))))))

(defun python-pytest--relative-file-name (file)
"Make FILE relative to the project root."
Expand All @@ -567,11 +607,29 @@ Example: ‘MyABCThingy.__repr__’ becomes ‘test_my_abc_thingy_repr’."

(defun python-pytest--test-file-p (file)
"Tell whether FILE is a test file."
(projectile-test-file-p file))
(if (python-pytest--using-projectile)
(projectile-test-file-p file)
(let ((base-name (file-name-nondirectory file)))
(or (string-prefix-p "test_" base-name)
(string-suffix-p "_test.py" base-name)))))

(defun python-pytest--find-test-file (file)
"Find a test file associated to FILE, if any."
(let ((test-file (projectile-find-matching-test file)))
(let ((test-file))
(if (python-pytest--using-projectile)
(setq test-file (projectile-find-matching-test file))
(let* ((base-name (file-name-sans-extension (file-name-nondirectory file)))
(test-file-regex (concat "\\`test_"
base-name "\\.py\\'\\|\\`"
base-name "_test\\.py\\'")))
(setq test-file
(car (cl-delete-if
(lambda (full-file)
(let ((file (file-name-nondirectory full-file)))
(not (string-match-p
test-file-regex
file))))
(project-files (project-current t)))))))
(unless test-file
(user-error "No test file found"))
test-file))
Expand All @@ -585,11 +643,34 @@ Example: ‘MyABCThingy.__repr__’ becomes ‘test_my_abc_thingy_repr’."
(cl-defun python-pytest--select-test-files (&key type)
"Interactively choose test files."
(let* ((test-files
(->> (projectile-project-files (python-pytest--project-root))
(-sort 'string<)
(projectile-sort-by-recentf-first)
;; show test files if any found, otherwise show everything
(funcall (-orfn #'projectile-test-files #'identity))))
(if (python-pytest--using-projectile)
(->> (projectile-project-files (python-pytest--project-root))
(-sort 'string<)
(projectile-sort-by-recentf-first)
;; show test files if any found, otherwise show everything
(funcall (-orfn #'projectile-test-files #'identity)))
(let* ((vc-directory-exclusion-list
(append vc-directory-exclusion-list '("venv" ".venv")))
(sorted-test-files
(sort (cl-delete-if
(lambda (file)
(not (python-pytest--test-file-p file)))
(project-files (project-current t)))
jeff-phil marked this conversation as resolved.
Show resolved Hide resolved
#'string<))
(recentf-test-files '())
(test-files-prj
(when (fboundp 'recentf)
(dolist (file recentf-list
(progn
(setq sorted-test-files
(append (nreverse recentf-test-files)
sorted-test-files))
(cl-delete-duplicates sorted-test-files
:test 'equal )))
(when (and (file-exists-p file)
(python-pytest--test-file-p file))
(push (expand-file-name file) recentf-test-files))))))
test-files-prj)))
(test-directories
(->> test-files
(-map 'file-name-directory)
Expand All @@ -615,7 +696,9 @@ Example: ‘MyABCThingy.__repr__’ becomes ‘test_my_abc_thingy_repr’."
;; check all project buffers
(-when-let*
((buffers
(projectile-buffers-with-file (projectile-project-buffers)))
(if (python-pytest--using-projectile)
(projectile-buffers-with-file (projectile-project-buffers))
(-filter 'buffer-file-name (project-buffers (project-current t)))))
(modified-buffers
(-filter 'buffer-modified-p buffers))
wbolster marked this conversation as resolved.
Show resolved Hide resolved
(confirmed
Expand Down