emacs-elpa-diffs
[Top][All Lists]
Advanced

[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



reply via email to

[Prev in Thread] Current Thread [Next in Thread]