Using Quarto Files with Denote
Custom Filetypes in Denote
The latest release of Denote (by the inimitable Protesilaos Stavrou) introduced support for custom file types in addition to the defaults, Org, Markdown+YAML, Markdown+TOML, and plain text. This post shows how to add Quarto files (.qmd). Quarto, the successor to R Markdown, is "an open-source scientific and technical publishing system" with support for Python, R, Julia, and Observable. The setup detailed here will allow one to choose the .qmd filetype when creating a new Denote file.
Find the code snippet here.
All the details about creating a custom Denote filetype can be found
here. First, let's inspect the denote-file-types
alist to understand what a file
type definition looks like.
(print denote-file-types)
((quarto :extension ".qmd" :date-function denote-date-iso-8601 :front-matter denote-yaml-front-matter :title-key-regexp "^title\\s-*:" :title-value-function denote-surround-with-quotes :title-value-reverse-function denote-trim-whitespace-then-quotes :keywords-key-regexp "^tags\\s-*:" :keywords-value-function denote-format-keywords-for-md-front-matter :keywords-value-reverse-function denote-extract-keywords-from-front-matter :link denote-md-link-format :link-in-context-regexp denote-md-link-in-context-regexp) (quarto :extension ".qmd" :date-function denote-date-rfc3339 :front-matter denote-yaml-front-matter :title-key-regexp "^title\\s-*:" :title-value-function denote-surround-with-quotes :title-value-reverse-function denote-trim-whitespace-then-quotes :keywords-key-regexp "^tags\\s-*:" :keywords-value-function denote-format-keywords-for-md-front-matter :keywords-value-reverse-function denote-extract-keywords-from-front-matter :link denote-md-link-format :link-in-context-regexp denote-md-link-in-context-regexp) (org :extension ".org" :date-function denote-date-org-timestamp :front-matter denote-org-front-matter :title-key-regexp "^#\\+title\\s-*:" :title-value-function identity :title-value-reverse-function denote-trim-whitespace :keywords-key-regexp "^#\\+filetags\\s-*:" :keywords-value-function denote-format-keywords-for-org-front-matter :keywords-value-reverse-function denote-extract-keywords-from-front-matter :link denote-org-link-format :link-in-context-regexp denote-org-link-in-context-regexp) (markdown-yaml :extension ".qmd" :date-function denote-date-rfc3339 :front-matter denote-yaml-front-matter :title-key-regexp "^title\\s-*:" :title-value-function denote-surround-with-quotes :title-value-reverse-function denote-trim-whitespace-then-quotes :keywords-key-regexp "^tags\\s-*:" :keywords-value-function denote-format-keywords-for-md-front-matter :keywords-value-reverse-function denote-extract-keywords-from-front-matter :link denote-md-link-format :link-in-context-regexp denote-md-link-in-context-regexp) (markdown-toml :extension ".md" :date-function denote-date-rfc3339 :front-matter denote-toml-front-matter :title-key-regexp "^title\\s-*=" :title-value-function denote-surround-with-quotes :title-value-reverse-function denote-trim-whitespace-then-quotes :keywords-key-regexp "^tags\\s-*=" :keywords-value-function denote-format-keywords-for-md-front-matter :keywords-value-reverse-function denote-extract-keywords-from-front-matter :link denote-md-link-format :link-in-context-regexp denote-md-link-in-context-regexp) (text :extension ".txt" :date-function denote-date-iso-8601 :front-matter denote-text-front-matter :title-key-regexp "^title\\s-*:" :title-value-function identity :title-value-reverse-function denote-trim-whitespace :keywords-key-regexp "^tags\\s-*:" :keywords-value-function denote-format-keywords-for-text-front-matter :keywords-value-reverse-function denote-extract-keywords-from-front-matter :link denote-org-link-format :link-in-context-regexp denote-org-link-in-context-regexp))
We need to add to this list. We can define a new filetype in this alist by
defining a new element of the form (SYMBOL PROPERTY-LIST)
.
Adding a basic quarto file should be very simple. We only need to duplicatge the
markdown file type definition, changing the name to quarto
and the extension to
".qmd"
. We could do this as follows.
(add-to-list 'denote-file-types '(quarto :extension ".qmd" :date-function denote-date-iso-8601 :front-matter denote-yaml-front-matter :title-key-regexp "^title\\s-*:" :title-value-function denote-surround-with-quotes :title-value-reverse-function denote-trim-whitespace-then-quotes :keywords-key-regexp "^tags\\s-*:" :keywords-value-function denote-format-keywords-for-md-front-matter :keywords-value-reverse-function denote-extract-keywords-from-front-matter :link denote-md-link-format :link-in-context-regexp denote-md-link-in-context-regexp))
((quarto :extension ".qmd" :date-function denote-date-iso-8601 :front-matter denote-yaml-front-matter :title-key-regexp "^title\\s-*:" :title-value-function denote-surround-with-quotes :title-value-reverse-function denote-trim-whitespace-then-quotes :keywords-key-regexp "^tags\\s-*:" :keywords-value-function denote-format-keywords-for-md-front-matter :keywords-value-reverse-function denote-extract-keywords-from-front-matter :link denote-md-link-format :link-in-context-regexp denote-md-link-in-context-regexp) (quarto :extension ".qmd" :date-function denote-date-rfc3339 :front-matter denote-yaml-front-matter :title-key-regexp "^title\\s-*:" :title-value-function denote-surround-with-quotes :title-value-reverse-function denote-trim-whitespace-then-quotes :keywords-key-regexp "^tags\\s-*:" :keywords-value-function denote-format-keywords-for-md-front-matter :keywords-value-reverse-function denote-extract-keywords-from-front-matter :link denote-md-link-format :link-in-context-regexp denote-md-link-in-context-regexp) (org :extension ".org" :date-function denote-date-org-timestamp :front-matter denote-org-front-matter :title-key-regexp "^#\\+title\\s-*:" :title-value-function identity :title-value-reverse-function denote-trim-whitespace :keywords-key-regexp "^#\\+filetags\\s-*:" :keywords-value-function denote-format-keywords-for-org-front-matter :keywords-value-reverse-function denote-extract-keywords-from-front-matter :link denote-org-link-format :link-in-context-regexp denote-org-link-in-context-regexp) (markdown-yaml :extension ".qmd" :date-function denote-date-rfc3339 :front-matter denote-yaml-front-matter :title-key-regexp "^title\\s-*:" :title-value-function denote-surround-with-quotes :title-value-reverse-function denote-trim-whitespace-then-quotes :keywords-key-regexp "^tags\\s-*:" :keywords-value-function denote-format-keywords-for-md-front-matter :keywords-value-reverse-function denote-extract-keywords-from-front-matter :link denote-md-link-format :link-in-context-regexp denote-md-link-in-context-regexp) (markdown-toml :extension ".md" :date-function denote-date-rfc3339 :front-matter denote-toml-front-matter :title-key-regexp "^title\\s-*=" :title-value-function denote-surround-with-quotes :title-value-reverse-function denote-trim-whitespace-then-quotes :keywords-key-regexp "^tags\\s-*=" :keywords-value-function denote-format-keywords-for-md-front-matter :keywords-value-reverse-function denote-extract-keywords-from-front-matter :link denote-md-link-format :link-in-context-regexp denote-md-link-in-context-regexp) (text :extension ".txt" :date-function denote-date-iso-8601 :front-matter denote-text-front-matter :title-key-regexp "^title\\s-*:" :title-value-function identity :title-value-reverse-function denote-trim-whitespace :keywords-key-regexp "^tags\\s-*:" :keywords-value-function denote-format-keywords-for-text-front-matter :keywords-value-reverse-function denote-extract-keywords-from-front-matter :link denote-org-link-format :link-in-context-regexp denote-org-link-in-context-regexp))
We can do this more concisely (and learn a litle bit of emacs lisp on the way)
by modifying the markdown-yaml
file type definition.
Modifying the markdown-yaml
file type
As noted, all we're actually doing is changing the file extension in the existing markdown type. So instead of writing out all of the redundant details, let's just copy them from the markdown type.
(let ((quarto (cdr (assoc 'markdown-yaml denote-file-types)))) (setf (plist-get quarto :extension) ".qmd") (add-to-list 'denote-file-types (cons 'quarto quarto))) (print denote-file-types)
((quarto :extension ".qmd" :date-function denote-date-rfc3339 :front-matter denote-yaml-front-matter :title-key-regexp "^title\\s-*:" :title-value-function denote-surround-with-quotes :title-value-reverse-function denote-trim-whitespace-then-quotes :keywords-key-regexp "^tags\\s-*:" :keywords-value-function denote-format-keywords-for-md-front-matter :keywords-value-reverse-function denote-extract-keywords-from-front-matter :link denote-md-link-format :link-in-context-regexp denote-md-link-in-context-regexp) (org :extension ".org" :date-function denote-date-org-timestamp :front-matter denote-org-front-matter :title-key-regexp "^#\\+title\\s-*:" :title-value-function identity :title-value-reverse-function denote-trim-whitespace :keywords-key-regexp "^#\\+filetags\\s-*:" :keywords-value-function denote-format-keywords-for-org-front-matter :keywords-value-reverse-function denote-extract-keywords-from-front-matter :link denote-org-link-format :link-in-context-regexp denote-org-link-in-context-regexp) (markdown-yaml :extension ".qmd" :date-function denote-date-rfc3339 :front-matter denote-yaml-front-matter :title-key-regexp "^title\\s-*:" :title-value-function denote-surround-with-quotes :title-value-reverse-function denote-trim-whitespace-then-quotes :keywords-key-regexp "^tags\\s-*:" :keywords-value-function denote-format-keywords-for-md-front-matter :keywords-value-reverse-function denote-extract-keywords-from-front-matter :link denote-md-link-format :link-in-context-regexp denote-md-link-in-context-regexp) (markdown-toml :extension ".md" :date-function denote-date-rfc3339 :front-matter denote-toml-front-matter :title-key-regexp "^title\\s-*=" :title-value-function denote-surround-with-quotes :title-value-reverse-function denote-trim-whitespace-then-quotes :keywords-key-regexp "^tags\\s-*=" :keywords-value-function denote-format-keywords-for-md-front-matter :keywords-value-reverse-function denote-extract-keywords-from-front-matter :link denote-md-link-format :link-in-context-regexp denote-md-link-in-context-regexp) (text :extension ".txt" :date-function denote-date-iso-8601 :front-matter denote-text-front-matter :title-key-regexp "^title\\s-*:" :title-value-function identity :title-value-reverse-function denote-trim-whitespace :keywords-key-regexp "^tags\\s-*:" :keywords-value-function denote-format-keywords-for-text-front-matter :keywords-value-reverse-function denote-extract-keywords-from-front-matter :link denote-org-link-format :link-in-context-regexp denote-org-link-in-context-regexp))
What's actually happening here? Let's go through it step by step.
The assoc
function takes a key and an alist as arguments and returns the first
element of that alist whose CAR is equal to the key. In effect, we're searching
the denote-file-types
alist for the element whose CAR is markdown-yaml
.
(assoc 'markdown-yaml denote-file-types)
(markdown-yaml :extension ".qmd" :date-function denote-date-rfc3339 :front-matter denote-yaml-front-matter :title-key-regexp "^title\\s-*:" :title-value-function denote-surround-with-quotes :title-value-reverse-function denote-trim-whitespace-then-quotes :keywords-key-regexp "^tags\\s-*:" :keywords-value-function denote-format-keywords-for-md-front-matter :keywords-value-reverse-function denote-extract-keywords-from-front-matter :link denote-md-link-format :link-in-context-regexp denote-md-link-in-context-regexp)
We only want the CDR of this element (everything but the markdown-yaml
at the
beginning).
(cdr (assoc 'markdown-yaml denote-file-types))
(:extension ".qmd" :date-function denote-date-rfc3339 :front-matter denote-yaml-front-matter :title-key-regexp "^title\\s-*:" :title-value-function denote-surround-with-quotes :title-value-reverse-function denote-trim-whitespace-then-quotes :keywords-key-regexp "^tags\\s-*:" :keywords-value-function denote-format-keywords-for-md-front-matter :keywords-value-reverse-function denote-extract-keywords-from-front-matter :link denote-md-link-format :link-in-context-regexp denote-md-link-in-context-regexp)
We use let
to say that, for the purposes of this procedure, we want to associate
the above plist with the name quarto
.
Next, we need to change the extension from .md
to .qmd
. We use plist-get
to get
the desired property (:extension
) and setf
to set it to ".qmd"
.
(setf (plist-get (cdr (assoc 'markdown-yaml denote-file-types)) :extension) ".qmd")
.qmd
In the end, we add the quarto
plist to the denote-file-types
alist with
(add-to-list 'denote-file-types (cons 'quarto quarto))
. The cons
function
creates a new cons from a CAR
and a CDR
(in this case, quarto
and the modified copy of the markdown-yaml
plist with the changed extension).
And with that, we can now create new quarto files with denote!
Check out quarto-emacs if you're interested in working with quarto files in emacs (though I have to admit that working with quarto files in RStudio is a joy and is at least worth a try before commiting to an emacs-only workflow).