Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
bec750e
initial version of new_blog_post
topepo Apr 12, 2024
b41f130
add namespace to call
topepo Apr 12, 2024
a8e7d13
initial unit tests
topepo Apr 12, 2024
ad0a01b
news file update related to #22
topepo Apr 12, 2024
56be9fb
use test fixtures
Apr 16, 2024
0f2942d
set env var for testing
Apr 18, 2024
4066f0e
Merge remote-tracking branch 'upstream/main'
Apr 26, 2024
a229171
Merge remote-tracking branch 'upstream/main'
Jul 18, 2024
cc2adcf
conditionally use whoami
Jul 18, 2024
9aaa636
Merge branch 'main' into main
cderv Jun 10, 2025
dbc286c
Rename file and reformat
cderv Jun 10, 2025
be42dbd
let's use .call like other places in this package
cderv Jun 10, 2025
0722da0
leverage usethis if it is installed to open the file
cderv Jun 10, 2025
08d1bad
xfun is an import
cderv Jun 10, 2025
a049e03
use .file iniline markup
cderv Jun 10, 2025
e2d1c99
Add as_yaml to use same handler as write_yaml
cderv Jun 10, 2025
380f65b
Revert "let's use .call like other places in this package"
cderv Jun 10, 2025
e3a7edd
fixup YAML importFrom
cderv Jun 10, 2025
7ed3c89
inherits call doc from rlang
cderv Jun 10, 2025
36d05c0
use internal merge_list
cderv Jun 10, 2025
34c52af
document usage of `whoami`
cderv Jun 10, 2025
8305306
fixup rename test file
cderv Jun 10, 2025
3045268
Update test
cderv Jun 10, 2025
21541e0
Check that this is run in the project root directory
cderv Jun 10, 2025
9ee3784
Add a way to change working directory
cderv Jun 10, 2025
f0e7198
document
cderv Jun 10, 2025
b19582d
bump version
cderv Jun 10, 2025
b09e75d
Add new function to pkgdown
cderv Jun 10, 2025
dde5346
bump again to avoid conflict
cderv Jun 10, 2025
975ca64
Merge branch 'main' into main
cderv Jun 10, 2025
dd0880e
Correct snapshot by hidding path
cderv Jun 10, 2025
94f1229
fix tests
cderv Jun 10, 2025
6327e8c
Correctly try usethis
cderv Jun 10, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions DESCRIPTION
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
Package: quarto
Title: R Interface to 'Quarto' Markdown Publishing System
Version: 1.4.4.9012
Version: 1.4.4.9013
Authors@R: c(
person("JJ", "Allaire", , "jj@posit.co", role = "aut",
comment = c(ORCID = "0000-0003-0174-9868")),
Expand All @@ -19,6 +19,7 @@ Depends:
R (>= 3.6)
Imports:
cli,
fs,
jsonlite,
later,
processx,
Expand All @@ -27,6 +28,7 @@ Imports:
rstudioapi,
tools,
utils,
xfun,
yaml
Suggests:
bslib,
Expand All @@ -48,7 +50,7 @@ Suggests:
thematic,
tidyverse,
withr,
xfun
whoami
VignetteBuilder:
quarto
Config/testthat/edition: 3
Expand Down
2 changes: 2 additions & 0 deletions NAMESPACE
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
# Generated by roxygen2: do not edit by hand

export(is_using_quarto)
export(new_blog_post)
export(quarto_add_extension)
export(quarto_available)
export(quarto_binary_sitrep)
Expand Down Expand Up @@ -42,4 +43,5 @@ importFrom(rstudioapi,isAvailable)
importFrom(rstudioapi,viewer)
importFrom(tools,vignetteEngine)
importFrom(utils,browseURL)
importFrom(yaml,as.yaml)
importFrom(yaml,write_yaml)
2 changes: 2 additions & 0 deletions NEWS.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
# quarto (development version)

- Added a `new_blog_post()` function (thanks, @topeto, #22).

- Make `quarto_render(as_job = TRUE)` wrapable (thanks, @salim-b, #105).

- Quarto CLI will now correctly use the same R version than the one used to run functions in this package (#204).
Expand Down
111 changes: 111 additions & 0 deletions R/blog.R
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
#' Create a new blog post
#'
#' Creates (and potentially opens) the `index.qmd` file for a new blog post.
#'
#' @inheritParams rlang::args_error_context
#' @param title A character string for the title of the post. It is converted
#' to title case via [tools::toTitleCase()].
#' @param dest A character string (or NULL) for the path within `posts`. By
#' default, the title is adapted as the directory name.
#' @param wd An optional working directory. If `NULL`, the current working is used.
#' @param open A logical: have the default editor open a window to edit the
#' `index.qmd` file?
#' @param ... A named list of values to be added to the yaml header, such as
#' `date`, `author`, `categories`, `description`, etc.
#' If no `date` is provided, the current date is used.
#' If no `author` is provided, `whoami::fullname()` is used to get the user's name.
#' @return The path to the index file.
#' @export
#' @examples
#' \dontrun{
#' \donttest{
#' new_blog_post("making quarto blog posts", categories = c("R"))
#'
#' }
#' }
#'
new_blog_post <- function(
title,
dest = NULL,
wd = NULL,
open = rlang::is_interactive(),
call = rlang::current_env(),
...
) {
rlang::check_installed("whoami")

if (is.null(dest)) {
# Scrub title to make directory name
dest <- gsub("[[:space:]]", "-", tolower(title))
}
dest_path <- make_post_dir(dest, wd, call)
post_yaml <- make_post_yaml(title, ...)
qmd_path <- write_post_yaml(post_yaml, dest_path, call)
if (open) {
edit_file <- utils::file.edit

Check warning on line 45 in R/blog.R

View check run for this annotation

Codecov / codecov/patch

R/blog.R#L45

Added line #L45 was not covered by tests
if (
rlang::is_installed("usethis") &&
is.function(asNamespace("usethis")$edit_file)

Check warning on line 48 in R/blog.R

View check run for this annotation

Codecov / codecov/patch

R/blog.R#L47-L48

Added lines #L47 - L48 were not covered by tests
) {
edit_file <- utils::getFromNamespace("edit_file", "usethis")

Check warning on line 50 in R/blog.R

View check run for this annotation

Codecov / codecov/patch

R/blog.R#L50

Added line #L50 was not covered by tests
}
edit_file(qmd_path)

Check warning on line 52 in R/blog.R

View check run for this annotation

Codecov / codecov/patch

R/blog.R#L52

Added line #L52 was not covered by tests
}
invisible(qmd_path)
}

make_post_dir <- function(dest, wd, call) {
working <- if (is.null(wd)) fs::path_wd() else fs::path_abs(wd)

# is this a quarto project for blog ? Expecting _quarto.yml in working dir
if (!fs::file_exists(fs::path(working, "_quarto.yml"))) {
cli::cli_abort(
"You need to be at root of a Quarto project to create a blog post in the {.file posts/} directory at {.file {fs::path_real(working)}}.",
call = call
)
}

post_path <- fs::path(working, "posts", dest)

if (fs::dir_exists(post_path)) {
cli::cli_abort(
"There is already a {.file {dest}} directory in 'posts/'",
call = call
)
} else {
ret <- fs::dir_create(post_path)
}
ret
}

make_post_yaml <- function(title, ...) {
default_values <- list(
title = tools::toTitleCase(title),
author = tools::toTitleCase(whoami::fullname("Your name")),
date = format(Sys.Date(), "%Y-%m-%d"),
categories = character(0)
)

user_values <- list(...)

yml_values <- merge_list(default_values, user_values)
if (length(yml_values$categories) == 0) {
yml_values <- yml_values[names(yml_values) != "categories"]
}
yml_values <- as_yaml(yml_values)
yml_values <- paste0("---\n", yml_values, "---\n")
yml_values
}

write_post_yaml <- function(x, dest, call) {
dest_file <- fs::path(dest, "index.qmd")
if (fs::file_exists(dest_file)) {
cli::cli_abort(
"There is already am index.qmd file at {.code {path}}",
call = call
)

Check warning on line 106 in R/blog.R

View check run for this annotation

Codecov / codecov/patch

R/blog.R#L103-L106

Added lines #L103 - L106 were not covered by tests
} else {
ret <- xfun::write_utf8(x, con = dest_file)
}
dest_file
}
36 changes: 27 additions & 9 deletions R/utils.R
Original file line number Diff line number Diff line change
Expand Up @@ -3,17 +3,25 @@ relative_to_wd <- function(path) {
rmarkdown::relative_to(getwd(), path)
}

# Specific YAML handlers
# as quarto expects YAML 1.2 and yaml R package supports 1.1
yaml_handlers <- list(
# Handle yes/no from 1.1 to 1.2
# https://github.com/vubiostat/r-yaml/issues/131
logical = function(x) {
value <- ifelse(x, "true", "false")
structure(value, class = "verbatim")
}
)

#' @importFrom yaml as.yaml
as_yaml <- function(x) {
yaml::as.yaml(x, handlers = yaml_handlers)
}

#' @importFrom yaml write_yaml
write_yaml <- function(x, file) {
handlers <- list(
# Handle yes/no from 1.1 to 1.2
# https://github.com/vubiostat/r-yaml/issues/131
logical = function(x) {
value <- ifelse(x, "true", "false")
structure(value, class = "verbatim")
}
)
yaml::write_yaml(x, file, handlers = handlers)
yaml::write_yaml(x, file, handlers = yaml_handlers)
}


Expand All @@ -34,3 +42,13 @@ in_positron <- function() {
in_rstudio <- function() {
identical(Sys.getenv("RSTUDIO"), "1")
}


# for test

hide_path <- function(path) {
function(x) {
x <- gsub(path, "<project directory>", x, fixed = TRUE)
gsub(fs::path_real(path), "<project directory>", x, fixed = TRUE)
}
}
1 change: 1 addition & 0 deletions _pkgdown.yml
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ reference:
More about Quarto extensions at <https://quarto.org/docs/projects/quarto-projects.html>
contents:
- quarto_create_project
- new_blog_post

- title: "Configuration"
desc: >
Expand Down
52 changes: 52 additions & 0 deletions man/new_blog_post.Rd

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

24 changes: 24 additions & 0 deletions tests/testthat/_snaps/blog.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
# Create a blog post

Code
new_blog_post("Intro to Felt Surrogacy", data = "1999-12-31", open = FALSE)
Condition
Error in `new_blog_post()`:
! There is already a 'intro-to-felt-surrogacy' directory in 'posts/'

# Error if not a quarto project

Code
new_blog_post("Intro to Felt Surrogacy", open = FALSE)
Condition
Error in `new_blog_post()`:
! You need to be at root of a Quarto project to create a blog post in the 'posts/' directory at '<project directory>'.

---

Code
new_blog_post("Intro to Felt Surrogacy", wd = tmp_dir, open = FALSE)
Condition
Error in `new_blog_post()`:
! You need to be at root of a Quarto project to create a blog post in the 'posts/' directory at '<project directory>'.

8 changes: 8 additions & 0 deletions tests/testthat/_snaps/new-blog-post.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
# Create a blog post

Code
new_blog_post("Intro to Felt Surrogacy", data = "1999-12-31", open = FALSE)
Condition
Error in `new_blog_post()`:
! There is already a `intro-to-felt-surrogacy` directory in 'posts/'

Loading