From 0cb3a179e2e949d414949bfa0e7bfc8383970fa0 Mon Sep 17 00:00:00 2001 From: Andreas Gerler Date: Wed, 19 Nov 2025 10:10:25 +0100 Subject: [PATCH] added rfc-mode Signed-off-by: Andreas Gerler --- .../rfc-mode-autoloads.el | 40 ++ elpa/rfc-mode-20231013.1353/rfc-mode-pkg.el | 9 + elpa/rfc-mode-20231013.1353/rfc-mode.el | 536 ++++++++++++++++++ 3 files changed, 585 insertions(+) create mode 100644 elpa/rfc-mode-20231013.1353/rfc-mode-autoloads.el create mode 100644 elpa/rfc-mode-20231013.1353/rfc-mode-pkg.el create mode 100644 elpa/rfc-mode-20231013.1353/rfc-mode.el diff --git a/elpa/rfc-mode-20231013.1353/rfc-mode-autoloads.el b/elpa/rfc-mode-20231013.1353/rfc-mode-autoloads.el new file mode 100644 index 00000000..42f9d60f --- /dev/null +++ b/elpa/rfc-mode-20231013.1353/rfc-mode-autoloads.el @@ -0,0 +1,40 @@ +;;; rfc-mode-autoloads.el --- automatically extracted autoloads (do not edit) -*- lexical-binding: t -*- +;; Generated by the `loaddefs-generate' function. + +;; This file is part of GNU Emacs. + +;;; Code: + +(add-to-list 'load-path (or (and load-file-name (directory-file-name (file-name-directory load-file-name))) (car load-path))) + + + +;;; Generated autoloads from rfc-mode.el + +(autoload 'rfc-mode-read "rfc-mode" "\ +Read the RFC document NUMBER. +Offer the number at point as default. + +(fn NUMBER)" t) +(autoload 'rfc-mode-browse "rfc-mode" "\ +Browse through all RFC documents referenced in the index." t) +(autoload 'rfc-mode "rfc-mode" "\ +Major mode to browse and read RFC documents. + +(fn)" t) +(add-to-list 'auto-mode-alist '("/rfc[0-9]+\\.txt\\'" . rfc-mode)) +(register-definition-prefixes "rfc-mode" '("rfc-mode-")) + +;;; End of scraped data + +(provide 'rfc-mode-autoloads) + +;; Local Variables: +;; version-control: never +;; no-byte-compile: t +;; no-update-autoloads: t +;; no-native-compile: t +;; coding: utf-8-emacs-unix +;; End: + +;;; rfc-mode-autoloads.el ends here diff --git a/elpa/rfc-mode-20231013.1353/rfc-mode-pkg.el b/elpa/rfc-mode-20231013.1353/rfc-mode-pkg.el new file mode 100644 index 00000000..b5f19707 --- /dev/null +++ b/elpa/rfc-mode-20231013.1353/rfc-mode-pkg.el @@ -0,0 +1,9 @@ +;; -*- no-byte-compile: t; lexical-binding: nil -*- +(define-package "rfc-mode" "20231013.1353" + "RFC document browser and viewer." + '((emacs "25.1")) + :url "https://github.com/galdor/rfc-mode" + :commit "ab09db78d9d1baa4da4f926930833598e1e978ce" + :revdesc "ab09db78d9d1" + :authors '(("Nicolas Martyanoff" . "nicolas@n16f.net")) + :maintainers '(("Nicolas Martyanoff" . "nicolas@n16f.net"))) diff --git a/elpa/rfc-mode-20231013.1353/rfc-mode.el b/elpa/rfc-mode-20231013.1353/rfc-mode.el new file mode 100644 index 00000000..593db2b5 --- /dev/null +++ b/elpa/rfc-mode-20231013.1353/rfc-mode.el @@ -0,0 +1,536 @@ +;;; rfc-mode.el --- RFC document browser and viewer -*- lexical-binding: t -*- + +;; Author: Nicolas Martyanoff +;; SPDX-License-Identifier: ISC +;; URL: https://github.com/galdor/rfc-mode +;; Package-Version: 20231013.1353 +;; Package-Revision: ab09db78d9d1 +;; Package-Requires: ((emacs "25.1")) + +;;; Commentary: + +;; This package makes it easy to browse and read RFC documents. + +;; It offers a Helm-based browser of the list of RFCs as well as +;; some highlighting of hyperlinks when reading the actual RFCs. +;; If you want to browse the list without Helm, you might prefer +;; [rfcview](http://github.com/zeph1e/rfcview.el). + +;; Todo: +;; - Use font-lock +;; - Add hyperlinks from the `Table of Contents' + +;;; Code: + +(require 'helm nil t) +(require 'seq) + + +(declare-function helm-build-sync-source "helm-source") +(declare-function helm-make-actions "helm-lib") + +;;; Configuration: + +(defgroup rfc-mode-group nil + "Tools to browse and read RFC documents." + :prefix "rfc-mode-" + :link '(url-link :tag "GitHub" "https://github.com/galdor/rfc-mode") + :group 'external) + +(defface rfc-mode-document-header-face + '((t :inherit font-lock-comment-face)) + "Face used for RFC document page headers.") + +(defface rfc-mode-document-footer-face + '((t :inherit font-lock-comment-face)) + "Face used for RFC document page footers.") + +(defface rfc-mode-document-section-title-face + '((t :inherit font-lock-keyword-face)) + "Face used for RFC document section titles.") + +(defface rfc-mode-browser-ref-face + '((t :inherit font-lock-preprocessor-face)) + "Face used to highlight RFC references in the RFC browser.") + +(defface rfc-mode-browser-title-face + '((t :inherit default)) + "Face used to highlight the title of RFC documents in the RFC browser.") + +(defface rfc-mode-browser-title-obsolete-face + '((t :inherit font-lock-comment-face)) + "Face used to highlight the title of obsolete RFC documents in the RFC browser.") + +(defface rfc-mode-browser-status-face + '((t :inherit font-lock-keyword-face)) + "Face used to highlight RFC document statuses in the RFC browser.") + +(defcustom rfc-mode-directory (expand-file-name "~/rfc/") + "The directory where RFC documents are stored." + :type 'directory) + +(defcustom rfc-mode-document-url + "https://www.rfc-editor.org/rfc/rfc%s.txt" + "A `format'able URL for fetching arbitrary RFC documents. +Assume RFC documents are named as e.g. rfc21.txt, rfc-index.txt." + :type 'string) + +(defcustom rfc-mode-browse-input-function + (if (featurep 'helm) 'helm 'completing-read) + "Function used by `rfc-mode-browse' to read user input. + +Only `read-number', `completing-read' and `helm' are explicitly +supported. Any other function is called with no arguments and +must return an integer. + +Here `completion-read' works best if you use some completion +mode that displays candidates \"vertically\" like `helm' does. +`ivy-mode' is a popular choice. `fido-mode' in combination +with `icomplete-vertical-mode' should also work well." + :type '(choice (const read-number) + (const completing-read) + (const helm) + function)) + +(defcustom rfc-mode-use-original-buffer-names nil + "Whether RFC document buffers should have the name of the document file. +If nil (the default) then use e.g. *rfc21*, otherwise use e.g. rfc21.txt." + :type 'boolean) + +(defcustom rfc-mode-browser-entry-title-width 60 + "The width of the column containing RFC titles in the browser." + :type 'integer) + +(defcustom rfc-mode-imenu-title "RFC Contents" + "The title to use if `rfc-mode' adds a RFC Contents menu to the menubar." + :type 'string) + +;;; Misc variables: + +(defvar rfc-mode-index-entries nil + "The list of entries in the RFC index.") + +(defconst rfc-mode-title-regexp "^\\(?:[0-9]+\\.\\)+\\(?:[0-9]+\\)? .*$" + "Regular expression to model section titles in RFC documents.") + +(defvar-local rfc-mode--titles nil + "Buffer-local variable that keeps a list of section titles in this RFC.") + +(defvar rfc-mode--last-title nil + "Last section title that the user visited.") + +;;; Keys: + +(defvar rfc-mode-map + (let ((map (make-keymap))) + (set-keymap-parent map special-mode-map) + (define-key map (kbd "") #'forward-button) + (define-key map (kbd "") #'backward-button) + (define-key map (kbd "") #'rfc-mode-backward-page) + (define-key map (kbd "") #'rfc-mode-forward-page) + (define-key map (kbd "g") #'rfc-mode-goto-section) + (define-key map (kbd "n") #'rfc-mode-next-section) + (define-key map (kbd "p") #'rfc-mode-previous-section) + map) + "The keymap for `rfc-mode'.") + +;;; Main: + +(defun rfc-mode-init () + "Initialize the current buffer for `rfc-mode'." + (setq-local page-delimiter "^.*?\n ") + (rfc-mode-highlight) + (setq imenu-generic-expression (list (list nil rfc-mode-title-regexp 0))) + (imenu-add-to-menubar rfc-mode-imenu-title)) + +(define-obsolete-function-alias 'rfc-mode-quit #'quit-window "rfc-mode-1.4") + +(defun rfc-mode-recenter () + "Do the same as `recenter-top-bottom' would for the `top' position." + (rfc-mode-header-start) + (let ((recenter-positions '(top))) + (recenter-top-bottom))) + +(defun rfc-mode-backward-page () + "Scroll to the previous page of the current buffer." + (interactive) + (beginning-of-line) + (unless (looking-at " ") + (backward-page)) + (backward-page) + (beginning-of-line 1) + (rfc-mode-recenter)) + +(defun rfc-mode-forward-page () + "Scroll to the next page of the current buffer." + (interactive) + (forward-page) + (beginning-of-line 1) + (rfc-mode-recenter)) + +(defun rfc-mode-goto-section (section) ;FIXME: Why not use imenu for that? + "Move point to SECTION." + (interactive + (let* ((default (if (member rfc-mode--last-title rfc-mode--titles) + rfc-mode--last-title + (car rfc-mode--titles))) + (completion-ignore-case t) + (prompt (concat "Go to section (default " default "): ")) + (chosen (completing-read prompt rfc-mode--titles + nil nil nil nil default))) + (list chosen))) + (setq rfc-mode--last-title section) + (unless (rfc-mode--goto-section section) + (error "Section %s not found" section))) + +(defun rfc-mode--goto-section (section) + "Move point to SECTION if it exists, otherwise don't move point. +Returns t if section is found, nil otherwise." + (let ((curpos (point)) + (case-fold-search nil)) + (goto-char (point-min)) + (if (re-search-forward (concat "^" section) (point-max) t) + (progn + (beginning-of-line) + (rfc-mode-recenter) + t) + (goto-char curpos) + nil))) + +(defun rfc-mode-next-section (n) + "Move point to Nth next section (default 1)." + (interactive "p") + (let ((case-fold-search nil) + (start (point))) + (if (looking-at rfc-mode-title-regexp) + (forward-line 1)) + (if (re-search-forward rfc-mode-title-regexp (point-max) t n) + (progn + (beginning-of-line) + (rfc-mode-recenter)) + (goto-char (point-max)) + ;; The last line doesn't belong to any section. + (forward-line -1)) + ;; Ensure we never move back from the starting point. + (if (< (point) start) (goto-char start)))) + +(defun rfc-mode-previous-section (n) + "Move point to Nth previous section (default 1)." + (interactive "p") + (let ((case-fold-search nil)) + (if (looking-at rfc-mode-title-regexp) + (forward-line -1)) + (if (re-search-backward rfc-mode-title-regexp (point-min) t n) + (progn + (beginning-of-line) + (rfc-mode-recenter)) + (goto-char (point-min))))) + +;;;###autoload +(defun rfc-mode-read (number) + "Read the RFC document NUMBER. +Offer the number at point as default." + (interactive + (if (and current-prefix-arg (not (consp current-prefix-arg))) + (list (prefix-numeric-value current-prefix-arg)) + (list (read-number "RFC number: " (rfc-mode--integer-at-point))))) + (display-buffer (rfc-mode--document-buffer number))) + +(defun rfc-mode-reload-index () + "Reload the RFC document index from its original file." + (interactive) + (setq rfc-mode-index-entries nil)) + +(defun rfc-mode--index-entries () + (or rfc-mode-index-entries + (let ((file (rfc-mode--document-file "-index"))) + (setq rfc-mode-index-entries + (rfc-mode-read-index-file file))))) + +;;;###autoload +(defun rfc-mode-browse () + "Browse through all RFC documents referenced in the index." + (interactive) + (pcase rfc-mode-browse-input-function + ('read-number + (display-buffer (rfc-mode--document-buffer + (read-number "View RFC document: " + (rfc-mode--integer-at-point))))) + ('helm + (if (fboundp 'helm) + (helm :buffer "*helm rfc browser*" + :sources (rfc-mode-browser-helm-sources + (rfc-mode--index-entries))) + (user-error "Helm has to be installed explicitly"))) + ('completing-read + (let* ((default (rfc-mode--integer-at-point)) + (cands (mapcar (lambda (entry) + (let ((cand + (rfc-mode-browser-format-candidate entry))) + (and (numberp default) + (= (plist-get entry :number) default) + (setq default (car cand))) + cand)) + (rfc-mode--index-entries))) + (choice (completing-read "View RFC document: " + cands nil nil nil nil default)) + (number (or (and (string-match "\\`RFC\\([0-9]+\\)" choice) + (string-to-number (match-string 1 choice))) + (ignore-errors (string-to-number choice))))) + (unless number + (user-error + "%s doesn't match a completion candidate and is not a number" + choice)) + (display-buffer (rfc-mode--document-buffer number)))) + (_ (display-buffer (rfc-mode--document-buffer + (funcall rfc-mode-browse-input-function)))))) + +;;;###autoload +(define-derived-mode rfc-mode special-mode "rfc-mode" + "Major mode to browse and read RFC documents." + (rfc-mode-init)) + +;;;###autoload +(add-to-list 'auto-mode-alist '("/rfc[0-9]+\\.txt\\'" . rfc-mode)) +(add-to-list 'auto-mode-alist '("\\*rfc[0-9]+\\*\\'" . rfc-mode)) + +;;; Syntax utils: + +(defun rfc-mode-highlight () + "Highlight the current buffer." + (setq rfc-mode--titles nil) + ;; FIXME: Use font-lock! + (with-silent-modifications + (let ((inhibit-read-only t)) + ;; Headers and footers + (save-excursion + (goto-char (point-min)) + (while (search-forward " " nil t) + (beginning-of-line) + (let ((form-feed (point))) + (let* ((footer-end (rfc-mode-previous-footer-start)) + (footer-start (point))) + (put-text-property + footer-start footer-end + 'face 'rfc-mode-document-footer-face)) + (goto-char form-feed) + (let* ((header-end (rfc-mode-header-start)) + (header-start (point))) + (put-text-property + header-start header-end + 'face 'rfc-mode-document-header-face) + (goto-char header-end))))) + ;; Section titles + (save-excursion + (goto-char (point-min)) + (while (search-forward-regexp rfc-mode-title-regexp nil t) + (let ((start (match-beginning 0)) + (end (match-end 0))) + (put-text-property start end + 'face 'rfc-mode-document-section-title-face) + (push (match-string 0) rfc-mode--titles) + (goto-char end)))) + ;; Keep titles in expected top to bottom order. + (setq rfc-mode--titles (nreverse rfc-mode--titles)) + ;; RFC references + (save-excursion + (goto-char (point-min)) + (while (search-forward-regexp "RFC *\\([0-9]+\\)" nil t) + (let ((start (match-beginning 0)) + (end (match-end 0)) + (number (string-to-number (match-string 1)))) + (unless (= start (line-beginning-position)) + (make-text-button start end + 'action (lambda (_button) + (rfc-mode-read number)) + 'help-echo (format "Read RFC %d" number) + 'follow-link t)) + (goto-char end))))))) + +(defun rfc-mode-header-start () + "Move to the start of the current header. + +When the point is on a form feed character, move it to the start +of the current page header and return the position of the end of +the header." + (when (looking-at " ") + (forward-line 1) + (move-end-of-line 1) + (prog1 (point) + (move-beginning-of-line 1)))) + +(defun rfc-mode-previous-footer-start () + "Move to the start of the previous footer. + +When the point is on a form feed character, move it to the start +of the previous page footer and return the position of the end of +the footer." + (when (looking-at " ") + (forward-line -1) + (move-end-of-line 1) + (prog1 (point) + (move-beginning-of-line 1)))) + +;;; Browser utils: + +(defun rfc-mode-browser-helm-sources (entries) + "Create a Helm source for ENTRIES. + +ENTRIES is a list of RFC index entries in the browser." + (helm-build-sync-source "RFC documents" + :candidates (mapcar #'rfc-mode-browser-format-candidate entries) + :action (helm-make-actions + "Read" #'rfc-mode-browser-helm-entry-read))) + +(defun rfc-mode-browser-format-candidate (entry) + "Create a Helm candidate for ENTRY. + +ENTRY is a RFC index entry in the browser." + (let* ((ref (rfc-mode--pad-string + (format "RFC%d" (plist-get entry :number)) 7)) + (title (rfc-mode--pad-string + (plist-get entry :title) + rfc-mode-browser-entry-title-width)) + (status (or (plist-get entry :status) "")) + (obsoleted-by (plist-get entry :obsoleted-by)) + (obsoletep (> (length obsoleted-by) 0)) + (string (format "%s %s %s" + (rfc-mode--highlight-string + ref 'rfc-mode-browser-ref-face) + (rfc-mode--highlight-string + title (if obsoletep + 'rfc-mode-browser-title-obsolete-face + 'rfc-mode-browser-title-face)) + (rfc-mode--highlight-string + status 'rfc-mode-browser-status-face)))) + (cons string entry))) + +(defun rfc-mode-browser-helm-entry-read (entry) + "The read action the Helm candidate ENTRY in the browser." + (let ((number (plist-get entry :number))) + (rfc-mode-read number))) + +;;; Index utils: + +(defun rfc-mode-read-index-file (filename) + "Read an RFC index file at FILENAME and return a list of entries." + (with-temp-buffer + (insert-file-contents filename) + (rfc-mode-read-index (current-buffer)))) + +(defun rfc-mode-read-index (buffer) + "Read an RFC index file from BUFFER and return a list of entries." + (with-current-buffer buffer + (goto-char (point-min)) + (let ((entries nil)) + (while (search-forward-regexp "^[0-9]+ " nil t) + (let ((start (match-beginning 0))) + (search-forward-regexp " $") + (let* ((end (match-beginning 0)) + (lines (buffer-substring start end)) + (entry-string (replace-regexp-in-string "[ \n]+" " " lines)) + (entry (rfc-mode-parse-index-entry entry-string))) + (unless (string= (plist-get entry :title) "Not Issued") + (push entry entries))))) + (nreverse entries)))) + +(defun rfc-mode-parse-index-entry (string) + "Parse the RFC document index entry STRING and return it as a plist." + (unless (string-match "\\(^[0-9]+\\) *\\(.*?\\)\\.\\(?: \\|$\\)" string) + (error "Invalid index entry format: %S" string)) + (let* ((number-string (match-string 1 string)) + (number (string-to-number number-string)) + (title (match-string 2 string))) + (when (zerop number) + (error "Invalid index entry number: %S" number-string)) + (let ((entry (list :number number :title title))) + (when (string-match "(Status: \\([^)]+\\))" string) + (plist-put entry :status (downcase (match-string 1 string)))) + (when (string-match "(Obsoletes \\([^)]+\\))" string) + (plist-put entry :obsoletes + (rfc-mode--parse-rfc-refs (match-string 1 string)))) + (when (string-match "(Obsoleted by \\([^)]+\\))" string) + (plist-put entry :obsoleted-by + (rfc-mode--parse-rfc-refs (match-string 1 string)))) + (when (string-match "(Updates \\([^)]+\\))" string) + (plist-put entry :updates + (rfc-mode--parse-rfc-refs (match-string 1 string)))) + (when (string-match "(Updated by \\([^)]+\\))" string) + (plist-put entry :updated-by + (rfc-mode--parse-rfc-refs (match-string 1 string)))) + entry))) + +;;; Document utils: + +(defun rfc-mode--document-buffer-name (number) + "Return the buffer name for the RFC document NUMBER." + (concat "*rfc" (number-to-string number) "*")) + +(defun rfc-mode--document-file (number) + "Return the absolute file name of the RFC document NUMBER." + (let ((file + (expand-file-name (format "rfc%s.txt" number) rfc-mode-directory))) + (rfc-mode--ensure-directory-exists) + (unless (file-exists-p file) + (url-copy-file (format rfc-mode-document-url number) file)) + file)) + +(defun rfc-mode--document-buffer (number) + "Return a buffer visiting the RFC document NUMBER. + +The buffer is created if it does not exist." + (let* ((buffer-name (rfc-mode--document-buffer-name number)) + (document-path (rfc-mode--document-file number))) + (with-current-buffer (find-file-noselect document-path) + (unless rfc-mode-use-original-buffer-names + (rename-buffer buffer-name)) + (rfc-mode) + (current-buffer)))) + +;;; Misc utils: + +(defun rfc-mode--integer-at-point () + ;; Note that we don't use `number-at-point' as it will match + ;; number formats that make no sense as RFC numbers (floating + ;; point, hexadecimal, etc.). + (save-excursion + (skip-chars-backward "0-9") + (and (looking-at "[0-9]") + (string-to-number + (buffer-substring-no-properties + (point) + (progn (skip-chars-forward "0-9") + (point))))))) + +(defun rfc-mode--ensure-directory-exists () + "Check that `rfc-mode-directory' exists, creating it if it does not." + (when (and (not (file-exists-p rfc-mode-directory)) + (y-or-n-p (format "Create directory %s? " rfc-mode-directory))) + (make-directory rfc-mode-directory t))) + +(defun rfc-mode--parse-rfc-ref (string) + "Parse a reference to a RFC document from STRING. + +For example: \"RFC 2822\"." + (when (string-match "^RFC *\\([0-9]+\\)" string) + (string-to-number (match-string 1 string)))) + +(defun rfc-mode--parse-rfc-refs (string) + "Parse a list of references to RFC documents from STRING. + +For example: \"RFC3401, RFC3402 ,RFC 3403\"." + (seq-remove #'null (mapcar #'rfc-mode--parse-rfc-ref + (split-string string "," t " +")))) + +(defun rfc-mode--pad-string (string width) + "Pad STRING with spaces to WIDTH characters." + (truncate-string-to-width string width 0 ?\s)) + +(defun rfc-mode--highlight-string (string face) + "Highlight STRING using FACE." + (put-text-property 0 (length string) 'face face string) + string) + +(provide 'rfc-mode) + +;;; rfc-mode.el ends here -- 2.47.3