Skip navigation

Quarto is amazing! And, it’s eating the world! OK. Perhaps not the entire world. But it’s still amazing!

If you browse around the HQ, you’ll find many interesting notebooks. You may even have a few yourself! Wouldn’t it be great if you could just import an Observable notebook right into Quarto? Well, now you can.

#' Transform an Observable Notebook into a Quarto project
#' 
#' This will yank the cells from a live Observable notebook and turn it into a Quarto project,
#' downloading all the `FileAttachments` as well.
#' 
#' @param ohq_ref either a short ref (e.g. `@@hrbrmstr/just-one-more-thing`) or a full
#'     URL to a published Observable notebook
#' @param output_dir quarto project directory (will be created if not already present)
#' @param quarto_filename if `NULL` (the default) the name will be the slug (e.g. `just-one-more-thing`
#'     as in the `ohq_ref` param eample) with `.qmd` suffix
#' @param echo set `echo` to `true` or `false` in the YAML
ohq_to_quarto <- function(ohq_ref, output_dir, quarto_filename = NULL, echo = FALSE) {

  ohq_ref <- ohq_ref[1]
  if (grepl("^@", ohq_ref)) ohq_ref <- sprintf("https://observablehq.com/%s", ohq_ref)

  output_dir <- output_dir[1]
  if (!dir.exists(output_dir)) dir.create(output_dir)

  quarto_filename <- quarto_filename[1]

  pg <- rvest::read_html(ohq_ref)

  pg |> 
    html_nodes("script#__NEXT_DATA__") |> 
    html_text() |> 
    jsonlite::fromJSON() -> x

  meta <- x$props$pageProps$initialNotebook
  nodes <- x$props$pageProps$initialNotebook$nodes

  if (is.null(quarto_filename)) quarto_filename <- sprintf("%.qmd", meta$slug)

  c(
    "---", 
    sprintf("title: '%s'", meta$title), 
    "format: html", 
    if (echo) "echo: true" else "echo: false",
    "---",
    "",
    purrr::map2(nodes$value, nodes$mode, ~{
      c(

        "```{ojs}",
        dplyr::case_when(
          .y == "md" ~ sprintf("md`%s`", .x),
          .y == "html" ~ sprintf("html`%s`", .x),
          TRUE ~ .x
        ),
        "```",
        ""
      )

    })
  ) |> 
    purrr::flatten_chr() |> 
    cat(
      file = file.path(output_dir, quarto_filename), 
      sep = "\n"
    )

  if (length(meta$files)) {
    if (nrow(meta$files) > 0) {
      purrr::walk2(
        meta$files$download_url,
        meta$files$name, ~{
          download.file(
            url = .x,
            destfile = file.path(output_dir, .y),
            quiet = TRUE
          )
        }
      )
    }
  }

}

You can try that out with my Columbo notebook:

ohq_to_quarto(
  ohq_ref = "@hrbrmstr/just-one-more-thing", 
  output_dir = "~/Development/columbo",
  quarto_filename = "columbo.qmd",
  echo = FALSE
)

That will download the CSV file into the specified directory and convert the cells to a .qmd. You can download that example file, but you’ll need the data to run it (or just run the converter).

This is what the directory tree looks like after the script is run and the document is rendered:

columbo/
├── columbo.html
├── columbo.qmd
├── columbo_data.csv
└── columbo_files
    └── libs
        ├── bootstrap
        │   ├── bootstrap-icons.css
        │   ├── bootstrap-icons.woff
        │   ├── bootstrap.min.css
        │   └── bootstrap.min.js
        ├── clipboard
        │   └── clipboard.min.js
        ├── quarto-html
        │   ├── anchor.min.js
        │   ├── popper.min.js
        │   ├── quarto-syntax-highlighting.css
        │   ├── quarto.js
        │   ├── tippy.css
        │   └── tippy.umd.min.js
        └── quarto-ojs
            ├── quarto-ojs-runtime.js
            └── quarto-ojs.css

The function has not been battle tested, and it’s limited to the current functionality, but it should do what it says on the tin.

I’ll turn this into a Rust binary so it’s more usable outside of the R ecosystem.

You can try out a fledgling Rust version here.

2 Comments

  1. Nice! It’s also worth mentioning that Quarto also has a native ability to import OJS cells from Observable notebooks (although you can only interact with them in another OJS cell, not a rendered language like R or Python): https://quarto.org/docs/interactive/ojs/libraries.html#observablehq


2 Trackbacks/Pingbacks

  1. […] *** Ceci est un blog syndiqué du Security Bloggers Network de rud.is Rédigé par hrbrmstr. Lire le message d’origine sur : https://rud.is/b/2022/08/19/bootstrapping-an-ojs-quarto-document-with-an-observable-notebook/ […]

  2. […] article was first published on R – rud.is, and kindly contributed to R-bloggers]. (You can report issue about the content on this page here) […]

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.