From bec750e167ed6fc8eebe3e4a23a8e3c7fc0f4aab Mon Sep 17 00:00:00 2001 From: topepo Date: Fri, 12 Apr 2024 14:25:38 -0400 Subject: [PATCH 1/7] initial version of new_blog_post --- DESCRIPTION | 2 ++ NAMESPACE | 1 + R/new-blog-post.R | 80 ++++++++++++++++++++++++++++++++++++++++++++ man/new_blog_post.Rd | 44 ++++++++++++++++++++++++ 4 files changed, 127 insertions(+) create mode 100644 R/new-blog-post.R create mode 100644 man/new_blog_post.Rd diff --git a/DESCRIPTION b/DESCRIPTION index 4259b01..67732d8 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -18,6 +18,7 @@ Depends: R (>= 3.6) Imports: cli, + fs, jsonlite, later, processx, @@ -26,6 +27,7 @@ Imports: rstudioapi, tools, utils, + whoami, yaml Suggests: curl, diff --git a/NAMESPACE b/NAMESPACE index 9e9fa9f..dfaa800 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -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_binary_sitrep) export(quarto_create_project) diff --git a/R/new-blog-post.R b/R/new-blog-post.R new file mode 100644 index 0000000..600e047 --- /dev/null +++ b/R/new-blog-post.R @@ -0,0 +1,80 @@ +#' Create 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. +#' @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{ +#' \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)) + } + 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) { + file.edit(qmd_path) + } + invisible(qmd_path) +} + +make_post_dir <- function(dest, call) { + working <- fs::path_wd() + + post_path <- fs::path(working, "posts", dest) + + if (fs::dir_exists(post_path)) { + cli::cli_abort("There is already a {.code {path}} 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 <- utils::modifyList(default_values, user_values) + 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") + 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 +} diff --git a/man/new_blog_post.Rd b/man/new_blog_post.Rd new file mode 100644 index 0000000..1c507ac --- /dev/null +++ b/man/new_blog_post.Rd @@ -0,0 +1,44 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/new-blog-post.R +\name{new_blog_post} +\alias{new_blog_post} +\title{Create a new blog post} +\usage{ +new_blog_post( + title, + dest = NULL, + open = rlang::is_interactive(), + call = rlang::current_env(), + ... +) +} +\arguments{ +\item{title}{A character string for the title of the post. It is converted +to title case via \code{\link[tools:toTitleCase]{tools::toTitleCase()}}.} + +\item{dest}{A character string (or NULL) for the path within \code{posts}. By +default, the title is adapted as the directory name.} + +\item{open}{A logical: have the default editor open a window to edit the +\code{index.qmd} file?} + +\item{call}{A call object for reporting errors.} + +\item{...}{A named list of values to be added to the yaml header, such as +\code{description}, \code{author}, \code{categories}, etc.} +} +\value{ +The path to the index file. +} +\description{ +Create a new blog post +} +\examples{ +\dontrun{ + \donttest{ +new_blog_post("making quarto blog posts", categories = c("R")) + + } +} + +} From b41f130322e5928964e2dc9ed8856dd571826fbe Mon Sep 17 00:00:00 2001 From: topepo Date: Fri, 12 Apr 2024 14:29:33 -0400 Subject: [PATCH 2/7] add namespace to call --- R/new-blog-post.R | 4 +++- man/new_blog_post.Rd | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/R/new-blog-post.R b/R/new-blog-post.R index 600e047..0c4b0b2 100644 --- a/R/new-blog-post.R +++ b/R/new-blog-post.R @@ -1,5 +1,7 @@ #' 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 @@ -30,7 +32,7 @@ new_blog_post <- function(title, dest = NULL, open = rlang::is_interactive(), post_yaml <- make_post_yaml(title, ...) qmd_path <- write_post_yaml(post_yaml, dest_path, call) if (open) { - file.edit(qmd_path) + utils::file.edit(qmd_path) } invisible(qmd_path) } diff --git a/man/new_blog_post.Rd b/man/new_blog_post.Rd index 1c507ac..175935c 100644 --- a/man/new_blog_post.Rd +++ b/man/new_blog_post.Rd @@ -31,7 +31,7 @@ default, the title is adapted as the directory name.} The path to the index file. } \description{ -Create a new blog post +Creates (and potentially opens) the \code{index.qmd} file for a new blog post. } \examples{ \dontrun{ From a8e7d13dc4f6e1b0ae8037038ec15b98068db58b Mon Sep 17 00:00:00 2001 From: topepo Date: Fri, 12 Apr 2024 15:12:18 -0400 Subject: [PATCH 3/7] initial unit tests --- R/new-blog-post.R | 2 +- tests/testthat/_snaps/new-blog-post.md | 8 ++++ tests/testthat/test-new-blog-post.R | 57 ++++++++++++++++++++++++++ 3 files changed, 66 insertions(+), 1 deletion(-) create mode 100644 tests/testthat/_snaps/new-blog-post.md create mode 100644 tests/testthat/test-new-blog-post.R diff --git a/R/new-blog-post.R b/R/new-blog-post.R index 0c4b0b2..ed40ed4 100644 --- a/R/new-blog-post.R +++ b/R/new-blog-post.R @@ -43,7 +43,7 @@ make_post_dir <- function(dest, call) { post_path <- fs::path(working, "posts", dest) if (fs::dir_exists(post_path)) { - cli::cli_abort("There is already a {.code {path}} directory in 'posts/'", + cli::cli_abort("There is already a {.code {dest}} directory in 'posts/'", call = call) } else { ret <- fs::dir_create(post_path) diff --git a/tests/testthat/_snaps/new-blog-post.md b/tests/testthat/_snaps/new-blog-post.md new file mode 100644 index 0000000..0ded61f --- /dev/null +++ b/tests/testthat/_snaps/new-blog-post.md @@ -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/' + diff --git a/tests/testthat/test-new-blog-post.R b/tests/testthat/test-new-blog-post.R new file mode 100644 index 0000000..f3f9744 --- /dev/null +++ b/tests/testthat/test-new-blog-post.R @@ -0,0 +1,57 @@ +test_that("Create a blog post", { + skip_if_no_quarto("1.4") + + tempdir <- withr::local_tempdir() + withr::local_dir(tempdir) + quarto_create_project(name = "test-blog-project", type = "blog", + dir = tempdir(), quiet = TRUE) + + # ------------------------------------------------------------------------------ + + 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", + 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) +}) + From ad0a01b89e54d81b8be5521d9b816b494be7a409 Mon Sep 17 00:00:00 2001 From: topepo Date: Fri, 12 Apr 2024 15:27:00 -0400 Subject: [PATCH 4/7] news file update related to #22 --- NEWS.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/NEWS.md b/NEWS.md index 3e1f13e..ccb46fa 100644 --- a/NEWS.md +++ b/NEWS.md @@ -1,5 +1,7 @@ # quarto (development version) +- Added a `new_blog_post()` function (#22). + # quarto 1.4 - This version is now adapted to Quarto 1.4 latest stable release. From 56be9fb75697ec6064e9ace045b94bb419b1ecd7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=98topepo=E2=80=99?= <‘mxkuhn@gmail.com’> Date: Tue, 16 Apr 2024 09:32:47 -0400 Subject: [PATCH 5/7] use test fixtures --- tests/testthat/test-new-blog-post.R | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/tests/testthat/test-new-blog-post.R b/tests/testthat/test-new-blog-post.R index f3f9744..72204f3 100644 --- a/tests/testthat/test-new-blog-post.R +++ b/tests/testthat/test-new-blog-post.R @@ -1,10 +1,18 @@ test_that("Create a blog post", { skip_if_no_quarto("1.4") - tempdir <- withr::local_tempdir() - withr::local_dir(tempdir) + 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()) + quarto_create_project(name = "test-blog-project", type = "blog", - dir = tempdir(), quiet = TRUE) + dir = temp_dir, quiet = TRUE) + + setwd(dir_path) + withr::defer(setwd(current_dir), envir = rlang::current_env()) # ------------------------------------------------------------------------------ @@ -38,6 +46,7 @@ test_that("Create a blog post", { "Intro to Felt Surrogacy", dest = "The Science of Illusion", author = "Annie Edison", + date = '2024-04-12', categories = c("shenanigans", "security"), open = FALSE) From 0f2942daecf0d97cf73046559a8ebeda5810523e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=98topepo=E2=80=99?= <‘mxkuhn@gmail.com’> Date: Thu, 18 Apr 2024 11:14:42 -0400 Subject: [PATCH 6/7] set env var for testing --- tests/testthat/test-new-blog-post.R | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/testthat/test-new-blog-post.R b/tests/testthat/test-new-blog-post.R index 72204f3..93a9b46 100644 --- a/tests/testthat/test-new-blog-post.R +++ b/tests/testthat/test-new-blog-post.R @@ -14,6 +14,8 @@ test_that("Create a blog post", { setwd(dir_path) withr::defer(setwd(current_dir), envir = rlang::current_env()) + Sys.setenv(FULLNAME="Max Kuhn") + # ------------------------------------------------------------------------------ post_1 <- new_blog_post("Intro to Felt Surrogacy", date = "March 25, 2010", From cc2adcf1c5ccf624b78da4bdb237ad86c4a2c22a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=98topepo=E2=80=99?= <‘mxkuhn@gmail.com’> Date: Wed, 17 Jul 2024 21:02:20 -0400 Subject: [PATCH 7/7] conditionally use whoami --- DESCRIPTION | 2 +- R/new-blog-post.R | 1 + tests/testthat/test-new-blog-post.R | 1 + 3 files changed, 3 insertions(+), 1 deletion(-) diff --git a/DESCRIPTION b/DESCRIPTION index e97d593..f89a5be 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -27,7 +27,6 @@ Imports: rstudioapi, tools, utils, - whoami, yaml Suggests: curl, @@ -35,6 +34,7 @@ Suggests: rsconnect (>= 0.8.26), testthat (>= 3.1.7), withr, + whoami, xfun VignetteBuilder: quarto diff --git a/R/new-blog-post.R b/R/new-blog-post.R index ed40ed4..058e554 100644 --- a/R/new-blog-post.R +++ b/R/new-blog-post.R @@ -23,6 +23,7 @@ #' new_blog_post <- function(title, dest = NULL, open = rlang::is_interactive(), call = rlang::current_env(), ...) { + rlang::check_installed("whoami") if (is.null(dest)) { # Scrub title to make directory name diff --git a/tests/testthat/test-new-blog-post.R b/tests/testthat/test-new-blog-post.R index 93a9b46..d805e2e 100644 --- a/tests/testthat/test-new-blog-post.R +++ b/tests/testthat/test-new-blog-post.R @@ -1,5 +1,6 @@ test_that("Create a blog post", { skip_if_no_quarto("1.4") + skip_if_not_installed("whoami") current_dir <- getwd()