Emacs IETF

Prologue

Some years ago I discovered ietf-cli in OpenBSD ports. It creates a local mirror of IETF drafts, RFCs and other files and makes them accessible from the command line. I have it configured to use Emacs for the pager1.

I was never too happy about the cli interface though. While it is open source I never got around to hack on it or to send feature requests. These are the things I do not like. Most of them are a question of work flow more than limitations of the ietf tool:

  1. I never figured out how to configure tab-completion in my shell for it.
  2. I am not yet running a shell inside of Emacs, so it breaks my work flow when reading a draft and I want to quickly open an RFC for reference.
  3. The tool is pretty opinionated. You need to call just so for it do what you ask. To open an RFC you need to type ietf rfc 1925. It will not accept ietf RFC1925 or ietf rfc1925. That means that copy & paste does not work most of the time.

More recently I discovered the vertico completion extension combined with orderless completion. Those two extensions provide the kind of completion I want for RFCs and even more so for drafts:

[Orderless] divides the pattern into space-separated components, and matches candidates that match all of the components in any order.

Emacs-lisp

The ietf tool provides the mirror command that rsyncs the IETF documents to a configurable folder. Reading an RFC or draft then comes down to opening the correct file. So we can copy the code of find-file, change default-directory and we are good to go:

;; were ietf mirror stores the local mirror
(setq ietf-mirror "~/ietf-mirror/")

;; look for drafts in the 'short-id/' sub-directory and make sure the
;; buffer is a read-only text buffer.

(defun ietf-draft (filename &optional wildcards)
  (interactive
   (let
       ((default-directory (concat ietf-mirror "short-id/")))
     (find-file-read-args "Find file: "
                          (confirm-nonexistent-file-or-buffer))))
  (let ((value (find-file-noselect filename nil nil wildcards)))
    (if (listp value)
        (mapcar 'pop-to-buffer-same-window (nreverse value))
      (pop-to-buffer-same-window value))
    (text-mode)
    (read-only-mode)))

;; RFCs are stored in 'in-notes/'
(defun ietf-rfc (filename &optional wildcards)
  (interactive
   (let
       ((default-directory (concat ietf-mirror "in-notes/"))
        (completion-ignored-extensions
         '("/" ".html" ".json" ".pdf" ".ps" ".tar" ".xml" ".xsd")))
     (find-file-read-args "Find file: "
                          (confirm-nonexistent-file-or-buffer))))
  (let ((value (find-file-noselect filename nil nil wildcards)))
    (if (listp value)
        (mapcar 'pop-to-buffer-same-window (nreverse value))
      (pop-to-buffer-same-window value))
    (text-mode)
    (read-only-mode)))

Update 2024-03-25: Flipping the order of (text-mode) and (read-only-mode) so that (read-only-mode) gets activated last makes it work correctly with view-mode if that mode is automatically activated by (setq view-read-only t) on read-only buffers.

Setting a global key binding lets us open RFCs and drafts from anywhere within Emacs:

(global-set-key (kbd "C-c i d") 'ietf-draft)
(global-set-key (kbd "C-c i r") 'ietf-rfc)

Epilogue

The ietf tool has many more options that I have not explored or used yet. rfc and draft are the two main commands I am using and on occasion I am using the diff tool.

I will keep the tool around to keep the mirror up2date and for the occasional use of diff. Having moved the functionality I use most often into Emacs and making it behave like I want it is a huge improvement.

Footnotes:

1

Like normal people… I uses emacsclient for performance reasons.

Published: 2023-04-05