Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
13 changes: 6 additions & 7 deletions DESCRIPTION
Original file line number Diff line number Diff line change
@@ -1,17 +1,17 @@
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")),
person("Maximilian", "Girlich", , "maximilian.girlich@outlook.com", role = c("aut", "cph")),
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,
Expand All @@ -34,6 +34,7 @@ Suggests:
jsonlite (>= 1.8.0),
knitr (>= 1.40),
memoise (>= 2.0.1),
repurrrsive,
rmarkdown (>= 2.16),
spelling (>= 2.2),
stbl,
Expand All @@ -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
25 changes: 19 additions & 6 deletions NEWS.md
Original file line number Diff line number Diff line change
@@ -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 (<https://github.com/wranglezone/tibblify/issues>) 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

Expand Down
33 changes: 0 additions & 33 deletions R/data.R
Original file line number Diff line number Diff line change
@@ -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 <https://anapioficeandfire.com>
#' @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"
1 change: 1 addition & 0 deletions R/finalize_tspec_object.R
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand Down
2 changes: 0 additions & 2 deletions R/guess_tspec.R
Original file line number Diff line number Diff line change
Expand Up @@ -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,
...,
Expand Down
3 changes: 1 addition & 2 deletions R/guess_tspec_list.R
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
16 changes: 8 additions & 8 deletions R/guess_tspec_object.R
Original file line number Diff line number Diff line change
Expand Up @@ -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))
}
Expand Down Expand Up @@ -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))
}
Expand All @@ -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))
}
Expand Down
10 changes: 6 additions & 4 deletions R/guess_tspec_utils.R
Original file line number Diff line number Diff line change
Expand Up @@ -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"
}
Expand Down
34 changes: 20 additions & 14 deletions R/nest_tree.R
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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)
}
Expand All @@ -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)
}
Expand Down
3 changes: 2 additions & 1 deletion R/parse_openapi.R
Original file line number Diff line number Diff line change
Expand Up @@ -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"
#' )
Expand Down
14 changes: 6 additions & 8 deletions R/shape_utils.R
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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",
Expand Down
2 changes: 2 additions & 0 deletions R/should_inform_unspecified.R
Original file line number Diff line number Diff line change
Expand Up @@ -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"))
}
16 changes: 10 additions & 6 deletions R/spec_combine.R
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Expand Down Expand Up @@ -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
Expand Down
Loading
Loading