#+TITLE: Emacs IETF
#+DATE: 2023-04-05
* Prologue
Some years ago I discovered [[https://github.com/paulehoffman/ietf-cli][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 pager[fn::Like normal people... I uses emacsclient for
performance reasons.].

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 [[https://github.com/minad/vertico][vertico]] completion extension combined
with [[https://github.com/oantolin/orderless][orderless]] completion. Those two extensions provide the kind of
completion I want for RFCs and even more so for drafts:
#+begin_quote
[Orderless] divides the pattern into space-separated components, and
matches candidates that match all of the components in any order.
#+end_quote
* 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:

#+begin_src emacs-lisp
  ;; 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))
      (read-only-mode)
      (text-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))
      (read-only-mode)
      (text-mode)))
#+end_src

Setting a global key binding lets us open RFCs and drafts from
anywhere within Emacs:
#+begin_src emacs-lisp
  (global-set-key (kbd "C-c i d") 'ietf-draft)
  (global-set-key (kbd "C-c i r") 'ietf-rfc)
#+end_src
* 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.
