1 ;;; apt-sources-list.el --- Mode for editing APT source.list files
3 ;; Copyright (C) 2001-2003, Dr. Rafael Sepúlveda <drs@gnulinux.org.mx>
4 ;; 2009 Peter S. Galbraith <psg@debian.org>
7 ;; Author: Dr. Rafael Sepúlveda <drs@gnulinux.org.mx>
8 ;; Maintainer: Joe Wreschnig <joe.wreschnig@gmail.com>
9 ;; URL: https://git.korewanetadesu.com/apt-sources-list.git
10 ;; Package-Requires: ((emacs "24.4"))
13 ;; This program is free software; you can redistribute it and/or modify
14 ;; it under the terms of the GNU General Public License as published by
15 ;; the Free Software Foundation, either version 3 of the License, or (at
16 ;; your option) any later version.
18 ;; This program is distributed in the hope that it will be useful, but
19 ;; WITHOUT ANY WARRANTY; without even the implied warranty of
20 ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
21 ;; General Public License for more details.
23 ;; You should have received a copy of the GNU General Public License
24 ;; along with this program. If not, see <http://www.gnu.org/licenses/>.
29 ;; This package contains a major mode for editing APT’s “.list” files.
31 ;; The “/etc/apt/sources.list” file and other files in
32 ;; “/etc/apt/sources.list.d” tell APT, found on Debian-based systems and
33 ;; others, where to find packages for installation.
35 ;; This format specifies a package source with a single line, e.g.:
37 ;; deb http://deb.debian.org/debian stable main contrib
39 ;; For more information about the format you can read the manual
40 ;; pages “apt(8)” and “sources.list(5)”, also on the web at URL
41 ;; ‘https://manpages.debian.org/stable/apt/sources.list.5.en.html’
42 ;; and URL ‘https://manpages.debian.org/stable/apt/apt.8.en.html’.
51 (defgroup apt-sources-list nil
52 "Mode for editing APT sources.list file."
55 (defface apt-sources-list-type
56 '((t (:inherit font-lock-constant-face)))
57 "Face for a source’s type (i.e. “deb” or “deb-src”).")
59 (defface apt-sources-list-uri
60 '((t (:inherit font-lock-variable-name-face)))
61 "Face for a source’s URI.")
63 (defface apt-sources-list-suite
64 '((t (:inherit font-lock-type-face)))
65 "Face for a source’s suite (e.g. “unstable”, “stretch/updates”).")
67 (defface apt-sources-list-options
68 '((t (:inherit font-lock-builtin-face)))
69 "Face for a package source’s options (e.g. “[arch=amd64]”).")
71 (defface apt-sources-list-components
72 '((t (:inherit font-lock-keyword-face)))
73 "Face for a package source’s components (e.g. “main”, “non-free”).")
75 (defcustom apt-sources-list-suites
76 '("stable" "testing" "unstable" "oldstable" "jessie" "stretch" "sid")
77 "Suites to offer for completion.
79 The first item in this list is used as the default value when
81 :type '(repeat string))
83 (defcustom apt-sources-list-components
84 '("main" "contrib" "non-free")
85 "Components to offer for completion.
87 The first item in this list is used as the default value when
89 :type '(repeat string))
91 (defcustom apt-sources-list-name-format "# %s"
92 "Format used in the name of a new source line.
94 This is used by ‘apt-sources-list-insert’. It should contain a
95 single “%s” which will be replaced with the source name."
97 :group 'apt-sources-list)
99 (defconst apt-sources-list-one-line
102 (group (or "deb" "deb-src"))
105 ;; TODO: This matches malformed options.
106 "[" (group (one-or-more (not (any "]\n#")))) "]"
109 (one-or-more (any "-A-Za-z0-9._"))
111 (one-or-more (not (any " \t\n#"))))
114 (or (and (one-or-more (not (any " \t\n#"))) "/")
115 (and (zero-or-more (not (any " \t\n#")))
116 (not (any " \t\n/#"))
119 (one-or-more (not (any " \t\n#")))
122 (one-or-more (not (any " \t\n#"))))))))
125 "Regex to match a valid APT source in one-line format.")
127 (defconst apt-sources-list-font-lock-keywords
128 `((,apt-sources-list-one-line
129 (1 'apt-sources-list-type)
130 (2 'apt-sources-list-options nil t)
131 (3 'apt-sources-list-uri)
132 (4 'apt-sources-list-suite)
133 (5 'apt-sources-list-components t t)))
134 "Faces for parts of sources.list lines.")
136 (defun apt-sources-list-insert (uri &rest properties)
137 "Insert a new package source at URI, with extra PROPERTIES.
139 When called interactively without a prefix argument, assume
140 the type is “deb” and no special options.
142 When called from Lisp, optional properties include:
144 ‘:name’ - a source name to include in a leading comment
145 ‘:type’ - “deb” or “deb-src”, defaulting to “deb”
146 ‘:options’ - an options string, without […] delimiters
147 ‘:suite’ - defaults to the first of ‘apt-sources-list-suites’
148 ‘:components’ - defaults to the first of ‘apt-sources-list-components’
150 You should read the official APT documentation for further
151 explanation of the format."
153 (let* ((_ (barf-if-buffer-read-only))
154 (name (read-string "Source name: "))
155 (type (if current-prefix-arg
156 (completing-read "Type: " '("deb" "deb-src") nil t "deb")
158 (options (if current-prefix-arg (read-string "Options: ") ""))
159 (uri (read-string "URI: " "https://"))
160 (suite (completing-read "Suite: "
161 apt-sources-list-suites nil nil
162 (car apt-sources-list-suites)))
164 (unless (string-suffix-p "/" suite)
165 (apt-sources-list--read-components))))
167 :name (unless (string-blank-p name) name)
169 :options (unless (string-blank-p options) options)
170 :suite suite :components components)))
172 (insert (let ((name (plist-get properties :name)))
174 (concat (format apt-sources-list-name-format name) "\n")
176 (or (plist-get properties :type) "deb")
177 (let ((options (plist-get properties :options)))
178 (if options (format " [%s] " options) " "))
181 (or (plist-get properties :suite)
182 (car apt-sources-list-suites))
183 (if (string-suffix-p "/" (or (plist-get properties :suite)
184 (car apt-sources-list-suites)))
186 (format " %s" (or (plist-get properties :components)
187 (car apt-sources-list-components))))))
189 (defun apt-sources-list-forward-source (&optional n)
190 "Go N source lines forward (backward if N is negative)."
197 (re-search-forward apt-sources-list-one-line nil nil n)
199 (error "No further repositories found buffer"))))
200 (goto-char (match-beginning 0)))
202 (defun apt-sources-list-backward-source (&optional n)
203 "Go N source lines backward (forward if N is negative)."
205 (apt-sources-list-forward-source (- (or n 1))))
207 (defun apt-sources-list-source-p ()
208 "Return non-nil if the line at point is a source."
209 (string-match-p apt-sources-list-one-line (thing-at-point 'line)))
211 (define-error 'apt-sources-list-not-found
212 "The point is not on an APT source line")
214 (define-error 'apt-sources-list-suite-component-mismatch
215 "Exact suite paths (ending with “/”) may not specify components")
217 (defun apt-sources-list-match-source ()
218 "Fill the match data with the source at point.
220 If there is no source, error."
221 (save-mark-and-excursion
223 (or (looking-at apt-sources-list-one-line)
224 (signal 'apt-sources-list-not-found nil))))
226 (defun apt-sources-list-change-type (&optional type)
227 "Change the type of the source at point to TYPE.
229 Interactively or when TYPE is nil, toggle the type between “deb”
233 (apt-sources-list-match-source)
235 (setq type (if (equal (match-string 1) "deb") "deb-src" "deb")))
236 (save-mark-and-excursion
237 (replace-match type t t nil 1))))
239 (defun apt-sources-list-change-options (options)
240 "Change the options of the source at point to OPTIONS (excluding []s)."
242 (list (save-match-data
243 (barf-if-buffer-read-only)
244 (apt-sources-list-match-source)
245 (read-string "Options: " (match-string 2)))))
247 (apt-sources-list-match-source)
248 (when (= 0 (length options))
250 (save-mark-and-excursion
251 (cond ((and (match-string 2) options)
252 (replace-match options t t nil 2))
254 (delete-region (- (match-beginning 2) 2) (1+ (match-end 2))))
256 (replace-match (concat (match-string 1) " [" options "]")
259 (defun apt-sources-list-change-uri (uri)
260 "Change the URI of the source at point to URI."
262 (list (save-match-data
263 (barf-if-buffer-read-only)
264 (apt-sources-list-match-source)
265 (read-string "URI: " (match-string 3)))))
267 (apt-sources-list-match-source)
268 (save-mark-and-excursion
269 (replace-match uri t t nil 3))))
271 (defun apt-sources-list--read-components (&optional initial)
272 "Read a components string, defaulting to INITIAL."
274 (let ((minibuffer-local-completion-map
275 (copy-keymap minibuffer-local-completion-map)))
276 (define-key minibuffer-local-completion-map (kbd "<SPC>") nil)
277 (completing-read "Components: "
278 apt-sources-list-components
280 (or initial (car apt-sources-list-components))))))
282 (defun apt-sources-list-change-suite (suite &optional default-components)
283 "Change the suite of the source at point to SUITE.
285 If the new suite requires components and the old one did not,
286 DEFAULT-COMPONENTS is used. If none are provided, the first item
287 in ‘apt-sources-list-components’ is used."
290 (barf-if-buffer-read-only)
291 (apt-sources-list-match-source)
292 (let ((components (match-string 5))
293 (suite (completing-read "Suite: "
294 apt-sources-list-suites)))
295 (if (not (string-suffix-p "/" suite))
296 (list suite (apt-sources-list--read-components))
299 (save-mark-and-excursion
301 (apt-sources-list-match-source)
302 (if (string-suffix-p "/" suite)
303 (when (match-string 5)
304 (replace-match "" t t nil 5))
305 (setq suite (concat suite " "
308 (car apt-sources-list-components)
310 (replace-match suite t t nil 4))))
312 (defun apt-sources-list-change-components (components)
313 "Change the components of the source at point to COMPONENTS."
316 (barf-if-buffer-read-only)
317 (apt-sources-list-match-source)
318 (when (string-suffix-p "/" (match-string 4))
319 (signal 'apt-sources-list-suite-component-mismatch nil))
320 (list (apt-sources-list--read-components
321 (substring-no-properties (match-string 5))))))
324 (apt-sources-list-match-source)
325 (when (string-suffix-p "/" (match-string 4))
326 (signal 'apt-sources-list-suite-component-mismatch nil))
327 (save-mark-and-excursion
328 (replace-match components t t nil 5))))
330 (defun apt-sources-list-replicate ()
331 "Copy the source line, toggling the type."
333 (apt-sources-list-match-source)
334 (let ((copy (buffer-substring (line-beginning-position)
335 (line-end-position))))
338 (insert (concat "\n" copy))
339 (apt-sources-list-change-type))))
342 (define-derived-mode apt-sources-list-mode prog-mode "apt/sources.list"
343 "Major mode for editing APT’s “.list” files.
345 The “/etc/apt/sources.list” file and other files in
346 “/etc/apt/sources.list.d” tell APT, found on Debian-based systems
347 and others, where to find packages for installation.
349 This format specifies a package source with a single line, e.g.:
351 deb http://deb.debian.org/debian stable main contrib
353 For more information about the format you can read the manual
354 pages “apt(8)” and “sources.list(5)”, also on the web at URL
355 ‘https://manpages.debian.org/stable/apt/sources.list.5.en.html’
356 and URL ‘https://manpages.debian.org/stable/apt/apt.8.en.html’.
358 \\{apt-sources-list-mode-map}
360 The above editing commands will raise errors if the current line
361 is not a correctly-formatted APT source."
363 (let ((syntab (make-syntax-table)))
364 (modify-syntax-entry ?# "<" syntab)
365 (modify-syntax-entry ?\n "> " syntab)
368 (setq-local comment-start "#")
369 (setq-local comment-start-skip "#+ *")
370 (font-lock-add-keywords nil apt-sources-list-font-lock-keywords))
374 (cons (rx (or (and (any "./") "sources.list")
375 (and "/sources.list.d/" (one-or-more anything) ".list"))
377 #'apt-sources-list-mode))
379 (let ((map apt-sources-list-mode-map))
380 (define-key map (kbd "C-c C-i") #'apt-sources-list-insert)
381 (define-key map (kbd "C-c C-r") #'apt-sources-list-replicate)
382 (define-key map (kbd "C-c C-t") #'apt-sources-list-change-type)
383 (define-key map (kbd "C-c C-o") #'apt-sources-list-change-options)
384 (define-key map (kbd "C-c C-u") #'apt-sources-list-change-uri)
385 (define-key map (kbd "C-c C-s") #'apt-sources-list-change-suite)
386 (define-key map (kbd "C-c C-c") #'apt-sources-list-change-components)
387 (define-key map [remap forward-list] #'apt-sources-list-forward-source)
388 (define-key map [remap backward-list] #'apt-sources-list-backward-source)
390 (easy-menu-define apt-sources-list-mode-menu map
391 "Menu for APT sources.list mode."
393 ["Insert Source" apt-sources-list-insert]
394 ["Copy Source" apt-sources-list-replicate
395 (apt-sources-list-source-p)]
397 ["Backward Source" apt-sources-list-backward-source]
398 ["Forward Source" apt-sources-list-forward-source]
400 ["Change Type" apt-sources-list-change-type
401 (apt-sources-list-source-p)]
402 ["Change Options" apt-sources-list-change-options
403 (apt-sources-list-source-p)]
404 ["Change URI" apt-sources-list-change-uri
405 (apt-sources-list-source-p)]
406 ["Change Suite" apt-sources-list-change-suite
407 (apt-sources-list-source-p)]
408 ["Change Components" apt-sources-list-change-components
410 (and (apt-sources-list-match-source) (match-string 5)))])))
413 (provide 'apt-sources-list)
414 ;;; apt-sources-list.el ends here