Skip to content

Commit 1ecfb44

Browse files
authored
feat: add project.el support (#73)
does not replace projectile, but simply adds project.el support; projectile is still default if installed and projectile-mode active. closes #55
1 parent 9f850e2 commit 1ecfb44

File tree

1 file changed

+97
-14
lines changed

1 file changed

+97
-14
lines changed

python-pytest.el

+97-14
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
;; Author: wouter bolsterlee <[email protected]>
44
;; Version: 3.3.0
5-
;; Package-Requires: ((emacs "24.4") (dash "2.18.0") (transient "0.3.7") (projectile "0.14.0") (s "1.12.0"))
5+
;; Package-Requires: ((emacs "24.4") (dash "2.18.0") (transient "0.3.7") (s "1.12.0"))
66
;; Keywords: pytest, test, python, languages, processes, tools
77
;; URL: https://github.com/wbolster/emacs-python-pytest
88
;;
@@ -25,9 +25,11 @@
2525

2626
(require 'dash)
2727
(require 'transient)
28-
(require 'projectile)
2928
(require 's)
3029

30+
(require 'projectile nil t)
31+
(require 'project nil t)
32+
3133
(defgroup python-pytest nil
3234
"pytest integration"
3335
:group 'python
@@ -106,6 +108,25 @@ When non-nil only ‘test_foo()’ will match, and nothing else."
106108
(const :tag "Save current buffer" save-current)
107109
(const :tag "Ignore" nil)))
108110

111+
(defcustom python-pytest-preferred-project-manager 'auto
112+
"Override `projectile' or `project' auto-discovery to set preference if using both."
113+
:group 'python-pytest
114+
:type '(choice (const :tag "Projectile" projectile)
115+
(const :tag "Project" project)
116+
(const :tag "Automatically selected" auto))
117+
:set (lambda (symbol value)
118+
(cond
119+
((and (eq value 'projectile)
120+
(not (featurep 'projectile)))
121+
(user-error "Projectile preferred for python-pytest.el, but not available."))
122+
((and (eq value 'project)
123+
(not (fboundp 'project-root)))
124+
(user-error (concat "Project.el preferred for python-pytest.el, "
125+
"but need a newer version of Project (28.1+) to use.")))
126+
(t
127+
(set-default symbol value)
128+
value))))
129+
109130
(defvar python-pytest--history nil
110131
"History for pytest invocations.")
111132

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

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

512538
;; python helpers
513539

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

552578
(defun python-pytest--project-name ()
553579
"Find the project name."
554-
(projectile-project-name))
580+
(if (python-pytest--using-projectile)
581+
(projectile-project-name)
582+
(if (fboundp 'project-name)
583+
(project-name (project-current))
584+
;; older emacs...
585+
(file-name-nondirectory
586+
(directory-file-name (car (project-roots (project-current))))))))
555587

556588
(defun python-pytest--project-root ()
557-
"Find the project root directory."
558-
(let ((projectile-require-project-root nil))
559-
(projectile-compilation-dir)))
589+
"Find the project root directory, for project.el can manually set your own
590+
`project-compilation-dir' variable to override `project-root' being used."
591+
(if (python-pytest--using-projectile)
592+
(let ((projectile-require-project-root nil))
593+
(projectile-compilation-dir))
594+
(or (and (bound-and-true-p project-compilation-dir)
595+
project-compilation-dir)
596+
(if (fboundp 'project-root)
597+
(project-root (project-current))
598+
;; pre-emacs "28.1"
599+
(car (project-roots (project-current)))))))
560600

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

568608
(defun python-pytest--test-file-p (file)
569609
"Tell whether FILE is a test file."
570-
(projectile-test-file-p file))
610+
(if (python-pytest--using-projectile)
611+
(projectile-test-file-p file)
612+
(let ((base-name (file-name-nondirectory file)))
613+
(or (string-prefix-p "test_" base-name)
614+
(string-suffix-p "_test.py" base-name)))))
571615

572616
(defun python-pytest--find-test-file (file)
573617
"Find a test file associated to FILE, if any."
574-
(let ((test-file (projectile-find-matching-test file)))
618+
(let ((test-file))
619+
(if (python-pytest--using-projectile)
620+
(setq test-file (projectile-find-matching-test file))
621+
(let* ((base-name (file-name-sans-extension (file-name-nondirectory file)))
622+
(test-file-regex (concat "\\`test_"
623+
base-name "\\.py\\'\\|\\`"
624+
base-name "_test\\.py\\'")))
625+
(setq test-file
626+
(car (cl-delete-if
627+
(lambda (full-file)
628+
(let ((file (file-name-nondirectory full-file)))
629+
(not (string-match-p
630+
test-file-regex
631+
file))))
632+
(project-files (project-current t)))))))
575633
(unless test-file
576634
(user-error "No test file found"))
577635
test-file))
@@ -585,11 +643,34 @@ Example: ‘MyABCThingy.__repr__’ becomes ‘test_my_abc_thingy_repr’."
585643
(cl-defun python-pytest--select-test-files (&key type)
586644
"Interactively choose test files."
587645
(let* ((test-files
588-
(->> (projectile-project-files (python-pytest--project-root))
589-
(-sort 'string<)
590-
(projectile-sort-by-recentf-first)
591-
;; show test files if any found, otherwise show everything
592-
(funcall (-orfn #'projectile-test-files #'identity))))
646+
(if (python-pytest--using-projectile)
647+
(->> (projectile-project-files (python-pytest--project-root))
648+
(-sort 'string<)
649+
(projectile-sort-by-recentf-first)
650+
;; show test files if any found, otherwise show everything
651+
(funcall (-orfn #'projectile-test-files #'identity)))
652+
(let* ((vc-directory-exclusion-list
653+
(append vc-directory-exclusion-list '("venv" ".venv")))
654+
(sorted-test-files
655+
(sort (cl-delete-if
656+
(lambda (file)
657+
(not (python-pytest--test-file-p file)))
658+
(project-files (project-current t)))
659+
#'string<))
660+
(recentf-test-files '())
661+
(test-files-prj
662+
(when (fboundp 'recentf)
663+
(dolist (file recentf-list
664+
(progn
665+
(setq sorted-test-files
666+
(append (nreverse recentf-test-files)
667+
sorted-test-files))
668+
(cl-delete-duplicates sorted-test-files
669+
:test 'equal )))
670+
(when (and (file-exists-p file)
671+
(python-pytest--test-file-p file))
672+
(push (expand-file-name file) recentf-test-files))))))
673+
test-files-prj)))
593674
(test-directories
594675
(->> test-files
595676
(-map 'file-name-directory)
@@ -615,7 +696,9 @@ Example: ‘MyABCThingy.__repr__’ becomes ‘test_my_abc_thingy_repr’."
615696
;; check all project buffers
616697
(-when-let*
617698
((buffers
618-
(projectile-buffers-with-file (projectile-project-buffers)))
699+
(if (python-pytest--using-projectile)
700+
(projectile-buffers-with-file (projectile-project-buffers))
701+
(-filter 'buffer-file-name (project-buffers (project-current t)))))
619702
(modified-buffers
620703
(-filter 'buffer-modified-p buffers))
621704
(confirmed

0 commit comments

Comments
 (0)