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’.
52 (defgroup apt-sources-list nil
53 "Mode for editing APT sources.list file."
56 (defface apt-sources-list-type
57 '((t (:inherit font-lock-constant-face)))
58 "Face for a source’s type (i.e. “deb” or “deb-src”).")
60 (defface apt-sources-list-uri
61 '((t (:inherit font-lock-variable-name-face)))
62 "Face for a source’s URI.")
64 (defface apt-sources-list-suite
65 '((t (:inherit font-lock-type-face)))
66 "Face for a source’s suite (e.g. “unstable”, “stretch/updates”).")
68 (defface apt-sources-list-options
69 '((t (:inherit font-lock-builtin-face)))
70 "Face for a package source’s options (e.g. “[arch=amd64]”).")
72 (defface apt-sources-list-components
73 '((t (:inherit font-lock-keyword-face)))
74 "Face for a package source’s components (e.g. “main”, “non-free”).")
76 (defcustom apt-sources-list-suites
77 '("stable" "testing" "unstable" "oldstable" "jessie" "stretch" "sid")
78 "Suites to offer for completion.
80 The first item in this list is used as the default value when
82 :type '(repeat string))
84 (defcustom apt-sources-list-components
85 '("main" "contrib" "non-free")
86 "Components to offer for completion.
88 The first item in this list is used as the default value when
90 :type '(repeat string))
92 (defcustom apt-sources-list-name-format "# %s"
93 "Format used in the name of a new source line.
95 This is used by ‘apt-sources-list-insert’. It should contain a
96 single “%s” which will be replaced with the source name."
98 :group 'apt-sources-list)
100 (defconst apt-sources-list-one-line
103 (group (or "deb" "deb-src"))
106 ;; TODO: This matches malformed options.
107 "[" (group (one-or-more (not (any "]\n#")))) "]"
110 (one-or-more (any "-A-Za-z0-9._"))
112 (one-or-more (not (any " \t\n#"))))
115 (or (and (zero-or-more (not (any " \t\n#"))) "/")
116 (and (zero-or-more (not (any " \t\n#")))
117 (not (any " \t\n/#"))
120 (one-or-more (not (any " \t\n#")))
123 (one-or-more (not (any " \t\n#"))))))))
126 "Regex to match a valid APT source in one-line format.")
128 (defconst apt-sources-list-font-lock-keywords
129 `((,apt-sources-list-one-line
130 (1 'apt-sources-list-type)
131 (2 'apt-sources-list-options nil t)
132 (3 'apt-sources-list-uri)
133 (4 'apt-sources-list-suite)
134 (5 'apt-sources-list-components t t)))
135 "Faces for parts of sources.list lines.")
137 (cl-defun apt-sources-list-insert
138 (uri &key name (type "deb") options
139 (suite (car apt-sources-list-suites))
140 (components (car apt-sources-list-components)))
141 "Insert a new package source at URI.
143 When called interactively without a prefix argument, assume
144 the type is “deb” and no special options.
146 When called from Lisp, optional arguments include:
148 NAME - a source name to include in a leading comment
149 TYPE - “deb” or “deb-src”, defaulting to “deb”
150 OPTIONS - an options string, without […] delimiters
151 SUITE - defaults to the first item of ‘apt-sources-list-suites’
152 COMPONENTS - defaults to the first item of ‘apt-sources-list-components’
154 You should read the official APT documentation for further
155 explanation of the format."
157 (let* ((_ (barf-if-buffer-read-only))
158 (name (read-string "Source name: "))
159 (type (if current-prefix-arg
160 (completing-read "Type: " '("deb" "deb-src") nil t "deb")
162 (options (if current-prefix-arg (read-string "Options: ") ""))
163 (uri (read-string "URI: " "https://"))
164 (suite (completing-read "Suite: "
165 apt-sources-list-suites nil nil
166 (car apt-sources-list-suites)))
168 (unless (string-suffix-p "/" suite)
169 (apt-sources-list--read-components))))
171 :name (unless (string-blank-p name) name)
173 :options (unless (string-blank-p options) options)
174 :suite suite :components components)))
177 (insert (format apt-sources-list-name-format name) "\n"))
178 (insert type (if options (format " [%s] " options) " ") uri " "
179 suite (if (string-suffix-p "/" suite) ""
180 (format " %s" components))))
182 (defun apt-sources-list-forward-source (&optional n)
183 "Go N source lines forward (backward if N is negative)."
190 (re-search-forward apt-sources-list-one-line nil nil n)
192 (error "No further repositories found buffer"))))
193 (goto-char (match-beginning 0)))
195 (defun apt-sources-list-backward-source (&optional n)
196 "Go N source lines backward (forward if N is negative)."
198 (apt-sources-list-forward-source (- (or n 1))))
200 (defun apt-sources-list-source-p ()
201 "Return non-nil if the line at point is a source."
202 (string-match-p apt-sources-list-one-line (thing-at-point 'line)))
204 (define-error 'apt-sources-list-not-found
205 "The point is not on an APT source line")
207 (define-error 'apt-sources-list-suite-component-mismatch
208 "Exact suite paths (ending with “/”) may not specify components")
210 (defun apt-sources-list-match-source ()
211 "Fill the match data with the source at point.
213 If there is no source, error."
214 (save-mark-and-excursion
216 (or (looking-at apt-sources-list-one-line)
217 (signal 'apt-sources-list-not-found nil))))
219 (defun apt-sources-list-change-type (&optional type)
220 "Change the type of the source at point to TYPE.
222 Interactively or when TYPE is nil, toggle the type between “deb”
226 (apt-sources-list-match-source)
228 (setq type (if (equal (match-string 1) "deb") "deb-src" "deb")))
229 (save-mark-and-excursion
230 (replace-match type t t nil 1))))
232 (defun apt-sources-list-change-options (options)
233 "Change the options of the source at point to OPTIONS (excluding []s)."
235 (list (save-match-data
236 (barf-if-buffer-read-only)
237 (apt-sources-list-match-source)
238 (read-string "Options: " (match-string 2)))))
240 (apt-sources-list-match-source)
241 (when (= 0 (length options))
243 (save-mark-and-excursion
244 (cond ((and (match-string 2) options)
245 (replace-match options t t nil 2))
247 (delete-region (- (match-beginning 2) 2) (1+ (match-end 2))))
249 (replace-match (concat (match-string 1) " [" options "]")
252 (defun apt-sources-list-change-uri (uri)
253 "Change the URI of the source at point to URI."
255 (list (save-match-data
256 (barf-if-buffer-read-only)
257 (apt-sources-list-match-source)
258 (read-string "URI: " (match-string 3)))))
260 (apt-sources-list-match-source)
261 (save-mark-and-excursion
262 (replace-match uri t t nil 3))))
264 (defun apt-sources-list--read-components (&optional initial)
265 "Read a components string, defaulting to INITIAL."
267 (let ((minibuffer-local-completion-map
268 (copy-keymap minibuffer-local-completion-map)))
269 (define-key minibuffer-local-completion-map (kbd "<SPC>") nil)
270 (completing-read "Components: "
271 apt-sources-list-components
273 (or initial (car apt-sources-list-components))))))
275 (defun apt-sources-list-change-suite (suite &optional default-components)
276 "Change the suite of the source at point to SUITE.
278 If the new suite requires components and the old one did not,
279 DEFAULT-COMPONENTS is used. If none are provided, the first item
280 in ‘apt-sources-list-components’ is used."
283 (barf-if-buffer-read-only)
284 (apt-sources-list-match-source)
285 (let ((components (match-string 5))
286 (suite (completing-read "Suite: "
287 apt-sources-list-suites)))
288 (if (not (string-suffix-p "/" suite))
289 (list suite (apt-sources-list--read-components))
292 (save-mark-and-excursion
294 (apt-sources-list-match-source)
295 (if (string-suffix-p "/" suite)
296 (when (match-string 5)
297 (replace-match "" t t nil 5))
298 (setq suite (concat suite " "
301 (car apt-sources-list-components)
303 (replace-match suite t t nil 4))))
305 (defun apt-sources-list-change-components (components)
306 "Change the components of the source at point to COMPONENTS."
309 (barf-if-buffer-read-only)
310 (apt-sources-list-match-source)
311 (when (string-suffix-p "/" (match-string 4))
312 (signal 'apt-sources-list-suite-component-mismatch nil))
313 (list (apt-sources-list--read-components
314 (substring-no-properties (match-string 5))))))
317 (apt-sources-list-match-source)
318 (when (string-suffix-p "/" (match-string 4))
319 (signal 'apt-sources-list-suite-component-mismatch nil))
320 (save-mark-and-excursion
321 (replace-match components t t nil 5))))
323 (defun apt-sources-list-replicate ()
324 "Copy the source line, toggling the type."
326 (apt-sources-list-match-source)
327 (let ((copy (buffer-substring (line-beginning-position)
328 (line-end-position))))
331 (insert (concat "\n" copy))
332 (apt-sources-list-change-type))))
335 (define-derived-mode apt-sources-list-mode prog-mode "apt/sources.list"
336 "Major mode for editing APT’s “.list” files.
338 The “/etc/apt/sources.list” file and other files in
339 “/etc/apt/sources.list.d” tell APT, found on Debian-based systems
340 and others, where to find packages for installation.
342 This format specifies a package source with a single line, e.g.:
344 deb http://deb.debian.org/debian stable main contrib
346 For more information about the format you can read the manual
347 pages “apt(8)” and “sources.list(5)”, also on the web at URL
348 ‘https://manpages.debian.org/stable/apt/sources.list.5.en.html’
349 and URL ‘https://manpages.debian.org/stable/apt/apt.8.en.html’.
351 \\{apt-sources-list-mode-map}
353 The above editing commands will raise errors if the current line
354 is not a correctly-formatted APT source."
356 (let ((syntab (make-syntax-table)))
357 (modify-syntax-entry ?# "<" syntab)
358 (modify-syntax-entry ?\n "> " syntab)
361 (setq-local comment-start "#")
362 (setq-local comment-start-skip "#+ *")
363 (font-lock-add-keywords nil apt-sources-list-font-lock-keywords))
368 (cons (rx (or (and (any "./") "sources.list")
369 (and "/sources.list.d/" (one-or-more anything) ".list"))
371 #'apt-sources-list-mode))
373 (let ((map apt-sources-list-mode-map))
374 (define-key map (kbd "C-c C-i") #'apt-sources-list-insert)
375 (define-key map (kbd "C-c C-r") #'apt-sources-list-replicate)
376 (define-key map (kbd "C-c C-t") #'apt-sources-list-change-type)
377 (define-key map (kbd "C-c C-o") #'apt-sources-list-change-options)
378 (define-key map (kbd "C-c C-u") #'apt-sources-list-change-uri)
379 (define-key map (kbd "C-c C-s") #'apt-sources-list-change-suite)
380 (define-key map (kbd "C-c C-c") #'apt-sources-list-change-components)
381 (define-key map [remap forward-list] #'apt-sources-list-forward-source)
382 (define-key map [remap backward-list] #'apt-sources-list-backward-source)
384 (easy-menu-define apt-sources-list-mode-menu map
385 "Menu for APT sources.list mode."
387 ["Insert Source" apt-sources-list-insert]
388 ["Copy Source" apt-sources-list-replicate
389 (apt-sources-list-source-p)]
391 ["Backward Source" apt-sources-list-backward-source]
392 ["Forward Source" apt-sources-list-forward-source]
394 ["Change Type" apt-sources-list-change-type
395 (apt-sources-list-source-p)]
396 ["Change Options" apt-sources-list-change-options
397 (apt-sources-list-source-p)]
398 ["Change URI" apt-sources-list-change-uri
399 (apt-sources-list-source-p)]
400 ["Change Suite" apt-sources-list-change-suite
401 (apt-sources-list-source-p)]
402 ["Change Components" apt-sources-list-change-components
404 (and (apt-sources-list-match-source) (match-string 5)))])))
407 (provide 'apt-sources-list)
408 ;;; apt-sources-list.el ends here