parsermd

implements a C++ parser and abstract syntax tree (AST) for Quarto and R Markdown documents in R.

  • supports manipulating ASTs (filtering, editing, etc.)

  • nodes classes use S7 for validation and dispatch

  • ability to directly source and render ASTs

  • off-and-on project since covid, original use case was to aide in the grading for a large machine learning course

  • v0.1.3 is on CRAN, v0.2.0 with full Quarto support on GitHub (CRAN soon*)

  • Quarto examples today, but everything works with RMarkdown

hello.qmd

---
title: "Hello, Quarto"
format:
  html:
    self-contained: true
---
  
```{r}
#| label: load-packages
#| include: false

library(tidyverse)
library(palmerpenguins)
```

## Meet Quarto

Quarto enables you to weave together content and executable code into a finished document. 
To learn more about Quarto see <https://quarto.org>.

## Meet the penguins

![](https://raw.githubusercontent.com/quarto-dev/quarto-web/main/docs/get-started/hello/rstudio/lter_penguins.png){style="float:right;" fig-alt="Illustration of three species of Palmer Archipelago penguins: Chinstrap, Gentoo, and Adelie. Artwork by @allison_horst." width="401"}

The `penguins` data from the [**palmerpenguins**](https://allisonhorst.github.io/palmerpenguins "palmerpenguins R package") 
package contains size measurements for `{r} nrow(penguins)` penguins from three species
observed on three islands in the Palmer Archipelago, Antarctica.

The plot below shows the relationship between flipper and bill lengths of these penguins.

```{r}
#| label: plot-penguins
#| warning: false
#| echo: false

ggplot(penguins, 
       aes(x = flipper_length_mm, y = bill_length_mm)) +
  geom_point(aes(color = species, shape = species)) +
  scale_color_manual(values = c("darkorange","purple","cyan4")) +
  labs(
    title = "Flipper and bill length",
    subtitle = "Dimensions for penguins at Palmer Station LTER",
    x = "Flipper length (mm)", y = "Bill length (mm)",
    color = "Penguin species", shape = "Penguin species"
  ) +
  theme_minimal()
```

## Other Quarto features

### Fenced divs

:::{.callout-note}
Note that there are five types of callouts, including: 
`note`, `tip`, `warning`, `caution`, and `important`.
:::

### Markdown code blocks

Some sample python code,

```python
import numpy as np
import matplotlib.pyplot as plt

r = np.arange(0, 2, 0.01)
theta = 2 * np.pi * r
fig, ax = plt.subplots(
  subplot_kw = {'projection': 'polar'} 
)
ax.plot(theta, r)
ax.set_rticks([0.5, 1, 1.5, 2])
ax.grid(True)
plt.show()
```

### Short codes

Shortcodes are special markdown directives that generate various types of content,

{{< lipsum 1 >}}

Elements as AST

qmd = parse_qmd("hello.qmd")
qmd |> print(flat = TRUE)
├── YAML [2 fields]
├── Markdown [1 line]
├── Chunk [r, 4 lines] - load-packages
├── Heading [h2] - Meet Quarto
├── Markdown [1 line]
├── Heading [h2] - Meet the penguins
├── Markdown [7 lines]
├── Chunk [r, 12 lines] - plot-penguins
├── Heading [h2] - Other Quarto features
├── Heading [h3] - Fenced divs
├── Open Fenced div [.callout-note]
├── Markdown [2 lines]
├── Close Fenced div 
├── Heading [h3] - Markdown code blocks
├── Markdown [1 line]
├── Code block [python, 12 lines]
├── Heading [h3] - Short codes
└── Markdown [3 lines]
qmd |> print()
├── YAML [2 fields]
├── Markdown [1 line]
├── Chunk [r, 4 lines] - load-packages
├── Heading [h2] - Meet Quarto
│   └── Markdown [1 line]
├── Heading [h2] - Meet the penguins
│   ├── Markdown [7 lines]
│   └── Chunk [r, 12 lines] - plot-penguins
└── Heading [h2] - Other Quarto features
    ├── Heading [h3] - Fenced divs
    │   ├── Open Fenced div [.callout-note]
    │   │   └── Markdown [2 lines]
    │   └── Close Fenced div 
    ├── Heading [h3] - Markdown code blocks
    │   ├── Markdown [1 line]
    │   └── Code block [python, 12 lines]
    └── Heading [h3] - Short codes
        └── Markdown [3 lines]

Why hierarchical?

Assuming a hierarchy lets us use a CSS selector like approach to target specific nodes based on headings and their descendants,

qmd |> rmd_select(by_section("Meet Quarto"))
├── YAML [2 fields]
└── Heading [h2] - Meet Quarto
    └── Markdown [1 line]
qmd |> rmd_select(by_section("Fenced divs"))
├── YAML [2 fields]
└── Heading [h3] - Fenced divs
    ├── Open Fenced div [.callout-note]
    │   └── Markdown [2 lines]
    └── Close Fenced div 
qmd |> 
  rmd_select(
    by_section(c("Other Quarto features", "*code*"))
  )
├── YAML [2 fields]
└── Heading [h2] - Other Quarto features
    ├── Heading [h3] - Markdown code blocks
    │   ├── Markdown [1 line]
    │   └── Code block [python, 12 lines]
    └── Heading [h3] - Short codes
        └── Markdown [3 lines]
qmd |> 
  rmd_select(
    by_section(
      c("Other Quarto features", "*code*"), 
      keep_parents = FALSE
    ), 
    keep_yaml = FALSE
  )
├── Heading [h3] - Markdown code blocks
│   ├── Markdown [1 line]
│   └── Code block [python, 12 lines]
└── Heading [h3] - Short codes
    └── Markdown [3 lines]

as_document()

ASTs and nodes can be converted back to Quarto documents,

qmd |> 
  rmd_select(by_section("Meet Quarto")) |>
  as_document() |>
  cat(sep = "\n")
---
title: Hello, Quarto
format:
  html:
    self-contained: true
---

## Meet Quarto

Quarto enables you to weave together content and executable code into a finished document. To learn more about Quarto see <https://quarto.org>.
qmd |> 
  rmd_select(by_section("Fenced divs")) |>
  as_document() |>
  cat(sep = "\n")
---
title: Hello, Quarto
format:
  html:
    self-contained: true
---

### Fenced divs

::: {.callout-note}

Note that there are five types of callouts, including: 
`note`, `tip`, `warning`, `caution`, and `important`.


:::

Additional selectors

rmd_select() and helpers are built using tidyselect (multiple selectors are or’d / unioned together)

  • by_section()

  • has_type()

  • has_label()

  • has_heading()

  • has_option()

  • has_shortcode()

  • by_fdiv()
qmd |> rmd_select(
  has_label("*p*")
)
├── YAML [2 fields]
├── Chunk [r, 4 lines] - load-packages
└── Chunk [r, 12 lines] - plot-penguins
qmd |> rmd_select(
  has_heading("Meet Quarto"):has_heading("Meet the penguins")
)
├── YAML [2 fields]
├── Heading [h2] - Meet Quarto
│   └── Markdown [1 line]
└── Heading [h2] - Meet the penguins

qmd |> rmd_select(
  has_type(c("rmd_yaml","rmd_chunk"))
)
├── YAML [2 fields]
├── Chunk [r, 4 lines] - load-packages
└── Chunk [r, 12 lines] - plot-penguins
qmd |> rmd_select(
  has_type("rmd_yaml"),
  has_type("rmd_chunk")
)
├── YAML [2 fields]
├── Chunk [r, 4 lines] - load-packages
└── Chunk [r, 12 lines] - plot-penguins
qmd |> rmd_select(
  !has_type("rmd_markdown")
)
├── YAML [2 fields]
├── Chunk [r, 4 lines] - load-packages
├── Heading [h2] - Meet Quarto
├── Heading [h2] - Meet the penguins
│   └── Chunk [r, 12 lines] - plot-penguins
└── Heading [h2] - Other Quarto features
    ├── Heading [h3] - Fenced divs
    │   ├── Open Fenced div [.callout-note]
    │   └── Close Fenced div 
    ├── Heading [h3] - Markdown code blocks
    │   └── Code block [python, 12 lines]
    └── Heading [h3] - Short codes

Rendering

qmd |> render("hello_quarto")