[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]
[nongnu] scratch/rfc-mode ff1e3251e9 01/52: initial import
From: |
Stefan Monnier |
Subject: |
[nongnu] scratch/rfc-mode ff1e3251e9 01/52: initial import |
Date: |
Wed, 12 Oct 2022 16:29:18 -0400 (EDT) |
branch: scratch/rfc-mode
commit ff1e3251e9b4baa3c9006c2824375b4b7eb8a816
Author: Nicolas Martyanoff <khaelin@gmail.com>
Commit: Nicolas Martyanoff <khaelin@gmail.com>
initial import
---
LICENSE | 13 ++
README.md | 26 ++++
img/helm-browser.png | Bin 0 -> 112811 bytes
img/reader.png | Bin 0 -> 181692 bytes
rfc-mode.el | 333 +++++++++++++++++++++++++++++++++++++++++++++++++++
5 files changed, 372 insertions(+)
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000000..d3d870f2c9
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,13 @@
+Copyright (c) 2019 Nicolas Martyanoff <khaelin@gmail.com>.
+
+Permission to use, copy, modify, and/or distribute this software for any
+purpose with or without fee is hereby granted, provided that the above
+copyright notice and this permission notice appear in all copies.
+
+THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
+REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
+INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
+OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+PERFORMANCE OF THIS SOFTWARE.
diff --git a/README.md b/README.md
new file mode 100644
index 0000000000..e485ec1ed1
--- /dev/null
+++ b/README.md
@@ -0,0 +1,26 @@
+
+# rfc-mode
+
+## Introduction
+
+The rfc-mode Emacs major mode is a browser and reader for RFC documents.
+
+## Installation
+
+The package should be installed from MELPA. After that, just load rfc-mode:
+
+```elisp
+(require 'rfc-mode)
+```
+
+Call `rfc-mode-browse` to choose a RFC document to read, or `rfc-mode-read` to
+enter the reference of the RFC document yourself.
+
+## Screenshots
+
+![Reader](img/reader.png)
+
+![Helm-based browser](img/helm-browser.png)
+
+## Contact
+If you have an idea or a question, email me at <khaelin@gmail.com>.
diff --git a/img/helm-browser.png b/img/helm-browser.png
new file mode 100644
index 0000000000..54bd72c774
Binary files /dev/null and b/img/helm-browser.png differ
diff --git a/img/reader.png b/img/reader.png
new file mode 100644
index 0000000000..4556e18191
Binary files /dev/null and b/img/reader.png differ
diff --git a/rfc-mode.el b/rfc-mode.el
new file mode 100644
index 0000000000..0a2d5eb9e6
--- /dev/null
+++ b/rfc-mode.el
@@ -0,0 +1,333 @@
+;;; rfc-mode.el --- RFC document browser and viewer -*- lexical-binding: t -*-
+
+;; Author: Nicolas Martyanoff <khaelin@gmail.com>
+;; URL: https://github.com/galdor/rfc-mode
+;; Version: 0.1.0
+;; Package-Requires: ((emacs "25.1") (helm "3.2"))
+
+;; Copyright 2019 Nicolas Martyanoff <khaelin@gmail.com>
+;;
+;; Permission to use, copy, modify, and/or distribute this software for any
+;; purpose with or without fee is hereby granted, provided that the above
+;; copyright notice and this permission notice appear in all copies.
+;;
+;; THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+;; WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+;; MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
+;; SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+;; WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+;; ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR
+;; IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+
+;;; Commentary:
+
+;; This package makes it easy to browse and read RFC documents.
+
+;;; Code:
+
+;;; Configuration
+(defgroup rfc-mode-group nil
+ "Tools to browse and read RFC documents."
+ :prefix "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-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
+ :group 'rfc-mode)
+
+(defcustom rfc-mode-browser-entry-title-width 60
+ "The width of the column containing the title of each entry in
+the RFC browser."
+ :type 'integer
+ :group 'rfc-mode)
+
+;;; Misc variables
+(defvar rfc-mode-index-path (concat rfc-mode-directory "rfc-index.txt")
+ "The path of the file containing the index of all RFC documents.")
+
+(defvar rfc-mode-index-entries nil
+ "The list of entries in the RFC index.")
+
+;;; Keys
+(defvar rfc-mode-map
+ (let ((map (make-keymap)))
+ (define-key map (kbd "q") 'rfc-mode-quit)
+ (define-key map (kbd "<prior>") 'rfc-mode-backward-page)
+ (define-key map (kbd "<next>") 'rfc-mode-forward-page)
+ map)
+ "The keymap for `rfc-mode'.")
+
+;;; Main
+(defun rfc-mode-init ()
+ "Initialize the current buffer for `rfc-mode'."
+ (setq-local buffer-read-only t)
+ (setq-local page-delimiter "^.*?\n")
+ (rfc-mode-highlight))
+
+(defun rfc-mode-quit ()
+ "Quit the current window and bury its buffer."
+ (interactive)
+ (quit-window))
+
+(defun rfc-mode-backward-page ()
+ "Scroll to the previous page of the current buffer."
+ (interactive)
+ (backward-page)
+ (rfc-mode-previous-header)
+ (recenter 0))
+
+(defun rfc-mode-forward-page ()
+ "Scroll to the next page of the current buffer."
+ (interactive)
+ (forward-page)
+ (rfc-mode-previous-header)
+ (recenter 0))
+
+(defun rfc-mode-read (number)
+ "Read a RFC document."
+ (interactive "nRFC number: ")
+ (switch-to-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
+ (rfc-mode-read-index-file rfc-mode-index-path)))
+
+(defun rfc-mode-browse ()
+ "Browse through all RFC documents referenced in the index using Helm."
+ (interactive)
+ (unless rfc-mode-index-entries
+ (setq rfc-mode-index-entries
+ (rfc-mode-read-index-file rfc-mode-index-path)))
+ (helm :buffer "*helm rfc browser*"
+ :sources (rfc-mode-browser-helm-sources rfc-mode-index-entries)))
+
+;;;###autoload
+(define-derived-mode rfc-mode fundamental-mode "rfc-mode"
+ "Major mode to browse and read RFC documents."
+ :syntax-table text-mode-syntax-table
+ :mode-map rfc-mode-map
+ (rfc-mode-init))
+
+;;;###autoload
+(add-to-list 'auto-mode-alist '("rfc[0-9]+\\.txt\\'" . rfc-mode))
+
+;;; Syntax utils
+(defun rfc-mode-highlight ()
+ "Highlight the current buffer."
+ (with-silent-modifications
+ (let ((inhibit-read-only t))
+ ;; Headers
+ (save-excursion
+ (goto-char (point-min))
+ (cl-loop
+ (let* ((end (rfc-mode-next-header))
+ (start (point)))
+ (unless end
+ (cl-return))
+ (put-text-property start end
+ 'face 'rfc-mode-document-header-face)
+ (goto-char end))))
+ ;; Section titles
+ (save-excursion
+ (goto-char (point-min))
+ (while (search-forward-regexp "^\\([0-9]+\\.\\)+ .*$" nil t)
+ (let ((start (match-beginning 0))
+ (end (match-end 0)))
+ (put-text-property start end
+ 'face 'rfc-mode-document-section-title-face)
+ (goto-char end))))
+ ;; 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))))
+ (make-text-button start end
+ 'action `(lambda (button)
+ (rfc-mode-read ,number))
+ 'help-echo (format "Read RFC %d" number))
+ (goto-char end)))))))
+
+(defun rfc-mode-header-start ()
+ "When the point is on a linebreak, 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)
+ (let ((end (point)))
+ (forward-line -2)
+ (move-beginning-of-line 1)
+ end)))
+
+(defun rfc-mode-previous-header ()
+ "Move to the start of the previous page header and return the
+position of its end. Return NIL if no previous header is found."
+ (when (search-backward "" nil t)
+ (goto-char (match-beginning 0))
+ (rfc-mode-header-start)))
+
+(defun rfc-mode-next-header ()
+ "Move to the start of the next page header and return the
+position of its end. Return NIL if no next header is found."
+ (when (search-forward "" nil t)
+ (goto-char (match-beginning 0))
+ (rfc-mode-header-start)))
+
+;;; Browser utils
+(defun rfc-mode-browser-helm-sources (entries)
+ "Create a Helm source for a list of RFC index entries in the browser."
+ (helm-build-sync-source "RFC documents"
+ :candidates (mapcar #'rfc-mode-browser-helm-candidate entries)
+ :action (helm-make-actions
+ "Read" #'rfc-mode-browser-helm-entry-read)))
+
+(defun rfc-mode-browser-helm-candidate (entry)
+ "Create a Helm candidate for 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 for Helm candidates in the browser."
+ (let ((number (plist-get entry :number)))
+ (rfc-mode-read number)))
+
+;;; Index utils
+(defun rfc-mode-read-index-file (path)
+ "Read an RFC index file at PATH and return a list of entries."
+ (with-temp-buffer
+ (insert-file-contents path)
+ (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 an entry in the RFC document index 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)))
+ (unless 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 a RFC document."
+ (concat "*rfc" (number-to-string number) "*"))
+
+(defun rfc-mode-document-path (number)
+ "Return the absolute path of a RFC document."
+ (concat rfc-mode-directory "rfc" (number-to-string number) ".txt"))
+
+(defun rfc-mode-document-buffer (number)
+ "Return a buffer visiting a RFC document, creating it if necessary."
+ (let* ((buffer-name (rfc-mode-document-buffer-name number))
+ (document-path (rfc-mode-document-path number)))
+ (find-file document-path)
+ (rename-buffer buffer-name)
+ (rfc-mode)
+ (current-buffer)))
+
+;;; Misc utils
+
+(defun rfc-mode-parse-rfc-ref (string)
+ "Parse a reference to a RFC document, e.g. \"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 a RFC document, e.g. \"RFC3401,
+ RFC3402 ,RFC 3403\"."
+ (seq-remove #'null (mapcar #'rfc-mode-parse-rfc-ref
+ (split-string string "," t " +"))))
+
+(defun rfc-mode-pad-string (string width)
+ (truncate-string-to-width string width 0 ?\s))
+
+(defun rfc-mode-highlight-string (string face)
+ (put-text-property 0 (length string) 'face face string)
+ string)
+
+
+(provide 'rfc-mode)
+
+;;; rfc-mode.el ends here
- [nongnu] branch scratch/rfc-mode created (now a734721104), Stefan Monnier, 2022/10/12
- [nongnu] scratch/rfc-mode 5bda3310af 17/52: remove useless groups for custom variables, Stefan Monnier, 2022/10/12
- [nongnu] scratch/rfc-mode 91bf9ff561 34/52: Avoid rfc-mode-read replacing the current buffer, Stefan Monnier, 2022/10/12
- [nongnu] scratch/rfc-mode b651eac96c 09/52: add repository link to group, Stefan Monnier, 2022/10/12
- [nongnu] scratch/rfc-mode 1ae16d2fb8 35/52: Implement a command to navigate to existing sections, Stefan Monnier, 2022/10/12
- [nongnu] scratch/rfc-mode ff1e3251e9 01/52: initial import,
Stefan Monnier <=
- [nongnu] scratch/rfc-mode dc1a3a978f 02/52: improve the readme, Stefan Monnier, 2022/10/12
- [nongnu] scratch/rfc-mode 3719b9ce9f 20/52: derive rfc-mode from text-mode, Stefan Monnier, 2022/10/12
- [nongnu] scratch/rfc-mode 821a039ae6 18/52: autoload rfc-mode-read, Stefan Monnier, 2022/10/12
- [nongnu] scratch/rfc-mode 355f25ee2a 31/52: Add commands to move to the previous/next RFC section, Stefan Monnier, 2022/10/12
- [nongnu] scratch/rfc-mode a3a58e9883 15/52: better section title detection, Stefan Monnier, 2022/10/12
- [nongnu] scratch/rfc-mode 55c0c24f9b 28/52: Make rfc-mode inherit from special-mode, Stefan Monnier, 2022/10/12
- [nongnu] scratch/rfc-mode e037a7ce5c 33/52: Add Tab and S-Tab to navigate through RFC button links, Stefan Monnier, 2022/10/12
- [nongnu] scratch/rfc-mode 02d8dfeb70 16/52: do not fail if helm is not available, Stefan Monnier, 2022/10/12
- [nongnu] scratch/rfc-mode 7819cb64b9 25/52: add a changelog, Stefan Monnier, 2022/10/12
- [nongnu] scratch/rfc-mode e2608adbac 36/52: Make hyperlinks clickable with the mouse too, Stefan Monnier, 2022/10/12