|
0 |
;;; pip-requirements.el --- A major mode for editing pip requirements files.
|
|
1 |
|
|
2 |
;; Copyright (C) 2014 Wilfred Hughes <me@wilfred.me.uk>
|
|
3 |
;;
|
|
4 |
;; Author: Wilfred Hughes <me@wilfred.me.uk>
|
|
5 |
;; Created: 11 September 2014
|
|
6 |
;; Version: 0.5
|
|
7 |
;; Package-Requires: ((dash "2.8.0"))
|
|
8 |
|
|
9 |
;;; License:
|
|
10 |
|
|
11 |
;; This file is not part of GNU Emacs.
|
|
12 |
;; However, it is distributed under the same license.
|
|
13 |
|
|
14 |
;; GNU Emacs is free software; you can redistribute it and/or modify
|
|
15 |
;; it under the terms of the GNU General Public License as published by
|
|
16 |
;; the Free Software Foundation; either version 3, or (at your option)
|
|
17 |
;; any later version.
|
|
18 |
|
|
19 |
;; GNU Emacs is distributed in the hope that it will be useful,
|
|
20 |
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
21 |
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
22 |
;; GNU General Public License for more details.
|
|
23 |
|
|
24 |
;; You should have received a copy of the GNU General Public License
|
|
25 |
;; along with GNU Emacs; see the file COPYING. If not, write to the
|
|
26 |
;; Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
|
|
27 |
;; Boston, MA 02110-1301, USA.
|
|
28 |
|
|
29 |
;;; Commentary:
|
|
30 |
|
|
31 |
;; This is a major mode for editing pip requirements files, with the following features:
|
|
32 |
|
|
33 |
;; * Syntax highlighting
|
|
34 |
;; * Togglable comments
|
|
35 |
;; * Auto completion of package names from PyPI
|
|
36 |
|
|
37 |
;; TODO: Steal shamelessly all the fantasic ideas in
|
|
38 |
;; https://github.com/wuub/requirementstxt
|
|
39 |
|
|
40 |
;;; Code:
|
|
41 |
|
|
42 |
(require 'dash)
|
|
43 |
(require 'cl-lib)
|
|
44 |
|
|
45 |
(defgroup pip-requirements nil
|
|
46 |
"Requirements files for pip"
|
|
47 |
:prefix "pip-requirements-"
|
|
48 |
:group 'languages
|
|
49 |
:link '(url-link :tag "Github" "https://github.com/Wilfred/pip-requirements.el"))
|
|
50 |
|
|
51 |
(defcustom pip-requirements-mode-hook nil
|
|
52 |
"Hook to run after `pip-requirements-mode'."
|
|
53 |
:group 'pip-requirements
|
|
54 |
:type 'hook
|
|
55 |
:risky t)
|
|
56 |
|
|
57 |
;;;###autoload
|
|
58 |
(add-to-list 'auto-mode-alist
|
|
59 |
`(,(rx ".pip" string-end) . pip-requirements-mode))
|
|
60 |
;;;###autoload
|
|
61 |
(add-to-list 'auto-mode-alist
|
|
62 |
`(,(rx "requirements" (zero-or-more anything) ".txt" string-end) . pip-requirements-mode))
|
|
63 |
|
|
64 |
;;;###autoload
|
|
65 |
(add-to-list 'auto-mode-alist
|
|
66 |
`(,(rx "requirements.in") . pip-requirements-mode))
|
|
67 |
|
|
68 |
(defconst pip-requirements-name-regex
|
|
69 |
(rx
|
|
70 |
line-start
|
|
71 |
(group (1+ (or alphanumeric "-" "_" ".")))))
|
|
72 |
|
|
73 |
(defconst pip-requirements-version-regex
|
|
74 |
(rx
|
|
75 |
(group (or "==" ">" ">=" "<" "<=" "!="))
|
|
76 |
(group (1+ (or digit "b" "." "post")))))
|
|
77 |
|
|
78 |
(defconst pip-requirements-operators
|
|
79 |
(list
|
|
80 |
(list pip-requirements-name-regex 1 'font-lock-variable-name-face)
|
|
81 |
(list pip-requirements-version-regex 1 'font-lock-builtin-face)
|
|
82 |
(list pip-requirements-version-regex 2 'font-lock-constant-face)))
|
|
83 |
|
|
84 |
(defconst pip-requirements-syntax-table
|
|
85 |
(let ((table (make-syntax-table)))
|
|
86 |
(modify-syntax-entry ?# "<" table)
|
|
87 |
(modify-syntax-entry ?\n ">" table)
|
|
88 |
table))
|
|
89 |
|
|
90 |
(defvar pip-http-buffer nil)
|
|
91 |
(defvar pip-packages nil
|
|
92 |
"List of PyPI packages for completion.")
|
|
93 |
|
|
94 |
(defun pip-requirements-callback (&rest _)
|
|
95 |
(with-current-buffer pip-http-buffer
|
|
96 |
;; Move over the HTTP header.
|
|
97 |
(goto-char (point-min))
|
|
98 |
(re-search-forward "^$" nil 'move)
|
|
99 |
|
|
100 |
(setq pip-packages
|
|
101 |
(->> (libxml-parse-html-region (point) (point-max))
|
|
102 |
;; Get the body tag.
|
|
103 |
-last-item
|
|
104 |
;; Immediate children of the body.
|
|
105 |
cdr cdr cdr
|
|
106 |
;; Anchor tags.
|
|
107 |
(--filter (eq (car it) 'a))
|
|
108 |
;; Inner text of anchor tags.
|
|
109 |
(-map #'cl-third))))
|
|
110 |
(kill-buffer pip-http-buffer))
|
|
111 |
|
|
112 |
(defun pip-requirements-fetch-packages ()
|
|
113 |
"Get a list of all packages available on PyPI and store them in `pip-packages'.
|
|
114 |
Assumes Emacs is compiled with libxml."
|
|
115 |
(setq pip-http-buffer
|
|
116 |
(url-retrieve "https://pypi.python.org/simple/"
|
|
117 |
#'pip-requirements-callback nil t)))
|
|
118 |
|
|
119 |
(defun pip-requirements-complete-at-point ()
|
|
120 |
"Complete at point in Pip Requirements Mode."
|
|
121 |
(let* ((bounds (bounds-of-thing-at-point 'symbol))
|
|
122 |
(start (or (car bounds) (point)))
|
|
123 |
(end (or (cdr bounds) (point))))
|
|
124 |
(list start end pip-packages)))
|
|
125 |
|
|
126 |
;; Declare variables from AC, to avoid a hard dependency on Auto Complete.
|
|
127 |
(defvar ac-modes)
|
|
128 |
(defvar ac-sources)
|
|
129 |
|
|
130 |
;;;###autoload
|
|
131 |
(defun pip-requirements-auto-complete-setup ()
|
|
132 |
"Setup Auto-Complete for Pip Requirements.
|
|
133 |
|
|
134 |
See URL `https://github.com/auto-complete/auto-complete' for
|
|
135 |
information about Auto Complete."
|
|
136 |
(add-to-list 'ac-modes 'pip-requirements-mode)
|
|
137 |
(add-to-list 'ac-sources '((candidates . pip-packages)))
|
|
138 |
(when (and (fboundp 'auto-complete-mode)
|
|
139 |
(not (bound-and-true-p auto-complete-mode)))
|
|
140 |
;; Enable Auto Complete
|
|
141 |
(auto-complete-mode)))
|
|
142 |
|
|
143 |
(custom-add-frequent-value 'pip-requirements-mode-hook
|
|
144 |
'pip-requirements-auto-complete-setup)
|
|
145 |
|
|
146 |
;;;###autoload
|
|
147 |
(define-derived-mode pip-requirements-mode prog-mode "pip-require"
|
|
148 |
"Major mode for editing pip requirements files."
|
|
149 |
:syntax-table pip-requirements-syntax-table
|
|
150 |
(set (make-local-variable 'font-lock-defaults) '(pip-requirements-operators))
|
|
151 |
(set (make-local-variable 'comment-start) "#")
|
|
152 |
(add-hook 'completion-at-point-functions
|
|
153 |
#'pip-requirements-complete-at-point nil 'local)
|
|
154 |
(unless pip-packages
|
|
155 |
;; Fetch the list of packages for completion
|
|
156 |
(pip-requirements-fetch-packages)))
|
|
157 |
|
|
158 |
(provide 'pip-requirements)
|
|
159 |
;;; pip-requirements.el ends here
|