diff --git a/DESCRIPTION b/DESCRIPTION index a21209f3..8f5e4f03 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -1,6 +1,6 @@ Package: tibblify Title: Rectangle Nested Lists -Version: 0.3.1.9002 +Version: 0.4.0 Authors@R: c( person("Jon", "Harmon", , "jonthegeek@gmail.com", role = c("aut", "cre"), comment = c(ORCID = "0000-0003-4781-4346")), @@ -8,10 +8,10 @@ Authors@R: c( person("Kirill", "Müller", role = "ctb") ) Description: A tool to rectangle a nested list, that is to convert it into - a tibble. This is done automatically or according to a given - specification. A common use case is for nested lists coming from + a 'tibble'. This is done automatically or according to a given + specification. A common use case is for nested lists coming from parsing 'JSON' files, or the 'JSON' responses of 'REST' 'APIs'. - Rectangling uses the 'vctrs' package, and therefore offers a wide + 'Rectangling' uses the 'vctrs' package, and therefore offers a wide support of vector types. License: MIT + file LICENSE URL: https://tibblify.wrangle.zone, @@ -34,6 +34,7 @@ Suggests: jsonlite (>= 1.8.0), knitr (>= 1.40), memoise (>= 2.0.1), + repurrrsive, rmarkdown (>= 2.16), spelling (>= 2.2), stbl, @@ -44,12 +45,10 @@ LinkingTo: vctrs VignetteBuilder: knitr -Remotes: - r-lib/rlang +Config/roxygen2/version: 8.0.0 Config/testthat/edition: 3 Encoding: UTF-8 Language: en-US LazyData: true NeedsCompilation: yes Roxygen: list(markdown = TRUE) -Config/roxygen2/version: 8.0.0 diff --git a/NEWS.md b/NEWS.md index eccde6ea..e7074304 100644 --- a/NEWS.md +++ b/NEWS.md @@ -1,14 +1,27 @@ -# tibblify (development version) +# tibblify 0.4.0 + +## Breaking changes + +* All arguments of functions that accept meaningful named `...` are now prefixed with `.` to minimize conflicts with column and object names in `...`. The un-dotted versions of the arguments are still accepted, but calling functions directly with un-dotted arguments will produce a warning once per session (see `?lifecycle::deprecate_soft()`). Un-dotted arguments will be phased out in a future version of this package, so we recommend switching to the dot-prefixed versions. See `?tspec_df` and `?tib_scalar()` for details (#228). + +## Potential breaking changes -* All arguments of functions that accept meaningful named `...` are now prefixed with `.` to minimize conflicts with column and object names in `...`. The un-dotted versions of the arguments are still accepted, but calling functions directly with un-dotted arguments will produce a warning once per session (see `?lifecycle::deprecate_soft()`). Un-dotted arguments will be phased out in a future version of this package, so we recommend switching to the dot-prefixed versions. See `?tspec_df` and `?tib_scalar()` for details. * All code has been refactored for maintainability. While we were careful to ensure that output is unchanged, it is possible that a corner case is no longer handled how it was in version 0.3.0. Please notify us () if something has changed for the worse in an unexpected way (#243). + +## New features + * The `guess_tspec()` variants `guess_tspec_list()` and `guess_tspec_object_list()` are now exported (along with `guess_tspec_df()` and `guess_tspec_object()`, which were already exported). `guess_tspec()` should correctly guess the format in most cases, but you can call the variant directly if you think `guess_tspec()` is dispatching incorrectly (#249). * `untibblify()` now automatically uses the `tib_spec` attribute when present, so tibblified objects can be round-tripped without explicitly passing the spec (#235). -* `parse_openapi_spec()` supports many more fields and works for many more APIs (#190, #200, @jonthegeek and @mgirlich). -* The underlying C implementation has been updated to better comply with R's C API. We also fixed various bugs during this update (#203, #204, #222). -* All vignettes and the documentation of all functions has been updated for clarity (#243). +* `parse_openapi_spec()` supports many more fields and works for many more APIs (#170, #186, #190, #200, @jonthegeek and @mgirlich). + +## Bug fixes + +* The underlying C implementation has been updated to comply with R's C API. We also fixed various bugs during this update (#203, #204, #222). + +## Documentation + +* All vignettes and the documentation of all functions have been updated for clarity (#243). -(roughly sorted into "Breaking changes", "Potential breaking changes", "New features", "Bug fixes", and "Documentation" as of 2026-04-10, but I left out the headers to make it easier to add more bullets during development) # tibblify 0.3.1 diff --git a/R/data.R b/R/data.R index 5edca59e..4a156f35 100644 --- a/R/data.R +++ b/R/data.R @@ -1,39 +1,6 @@ -#' Game of Thrones POV characters -#' -#' The data is from the [repurrrsive package](https://github.com/jennybc/repurrrsive). -#' -#' Info on the point-of-view (POV) characters from the first five books in the -#' Song of Ice and Fire series by George R. R. Martin. Retrieved from An API Of -#' Ice And Fire. -#' -#' @format A unnamed list with 30 components, each representing a POV character. -#' Each character's component is a named list of length 18, containing -#' information such as name, aliases, and house allegiances. -#' -#' @family Game of Thrones data and functions -#' @source -#' @examples -#' got_chars -#' str(lapply(got_chars, `[`, c("name", "culture"))) -"got_chars" - #' Politicians #' #' A dataset containing some basic information about some politicians. #' #' @format A list of lists. "politicians" - -#' GitHub users -#' -#' A dataset containing some basic information about six GitHub users. -#' -#' @format A list of lists. -"gh_users" - -#' GitHub repositories -#' -#' A dataset containing some basic information about some GitHub repositories. -#' -#' @format A list of lists. -"gh_repos" diff --git a/R/finalize_tspec_object.R b/R/finalize_tspec_object.R index ab64caaa..cc6eeeb2 100644 --- a/R/finalize_tspec_object.R +++ b/R/finalize_tspec_object.R @@ -2,6 +2,7 @@ #' #' @param field (`any`) The field value. #' @inheritParams .shared-params-spec_prep +#' @returns The finalized field spec. #' @keywords internal .finalize_tspec_object <- function(field_spec, field) { UseMethod(".finalize_tspec_object") diff --git a/R/guess_tspec.R b/R/guess_tspec.R index 0f88639c..fca4ab0a 100644 --- a/R/guess_tspec.R +++ b/R/guess_tspec.R @@ -29,8 +29,6 @@ #' @examples #' guess_tspec(list(x = 1, y = "a")) #' guess_tspec(list(list(x = 1), list(x = 2))) -#' -#' guess_tspec(gh_users) guess_tspec <- function( x, ..., diff --git a/R/guess_tspec_list.R b/R/guess_tspec_list.R index 8bfc5e9b..bd4f19f7 100644 --- a/R/guess_tspec_list.R +++ b/R/guess_tspec_list.R @@ -12,8 +12,7 @@ guess_tspec_list <- function( rlang::check_dots_empty() .check_list(x) if (rlang::is_empty(x)) { - msg <- "{.arg {arg}} must not be empty." - cli::cli_abort(msg, call = call) + cli::cli_abort("{.arg {arg}} must not be empty.", call = call) } # if `x` is both an object list and an object, we default to treating it as diff --git a/R/guess_tspec_object.R b/R/guess_tspec_object.R index b7bf1791..e4878a6b 100644 --- a/R/guess_tspec_object.R +++ b/R/guess_tspec_object.R @@ -40,11 +40,13 @@ guess_tspec_object <- function( #' @keywords internal .check_not_df <- function(x, call) { if (is.data.frame(x)) { - msg <- c( - "{.arg x} must not be a dataframe.", - i = "Did you want to use {.fn guess_tspec_df} instead?" + cli::cli_abort( + c( + "{.arg x} must not be a dataframe.", + i = "Did you want to use {.fn guess_tspec_df} instead?" + ), + call = call ) - cli::cli_abort(msg, call = call) } return(invisible(x)) } @@ -215,8 +217,7 @@ guess_tspec_object <- function( #' @keywords internal .check_named <- function(x, call) { if (!rlang::is_named2(x)) { - msg <- "{.arg x} must be fully named." - cli::cli_abort(msg, call = call) + cli::cli_abort("{.arg x} must be fully named.", call = call) } return(invisible(x)) } @@ -228,8 +229,7 @@ guess_tspec_object <- function( #' @keywords internal .check_names_not_duplicated <- function(x, call) { if (vctrs::vec_duplicate_any(names(x))) { - msg <- "Names of {.arg x} must be unique." - cli::cli_abort(msg, call = call) + cli::cli_abort("Names of {.arg x} must be unique.", call = call) } return(invisible(x)) } diff --git a/R/guess_tspec_utils.R b/R/guess_tspec_utils.R index 336cae8e..f52061b5 100644 --- a/R/guess_tspec_utils.R +++ b/R/guess_tspec_utils.R @@ -122,11 +122,13 @@ "vector" } else { if (!other) { - msg <- c( - "Column {name} must be a dataframe, a list, or a vector.", - x = "Column {name} has classes {.cls class(x)}." + cli::cli_abort( + c( + "Column {name} must be a dataframe, a list, or a vector.", + x = "Column {name} has classes {.cls class(x)}." + ), + .internal = TRUE ) - cli::cli_abort(msg, .internal = TRUE) } "other" } diff --git a/R/nest_tree.R b/R/nest_tree.R index 60e01b36..c5cf25e8 100644 --- a/R/nest_tree.R +++ b/R/nest_tree.R @@ -70,7 +70,7 @@ nest_tree <- function(x, id_col, parent_col, children_to) { x } -#' Confirm that `children_to` is a single string that is different from `id_col` and `parent_col` +#' Confirm that `children_to` is usable #' #' @inheritParams .shared-params #' @returns The input `children_to` object as a string. @@ -151,7 +151,7 @@ nest_tree <- function(x, id_col, parent_col, children_to) { parent_col } -#' Validate parent ids: cast to id type, check for self-references and missing roots +#' Confirm that parent ids are usable #' #' @inheritParams .shared-params #' @returns The parent ids, cast to the same type as ids. @@ -195,11 +195,13 @@ nest_tree <- function(x, id_col, parent_col, children_to) { if (any(self_reference, na.rm = TRUE)) { self_reference_loc <- which(self_reference) n <- length(self_reference_loc) - msg <- c( - "An element must not be its own parent", - i = "{qty(n)}Element{?s} {self_reference_loc} {qty(n)}refer{?s/} to {?itself/themselves} as parent." + cli::cli_abort( + c( + "An element must not be its own parent", + i = "{qty(n)}Element{?s} {self_reference_loc} {qty(n)}refer{?s/} to {?itself/themselves} as parent." + ), + call = call ) - cli::cli_abort(msg, call = call) } return(parent_ids) } @@ -213,21 +215,25 @@ nest_tree <- function(x, id_col, parent_col, children_to) { .check_parent_id_missing <- function(parent_ids, ids, call = caller_env()) { parent_na <- vctrs::vec_detect_missing(parent_ids) if (!any(parent_na) && !vctrs::vec_is_empty(parent_ids)) { - msg <- c( - "There must be root elements.", - i = "A root element is an elements whose parent id is missing." + cli::cli_abort( + c( + "There must be root elements.", + i = "A root element is an elements whose parent id is missing." + ), + call = call ) - cli::cli_abort(msg, call = call) } missing_parents <- !vctrs::vec_in(parent_ids, ids) & !parent_na if (any(missing_parents)) { missing_parent_ids <- vctrs::vec_slice(parent_ids, missing_parents) n <- sum(missing_parents) - msg <- c( - "The parent of each element must be found.", - i = "The parent {qty(n)} id{?s} {.value {missing_parent_ids}} {qty(n)}{?is/are} not found." + cli::cli_abort( + c( + "The parent of each element must be found.", + i = "The parent {qty(n)} id{?s} {.value {missing_parent_ids}} {qty(n)}{?is/are} not found." + ), + call = call ) - cli::cli_abort(msg, call = call) } return(parent_ids) } diff --git a/R/parse_openapi.R b/R/parse_openapi.R index 8095fd4c..25c98c10 100644 --- a/R/parse_openapi.R +++ b/R/parse_openapi.R @@ -80,7 +80,8 @@ #' }' #' parse_openapi_schema(file) #' -#' # Spec example from https://swagger.io/docs/specification/v3_0/basic-structure/ +#' # Spec example from +#' # https://swagger.io/docs/specification/v3_0/basic-structure/ #' spec_path <- system.file( #' "examples", "openapi", "sample_api.yaml", package = "tibblify" #' ) diff --git a/R/shape_utils.R b/R/shape_utils.R index c239609c..69f2d97c 100644 --- a/R/shape_utils.R +++ b/R/shape_utils.R @@ -18,7 +18,7 @@ .Call(ffi_is_object_list, x) } -#' bort if `x` is not a list of objects +#' Abort if `x` is not a list of objects #' #' @inheritParams .shared-params #' @returns `x` (invisibly). Called for side effect. @@ -110,14 +110,12 @@ )) ol_msg <- rlang::set_names(object_list_cnd, c("", object_list_bullets)) - msg <- c( - "{.arg {arg}} is neither an object nor a list of objects.", - o_msg, - ol_msg - ) - cli::cli_abort( - msg, + c( + "{.arg {arg}} is neither an object nor a list of objects.", + o_msg, + ol_msg + ), class = c( "tibblify-error-untibblifiable_object", "tibblify-error", diff --git a/R/should_inform_unspecified.R b/R/should_inform_unspecified.R index 20019e82..2996b218 100644 --- a/R/should_inform_unspecified.R +++ b/R/should_inform_unspecified.R @@ -5,6 +5,8 @@ #' #' @returns `FALSE` if the option is set to `FALSE`, `TRUE` otherwise. #' @export +#' @examples +#' should_inform_unspecified() should_inform_unspecified <- function() { !rlang::is_false(getOption("tibblify.show_unspecified")) } diff --git a/R/spec_combine.R b/R/spec_combine.R index 7f169fe4..75bb454a 100644 --- a/R/spec_combine.R +++ b/R/spec_combine.R @@ -85,11 +85,13 @@ tspec_combine <- function(...) { bad_idx <- purrr::detect_index(spec_list, ~ !.is_tspec(.x)) if (bad_idx != 0) { cls1 <- class(spec_list[[bad_idx]])[[1]] - msg <- c( - "Every element of {.arg ...} must be a tibblify spec.", - x = "Element {bad_idx} has class {.cls {cls1}}." + cli::cli_abort( + c( + "Every element of {.arg ...} must be a tibblify spec.", + x = "Element {bad_idx} has class {.cls {cls1}}." + ), + call = .call ) - cli::cli_abort(msg, call = .call) } spec_list } @@ -437,8 +439,10 @@ tspec_combine <- function(...) { input_form <- input_forms[[input_form_locs]] if (any(types == "scalar") && input_form != "vector") { - msg <- "Cannot combine input form {.val {input_form}} with {.code tib_scalar()}." - cli::cli_abort(msg, call = .call) + cli::cli_abort( + "Cannot combine input form {.val {input_form}} with {.code tib_scalar()}.", + call = .call + ) } input_form diff --git a/R/tib_spec_basics.R b/R/tib_spec_basics.R index 67e37250..9b5c76b3 100644 --- a/R/tib_spec_basics.R +++ b/R/tib_spec_basics.R @@ -60,7 +60,11 @@ #' tib_int("int", .required = FALSE, .fill = 0) #' #' # This is essentially how `tib_chr_date()` is implemented. -#' tib_scalar("date", Sys.Date(), .transform = function(x) as.Date(x, format = "%Y-%m-%d")) +#' tib_scalar( +#' "date", +#' Sys.Date(), +#' .transform = function(x) as.Date(x, format = "%Y-%m-%d") +#' ) #' #' tib_df( #' "data", @@ -134,13 +138,14 @@ NULL } else { if (vctrs::vec_any_missing(.key)) { na_idx <- purrr::detect_index(vctrs::vec_detect_missing(.key), ~.x) - msg <- "`.key[{.field {na_idx}}] must not be NA." - cli::cli_abort(msg, call = .call) + cli::cli_abort("`.key[{.field {na_idx}}] must not be NA.", call = .call) } if (any(.key == "")) { empty_string_idx <- purrr::detect_index(.key == "", ~.x) - msg <- "`.key[{.field {empty_string_idx}}] must not be an empty string." - cli::cli_abort(msg, call = .call) + cli::cli_abort( + "`.key[{.field {empty_string_idx}}] must not be an empty string.", + call = .call + ) } } } @@ -425,17 +430,23 @@ tib_vector <- function( .stabilize_names_to <- function(.names_to, .values_to, .input_form, .call) { if (!is.null(.names_to)) { if (is.null(.values_to)) { - msg <- "{.arg .names_to} can only be used if {.arg .values_to} is not {.code NULL}." - cli::cli_abort(msg, call = .call) + cli::cli_abort( + "{.arg .names_to} can only be used if {.arg .values_to} is not {.code NULL}.", + call = .call + ) } if (.input_form == "scalar_list") { - msg <- '{.arg .names_to} can\'t be used for {.code .input_form = "scalar_list"}.' - cli::cli_abort(msg, call = .call) + cli::cli_abort( + '{.arg .names_to} can\'t be used for {.code .input_form = "scalar_list"}.', + call = .call + ) } rlang::check_string(.names_to, call = .call) if (.names_to == .values_to) { - msg <- "{.arg .names_to} must be different from {.arg .values_to}." - cli::cli_abort(msg, call = .call) + cli::cli_abort( + "{.arg .names_to} must be different from {.arg .values_to}.", + call = .call + ) } } .names_to diff --git a/R/tspec.R b/R/tspec.R index f674a404..72ab72c5 100644 --- a/R/tspec.R +++ b/R/tspec.R @@ -79,8 +79,9 @@ tspec_df <- function( .vector_allows_empty_list = .vector_allows_empty_list ) if (!is.null(.names_to) && .names_to %in% names(out$fields)) { - msg <- "The column name of {.arg .names_to} is already specified in {.arg ...}." - cli::cli_abort(msg) + cli::cli_abort( + "The column name of {.arg .names_to} is already specified in {.arg ...}." + ) } out @@ -164,8 +165,10 @@ tspec_recursive <- function( .check_names_to <- function(.names_to, .input_form, .call = caller_env()) { if (!is.null(.names_to)) { if (.input_form == "colmajor") { - msg <- 'Can\'t use {.arg .names_to} with {.code .input_form = "colmajor"}.' - cli::cli_abort(msg, call = .call) + cli::cli_abort( + 'Can\'t use {.arg .names_to} with {.code .input_form = "colmajor"}.', + call = .call + ) } rlang::check_string(.names_to, allow_null = TRUE, call = .call) } @@ -282,11 +285,13 @@ tspec_recursive <- function( function(field) { key <- field$key if (!rlang::is_string(key)) { - msg <- c( - "{.arg key} must be a single string to infer name.", - x = "{.arg key} has length {length(key)}." + cli::cli_abort( + c( + "{.arg key} must be a single string to infer name.", + x = "{.arg key} has length {length(key)}." + ), + call = NULL ) - cli::cli_abort(msg, call = NULL) } key diff --git a/R/unnest_tree.R b/R/unnest_tree.R index 7fec4c20..40a403d9 100644 --- a/R/unnest_tree.R +++ b/R/unnest_tree.R @@ -101,7 +101,7 @@ unnest_tree <- function( .check_arg_different(id_col, arg_name = "child_col", call = call) } -#' Validate and normalise an output column name +#' Validate and normalize an output column name #' #' @inheritParams unnest_tree #' @inheritParams .shared-params-tree diff --git a/R/utils.R b/R/utils.R index ec9ec0d0..1797b297 100644 --- a/R/utils.R +++ b/R/utils.R @@ -58,9 +58,8 @@ for (i in seq_along(other_args)) { if (identical(arg, other_args[[i]])) { other_arg_nm <- names(other_args)[[i]] - msg <- "{.arg {arg_name}} must be different from {.arg {other_arg_nm}}." cli::cli_abort( - msg, + "{.arg {arg_name}} must be different from {.arg {other_arg_nm}}.", call = call, class = c("tibblify-error-args_same_value", "tibblify-error") ) diff --git a/README.Rmd b/README.Rmd index 49c1076f..9fa1c1d6 100644 --- a/README.Rmd +++ b/README.Rmd @@ -47,8 +47,12 @@ To illustrate how `tibblify()` works, we'll start with a list containing informa ```{r} library(tibblify) +library(repurrrsive) -gh_users_small <- purrr::map(gh_users, ~ .x[c("followers", "login", "url", "name", "location", "email", "public_gists")]) +gh_users_small <- purrr::map( + repurrrsive::gh_users, + ~ .x[c("followers", "login", "url", "name", "location", "email", "public_gists")] +) names(gh_users_small[[1]]) ``` diff --git a/README.md b/README.md index aabdb7e9..e40dab0b 100644 --- a/README.md +++ b/README.md @@ -47,8 +47,12 @@ information about four GitHub users. ``` r library(tibblify) +library(repurrrsive) -gh_users_small <- purrr::map(gh_users, ~ .x[c("followers", "login", "url", "name", "location", "email", "public_gists")]) +gh_users_small <- purrr::map( + repurrrsive::gh_users, + ~ .x[c("followers", "login", "url", "name", "location", "email", "public_gists")] +) names(gh_users_small[[1]]) #> [1] "followers" "login" "url" "name" "location" @@ -59,15 +63,15 @@ We can rectangle `gh_users_small` automatically with `tibblify()`: ``` r tibblify(gh_users_small) -#> The spec contains 1 unspecified field: -#> • email -#> # A tibble: 4 × 7 -#> followers login url name location email public_gists -#> -#> 1 780 jennybc https://api.github.co… Jenn… Vancouv… 54 -#> 2 3958 jtleek https://api.github.co… Jeff… Baltimo… 12 -#> 3 115 juliasilge https://api.github.co… Juli… Salt La… 4 -#> 4 213 leeper https://api.github.co… Thom… London,… 46 +#> # A tibble: 6 × 7 +#> followers login url name location email public_gists +#> +#> 1 303 gaborcsardi https://api.github.co… Gábo… Chippen… csar… 6 +#> 2 780 jennybc https://api.github.co… Jenn… Vancouv… 54 +#> 3 3958 jtleek https://api.github.co… Jeff… Baltimo… 12 +#> 4 115 juliasilge https://api.github.co… Juli… Salt La… 4 +#> 5 213 leeper https://api.github.co… Thom… London,… 46 +#> 6 34 masalmon https://api.github.co… Maël… Barcelo… 0 ``` We can avoid the note about the unspecified field by formally providing @@ -78,13 +82,15 @@ spec <- guess_tspec(gh_users_small, inform_unspecified = FALSE) # Drop the unused email specification. spec$fields$email <- NULL tibblify(gh_users_small, spec = spec) -#> # A tibble: 4 × 6 -#> followers login url name location public_gists -#> -#> 1 780 jennybc https://api.github.com/users… Jenn… Vancouv… 54 -#> 2 3958 jtleek https://api.github.com/users… Jeff… Baltimo… 12 -#> 3 115 juliasilge https://api.github.com/users… Juli… Salt La… 4 -#> 4 213 leeper https://api.github.com/users… Thom… London,… 46 +#> # A tibble: 6 × 6 +#> followers login url name location public_gists +#> +#> 1 303 gaborcsardi https://api.github.com/user… Gábo… Chippen… 6 +#> 2 780 jennybc https://api.github.com/user… Jenn… Vancouv… 54 +#> 3 3958 jtleek https://api.github.com/user… Jeff… Baltimo… 12 +#> 4 115 juliasilge https://api.github.com/user… Juli… Salt La… 4 +#> 5 213 leeper https://api.github.com/user… Thom… London,… 46 +#> 6 34 masalmon https://api.github.com/user… Maël… Barcelo… 0 ``` Learn more in `vignette("tibblify")`. diff --git a/cran-comments.md b/cran-comments.md index f18cf735..169f40d4 100644 --- a/cran-comments.md +++ b/cran-comments.md @@ -2,5 +2,6 @@ 0 errors | 0 warnings | 1 note -* This fixes the C issue: format string is not a string literal. - +* This package was archived on 2025-12-18. Maintainer was transferred from @mgirlich to @jonthegeek on 2025-12-20. +* All errors and notes from the archived version have been addressed. +* The license of this package has also been updated from GPL-3 to MIT, with agreement from all code copyright holders in this PR: https://github.com/wranglezone/tibblify/pull/226 diff --git a/data/gh_repos.rda b/data/gh_repos.rda deleted file mode 100644 index 36374f14..00000000 Binary files a/data/gh_repos.rda and /dev/null differ diff --git a/data/gh_users.rda b/data/gh_users.rda deleted file mode 100644 index ef2167ee..00000000 Binary files a/data/gh_users.rda and /dev/null differ diff --git a/data/got_chars.rda b/data/got_chars.rda deleted file mode 100644 index 3c972ab0..00000000 Binary files a/data/got_chars.rda and /dev/null differ diff --git a/inst/WORDLIST b/inst/WORDLIST index a6918acf..eec225f0 100644 --- a/inst/WORDLIST +++ b/inst/WORDLIST @@ -1,18 +1,57 @@ +Backtick CMD +CamelCase Codecov Lifecycle +NULLs ORCID OpenAPI +POSIXct +POSIXlt POV Rectangling +Un +Unchop +Unclass Unnest +Untibblify +backtick +backticked bugfixes +cli +colmajor +datatypes +df +difftime http +jsonlite +memoise +memoised +pkgdown ptype +ptypes +purrr px recordlist rectangling repurrrsive +rrapply +schemas +tib tibble tibbles +tibblification +tibblified +tibblify's +tibblifying +tibs +tidyjson +tidyr +tidyselect +tspec +un +unchop +unchopped +unnest +untibblify vctrs diff --git a/man/dot-check_children_to.Rd b/man/dot-check_children_to.Rd index 6ff509ea..16a2ef8a 100644 --- a/man/dot-check_children_to.Rd +++ b/man/dot-check_children_to.Rd @@ -2,7 +2,7 @@ % Please edit documentation in R/nest_tree.R \name{.check_children_to} \alias{.check_children_to} -\title{Confirm that \code{children_to} is a single string that is different from \code{id_col} and \code{parent_col}} +\title{Confirm that \code{children_to} is usable} \usage{ .check_children_to(children_to, id_col, parent_col, call = caller_env()) } @@ -23,6 +23,6 @@ missing (for the root elements) or appear in the \code{id_col} column.} The input \code{children_to} object as a string. } \description{ -Confirm that \code{children_to} is a single string that is different from \code{id_col} and \code{parent_col} +Confirm that \code{children_to} is usable } \keyword{internal} diff --git a/man/dot-check_object_list.Rd b/man/dot-check_object_list.Rd index 0f9ee11b..3c9f8f9f 100644 --- a/man/dot-check_object_list.Rd +++ b/man/dot-check_object_list.Rd @@ -2,7 +2,7 @@ % Please edit documentation in R/shape_utils.R \name{.check_object_list} \alias{.check_object_list} -\title{bort if \code{x} is not a list of objects} +\title{Abort if \code{x} is not a list of objects} \usage{ .check_object_list(x, arg = caller_arg(x), call = caller_env()) } @@ -18,6 +18,6 @@ error messages as the input that is at the origin of a problem.} \code{x} (invisibly). Called for side effect. } \description{ -bort if \code{x} is not a list of objects +Abort if \code{x} is not a list of objects } \keyword{internal} diff --git a/man/dot-check_parent_ids.Rd b/man/dot-check_parent_ids.Rd index c5eb8b52..2a5e05fd 100644 --- a/man/dot-check_parent_ids.Rd +++ b/man/dot-check_parent_ids.Rd @@ -2,7 +2,7 @@ % Please edit documentation in R/nest_tree.R \name{.check_parent_ids} \alias{.check_parent_ids} -\title{Validate parent ids: cast to id type, check for self-references and missing roots} +\title{Confirm that parent ids are usable} \usage{ .check_parent_ids(x, parent_col, id_col, id_col_name, call = caller_env()) } @@ -25,6 +25,6 @@ identifies each observation.} The parent ids, cast to the same type as ids. } \description{ -Validate parent ids: cast to id type, check for self-references and missing roots +Confirm that parent ids are usable } \keyword{internal} diff --git a/man/dot-check_unnest_col_name.Rd b/man/dot-check_unnest_col_name.Rd index 3b5e6d41..1389776c 100644 --- a/man/dot-check_unnest_col_name.Rd +++ b/man/dot-check_unnest_col_name.Rd @@ -2,7 +2,7 @@ % Please edit documentation in R/unnest_tree.R \name{.check_unnest_col_name} \alias{.check_unnest_col_name} -\title{Validate and normalise an output column name} +\title{Validate and normalize an output column name} \usage{ .check_unnest_col_name( col_name, @@ -25,6 +25,6 @@ error messages.} \code{col_name} (cast to \code{character}) or \code{NULL}. } \description{ -Validate and normalise an output column name +Validate and normalize an output column name } \keyword{internal} diff --git a/man/dot-finalize_tspec_object.Rd b/man/dot-finalize_tspec_object.Rd index 2c42cbef..9419dade 100644 --- a/man/dot-finalize_tspec_object.Rd +++ b/man/dot-finalize_tspec_object.Rd @@ -20,6 +20,9 @@ \item{field}{(\code{any}) The field value.} } +\value{ +The finalized field spec. +} \description{ Finalize a tibblify object } diff --git a/man/gh_repos.Rd b/man/gh_repos.Rd deleted file mode 100644 index c17015ff..00000000 --- a/man/gh_repos.Rd +++ /dev/null @@ -1,16 +0,0 @@ -% Generated by roxygen2: do not edit by hand -% Please edit documentation in R/data.R -\docType{data} -\name{gh_repos} -\alias{gh_repos} -\title{GitHub repositories} -\format{ -A list of lists. -} -\usage{ -gh_repos -} -\description{ -A dataset containing some basic information about some GitHub repositories. -} -\keyword{datasets} diff --git a/man/gh_users.Rd b/man/gh_users.Rd deleted file mode 100644 index 8b68133a..00000000 --- a/man/gh_users.Rd +++ /dev/null @@ -1,16 +0,0 @@ -% Generated by roxygen2: do not edit by hand -% Please edit documentation in R/data.R -\docType{data} -\name{gh_users} -\alias{gh_users} -\title{GitHub users} -\format{ -A list of lists. -} -\usage{ -gh_users -} -\description{ -A dataset containing some basic information about six GitHub users. -} -\keyword{datasets} diff --git a/man/got_chars.Rd b/man/got_chars.Rd deleted file mode 100644 index aacb3971..00000000 --- a/man/got_chars.Rd +++ /dev/null @@ -1,31 +0,0 @@ -% Generated by roxygen2: do not edit by hand -% Please edit documentation in R/data.R -\docType{data} -\name{got_chars} -\alias{got_chars} -\title{Game of Thrones POV characters} -\format{ -A unnamed list with 30 components, each representing a POV character. -Each character's component is a named list of length 18, containing -information such as name, aliases, and house allegiances. -} -\source{ -\url{https://anapioficeandfire.com} -} -\usage{ -got_chars -} -\description{ -The data is from the \href{https://github.com/jennybc/repurrrsive}{repurrrsive package}. -} -\details{ -Info on the point-of-view (POV) characters from the first five books in the -Song of Ice and Fire series by George R. R. Martin. Retrieved from An API Of -Ice And Fire. -} -\examples{ -got_chars -str(lapply(got_chars, `[`, c("name", "culture"))) -} -\concept{Game of Thrones data and functions} -\keyword{datasets} diff --git a/man/guess_tspec.Rd b/man/guess_tspec.Rd index a1d0abd8..feb23b2a 100644 --- a/man/guess_tspec.Rd +++ b/man/guess_tspec.Rd @@ -101,6 +101,4 @@ supported by tibblify. \examples{ guess_tspec(list(x = 1, y = "a")) guess_tspec(list(list(x = 1), list(x = 2))) - -guess_tspec(gh_users) } diff --git a/man/parse_openapi_spec.Rd b/man/parse_openapi_spec.Rd index 19aec304..a5466e2b 100644 --- a/man/parse_openapi_spec.Rd +++ b/man/parse_openapi_spec.Rd @@ -88,7 +88,8 @@ file <- '{ }' parse_openapi_schema(file) -# Spec example from https://swagger.io/docs/specification/v3_0/basic-structure/ +# Spec example from +# https://swagger.io/docs/specification/v3_0/basic-structure/ spec_path <- system.file( "examples", "openapi", "sample_api.yaml", package = "tibblify" ) diff --git a/man/should_inform_unspecified.Rd b/man/should_inform_unspecified.Rd index 0e46ec78..8632836b 100644 --- a/man/should_inform_unspecified.Rd +++ b/man/should_inform_unspecified.Rd @@ -13,3 +13,6 @@ should_inform_unspecified() Wrapper around \code{getOption("tibblify.show_unspecified")} to return \code{TRUE} unless the option is explicitly set to \code{FALSE}. } +\examples{ +should_inform_unspecified() +} diff --git a/man/tib_spec.Rd b/man/tib_spec.Rd index ddca3f8a..ca148c31 100644 --- a/man/tib_spec.Rd +++ b/man/tib_spec.Rd @@ -410,7 +410,11 @@ tib_int("int") tib_int("int", .required = FALSE, .fill = 0) # This is essentially how `tib_chr_date()` is implemented. -tib_scalar("date", Sys.Date(), .transform = function(x) as.Date(x, format = "\%Y-\%m-\%d")) +tib_scalar( + "date", + Sys.Date(), + .transform = function(x) as.Date(x, format = "\%Y-\%m-\%d") +) tib_df( "data", diff --git a/man/tibblify-package.Rd b/man/tibblify-package.Rd index edf28494..d62ca1a6 100644 --- a/man/tibblify-package.Rd +++ b/man/tibblify-package.Rd @@ -5,7 +5,7 @@ \alias{tibblify-package} \title{tibblify: Rectangle Nested Lists} \description{ -A tool to rectangle a nested list, that is to convert it into a tibble. This is done automatically or according to a given specification. A common use case is for nested lists coming from parsing 'JSON' files, or the 'JSON' responses of 'REST' 'APIs'. Rectangling uses the 'vctrs' package, and therefore offers a wide support of vector types. +A tool to rectangle a nested list, that is to convert it into a 'tibble'. This is done automatically or according to a given specification. A common use case is for nested lists coming from parsing 'JSON' files, or the 'JSON' responses of 'REST' 'APIs'. 'Rectangling' uses the 'vctrs' package, and therefore offers a wide support of vector types. } \seealso{ Useful links: diff --git a/tests/testthat/_snaps/guess_tspec_object_list.md b/tests/testthat/_snaps/guess_tspec_object_list.md index cedefb4c..1077036d 100644 --- a/tests/testthat/_snaps/guess_tspec_object_list.md +++ b/tests/testthat/_snaps/guess_tspec_object_list.md @@ -1,7 +1,7 @@ # guess_tspec_object_list can guess spec for discog Code - guess_tspec_object_list(discog) + guess_tspec_object_list(repurrrsive::discog) Output tspec_df( tib_int("instance_id"), @@ -50,7 +50,7 @@ # guess_tspec_object_list can guess spec for gh_users Code - guess_tspec_object_list(gh_users) + guess_tspec_object_list(repurrrsive::gh_users) Output tspec_df( tib_chr("login"), diff --git a/tests/testthat/test-guess_tspec.R b/tests/testthat/test-guess_tspec.R index 71d1d8d0..22215ac3 100644 --- a/tests/testthat/test-guess_tspec.R +++ b/tests/testthat/test-guess_tspec.R @@ -7,15 +7,16 @@ test_that("guess_tspec errors informatively when x is neither df nor list", { }) test_that("guess_tspec dispatches correctly for object lists", { + skip_if_not_installed("repurrrsive") local_mocked_bindings( guess_tspec_list = function(x, ...) { expect_type(x, "list") cli::cli_inform("list", class = "list-called") } ) - expect_message(guess_tspec(discog), class = "list-called") - expect_message(guess_tspec(gh_users), class = "list-called") - expect_message(guess_tspec(got_chars), class = "list-called") + expect_message(guess_tspec(repurrrsive::discog), class = "list-called") + expect_message(guess_tspec(repurrrsive::gh_users), class = "list-called") + expect_message(guess_tspec(repurrrsive::got_chars), class = "list-called") }) test_that("guess_tspec dispatches correctly for objects", { diff --git a/tests/testthat/test-guess_tspec_df.R b/tests/testthat/test-guess_tspec_df.R index 480c640d..90356acb 100644 --- a/tests/testthat/test-guess_tspec_df.R +++ b/tests/testthat/test-guess_tspec_df.R @@ -459,6 +459,7 @@ test_that("inform about unspecified elements", { }) test_that("guess_tspec_df dispatches properly for object lists", { + skip_if_not_installed("repurrrsive") local_mocked_bindings( guess_tspec_object_list = function( x, @@ -469,5 +470,8 @@ test_that("guess_tspec_df dispatches properly for object lists", { cli::cli_inform("guess_tspec_object_list called") } ) - expect_message(guess_tspec_df(discog), "guess_tspec_object_list called") + expect_message( + guess_tspec_df(repurrrsive::discog), + "guess_tspec_object_list called" + ) }) diff --git a/tests/testthat/test-guess_tspec_list.R b/tests/testthat/test-guess_tspec_list.R index e688c5bb..7d8c031e 100644 --- a/tests/testthat/test-guess_tspec_list.R +++ b/tests/testthat/test-guess_tspec_list.R @@ -14,6 +14,7 @@ test_that("guess_tspec_list errors informatively for bad objects", { }) test_that("guess_tspec_list dispatches appropriately", { + skip_if_not_installed("repurrrsive") local_mocked_bindings( guess_tspec_object_list = function(x, ...) { cli::cli_inform("object_list", class = "object_list") @@ -31,9 +32,9 @@ test_that("guess_tspec_list dispatches appropriately", { expect_message(class = "object") # guess_tspec_object_list() - guess_tspec_list(discog) |> + guess_tspec_list(repurrrsive::discog) |> expect_message(class = "object_list") - guess_tspec_list(gh_users) |> + guess_tspec_list(repurrrsive::gh_users) |> expect_message(class = "object_list") read_sample_json("gsoc-2018.json") |> guess_tspec_list() |> diff --git a/tests/testthat/test-guess_tspec_object.R b/tests/testthat/test-guess_tspec_object.R index da8943f5..3fe0eab6 100644 --- a/tests/testthat/test-guess_tspec_object.R +++ b/tests/testthat/test-guess_tspec_object.R @@ -285,30 +285,12 @@ test_that("guess_tspec_object can guess tib_unspecified for an object (#83)", { tspec_object(x = tib_unspecified("x")) ) - # TODO not yet decided - # expect_equal( - # guess_tspec_object(list(x = list(NULL, NULL))), - # tspec_object(x = tib_unspecified("x")) - # ) - # in a row expect_equal( guess_tspec_object(list(x = list(a = NULL))), tspec_object(x = tib_unspecified("x")) ) - # TODO undecided - # expect_equal( - # guess_tspec_object(list(x = list(a = list()))), - # tspec_object(x = tib_row("x", tib_unspecified("a"))) - # ) - - # TODO undecided - # expect_equal( - # guess_tspec_object(list(x = list(a = list())), empty_list_unspecified = FALSE), - # tspec_object(x = tib_row("x", tib_unspecified("a"))) - # ) - # in a df expect_equal( guess_tspec_object( diff --git a/tests/testthat/test-guess_tspec_object_list.R b/tests/testthat/test-guess_tspec_object_list.R index adc07ac7..ee5b2cd0 100644 --- a/tests/testthat/test-guess_tspec_object_list.R +++ b/tests/testthat/test-guess_tspec_object_list.R @@ -51,13 +51,6 @@ test_that("can guess tib_unspecified", { tspec_df(x = tib_unspecified("x")) ) - # in a row - # TODO - # expect_equal( - # guess_tspec_object_list(list(list(x = list(a = NULL)), list(x = list(a = NULL)))), - # tspec_df(x = tib_row("x", a = tib_unspecified("a"))) - # ) - # in a df expect_equal( guess_tspec_object_list( @@ -128,11 +121,13 @@ test_that("guess_tspec_object_list errors informatively list of non-objects", { # specific cases ---- test_that("guess_tspec_object_list can guess spec for discog", { - expect_snapshot(guess_tspec_object_list(discog)) + skip_if_not_installed("repurrrsive") + expect_snapshot(guess_tspec_object_list(repurrrsive::discog)) }) test_that("guess_tspec_object_list can guess spec for gh_users", { - expect_snapshot(guess_tspec_object_list(gh_users)) + skip_if_not_installed("repurrrsive") + expect_snapshot(guess_tspec_object_list(repurrrsive::gh_users)) }) test_that("guess_tspec_object_list can guess spec for gsoc-2018", { diff --git a/tests/testthat/test-guess_tspec_object_utils.R b/tests/testthat/test-guess_tspec_object_utils.R index ddc915d5..9643e582 100644 --- a/tests/testthat/test-guess_tspec_object_utils.R +++ b/tests/testthat/test-guess_tspec_object_utils.R @@ -332,7 +332,6 @@ test_that(".guess_object_list_field_spec returns tib_variant when simplify_list }) test_that(".guess_object_list_field_spec respects empty_list_unspecified for vector ptype", { - # empty_list_unspecified = TRUE: empty lists are dropped, had_empty_lists is set local_env <- rlang::new_environment(list(empty_list_used = FALSE)) result <- .guess_object_list_field_spec( list(list(), 1L, 2L), diff --git a/tests/testthat/test-tibblify.R b/tests/testthat/test-tibblify.R index 7058ab1e..549484e1 100644 --- a/tests/testthat/test-tibblify.R +++ b/tests/testthat/test-tibblify.R @@ -158,6 +158,7 @@ test_that("can tibblify empty objects (#204)", { }) test_that("can tibblify discog", { + skip_if_not_installed("repurrrsive") row1 <- tibble( instance_id = 354823933L, date_added = "2019-02-16T17:48:59-08:00", @@ -252,14 +253,14 @@ test_that("can tibblify discog", { tib_int("rating"), ) - expect_equal(tibblify(discog[1], spec_collection), row1) + expect_equal(tibblify(repurrrsive::discog[1], spec_collection), row1) spec_collection2 <- spec_collection spec_collection2$fields$basic_information$fields$formats$fields$descriptions <- tib_chr_vec("descriptions", .required = FALSE) expect_equal(tibblify(row1, spec_collection2), row1) specs_object <- tspec_row(!!!spec_collection$fields) - expect_equal(tibblify(discog[[1]], specs_object), row1) + expect_equal(tibblify(repurrrsive::discog[[1]], specs_object), row1) expect_equal(tibblify(row1, specs_object), row1) }) diff --git a/vignettes/tibblify.Rmd b/vignettes/tibblify.Rmd index f2555a3d..12358657 100644 --- a/vignettes/tibblify.Rmd +++ b/vignettes/tibblify.Rmd @@ -17,6 +17,7 @@ knitr::opts_chunk$set( ```{r packages-used} library(tibblify) library(purrr) +library(repurrrsive) library(vctrs) ``` @@ -27,11 +28,22 @@ These lists might come from an API in the form of JSON or from scraping XML. ## Example -Let's start with `gh_users`, which is a list containing information about four GitHub users. +Let's start with `gh_users`, which is a list from the {repurrrsive} package containing information about four GitHub users. We'll select a subset of columns to keep the example relatively simple. ```{r} -gh_users_small <- purrr::map(gh_users, ~ .x[c("followers", "login", "url", "name", "location", "email", "public_gists")]) +gh_users_small <- purrr::map( + repurrrsive::gh_users, + ~ .x[c( + "followers", + "login", + "url", + "name", + "location", + "email", + "public_gists" + )] +) names(gh_users_small[[1]]) ``` @@ -184,7 +196,10 @@ You can use [`tidyr::unnest()`](https://tidyr.tidyverse.org/reference/nest.html) In `gh_repos_small`, the field `owner` is an object itself. ```{r} -gh_repos_small <- purrr::map(gh_repos, ~ .x[c("id", "name", "owner")]) +gh_repos_small <- purrr::map( + repurrrsive::gh_repos[[1]], + ~ .x[c("id", "name", "owner")] +) gh_repos_small <- purrr::map( gh_repos_small, function(repo) {