├── .github ├── .gitignore └── workflows │ ├── R-CMD-check.yaml │ └── test-coverage.yaml ├── vignettes └── .gitignore ├── .gitattributes ├── R ├── print.R ├── quartools-package.R ├── span.R ├── basics.R ├── utilities-list.R ├── utilities-check.R ├── import-standalone-purrr.R ├── shortcodes.R ├── utilities.R ├── link.R ├── callout.R ├── figures.R ├── block.R ├── map.R ├── lists.R ├── with.R ├── attributes.R ├── import-standalone-obj-type.R └── import-standalone-types-check.R ├── tests ├── testthat │ ├── _snaps │ │ ├── span.md │ │ ├── block.md │ │ ├── link.md │ │ ├── basics.md │ │ ├── figures.md │ │ ├── lists.md │ │ ├── shortcodes.md │ │ ├── map.md │ │ └── with.md │ ├── test-basics.R │ ├── test-lists.R │ ├── test-link.R │ ├── test-figures.R │ ├── test-block.R │ ├── test-span.R │ ├── test-shortcodes.R │ ├── test_with.R │ └── test_map.R └── testthat.R ├── .Rbuildignore ├── codecov.yml ├── .lintr ├── quartools.Rproj ├── man ├── list_drop_or_replace_na.Rd ├── combine.Rd ├── qto_basics.Rd ├── quartools-package.Rd ├── qto_shortcode.Rd ├── check_src.Rd ├── qto_fig_span.Rd ├── qto_block.Rd ├── qto_list.Rd ├── qto_fig.Rd ├── qto_link.Rd ├── map_qto.Rd ├── qto_dl.Rd ├── qto_attributes.Rd ├── qto_span.Rd ├── qto_src_span.Rd ├── pmap_qto.Rd ├── qto_div.Rd ├── page-layout.Rd └── qto_callout.Rd ├── NAMESPACE ├── .gitignore ├── DESCRIPTION ├── README.qmd ├── README.md └── LICENSE.md /.github/.gitignore: -------------------------------------------------------------------------------- 1 | *.html 2 | -------------------------------------------------------------------------------- /vignettes/.gitignore: -------------------------------------------------------------------------------- 1 | *.html 2 | *.R 3 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | -------------------------------------------------------------------------------- /R/print.R: -------------------------------------------------------------------------------- 1 | #' @export 2 | print.quarto_block <- function(x, ...) { 3 | cat(x, sep = "\n") 4 | } 5 | -------------------------------------------------------------------------------- /tests/testthat/_snaps/span.md: -------------------------------------------------------------------------------- 1 | # qto_span works 2 | 3 | Code 4 | qto_span("Text", id = "value") 5 | Output 6 | [Text]{#value} 7 | 8 | -------------------------------------------------------------------------------- /.Rbuildignore: -------------------------------------------------------------------------------- 1 | ^quartools\.Rproj$ 2 | ^\.Rproj\.user$ 3 | ^\.github$ 4 | ^README\.qmd$ 5 | ^LICENSE\.md$ 6 | ^\.lintr$ 7 | ^codecov\.yml$ 8 | ^vignettes/articles$ 9 | -------------------------------------------------------------------------------- /R/quartools-package.R: -------------------------------------------------------------------------------- 1 | #' @keywords internal 2 | "_PACKAGE" 3 | 4 | 5 | ## usethis namespace: start 6 | #' @import rlang 7 | ## usethis namespace: end 8 | NULL 9 | -------------------------------------------------------------------------------- /tests/testthat/test-basics.R: -------------------------------------------------------------------------------- 1 | test_that("qto basics works", { 2 | expect_snapshot( 3 | qto_heading("Heading 1") 4 | ) 5 | expect_snapshot( 6 | qto_hr() 7 | ) 8 | }) 9 | -------------------------------------------------------------------------------- /tests/testthat/test-lists.R: -------------------------------------------------------------------------------- 1 | test_that("lists work", { 2 | expect_snapshot(qto_li(LETTERS[1:2])) 3 | expect_snapshot(qto_ol(LETTERS[3:4])) 4 | expect_snapshot(qto_dl("Term" = "Definition")) 5 | }) 6 | -------------------------------------------------------------------------------- /tests/testthat/test-link.R: -------------------------------------------------------------------------------- 1 | test_that("qto_link works", { 2 | expect_snapshot( 3 | qto_link("https://quarto.org") 4 | ) 5 | 6 | expect_snapshot( 7 | qto_link("https://quarto.org", "Quarto website") 8 | ) 9 | }) 10 | -------------------------------------------------------------------------------- /tests/testthat/_snaps/block.md: -------------------------------------------------------------------------------- 1 | # qto_div works 2 | 3 | Code 4 | qto_div("Example text.", id = "id", class = "class") 5 | Output 6 | 7 | ::: {#id .class} 8 | Example text. 9 | ::: 10 | 11 | 12 | -------------------------------------------------------------------------------- /tests/testthat/test-figures.R: -------------------------------------------------------------------------------- 1 | test_that("qto_fig works", { 2 | expect_snapshot( 3 | qto_fig("image.jpeg", "Image caption", alt = "Image alt text") 4 | ) 5 | expect_snapshot( 6 | qto_fig("graphic.pdf", title = "Title of the graphic") 7 | ) 8 | }) 9 | -------------------------------------------------------------------------------- /codecov.yml: -------------------------------------------------------------------------------- 1 | comment: false 2 | 3 | coverage: 4 | status: 5 | project: 6 | default: 7 | target: auto 8 | threshold: 1% 9 | informational: true 10 | patch: 11 | default: 12 | target: auto 13 | threshold: 1% 14 | informational: true 15 | -------------------------------------------------------------------------------- /tests/testthat/_snaps/link.md: -------------------------------------------------------------------------------- 1 | # qto_link works 2 | 3 | Code 4 | qto_link("https://quarto.org") 5 | Output 6 | 7 | 8 | --- 9 | 10 | Code 11 | qto_link("https://quarto.org", "Quarto website") 12 | Output 13 | [Quarto website](https://quarto.org) 14 | 15 | -------------------------------------------------------------------------------- /tests/testthat/test-block.R: -------------------------------------------------------------------------------- 1 | test_that("qto_block works", { 2 | expect_s3_class(qto_block(), "quarto_block") 3 | expect_type(qto_block(), "character") 4 | expect_length(qto_block(), 1) 5 | }) 6 | 7 | test_that("qto_div works", { 8 | expect_snapshot(qto_div("Example text.", id = "id", class = "class")) 9 | }) 10 | -------------------------------------------------------------------------------- /tests/testthat/test-span.R: -------------------------------------------------------------------------------- 1 | test_that("qto_span errors", { 2 | expect_error(qto_span()) 3 | }) 4 | 5 | test_that("qto_span works", { 6 | expect_identical( 7 | qto_span("Text"), 8 | qto_block("[Text]{}") 9 | ) 10 | 11 | expect_snapshot( 12 | qto_span("Text", id = "value") 13 | ) 14 | }) 15 | -------------------------------------------------------------------------------- /tests/testthat/_snaps/basics.md: -------------------------------------------------------------------------------- 1 | # qto basics works 2 | 3 | Code 4 | qto_heading("Heading 1") 5 | Output 6 | # Heading 1 7 | 8 | --- 9 | 10 | Code 11 | qto_hr() 12 | Output 13 | 14 | 15 | ------------------------------------------------------------------------ 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /.lintr: -------------------------------------------------------------------------------- 1 | linters: linters_with_defaults( 2 | line_length_linter(120), 3 | implicit_integer_linter(), 4 | indentation_linter(indent = 4L), 5 | object_usage_linter = NULL, 6 | object_name_linter = NULL 7 | ) 8 | exclusions: list("man/", "inst/", "src/", ".vscode/", ".Rproj.user/", "R/import-standalone-obj-type.R", "R/import-standalone-types-check.R") 9 | encoding: "UTF-8" 10 | -------------------------------------------------------------------------------- /tests/testthat/_snaps/figures.md: -------------------------------------------------------------------------------- 1 | # qto_fig works 2 | 3 | Code 4 | qto_fig("image.jpeg", "Image caption", alt = "Image alt text") 5 | Output 6 | ![Image caption](image.jpeg){fig-alt='Image alt text'} 7 | 8 | --- 9 | 10 | Code 11 | qto_fig("graphic.pdf", title = "Title of the graphic") 12 | Output 13 | ![](graphic.pdf "Title of the graphic") 14 | 15 | -------------------------------------------------------------------------------- /tests/testthat/_snaps/lists.md: -------------------------------------------------------------------------------- 1 | # lists work 2 | 3 | Code 4 | qto_li(LETTERS[1:2]) 5 | Output 6 | 7 | * A 8 | * B 9 | 10 | --- 11 | 12 | Code 13 | qto_ol(LETTERS[3:4]) 14 | Output 15 | 16 | 1. C 17 | 1. D 18 | 19 | --- 20 | 21 | Code 22 | qto_dl(Term = "Definition") 23 | Output 24 | Term 25 | 26 | : Definition 27 | 28 | -------------------------------------------------------------------------------- /tests/testthat.R: -------------------------------------------------------------------------------- 1 | # This file is part of the standard setup for testthat. 2 | # It is recommended that you do not modify it. 3 | # 4 | # Where should you do additional test configuration? 5 | # Learn more about the roles of various files in: 6 | # * https://r-pkgs.org/testing-design.html#sec-tests-files-overview 7 | # * https://testthat.r-lib.org/articles/special-files.html 8 | 9 | library(testthat) 10 | library(quartools) 11 | 12 | test_check("quartools") 13 | -------------------------------------------------------------------------------- /quartools.Rproj: -------------------------------------------------------------------------------- 1 | Version: 1.0 2 | 3 | RestoreWorkspace: No 4 | SaveWorkspace: No 5 | AlwaysSaveHistory: Default 6 | 7 | EnableCodeIndexing: Yes 8 | UseSpacesForTab: Yes 9 | NumSpacesForTab: 4 10 | Encoding: UTF-8 11 | 12 | RnwWeave: Sweave 13 | LaTeX: pdfLaTeX 14 | 15 | AutoAppendNewline: Yes 16 | StripTrailingWhitespace: Yes 17 | LineEndingConversion: Posix 18 | 19 | BuildType: Package 20 | PackageUseDevtools: Yes 21 | PackageInstallArgs: --no-multiarch --with-keep.source 22 | PackageRoxygenize: rd,collate,namespace 23 | -------------------------------------------------------------------------------- /tests/testthat/_snaps/shortcodes.md: -------------------------------------------------------------------------------- 1 | # qto_shortcode works 2 | 3 | Code 4 | qto_video("https://www.youtube.com/embed/wo9vZccmqwc") 5 | Output 6 | {{< video https://www.youtube.com/embed/wo9vZccmqwc >}} 7 | 8 | --- 9 | 10 | Code 11 | qto_pagebreak() 12 | Output 13 | {{< pagebreak >}} 14 | 15 | --- 16 | 17 | Code 18 | qto_kbd(mac = "Shift-Command-O", win = "Shift-Control-O", linux = "Shift-Ctrl-L") 19 | Output 20 | {{< kbd mac=Shift-Command-O win=Shift-Control-O linux=Shift-Ctrl-L >}} 21 | 22 | -------------------------------------------------------------------------------- /tests/testthat/test-shortcodes.R: -------------------------------------------------------------------------------- 1 | test_that("qto_shortcode works", { 2 | expect_error( 3 | qto_shortcode() 4 | ) 5 | expect_snapshot( 6 | qto_video("https://www.youtube.com/embed/wo9vZccmqwc") 7 | ) 8 | expect_snapshot( 9 | qto_pagebreak() 10 | ) 11 | expect_identical( 12 | qto_kbd("Shift", "Ctrl", "P"), 13 | qto_kbd("Shift-Ctrl-P") 14 | ) 15 | expect_snapshot( 16 | qto_kbd( 17 | mac = "Shift-Command-O", 18 | win = "Shift-Control-O", 19 | linux = "Shift-Ctrl-L" 20 | ) 21 | ) 22 | }) 23 | -------------------------------------------------------------------------------- /man/list_drop_or_replace_na.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/utilities-list.R 3 | \name{list_drop_or_replace_na} 4 | \alias{list_drop_or_replace_na} 5 | \title{Drop or replace NA items from a list} 6 | \usage{ 7 | list_drop_or_replace_na(x, drop_na = FALSE, replacement = "") 8 | } 9 | \arguments{ 10 | \item{drop_na}{If \code{TRUE}, drop \code{NA} values from the supplied list.} 11 | 12 | \item{replacement}{If not \code{NULL}, replace NA values from the supplied list 13 | with the value of replacement.} 14 | } 15 | \description{ 16 | Drop or replace NA items from a list 17 | } 18 | \keyword{internal} 19 | -------------------------------------------------------------------------------- /NAMESPACE: -------------------------------------------------------------------------------- 1 | # Generated by roxygen2: do not edit by hand 2 | 3 | S3method(print,quarto_block) 4 | export(map_qto) 5 | export(pmap_qto) 6 | export(qto_attributes) 7 | export(qto_block) 8 | export(qto_callout) 9 | export(qto_div) 10 | export(qto_dl) 11 | export(qto_fig) 12 | export(qto_heading) 13 | export(qto_hr) 14 | export(qto_kbd) 15 | export(qto_li) 16 | export(qto_link) 17 | export(qto_ol) 18 | export(qto_pagebreak) 19 | export(qto_shortcode) 20 | export(qto_span) 21 | export(qto_video) 22 | export(with_body_column) 23 | export(with_margin_column) 24 | export(with_page_column) 25 | export(with_screen_column) 26 | export(with_screen_inset_column) 27 | import(rlang) 28 | -------------------------------------------------------------------------------- /R/span.R: -------------------------------------------------------------------------------- 1 | #' Create a Quarto span 2 | #' 3 | #' See the Quarto documentation for more on divs and spans: 4 | #' 5 | #' 6 | #' @param text Text to wrap in span bracket. 7 | #' @inheritParams qto_attributes 8 | #' @inheritDotParams qto_attributes 9 | #' @family span 10 | #' @examples 11 | #' 12 | #' qto_span("This is *some text*", class = "class", key = "value") 13 | #' 14 | #' qto_span("This is good", id = "id", class = "class", key1 = "val1", key2 = "val2") 15 | #' 16 | #' @export 17 | qto_span <- function(text, ..., .attributes = NULL) { 18 | qto_block( 19 | bracket(text), 20 | qto_attributes( 21 | ..., 22 | .attributes = .attributes 23 | ) 24 | ) 25 | } 26 | -------------------------------------------------------------------------------- /man/combine.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/utilities.R 3 | \name{combine} 4 | \alias{combine} 5 | \title{Combine a string with text before and after} 6 | \usage{ 7 | combine(x, before = "", after = before, allow_empty = TRUE, allow_null = TRUE) 8 | } 9 | \arguments{ 10 | \item{x}{An input string.} 11 | 12 | \item{before, after}{Text to insert before and after the supplied string.} 13 | 14 | \item{allow_empty}{If \code{TRUE}, allow empty string ("") values as input. If 15 | \code{FALSE}, error on empty string inputs.} 16 | 17 | \item{allow_null}{If \code{TRUE}, allow \code{NULL} values as input (replaced with an 18 | empty string ""). If \code{FALSE}, error on \code{NULL} inputs.} 19 | } 20 | \description{ 21 | Combine a string with text before and after 22 | } 23 | \keyword{internal} 24 | -------------------------------------------------------------------------------- /man/qto_basics.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/basics.R 3 | \name{qto_basics} 4 | \alias{qto_basics} 5 | \alias{qto_heading} 6 | \alias{qto_hr} 7 | \title{Basic formatting and markdown elements} 8 | \usage{ 9 | qto_heading(..., level = 1L) 10 | 11 | qto_hr(rule = "-", length = 72L, before = "\\n\\n", after = before) 12 | } 13 | \arguments{ 14 | \item{...}{Heading text passed sto \code{\link[=qto_block]{qto_block()}}.} 15 | 16 | \item{level}{Heading level. Defaults to 1.} 17 | 18 | \item{rule, length}{Horizontal rule character and length of rule.} 19 | 20 | \item{before, after}{Text to insert before and after a horizontal rule.} 21 | } 22 | \description{ 23 | \code{\link[=qto_heading]{qto_heading()}} creates headings and \code{\link[=qto_hr]{qto_hr()}} creates horizontal rules. 24 | } 25 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # History files 2 | .Rhistory 3 | .Rapp.history 4 | 5 | # Session Data files 6 | .RData 7 | .RDataTmp 8 | 9 | # User-specific files 10 | .Ruserdata 11 | 12 | # Example code in package build process 13 | *-Ex.R 14 | 15 | # Output files from R CMD build 16 | /*.tar.gz 17 | 18 | # Output files from R CMD check 19 | /*.Rcheck/ 20 | 21 | # RStudio files 22 | .Rproj.user/ 23 | 24 | # produced vignettes 25 | vignettes/*.html 26 | vignettes/*.pdf 27 | 28 | # OAuth2 token, see https://github.com/hadley/httr/releases/tag/v0.3 29 | .httr-oauth 30 | 31 | # knitr and R markdown default cache directories 32 | *_cache/ 33 | /cache/ 34 | 35 | # Temporary files created by R markdown 36 | *.utf8.md 37 | *.knit.md 38 | 39 | # R Environment Variables 40 | .Renviron 41 | 42 | # pkgdown site 43 | docs/ 44 | 45 | # translation temp files 46 | po/*~ 47 | 48 | # RStudio Connect folder 49 | rsconnect/ 50 | inst/doc 51 | -------------------------------------------------------------------------------- /man/quartools-package.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/quartools-package.R 3 | \docType{package} 4 | \name{quartools-package} 5 | \alias{quartools} 6 | \alias{quartools-package} 7 | \title{quartools: Programmatic Element Creation For Quarto Documents} 8 | \description{ 9 | Programatically generate quarto-compliant markdown elements. 10 | } 11 | \seealso{ 12 | Useful links: 13 | \itemize{ 14 | \item \url{https://github.com/ElianHugh/quartools} 15 | \item Report bugs at \url{https://github.com/ElianHugh/quartools/issues} 16 | } 17 | 18 | } 19 | \author{ 20 | \strong{Maintainer}: Elian Thiele-Evans \email{elianhte@gmail.com} (\href{https://orcid.org/0000-0001-8008-3165}{ORCID}) 21 | 22 | Authors: 23 | \itemize{ 24 | \item Eli Pousson \email{eli.pousson@gmail.com} (\href{https://orcid.org/0000-0001-8280-1706}{ORCID}) 25 | } 26 | 27 | } 28 | \keyword{internal} 29 | -------------------------------------------------------------------------------- /DESCRIPTION: -------------------------------------------------------------------------------- 1 | Package: quartools 2 | Title: Programmatic Element Creation For Quarto Documents 3 | Version: 0.0.0.9000 4 | Authors@R: c( 5 | person("Elian", "Thiele-Evans", , "elianhte@gmail.com", role = c("aut", "cre"), 6 | comment = c(ORCID = "0000-0001-8008-3165")), 7 | person("Eli", "Pousson", , "eli.pousson@gmail.com", role = "aut", 8 | comment = c(ORCID = "0000-0001-8280-1706")) 9 | ) 10 | Description: Programatically generate quarto-compliant markdown elements. 11 | License: GPL (>= 3) 12 | URL: https://github.com/ElianHugh/quartools 13 | BugReports: https://github.com/ElianHugh/quartools/issues 14 | Imports: 15 | rlang (>= 1.1.0) 16 | Suggests: 17 | htmltools, 18 | knitr, 19 | quarto, 20 | rmarkdown, 21 | testthat (>= 3.0.0) 22 | VignetteBuilder: 23 | knitr 24 | Config/testthat/edition: 3 25 | Encoding: UTF-8 26 | Roxygen: list(markdown = TRUE) 27 | RoxygenNote: 7.2.3.9000 28 | -------------------------------------------------------------------------------- /R/basics.R: -------------------------------------------------------------------------------- 1 | #' Basic formatting and markdown elements 2 | #' 3 | #' [qto_heading()] creates headings and [qto_hr()] creates horizontal rules. 4 | #' 5 | #' @name qto_basics 6 | NULL 7 | 8 | #' @rdname qto_basics 9 | #' @name qto_heading 10 | #' @param ... Heading text passed sto [qto_block()]. 11 | #' @param level Heading level. Defaults to 1. 12 | #' @export 13 | qto_heading <- function(..., 14 | level = 1L) { 15 | qto_block(strrep("#", level), " ", ...) 16 | } 17 | 18 | #' @rdname qto_basics 19 | #' @name qto_heading 20 | #' @param rule,length Horizontal rule character and length of rule. 21 | #' @param before,after Text to insert before and after a horizontal rule. 22 | #' @export 23 | qto_hr <- function(rule = "-", 24 | length = 72L, 25 | before = "\n\n", 26 | after = before) { 27 | rule <- arg_match0(rule, c("-", "_", "*")) 28 | qto_block( 29 | before, 30 | strrep(rule, length), 31 | after 32 | ) 33 | } 34 | -------------------------------------------------------------------------------- /tests/testthat/test_with.R: -------------------------------------------------------------------------------- 1 | test_that("with_*() output is as expected", { 2 | expect_snapshot(with_body_column("foo", .attributes = ".bar")) 3 | expect_snapshot(with_page_column("foo", .attributes = ".bar")) 4 | expect_snapshot(with_screen_inset_column("foo", .attributes = ".bar")) 5 | expect_snapshot(with_screen_column("foo", .attributes = ".bar")) 6 | expect_snapshot(with_margin_column("foo", .attributes = ".bar")) 7 | 8 | expect_snapshot(with_body_column("foo", .attributes = ".bar", extension = "left")) 9 | expect_snapshot(with_page_column("foo", .attributes = ".bar", extension = "right")) 10 | expect_snapshot(with_screen_inset_column("foo", .attributes = ".bar", extension = "shaded")) 11 | expect_snapshot(with_screen_column("foo", .attributes = ".bar", extension = "left")) 12 | }) 13 | 14 | test_that("with_*() disallows invalid extensions", { 15 | expect_error(with_body_column(extension = "shaded")) 16 | expect_error(with_page_column(extension = "shaded")) 17 | expect_error(with_screen_inset_column(extension = "bad")) 18 | expect_error(with_screen_column(extension = "shaded")) 19 | expect_error(with_margin_column(extension = "shaded")) 20 | }) 21 | -------------------------------------------------------------------------------- /R/utilities-list.R: -------------------------------------------------------------------------------- 1 | #' Drop or replace NA items from a list 2 | #' 3 | #' @param drop_na If `TRUE`, drop `NA` values from the supplied list. 4 | #' @param replacement If not `NULL`, replace NA values from the supplied list 5 | #' with the value of replacement. 6 | #' @keywords internal 7 | list_drop_or_replace_na <- function(x, 8 | drop_na = FALSE, 9 | replacement = "") { 10 | if (drop_na) { 11 | return(list_drop_na(x)) 12 | } 13 | 14 | list_replace_na(x, replacement) 15 | } 16 | 17 | #' Drop empty items from a list 18 | #' 19 | #' @noRd 20 | list_drop_empty <- function(x) { 21 | x[!vapply(x, is_empty, TRUE)] 22 | } 23 | 24 | #' Drop NA items from a list 25 | #' 26 | #' @noRd 27 | list_drop_na <- function(x) { 28 | x[!is.na(x)] 29 | } 30 | 31 | #' Replace NA items in a list 32 | #' 33 | #' @noRd 34 | list_replace_na <- function(x, 35 | replacement = "", 36 | call = caller_env()) { 37 | if (!any(is.na(x))) { 38 | return(x) 39 | } 40 | 41 | if (!is.null(replacement)) { 42 | x[is.na(x)] <- replacement 43 | } 44 | 45 | x 46 | } 47 | -------------------------------------------------------------------------------- /man/qto_shortcode.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/shortcodes.R 3 | \name{qto_shortcode} 4 | \alias{qto_shortcode} 5 | \alias{qto_pagebreak} 6 | \alias{qto_video} 7 | \alias{qto_kbd} 8 | \title{Create a Quarto shortcode} 9 | \usage{ 10 | qto_shortcode(x, before = "{{< ", after = " >}}", ...) 11 | 12 | qto_video(src) 13 | 14 | qto_pagebreak() 15 | 16 | qto_kbd(...) 17 | } 18 | \arguments{ 19 | \item{x}{An input string.} 20 | 21 | \item{before, after}{Text to insert before and after the supplied string.} 22 | 23 | \item{...}{For \code{\link[=qto_kbd]{qto_kbd()}}, a set of strings to combine with "-" or a named 24 | set of attributes where names are one of "mac", "win", or "linux".} 25 | 26 | \item{src}{For \code{\link[=qto_video]{qto_video()}}, a URL with the video source.} 27 | } 28 | \description{ 29 | \code{\link[=qto_shortcode]{qto_shortcode()}} creates a shortcode based on an input string. 30 | } 31 | \examples{ 32 | qto_video("https://www.youtube.com/embed/wo9vZccmqwc") 33 | 34 | qto_pagebreak() 35 | 36 | qto_kbd("Shift", "Ctrl", "P") 37 | 38 | qto_kbd( 39 | mac = "Shift-Command-O", 40 | win = "Shift-Control-O", 41 | linux = "Shift-Ctrl-L" 42 | ) 43 | 44 | } 45 | -------------------------------------------------------------------------------- /tests/testthat/_snaps/map.md: -------------------------------------------------------------------------------- 1 | # map_qto works 2 | 3 | Code 4 | qto_list 5 | Output 6 | [[1]] 7 | 8 | ::: {.callout-note} 9 | This is a note. 10 | ::: 11 | 12 | 13 | [[2]] 14 | 15 | ::: {.callout-note} 16 | And this is a note. 17 | ::: 18 | 19 | 20 | [[3]] 21 | 22 | ::: {.callout-note} 23 | And this is a note 24 | ::: 25 | 26 | 27 | 28 | --- 29 | 30 | Code 31 | qto_list 32 | Output 33 | [[1]] 34 | foo bar baz 35 | 36 | [[2]] 37 | a b c 38 | 39 | 40 | # pmap_qto works 41 | 42 | Code 43 | qto_list 44 | Output 45 | [[1]] 46 | Answer: Yes 47 | 48 | [[2]] 49 | Answer: No 50 | 51 | [[3]] 52 | Answer: Yes 53 | 54 | 55 | --- 56 | 57 | Code 58 | qto_list 59 | Output 60 | [[1]] 61 | 62 | * mpg is: 21 63 | * cyl is: 6 64 | * disp is: 160 65 | 66 | [[2]] 67 | 68 | * mpg is: 21 69 | * cyl is: 6 70 | * disp is: 160 71 | 72 | [[3]] 73 | 74 | * mpg is: 22.8 75 | * cyl is: 4 76 | * disp is: 108 77 | 78 | 79 | -------------------------------------------------------------------------------- /R/utilities-check.R: -------------------------------------------------------------------------------- 1 | #' Check source string 2 | #' 3 | #' Used by [qto_src_span()] to check input strings. 4 | #' 5 | #' @param x Input string. Typically a file path or URL. 6 | #' @param allow_missing If `FALSE` (default), error if string is not a URL and 7 | #' not a valid web path. If `TRUE`, allow input to be a path to a non-existent 8 | #' file. 9 | #' @param allow_empty If `FALSE` (default), error if string is empty. If `TRUE`, 10 | #' allow input to be an empty string. 11 | #' @inheritParams rlang::args_error_context 12 | #' @keywords internal 13 | check_src <- function(x, 14 | allow_missing = FALSE, 15 | allow_empty = FALSE, 16 | arg = caller_arg(x), 17 | call = caller_env()) { 18 | check_string(x, allow_empty = allow_empty, arg = arg, call = call) 19 | what <- "an existing file or a valid web path" 20 | 21 | if (grepl("^(f|ht)tps?://", x)) { 22 | return(NULL) 23 | } 24 | 25 | if (file.exists(x) || allow_missing) { 26 | return(NULL) 27 | } 28 | 29 | if (allow_missing) { 30 | what <- "a file or a valid web path" 31 | } 32 | 33 | stop_input_type( 34 | x, 35 | what = what, 36 | arg = arg, 37 | call = call 38 | ) 39 | } 40 | -------------------------------------------------------------------------------- /man/check_src.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/utilities-check.R 3 | \name{check_src} 4 | \alias{check_src} 5 | \title{Check source string} 6 | \usage{ 7 | check_src( 8 | x, 9 | allow_missing = FALSE, 10 | allow_empty = FALSE, 11 | arg = caller_arg(x), 12 | call = caller_env() 13 | ) 14 | } 15 | \arguments{ 16 | \item{x}{Input string. Typically a file path or URL.} 17 | 18 | \item{allow_missing}{If \code{FALSE} (default), error if string is not a URL and 19 | not a valid web path. If \code{TRUE}, allow input to be a path to a non-existent 20 | file.} 21 | 22 | \item{allow_empty}{If \code{FALSE} (default), error if string is empty. If \code{TRUE}, 23 | allow input to be an empty string.} 24 | 25 | \item{arg}{An argument name as a string. This argument 26 | will be mentioned in error messages as the input that is at the 27 | origin of a problem.} 28 | 29 | \item{call}{The execution environment of a currently 30 | running function, e.g. \code{caller_env()}. The function will be 31 | mentioned in error messages as the source of the error. See the 32 | \code{call} argument of \code{\link[rlang:abort]{abort()}} for more information.} 33 | } 34 | \description{ 35 | Used by \code{\link[=qto_src_span]{qto_src_span()}} to check input strings. 36 | } 37 | \keyword{internal} 38 | -------------------------------------------------------------------------------- /man/qto_fig_span.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/figures.R 3 | \name{qto_fig_span} 4 | \alias{qto_fig_span} 5 | \title{Simplified helper for qto_fig()} 6 | \usage{ 7 | qto_fig_span( 8 | src, 9 | caption = NULL, 10 | ..., 11 | allow_missing = TRUE, 12 | allow_empty = FALSE, 13 | call = caller_env() 14 | ) 15 | } 16 | \arguments{ 17 | \item{src}{Image source. Either a file path or URL.} 18 | 19 | \item{caption}{Caption text} 20 | 21 | \item{...}{ 22 | Arguments passed on to \code{\link[=qto_src_span]{qto_src_span}} 23 | \describe{ 24 | \item{\code{text}}{Caption or link text.} 25 | }} 26 | 27 | \item{allow_missing}{If \code{FALSE} (default), error if string is not a URL and 28 | not a valid web path. If \code{TRUE}, allow input to be a path to a non-existent 29 | file.} 30 | 31 | \item{allow_empty}{If \code{FALSE} (default), error if string is empty. If \code{TRUE}, 32 | allow input to be an empty string.} 33 | 34 | \item{call}{The execution environment of a currently 35 | running function, e.g. \code{caller_env()}. The function will be 36 | mentioned in error messages as the source of the error. See the 37 | \code{call} argument of \code{\link[rlang:abort]{abort()}} for more information.} 38 | } 39 | \description{ 40 | Simplified helper for qto_fig() 41 | } 42 | \keyword{internal} 43 | -------------------------------------------------------------------------------- /R/import-standalone-purrr.R: -------------------------------------------------------------------------------- 1 | # nocov start 2 | 3 | #' all functions here are copied from the 4 | #' `standalone-purrr.R` script in rlang: 5 | #' https://github.com/r-lib/rlang/blob/main/R/standalone-purrr.R 6 | #' @noRd 7 | map <- function(.x, .f, ...) { 8 | .f <- as_function(.f, env = global_env()) 9 | lapply(.x, .f, ...) 10 | } 11 | 12 | #' @noRd 13 | map_chr <- function(.x, .f, ...) { 14 | .rlang_purrr_map_mold(.x, .f, character(1L), ...) 15 | } 16 | 17 | #' @noRd 18 | map_int <- function(.x, .f, ...) { 19 | .rlang_purrr_map_mold(.x, .f, integer(1), ...) 20 | } 21 | 22 | #' @noRd 23 | .rlang_purrr_map_mold <- function(.x, .f, .mold, ...) { 24 | .f <- as_function(.f, env = global_env()) 25 | out <- vapply(.x, .f, .mold, ..., USE.NAMES = FALSE) 26 | names(out) <- names(.x) 27 | out 28 | } 29 | 30 | #' @noRd 31 | pmap <- function(.l, .f, ...) { 32 | .f <- as.function(.f) 33 | args <- .rlang_purrr_args_recycle(.l) 34 | do.call("mapply", c( 35 | FUN = list(quote(.f)), 36 | args, MoreArgs = quote(list(...)), 37 | SIMPLIFY = FALSE, USE.NAMES = FALSE 38 | )) 39 | } 40 | 41 | #' @noRd 42 | .rlang_purrr_args_recycle <- function(args) { 43 | lengths <- map_int(args, length) 44 | n <- max(lengths) 45 | 46 | stopifnot(all(lengths == 1L | lengths == n)) 47 | to_recycle <- lengths == 1L 48 | args[to_recycle] <- map(args[to_recycle], function(x) rep.int(x, n)) 49 | 50 | args 51 | } 52 | 53 | # nocov end -------------------------------------------------------------------------------- /man/qto_block.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/block.R 3 | \name{qto_block} 4 | \alias{qto_block} 5 | \title{Create a block of text to render as Markdown text in Quarto} 6 | \usage{ 7 | qto_block(..., sep = "", collapse = "", call = caller_env()) 8 | } 9 | \arguments{ 10 | \item{...}{dots to convert to character vector} 11 | 12 | \item{sep}{a character string to separate the terms. Not 13 | \code{\link[base]{NA_character_}}.} 14 | 15 | \item{collapse}{an optional character string to separate the results. Not 16 | \code{\link[base]{NA_character_}}. When \code{collapse} is a string, 17 | the result is always a string (\code{\link[base]{character}} of length 1).} 18 | 19 | \item{call}{The execution environment of a currently 20 | running function, e.g. \code{caller_env()}. The function will be 21 | mentioned in error messages as the source of the error. See the 22 | \code{call} argument of \code{\link[rlang:abort]{abort()}} for more information.} 23 | } 24 | \value{ 25 | character vector of length 1 26 | } 27 | \description{ 28 | \code{\link[=qto_block]{qto_block()}} passes the input to \code{\link[=paste]{paste()}} and assigns the classes 29 | "knit_asis" and "quarto_block". 30 | } 31 | \examples{ 32 | qto_block("Hello world!") 33 | 34 | qto_block("Hello", "world!", sep = " ") 35 | 36 | qto_block("- ", LETTERS[1:4], collapse = "\n") 37 | 38 | } 39 | \seealso{ 40 | \itemize{ 41 | \item \code{\link[knitr:asis_output]{knitr::asis_output()}} 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /man/qto_list.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/lists.R 3 | \name{qto_list} 4 | \alias{qto_list} 5 | \alias{qto_ol} 6 | \alias{qto_li} 7 | \title{Create ordered or unordered lists for Quarto} 8 | \usage{ 9 | qto_ol(.list = NULL, ..., level = 1L, symbol = "1.") 10 | 11 | qto_li(.list = NULL, ..., level = 1L, symbol = "*", sep = "") 12 | } 13 | \arguments{ 14 | \item{.list}{A vector of list items. Optional if items are passed to \code{...}.} 15 | 16 | \item{...}{Items to use as list items. Ignored if .list is supplied.} 17 | 18 | \item{level}{Indent level of list. Nested levels in a single function call 19 | are not yet supported.} 20 | 21 | \item{symbol}{Symbol to use for list bullet. Use "(@)" to create a list where 22 | numbering continues after interruption.} 23 | 24 | \item{sep}{Separator character between list items passed to \code{\link[=qto_block]{qto_block()}}. 25 | Defaults to "". Set to \code{"\\n"} for wider list spacing.} 26 | } 27 | \description{ 28 | \code{\link[=qto_ol]{qto_ol()}} created ordered lists and \code{\link[=qto_li]{qto_li()}} creates unordered lists. See 29 | the Quarto documentation for more information on lists: 30 | \url{https://quarto.org/docs/authoring/markdown-basics.html#lists} 31 | } 32 | \examples{ 33 | qto_ol(LETTERS[1:2]) 34 | 35 | qto_li(LETTERS[3:4]) 36 | 37 | qto_li(LETTERS[5:6], level = 2) 38 | 39 | qto_ol(LETTERS[7:8], sep = "\n") 40 | 41 | qto_li(LETTERS[5:6], symbol = c("*", " +")) 42 | 43 | } 44 | \seealso{ 45 | \code{\link[=qto_dl]{qto_dl()}} 46 | } 47 | -------------------------------------------------------------------------------- /.github/workflows/R-CMD-check.yaml: -------------------------------------------------------------------------------- 1 | # Workflow derived from https://github.com/r-lib/actions/tree/v2/examples 2 | # Need help debugging build failures? Start at https://github.com/r-lib/actions#where-to-find-help 3 | on: 4 | push: 5 | branches: [main, master] 6 | pull_request: 7 | branches: [main, master] 8 | 9 | name: R-CMD-check 10 | 11 | jobs: 12 | R-CMD-check: 13 | runs-on: ${{ matrix.config.os }} 14 | 15 | name: ${{ matrix.config.os }} (${{ matrix.config.r }}) 16 | 17 | strategy: 18 | fail-fast: false 19 | matrix: 20 | config: 21 | - {os: macos-latest, r: 'release'} 22 | - {os: windows-latest, r: 'release'} 23 | - {os: ubuntu-latest, r: 'devel', http-user-agent: 'release'} 24 | - {os: ubuntu-latest, r: 'release'} 25 | - {os: ubuntu-latest, r: 'oldrel-1'} 26 | 27 | env: 28 | GITHUB_PAT: ${{ secrets.GITHUB_TOKEN }} 29 | R_KEEP_PKG_SOURCE: yes 30 | 31 | steps: 32 | - uses: actions/checkout@v3 33 | 34 | - uses: r-lib/actions/setup-pandoc@v2 35 | 36 | - uses: r-lib/actions/setup-r@v2 37 | with: 38 | r-version: ${{ matrix.config.r }} 39 | http-user-agent: ${{ matrix.config.http-user-agent }} 40 | use-public-rspm: true 41 | 42 | - uses: r-lib/actions/setup-r-dependencies@v2 43 | with: 44 | extra-packages: any::rcmdcheck 45 | needs: check 46 | 47 | - uses: r-lib/actions/check-r-package@v2 48 | with: 49 | upload-snapshots: true 50 | -------------------------------------------------------------------------------- /.github/workflows/test-coverage.yaml: -------------------------------------------------------------------------------- 1 | # Workflow derived from https://github.com/r-lib/actions/tree/v2/examples 2 | # Need help debugging build failures? Start at https://github.com/r-lib/actions#where-to-find-help 3 | on: 4 | push: 5 | branches: [main, master] 6 | pull_request: 7 | branches: [main, master] 8 | 9 | name: test-coverage 10 | 11 | jobs: 12 | test-coverage: 13 | runs-on: ubuntu-latest 14 | env: 15 | GITHUB_PAT: ${{ secrets.GITHUB_TOKEN }} 16 | 17 | steps: 18 | - uses: actions/checkout@v3 19 | 20 | - uses: r-lib/actions/setup-r@v2 21 | with: 22 | use-public-rspm: true 23 | 24 | - uses: r-lib/actions/setup-r-dependencies@v2 25 | with: 26 | extra-packages: any::covr 27 | needs: coverage 28 | 29 | - name: Test coverage 30 | run: | 31 | covr::codecov( 32 | quiet = FALSE, 33 | clean = FALSE, 34 | install_path = file.path(Sys.getenv("RUNNER_TEMP"), "package") 35 | ) 36 | shell: Rscript {0} 37 | 38 | - name: Show testthat output 39 | if: always() 40 | run: | 41 | ## -------------------------------------------------------------------- 42 | find ${{ runner.temp }}/package -name 'testthat.Rout*' -exec cat '{}' \; || true 43 | shell: bash 44 | 45 | - name: Upload test results 46 | if: failure() 47 | uses: actions/upload-artifact@v3 48 | with: 49 | name: coverage-test-failures 50 | path: ${{ runner.temp }}/package 51 | -------------------------------------------------------------------------------- /man/qto_fig.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/figures.R 3 | \name{qto_fig} 4 | \alias{qto_fig} 5 | \title{Insert an figure or image} 6 | \usage{ 7 | qto_fig( 8 | src, 9 | caption = NULL, 10 | alt = NULL, 11 | reference = NULL, 12 | align = NULL, 13 | pos = NULL, 14 | width = NULL, 15 | height = NULL, 16 | title = NULL, 17 | ..., 18 | allow_missing = TRUE 19 | ) 20 | } 21 | \arguments{ 22 | \item{src}{Figure file path or URL.} 23 | 24 | \item{caption}{Figure caption.} 25 | 26 | \item{alt}{Figure alt text.} 27 | 28 | \item{reference}{Figure reference. "fig-" prefix is optional.} 29 | 30 | \item{align, pos, width, height}{Figure alignment, position, width, and height.} 31 | 32 | \item{title}{Figure title.} 33 | 34 | \item{...}{Additional attributes passed to attributes argument of 35 | \code{\link[=qto_fig_span]{qto_fig_span()}}.} 36 | 37 | \item{allow_missing}{If \code{FALSE}, error if src is not an existing file or a 38 | valid URL. URLs are not checked if they work.} 39 | } 40 | \description{ 41 | \code{\link[=qto_fig]{qto_fig()}} creates Markdown formatting to insert a figure or image. Learn 42 | more about figure formatting options in the Quarto documentation: 43 | \url{https://quarto.org/docs/authoring/figures.html} Note that LaTeX output 44 | formats may not support external images: 45 | \url{https://tex.stackexchange.com/questions/5433/can-i-use-an-image-located-on-the-web-in-a-latex-document} 46 | } 47 | \examples{ 48 | qto_fig("image.jpeg", "Image caption", alt = "Image alt text") 49 | 50 | qto_fig("graphic.pdf", title = "Title of the graphic") 51 | 52 | } 53 | \seealso{ 54 | \code{\link[knitr:include_graphics]{knitr::include_graphics()}} 55 | 56 | Other span: 57 | \code{\link{qto_link}()}, 58 | \code{\link{qto_span}()} 59 | } 60 | \concept{span} 61 | -------------------------------------------------------------------------------- /man/qto_link.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/link.R 3 | \name{qto_link} 4 | \alias{qto_link} 5 | \title{Create a Markdown link} 6 | \usage{ 7 | qto_link(src, text = NULL, ..., allow_empty = FALSE, call = caller_env()) 8 | } 9 | \arguments{ 10 | \item{src}{Path or URL for link. Required.} 11 | 12 | \item{text}{Optional link text. If link text is not provided, a bare link, 13 | e.g. \verb{} is returned.} 14 | 15 | \item{...}{ 16 | Arguments passed on to \code{\link[=qto_attributes]{qto_attributes}} 17 | \describe{ 18 | \item{\code{id}}{Div or span identifier. If \code{id} does not start with \code{"#"}, the 19 | hash character is applied as a prefix.} 20 | \item{\code{class}}{Div or span class. If \code{class} does not start with \code{"."}, the 21 | period character is applied as a prefix.} 22 | \item{\code{css}}{If \code{{htmltools}} is installed, a list of css style attributes to 23 | pass to \code{\link[htmltools:css]{htmltools::css()}}.} 24 | \item{\code{.attributes}}{Optional list of attributes. If supplied, any attributes 25 | passed to \code{...} are ignored.} 26 | }} 27 | 28 | \item{allow_empty}{If \code{FALSE} (default), error if string is empty. If \code{TRUE}, 29 | allow input to be an empty string.} 30 | 31 | \item{call}{The execution environment of a currently 32 | running function, e.g. \code{caller_env()}. The function will be 33 | mentioned in error messages as the source of the error. See the 34 | \code{call} argument of \code{\link[rlang:abort]{abort()}} for more information.} 35 | } 36 | \description{ 37 | Create a Markdown link 38 | } 39 | \examples{ 40 | qto_link("https://quarto.org") 41 | 42 | qto_link("https://quarto.org", "Quarto") 43 | 44 | qto_link("https://quarto.org", "Quarto", class = "smaller") 45 | 46 | } 47 | \seealso{ 48 | Other span: 49 | \code{\link{qto_fig}()}, 50 | \code{\link{qto_span}()} 51 | } 52 | \concept{span} 53 | -------------------------------------------------------------------------------- /R/shortcodes.R: -------------------------------------------------------------------------------- 1 | #' Create a Quarto shortcode 2 | #' 3 | #' [qto_shortcode()] creates a shortcode based on an input string. 4 | #' 5 | #' @inheritParams combine 6 | #' @export 7 | qto_shortcode <- function(x, before = "{{< ", after = " >}}", ...) { 8 | qto_block(combine(x, before, after), ...) 9 | } 10 | 11 | #' Videos 12 | #' 13 | #' @rdname qto_shortcode 14 | #' @name qto_pagebreak 15 | #' @param src For [qto_video()], a URL with the video source. 16 | #' @examples 17 | #' qto_video("https://www.youtube.com/embed/wo9vZccmqwc") 18 | #' 19 | #' @export 20 | qto_video <- function(src) { 21 | check_src(x = src) 22 | qto_shortcode(paste0("video ", src)) 23 | } 24 | 25 | #' Page breaks 26 | #' 27 | #' @rdname qto_shortcode 28 | #' @name qto_pagebreak 29 | #' @examples 30 | #' qto_pagebreak() 31 | #' 32 | #' @export 33 | qto_pagebreak <- function() { 34 | qto_shortcode("pagebreak") 35 | } 36 | 37 | #' Keyboard shortcuts 38 | #' 39 | #' @rdname qto_shortcode 40 | #' @param ... For [qto_kbd()], a set of strings to combine with "-" or a named 41 | #' set of attributes where names are one of "mac", "win", or "linux". 42 | #' @name qto_kbd 43 | #' @examples 44 | #' qto_kbd("Shift", "Ctrl", "P") 45 | #' 46 | #' qto_kbd( 47 | #' mac = "Shift-Command-O", 48 | #' win = "Shift-Control-O", 49 | #' linux = "Shift-Ctrl-L" 50 | #' ) 51 | #' 52 | #' @export 53 | qto_kbd <- function(...) { 54 | shortcut <- dots_list(...) 55 | 56 | if (is_named(shortcut)) { 57 | shortcut <- lapply(shortcut, paste0, collapse = "-") 58 | shortcut <- paste0(names(shortcut), "=", shortcut, collapse = " ") 59 | } else { 60 | shortcut <- paste0(shortcut, collapse = "-") 61 | } 62 | 63 | qto_shortcode(paste0("kbd ", shortcut)) 64 | } 65 | -------------------------------------------------------------------------------- /man/map_qto.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/map.R 3 | \name{map_qto} 4 | \alias{map_qto} 5 | \title{Apply a function to each element of a vector and return Quarto block vector} 6 | \usage{ 7 | map_qto( 8 | .x, 9 | .f = NULL, 10 | ..., 11 | .type = c("block", "div", "callout", "heading"), 12 | .sep = "", 13 | .collapse = "", 14 | call = caller_env() 15 | ) 16 | } 17 | \arguments{ 18 | \item{.x}{An input vector.} 19 | 20 | \item{.f}{Optional function to apply to each element. If function does not 21 | return a "quarto_block" class object, the output is passed to \code{\link[=qto_block]{qto_block()}}} 22 | 23 | \item{...}{Additional parameters passed to function defined by \code{.f}.} 24 | 25 | \item{.type}{If .f is \code{NULL}, type is used to define the function applied to 26 | each element of the vector. Options include "block", "div", "callout", or 27 | "heading".} 28 | 29 | \item{.sep, .collapse}{Additional parameters passed to \code{\link[=qto_block]{qto_block()}} if .f 30 | does not return a quarto block class object. Ignored if .f does return a 31 | quarto block class object.} 32 | 33 | \item{call}{The execution environment of a currently 34 | running function, e.g. \code{caller_env()}. The function will be 35 | mentioned in error messages as the source of the error. See the 36 | \code{call} argument of \code{\link[rlang:abort]{abort()}} for more information.} 37 | } 38 | \description{ 39 | \code{\link[=map_qto]{map_qto()}} loops a list over a package function defined by .type or a custom 40 | function that returns a quarto block output. This function always returns a 41 | list of quarto block objects. 42 | } 43 | \examples{ 44 | qto_list <- map_qto( 45 | list("This is a note.", "And this is a note.", "And this is a note"), 46 | .type = "callout" 47 | ) 48 | 49 | qto_block(qto_list) 50 | 51 | } 52 | \seealso{ 53 | \code{\link[=pmap_qto]{pmap_qto()}}, \code{\link[purrr:map]{purrr::map()}} 54 | } 55 | -------------------------------------------------------------------------------- /tests/testthat/_snaps/with.md: -------------------------------------------------------------------------------- 1 | # with_*() output is as expected 2 | 3 | Code 4 | with_body_column("foo", .attributes = ".bar") 5 | Output 6 | 7 | ::: {.column-body .bar} 8 | foo 9 | ::: 10 | 11 | 12 | --- 13 | 14 | Code 15 | with_page_column("foo", .attributes = ".bar") 16 | Output 17 | 18 | ::: {.column-page .bar} 19 | foo 20 | ::: 21 | 22 | 23 | --- 24 | 25 | Code 26 | with_screen_inset_column("foo", .attributes = ".bar") 27 | Output 28 | 29 | ::: {.column-screen-inset .bar} 30 | foo 31 | ::: 32 | 33 | 34 | --- 35 | 36 | Code 37 | with_screen_column("foo", .attributes = ".bar") 38 | Output 39 | 40 | ::: {.column-screen .bar} 41 | foo 42 | ::: 43 | 44 | 45 | --- 46 | 47 | Code 48 | with_margin_column("foo", .attributes = ".bar") 49 | Output 50 | 51 | ::: {.column-margin .bar} 52 | foo 53 | ::: 54 | 55 | 56 | --- 57 | 58 | Code 59 | with_body_column("foo", .attributes = ".bar", extension = "left") 60 | Output 61 | 62 | ::: {.column-body-left .bar} 63 | foo 64 | ::: 65 | 66 | 67 | --- 68 | 69 | Code 70 | with_page_column("foo", .attributes = ".bar", extension = "right") 71 | Output 72 | 73 | ::: {.column-page-right .bar} 74 | foo 75 | ::: 76 | 77 | 78 | --- 79 | 80 | Code 81 | with_screen_inset_column("foo", .attributes = ".bar", extension = "shaded") 82 | Output 83 | 84 | ::: {.column-screen-inset-shaded .bar} 85 | foo 86 | ::: 87 | 88 | 89 | --- 90 | 91 | Code 92 | with_screen_column("foo", .attributes = ".bar", extension = "left") 93 | Output 94 | 95 | ::: {.column-screen-left .bar} 96 | foo 97 | ::: 98 | 99 | 100 | -------------------------------------------------------------------------------- /man/qto_dl.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/lists.R 3 | \name{qto_dl} 4 | \alias{qto_dl} 5 | \title{Create a definition list} 6 | \usage{ 7 | qto_dl( 8 | ..., 9 | .term = NULL, 10 | .definition = NULL, 11 | .list = NULL, 12 | .drop_na = FALSE, 13 | .sep = "\\n", 14 | .replacement = "" 15 | ) 16 | } 17 | \arguments{ 18 | \item{...}{Optional. Named argument where the argument name is a term and the 19 | value name is the text to display as a definition for the term. Ignored if 20 | \code{.list} or \code{.term} and \code{.definition} are supplied.} 21 | 22 | \item{.term, .definition}{If supplied, \code{.list} is set to a named list using 23 | terms as names and definitions as values. Ignored if \code{.list} is supplied.} 24 | 25 | \item{.list}{If \code{.list} is supplied, any values passed to \code{...} are ignored. 26 | One of \code{.list}, \code{.term} and \code{.definition}, or \code{...} must be supplied.} 27 | 28 | \item{.drop_na}{If \code{TRUE}, drop \code{NA} values from the supplied list.} 29 | 30 | \item{.sep}{Added separator string between terms and definitions. Defaults to 31 | \code{"\\n"} for wider list spacing. If set to \code{""}, the definition list uses a 32 | tighter spacing.} 33 | 34 | \item{.replacement}{If not \code{NULL}, replace NA values from the supplied list 35 | with the value of replacement.} 36 | } 37 | \description{ 38 | See the Quarto documentation for more information on lists: 39 | \url{https://quarto.org/docs/authoring/markdown-basics.html#lists} 40 | } 41 | \examples{ 42 | qto_dl( 43 | "Quarto" = "An open-source scientific and technical publishing system" 44 | ) 45 | 46 | qto_dl( 47 | .term = "Quarto", 48 | .definition = "An open-source scientific and technical publishing system" 49 | ) 50 | 51 | qto_dl( 52 | .list = list("Quarto" = "An open-source scientific and technical publishing system") 53 | ) 54 | 55 | } 56 | \seealso{ 57 | \itemize{ 58 | \item \code{\link[=qto_ol]{qto_ol()}} 59 | \item \code{\link[=qto_li]{qto_li()}} 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /man/qto_attributes.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/attributes.R 3 | \name{qto_attributes} 4 | \alias{qto_attributes} 5 | \title{Create an attribute string for a div or span} 6 | \usage{ 7 | qto_attributes( 8 | id = NULL, 9 | class = NULL, 10 | css = NULL, 11 | ..., 12 | .attributes = NULL, 13 | .output = "embrace", 14 | .drop_empty = TRUE, 15 | call = caller_env() 16 | ) 17 | } 18 | \arguments{ 19 | \item{id}{Div or span identifier. If \code{id} does not start with \code{"#"}, the 20 | hash character is applied as a prefix.} 21 | 22 | \item{class}{Div or span class. If \code{class} does not start with \code{"."}, the 23 | period character is applied as a prefix.} 24 | 25 | \item{css}{If \code{{htmltools}} is installed, a list of css style attributes to 26 | pass to \code{\link[htmltools:css]{htmltools::css()}}.} 27 | 28 | \item{...}{Optional named attributes.} 29 | 30 | \item{.attributes}{Optional list of attributes. If supplied, any attributes 31 | passed to \code{...} are ignored.} 32 | 33 | \item{.output}{Output type. If "embrace", the returned attributes are always 34 | enclosed in curly brackets, e.g. "{}" if no attributes are supplied. If 35 | "span", an empty string is returned if no attributes are provided.} 36 | 37 | \item{.drop_empty}{If \code{TRUE}, empty attributes are dropped.} 38 | 39 | \item{call}{The execution environment of a currently 40 | running function, e.g. \code{caller_env()}. The function will be 41 | mentioned in error messages as the source of the error. See the 42 | \code{call} argument of \code{\link[rlang:abort]{abort()}} for more information.} 43 | } 44 | \description{ 45 | \code{\link[=qto_attributes]{qto_attributes()}} creates an attribute string used by \code{\link[=qto_div]{qto_div()}}, 46 | \code{\link[=qto_span]{qto_span()}}, or \code{\link[=qto_fig_span]{qto_fig_span()}}. 47 | } 48 | \examples{ 49 | qto_attributes(id = "id", class = "class") 50 | 51 | qto_attributes(class = "class", key1 = "val", key2 = "val") 52 | 53 | qto_attributes(width = 4) 54 | 55 | } 56 | -------------------------------------------------------------------------------- /man/qto_span.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/span.R 3 | \name{qto_span} 4 | \alias{qto_span} 5 | \title{Create a Quarto span} 6 | \usage{ 7 | qto_span(text, ..., .attributes = NULL) 8 | } 9 | \arguments{ 10 | \item{text}{Text to wrap in span bracket.} 11 | 12 | \item{...}{ 13 | Arguments passed on to \code{\link[=qto_attributes]{qto_attributes}} 14 | \describe{ 15 | \item{\code{id}}{Div or span identifier. If \code{id} does not start with \code{"#"}, the 16 | hash character is applied as a prefix.} 17 | \item{\code{class}}{Div or span class. If \code{class} does not start with \code{"."}, the 18 | period character is applied as a prefix.} 19 | \item{\code{css}}{If \code{{htmltools}} is installed, a list of css style attributes to 20 | pass to \code{\link[htmltools:css]{htmltools::css()}}.} 21 | \item{\code{.output}}{Output type. If "embrace", the returned attributes are always 22 | enclosed in curly brackets, e.g. "{}" if no attributes are supplied. If 23 | "span", an empty string is returned if no attributes are provided.} 24 | \item{\code{.drop_empty}}{If \code{TRUE}, empty attributes are dropped.} 25 | \item{\code{call}}{The execution environment of a currently 26 | running function, e.g. \code{caller_env()}. The function will be 27 | mentioned in error messages as the source of the error. See the 28 | \code{call} argument of \code{\link[rlang:abort]{abort()}} for more information.} 29 | }} 30 | 31 | \item{.attributes}{Optional list of attributes. If supplied, any attributes 32 | passed to \code{...} are ignored.} 33 | } 34 | \description{ 35 | See the Quarto documentation for more on divs and spans: 36 | \url{https://quarto.org/docs/authoring/markdown-basics.html#divs-and-spans} 37 | } 38 | \examples{ 39 | 40 | qto_span("This is *some text*", class = "class", key = "value") 41 | 42 | qto_span("This is good", id = "id", class = "class", key1 = "val1", key2 = "val2") 43 | 44 | } 45 | \seealso{ 46 | Other span: 47 | \code{\link{qto_fig}()}, 48 | \code{\link{qto_link}()} 49 | } 50 | \concept{span} 51 | -------------------------------------------------------------------------------- /R/utilities.R: -------------------------------------------------------------------------------- 1 | #' Combine a string with text before and after 2 | #' 3 | #' @param x An input string. 4 | #' @param before,after Text to insert before and after the supplied string. 5 | #' @param allow_empty If `TRUE`, allow empty string ("") values as input. If 6 | #' `FALSE`, error on empty string inputs. 7 | #' @param allow_null If `TRUE`, allow `NULL` values as input (replaced with an 8 | #' empty string ""). If `FALSE`, error on `NULL` inputs. 9 | #' @keywords internal 10 | combine <- function(x, 11 | before = "", 12 | after = before, 13 | allow_empty = TRUE, 14 | allow_null = TRUE) { 15 | check_string(x, allow_empty = allow_empty, allow_null = allow_null) 16 | 17 | paste0(before, x %||% "", after) 18 | } 19 | 20 | #' @noRd 21 | embrace <- function(x, 22 | before = "{", 23 | after = "}", 24 | allow_empty = TRUE, 25 | allow_null = TRUE) { 26 | combine(x, before, after, allow_empty, allow_null) 27 | } 28 | 29 | #' @noRd 30 | bracket <- function(x, 31 | before = "[", 32 | after = "]", 33 | allow_empty = TRUE, 34 | allow_null = TRUE) { 35 | combine(x, before, after, allow_empty, allow_null) 36 | } 37 | 38 | #' @noRd 39 | parentheses <- function(x, 40 | before = "(", 41 | after = ")", 42 | allow_empty = TRUE, 43 | allow_null = TRUE) { 44 | combine(x, before, after, allow_empty, allow_null) 45 | } 46 | 47 | #' @noRd 48 | build_readme_qmd <- function(path = ".", quiet = TRUE, ...) { 49 | if (path == ".") { 50 | path <- file.path(getwd(), "README.qmd") 51 | } 52 | 53 | if (file.exists(path)) { 54 | check_installed("quarto") 55 | 56 | quarto::quarto_render( 57 | input = path, 58 | output_file = "README.md", 59 | quiet = quiet, 60 | ... 61 | ) 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /man/qto_src_span.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/link.R 3 | \name{qto_src_span} 4 | \alias{qto_src_span} 5 | \title{Simplified helper for \code{qto_link()}} 6 | \usage{ 7 | qto_src_span( 8 | src, 9 | text = NULL, 10 | .before = "", 11 | ..., 12 | allow_missing = FALSE, 13 | allow_empty = FALSE, 14 | call = caller_env() 15 | ) 16 | } 17 | \arguments{ 18 | \item{src}{Image source or URL.} 19 | 20 | \item{text}{Caption or link text.} 21 | 22 | \item{...}{ 23 | Arguments passed on to \code{\link[=qto_attributes]{qto_attributes}} 24 | \describe{ 25 | \item{\code{id}}{Div or span identifier. If \code{id} does not start with \code{"#"}, the 26 | hash character is applied as a prefix.} 27 | \item{\code{class}}{Div or span class. If \code{class} does not start with \code{"."}, the 28 | period character is applied as a prefix.} 29 | \item{\code{css}}{If \code{{htmltools}} is installed, a list of css style attributes to 30 | pass to \code{\link[htmltools:css]{htmltools::css()}}.} 31 | \item{\code{.attributes}}{Optional list of attributes. If supplied, any attributes 32 | passed to \code{...} are ignored.} 33 | \item{\code{.output}}{Output type. If "embrace", the returned attributes are always 34 | enclosed in curly brackets, e.g. "{}" if no attributes are supplied. If 35 | "span", an empty string is returned if no attributes are provided.} 36 | \item{\code{.drop_empty}}{If \code{TRUE}, empty attributes are dropped.} 37 | }} 38 | 39 | \item{allow_missing}{If \code{FALSE} (default), error if string is not a URL and 40 | not a valid web path. If \code{TRUE}, allow input to be a path to a non-existent 41 | file.} 42 | 43 | \item{allow_empty}{If \code{FALSE} (default), error if string is empty. If \code{TRUE}, 44 | allow input to be an empty string.} 45 | 46 | \item{call}{The execution environment of a currently 47 | running function, e.g. \code{caller_env()}. The function will be 48 | mentioned in error messages as the source of the error. See the 49 | \code{call} argument of \code{\link[rlang:abort]{abort()}} for more information.} 50 | } 51 | \description{ 52 | Also used by \code{\link[=qto_fig_span]{qto_fig_span()}} 53 | } 54 | \keyword{internal} 55 | -------------------------------------------------------------------------------- /R/link.R: -------------------------------------------------------------------------------- 1 | #' Create a Markdown link 2 | #' 3 | #' @param src Path or URL for link. Required. 4 | #' @param text Optional link text. If link text is not provided, a bare link, 5 | #' e.g. `` is returned. 6 | #' @inheritParams check_src 7 | #' @inheritDotParams qto_attributes -.output -.drop_empty 8 | #' @examples 9 | #' qto_link("https://quarto.org") 10 | #' 11 | #' qto_link("https://quarto.org", "Quarto") 12 | #' 13 | #' qto_link("https://quarto.org", "Quarto", class = "smaller") 14 | #' 15 | #' @family span 16 | #' @export 17 | qto_link <- function(src, 18 | text = NULL, 19 | ..., 20 | allow_empty = FALSE, 21 | call = caller_env()) { 22 | check_src( 23 | src, 24 | allow_empty = allow_empty, 25 | call = call 26 | ) 27 | 28 | if (is.null(text)) { 29 | link <- qto_block( 30 | combine(src, before = "<", after = ">"), 31 | qto_attributes( 32 | ..., 33 | .output = "span", 34 | call = call 35 | ), 36 | call = call 37 | ) 38 | 39 | return(link) 40 | } 41 | 42 | qto_src_span( 43 | src = src, 44 | text = text, 45 | ..., 46 | allow_empty = allow_empty, 47 | call = call 48 | ) 49 | } 50 | 51 | 52 | #' Simplified helper for `qto_link()` 53 | #' 54 | #' Also used by [qto_fig_span()] 55 | #' 56 | #' @param src Image source or URL. 57 | #' @param text Caption or link text. 58 | #' @inheritParams check_src 59 | #' @inheritDotParams qto_attributes 60 | #' @keywords internal 61 | qto_src_span <- function(src, 62 | text = NULL, 63 | .before = "", 64 | ..., 65 | allow_missing = FALSE, 66 | allow_empty = FALSE, 67 | call = caller_env()) { 68 | qto_block( 69 | .before, 70 | bracket(text), 71 | parentheses(src), 72 | qto_attributes( 73 | ..., 74 | .output = "span", 75 | call = call 76 | ), 77 | call = call 78 | ) 79 | } 80 | -------------------------------------------------------------------------------- /man/pmap_qto.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/map.R 3 | \name{pmap_qto} 4 | \alias{pmap_qto} 5 | \title{Map over multiple inputs simultaenously and return Quarto block vector} 6 | \usage{ 7 | pmap_qto( 8 | .l, 9 | .f = NULL, 10 | ..., 11 | .type = c("block", "div", "callout", "heading"), 12 | .sep = "", 13 | .collapse = "", 14 | call = caller_env() 15 | ) 16 | } 17 | \arguments{ 18 | \item{.l}{An input vector.} 19 | 20 | \item{.f}{Optional function to apply to each element. If function does not 21 | return a "quarto_block" class object, the output is passed to \code{\link[=qto_block]{qto_block()}}} 22 | 23 | \item{...}{Additional parameters passed to function defined by \code{.f}.} 24 | 25 | \item{.type}{If .f is \code{NULL}, type is used to define the function applied to 26 | each element of the vector. Options include "block", "div", "callout", or 27 | "heading".} 28 | 29 | \item{.sep, .collapse}{Additional parameters passed to \code{\link[=qto_block]{qto_block()}} if .f 30 | does not return a quarto block class object. Ignored if .f does return a 31 | quarto block class object.} 32 | 33 | \item{call}{The execution environment of a currently 34 | running function, e.g. \code{caller_env()}. The function will be 35 | mentioned in error messages as the source of the error. See the 36 | \code{call} argument of \code{\link[rlang:abort]{abort()}} for more information.} 37 | } 38 | \description{ 39 | \code{\link[=pmap_qto]{pmap_qto()}} loops a list of vectors over a package function defined by .type or a custom 40 | function that returns a quarto block output. This function always returns a 41 | list of quarto block objects. 42 | } 43 | \examples{ 44 | qto_list <- pmap_qto( 45 | list( 46 | list("Answer:", "Answer:", "Answer:"), 47 | list("Yes", "No", "Yes") 48 | ) 49 | ) 50 | qto_block(qto_list) 51 | 52 | qto_list <- pmap_qto( 53 | mtcars[seq(3L), seq(3L)], 54 | function(mpg, cyl, disp) { 55 | qto_li( 56 | .list = list( 57 | sprintf("mpg is: \%s", mpg), 58 | sprintf("cyl is: \%s", cyl), 59 | sprintf("disp is: \%s", disp) 60 | ) 61 | ) 62 | } 63 | ) 64 | qto_block(qto_list) 65 | 66 | } 67 | \seealso{ 68 | \code{\link[=map_qto]{map_qto()}}, \code{\link[purrr:pmap]{purrr::pmap()}} 69 | } 70 | -------------------------------------------------------------------------------- /man/qto_div.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/block.R 3 | \name{qto_div} 4 | \alias{qto_div} 5 | \title{Create a Quarto div with optional classes, attributes, and other identifiers} 6 | \usage{ 7 | qto_div( 8 | ..., 9 | id = NULL, 10 | class = NULL, 11 | css = NULL, 12 | .attributes = NULL, 13 | .content = NULL, 14 | collapse = "", 15 | drop_empty = TRUE, 16 | drop_na = TRUE, 17 | call = caller_env() 18 | ) 19 | } 20 | \arguments{ 21 | \item{...}{Optional named attributes.} 22 | 23 | \item{id}{Div or span identifier. If \code{id} does not start with \code{"#"}, the 24 | hash character is applied as a prefix.} 25 | 26 | \item{class}{Div or span class. If \code{class} does not start with \code{"."}, the 27 | period character is applied as a prefix.} 28 | 29 | \item{css}{If \code{{htmltools}} is installed, a list of css style attributes to 30 | pass to \code{\link[htmltools:css]{htmltools::css()}}.} 31 | 32 | \item{.attributes}{Optional list of attributes. If supplied, any attributes 33 | passed to \code{...} are ignored.} 34 | 35 | \item{.content}{If \code{.content} is supplied, any values passed to \code{...} are 36 | ignored. If \code{.content} is \code{NULL}, it is set as all values passed to \code{...}.} 37 | 38 | \item{collapse}{Passed to \code{\link[base:paste]{base::paste0()}} with \code{.content.}} 39 | 40 | \item{drop_empty}{If \code{TRUE}, drop empty values from \code{.content} or \code{...}} 41 | 42 | \item{drop_na}{If \code{TRUE}, drop \code{NA} values from \code{.content} or \code{...}} 43 | 44 | \item{call}{The execution environment of a currently 45 | running function, e.g. \code{caller_env()}. The function will be 46 | mentioned in error messages as the source of the error. See the 47 | \code{call} argument of \code{\link[rlang:abort]{abort()}} for more information.} 48 | } 49 | \description{ 50 | \url{https://quarto.org/docs/authoring/markdown-basics.html#divs-and-spans} 51 | } 52 | \examples{ 53 | 54 | # div with an class 55 | qto_div( 56 | "This content can be styled with a border", 57 | class = "border" 58 | ) 59 | 60 | # Nested div 61 | qto_div( 62 | qto_div("Here is a warning.", class = "warning"), 63 | "More content.", 64 | id = "special", 65 | class = "sidebar" 66 | ) 67 | 68 | } 69 | \seealso{ 70 | \itemize{ 71 | \item \code{\link[=qto_callout]{qto_callout()}} 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /R/callout.R: -------------------------------------------------------------------------------- 1 | #' Create a Quarto callout block 2 | #' 3 | #' Callouts are an excellent way to draw extra attention to certain concepts, or 4 | #' to more clearly indicate that certain content is supplemental or applicable 5 | #' to only some scenarios. 6 | #' 7 | #' @param type Callout type. One of "note", "tip", "warning", "caution", or 8 | #' "important". 9 | #' @param appearance Callout appearance. One of "default", "simple", "minimal". 10 | #' 11 | #' @param collapse If `TRUE`, create a folded callout. 12 | #' @param icon If `FALSE`, create a callout with an icon. 13 | #' @param title Title attribute. Works the same as included a level 2 heading 14 | #' before the body of the callout contents. 15 | #' @inheritParams qto_div 16 | #' @inheritDotParams qto_div 17 | #' @examples 18 | #' 19 | #' qto_callout( 20 | #' "Callouts provide a simple way to attract attention, for example, to this warning.", 21 | #' type = "warning" 22 | #' ) 23 | #' 24 | #' qto_callout( 25 | #' "This is an example of a callout with a title.", 26 | #' type = "tip", 27 | #' title = "Tip with a title" 28 | #' ) 29 | #' 30 | #' @export 31 | qto_callout <- function(..., 32 | type = c("note", "tip", "warning", "caution", "important"), 33 | collapse = NULL, 34 | appearance = NULL, 35 | icon = NULL, 36 | title = NULL, 37 | id = NULL, 38 | class = NULL, 39 | .attributes = NULL, 40 | call = caller_env()) { 41 | type <- arg_match(type) 42 | class <- paste0("callout-", type) 43 | 44 | if (!is.null(appearance)) { 45 | appearance <- arg_match0( 46 | appearance, 47 | c("default", "simple", "minimal") 48 | ) 49 | } 50 | 51 | .attributes <- c( 52 | list( 53 | "collapse" = collapse, 54 | "appearance" = appearance, 55 | "icon" = icon 56 | ), 57 | as.list(.attributes), 58 | list( 59 | "title" = title 60 | ) 61 | ) 62 | 63 | .attributes <- list_drop_empty(.attributes) 64 | 65 | qto_div( 66 | ..., 67 | class = class, 68 | id = id, 69 | .attributes = .attributes, 70 | call = call 71 | ) 72 | } 73 | -------------------------------------------------------------------------------- /tests/testthat/test_map.R: -------------------------------------------------------------------------------- 1 | check_types <- function(lst) { 2 | res <- vapply(lst, function(x) { 3 | "quarto_block" %in% class(x) 4 | }, logical(1L)) 5 | all(res) 6 | } 7 | 8 | test_that("resolve_mapping_function works", { 9 | expect_type(resolve_mapping_function(f = ~ .x + 1L), "closure") 10 | 11 | block_fn <- resolve_mapping_function(type = "block") 12 | expect_identical( 13 | block_fn("Hello", c("world", "!"), sep = " ", collapse = " "), 14 | qto_block("Hello", c("world", "!"), sep = " ", collapse = " ") 15 | ) 16 | 17 | div_fn <- resolve_mapping_function(type = "div") 18 | expect_identical( 19 | div_fn("foo", "bar", "baz", collapse = " "), 20 | qto_div("foo", "bar", "baz", collapse = " ") 21 | ) 22 | 23 | callout_fn <- resolve_mapping_function(type = "callout") 24 | expect_identical( 25 | callout_fn("foo", "baz", collapse = TRUE), 26 | qto_callout("foo", "baz", collapse = TRUE) 27 | ) 28 | 29 | heading_fn <- resolve_mapping_function(type = "heading") 30 | expect_identical( 31 | heading_fn("foo", "baz", sep = " ", collapse = " "), 32 | qto_heading("foo", "baz", sep = " ", collapse = " ") 33 | ) 34 | 35 | }) 36 | 37 | 38 | test_that("map_qto works", { 39 | qto_list <- map_qto( 40 | list("This is a note.", "And this is a note.", "And this is a note"), 41 | .type = "callout" 42 | ) 43 | expect_length(qto_list, 3L) 44 | expect_true(check_types(qto_list)) 45 | expect_snapshot(qto_list) 46 | 47 | 48 | qto_list <- map_qto( 49 | list( 50 | c("foo", "bar", "baz"), 51 | c("a", "b", "c") 52 | ), 53 | .f = function(x) x, 54 | .collapse = " " 55 | ) 56 | expect_length(qto_list, 2L) 57 | expect_true(check_types(qto_list)) 58 | expect_snapshot(qto_list) 59 | }) 60 | 61 | test_that("pmap_qto works", { 62 | qto_list <- pmap_qto( 63 | list( 64 | list("Answer: ", "Answer: ", "Answer: "), 65 | list("Yes", "No", "Yes") 66 | ) 67 | ) 68 | expect_length(qto_list, 3L) 69 | expect_true(check_types(qto_list)) 70 | expect_snapshot(qto_list) 71 | 72 | 73 | qto_list <- pmap_qto( 74 | mtcars[seq(3L), seq(3L)], 75 | function(mpg, cyl, disp) { 76 | qto_li( 77 | .list = list( 78 | sprintf("mpg is: %s", mpg), 79 | sprintf("cyl is: %s", cyl), 80 | sprintf("disp is: %s", disp) 81 | ) 82 | ) 83 | } 84 | ) 85 | expect_length(qto_list, 3L) 86 | expect_true(check_types(qto_list)) 87 | expect_snapshot(qto_list) 88 | }) 89 | -------------------------------------------------------------------------------- /man/page-layout.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/with.R 3 | \name{with_body_column} 4 | \alias{with_body_column} 5 | \alias{with_page_column} 6 | \alias{with_screen_inset_column} 7 | \alias{with_screen_column} 8 | \alias{with_margin_column} 9 | \title{Temporarily modify page layout} 10 | \usage{ 11 | with_body_column( 12 | ..., 13 | outset = FALSE, 14 | id = NULL, 15 | class = NULL, 16 | extension = NULL, 17 | .attributes = NULL, 18 | call = caller_env() 19 | ) 20 | 21 | with_page_column( 22 | ..., 23 | id = NULL, 24 | class = NULL, 25 | extension = NULL, 26 | .attributes = NULL, 27 | call = caller_env() 28 | ) 29 | 30 | with_screen_inset_column( 31 | ..., 32 | id = NULL, 33 | class = NULL, 34 | extension = NULL, 35 | .attributes = NULL, 36 | call = caller_env() 37 | ) 38 | 39 | with_screen_column( 40 | ..., 41 | id = NULL, 42 | class = NULL, 43 | extension = NULL, 44 | .attributes = NULL, 45 | call = caller_env() 46 | ) 47 | 48 | with_margin_column(..., id = NULL, .attributes = NULL, call = caller_env()) 49 | } 50 | \arguments{ 51 | \item{...}{ 52 | Arguments passed on to \code{\link[=qto_div]{qto_div}} 53 | \describe{ 54 | \item{\code{.content}}{If \code{.content} is supplied, any values passed to \code{...} are 55 | ignored. If \code{.content} is \code{NULL}, it is set as all values passed to \code{...}.} 56 | \item{\code{collapse}}{Passed to \code{\link[base:paste]{base::paste0()}} with \code{.content.}} 57 | \item{\code{drop_empty}}{If \code{TRUE}, drop empty values from \code{.content} or \code{...}} 58 | \item{\code{drop_na}}{If \code{TRUE}, drop \code{NA} values from \code{.content} or \code{...}} 59 | \item{\code{css}}{If \code{{htmltools}} is installed, a list of css style attributes to 60 | pass to \code{\link[htmltools:css]{htmltools::css()}}.} 61 | }} 62 | 63 | \item{outset}{If \code{TRUE}, the ouset affix is applied to the column class} 64 | 65 | \item{id}{Div or span identifier. If \code{id} does not start with \code{"#"}, the 66 | hash character is applied as a prefix.} 67 | 68 | \item{class}{Div or span class. If \code{class} does not start with \code{"."}, the 69 | period character is applied as a prefix.} 70 | 71 | \item{extension}{affix to apply to column class} 72 | 73 | \item{.attributes}{Optional list of attributes. If supplied, any attributes 74 | passed to \code{...} are ignored.} 75 | 76 | \item{call}{The execution environment of a currently 77 | running function, e.g. \code{caller_env()}. The function will be 78 | mentioned in error messages as the source of the error. See the 79 | \code{call} argument of \code{\link[rlang:abort]{abort()}} for more information.} 80 | } 81 | \value{ 82 | character vector of length 1 83 | } 84 | \description{ 85 | Create a div block that modifies the current quarto layout column temporarily. 86 | 87 | See \href{https://quarto.org/docs/authoring/article-layout.html#available-columns}{the Quarto documentation for a full list of available extension options}. 88 | } 89 | \examples{ 90 | with_body_column("Hello world!") 91 | } 92 | -------------------------------------------------------------------------------- /man/qto_callout.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/callout.R 3 | \name{qto_callout} 4 | \alias{qto_callout} 5 | \title{Create a Quarto callout block} 6 | \usage{ 7 | qto_callout( 8 | ..., 9 | type = c("note", "tip", "warning", "caution", "important"), 10 | collapse = NULL, 11 | appearance = NULL, 12 | icon = NULL, 13 | title = NULL, 14 | id = NULL, 15 | class = NULL, 16 | .attributes = NULL, 17 | call = caller_env() 18 | ) 19 | } 20 | \arguments{ 21 | \item{...}{ 22 | Arguments passed on to \code{\link[=qto_div]{qto_div}} 23 | \describe{ 24 | \item{\code{.content}}{If \code{.content} is supplied, any values passed to \code{...} are 25 | ignored. If \code{.content} is \code{NULL}, it is set as all values passed to \code{...}.} 26 | \item{\code{drop_empty}}{If \code{TRUE}, drop empty values from \code{.content} or \code{...}} 27 | \item{\code{drop_na}}{If \code{TRUE}, drop \code{NA} values from \code{.content} or \code{...}} 28 | \item{\code{css}}{If \code{{htmltools}} is installed, a list of css style attributes to 29 | pass to \code{\link[htmltools:css]{htmltools::css()}}.} 30 | }} 31 | 32 | \item{type}{Callout type. One of "note", "tip", "warning", "caution", or 33 | "important". \url{https://quarto.org/docs/authoring/callouts.html#callout-types}} 34 | 35 | \item{collapse}{If \code{TRUE}, create a folded callout. \url{https://quarto.org/docs/authoring/callouts.html#collapse}} 36 | 37 | \item{appearance}{Callout appearance. One of "default", "simple", "minimal". 38 | \url{https://quarto.org/docs/authoring/callouts.html#appearance}} 39 | 40 | \item{icon}{If \code{FALSE}, create a callout with an icon. \url{https://quarto.org/docs/authoring/callouts.html#icons}} 41 | 42 | \item{title}{Title attribute. Works the same as included a level 2 heading 43 | before the body of the callout contents.} 44 | 45 | \item{id}{Div or span identifier. If \code{id} does not start with \code{"#"}, the 46 | hash character is applied as a prefix.} 47 | 48 | \item{class}{Div or span class. If \code{class} does not start with \code{"."}, the 49 | period character is applied as a prefix.} 50 | 51 | \item{.attributes}{Optional list of attributes. If supplied, any attributes 52 | passed to \code{...} are ignored.} 53 | 54 | \item{call}{The execution environment of a currently 55 | running function, e.g. \code{caller_env()}. The function will be 56 | mentioned in error messages as the source of the error. See the 57 | \code{call} argument of \code{\link[rlang:abort]{abort()}} for more information.} 58 | } 59 | \description{ 60 | Callouts are an excellent way to draw extra attention to certain concepts, or 61 | to more clearly indicate that certain content is supplemental or applicable 62 | to only some scenarios. \url{https://quarto.org/docs/authoring/callouts.html} 63 | } 64 | \examples{ 65 | 66 | qto_callout( 67 | "Callouts provide a simple way to attract attention, for example, to this warning.", 68 | type = "warning" 69 | ) 70 | 71 | qto_callout( 72 | "This is an example of a callout with a title.", 73 | type = "tip", 74 | title = "Tip with a title" 75 | ) 76 | 77 | } 78 | -------------------------------------------------------------------------------- /R/figures.R: -------------------------------------------------------------------------------- 1 | #' Insert an figure or image 2 | #' 3 | #' [qto_fig()] creates Markdown formatting to insert a figure or image. Learn 4 | #' more about figure formatting options in the Quarto documentation: 5 | #' Note that LaTeX output 6 | #' formats may not support external images: 7 | #' 8 | #' 9 | #' @param src Figure file path or URL. 10 | #' @param caption Figure caption. 11 | #' @param alt Figure alt text. 12 | #' @param reference Figure reference. "fig-" prefix is optional. 13 | #' @param align,pos,width,height Figure alignment, position, width, and height. 14 | #' @param title Figure title. 15 | #' @param allow_missing If `FALSE`, error if src is not an existing file or a 16 | #' valid URL. URLs are not checked if they work. 17 | #' @param ... Additional attributes passed to attributes argument of 18 | #' [qto_fig_span()]. 19 | #' @examples 20 | #' qto_fig("image.jpeg", "Image caption", alt = "Image alt text") 21 | #' 22 | #' qto_fig("graphic.pdf", title = "Title of the graphic") 23 | #' 24 | #' @seealso [knitr::include_graphics()] 25 | #' @family span 26 | #' @export 27 | qto_fig <- function(src, 28 | caption = NULL, 29 | alt = NULL, 30 | reference = NULL, 31 | align = NULL, 32 | pos = NULL, 33 | width = NULL, 34 | height = NULL, 35 | title = NULL, 36 | ..., 37 | allow_missing = TRUE) { 38 | check_src(src, allow_missing = allow_missing) 39 | 40 | if (is_string(reference)) { 41 | ref_prefix <- "#" 42 | 43 | if (!grepl("^fig-", reference)) { 44 | ref_prefix <- "#fig-" 45 | } 46 | 47 | reference <- paste0(ref_prefix, reference) 48 | } 49 | 50 | 51 | if (is_string(title)) { 52 | src <- paste0(src, ' "', title, '"') 53 | } 54 | 55 | qto_fig_span( 56 | src = src, 57 | caption = caption, 58 | id = reference, 59 | .attributes = c( 60 | list( 61 | "fig-alt" = alt, 62 | "fig-align" = align, 63 | "fig-pos" = pos, 64 | "width" = width, 65 | "height" = height 66 | ), 67 | dots_list(...) 68 | ) 69 | ) 70 | } 71 | 72 | #' Simplified helper for qto_fig() 73 | #' 74 | #' @param src Image source. Either a file path or URL. 75 | #' @param caption Caption text 76 | #' @inheritParams qto_src_span 77 | #' @inheritDotParams qto_src_span 78 | #' @keywords internal 79 | qto_fig_span <- function(src, 80 | caption = NULL, 81 | ..., 82 | allow_missing = TRUE, 83 | allow_empty = FALSE, 84 | call = caller_env()) { 85 | check_src( 86 | src, 87 | allow_missing = allow_missing, 88 | allow_empty = allow_empty, 89 | call = call 90 | ) 91 | 92 | qto_src_span( 93 | .before = "!", 94 | text = caption, 95 | src = src, 96 | ..., 97 | allow_missing = allow_missing, 98 | allow_empty = allow_empty, 99 | call = call 100 | ) 101 | } 102 | -------------------------------------------------------------------------------- /README.qmd: -------------------------------------------------------------------------------- 1 | --- 2 | format: gfm 3 | --- 4 | 5 | 6 | 7 | # quartools 8 | 9 | 10 | [![Lifecycle: experimental](https://img.shields.io/badge/lifecycle-experimental-orange.svg)](https://lifecycle.r-lib.org/articles/stages.html#experimental) 11 | [![Codecov test coverage](https://codecov.io/gh/ElianHugh/quartools/branch/main/graph/badge.svg)](https://app.codecov.io/gh/ElianHugh/quartools?branch=main) 12 | 13 | 14 | _quartools_ allows for the creation of quarto-compliant markdown via R function calls. As _quartools_ generates quarto-compliant markdown, and _not_ HTML tags, the content will work on any quarto output format. 15 | 16 | 17 | ## Why quartools? 18 | 19 | At work, I ran into an issue where I was generating hundreds of parameterised reports that would require parts of the report to be dynamically populated. I found myself leaning on R for programmatic markup creation, which meant that I could have one master document that I worked on. My prototype version (in other words, functions I threw together for work) required a lot of constant chunk configuration, and wasn't particularly user-friendly nor elegant. _quartools_ is a more streamlined version of my prototype, with the added benefit of it requiring little to no setup on the end user's part. 20 | 21 | 22 | ## Installation 23 | 24 | ### Release build 25 | 26 | ``` r 27 | install.packages('quartools', repos = 'https://elianhugh.r-universe.dev') 28 | ``` 29 | 30 | ### Development build 31 | 32 | ``` r 33 | # install.packages("devtools") 34 | devtools::install_github("ElianHugh/quartools") 35 | ``` 36 | 37 | ## Example 38 | 39 | ```{r example} 40 | library(quartools) 41 | ``` 42 | 43 | ### Basic usage 44 | 45 | The simplest way to create a div block element with quartools is via the `qto_div` function. Note that the chunk configuration of `results: asis` is not necessary. 46 | 47 | ```{r} 48 | div_example <- qto_div( 49 | "It is also possible to supply attributes to the div block element via the `id`, `class`, and `.attributes` parameters.", 50 | id = "qto-div-example" 51 | ) 52 | 53 | print(div_example) 54 | ``` 55 | 56 | The `qto_callout()` function creates a callout styled div: 57 | 58 | ```{r} 59 | callout_example <- qto_callout( 60 | "Callouts provide a simple way to attract attention, for example, to this warning.", 61 | type = "warning" 62 | ) 63 | 64 | print(callout_example) 65 | ``` 66 | Other simple functions include `qto_heading` or `qto_definition_list`: 67 | 68 | ```{r} 69 | heading_example <- qto_heading("Heading 1") 70 | 71 | print(heading_example) 72 | 73 | dl_example <- qto_dl("Term" = "Definition") 74 | 75 | print(dl_example) 76 | ``` 77 | 78 | You can also use `qto_fig()` to embed images: 79 | 80 | ```{r} 81 | fig_example <- qto_fig( 82 | "https://quarto.org/quarto.png", 83 | "Quarto logo" 84 | ) 85 | 86 | print(fig_example) 87 | ``` 88 | 89 | 90 | ### Using loops to create Quarto Markdown 91 | 92 | We can leverage the `apply` family of functions ability to loop over list elements to simplify creating many divs at once. 93 | 94 | `map_qto()` allows users to set the function using the `.type` parameter: 95 | 96 | ```{r} 97 | qto_list <- map_qto(list("This is a note.", "And this is a note.", "And this is a note"), .type = "callout") 98 | 99 | print(qto_list) 100 | ``` 101 | 102 | 103 | -------------------------------------------------------------------------------- /R/block.R: -------------------------------------------------------------------------------- 1 | #' Create a block of text to render as Markdown text in Quarto 2 | #' 3 | #' [qto_block()] passes the input to [paste()] and assigns the classes 4 | #' "knit_asis" and "quarto_block". 5 | #' 6 | #' @param ... dots to convert to character vector 7 | #' @inheritParams base::paste 8 | #' @inheritParams rlang::args_error_context 9 | #' @examples 10 | #' qto_block("Hello world!") 11 | #' 12 | #' qto_block("Hello", "world!", sep = " ") 13 | #' 14 | #' qto_block("- ", LETTERS[1:4], collapse = "\n") 15 | #' 16 | #' @return character vector of length 1 17 | #' @seealso 18 | #' - [knitr::asis_output()] 19 | #' 20 | #' @export 21 | qto_block <- function(..., sep = "", collapse = "", call = caller_env()) { 22 | check_dots_unnamed(call = call) 23 | structure( 24 | paste(..., sep = sep, collapse = collapse), 25 | class = c("knit_asis", "quarto_block") 26 | ) 27 | } 28 | 29 | #' Create a Quarto div with optional classes, attributes, and other identifiers 30 | #' 31 | #' 32 | #' 33 | #' @inheritParams qto_attributes 34 | #' @param .content If `.content` is supplied, any values passed to `...` are 35 | #' ignored. If `.content` is `NULL`, it is set as all values passed to `...`. 36 | #' @param collapse Passed to [base::paste0()] with `.content.` 37 | #' @param drop_empty If `TRUE`, drop empty values from `.content` or `...` 38 | #' @param drop_na If `TRUE`, drop `NA` values from `.content` or `...` 39 | #' @inheritParams qto_block 40 | #' @seealso 41 | #' - [qto_callout()] 42 | #' @examples 43 | #' 44 | #' # div with an class 45 | #' qto_div( 46 | #' "This content can be styled with a border", 47 | #' class = "border" 48 | #' ) 49 | #' 50 | #' # Nested div 51 | #' qto_div( 52 | #' qto_div("Here is a warning.", class = "warning"), 53 | #' "More content.", 54 | #' id = "special", 55 | #' class = "sidebar" 56 | #' ) 57 | #' 58 | #' @export 59 | qto_div <- function(..., 60 | id = NULL, 61 | class = NULL, 62 | css = NULL, 63 | .attributes = NULL, 64 | .content = NULL, 65 | collapse = "", 66 | drop_empty = TRUE, 67 | drop_na = TRUE, 68 | call = caller_env()) { 69 | check_dots_unnamed() 70 | 71 | .content <- .content %||% dots_list(...) 72 | 73 | if (drop_empty) { 74 | .content <- list_drop_empty(.content) 75 | } 76 | 77 | if (drop_na) { 78 | .content <- list_drop_na(.content) 79 | } 80 | 81 | .attributes <- qto_attributes( 82 | id = id, 83 | class = class, 84 | css = css, 85 | .attributes = .attributes 86 | ) 87 | 88 | qto_block( 89 | qto_fence(.attributes = .attributes), 90 | paste0(.content, collapse = collapse), 91 | qto_fence(), 92 | call = call 93 | ) 94 | } 95 | 96 | #' Create a fence for a div or code block 97 | #' 98 | #' @param fence If numeric, `fence` must be a minimum of 3 and sets the number 99 | #' of times the standard fence character ":" should be repeated. If character, 100 | #' `fence` is used as is. 101 | #' @noRd 102 | qto_fence <- function(fence = ":::", .attributes = NULL, .sep = " ") { 103 | if (is.numeric(fence)) { 104 | stopifnot(fence > 2L) 105 | fence <- strrep(":", fence) 106 | } 107 | 108 | paste0( 109 | "\n", fence, .sep, .attributes %||% "", "\n" 110 | ) 111 | } 112 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | # quartools 5 | 6 | 7 | 8 | [![Lifecycle: 9 | experimental](https://img.shields.io/badge/lifecycle-experimental-orange.svg)](https://lifecycle.r-lib.org/articles/stages.html#experimental) 10 | [![Codecov test 11 | coverage](https://codecov.io/gh/ElianHugh/quartools/branch/main/graph/badge.svg)](https://app.codecov.io/gh/ElianHugh/quartools?branch=main) 12 | 13 | 14 | *quartools* allows for the creation of quarto-compliant markdown via R 15 | function calls. As *quartools* generates quarto-compliant markdown, and 16 | *not* HTML tags, the content will work on any quarto output format. 17 | 18 | ## Why quartools? 19 | 20 | At work, I ran into an issue where I was generating hundreds of 21 | parameterised reports that would require parts of the report to be 22 | dynamically populated. I found myself leaning on R for programmatic 23 | markup creation, which meant that I could have one master document that 24 | I worked on. My prototype version (in other words, functions I threw 25 | together for work) required a lot of constant chunk configuration, and 26 | wasn’t particularly user-friendly nor elegant. *quartools* is a more 27 | streamlined version of my prototype, with the added benefit of it 28 | requiring little to no setup on the end user’s part. 29 | 30 | ## Installation 31 | 32 | ### Release build 33 | 34 | ``` r 35 | install.packages('quartools', repos = 'https://elianhugh.r-universe.dev') 36 | ``` 37 | 38 | ### Development build 39 | 40 | ``` r 41 | # install.packages("devtools") 42 | devtools::install_github("ElianHugh/quartools") 43 | ``` 44 | 45 | ## Example 46 | 47 | ``` r 48 | library(quartools) 49 | ``` 50 | 51 | ### Basic usage 52 | 53 | The simplest way to create a div block element with quartools is via the 54 | `qto_div` function. Note that the chunk configuration of `results: asis` 55 | is not necessary. 56 | 57 | ``` r 58 | div_example <- qto_div( 59 | "It is also possible to supply attributes to the div block element via the `id`, `class`, and `.attributes` parameters.", 60 | id = "qto-div-example" 61 | ) 62 | 63 | print(div_example) 64 | ``` 65 | 66 | 67 | ::: {#qto-div-example} 68 | It is also possible to supply attributes to the div block element via the `id`, `class`, and `.attributes` parameters. 69 | ::: 70 | 71 | The `qto_callout()` function creates a callout styled div: 72 | 73 | ``` r 74 | callout_example <- qto_callout( 75 | "Callouts provide a simple way to attract attention, for example, to this warning.", 76 | type = "warning" 77 | ) 78 | 79 | print(callout_example) 80 | ``` 81 | 82 | 83 | ::: {.callout-warning} 84 | Callouts provide a simple way to attract attention, for example, to this warning. 85 | ::: 86 | 87 | Other simple functions include `qto_heading` or `qto_definition_list`: 88 | 89 | ``` r 90 | heading_example <- qto_heading("Heading 1") 91 | 92 | print(heading_example) 93 | ``` 94 | 95 | # Heading 1 96 | 97 | ``` r 98 | dl_example <- qto_dl("Term" = "Definition") 99 | 100 | print(dl_example) 101 | ``` 102 | 103 | Term 104 | 105 | : Definition 106 | 107 | You can also use `qto_fig()` to embed images: 108 | 109 | ``` r 110 | fig_example <- qto_fig( 111 | "https://quarto.org/quarto.png", 112 | "Quarto logo" 113 | ) 114 | 115 | print(fig_example) 116 | ``` 117 | 118 | ![Quarto logo](https://quarto.org/quarto.png) 119 | 120 | ### Using loops to create Quarto Markdown 121 | 122 | We can leverage the `apply` family of functions ability to loop over 123 | list elements to simplify creating many divs at once. 124 | 125 | `map_qto()` allows users to set the function using the `.type` 126 | parameter: 127 | 128 | ``` r 129 | qto_list <- map_qto(list("This is a note.", "And this is a note.", "And this is a note"), .type = "callout") 130 | 131 | print(qto_list) 132 | ``` 133 | 134 | [[1]] 135 | 136 | ::: {.callout-note} 137 | This is a note. 138 | ::: 139 | 140 | 141 | [[2]] 142 | 143 | ::: {.callout-note} 144 | And this is a note. 145 | ::: 146 | 147 | 148 | [[3]] 149 | 150 | ::: {.callout-note} 151 | And this is a note 152 | ::: 153 | -------------------------------------------------------------------------------- /R/map.R: -------------------------------------------------------------------------------- 1 | resolve_mapping_function <- function(f = NULL, 2 | type = NULL, 3 | call = NULL) { 4 | f <- f %||% switch(type, 5 | block = qto_block, 6 | div = qto_div, 7 | callout = qto_callout, 8 | heading = qto_heading, 9 | ) 10 | if (!is_function(f)) { 11 | f <- as_function(f, call = call) 12 | } 13 | f 14 | } 15 | 16 | #' Apply a function to each element of a vector and return Quarto block vector 17 | #' 18 | #' [map_qto()] loops a list over a package function defined by .type or a custom 19 | #' function that returns a quarto block output. This function always returns a 20 | #' list of quarto block objects. 21 | #' 22 | #' @param .x An input vector. 23 | #' @param .f Optional function to apply to each element. If function does not 24 | #' return a "quarto_block" class object, the output is passed to [qto_block()] 25 | #' @param ... Additional parameters passed to function defined by `.f`. 26 | #' @param .type If .f is `NULL`, type is used to define the function applied to 27 | #' each element of the vector. Options include "block", "div", "callout", or 28 | #' "heading". 29 | #' @param .sep,.collapse Additional parameters passed to [qto_block()] if .f 30 | #' does not return a quarto block class object. Ignored if .f does return a 31 | #' quarto block class object. 32 | #' @inheritParams rlang::args_error_context 33 | #' @examples 34 | #' qto_list <- map_qto( 35 | #' list("This is a note.", "And this is a note.", "And this is a note"), 36 | #' .type = "callout" 37 | #' ) 38 | #' 39 | #' qto_block(qto_list) 40 | #' 41 | #' @seealso [quartools::pmap_qto()], [purrr::map()] 42 | #' @export 43 | map_qto <- function(.x, 44 | .f = NULL, 45 | ..., 46 | .type = c("block", "div", "callout", "heading"), 47 | .sep = "", 48 | .collapse = "", 49 | call = caller_env()) { 50 | .type <- arg_match(.type, error_call = call) 51 | .f <- resolve_mapping_function( 52 | f = .f, 53 | type = .type, 54 | call = call 55 | ) 56 | map( 57 | .x, 58 | function(x) { 59 | x <- .f(x, ...) 60 | if (inherits(x, "quarto_block")) return(x) 61 | qto_block(x, sep = .sep, collapse = .collapse, call = call) 62 | } 63 | ) 64 | } 65 | 66 | #' Map over multiple inputs simultaenously and return Quarto block vector 67 | #' 68 | #' [pmap_qto()] loops a list of vectors over a package function defined by .type or a custom 69 | #' function that returns a quarto block output. This function always returns a 70 | #' list of quarto block objects. 71 | #' 72 | #' @param .l An input vector. 73 | #' @inheritParams rlang::args_error_context 74 | #' @inheritParams map_qto 75 | #' @examples 76 | #' qto_list <- pmap_qto( 77 | #' list( 78 | #' list("Answer:", "Answer:", "Answer:"), 79 | #' list("Yes", "No", "Yes") 80 | #' ) 81 | #' ) 82 | #' qto_block(qto_list) 83 | #' 84 | #' qto_list <- pmap_qto( 85 | #' mtcars[seq(3L), seq(3L)], 86 | #' function(mpg, cyl, disp) { 87 | #' qto_li( 88 | #' .list = list( 89 | #' sprintf("mpg is: %s", mpg), 90 | #' sprintf("cyl is: %s", cyl), 91 | #' sprintf("disp is: %s", disp) 92 | #' ) 93 | #' ) 94 | #' } 95 | #' ) 96 | #' qto_block(qto_list) 97 | #' 98 | #' @seealso [quartools::map_qto()], [purrr::pmap()] 99 | #' @export 100 | pmap_qto <- function(.l, 101 | .f = NULL, 102 | ..., 103 | .type = c("block", "div", "callout", "heading"), 104 | .sep = "", 105 | .collapse = "", 106 | call = caller_env()) { 107 | .type <- arg_match(.type, error_call = call) 108 | .f <- resolve_mapping_function( 109 | f = .f, 110 | type = .type, 111 | call = call 112 | ) 113 | pmap( 114 | .l, 115 | function(...) { 116 | x <- .f(...) 117 | if (inherits(x, "quarto_block")) return(x) 118 | qto_block(x, sep = .sep, collapse = .collapse, call = call) 119 | }, 120 | ... 121 | ) 122 | } 123 | -------------------------------------------------------------------------------- /R/lists.R: -------------------------------------------------------------------------------- 1 | #' Create a definition list 2 | #' 3 | #' See the Quarto documentation for more information on lists: 4 | #' 5 | #' 6 | #' @param ... Optional. Named argument where the argument name is a term and the 7 | #' value name is the text to display as a definition for the term. Ignored if 8 | #' `.list` or `.term` and `.definition` are supplied. 9 | #' @param .term,.definition If supplied, `.list` is set to a named list using 10 | #' terms as names and definitions as values. Ignored if `.list` is supplied. 11 | #' @param .list If `.list` is supplied, any values passed to `...` are ignored. 12 | #' One of `.list`, `.term` and `.definition`, or `...` must be supplied. 13 | #' @param .sep Added separator string between terms and definitions. Defaults to 14 | #' `"\n"` for wider list spacing. If set to `""`, the definition list uses a 15 | #' tighter spacing. 16 | #' @inheritParams list_drop_or_replace_na 17 | #' @examples 18 | #' qto_dl( 19 | #' "Quarto" = "An open-source scientific and technical publishing system" 20 | #' ) 21 | #' 22 | #' qto_dl( 23 | #' .term = "Quarto", 24 | #' .definition = "An open-source scientific and technical publishing system" 25 | #' ) 26 | #' 27 | #' qto_dl( 28 | #' .list = list("Quarto" = "An open-source scientific and technical publishing system") 29 | #' ) 30 | 31 | #' 32 | #' @seealso 33 | #' - [qto_ol()] 34 | #' - [qto_li()] 35 | #' @export 36 | qto_dl <- function(..., 37 | .term = NULL, 38 | .definition = NULL, 39 | .list = NULL, 40 | .drop_na = FALSE, 41 | .sep = "\n", 42 | .replacement = "") { 43 | if (!is.null(.term) && !is.null(.definition)) { 44 | .list <- .list %||% set_names(.definition, .term) 45 | } 46 | 47 | .list <- list_drop_or_replace_na( 48 | .list %||% list2(...), 49 | drop_na = .drop_na, 50 | replacement = .replacement 51 | ) 52 | 53 | # FIXME: Unsure why this is needed 54 | if (has_length(.list, 1L)) { 55 | .list <- .list[1L] 56 | } 57 | 58 | qto_block( 59 | names(.list), 60 | .sep, 61 | "\n: ", 62 | .list, 63 | collapse = "\n\n" 64 | ) 65 | } 66 | 67 | #' Create ordered or unordered lists for Quarto 68 | #' 69 | #' [qto_ol()] created ordered lists and [qto_li()] creates unordered lists. See 70 | #' the Quarto documentation for more information on lists: 71 | #' 72 | #' 73 | #' @param .list A vector of list items. Optional if items are passed to `...`. 74 | #' @param ... Items to use as list items. Ignored if .list is supplied. 75 | #' @param level Indent level of list. Nested levels in a single function call 76 | #' are not yet supported. 77 | #' @seealso [qto_dl()] 78 | #' @examples 79 | #' qto_ol(LETTERS[1:2]) 80 | #' 81 | #' qto_li(LETTERS[3:4]) 82 | #' 83 | #' qto_li(LETTERS[5:6], level = 2) 84 | #' 85 | #' qto_ol(LETTERS[7:8], sep = "\n") 86 | #' 87 | #' qto_li(LETTERS[5:6], symbol = c("*", " +")) 88 | #' 89 | #' @name qto_list 90 | NULL 91 | 92 | #' @rdname qto_list 93 | #' @name qto_ol 94 | #' @export 95 | qto_ol <- function(.list = NULL, ..., level = 1L, symbol = "1.") { 96 | qto_li( 97 | .list = .list, 98 | ..., 99 | level = level, 100 | symbol = symbol 101 | ) 102 | } 103 | 104 | #' @rdname qto_list 105 | #' @name qto_li 106 | #' @param symbol Symbol to use for list bullet. Use "(@)" to create a list where 107 | #' numbering continues after interruption. 108 | #' @param sep Separator character between list items passed to [qto_block()]. 109 | #' Defaults to "". Set to `"\n"` for wider list spacing. 110 | #' @export 111 | qto_li <- function(.list = NULL, ..., level = 1L, symbol = "*", sep = "") { 112 | .list <- .list %||% rlang::list2(...) 113 | 114 | check_character(symbol) 115 | 116 | qto_block( 117 | "\n", 118 | paste0( 119 | indent_level(level), 120 | symbol, " ", 121 | .list 122 | ), 123 | sep = sep 124 | ) 125 | } 126 | 127 | #' Helper function to set indent based on level 128 | #' 129 | #' @noRd 130 | indent_level <- function(level = 1L) { 131 | if (level == 1L) { 132 | return("") 133 | } 134 | 135 | level <- level - 1L 136 | strrep(" ", 4L * level) 137 | } 138 | -------------------------------------------------------------------------------- /R/with.R: -------------------------------------------------------------------------------- 1 | handle_extensions <- function(extension, valid_extensions) { 2 | if (!is.null(extension)) { 3 | rlang::arg_match0(extension, valid_extensions) 4 | extension_string <- paste0("-", extension) 5 | return(extension_string) 6 | } 7 | "" 8 | } 9 | 10 | #' Temporarily modify page layout 11 | #' @description 12 | #' Create a div block that modifies the current quarto layout column temporarily. 13 | #' 14 | #' See [the Quarto documentation for a full list 15 | #' of available extension options](https://quarto.org/docs/authoring/article-layout.html#available-columns). 16 | #' @param outset If `TRUE`, the ouset affix is applied to the column class 17 | #' @param extension affix to apply to column class 18 | #' @inheritParams qto_div 19 | #' @inheritDotParams qto_div 20 | #' @export 21 | #' @examples 22 | #' with_body_column("Hello world!") 23 | #' @return character vector of length 1 24 | #' @rdname page-layout 25 | with_body_column <- function(..., 26 | outset = FALSE, 27 | id = NULL, 28 | class = NULL, 29 | extension = NULL, 30 | .attributes = NULL, 31 | call = caller_env()) { 32 | extension_string <- handle_extensions(extension, c("left", "right")) 33 | outset_string <- ifelse(isTRUE(outset), "-outset", "") 34 | cls <- sprintf(".column-body%s%s", outset_string, extension_string) 35 | .attributes <- c(cls, as.list(.attributes)) 36 | .attributes <- list_drop_empty(.attributes) 37 | qto_div( 38 | ..., 39 | id = id, 40 | .attributes = .attributes, 41 | call = call 42 | ) 43 | } 44 | 45 | #' @export 46 | #' @rdname page-layout 47 | with_page_column <- function(..., 48 | id = NULL, 49 | class = NULL, 50 | extension = NULL, 51 | .attributes = NULL, 52 | call = caller_env()) { 53 | extension_string <- handle_extensions(extension, c("left", "right")) 54 | cls <- sprintf(".column-page%s", extension_string) 55 | .attributes <- c(cls, as.list(.attributes)) 56 | .attributes <- list_drop_empty(.attributes) 57 | qto_div( 58 | ..., 59 | id = id, 60 | .attributes = .attributes, 61 | call = call 62 | ) 63 | } 64 | 65 | #' @export 66 | #' @rdname page-layout 67 | with_screen_inset_column <- function(..., 68 | id = NULL, 69 | class = NULL, 70 | extension = NULL, 71 | .attributes = NULL, 72 | call = caller_env()) { 73 | extension_string <- handle_extensions(extension, c("left", "right", "shaded")) 74 | cls <- sprintf(".column-screen-inset%s", extension_string) 75 | .attributes <- c(cls, as.list(.attributes)) 76 | .attributes <- list_drop_empty(.attributes) 77 | qto_div( 78 | ..., 79 | id = id, 80 | .attributes = .attributes, 81 | call = call 82 | ) 83 | } 84 | 85 | #' @export 86 | #' @rdname page-layout 87 | with_screen_column <- function(..., 88 | id = NULL, 89 | class = NULL, 90 | extension = NULL, 91 | .attributes = NULL, 92 | call = caller_env()) { 93 | extension_string <- handle_extensions(extension, c("left", "right")) 94 | cls <- sprintf(".column-screen%s", extension_string) 95 | .attributes <- c(cls, as.list(.attributes)) 96 | .attributes <- list_drop_empty(.attributes) 97 | qto_div( 98 | ..., 99 | id = id, 100 | .attributes = .attributes, 101 | call = call 102 | ) 103 | } 104 | 105 | #' @export 106 | #' @rdname page-layout 107 | with_margin_column <- function(..., 108 | id = NULL, 109 | .attributes = NULL, 110 | call = caller_env()) { 111 | .attributes <- c(".column-margin", as.list(.attributes)) 112 | .attributes <- list_drop_empty(.attributes) 113 | qto_div( 114 | ..., 115 | id = id, 116 | .attributes = .attributes, 117 | call = call 118 | ) 119 | } 120 | -------------------------------------------------------------------------------- /R/attributes.R: -------------------------------------------------------------------------------- 1 | #' Create an attribute string for a div or span 2 | #' 3 | #' [qto_attributes()] creates an attribute string used by [qto_div()], 4 | #' [qto_span()], or [qto_fig_span()]. 5 | #' 6 | #' @param ... Optional named attributes. 7 | #' @param id Div or span identifier. If `id` does not start with `"#"`, the 8 | #' hash character is applied as a prefix. 9 | #' @param class Div or span class. If `class` does not start with `"."`, the 10 | #' period character is applied as a prefix. 11 | #' @param css If `{htmltools}` is installed, a list of css style attributes to 12 | #' pass to [htmltools::css()]. 13 | #' @param .attributes Optional list of attributes. If supplied, any attributes 14 | #' passed to `...` are ignored. 15 | #' @param .output Output type. If "embrace", the returned attributes are always 16 | #' enclosed in curly brackets, e.g. "{}" if no attributes are supplied. If 17 | #' "span", an empty string is returned if no attributes are provided. 18 | #' @param .drop_empty If `TRUE`, empty attributes are dropped. 19 | #' @inheritParams rlang::args_error_context 20 | #' @examples 21 | #' qto_attributes(id = "id", class = "class") 22 | #' 23 | #' qto_attributes(class = "class", key1 = "val", key2 = "val") 24 | #' 25 | #' qto_attributes(width = 4) 26 | #' 27 | #' @export 28 | qto_attributes <- function(id = NULL, 29 | class = NULL, 30 | css = NULL, 31 | ..., 32 | .attributes = NULL, 33 | .output = "embrace", 34 | .drop_empty = TRUE, 35 | call = caller_env()) { 36 | if (is_string(id) && !grepl("^#", id)) { 37 | id <- paste0("#", id) 38 | } 39 | 40 | if (all(is.character(class))) { 41 | has_class_prefix <- grepl("^\\.", class) 42 | class[!has_class_prefix] <- paste0(".", class[!has_class_prefix]) 43 | } 44 | 45 | if (!is.null(css) && is_installed("htmltools")) { 46 | css <- htmltools::css(!!!css) 47 | } 48 | 49 | .attributes <- as_qto_attr( 50 | ..., 51 | .attributes = .attributes, 52 | .drop_empty = .drop_empty, 53 | .collapse = " " 54 | ) 55 | 56 | if (!is.null(c(id, class, css, .attributes))) { 57 | .attributes <- paste0(c(id, class, css, .attributes), collapse = " ") 58 | } 59 | 60 | .output <- arg_match0(.output, c("embrace", "span"), error_call = call) 61 | 62 | if ((.output == "span") && !is.null(.attributes)) { 63 | .output <- "embrace" 64 | } 65 | 66 | switch(.output, 67 | embrace = embrace(.attributes), 68 | span = "" 69 | ) 70 | } 71 | 72 | #' Helper function to create key-value attribute strings 73 | #' 74 | #' @noRd 75 | as_qto_attr <- function(..., 76 | .attributes = NULL, 77 | op = "=", 78 | before = "", 79 | after = "", 80 | .collapse = " ", 81 | .drop_empty = TRUE, 82 | .drop_na = TRUE, 83 | .replacement = NULL) { 84 | .attributes <- .attributes %||% dots_list(...) 85 | 86 | if (.drop_empty) { 87 | .attributes <- list_drop_empty(.attributes) 88 | } 89 | 90 | if (is_empty(.attributes)) { 91 | return(NULL) 92 | } 93 | 94 | .attributes <- list_drop_or_replace_na( 95 | .attributes, 96 | drop_na = .drop_na, 97 | replacement = .replacement 98 | ) 99 | 100 | if (is_named(.attributes)) { 101 | .attributes <- paste0( 102 | names(.attributes), op, qto_attr_values(.attributes), 103 | collapse = .collapse 104 | ) 105 | } else { 106 | .attributes <- paste0(.attributes, collapse = .collapse) 107 | } 108 | 109 | paste0(before, .attributes, after) 110 | } 111 | 112 | 113 | #' Helper function to sanitize attribute values 114 | #' 115 | #' @noRd 116 | qto_attr_values <- function(values, mark = "'") { 117 | vapply( 118 | values, 119 | \(x) { 120 | if (is.logical(x)) { 121 | return(tolower(x)) 122 | } 123 | 124 | if (grepl(r"{\b[0-9]+\.?[0-9]*%\b}", x)) { 125 | mark <- "" 126 | } 127 | 128 | if (is.character(x)) { 129 | return(combine(x, mark, mark)) 130 | } 131 | 132 | as.character(x) 133 | }, 134 | NA_character_ 135 | ) 136 | } 137 | -------------------------------------------------------------------------------- /R/import-standalone-obj-type.R: -------------------------------------------------------------------------------- 1 | # Standalone file: do not edit by hand 2 | # Source: 3 | # ---------------------------------------------------------------------- 4 | # 5 | # --- 6 | # repo: r-lib/rlang 7 | # file: standalone-obj-type.R 8 | # last-updated: 2023-05-01 9 | # license: https://unlicense.org 10 | # imports: rlang (>= 1.1.0) 11 | # --- 12 | # 13 | # ## Changelog 14 | # 15 | # 2023-05-01: 16 | # - `obj_type_friendly()` now only displays the first class of S3 objects. 17 | # 18 | # 2023-03-30: 19 | # - `stop_input_type()` now handles `I()` input literally in `arg`. 20 | # 21 | # 2022-10-04: 22 | # - `obj_type_friendly(value = TRUE)` now shows numeric scalars 23 | # literally. 24 | # - `stop_friendly_type()` now takes `show_value`, passed to 25 | # `obj_type_friendly()` as the `value` argument. 26 | # 27 | # 2022-10-03: 28 | # - Added `allow_na` and `allow_null` arguments. 29 | # - `NULL` is now backticked. 30 | # - Better friendly type for infinities and `NaN`. 31 | # 32 | # 2022-09-16: 33 | # - Unprefixed usage of rlang functions with `rlang::` to 34 | # avoid onLoad issues when called from rlang (#1482). 35 | # 36 | # 2022-08-11: 37 | # - Prefixed usage of rlang functions with `rlang::`. 38 | # 39 | # 2022-06-22: 40 | # - `friendly_type_of()` is now `obj_type_friendly()`. 41 | # - Added `obj_type_oo()`. 42 | # 43 | # 2021-12-20: 44 | # - Added support for scalar values and empty vectors. 45 | # - Added `stop_input_type()` 46 | # 47 | # 2021-06-30: 48 | # - Added support for missing arguments. 49 | # 50 | # 2021-04-19: 51 | # - Added support for matrices and arrays (#141). 52 | # - Added documentation. 53 | # - Added changelog. 54 | # 55 | # nocov start 56 | 57 | #' Return English-friendly type 58 | #' @param x Any R object. 59 | #' @param value Whether to describe the value of `x`. Special values 60 | #' like `NA` or `""` are always described. 61 | #' @param length Whether to mention the length of vectors and lists. 62 | #' @return A string describing the type. Starts with an indefinite 63 | #' article, e.g. "an integer vector". 64 | #' @noRd 65 | obj_type_friendly <- function(x, value = TRUE) { 66 | if (is_missing(x)) { 67 | return("absent") 68 | } 69 | 70 | if (is.object(x)) { 71 | if (inherits(x, "quosure")) { 72 | type <- "quosure" 73 | } else { 74 | type <- class(x)[[1L]] 75 | } 76 | return(sprintf("a <%s> object", type)) 77 | } 78 | 79 | if (!is_vector(x)) { 80 | return(.rlang_as_friendly_type(typeof(x))) 81 | } 82 | 83 | n_dim <- length(dim(x)) 84 | 85 | if (!n_dim) { 86 | if (!is_list(x) && length(x) == 1) { 87 | if (is_na(x)) { 88 | return(switch( 89 | typeof(x), 90 | logical = "`NA`", 91 | integer = "an integer `NA`", 92 | double = 93 | if (is.nan(x)) { 94 | "`NaN`" 95 | } else { 96 | "a numeric `NA`" 97 | }, 98 | complex = "a complex `NA`", 99 | character = "a character `NA`", 100 | .rlang_stop_unexpected_typeof(x) 101 | )) 102 | } 103 | 104 | show_infinites <- function(x) { 105 | if (x > 0) { 106 | "`Inf`" 107 | } else { 108 | "`-Inf`" 109 | } 110 | } 111 | str_encode <- function(x, width = 30, ...) { 112 | if (nchar(x) > width) { 113 | x <- substr(x, 1, width - 3) 114 | x <- paste0(x, "...") 115 | } 116 | encodeString(x, ...) 117 | } 118 | 119 | if (value) { 120 | if (is.numeric(x) && is.infinite(x)) { 121 | return(show_infinites(x)) 122 | } 123 | 124 | if (is.numeric(x) || is.complex(x)) { 125 | number <- as.character(round(x, 2)) 126 | what <- if (is.complex(x)) "the complex number" else "the number" 127 | return(paste(what, number)) 128 | } 129 | 130 | return(switch( 131 | typeof(x), 132 | logical = if (x) "`TRUE`" else "`FALSE`", 133 | character = { 134 | what <- if (nzchar(x)) "the string" else "the empty string" 135 | paste(what, str_encode(x, quote = "\"")) 136 | }, 137 | raw = paste("the raw value", as.character(x)), 138 | .rlang_stop_unexpected_typeof(x) 139 | )) 140 | } 141 | 142 | return(switch( 143 | typeof(x), 144 | logical = "a logical value", 145 | integer = "an integer", 146 | double = if (is.infinite(x)) show_infinites(x) else "a number", 147 | complex = "a complex number", 148 | character = if (nzchar(x)) "a string" else "\"\"", 149 | raw = "a raw value", 150 | .rlang_stop_unexpected_typeof(x) 151 | )) 152 | } 153 | 154 | if (length(x) == 0) { 155 | return(switch( 156 | typeof(x), 157 | logical = "an empty logical vector", 158 | integer = "an empty integer vector", 159 | double = "an empty numeric vector", 160 | complex = "an empty complex vector", 161 | character = "an empty character vector", 162 | raw = "an empty raw vector", 163 | list = "an empty list", 164 | .rlang_stop_unexpected_typeof(x) 165 | )) 166 | } 167 | } 168 | 169 | vec_type_friendly(x) 170 | } 171 | 172 | vec_type_friendly <- function(x, length = FALSE) { 173 | if (!is_vector(x)) { 174 | abort("`x` must be a vector.") 175 | } 176 | type <- typeof(x) 177 | n_dim <- length(dim(x)) 178 | 179 | add_length <- function(type) { 180 | if (length && !n_dim) { 181 | paste0(type, sprintf(" of length %s", length(x))) 182 | } else { 183 | type 184 | } 185 | } 186 | 187 | if (type == "list") { 188 | if (n_dim < 2) { 189 | return(add_length("a list")) 190 | } else if (is.data.frame(x)) { 191 | return("a data frame") 192 | } else if (n_dim == 2) { 193 | return("a list matrix") 194 | } else { 195 | return("a list array") 196 | } 197 | } 198 | 199 | type <- switch( 200 | type, 201 | logical = "a logical %s", 202 | integer = "an integer %s", 203 | numeric = , 204 | double = "a double %s", 205 | complex = "a complex %s", 206 | character = "a character %s", 207 | raw = "a raw %s", 208 | type = paste0("a ", type, " %s") 209 | ) 210 | 211 | if (n_dim < 2) { 212 | kind <- "vector" 213 | } else if (n_dim == 2) { 214 | kind <- "matrix" 215 | } else { 216 | kind <- "array" 217 | } 218 | out <- sprintf(type, kind) 219 | 220 | if (n_dim >= 2) { 221 | out 222 | } else { 223 | add_length(out) 224 | } 225 | } 226 | 227 | .rlang_as_friendly_type <- function(type) { 228 | switch( 229 | type, 230 | 231 | list = "a list", 232 | 233 | NULL = "`NULL`", 234 | environment = "an environment", 235 | externalptr = "a pointer", 236 | weakref = "a weak reference", 237 | S4 = "an S4 object", 238 | 239 | name = , 240 | symbol = "a symbol", 241 | language = "a call", 242 | pairlist = "a pairlist node", 243 | expression = "an expression vector", 244 | 245 | char = "an internal string", 246 | promise = "an internal promise", 247 | ... = "an internal dots object", 248 | any = "an internal `any` object", 249 | bytecode = "an internal bytecode object", 250 | 251 | primitive = , 252 | builtin = , 253 | special = "a primitive function", 254 | closure = "a function", 255 | 256 | type 257 | ) 258 | } 259 | 260 | .rlang_stop_unexpected_typeof <- function(x, call = caller_env()) { 261 | abort( 262 | sprintf("Unexpected type <%s>.", typeof(x)), 263 | call = call 264 | ) 265 | } 266 | 267 | #' Return OO type 268 | #' @param x Any R object. 269 | #' @return One of `"bare"` (for non-OO objects), `"S3"`, `"S4"`, 270 | #' `"R6"`, or `"R7"`. 271 | #' @noRd 272 | obj_type_oo <- function(x) { 273 | if (!is.object(x)) { 274 | return("bare") 275 | } 276 | 277 | class <- inherits(x, c("R6", "R7_object"), which = TRUE) 278 | 279 | if (class[[1]]) { 280 | "R6" 281 | } else if (class[[2]]) { 282 | "R7" 283 | } else if (isS4(x)) { 284 | "S4" 285 | } else { 286 | "S3" 287 | } 288 | } 289 | 290 | #' @param x The object type which does not conform to `what`. Its 291 | #' `obj_type_friendly()` is taken and mentioned in the error message. 292 | #' @param what The friendly expected type as a string. Can be a 293 | #' character vector of expected types, in which case the error 294 | #' message mentions all of them in an "or" enumeration. 295 | #' @param show_value Passed to `value` argument of `obj_type_friendly()`. 296 | #' @param ... Arguments passed to [abort()]. 297 | #' @inheritParams args_error_context 298 | #' @noRd 299 | stop_input_type <- function(x, 300 | what, 301 | ..., 302 | allow_na = FALSE, 303 | allow_null = FALSE, 304 | show_value = TRUE, 305 | arg = caller_arg(x), 306 | call = caller_env()) { 307 | # From standalone-cli.R 308 | cli <- env_get_list( 309 | nms = c("format_arg", "format_code"), 310 | last = topenv(), 311 | default = function(x) sprintf("`%s`", x), 312 | inherit = TRUE 313 | ) 314 | 315 | if (allow_na) { 316 | what <- c(what, cli$format_code("NA")) 317 | } 318 | if (allow_null) { 319 | what <- c(what, cli$format_code("NULL")) 320 | } 321 | if (length(what)) { 322 | what <- oxford_comma(what) 323 | } 324 | if (inherits(arg, "AsIs")) { 325 | format_arg <- identity 326 | } else { 327 | format_arg <- cli$format_arg 328 | } 329 | 330 | message <- sprintf( 331 | "%s must be %s, not %s.", 332 | format_arg(arg), 333 | what, 334 | obj_type_friendly(x, value = show_value) 335 | ) 336 | 337 | abort(message, ..., call = call, arg = arg) 338 | } 339 | 340 | oxford_comma <- function(chr, sep = ", ", final = "or") { 341 | n <- length(chr) 342 | 343 | if (n < 2) { 344 | return(chr) 345 | } 346 | 347 | head <- chr[seq_len(n - 1)] 348 | last <- chr[n] 349 | 350 | head <- paste(head, collapse = sep) 351 | 352 | # Write a or b. But a, b, or c. 353 | if (n > 2) { 354 | paste0(head, sep, final, " ", last) 355 | } else { 356 | paste0(head, " ", final, " ", last) 357 | } 358 | } 359 | 360 | # nocov end 361 | -------------------------------------------------------------------------------- /R/import-standalone-types-check.R: -------------------------------------------------------------------------------- 1 | # Standalone file: do not edit by hand 2 | # Source: 3 | # ---------------------------------------------------------------------- 4 | # 5 | # --- 6 | # repo: r-lib/rlang 7 | # file: standalone-types-check.R 8 | # last-updated: 2023-03-13 9 | # license: https://unlicense.org 10 | # dependencies: standalone-obj-type.R 11 | # imports: rlang (>= 1.1.0) 12 | # --- 13 | # 14 | # ## Changelog 15 | # 16 | # 2023-03-13: 17 | # - Improved error messages of number checkers (@teunbrand) 18 | # - Added `allow_infinite` argument to `check_number_whole()` (@mgirlich). 19 | # - Added `check_data_frame()` (@mgirlich). 20 | # 21 | # 2023-03-07: 22 | # - Added dependency on rlang (>= 1.1.0). 23 | # 24 | # 2023-02-15: 25 | # - Added `check_logical()`. 26 | # 27 | # - `check_bool()`, `check_number_whole()`, and 28 | # `check_number_decimal()` are now implemented in C. 29 | # 30 | # - For efficiency, `check_number_whole()` and 31 | # `check_number_decimal()` now take a `NULL` default for `min` and 32 | # `max`. This makes it possible to bypass unnecessary type-checking 33 | # and comparisons in the default case of no bounds checks. 34 | # 35 | # 2022-10-07: 36 | # - `check_number_whole()` and `_decimal()` no longer treat 37 | # non-numeric types such as factors or dates as numbers. Numeric 38 | # types are detected with `is.numeric()`. 39 | # 40 | # 2022-10-04: 41 | # - Added `check_name()` that forbids the empty string. 42 | # `check_string()` allows the empty string by default. 43 | # 44 | # 2022-09-28: 45 | # - Removed `what` arguments. 46 | # - Added `allow_na` and `allow_null` arguments. 47 | # - Added `allow_decimal` and `allow_infinite` arguments. 48 | # - Improved errors with absent arguments. 49 | # 50 | # 51 | # 2022-09-16: 52 | # - Unprefixed usage of rlang functions with `rlang::` to 53 | # avoid onLoad issues when called from rlang (#1482). 54 | # 55 | # 2022-08-11: 56 | # - Added changelog. 57 | # 58 | # nocov start 59 | 60 | # Scalars ----------------------------------------------------------------- 61 | 62 | .standalone_types_check_dot_call <- .Call 63 | 64 | check_bool <- function(x, 65 | ..., 66 | allow_na = FALSE, 67 | allow_null = FALSE, 68 | arg = caller_arg(x), 69 | call = caller_env()) { 70 | if (!missing(x) && .standalone_types_check_dot_call(ffi_standalone_is_bool_1.0.7, x, allow_na, allow_null)) { 71 | return(invisible(NULL)) 72 | } 73 | 74 | stop_input_type( 75 | x, 76 | c("`TRUE`", "`FALSE`"), 77 | ..., 78 | allow_na = allow_na, 79 | allow_null = allow_null, 80 | arg = arg, 81 | call = call 82 | ) 83 | } 84 | 85 | check_string <- function(x, 86 | ..., 87 | allow_empty = TRUE, 88 | allow_na = FALSE, 89 | allow_null = FALSE, 90 | arg = caller_arg(x), 91 | call = caller_env()) { 92 | if (!missing(x)) { 93 | is_string <- .rlang_check_is_string( 94 | x, 95 | allow_empty = allow_empty, 96 | allow_na = allow_na, 97 | allow_null = allow_null 98 | ) 99 | if (is_string) { 100 | return(invisible(NULL)) 101 | } 102 | } 103 | 104 | stop_input_type( 105 | x, 106 | "a single string", 107 | ..., 108 | allow_na = allow_na, 109 | allow_null = allow_null, 110 | arg = arg, 111 | call = call 112 | ) 113 | } 114 | 115 | .rlang_check_is_string <- function(x, 116 | allow_empty, 117 | allow_na, 118 | allow_null) { 119 | if (is_string(x)) { 120 | if (allow_empty || !is_string(x, "")) { 121 | return(TRUE) 122 | } 123 | } 124 | 125 | if (allow_null && is_null(x)) { 126 | return(TRUE) 127 | } 128 | 129 | if (allow_na && (identical(x, NA) || identical(x, na_chr))) { 130 | return(TRUE) 131 | } 132 | 133 | FALSE 134 | } 135 | 136 | check_name <- function(x, 137 | ..., 138 | allow_null = FALSE, 139 | arg = caller_arg(x), 140 | call = caller_env()) { 141 | if (!missing(x)) { 142 | is_string <- .rlang_check_is_string( 143 | x, 144 | allow_empty = FALSE, 145 | allow_na = FALSE, 146 | allow_null = allow_null 147 | ) 148 | if (is_string) { 149 | return(invisible(NULL)) 150 | } 151 | } 152 | 153 | stop_input_type( 154 | x, 155 | "a valid name", 156 | ..., 157 | allow_na = FALSE, 158 | allow_null = allow_null, 159 | arg = arg, 160 | call = call 161 | ) 162 | } 163 | 164 | IS_NUMBER_true <- 0 165 | IS_NUMBER_false <- 1 166 | IS_NUMBER_oob <- 2 167 | 168 | check_number_decimal <- function(x, 169 | ..., 170 | min = NULL, 171 | max = NULL, 172 | allow_infinite = TRUE, 173 | allow_na = FALSE, 174 | allow_null = FALSE, 175 | arg = caller_arg(x), 176 | call = caller_env()) { 177 | if (missing(x)) { 178 | exit_code <- IS_NUMBER_false 179 | } else if (0 == (exit_code <- .standalone_types_check_dot_call( 180 | ffi_standalone_check_number_1.0.7, 181 | x, 182 | allow_decimal = TRUE, 183 | min, 184 | max, 185 | allow_infinite, 186 | allow_na, 187 | allow_null 188 | ))) { 189 | return(invisible(NULL)) 190 | } 191 | 192 | .stop_not_number( 193 | x, 194 | ..., 195 | exit_code = exit_code, 196 | allow_decimal = TRUE, 197 | min = min, 198 | max = max, 199 | allow_na = allow_na, 200 | allow_null = allow_null, 201 | arg = arg, 202 | call = call 203 | ) 204 | } 205 | 206 | check_number_whole <- function(x, 207 | ..., 208 | min = NULL, 209 | max = NULL, 210 | allow_infinite = FALSE, 211 | allow_na = FALSE, 212 | allow_null = FALSE, 213 | arg = caller_arg(x), 214 | call = caller_env()) { 215 | if (missing(x)) { 216 | exit_code <- IS_NUMBER_false 217 | } else if (0 == (exit_code <- .standalone_types_check_dot_call( 218 | ffi_standalone_check_number_1.0.7, 219 | x, 220 | allow_decimal = FALSE, 221 | min, 222 | max, 223 | allow_infinite, 224 | allow_na, 225 | allow_null 226 | ))) { 227 | return(invisible(NULL)) 228 | } 229 | 230 | .stop_not_number( 231 | x, 232 | ..., 233 | exit_code = exit_code, 234 | allow_decimal = FALSE, 235 | min = min, 236 | max = max, 237 | allow_na = allow_na, 238 | allow_null = allow_null, 239 | arg = arg, 240 | call = call 241 | ) 242 | } 243 | 244 | .stop_not_number <- function(x, 245 | ..., 246 | exit_code, 247 | allow_decimal, 248 | min, 249 | max, 250 | allow_na, 251 | allow_null, 252 | arg, 253 | call) { 254 | if (allow_decimal) { 255 | what <- "a number" 256 | } else { 257 | what <- "a whole number" 258 | } 259 | 260 | if (exit_code == IS_NUMBER_oob) { 261 | min <- min %||% -Inf 262 | max <- max %||% Inf 263 | 264 | if (min > -Inf && max < Inf) { 265 | what <- sprintf("%s between %s and %s", what, min, max) 266 | } else if (x < min) { 267 | what <- sprintf("%s larger than or equal to %s", what, min) 268 | } else if (x > max) { 269 | what <- sprintf("%s smaller than or equal to %s", what, max) 270 | } else { 271 | abort("Unexpected state in OOB check", .internal = TRUE) 272 | } 273 | } 274 | 275 | stop_input_type( 276 | x, 277 | what, 278 | ..., 279 | allow_na = allow_na, 280 | allow_null = allow_null, 281 | arg = arg, 282 | call = call 283 | ) 284 | } 285 | 286 | check_symbol <- function(x, 287 | ..., 288 | allow_null = FALSE, 289 | arg = caller_arg(x), 290 | call = caller_env()) { 291 | if (!missing(x)) { 292 | if (is_symbol(x)) { 293 | return(invisible(NULL)) 294 | } 295 | if (allow_null && is_null(x)) { 296 | return(invisible(NULL)) 297 | } 298 | } 299 | 300 | stop_input_type( 301 | x, 302 | "a symbol", 303 | ..., 304 | allow_na = FALSE, 305 | allow_null = allow_null, 306 | arg = arg, 307 | call = call 308 | ) 309 | } 310 | 311 | check_arg <- function(x, 312 | ..., 313 | allow_null = FALSE, 314 | arg = caller_arg(x), 315 | call = caller_env()) { 316 | if (!missing(x)) { 317 | if (is_symbol(x)) { 318 | return(invisible(NULL)) 319 | } 320 | if (allow_null && is_null(x)) { 321 | return(invisible(NULL)) 322 | } 323 | } 324 | 325 | stop_input_type( 326 | x, 327 | "an argument name", 328 | ..., 329 | allow_na = FALSE, 330 | allow_null = allow_null, 331 | arg = arg, 332 | call = call 333 | ) 334 | } 335 | 336 | check_call <- function(x, 337 | ..., 338 | allow_null = FALSE, 339 | arg = caller_arg(x), 340 | call = caller_env()) { 341 | if (!missing(x)) { 342 | if (is_call(x)) { 343 | return(invisible(NULL)) 344 | } 345 | if (allow_null && is_null(x)) { 346 | return(invisible(NULL)) 347 | } 348 | } 349 | 350 | stop_input_type( 351 | x, 352 | "a defused call", 353 | ..., 354 | allow_na = FALSE, 355 | allow_null = allow_null, 356 | arg = arg, 357 | call = call 358 | ) 359 | } 360 | 361 | check_environment <- function(x, 362 | ..., 363 | allow_null = FALSE, 364 | arg = caller_arg(x), 365 | call = caller_env()) { 366 | if (!missing(x)) { 367 | if (is_environment(x)) { 368 | return(invisible(NULL)) 369 | } 370 | if (allow_null && is_null(x)) { 371 | return(invisible(NULL)) 372 | } 373 | } 374 | 375 | stop_input_type( 376 | x, 377 | "an environment", 378 | ..., 379 | allow_na = FALSE, 380 | allow_null = allow_null, 381 | arg = arg, 382 | call = call 383 | ) 384 | } 385 | 386 | check_function <- function(x, 387 | ..., 388 | allow_null = FALSE, 389 | arg = caller_arg(x), 390 | call = caller_env()) { 391 | if (!missing(x)) { 392 | if (is_function(x)) { 393 | return(invisible(NULL)) 394 | } 395 | if (allow_null && is_null(x)) { 396 | return(invisible(NULL)) 397 | } 398 | } 399 | 400 | stop_input_type( 401 | x, 402 | "a function", 403 | ..., 404 | allow_na = FALSE, 405 | allow_null = allow_null, 406 | arg = arg, 407 | call = call 408 | ) 409 | } 410 | 411 | check_closure <- function(x, 412 | ..., 413 | allow_null = FALSE, 414 | arg = caller_arg(x), 415 | call = caller_env()) { 416 | if (!missing(x)) { 417 | if (is_closure(x)) { 418 | return(invisible(NULL)) 419 | } 420 | if (allow_null && is_null(x)) { 421 | return(invisible(NULL)) 422 | } 423 | } 424 | 425 | stop_input_type( 426 | x, 427 | "an R function", 428 | ..., 429 | allow_na = FALSE, 430 | allow_null = allow_null, 431 | arg = arg, 432 | call = call 433 | ) 434 | } 435 | 436 | check_formula <- function(x, 437 | ..., 438 | allow_null = FALSE, 439 | arg = caller_arg(x), 440 | call = caller_env()) { 441 | if (!missing(x)) { 442 | if (is_formula(x)) { 443 | return(invisible(NULL)) 444 | } 445 | if (allow_null && is_null(x)) { 446 | return(invisible(NULL)) 447 | } 448 | } 449 | 450 | stop_input_type( 451 | x, 452 | "a formula", 453 | ..., 454 | allow_na = FALSE, 455 | allow_null = allow_null, 456 | arg = arg, 457 | call = call 458 | ) 459 | } 460 | 461 | 462 | # Vectors ----------------------------------------------------------------- 463 | 464 | check_character <- function(x, 465 | ..., 466 | allow_null = FALSE, 467 | arg = caller_arg(x), 468 | call = caller_env()) { 469 | if (!missing(x)) { 470 | if (is_character(x)) { 471 | return(invisible(NULL)) 472 | } 473 | if (allow_null && is_null(x)) { 474 | return(invisible(NULL)) 475 | } 476 | } 477 | 478 | stop_input_type( 479 | x, 480 | "a character vector", 481 | ..., 482 | allow_na = FALSE, 483 | allow_null = allow_null, 484 | arg = arg, 485 | call = call 486 | ) 487 | } 488 | 489 | check_logical <- function(x, 490 | ..., 491 | allow_null = FALSE, 492 | arg = caller_arg(x), 493 | call = caller_env()) { 494 | if (!missing(x)) { 495 | if (is_logical(x)) { 496 | return(invisible(NULL)) 497 | } 498 | if (allow_null && is_null(x)) { 499 | return(invisible(NULL)) 500 | } 501 | } 502 | 503 | stop_input_type( 504 | x, 505 | "a logical vector", 506 | ..., 507 | allow_na = FALSE, 508 | allow_null = allow_null, 509 | arg = arg, 510 | call = call 511 | ) 512 | } 513 | 514 | check_data_frame <- function(x, 515 | ..., 516 | allow_null = FALSE, 517 | arg = caller_arg(x), 518 | call = caller_env()) { 519 | if (!missing(x)) { 520 | if (is.data.frame(x)) { 521 | return(invisible(NULL)) 522 | } 523 | if (allow_null && is_null(x)) { 524 | return(invisible(NULL)) 525 | } 526 | } 527 | 528 | stop_input_type( 529 | x, 530 | "a data frame", 531 | ..., 532 | allow_null = allow_null, 533 | arg = arg, 534 | call = call 535 | ) 536 | } 537 | 538 | # nocov end 539 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | GNU General Public License 2 | ========================== 3 | 4 | _Version 3, 29 June 2007_ 5 | _Copyright © 2007 Free Software Foundation, Inc. <>_ 6 | 7 | Everyone is permitted to copy and distribute verbatim copies of this license 8 | document, but changing it is not allowed. 9 | 10 | ## Preamble 11 | 12 | The GNU General Public License is a free, copyleft license for software and other 13 | kinds of works. 14 | 15 | The licenses for most software and other practical works are designed to take away 16 | your freedom to share and change the works. By contrast, the GNU General Public 17 | License is intended to guarantee your freedom to share and change all versions of a 18 | program--to make sure it remains free software for all its users. We, the Free 19 | Software Foundation, use the GNU General Public License for most of our software; it 20 | applies also to any other work released this way by its authors. You can apply it to 21 | your programs, too. 22 | 23 | When we speak of free software, we are referring to freedom, not price. Our General 24 | Public Licenses are designed to make sure that you have the freedom to distribute 25 | copies of free software (and charge for them if you wish), that you receive source 26 | code or can get it if you want it, that you can change the software or use pieces of 27 | it in new free programs, and that you know you can do these things. 28 | 29 | To protect your rights, we need to prevent others from denying you these rights or 30 | asking you to surrender the rights. Therefore, you have certain responsibilities if 31 | you distribute copies of the software, or if you modify it: responsibilities to 32 | respect the freedom of others. 33 | 34 | For example, if you distribute copies of such a program, whether gratis or for a fee, 35 | you must pass on to the recipients the same freedoms that you received. You must make 36 | sure that they, too, receive or can get the source code. And you must show them these 37 | terms so they know their rights. 38 | 39 | Developers that use the GNU GPL protect your rights with two steps: **(1)** assert 40 | copyright on the software, and **(2)** offer you this License giving you legal permission 41 | to copy, distribute and/or modify it. 42 | 43 | For the developers' and authors' protection, the GPL clearly explains that there is 44 | no warranty for this free software. For both users' and authors' sake, the GPL 45 | requires that modified versions be marked as changed, so that their problems will not 46 | be attributed erroneously to authors of previous versions. 47 | 48 | Some devices are designed to deny users access to install or run modified versions of 49 | the software inside them, although the manufacturer can do so. This is fundamentally 50 | incompatible with the aim of protecting users' freedom to change the software. The 51 | systematic pattern of such abuse occurs in the area of products for individuals to 52 | use, which is precisely where it is most unacceptable. Therefore, we have designed 53 | this version of the GPL to prohibit the practice for those products. If such problems 54 | arise substantially in other domains, we stand ready to extend this provision to 55 | those domains in future versions of the GPL, as needed to protect the freedom of 56 | users. 57 | 58 | Finally, every program is threatened constantly by software patents. States should 59 | not allow patents to restrict development and use of software on general-purpose 60 | computers, but in those that do, we wish to avoid the special danger that patents 61 | applied to a free program could make it effectively proprietary. To prevent this, the 62 | GPL assures that patents cannot be used to render the program non-free. 63 | 64 | The precise terms and conditions for copying, distribution and modification follow. 65 | 66 | ## TERMS AND CONDITIONS 67 | 68 | ### 0. Definitions 69 | 70 | “This License” refers to version 3 of the GNU General Public License. 71 | 72 | “Copyright” also means copyright-like laws that apply to other kinds of 73 | works, such as semiconductor masks. 74 | 75 | “The Program” refers to any copyrightable work licensed under this 76 | License. Each licensee is addressed as “you”. “Licensees” and 77 | “recipients” may be individuals or organizations. 78 | 79 | To “modify” a work means to copy from or adapt all or part of the work in 80 | a fashion requiring copyright permission, other than the making of an exact copy. The 81 | resulting work is called a “modified version” of the earlier work or a 82 | work “based on” the earlier work. 83 | 84 | A “covered work” means either the unmodified Program or a work based on 85 | the Program. 86 | 87 | To “propagate” a work means to do anything with it that, without 88 | permission, would make you directly or secondarily liable for infringement under 89 | applicable copyright law, except executing it on a computer or modifying a private 90 | copy. Propagation includes copying, distribution (with or without modification), 91 | making available to the public, and in some countries other activities as well. 92 | 93 | To “convey” a work means any kind of propagation that enables other 94 | parties to make or receive copies. Mere interaction with a user through a computer 95 | network, with no transfer of a copy, is not conveying. 96 | 97 | An interactive user interface displays “Appropriate Legal Notices” to the 98 | extent that it includes a convenient and prominently visible feature that **(1)** 99 | displays an appropriate copyright notice, and **(2)** tells the user that there is no 100 | warranty for the work (except to the extent that warranties are provided), that 101 | licensees may convey the work under this License, and how to view a copy of this 102 | License. If the interface presents a list of user commands or options, such as a 103 | menu, a prominent item in the list meets this criterion. 104 | 105 | ### 1. Source Code 106 | 107 | The “source code” for a work means the preferred form of the work for 108 | making modifications to it. “Object code” means any non-source form of a 109 | work. 110 | 111 | A “Standard Interface” means an interface that either is an official 112 | standard defined by a recognized standards body, or, in the case of interfaces 113 | specified for a particular programming language, one that is widely used among 114 | developers working in that language. 115 | 116 | The “System Libraries” of an executable work include anything, other than 117 | the work as a whole, that **(a)** is included in the normal form of packaging a Major 118 | Component, but which is not part of that Major Component, and **(b)** serves only to 119 | enable use of the work with that Major Component, or to implement a Standard 120 | Interface for which an implementation is available to the public in source code form. 121 | A “Major Component”, in this context, means a major essential component 122 | (kernel, window system, and so on) of the specific operating system (if any) on which 123 | the executable work runs, or a compiler used to produce the work, or an object code 124 | interpreter used to run it. 125 | 126 | The “Corresponding Source” for a work in object code form means all the 127 | source code needed to generate, install, and (for an executable work) run the object 128 | code and to modify the work, including scripts to control those activities. However, 129 | it does not include the work's System Libraries, or general-purpose tools or 130 | generally available free programs which are used unmodified in performing those 131 | activities but which are not part of the work. For example, Corresponding Source 132 | includes interface definition files associated with source files for the work, and 133 | the source code for shared libraries and dynamically linked subprograms that the work 134 | is specifically designed to require, such as by intimate data communication or 135 | control flow between those subprograms and other parts of the work. 136 | 137 | The Corresponding Source need not include anything that users can regenerate 138 | automatically from other parts of the Corresponding Source. 139 | 140 | The Corresponding Source for a work in source code form is that same work. 141 | 142 | ### 2. Basic Permissions 143 | 144 | All rights granted under this License are granted for the term of copyright on the 145 | Program, and are irrevocable provided the stated conditions are met. This License 146 | explicitly affirms your unlimited permission to run the unmodified Program. The 147 | output from running a covered work is covered by this License only if the output, 148 | given its content, constitutes a covered work. This License acknowledges your rights 149 | of fair use or other equivalent, as provided by copyright law. 150 | 151 | You may make, run and propagate covered works that you do not convey, without 152 | conditions so long as your license otherwise remains in force. You may convey covered 153 | works to others for the sole purpose of having them make modifications exclusively 154 | for you, or provide you with facilities for running those works, provided that you 155 | comply with the terms of this License in conveying all material for which you do not 156 | control copyright. Those thus making or running the covered works for you must do so 157 | exclusively on your behalf, under your direction and control, on terms that prohibit 158 | them from making any copies of your copyrighted material outside their relationship 159 | with you. 160 | 161 | Conveying under any other circumstances is permitted solely under the conditions 162 | stated below. Sublicensing is not allowed; section 10 makes it unnecessary. 163 | 164 | ### 3. Protecting Users' Legal Rights From Anti-Circumvention Law 165 | 166 | No covered work shall be deemed part of an effective technological measure under any 167 | applicable law fulfilling obligations under article 11 of the WIPO copyright treaty 168 | adopted on 20 December 1996, or similar laws prohibiting or restricting circumvention 169 | of such measures. 170 | 171 | When you convey a covered work, you waive any legal power to forbid circumvention of 172 | technological measures to the extent such circumvention is effected by exercising 173 | rights under this License with respect to the covered work, and you disclaim any 174 | intention to limit operation or modification of the work as a means of enforcing, 175 | against the work's users, your or third parties' legal rights to forbid circumvention 176 | of technological measures. 177 | 178 | ### 4. Conveying Verbatim Copies 179 | 180 | You may convey verbatim copies of the Program's source code as you receive it, in any 181 | medium, provided that you conspicuously and appropriately publish on each copy an 182 | appropriate copyright notice; keep intact all notices stating that this License and 183 | any non-permissive terms added in accord with section 7 apply to the code; keep 184 | intact all notices of the absence of any warranty; and give all recipients a copy of 185 | this License along with the Program. 186 | 187 | You may charge any price or no price for each copy that you convey, and you may offer 188 | support or warranty protection for a fee. 189 | 190 | ### 5. Conveying Modified Source Versions 191 | 192 | You may convey a work based on the Program, or the modifications to produce it from 193 | the Program, in the form of source code under the terms of section 4, provided that 194 | you also meet all of these conditions: 195 | 196 | * **a)** The work must carry prominent notices stating that you modified it, and giving a 197 | relevant date. 198 | * **b)** The work must carry prominent notices stating that it is released under this 199 | License and any conditions added under section 7. This requirement modifies the 200 | requirement in section 4 to “keep intact all notices”. 201 | * **c)** You must license the entire work, as a whole, under this License to anyone who 202 | comes into possession of a copy. This License will therefore apply, along with any 203 | applicable section 7 additional terms, to the whole of the work, and all its parts, 204 | regardless of how they are packaged. This License gives no permission to license the 205 | work in any other way, but it does not invalidate such permission if you have 206 | separately received it. 207 | * **d)** If the work has interactive user interfaces, each must display Appropriate Legal 208 | Notices; however, if the Program has interactive interfaces that do not display 209 | Appropriate Legal Notices, your work need not make them do so. 210 | 211 | A compilation of a covered work with other separate and independent works, which are 212 | not by their nature extensions of the covered work, and which are not combined with 213 | it such as to form a larger program, in or on a volume of a storage or distribution 214 | medium, is called an “aggregate” if the compilation and its resulting 215 | copyright are not used to limit the access or legal rights of the compilation's users 216 | beyond what the individual works permit. Inclusion of a covered work in an aggregate 217 | does not cause this License to apply to the other parts of the aggregate. 218 | 219 | ### 6. Conveying Non-Source Forms 220 | 221 | You may convey a covered work in object code form under the terms of sections 4 and 222 | 5, provided that you also convey the machine-readable Corresponding Source under the 223 | terms of this License, in one of these ways: 224 | 225 | * **a)** Convey the object code in, or embodied in, a physical product (including a 226 | physical distribution medium), accompanied by the Corresponding Source fixed on a 227 | durable physical medium customarily used for software interchange. 228 | * **b)** Convey the object code in, or embodied in, a physical product (including a 229 | physical distribution medium), accompanied by a written offer, valid for at least 230 | three years and valid for as long as you offer spare parts or customer support for 231 | that product model, to give anyone who possesses the object code either **(1)** a copy of 232 | the Corresponding Source for all the software in the product that is covered by this 233 | License, on a durable physical medium customarily used for software interchange, for 234 | a price no more than your reasonable cost of physically performing this conveying of 235 | source, or **(2)** access to copy the Corresponding Source from a network server at no 236 | charge. 237 | * **c)** Convey individual copies of the object code with a copy of the written offer to 238 | provide the Corresponding Source. This alternative is allowed only occasionally and 239 | noncommercially, and only if you received the object code with such an offer, in 240 | accord with subsection 6b. 241 | * **d)** Convey the object code by offering access from a designated place (gratis or for 242 | a charge), and offer equivalent access to the Corresponding Source in the same way 243 | through the same place at no further charge. You need not require recipients to copy 244 | the Corresponding Source along with the object code. If the place to copy the object 245 | code is a network server, the Corresponding Source may be on a different server 246 | (operated by you or a third party) that supports equivalent copying facilities, 247 | provided you maintain clear directions next to the object code saying where to find 248 | the Corresponding Source. Regardless of what server hosts the Corresponding Source, 249 | you remain obligated to ensure that it is available for as long as needed to satisfy 250 | these requirements. 251 | * **e)** Convey the object code using peer-to-peer transmission, provided you inform 252 | other peers where the object code and Corresponding Source of the work are being 253 | offered to the general public at no charge under subsection 6d. 254 | 255 | A separable portion of the object code, whose source code is excluded from the 256 | Corresponding Source as a System Library, need not be included in conveying the 257 | object code work. 258 | 259 | A “User Product” is either **(1)** a “consumer product”, which 260 | means any tangible personal property which is normally used for personal, family, or 261 | household purposes, or **(2)** anything designed or sold for incorporation into a 262 | dwelling. In determining whether a product is a consumer product, doubtful cases 263 | shall be resolved in favor of coverage. For a particular product received by a 264 | particular user, “normally used” refers to a typical or common use of 265 | that class of product, regardless of the status of the particular user or of the way 266 | in which the particular user actually uses, or expects or is expected to use, the 267 | product. A product is a consumer product regardless of whether the product has 268 | substantial commercial, industrial or non-consumer uses, unless such uses represent 269 | the only significant mode of use of the product. 270 | 271 | “Installation Information” for a User Product means any methods, 272 | procedures, authorization keys, or other information required to install and execute 273 | modified versions of a covered work in that User Product from a modified version of 274 | its Corresponding Source. The information must suffice to ensure that the continued 275 | functioning of the modified object code is in no case prevented or interfered with 276 | solely because modification has been made. 277 | 278 | If you convey an object code work under this section in, or with, or specifically for 279 | use in, a User Product, and the conveying occurs as part of a transaction in which 280 | the right of possession and use of the User Product is transferred to the recipient 281 | in perpetuity or for a fixed term (regardless of how the transaction is 282 | characterized), the Corresponding Source conveyed under this section must be 283 | accompanied by the Installation Information. But this requirement does not apply if 284 | neither you nor any third party retains the ability to install modified object code 285 | on the User Product (for example, the work has been installed in ROM). 286 | 287 | The requirement to provide Installation Information does not include a requirement to 288 | continue to provide support service, warranty, or updates for a work that has been 289 | modified or installed by the recipient, or for the User Product in which it has been 290 | modified or installed. Access to a network may be denied when the modification itself 291 | materially and adversely affects the operation of the network or violates the rules 292 | and protocols for communication across the network. 293 | 294 | Corresponding Source conveyed, and Installation Information provided, in accord with 295 | this section must be in a format that is publicly documented (and with an 296 | implementation available to the public in source code form), and must require no 297 | special password or key for unpacking, reading or copying. 298 | 299 | ### 7. Additional Terms 300 | 301 | “Additional permissions” are terms that supplement the terms of this 302 | License by making exceptions from one or more of its conditions. Additional 303 | permissions that are applicable to the entire Program shall be treated as though they 304 | were included in this License, to the extent that they are valid under applicable 305 | law. If additional permissions apply only to part of the Program, that part may be 306 | used separately under those permissions, but the entire Program remains governed by 307 | this License without regard to the additional permissions. 308 | 309 | When you convey a copy of a covered work, you may at your option remove any 310 | additional permissions from that copy, or from any part of it. (Additional 311 | permissions may be written to require their own removal in certain cases when you 312 | modify the work.) You may place additional permissions on material, added by you to a 313 | covered work, for which you have or can give appropriate copyright permission. 314 | 315 | Notwithstanding any other provision of this License, for material you add to a 316 | covered work, you may (if authorized by the copyright holders of that material) 317 | supplement the terms of this License with terms: 318 | 319 | * **a)** Disclaiming warranty or limiting liability differently from the terms of 320 | sections 15 and 16 of this License; or 321 | * **b)** Requiring preservation of specified reasonable legal notices or author 322 | attributions in that material or in the Appropriate Legal Notices displayed by works 323 | containing it; or 324 | * **c)** Prohibiting misrepresentation of the origin of that material, or requiring that 325 | modified versions of such material be marked in reasonable ways as different from the 326 | original version; or 327 | * **d)** Limiting the use for publicity purposes of names of licensors or authors of the 328 | material; or 329 | * **e)** Declining to grant rights under trademark law for use of some trade names, 330 | trademarks, or service marks; or 331 | * **f)** Requiring indemnification of licensors and authors of that material by anyone 332 | who conveys the material (or modified versions of it) with contractual assumptions of 333 | liability to the recipient, for any liability that these contractual assumptions 334 | directly impose on those licensors and authors. 335 | 336 | All other non-permissive additional terms are considered “further 337 | restrictions” within the meaning of section 10. If the Program as you received 338 | it, or any part of it, contains a notice stating that it is governed by this License 339 | along with a term that is a further restriction, you may remove that term. If a 340 | license document contains a further restriction but permits relicensing or conveying 341 | under this License, you may add to a covered work material governed by the terms of 342 | that license document, provided that the further restriction does not survive such 343 | relicensing or conveying. 344 | 345 | If you add terms to a covered work in accord with this section, you must place, in 346 | the relevant source files, a statement of the additional terms that apply to those 347 | files, or a notice indicating where to find the applicable terms. 348 | 349 | Additional terms, permissive or non-permissive, may be stated in the form of a 350 | separately written license, or stated as exceptions; the above requirements apply 351 | either way. 352 | 353 | ### 8. Termination 354 | 355 | You may not propagate or modify a covered work except as expressly provided under 356 | this License. Any attempt otherwise to propagate or modify it is void, and will 357 | automatically terminate your rights under this License (including any patent licenses 358 | granted under the third paragraph of section 11). 359 | 360 | However, if you cease all violation of this License, then your license from a 361 | particular copyright holder is reinstated **(a)** provisionally, unless and until the 362 | copyright holder explicitly and finally terminates your license, and **(b)** permanently, 363 | if the copyright holder fails to notify you of the violation by some reasonable means 364 | prior to 60 days after the cessation. 365 | 366 | Moreover, your license from a particular copyright holder is reinstated permanently 367 | if the copyright holder notifies you of the violation by some reasonable means, this 368 | is the first time you have received notice of violation of this License (for any 369 | work) from that copyright holder, and you cure the violation prior to 30 days after 370 | your receipt of the notice. 371 | 372 | Termination of your rights under this section does not terminate the licenses of 373 | parties who have received copies or rights from you under this License. If your 374 | rights have been terminated and not permanently reinstated, you do not qualify to 375 | receive new licenses for the same material under section 10. 376 | 377 | ### 9. Acceptance Not Required for Having Copies 378 | 379 | You are not required to accept this License in order to receive or run a copy of the 380 | Program. Ancillary propagation of a covered work occurring solely as a consequence of 381 | using peer-to-peer transmission to receive a copy likewise does not require 382 | acceptance. However, nothing other than this License grants you permission to 383 | propagate or modify any covered work. These actions infringe copyright if you do not 384 | accept this License. Therefore, by modifying or propagating a covered work, you 385 | indicate your acceptance of this License to do so. 386 | 387 | ### 10. Automatic Licensing of Downstream Recipients 388 | 389 | Each time you convey a covered work, the recipient automatically receives a license 390 | from the original licensors, to run, modify and propagate that work, subject to this 391 | License. You are not responsible for enforcing compliance by third parties with this 392 | License. 393 | 394 | An “entity transaction” is a transaction transferring control of an 395 | organization, or substantially all assets of one, or subdividing an organization, or 396 | merging organizations. If propagation of a covered work results from an entity 397 | transaction, each party to that transaction who receives a copy of the work also 398 | receives whatever licenses to the work the party's predecessor in interest had or 399 | could give under the previous paragraph, plus a right to possession of the 400 | Corresponding Source of the work from the predecessor in interest, if the predecessor 401 | has it or can get it with reasonable efforts. 402 | 403 | You may not impose any further restrictions on the exercise of the rights granted or 404 | affirmed under this License. For example, you may not impose a license fee, royalty, 405 | or other charge for exercise of rights granted under this License, and you may not 406 | initiate litigation (including a cross-claim or counterclaim in a lawsuit) alleging 407 | that any patent claim is infringed by making, using, selling, offering for sale, or 408 | importing the Program or any portion of it. 409 | 410 | ### 11. Patents 411 | 412 | A “contributor” is a copyright holder who authorizes use under this 413 | License of the Program or a work on which the Program is based. The work thus 414 | licensed is called the contributor's “contributor version”. 415 | 416 | A contributor's “essential patent claims” are all patent claims owned or 417 | controlled by the contributor, whether already acquired or hereafter acquired, that 418 | would be infringed by some manner, permitted by this License, of making, using, or 419 | selling its contributor version, but do not include claims that would be infringed 420 | only as a consequence of further modification of the contributor version. For 421 | purposes of this definition, “control” includes the right to grant patent 422 | sublicenses in a manner consistent with the requirements of this License. 423 | 424 | Each contributor grants you a non-exclusive, worldwide, royalty-free patent license 425 | under the contributor's essential patent claims, to make, use, sell, offer for sale, 426 | import and otherwise run, modify and propagate the contents of its contributor 427 | version. 428 | 429 | In the following three paragraphs, a “patent license” is any express 430 | agreement or commitment, however denominated, not to enforce a patent (such as an 431 | express permission to practice a patent or covenant not to sue for patent 432 | infringement). To “grant” such a patent license to a party means to make 433 | such an agreement or commitment not to enforce a patent against the party. 434 | 435 | If you convey a covered work, knowingly relying on a patent license, and the 436 | Corresponding Source of the work is not available for anyone to copy, free of charge 437 | and under the terms of this License, through a publicly available network server or 438 | other readily accessible means, then you must either **(1)** cause the Corresponding 439 | Source to be so available, or **(2)** arrange to deprive yourself of the benefit of the 440 | patent license for this particular work, or **(3)** arrange, in a manner consistent with 441 | the requirements of this License, to extend the patent license to downstream 442 | recipients. “Knowingly relying” means you have actual knowledge that, but 443 | for the patent license, your conveying the covered work in a country, or your 444 | recipient's use of the covered work in a country, would infringe one or more 445 | identifiable patents in that country that you have reason to believe are valid. 446 | 447 | If, pursuant to or in connection with a single transaction or arrangement, you 448 | convey, or propagate by procuring conveyance of, a covered work, and grant a patent 449 | license to some of the parties receiving the covered work authorizing them to use, 450 | propagate, modify or convey a specific copy of the covered work, then the patent 451 | license you grant is automatically extended to all recipients of the covered work and 452 | works based on it. 453 | 454 | A patent license is “discriminatory” if it does not include within the 455 | scope of its coverage, prohibits the exercise of, or is conditioned on the 456 | non-exercise of one or more of the rights that are specifically granted under this 457 | License. You may not convey a covered work if you are a party to an arrangement with 458 | a third party that is in the business of distributing software, under which you make 459 | payment to the third party based on the extent of your activity of conveying the 460 | work, and under which the third party grants, to any of the parties who would receive 461 | the covered work from you, a discriminatory patent license **(a)** in connection with 462 | copies of the covered work conveyed by you (or copies made from those copies), or **(b)** 463 | primarily for and in connection with specific products or compilations that contain 464 | the covered work, unless you entered into that arrangement, or that patent license 465 | was granted, prior to 28 March 2007. 466 | 467 | Nothing in this License shall be construed as excluding or limiting any implied 468 | license or other defenses to infringement that may otherwise be available to you 469 | under applicable patent law. 470 | 471 | ### 12. No Surrender of Others' Freedom 472 | 473 | If conditions are imposed on you (whether by court order, agreement or otherwise) 474 | that contradict the conditions of this License, they do not excuse you from the 475 | conditions of this License. If you cannot convey a covered work so as to satisfy 476 | simultaneously your obligations under this License and any other pertinent 477 | obligations, then as a consequence you may not convey it at all. For example, if you 478 | agree to terms that obligate you to collect a royalty for further conveying from 479 | those to whom you convey the Program, the only way you could satisfy both those terms 480 | and this License would be to refrain entirely from conveying the Program. 481 | 482 | ### 13. Use with the GNU Affero General Public License 483 | 484 | Notwithstanding any other provision of this License, you have permission to link or 485 | combine any covered work with a work licensed under version 3 of the GNU Affero 486 | General Public License into a single combined work, and to convey the resulting work. 487 | The terms of this License will continue to apply to the part which is the covered 488 | work, but the special requirements of the GNU Affero General Public License, section 489 | 13, concerning interaction through a network will apply to the combination as such. 490 | 491 | ### 14. Revised Versions of this License 492 | 493 | The Free Software Foundation may publish revised and/or new versions of the GNU 494 | General Public License from time to time. Such new versions will be similar in spirit 495 | to the present version, but may differ in detail to address new problems or concerns. 496 | 497 | Each version is given a distinguishing version number. If the Program specifies that 498 | a certain numbered version of the GNU General Public License “or any later 499 | version” applies to it, you have the option of following the terms and 500 | conditions either of that numbered version or of any later version published by the 501 | Free Software Foundation. If the Program does not specify a version number of the GNU 502 | General Public License, you may choose any version ever published by the Free 503 | Software Foundation. 504 | 505 | If the Program specifies that a proxy can decide which future versions of the GNU 506 | General Public License can be used, that proxy's public statement of acceptance of a 507 | version permanently authorizes you to choose that version for the Program. 508 | 509 | Later license versions may give you additional or different permissions. However, no 510 | additional obligations are imposed on any author or copyright holder as a result of 511 | your choosing to follow a later version. 512 | 513 | ### 15. Disclaimer of Warranty 514 | 515 | THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. 516 | EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES 517 | PROVIDE THE PROGRAM “AS IS” WITHOUT WARRANTY OF ANY KIND, EITHER 518 | EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 519 | MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE 520 | QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE 521 | DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 522 | 523 | ### 16. Limitation of Liability 524 | 525 | IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY 526 | COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS THE PROGRAM AS 527 | PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, 528 | INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE 529 | PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE 530 | OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE 531 | WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE 532 | POSSIBILITY OF SUCH DAMAGES. 533 | 534 | ### 17. Interpretation of Sections 15 and 16 535 | 536 | If the disclaimer of warranty and limitation of liability provided above cannot be 537 | given local legal effect according to their terms, reviewing courts shall apply local 538 | law that most closely approximates an absolute waiver of all civil liability in 539 | connection with the Program, unless a warranty or assumption of liability accompanies 540 | a copy of the Program in return for a fee. 541 | 542 | _END OF TERMS AND CONDITIONS_ 543 | 544 | ## How to Apply These Terms to Your New Programs 545 | 546 | If you develop a new program, and you want it to be of the greatest possible use to 547 | the public, the best way to achieve this is to make it free software which everyone 548 | can redistribute and change under these terms. 549 | 550 | To do so, attach the following notices to the program. It is safest to attach them 551 | to the start of each source file to most effectively state the exclusion of warranty; 552 | and each file should have at least the “copyright” line and a pointer to 553 | where the full notice is found. 554 | 555 | 556 | Copyright (C) 557 | 558 | This program is free software: you can redistribute it and/or modify 559 | it under the terms of the GNU General Public License as published by 560 | the Free Software Foundation, either version 3 of the License, or 561 | (at your option) any later version. 562 | 563 | This program is distributed in the hope that it will be useful, 564 | but WITHOUT ANY WARRANTY; without even the implied warranty of 565 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 566 | GNU General Public License for more details. 567 | 568 | You should have received a copy of the GNU General Public License 569 | along with this program. If not, see . 570 | 571 | Also add information on how to contact you by electronic and paper mail. 572 | 573 | If the program does terminal interaction, make it output a short notice like this 574 | when it starts in an interactive mode: 575 | 576 | Copyright (C) 577 | This program comes with ABSOLUTELY NO WARRANTY; for details type 'show w'. 578 | This is free software, and you are welcome to redistribute it 579 | under certain conditions; type 'show c' for details. 580 | 581 | The hypothetical commands `show w` and `show c` should show the appropriate parts of 582 | the General Public License. Of course, your program's commands might be different; 583 | for a GUI interface, you would use an “about box”. 584 | 585 | You should also get your employer (if you work as a programmer) or school, if any, to 586 | sign a “copyright disclaimer” for the program, if necessary. For more 587 | information on this, and how to apply and follow the GNU GPL, see 588 | <>. 589 | 590 | The GNU General Public License does not permit incorporating your program into 591 | proprietary programs. If your program is a subroutine library, you may consider it 592 | more useful to permit linking proprietary applications with the library. If this is 593 | what you want to do, use the GNU Lesser General Public License instead of this 594 | License. But first, please read 595 | <>. 596 | --------------------------------------------------------------------------------