-
Notifications
You must be signed in to change notification settings - Fork 24
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
function for making new blog posts #171
base: main
Are you sure you want to change the base?
Changes from 7 commits
bec750e
b41f130
a8e7d13
ad0a01b
56be9fb
0f2942d
4066f0e
a229171
cc2adcf
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
@@ -1,5 +1,7 @@ | ||||||
# quarto (development version) | ||||||
|
||||||
- Added a `new_blog_post()` function (#22). | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
|
||||||
- Approval check in `quarto_add_extension()` and `quarto_use_template()` now works correctly (thanks, @eveyp, #172). | ||||||
|
||||||
# quarto 1.4 | ||||||
|
Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
@@ -0,0 +1,82 @@ | ||||||||||||||||||||||||
#' Create a new blog post | ||||||||||||||||||||||||
#' | ||||||||||||||||||||||||
#' Creates (and potentially opens) the `index.qmd` file for a new blog post. | ||||||||||||||||||||||||
#' | ||||||||||||||||||||||||
#' @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 open A logical: have the default editor open a window to edit the | ||||||||||||||||||||||||
#' `index.qmd` file? | ||||||||||||||||||||||||
#' @param call A call object for reporting errors. | ||||||||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. My understanding is that the prefered way is to use
From https://rlang.r-lib.org/reference/args_error_context.html and usage at several tidyverse packages |
||||||||||||||||||||||||
#' @param ... A named list of values to be added to the yaml header, such as | ||||||||||||||||||||||||
#' `description`, `author`, `categories`, etc. | ||||||||||||||||||||||||
#' @return The path to the index file. | ||||||||||||||||||||||||
#' @export | ||||||||||||||||||||||||
#' @examples | ||||||||||||||||||||||||
#' \dontrun{ | ||||||||||||||||||||||||
cderv marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||||||||||||||||||||
#' \donttest{ | ||||||||||||||||||||||||
#' new_blog_post("making quarto blog posts", categories = c("R")) | ||||||||||||||||||||||||
#' | ||||||||||||||||||||||||
#' } | ||||||||||||||||||||||||
#' } | ||||||||||||||||||||||||
#' | ||||||||||||||||||||||||
new_blog_post <- function(title, dest = NULL, open = rlang::is_interactive(), | ||||||||||||||||||||||||
call = rlang::current_env(), ...) { | ||||||||||||||||||||||||
|
||||||||||||||||||||||||
if (is.null(dest)) { | ||||||||||||||||||||||||
# Scrub title to make directory name | ||||||||||||||||||||||||
dest <- gsub("[[:space:]]", "-", tolower(title)) | ||||||||||||||||||||||||
topepo marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||||||||||||||||||||
} | ||||||||||||||||||||||||
dest_path <- make_post_dir(dest, call) | ||||||||||||||||||||||||
post_yaml <- make_post_yaml(title, ...) | ||||||||||||||||||||||||
qmd_path <- write_post_yaml(post_yaml, dest_path, call) | ||||||||||||||||||||||||
if (open) { | ||||||||||||||||||||||||
utils::file.edit(qmd_path) | ||||||||||||||||||||||||
} | ||||||||||||||||||||||||
invisible(qmd_path) | ||||||||||||||||||||||||
} | ||||||||||||||||||||||||
|
||||||||||||||||||||||||
make_post_dir <- function(dest, call) { | ||||||||||||||||||||||||
working <- fs::path_wd() | ||||||||||||||||||||||||
|
||||||||||||||||||||||||
post_path <- fs::path(working, "posts", dest) | ||||||||||||||||||||||||
Comment on lines
+42
to
+44
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Should we try to make sure this is called at root of the quarto project ? So that if this function is called elsewhere in the project directory we don't create a post folder where we wouldn't want ? Maybe this check could be in
Related to some thinking at #180 (but this one is really about computing path during knitting) |
||||||||||||||||||||||||
|
||||||||||||||||||||||||
if (fs::dir_exists(post_path)) { | ||||||||||||||||||||||||
cli::cli_abort("There is already a {.code {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")), | ||||||||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We could document for .Rd file that whoami is used as default when available We could also easily make this deps a suggest by using it only if available tools::toTitleCase(if(rlang::is_installed("whoami")) whoami::fullname("Your name") else "Your name") |
||||||||||||||||||||||||
date = format(Sys.Date(), "%Y-%m-%d"), | ||||||||||||||||||||||||
categories = character(0) | ||||||||||||||||||||||||
) | ||||||||||||||||||||||||
|
||||||||||||||||||||||||
user_values <- list(...) | ||||||||||||||||||||||||
|
||||||||||||||||||||||||
yml_values <- utils::modifyList(default_values, user_values) | ||||||||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Probably I forgot about |
||||||||||||||||||||||||
if (length(yml_values$categories) == 0) { | ||||||||||||||||||||||||
yml_values <- yml_values[names(yml_values) != "categories"] | ||||||||||||||||||||||||
} | ||||||||||||||||||||||||
yml_values <- yaml::as.yaml(yml_values) | ||||||||||||||||||||||||
yml_values <- paste0("---\n", yml_values, "---\n") | ||||||||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Also, maybe there is a better API. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. While dealing with YAML in quarto context we have the issue of yaml R package being 1.1 spec, and Quarto using 1.2 spec. So it requires some specific handlers as workaround. See Lines 6 to 16 in d71b135
We probably need to adapt for generating not in a file, or refactor the handlers so that we can use them in other functions. Regaring This post creation may also be done through templating 🤔 Having a whisker template (or else) to file out with some information. Probably more complex than necessary, but we did that for bookdown skeleton for example using placeholder in template file (https://github.com/rstudio/bookdown/blob/f244cf12bf2c2d7106ac6322b2b2a5796d4ef0c8/R/skeleton.R#L66-L83) I am fine with current way |
||||||||||||||||||||||||
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) | ||||||||||||||||||||||||
} else { | ||||||||||||||||||||||||
ret <- cat(x, file = dest_file) | ||||||||||||||||||||||||
} | ||||||||||||||||||||||||
dest_file | ||||||||||||||||||||||||
} |
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
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/' | ||
|
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
@@ -0,0 +1,68 @@ | ||||||
test_that("Create a blog post", { | ||||||
skip_if_no_quarto("1.4") | ||||||
|
||||||
current_dir <- getwd() | ||||||
|
||||||
temp_dir <- withr::local_tempdir() | ||||||
dir_path <- fs::path(temp_dir, "test-blog-project") | ||||||
|
||||||
withr::defer(fs::dir_delete(dir_path), envir = rlang::current_env()) | ||||||
Comment on lines
+7
to
+10
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||||||
|
||||||
quarto_create_project(name = "test-blog-project", type = "blog", | ||||||
dir = temp_dir, quiet = TRUE) | ||||||
|
||||||
setwd(dir_path) | ||||||
withr::defer(setwd(current_dir), envir = rlang::current_env()) | ||||||
Comment on lines
+15
to
+16
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Usually I use This should be enough withr::local_dir(fs::path(temp_dir, "test-blog-project")) |
||||||
|
||||||
Sys.setenv(FULLNAME="Max Kuhn") | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Maybe we should keep using withr for that
Suggested change
|
||||||
|
||||||
# ------------------------------------------------------------------------------ | ||||||
|
||||||
post_1 <- new_blog_post("Intro to Felt Surrogacy", date = "March 25, 2010", | ||||||
open = FALSE) | ||||||
expect_true(fs::file_exists(post_1)) | ||||||
expect_equal(fs::path_file(post_1), "index.qmd") | ||||||
|
||||||
post_1_dir <- fs::path_split(post_1)[[1]] | ||||||
post_1_dir <- post_1_dir[length(post_1_dir) - 1] | ||||||
expect_equal(post_1_dir, "intro-to-felt-surrogacy") | ||||||
|
||||||
post_1_content <- readLines(post_1) | ||||||
post_1_content <- paste0(post_1_content, collapse = "\n") | ||||||
expect_equal( | ||||||
post_1_content, | ||||||
"---\ntitle: Intro to Felt Surrogacy\nauthor: Max Kuhn\ndate: March 25, 2010\n---" | ||||||
) | ||||||
|
||||||
# ------------------------------------------------------------------------------ | ||||||
|
||||||
expect_snapshot( | ||||||
new_blog_post("Intro to Felt Surrogacy", data = "1999-12-31", open = FALSE), | ||||||
error = TRUE | ||||||
) | ||||||
|
||||||
# ------------------------------------------------------------------------------ | ||||||
|
||||||
post_2 <- | ||||||
new_blog_post( | ||||||
"Intro to Felt Surrogacy", | ||||||
dest = "The Science of Illusion", | ||||||
author = "Annie Edison", | ||||||
date = '2024-04-12', | ||||||
categories = c("shenanigans", "security"), | ||||||
open = FALSE) | ||||||
|
||||||
expect_true(fs::file_exists(post_2)) | ||||||
expect_equal(fs::path_file(post_2), "index.qmd") | ||||||
|
||||||
post_2_dir <- fs::path_split(post_2)[[1]] | ||||||
post_2_dir <- post_2_dir[length(post_2_dir) - 1] | ||||||
expect_equal(post_2_dir, "The Science of Illusion") | ||||||
|
||||||
post_2_content <- readLines(post_2) | ||||||
post_2_exp <- c( | ||||||
"---", "title: Intro to Felt Surrogacy", "author: Annie Edison", | ||||||
"date: '2024-04-12'", "categories:", "- shenanigans", "- security", "---") | ||||||
expect_equal(post_2_content, post_2_exp) | ||||||
}) | ||||||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As those two deps are only required for the new blog post function, and this function is aimed to be used mostly interactively, I wonder if we could rely on
rlang::check_installed()
By using
if(rlang::check_installed("whoami"))
, user will be asked to install if missing when used interactively.This would allow to make them suggested dependencies.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For fs, we probably could start using it in this package.