Skip to content

Commit

Permalink
callouts, headings, lists, and more (#1, #4)
Browse files Browse the repository at this point in the history
- qto_block() and qto_div()
- qto_span()
- qto_dl(), qto_li(), and qto_ol()
- qto_heading() and qto_hr()
- qto_fig()
- qto_shortcode() (and related functions
- div, span, mdapply, and as_markdown are now marked as internal functions
  • Loading branch information
elipousson authored Dec 14, 2023
1 parent 8976333 commit c102a7b
Show file tree
Hide file tree
Showing 59 changed files with 2,804 additions and 61 deletions.
4 changes: 2 additions & 2 deletions .lintr
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
linters: with_defaults(
linters: linters_with_defaults(
line_length_linter(120),
implicit_integer_linter(),
indentation_linter(indent = 4L),
object_usage_linter = NULL,
object_name_linter = NULL
)
exclusions: list("man/", "inst/", "src/", ".vscode/", ".Rproj.user/")
exclusions: list("man/", "inst/", "src/", ".vscode/", ".Rproj.user/", "R/import-standalone-obj-type.R", "R/import-standalone-types-check.R")
encoding: "UTF-8"
24 changes: 15 additions & 9 deletions DESCRIPTION
Original file line number Diff line number Diff line change
@@ -1,21 +1,27 @@
Package: quartools
Title: Programmatic Element Creation For Quarto Documents
Version: 0.0.0.9000
Authors@R:
Authors@R: c(
person("Elian", "Thiele-Evans", , "[email protected]", role = c("aut", "cre"),
comment = c(ORCID = "0000-0001-8008-3165"))
comment = c(ORCID = "0000-0001-8008-3165")),
person("Eli", "Pousson", , "[email protected]", role = "aut",
comment = c(ORCID = "0000-0001-8280-1706"))
)
Description: Programatically generate quarto-compliant markdown elements.
License: GPL (>= 3)
Encoding: UTF-8
Roxygen: list(markdown = TRUE)
RoxygenNote: 7.2.3.9000
URL: https://github.com/ElianHugh/quartools
BugReports: https://github.com/ElianHugh/quartools/issues
Imports:
rlang
rlang (>= 1.1.0)
Suggests:
htmltools,
knitr,
quarto,
rmarkdown,
testthat (>= 3.0.0)
VignetteBuilder:
knitr
Config/testthat/edition: 3
URL: https://github.com/ElianHugh/quartools
BugReports: https://github.com/ElianHugh/quartools/issues
VignetteBuilder: knitr
Encoding: UTF-8
Roxygen: list(markdown = TRUE)
RoxygenNote: 7.2.3.9000
18 changes: 17 additions & 1 deletion NAMESPACE
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,27 @@
S3method(print,quarto_block)
export(as_markdown)
export(div)
export(map_qto)
export(mdapply)
export(qto_attributes)
export(qto_block)
export(qto_callout)
export(qto_div)
export(qto_dl)
export(qto_fig)
export(qto_heading)
export(qto_hr)
export(qto_kbd)
export(qto_li)
export(qto_ol)
export(qto_pagebreak)
export(qto_shortcode)
export(qto_span)
export(qto_video)
export(span)
export(with_body_column)
export(with_margin_column)
export(with_page_column)
export(with_screen_column)
export(with_screen_inset_column)
importFrom(rlang,"%||%")
import(rlang)
126 changes: 126 additions & 0 deletions R/attributes.R
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
#' Create an attribute string for a div or span
#'
#' [qto_attributes()] creates an attribute string used by [qto_div()],
#' [qto_span()], or [qto_fig_span()].
#'
#' @param ... Optional named attributes.
#' @param id Div or span identifier. If `id` does not start with `"#"`, the
#' hash character is applied as a prefix.
#' @param class Div or span class. If `class` does not start with `"."`, the
#' period character is applied as a prefix.
#' @param css If `{htmltools}` is installed, a list of css style attributes to
#' pass to [htmltools::css()].
#' @param .attributes Optional list of attributes. If supplied, any attributes
#' passed to `...` are ignored.
#' @param .output Output type. If "embrace", the returned attributes are
#' enclosed in curly brackets.
#' @param .drop_empty If `TRUE`, empty attributes are dropped.
#' @examples
#' qto_attributes(id = "id", class = "class")
#'
#' qto_attributes(class = "class", key1 = "val", key2 = "val")
#'
#' qto_attributes(width = 4)
#'
#' @export
qto_attributes <- function(id = NULL,
class = NULL,
css = NULL,
...,
.attributes = NULL,
.output = "embrace",
.drop_empty = TRUE) {
if (is_string(id) && !grepl("^#", id)) {
id <- paste0("#", id)
}

if (is_string(class) && !grepl("^\\.", class)) {
class <- paste0(".", class)
}

if (!is.null(css) && is_installed("htmltools")) {
css <- htmltools::css(!!!css)
}

.attributes <- as_qto_attr(
...,
.attributes = .attributes,
.drop_empty = .drop_empty,
.collapse = " "
)

if (!is.null(c(id, class, css, .attributes))) {
.attributes <- paste0(c(id, class, css, .attributes), collapse = " ")
}

switch(.output,
embrace = embrace(.attributes),
.attributes
)
}

#' Helper function to create key-value attribute strings
#'
#' @noRd
as_qto_attr <- function(...,
.attributes = NULL,
op = "=",
before = "",
after = "",
.collapse = " ",
.drop_empty = TRUE,
.drop_na = TRUE,
.replacement = NULL) {
.attributes <- .attributes %||% dots_list(...)

if (.drop_empty) {
.attributes <- list_drop_empty(.attributes)
}

if (is_empty(.attributes)) {
return(NULL)
}

.attributes <- list_drop_or_replace_na(
.attributes,
drop_na = .drop_na,
replacement = .replacement
)

if (is_named(.attributes)) {
.attributes <- paste0(
names(.attributes), op, qto_attr_values(.attributes),
collapse = .collapse
)
} else {
.attributes <- paste0(.attributes, collapse = .collapse)
}

paste0(before, .attributes, after)
}


#' Helper function to sanitize attribute values
#'
#' @noRd
qto_attr_values <- function(values, mark = "'") {
vapply(
values,
\(x) {
if (is.logical(x)) {
return(tolower(x))
}

if (grepl(r"{\b[0-9]+\.?[0-9]*%\b}", x)) {
mark <- ""
}

if (is.character(x)) {
return(combine(x, mark, mark))
}

as.character(x)
},
NA_character_
)
}
33 changes: 33 additions & 0 deletions R/basics.R
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
#' Basic formatting and markdown elements
#'
#' [qto_heading()] creates headings and [qto_hr()] creates horizontal rules.
#'
#' @name qto_basics
NULL

#' @rdname qto_basics
#' @name qto_heading
#' @param ... Heading text passed sto [qto_block()].
#' @param level Heading level. Defaults to 1.
#' @export
qto_heading <- function(...,
level = 1L) {
qto_block(strrep("#", level), " ", ...)
}

#' @rdname qto_basics
#' @name qto_heading
#' @param rule,length Horizontal rule character and length of rule.
#' @param before,after Text to insert before and after a horizontal rule.
#' @export
qto_hr <- function(rule = "-",
length = 72L,
before = "\n\n",
after = before) {
rule <- arg_match0(rule, c("-", "_", "*"))
qto_block(
before,
strrep(rule, length),
after
)
}
112 changes: 112 additions & 0 deletions R/block.R
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
#' Create a block of text to render as Markdown text in Quarto
#'
#' [qto_block()] passes the input to [paste()] and assigns the classes
#' "knit_asis" and "quarto_block".
#'
#' @param ... dots to convert to character vector
#' @inheritParams base::paste
#' @inheritParams rlang::args_error_context
#' @examples
#' qto_block("Hello world!")
#'
#' qto_block("Hello", "world!", sep = " ")
#'
#' qto_block("- ", LETTERS[1:4], collapse = "\n")
#'
#' @return character vector of length 1
#' @seealso
#' - [as_markdown()]
#' - [knitr::asis_output()]
#'
#' @export
qto_block <- function(..., sep = "", collapse = "", call = caller_env()) {
check_dots_unnamed(call = call)
structure(
paste(..., sep = sep, collapse = collapse),
class = c("knit_asis", "quarto_block")
)
}

#' Create a Quarto div with optional classes, attributes, and other identifiers
#'
#' <https://quarto.org/docs/authoring/markdown-basics.html#divs-and-spans>
#'
#' @inheritParams qto_attributes
#' @param .content If `.content` is supplied, any values passed to `...` are
#' ignored. If `.content` is `NULL`, it is set as all values passed to `...`.
#' @param collapse Passed to [base::paste0()] with `.content.`
#' @param drop_empty If `TRUE`, drop empty values from `.content` or `...`
#' @param drop_na If `TRUE`, drop `NA` values from `.content` or `...`
#' @inheritParams qto_block
#' @seealso
#' - [qto_callout()]
#' @examples
#'
#' # div with an class
#' qto_div(
#' "This content can be styled with a border",
#' class = "border"
#' )
#'
#' # Nested div
#' qto_div(
#' qto_div("Here is a warning.", class = "warning"),
#' "More content.",
#' id = "special",
#' class = "sidebar"
#' )
#'
#' @export
qto_div <- function(...,
id = NULL,
class = NULL,
css = NULL,
.attributes = NULL,
.content = NULL,
collapse = "",
drop_empty = TRUE,
drop_na = TRUE,
call = caller_env()) {
check_dots_unnamed()

.content <- .content %||% dots_list(...)

if (drop_empty) {
.content <- list_drop_empty(.content)
}

if (drop_na) {
.content <- list_drop_na(.content)
}

.attributes <- qto_attributes(
id = id,
class = class,
css = css,
.attributes = .attributes
)

qto_block(
qto_fence(.attributes = .attributes),
paste0(.content, collapse = collapse),
qto_fence(),
call = call
)
}

#' Create a fence for a div or code block
#'
#' @param fence If numeric, `fence` must be a minimum of 3 and sets the number
#' of times the standard fence character ":" should be repeated. If character,
#' `fence` is used as is.
#' @noRd
qto_fence <- function(fence = ":::", .attributes = NULL, .sep = " ") {
if (is.numeric(fence)) {
stopifnot(fence > 2L)
fence <- strrep(":", fence)
}

paste0(
"\n", fence, .sep, .attributes %||% "", "\n"
)
}
Loading

0 comments on commit c102a7b

Please sign in to comment.