diff --git a/DESCRIPTION b/DESCRIPTION
index 7dab2e6..d3a5c61 100644
--- a/DESCRIPTION
+++ b/DESCRIPTION
@@ -2,7 +2,7 @@ Package: multiverse.internals
Title: Internal Infrastructure for R-multiverse
Description: R-multiverse requires this internal infrastructure package to
automate contribution reviews and populate universes.
-Version: 0.2.21
+Version: 0.3.0
License: MIT + file LICENSE
URL:
https://r-multiverse.org/multiverse.internals/,
diff --git a/NAMESPACE b/NAMESPACE
index de821dc..f8fac6a 100644
--- a/NAMESPACE
+++ b/NAMESPACE
@@ -22,6 +22,7 @@ export(review_pull_requests)
export(try_message)
export(update_staging)
export(update_status)
+export(update_topics)
importFrom(base64enc,base64decode)
importFrom(desc,description)
importFrom(gh,gh)
diff --git a/NEWS.md b/NEWS.md
index 4475538..c2b7c1c 100644
--- a/NEWS.md
+++ b/NEWS.md
@@ -1,3 +1,7 @@
+# multiverse.internals 0.3.0
+
+* Add `update_topics()`.
+
# multiverse.internals 0.2.21
* Add back HTML line breaks in `update_status()`.
diff --git a/R/update_topics.R b/R/update_topics.R
new file mode 100644
index 0000000..9fd4d03
--- /dev/null
+++ b/R/update_topics.R
@@ -0,0 +1,80 @@
+#' @title Update topics
+#' @export
+#' @family topics
+#' @description Update the list of packages for each
+#' R-multiverse topic.
+#' @return `NULL` (invisibly). Called for its side effects.
+#' @inheritParams update_staging
+#' @param path Character string,
+#' local file path to the topics repository source code.
+#' @param repo Character string, URL of the Community universe.
+update_topics <- function(
+ path,
+ repo = "https://community.r-multiverse.org",
+ mock = NULL
+) {
+ meta <- mock$meta %||% meta_packages(repo, fields = c("Title", "URL"))
+ meta <- meta[, c("package", "title", "url")]
+ unlink(file.path(path, "*.html"))
+ topics <- setdiff(
+ list.files(path),
+ c(".gitignore", "README.md", "LICENSE.md")
+ )
+ for (topic in topics) {
+ update_topic(topic, path, meta)
+ }
+ topic_urls <- file.path(
+ "https://r-multiverse.org/topics", paste0(topics, ".html")
+ )
+ topic_list <- paste0(
+ "
",
+ topics,
+ ": ",
+ topic_urls,
+ "",
+ ""
+ )
+ topic_text <- paste(topic_list, collapse = "\n")
+ template <- system.file(
+ file.path("topics", "index.html"),
+ package = "multiverse.internals",
+ mustWork = TRUE
+ )
+ text <- readLines(template)
+ text <- gsub(pattern = "TOPICS", replacement = topic_text, x = text)
+ writeLines(text, file.path(path, "index.html"))
+}
+
+update_topic <- function(topic, path, meta) {
+ url <- file.path("https://r-multiverse.org/topics", paste0(topic, ".html"))
+ meta <- meta[grepl(pattern = url, x = meta$url, fixed = TRUE), ]
+ about <- readLines(file.path(path, topic))
+ template <- system.file(
+ file.path("topics", "topic.html"),
+ package = "multiverse.internals",
+ mustWork = TRUE
+ )
+ line <- paste0(
+ "",
+ "PACKAGE: ",
+ "TITLE",
+ ""
+ )
+ packages <- character(0L)
+ for (row in seq_len(nrow(meta))) {
+ package <- meta$package[row]
+ title <- meta$title[row]
+ element <- line
+ element <- gsub(pattern = "PACKAGE", replacement = package, x = element)
+ element <- gsub(pattern = "TITLE", replacement = title, x = element)
+ packages <- c(packages, element)
+ }
+ packages <- paste(packages, collapse = "\n")
+ text <- readLines(template)
+ text <- gsub(pattern = "ABOUT", replacement = about, x = text)
+ text <- gsub(pattern = "PACKAGES", replacement = packages, x = text)
+ text <- gsub(pattern = "TOPIC", replacement = topic, x = text)
+ writeLines(text, file.path(path, paste0(topic, ".html")))
+}
diff --git a/_pkgdown.yml b/_pkgdown.yml
index 91d9aad..ba1ba05 100644
--- a/_pkgdown.yml
+++ b/_pkgdown.yml
@@ -24,11 +24,14 @@ reference:
- record_issues
- record_nonstandard_licenses
- record_versions
-- title: Package status repository
- contents:
- - interpret_status
- - update_status
- title: Staging
contents:
- update_staging
- propose_snapshot
+- title: Package status
+ contents:
+ - interpret_status
+ - update_status
+- title: Topics
+ contents:
+ - update_topics
diff --git a/inst/topics/index.html b/inst/topics/index.html
new file mode 100644
index 0000000..1116f5c
--- /dev/null
+++ b/inst/topics/index.html
@@ -0,0 +1,31 @@
+
+
+ R-multiverse topics
+
+ About R-multiverse topics
+
+ An R-multiverse topic is a list of packages that share a subject
+ matter domain (for example, packages that fit Bayesian models).
+
+
+ To contribute a new package to an existing topic, add the topic URL
+ to the URL field of the DESCRIPTION file
+ (example here),
+ then create a new GitHub/GitLab release of the package.
+
+
+ To contribute a new topic, submit a pull request to
+ https://github.com/r-multiverse/topics
+ to add a new text file (with no file extension) whose name is the topic name
+ and whose contents describe the topic.
+ An automated workflow periodically creates HTML pages from these
+ text files.
+
+ List of R-multiverse topics
+
+
+
+
+
diff --git a/inst/topics/topic.html b/inst/topics/topic.html
new file mode 100644
index 0000000..1b6469a
--- /dev/null
+++ b/inst/topics/topic.html
@@ -0,0 +1,14 @@
+
+
+ TOPIC
+
+ Topic: TOPIC
+ ABOUT
+ Packages
+
+
+
+
+
diff --git a/man/update_topics.Rd b/man/update_topics.Rd
new file mode 100644
index 0000000..57ed15a
--- /dev/null
+++ b/man/update_topics.Rd
@@ -0,0 +1,25 @@
+% Generated by roxygen2: do not edit by hand
+% Please edit documentation in R/update_topics.R
+\name{update_topics}
+\alias{update_topics}
+\title{Update topics}
+\usage{
+update_topics(path, repo = "https://community.r-multiverse.org", mock = NULL)
+}
+\arguments{
+\item{path}{Character string,
+local file path to the topics repository source code.}
+
+\item{repo}{Character string, URL of the Community universe.}
+
+\item{mock}{For testing purposes only, a named list of data frames
+for inputs to various intermediate functions.}
+}
+\value{
+\code{NULL} (invisibly). Called for its side effects.
+}
+\description{
+Update the list of packages for each
+R-multiverse topic.
+}
+\concept{topics}
diff --git a/tests/testthat/test-update_topics.R b/tests/testthat/test-update_topics.R
new file mode 100644
index 0000000..ab48ff5
--- /dev/null
+++ b/tests/testthat/test-update_topics.R
@@ -0,0 +1,38 @@
+test_that("update_topics()", {
+ path <- tempfile()
+ on.exit(unlink(path))
+ dir.create(path, recursive = TRUE)
+ writeLines("bayesian description", file.path(path, "bayesian"))
+ writeLines("hpc description", file.path(path, "hpc"))
+ meta <- data.frame(
+ package = c("nope", "crew", "stantargets", "jagstargets"),
+ title = c("x", "crew-title", "stantargets-title", "jagstargets-title"),
+ url = c(
+ "https://asdf",
+ "https://r-multiverse.org/topics/hpc.html, https://crew",
+ "https://url, https://r-multiverse.org/topics/bayesian.html",
+ "https://url,\nhttps://r-multiverse.org/topics/bayesian.html"
+ )
+ )
+ update_topics(
+ path = path,
+ mock = list(meta = meta)
+ )
+ expect_equal(
+ sort(list.files(path)),
+ sort(c("bayesian", "bayesian.html", "hpc", "hpc.html", "index.html"))
+ )
+ out <- readLines(file.path(path, "index.html"))
+ expect_true(any(grepl("bayesian.html", out, fixed = TRUE)))
+ expect_true(any(grepl("hpc.html", out, fixed = TRUE)))
+ out <- readLines(file.path(path, "hpc.html"))
+ expect_false(any(grepl("nope", out, fixed = TRUE)))
+ expect_true(any(grepl("crew", out, fixed = TRUE)))
+ expect_false(any(grepl("stantargets", out, fixed = TRUE)))
+ expect_false(any(grepl("jagstargets", out, fixed = TRUE)))
+ out <- readLines(file.path(path, "bayesian.html"))
+ expect_false(any(grepl("nope", out, fixed = TRUE)))
+ expect_false(any(grepl("crew", out, fixed = TRUE)))
+ expect_true(any(grepl("stantargets", out, fixed = TRUE)))
+ expect_true(any(grepl("jagstargets", out, fixed = TRUE)))
+})