]> crepu.dev Git - config.git/blame_incremental - djavu-asus/emacs/elpa/elpy-20230803.1455/elpy-django.el
Reorganización de directorios
[config.git] / djavu-asus / emacs / elpa / elpy-20230803.1455 / elpy-django.el
... / ...
CommitLineData
1;;; elpy-django.el --- Django extension for elpy
2
3;; Copyright (C) 2013-2019 Jorgen Schaefer
4
5;; Author: Daniel Gopar <gopardaniel@gmail.com>
6;; URL: https://github.com/jorgenschaefer/elpy
7
8;; This program is free software; you can redistribute it and/or
9;; modify it under the terms of the GNU General Public License
10;; as published by the Free Software Foundation; either version 3
11;; of the License, or (at your option) any later version.
12
13;; This program is distributed in the hope that it will be useful,
14;; but WITHOUT ANY WARRANTY; without even the implied warranty of
15;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16;; GNU General Public License for more details.
17
18;; You should have received a copy of the GNU General Public License
19;; along with this program. If not, see <http://www.gnu.org/licenses/>.
20
21;;; Commentary:
22
23;; This file serves as an extension to elpy by adding django support
24
25;;; Code:
26
27(require 's)
28
29;;;;;;;;;;;;;;;;;;;;;;
30;;; User customization
31
32(defcustom elpy-django-command "django-admin.py"
33 "Command to use when running Django specific commands.
34Best to set it to full path to 'manage.py' if it's available."
35 :type 'string
36 :safe 'stringp
37 :group 'elpy-django)
38(make-variable-buffer-local 'elpy-django-command)
39
40(defcustom elpy-django-server-ipaddr "127.0.0.1"
41 "What address Django will use when running the dev server."
42 :type 'string
43 :safe 'stringp
44 :group 'elpy-django)
45(make-variable-buffer-local 'elpy-django-server-ipaddr)
46
47(defcustom elpy-django-server-port "8000"
48 "What port Django will use when running the dev server."
49 :type 'string
50 :safe 'stringp
51 :group 'elpy-django)
52(make-variable-buffer-local 'elpy-django-server-port)
53
54(defcustom elpy-django-server-command "runserver"
55 "When executing `elpy-django-runserver' what should be the server
56command to use."
57 :type 'string
58 :safe 'stringp
59 :group 'elpy-django)
60(make-variable-buffer-local 'elpy-django-server-command)
61
62(defcustom elpy-django-always-prompt nil
63 "When non-nil, it will always prompt for extra arguments
64to pass with the chosen command."
65 :type 'boolean
66 :safe 'booleanp
67 :group 'elpy-django)
68(make-variable-buffer-local 'elpy-django-always-prompt)
69
70(defcustom elpy-django-commands-with-req-arg '("startapp" "startproject"
71 "loaddata" "sqlmigrate"
72 "sqlsequencereset"
73 "squashmigrations")
74 "Used to determine if we should prompt for arguments. Some commands
75require arguments in order for it to work."
76 :type 'list
77 :safe 'listp
78 :group 'elpy-django)
79(make-variable-buffer-local 'elpy-django-commands-with-req-arg)
80
81(defcustom elpy-django-test-runner-formats '(("django_nose.NoseTestSuiteRunner" . ":")
82 (".*" . "."))
83 "List of test runners and their format for calling tests.
84
85 The keys are the regular expressions to match the runner used in test,
86while the values are the separators to use to build test target path.
87Some tests runners are called differently. For example, Nose requires a ':' when calling specific tests,
88but the default Django test runner uses '.'"
89 :type 'list
90 :safe 'listp
91 :group 'elpy-django)
92(make-variable-buffer-local 'elpy-django-test-runner-formats)
93
94(defcustom elpy-django-test-runner-args '("test" "--noinput")
95 "Arguments to pass to the test runner when calling tests."
96 :type '(repeat string)
97 :group 'elpy-django)
98(make-variable-buffer-local 'elpy-django-test-runner-args)
99
100(defcustom elpy-test-django-runner-command nil
101 "Deprecated. Please define Django command in `elpy-django-command' and
102test arguments in `elpy-django-test-runner-args'"
103 :type '(repeat string)
104 :group 'elpy-django)
105(make-obsolete-variable 'elpy-test-django-runner-command nil "March 2018")
106
107(defcustom elpy-test-django-runner-manage-command nil
108 "Deprecated. Please define Django command in `elpy-django-command' and
109test arguments in `elpy-django-test-runner-args'."
110 :type '(repeat string)
111 :group 'elpy-django)
112(make-obsolete-variable 'elpy-test-django-runner-manage-command nil "March 2018")
113
114(defcustom elpy-test-django-with-manage nil
115 "Deprecated. Please define Django command in `elpy-django-command' and
116test arguments in `elpy-django-test-runner-args'."
117 :type 'boolean
118 :group 'elpy-django)
119(make-obsolete-variable 'elpy-test-django-with-manage nil "March 2018")
120
121;;;;;;;;;;;;;;;;;;;;;;
122;; Key map
123
124(defvar elpy-django-mode-map
125 (let ((map (make-sparse-keymap)))
126 (define-key map (kbd "c") 'elpy-django-command)
127 (define-key map (kbd "r") 'elpy-django-runserver)
128 map)
129 "Key map for django extension")
130
131;;;;;;;;;;;;;;;;;;;;;;
132;;; Helper Functions
133
134(defun elpy-django-setup ()
135 "Decides whether to start the minor mode or not."
136 ;; Make sure we're in an actual file and we can find
137 ;; manage.py. Otherwise user will have to manually
138 ;; start this mode if they're using 'django-admin.py'
139 (when (locate-dominating-file default-directory "manage.py")
140 ;; Let's be nice and point to full path of 'manage.py'
141 ;; This only affects the buffer if there's no directory
142 ;; variable overwriting it.
143 (setq elpy-django-command
144 (expand-file-name (concat (locate-dominating-file default-directory "manage.py") "manage.py")))
145 (elpy-django 1)))
146
147(defun elpy-project-find-django-root ()
148 "Return the current Django project root, if any.
149
150This is marked with 'manage.py' or 'django-admin.py'."
151 (or (locate-dominating-file default-directory "django-admin.py")
152 (locate-dominating-file default-directory "manage.py")))
153
154(defun elpy-django--get-commands ()
155 "Return list of django commands."
156 (let ((dj-commands-str nil)
157 (help-output
158 (shell-command-to-string (concat elpy-django-command " -h"))))
159 (setq dj-commands-str
160 (with-temp-buffer
161 (progn
162 (insert help-output)
163 (goto-char (point-min))
164 (delete-region (point) (search-forward "Available subcommands:" nil nil nil))
165 ;; cleanup [auth] and stuff
166 (goto-char (point-min))
167 (save-excursion
168 (while (re-search-forward "\\[.*\\]" nil t)
169 (replace-match "" nil nil)))
170 (buffer-string))))
171 ;; get a list of commands from the output of manage.py -h
172 ;; What would be the pattern to optimize this ?
173 (setq dj-commands-str (split-string dj-commands-str "\n"))
174 (setq dj-commands-str (cl-remove-if (lambda (x) (string= x "")) dj-commands-str))
175 (setq dj-commands-str (mapcar (lambda (x) (s-trim x)) dj-commands-str))
176 (sort dj-commands-str 'string-lessp)))
177
178
179(defvar elpy-django--test-runner-cache nil
180 "Internal cache for elpy-django--get-test-runner.
181The cache is keyed on project root and DJANGO_SETTINGS_MODULE env var")
182
183(defvar elpy-django--test-runner-cache-max-size 100
184 "Maximum number of entries in test runner cache")
185
186
187(defun elpy-django--get-test-runner ()
188 "Return the name of the django test runner.
189Needs `DJANGO_SETTINGS_MODULE' to be set in order to work.
190The result is memoized on project root and `DJANGO_SETTINGS_MODULE'"
191 (let ((django-import-cmd "import django;django.setup();from django.conf import settings;print(settings.TEST_RUNNER)")
192 (django-settings-env (getenv "DJANGO_SETTINGS_MODULE"))
193 (default-directory (elpy-project-root)))
194 ;; If no Django settings has been set, then nothing will work. Warn user
195 (unless django-settings-env
196 (error "Please set environment variable `DJANGO_SETTINGS_MODULE' if you'd like to run the test runner"))
197
198 (let* ((runner-key (list default-directory django-settings-env))
199 (runner (or (elpy-django--get-test-runner-from-cache runner-key)
200 (elpy-django--cache-test-runner
201 runner-key
202 (elpy-django--detect-test-runner django-settings-env)))))
203 (elpy-django--limit-test-runner-cache-size)
204 runner)))
205
206
207(defun elpy-django--get-test-format ()
208 "When running a Django test, some test runners require a different format than others.
209Return the correct string format here."
210 (let ((runner (elpy-django--get-test-runner))
211 (found nil)
212 (formats elpy-django-test-runner-formats))
213 (while (and formats (not found))
214 (let* ((entry (car formats)) (regex (car entry)))
215 (when (string-match regex runner)
216 (setq found (cdr entry))))
217 (setq formats (cdr formats)))
218 (or found (error (format "Unable to find test format for `%s'"
219 (elpy-django--get-test-runner))))))
220
221
222(defun elpy-django--detect-test-runner (django-settings-env)
223 "Detects django test runner in current configuration"
224 ;; We have to be able to import the DJANGO_SETTINGS_MODULE to detect test
225 ;; runner; if python process importing settings exits with error,
226 ;; then warn the user that settings is not valid
227 (unless (= 0 (call-process elpy-rpc-python-command nil nil nil
228 "-c" (format "import %s" django-settings-env)))
229 (error (format "Unable to import DJANGO_SETTINGS_MODULE: '%s'"
230 django-settings-env)))
231 (s-trim (shell-command-to-string
232 (format "%s -c '%s'" elpy-rpc-python-command
233 django-import-cmd))))
234
235
236(defun elpy-django--get-test-runner-from-cache (key)
237 "Retrieve from cache test runner with given caching key.
238Return nil if the runner is missing in cache"
239 (let ((runner (cdr (assoc key elpy-django--test-runner-cache))))
240 ;; if present re-add to implement lru cache
241 (when runner (elpy-django--cache-test-runner key runner))))
242
243
244(defun elpy-django--cache-test-runner (key runner)
245 "Store in test runner cache a runner with a key"""
246 (push (cons key runner) elpy-django--test-runner-cache)
247 runner)
248
249
250(defun elpy-django--limit-test-runner-cache-size ()
251 "Ensure elpy-django--test-runner-cache does not overflow a fixed size"
252 (while (> (length elpy-django--test-runner-cache)
253 elpy-django--test-runner-cache-max-size)
254 (setq elpy-django--test-runner-cache (cdr elpy-django--test-runner-cache))))
255
256
257;;;;;;;;;;;;;;;;;;;;;;
258;;; User Functions
259
260(defun elpy-django-command (cmd)
261 "Prompt user for Django command. If called with `C-u',
262it will prompt for other flags/arguments to run."
263 (interactive (list (completing-read "Command: " (elpy-django--get-commands) nil nil)))
264 ;; Called with C-u, variable is set or is a cmd that requires an argument
265 (when (or current-prefix-arg
266 elpy-django-always-prompt
267 (member cmd elpy-django-commands-with-req-arg))
268 (setq cmd (concat cmd " " (read-shell-command (concat cmd ": ") "--noinput"))))
269 ;;
270 (cond ((string= cmd "shell")
271 (run-python (concat elpy-django-command " shell -i python") t t))
272 (t
273 (let* ((program (car (split-string elpy-django-command)))
274 (args (cdr (split-string elpy-django-command)))
275 (buffer-name (format "django-%s" (car (split-string cmd)))))
276 (when (get-buffer (format "*%s*" buffer-name))
277 (kill-buffer (format "*%s*" buffer-name)))
278 (pop-to-buffer
279 (apply 'make-comint buffer-name program nil
280 (append args (split-string cmd))))))))
281
282(defun elpy-django-runserver (arg)
283 "Start the server and automatically add the ipaddr and port.
284Also create it's own special buffer so that we can have multiple
285servers running per project.
286
287When called with a prefix (C-u), it will prompt for additional args."
288 (interactive "P")
289 (let* ((cmd (concat elpy-django-command " " elpy-django-server-command))
290 (proj-root (if (elpy-project-root)
291 (file-name-base (directory-file-name
292 (elpy-project-root)))
293 (message "Elpy cannot find the root of the current django project. Starting the server in the current directory: '%s'."
294 default-directory)
295 default-directory))
296 (buff-name (format "*runserver[%s]*" proj-root)))
297 ;; Kill any previous instance of runserver since we might be doing something new
298 (when (get-buffer buff-name)
299 (kill-buffer buff-name))
300 (setq cmd (concat cmd " " elpy-django-server-ipaddr ":" elpy-django-server-port))
301 (when (or arg elpy-django-always-prompt)
302 (setq cmd (concat cmd " "(read-shell-command (concat cmd ": ")))))
303 (compile cmd)
304 (with-current-buffer "*compilation*"
305 (rename-buffer buff-name))))
306
307(defun elpy-test-django-runner (top _file module test)
308 "Test the project using the Django discover runner,
309or with manage.py if elpy-test-django-with-manage is true.
310
311This requires Django 1.6 or the django-discover-runner package."
312 (interactive (elpy-test-at-point))
313 (if module
314 (apply #'elpy-test-run
315 top
316 (append
317 (list elpy-django-command)
318 elpy-django-test-runner-args
319 (list (if test
320 (format "%s%s%s" module (elpy-django--get-test-format) test)
321 module))))
322 (apply #'elpy-test-run
323 top
324 (append
325 (list elpy-django-command)
326 elpy-django-test-runner-args))))
327(put 'elpy-test-django-runner 'elpy-test-runner-p t)
328
329(define-minor-mode elpy-django
330 "Minor mode for Django commands."
331 :group 'elpy-django)
332
333(provide 'elpy-django)
334;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
335;;; elpy-django.el ends here