├── .github ├── .gitignore ├── ISSUE_TEMPLATE │ └── issue_template.md ├── workflows │ ├── qcthis.yaml │ ├── pkgdown.yaml │ ├── R-CMD-check.yaml │ ├── test-coverage.yaml │ ├── pr-commands.yaml │ └── rhub.yaml ├── scripts │ ├── check-workflow-runs.sh │ ├── check-timestamp.sh │ └── fetch-issues.R ├── SUPPORT.md └── CONTRIBUTING.md ├── vignettes └── .gitignore ├── LICENSE ├── cran-comments.md ├── tests ├── testthat │ ├── helper-snapshots.R │ ├── test-object_type.R │ ├── _snaps │ │ ├── to_lst.md │ │ ├── pkg_abort.md │ │ ├── stabilize_fct.md │ │ ├── to_null.md │ │ ├── stabilize_int.md │ │ ├── stabilize_lgl.md │ │ ├── check.md │ │ ├── stabilize_dbl.md │ │ ├── specify_cls.md │ │ ├── aaa-conditions.md │ │ ├── to_fct.md │ │ ├── to_dbl.md │ │ └── to_chr.md │ ├── helper-wrap.R │ ├── test-utils.R │ ├── test-regex.R │ ├── test-to_null.R │ ├── test-stabilize_fct.R │ ├── test-to_lst.R │ ├── test-stabilize_int.R │ ├── test-are_fct_ish.R │ ├── test-stabilize_dbl.R │ ├── test-are_chr_ish.R │ ├── test-are_lgl_ish.R │ ├── test-are_dbl_ish.R │ ├── test-stabilize_lgl.R │ └── test-are_int_ish.R └── testthat.R ├── .gitignore ├── .Rbuildignore ├── codecov.yml ├── man ├── injection-operator.Rd ├── dot-shared-params-check.Rd ├── dot-compile_dash.Rd ├── dot-collapse_dash.Rd ├── dot-elements_are_cls_ish.Rd ├── dot-are_not_dbl_ish_chr.Rd ├── dot-fast_false.Rd ├── reexports.Rd ├── dot-cli_mark.Rd ├── dot-are_not_int_ish_chr.Rd ├── dot-maybe_check_dupes.Rd ├── object_type.Rd ├── dot-compile_pkg_error_classes.Rd ├── dot-compile_pkg_condition_classes.Rd ├── dot-cli_escape.Rd ├── dot-are_not_fct_ish_chr.Rd ├── dot-describe_failure_dbl_value_single.Rd ├── dot-construct_stabilizer_symbol.Rd ├── dot-coerce_fct_to_na.Rd ├── dot-describe_failure_dbl_value_multi.Rd ├── dot-define_main_msg.Rd ├── dot-find_failures.Rd ├── dot-is_allowed_null.Rd ├── grapes-and-and-grapes.Rd ├── dot-describe_failure_chr.Rd ├── dot-stop_function.Rd ├── stbl-package.Rd ├── dot-check_is_not_primitive.Rd ├── dot-to_null.Rd ├── dot-check_na.Rd ├── dot-glue2.Rd ├── dot-apply_regex_rule.Rd ├── dot-check_function_allowed.Rd ├── dot-coerce_fct_levels.Rd ├── dot-check_x_no_more_than_y.Rd ├── dot-describe_failure_dbl_value.Rd ├── dot-check_chr_to_int_failures.Rd ├── dot-coerce_fct_levels_impl.Rd ├── dot-stop_null.Rd ├── dot-stop_bad_levels.Rd ├── dot-check_value_dbl.Rd ├── specify_cls.Rd ├── dot-check_size.Rd ├── dot-check_cast_failures.Rd ├── expect_pkg_error_classes.Rd ├── dot-stbl_abort.Rd ├── dot-to_cls_from_list.Rd ├── regex_must_match.Rd ├── dot-check_scalar.Rd ├── dot-construct_specification_fn.Rd ├── dot-has_regex_pattern.Rd ├── dot-to_num_from_complex.Rd ├── dot-check_value_chr.Rd ├── dot-stop_must.Rd ├── dot-stop_cant_coerce.Rd ├── dot-stop_incompatible.Rd ├── dot-to_cls_scalar.Rd ├── dot-to_cls_from_fct.Rd ├── pkg_abort.Rd ├── are_chr_ish.Rd ├── dot-stabilize_cls_scalar.Rd ├── are_lgl_ish.Rd ├── dot-stabilize_cls.Rd ├── to_lst.Rd ├── are_fct_ish.Rd ├── are_dbl_ish.Rd ├── specify_lgl.Rd ├── are_int_ish.Rd ├── stabilize_arg.Rd └── specify_fct.Rd ├── stbl.Rproj ├── R ├── to_null.R ├── stbl-package.R ├── object_type.R ├── regex.R ├── are_chr_ish.R ├── stabilize_arg.R ├── to_chr.R ├── are_lgl_ish.R ├── utils.R ├── to_lgl.R ├── are_dbl_ish.R ├── are_fct_ish.R └── stabilize_lgl.R ├── _pkgdown.yml ├── DESCRIPTION ├── LICENSE.md └── NEWS.md /.github/.gitignore: -------------------------------------------------------------------------------- 1 | *.html 2 | -------------------------------------------------------------------------------- /vignettes/.gitignore: -------------------------------------------------------------------------------- 1 | *.html 2 | *.R 3 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | YEAR: 2023 2 | COPYRIGHT HOLDER: stbl authors 3 | -------------------------------------------------------------------------------- /cran-comments.md: -------------------------------------------------------------------------------- 1 | ## R CMD check results 2 | 3 | 0 errors | 0 warnings | 0 notes 4 | -------------------------------------------------------------------------------- /tests/testthat/helper-snapshots.R: -------------------------------------------------------------------------------- 1 | clean_function_snapshot <- function(x) { 2 | x[!grepl(", , or . 7 | 8 | Please include a minimal reproducible example (AKA a reprex). If you've never heard of a [reprex](http://reprex.tidyverse.org/) before, start by reading . 9 | 10 | Brief description of the problem 11 | 12 | ```r 13 | # insert reprex here 14 | ``` 15 | -------------------------------------------------------------------------------- /man/dot-cli_escape.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/utils.R 3 | \name{.cli_escape} 4 | \alias{.cli_escape} 5 | \title{Escape curly braces for safe printing with cli} 6 | \usage{ 7 | .cli_escape(msg) 8 | } 9 | \arguments{ 10 | \item{msg}{\code{(character)} The messages to escape.} 11 | } 12 | \value{ 13 | The messages with curly braces escaped. 14 | } 15 | \description{ 16 | Replaces single curly braces (\verb{\{}, \verb{\}}) with double curly braces (\verb{\{\{}, 17 | \verb{\}\}}) so that they are interpreted as literal characters by 18 | \code{\link[cli:cli_abort]{cli::cli_abort()}} and not as expressions to be evaluated. 19 | } 20 | \keyword{internal} 21 | -------------------------------------------------------------------------------- /man/dot-are_not_fct_ish_chr.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/are_fct_ish.R 3 | \name{.are_not_fct_ish_chr} 4 | \alias{.are_not_fct_ish_chr} 5 | \title{Check for values that would be lost during factor coercion} 6 | \usage{ 7 | .are_not_fct_ish_chr(x, levels, to_na = character()) 8 | } 9 | \arguments{ 10 | \item{x}{The object to check.} 11 | 12 | \item{levels}{\code{(character)} The desired factor levels.} 13 | 14 | \item{to_na}{\code{(character)} Values to convert to \code{NA}.} 15 | } 16 | \value{ 17 | A logical vector where \code{TRUE} indicates a failure. 18 | } 19 | \description{ 20 | Check for values that would be lost during factor coercion 21 | } 22 | \keyword{internal} 23 | -------------------------------------------------------------------------------- /man/dot-describe_failure_dbl_value_single.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/stabilize_dbl.R 3 | \name{.describe_failure_dbl_value_single} 4 | \alias{.describe_failure_dbl_value_single} 5 | \title{Describe a single numeric value failure} 6 | \usage{ 7 | .describe_failure_dbl_value_single(x, msg_main, direction) 8 | } 9 | \arguments{ 10 | \item{x}{\code{(numeric)} The vector being checked.} 11 | 12 | \item{msg_main}{\code{(character)} The main error message.} 13 | 14 | \item{direction}{\code{(character)} One of \code{"low"} or \code{"high"}.} 15 | } 16 | \value{ 17 | A named character vector. 18 | } 19 | \description{ 20 | Describe a single numeric value failure 21 | } 22 | \keyword{internal} 23 | -------------------------------------------------------------------------------- /man/dot-construct_stabilizer_symbol.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/specify_cls.R 3 | \name{.construct_stabilizer_symbol} 4 | \alias{.construct_stabilizer_symbol} 5 | \title{Assemble the function name of the stabilizer} 6 | \usage{ 7 | .construct_stabilizer_symbol(stabilizer, scalar = FALSE) 8 | } 9 | \arguments{ 10 | \item{stabilizer}{\verb{(length-1 character)} Name of the stabilizer function to 11 | call.} 12 | 13 | \item{scalar}{\verb{(length-1 logical)} Whether to call the scalar version of the 14 | stabilizer.} 15 | } 16 | \value{ 17 | The symbol of the stabilizer function to call. 18 | } 19 | \description{ 20 | Assemble the function name of the stabilizer 21 | } 22 | \keyword{internal} 23 | -------------------------------------------------------------------------------- /_pkgdown.yml: -------------------------------------------------------------------------------- 1 | url: https://stbl.wrangle.zone/ 2 | development: 3 | mode: auto 4 | template: 5 | bootstrap: 5 6 | lang: en-US 7 | 8 | reference: 9 | - title: character vectors 10 | - contents: 11 | - matches("chr") 12 | - regex_must_match 13 | - title: double vectors 14 | - contents: 15 | - matches("dbl") 16 | - title: factors 17 | - contents: 18 | - matches("fct") 19 | - title: integer vectors 20 | - contents: 21 | - matches("int") 22 | - title: logical vectors 23 | - contents: 24 | - matches("lgl") 25 | - title: lists 26 | - contents: 27 | - matches("lst") 28 | - title: generic args 29 | - contents: 30 | - object_type 31 | - stabilize_arg 32 | - title: conditions 33 | - contents: 34 | - pkg_abort 35 | - expect_pkg_error_classes 36 | -------------------------------------------------------------------------------- /man/dot-coerce_fct_to_na.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/to_fct.R 3 | \name{.coerce_fct_to_na} 4 | \alias{.coerce_fct_to_na} 5 | \title{Coerce specified values to NA} 6 | \usage{ 7 | .coerce_fct_to_na(x, to_na = character(), call = caller_env()) 8 | } 9 | \arguments{ 10 | \item{x}{The argument to stabilize.} 11 | 12 | \item{to_na}{\code{(character)} Values to convert to \code{NA}.} 13 | 14 | \item{call}{\code{(environment)} The execution environment to mention as the 15 | source of error messages.} 16 | } 17 | \value{ 18 | \code{x} with specified values converted to \code{NA}. 19 | } 20 | \description{ 21 | A helper that converts specified values in \code{x} to \code{NA}. 22 | } 23 | \keyword{internal} 24 | -------------------------------------------------------------------------------- /man/dot-describe_failure_dbl_value_multi.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/stabilize_dbl.R 3 | \name{.describe_failure_dbl_value_multi} 4 | \alias{.describe_failure_dbl_value_multi} 5 | \title{Describe multiple numeric value failures} 6 | \usage{ 7 | .describe_failure_dbl_value_multi(x, msg_main, failure_locations, direction) 8 | } 9 | \arguments{ 10 | \item{x}{\code{(numeric)} The vector being checked.} 11 | 12 | \item{msg_main}{\code{(character)} The main error message.} 13 | 14 | \item{direction}{\code{(character)} One of \code{"low"} or \code{"high"}.} 15 | } 16 | \value{ 17 | A named character vector. 18 | } 19 | \description{ 20 | Describe multiple numeric value failures 21 | } 22 | \keyword{internal} 23 | -------------------------------------------------------------------------------- /man/dot-define_main_msg.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/aaa-conditions.R 3 | \name{.define_main_msg} 4 | \alias{.define_main_msg} 5 | \title{Define the main error message for a "must" error} 6 | \usage{ 7 | .define_main_msg(x_arg, msg) 8 | } 9 | \arguments{ 10 | \item{x_arg}{\verb{(length-1 character)} An argument name for x. The automatic 11 | value will work in most cases, or pass it through from higher-level 12 | functions to make error messages clearer in unexported functions.} 13 | 14 | \item{msg}{\code{(character)} The core error message describing the requirement.} 15 | } 16 | \value{ 17 | A character string. 18 | } 19 | \description{ 20 | Define the main error message for a "must" error 21 | } 22 | \keyword{internal} 23 | -------------------------------------------------------------------------------- /man/dot-find_failures.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/utils.R 3 | \name{.find_failures} 4 | \alias{.find_failures} 5 | \title{Safely find failure locations in a vector} 6 | \usage{ 7 | .find_failures(x, check_value, check_fn) 8 | } 9 | \arguments{ 10 | \item{x}{The vector to check.} 11 | 12 | \item{check_value}{The value to check against (e.g., a regex pattern). If 13 | \code{NULL}, the check is skipped.} 14 | 15 | \item{check_fn}{The function to use for checking.} 16 | } 17 | \value{ 18 | An integer vector of failure locations, or \code{NULL} if there are no 19 | failures or the check is skipped. 20 | } 21 | \description{ 22 | Run \code{check_fn(x, check_value)} if \code{check_value} isn't \code{NULL}. 23 | } 24 | \keyword{internal} 25 | -------------------------------------------------------------------------------- /man/dot-is_allowed_null.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/check.R 3 | \name{.is_allowed_null} 4 | \alias{.is_allowed_null} 5 | \title{Check if a value is NULL and NULLs are allowed} 6 | \usage{ 7 | .is_allowed_null(x, allow_null = TRUE, call = caller_env()) 8 | } 9 | \arguments{ 10 | \item{x}{The object to check.} 11 | 12 | \item{allow_null}{\verb{(length-1 logical)} Is NULL an acceptable value?} 13 | 14 | \item{call}{\code{(environment)} The execution environment to mention as the 15 | source of error messages.} 16 | } 17 | \value{ 18 | \verb{(length-1 logical)} \code{TRUE} if \code{x} is \code{NULL} and \code{allow_null} is 19 | \code{TRUE}. 20 | } 21 | \description{ 22 | Check if a value is NULL and NULLs are allowed 23 | } 24 | \keyword{internal} 25 | -------------------------------------------------------------------------------- /man/grapes-and-and-grapes.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/utils.R 3 | \name{\%&&\%} 4 | \alias{\%&&\%} 5 | \title{NULL-coalescing-like operator} 6 | \usage{ 7 | x \%&&\% y 8 | } 9 | \arguments{ 10 | \item{x}{The object to check for \code{NULL}.} 11 | 12 | \item{y}{The value to return if \code{x} is not \code{NULL}.} 13 | } 14 | \value{ 15 | \code{NULL} or the value of \code{y}. 16 | } 17 | \description{ 18 | If the left-hand side is not \code{NULL}, returns the right-hand side. Otherwise, 19 | returns \code{NULL}. This is useful for guarding expressions that should only be 20 | executed if a value is not \code{NULL}. Meant to be similar to the \verb{\%||\%} operator 21 | (which returns \code{y} if \code{x} is \code{NULL}). 22 | } 23 | \keyword{internal} 24 | -------------------------------------------------------------------------------- /man/dot-describe_failure_chr.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/stabilize_chr.R 3 | \name{.describe_failure_chr} 4 | \alias{.describe_failure_chr} 5 | \title{Describe a character-based validation failure} 6 | \usage{ 7 | .describe_failure_chr(x, success, negate = FALSE) 8 | } 9 | \arguments{ 10 | \item{x}{The argument to stabilize.} 11 | 12 | \item{success}{\code{(logical)} A logical vector indicating which elements of \code{x} 13 | passed the check.} 14 | 15 | \item{negate}{\verb{(length-1 logical)} Was the check a negative one?} 16 | } 17 | \value{ 18 | A named character vector to be used as \code{additional_msg} in 19 | \code{\link[=.stop_must]{.stop_must()}}. 20 | } 21 | \description{ 22 | Describe a character-based validation failure 23 | } 24 | \keyword{internal} 25 | -------------------------------------------------------------------------------- /man/dot-stop_function.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/to_lst.R 3 | \name{.stop_function} 4 | \alias{.stop_function} 5 | \title{Abort because an argument must not be a function} 6 | \usage{ 7 | .stop_function(x_arg, call) 8 | } 9 | \arguments{ 10 | \item{x_arg}{\verb{(length-1 character)} An argument name for x. The automatic 11 | value will work in most cases, or pass it through from higher-level 12 | functions to make error messages clearer in unexported functions.} 13 | 14 | \item{call}{\code{(environment)} The execution environment to mention as the 15 | source of error messages.} 16 | } 17 | \value{ 18 | This function is called for its side effect of throwing an error and 19 | does not return a value. 20 | } 21 | \description{ 22 | Abort because an argument must not be a function 23 | } 24 | \keyword{internal} 25 | -------------------------------------------------------------------------------- /man/stbl-package.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/stbl-package.R 3 | \docType{package} 4 | \name{stbl-package} 5 | \alias{stbl} 6 | \alias{stbl-package} 7 | \title{stbl: Stabilize Function Arguments} 8 | \description{ 9 | A set of consistent, opinionated functions to quickly check function arguments, coerce them to the desired configuration, or deliver informative error messages when that is not possible. 10 | } 11 | \seealso{ 12 | Useful links: 13 | \itemize{ 14 | \item \url{https://stbl.wrangle.zone/} 15 | \item \url{https://github.com/wranglezone/stbl} 16 | \item Report bugs at \url{https://github.com/wranglezone/stbl/issues} 17 | } 18 | 19 | } 20 | \author{ 21 | \strong{Maintainer}: Jon Harmon \email{jonthegeek@gmail.com} (\href{https://orcid.org/0000-0003-4781-4346}{ORCID}) [copyright holder] 22 | 23 | } 24 | \keyword{internal} 25 | -------------------------------------------------------------------------------- /man/dot-check_is_not_primitive.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/to_lst.R 3 | \name{.check_is_not_primitive} 4 | \alias{.check_is_not_primitive} 5 | \title{Error if an object is a primitive function} 6 | \usage{ 7 | .check_is_not_primitive(x, x_arg = caller_arg(x), call = caller_env()) 8 | } 9 | \arguments{ 10 | \item{x}{The object to check.} 11 | 12 | \item{x_arg}{\verb{(length-1 character)} An argument name for x. The automatic 13 | value will work in most cases, or pass it through from higher-level 14 | functions to make error messages clearer in unexported functions.} 15 | 16 | \item{call}{\code{(environment)} The execution environment to mention as the 17 | source of error messages.} 18 | } 19 | \value{ 20 | \code{NULL}, invisibly, if \code{x} passes the check. 21 | } 22 | \description{ 23 | Error if an object is a primitive function 24 | } 25 | \keyword{internal} 26 | -------------------------------------------------------------------------------- /man/dot-to_null.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/to_null.R 3 | \name{.to_null} 4 | \alias{.to_null} 5 | \title{Ensure an argument is NULL} 6 | \usage{ 7 | .to_null(x, allow_null = TRUE, x_arg = caller_arg(x), call = caller_env()) 8 | } 9 | \arguments{ 10 | \item{x}{The argument to stabilize.} 11 | 12 | \item{allow_null}{\verb{(length-1 logical)} Is NULL an acceptable value?} 13 | 14 | \item{x_arg}{\verb{(length-1 character)} An argument name for x. The automatic 15 | value will work in most cases, or pass it through from higher-level 16 | functions to make error messages clearer in unexported functions.} 17 | 18 | \item{call}{\code{(environment)} The execution environment to mention as the 19 | source of error messages.} 20 | } 21 | \value{ 22 | \code{NULL} or an error. 23 | } 24 | \description{ 25 | If \code{allow_null} is \code{TRUE}, coerce \code{x} to \code{NULL}. Otherwise throw an 26 | informative error. 27 | } 28 | \keyword{internal} 29 | -------------------------------------------------------------------------------- /man/dot-check_na.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/check.R 3 | \name{.check_na} 4 | \alias{.check_na} 5 | \title{Check for NA values} 6 | \usage{ 7 | .check_na(x, allow_na = TRUE, x_arg = caller_arg(x), call = caller_env()) 8 | } 9 | \arguments{ 10 | \item{x}{The object to check.} 11 | 12 | \item{allow_na}{\verb{(length-1 logical)} Are NA values ok?} 13 | 14 | \item{x_arg}{\verb{(length-1 character)} An argument name for x. The automatic 15 | value will work in most cases, or pass it through from higher-level 16 | functions to make error messages clearer in unexported functions.} 17 | 18 | \item{call}{\code{(environment)} The execution environment to mention as the 19 | source of error messages.} 20 | } 21 | \value{ 22 | \code{NULL}, invisibly, if \code{x} passes the check. 23 | } 24 | \description{ 25 | Checks for \code{NA} values in \code{x}, throwing an error if any are found and 26 | \code{allow_na} is \code{FALSE}. 27 | } 28 | \keyword{internal} 29 | -------------------------------------------------------------------------------- /man/dot-glue2.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/utils.R 3 | \name{.glue2} 4 | \alias{.glue2} 5 | \title{A wrapper for \code{glue::glue} with custom delimiters} 6 | \usage{ 7 | .glue2(..., env = caller_env()) 8 | } 9 | \arguments{ 10 | \item{...}{Arguments passed on to \code{\link[glue:glue]{glue::glue()}}. Usually expects unnamed 11 | arguments but named arguments other than \code{.envir}, \code{.open}, and \code{.close} 12 | are acceptable.} 13 | 14 | \item{env}{The environment in which to evaluate the expressions.} 15 | } 16 | \value{ 17 | A character string with evaluated expressions. 18 | } 19 | \description{ 20 | This wrapper sets the \code{.open} and \code{.close} arguments of \code{\link[glue:glue]{glue::glue()}} to \code{[} 21 | and \verb{]}, respectively. This allows for safe use of glue interpolation 22 | within messages that will be processed by \code{\link[cli:cli_abort]{cli::cli_abort()}}, which uses \verb{\{} 23 | and \verb{\}} for its own styling. 24 | } 25 | \keyword{internal} 26 | -------------------------------------------------------------------------------- /man/dot-apply_regex_rule.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/stabilize_chr.R 3 | \name{.apply_regex_rule} 4 | \alias{.apply_regex_rule} 5 | \title{Apply a single regex rule to a character vector} 6 | \usage{ 7 | .apply_regex_rule(rule, x, x_arg, call) 8 | } 9 | \arguments{ 10 | \item{rule}{\verb{(length-1 character)} A regex rule (possibly with a \code{name} and 11 | \code{negate} attribute).} 12 | 13 | \item{x}{The argument to stabilize.} 14 | 15 | \item{x_arg}{\verb{(length-1 character)} An argument name for x. The automatic 16 | value will work in most cases, or pass it through from higher-level 17 | functions to make error messages clearer in unexported functions.} 18 | 19 | \item{call}{\code{(environment)} The execution environment to mention as the 20 | source of error messages.} 21 | } 22 | \value{ 23 | A character vector of error messages if the rule fails, otherwise 24 | \code{NULL}. 25 | } 26 | \description{ 27 | Apply a single regex rule to a character vector 28 | } 29 | \keyword{internal} 30 | -------------------------------------------------------------------------------- /man/dot-check_function_allowed.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/to_lst.R 3 | \name{.check_function_allowed} 4 | \alias{.check_function_allowed} 5 | \title{Check whether functions are allowed} 6 | \usage{ 7 | .check_function_allowed( 8 | x, 9 | coerce_function = FALSE, 10 | x_arg = caller_arg(x), 11 | call = caller_env() 12 | ) 13 | } 14 | \arguments{ 15 | \item{x}{The object to check.} 16 | 17 | \item{coerce_function}{\verb{(length-1 logical)} Should functions be coerced?} 18 | 19 | \item{x_arg}{\verb{(length-1 character)} An argument name for x. The automatic 20 | value will work in most cases, or pass it through from higher-level 21 | functions to make error messages clearer in unexported functions.} 22 | 23 | \item{call}{\code{(environment)} The execution environment to mention as the 24 | source of error messages.} 25 | } 26 | \value{ 27 | \code{NULL}, invisibly, if \code{x} passes the check. 28 | } 29 | \description{ 30 | Check whether functions are allowed 31 | } 32 | \keyword{internal} 33 | -------------------------------------------------------------------------------- /R/object_type.R: -------------------------------------------------------------------------------- 1 | # object_type was derived from use_standalone("r-lib/rlang", 2 | # "standalone-obj-type.R") but simplified. 3 | 4 | #' Identify the class, type, etc of an object 5 | #' 6 | #' Extract the class (or type) of an object for use in error messages. 7 | #' 8 | #' @param x An object to test. 9 | #' 10 | #' @returns A length-1 character vector describing the class of the object. 11 | #' @export 12 | #' 13 | #' @examples 14 | #' object_type("a") 15 | #' object_type(1L) 16 | #' object_type(1.1) 17 | #' object_type(mtcars) 18 | #' object_type(rlang::quo(something)) 19 | object_type <- function(x) { 20 | if (missing(x)) { 21 | return("missing") 22 | } 23 | 24 | # Anything with a class. 25 | if (is.object(x)) { 26 | if (inherits(x, "quosure")) { 27 | return("quosure") 28 | } 29 | return(class(x)[[1L]]) 30 | } 31 | 32 | # Leftovers. Consider adding more cases here, but for now I like the 33 | # specificity. 34 | type <- typeof(x) 35 | return( 36 | switch(type, language = "call", closure = "function", type) 37 | ) 38 | } 39 | -------------------------------------------------------------------------------- /DESCRIPTION: -------------------------------------------------------------------------------- 1 | Package: stbl 2 | Title: Stabilize Function Arguments 3 | Version: 0.2.0.9004 4 | Authors@R: 5 | person("Jon", "Harmon", , "jonthegeek@gmail.com", role = c("aut", "cre", "cph"), 6 | comment = c(ORCID = "0000-0003-4781-4346")) 7 | Description: A set of consistent, opinionated functions to quickly check 8 | function arguments, coerce them to the desired configuration, or 9 | deliver informative error messages when that is not possible. 10 | License: MIT + file LICENSE 11 | URL: https://stbl.wrangle.zone/, https://github.com/wranglezone/stbl 12 | BugReports: https://github.com/wranglezone/stbl/issues 13 | Depends: 14 | R (>= 4.1) 15 | Imports: 16 | cli (>= 3.4.0), 17 | glue, 18 | rlang (>= 1.0.2.9003), 19 | vctrs 20 | Suggests: 21 | knitr, 22 | rmarkdown, 23 | stringi, 24 | stringr, 25 | testthat (>= 3.3.0) 26 | VignetteBuilder: 27 | knitr 28 | Config/testthat/edition: 3 29 | Config/testthat/parallel: true 30 | Encoding: UTF-8 31 | Language: en-US 32 | Roxygen: list(markdown = TRUE) 33 | RoxygenNote: 7.3.3 34 | -------------------------------------------------------------------------------- /man/dot-coerce_fct_levels.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/to_fct.R 3 | \name{.coerce_fct_levels} 4 | \alias{.coerce_fct_levels} 5 | \title{Coerce to factor with specified levels} 6 | \usage{ 7 | .coerce_fct_levels( 8 | x, 9 | levels = NULL, 10 | to_na = character(), 11 | x_arg = caller_arg(x), 12 | call = caller_env() 13 | ) 14 | } 15 | \arguments{ 16 | \item{x}{The argument to stabilize.} 17 | 18 | \item{levels}{\code{(character)} The desired factor levels.} 19 | 20 | \item{to_na}{\code{(character)} Values to convert to \code{NA}.} 21 | 22 | \item{x_arg}{\verb{(length-1 character)} An argument name for x. The automatic 23 | value will work in most cases, or pass it through from higher-level 24 | functions to make error messages clearer in unexported functions.} 25 | 26 | \item{call}{\code{(environment)} The execution environment to mention as the 27 | source of error messages.} 28 | } 29 | \value{ 30 | \code{x} as a factor with specified levels and NAs. 31 | } 32 | \description{ 33 | A wrapper around level-coercion helpers. 34 | } 35 | \keyword{internal} 36 | -------------------------------------------------------------------------------- /man/dot-check_x_no_more_than_y.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/check.R 3 | \name{.check_x_no_more_than_y} 4 | \alias{.check_x_no_more_than_y} 5 | \title{Check that one value is not greater than another} 6 | \usage{ 7 | .check_x_no_more_than_y( 8 | x, 9 | y, 10 | x_arg = caller_arg(x), 11 | y_arg = caller_arg(y), 12 | call = caller_env() 13 | ) 14 | } 15 | \arguments{ 16 | \item{x}{The object to check.} 17 | 18 | \item{y}{The value to compare against.} 19 | 20 | \item{x_arg}{\verb{(length-1 character)} An argument name for x. The automatic 21 | value will work in most cases, or pass it through from higher-level 22 | functions to make error messages clearer in unexported functions.} 23 | 24 | \item{y_arg}{\verb{(length-1 character)} The name of the \code{y} argument.} 25 | 26 | \item{call}{\code{(environment)} The execution environment to mention as the 27 | source of error messages.} 28 | } 29 | \value{ 30 | \code{NULL}, invisibly, if \code{x} is not greater than \code{y}. 31 | } 32 | \description{ 33 | Check that one value is not greater than another 34 | } 35 | \keyword{internal} 36 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | # MIT License 2 | 3 | Copyright (c) 2023 stbl authors 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /man/dot-describe_failure_dbl_value.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/stabilize_dbl.R 3 | \name{.describe_failure_dbl_value} 4 | \alias{.describe_failure_dbl_value} 5 | \title{Describe a numeric value validation failure} 6 | \usage{ 7 | .describe_failure_dbl_value( 8 | x, 9 | failure_locations, 10 | direction, 11 | target_value, 12 | x_arg 13 | ) 14 | } 15 | \arguments{ 16 | \item{x}{\code{(numeric)} The vector being checked.} 17 | 18 | \item{failure_locations}{\code{(integer)} Indices where the check failed.} 19 | 20 | \item{direction}{\code{(character)} One of \code{"low"} or \code{"high"}.} 21 | 22 | \item{target_value}{\code{(numeric)} The value against which \code{x} is being compared.} 23 | 24 | \item{x_arg}{\verb{(length-1 character)} An argument name for x. The automatic 25 | value will work in most cases, or pass it through from higher-level 26 | functions to make error messages clearer in unexported functions.} 27 | } 28 | \value{ 29 | A named character vector for \code{.stbl_abort()}. 30 | } 31 | \description{ 32 | Describe a numeric value validation failure 33 | } 34 | \keyword{internal} 35 | -------------------------------------------------------------------------------- /man/dot-check_chr_to_int_failures.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/to_int.R 3 | \name{.check_chr_to_int_failures} 4 | \alias{.check_chr_to_int_failures} 5 | \title{Check for character to integer coercion failures} 6 | \usage{ 7 | .check_chr_to_int_failures(x, x_class, x_arg, call) 8 | } 9 | \arguments{ 10 | \item{x}{The argument to stabilize.} 11 | 12 | \item{x_class}{\verb{(length-1 character)} The class name of \code{x} to use in error 13 | messages. Use this if you remove a special class from \code{x} before checking 14 | its coercion, but want the error message to match the original class.} 15 | 16 | \item{x_arg}{\verb{(length-1 character)} An argument name for x. The automatic 17 | value will work in most cases, or pass it through from higher-level 18 | functions to make error messages clearer in unexported functions.} 19 | 20 | \item{call}{\code{(environment)} The execution environment to mention as the 21 | source of error messages.} 22 | } 23 | \value{ 24 | \code{NULL}, invisibly, if \code{x} passes all checks. 25 | } 26 | \description{ 27 | Check for character to integer coercion failures 28 | } 29 | \keyword{internal} 30 | -------------------------------------------------------------------------------- /tests/testthat/_snaps/to_lst.md: -------------------------------------------------------------------------------- 1 | # to_lst() respects allow_null (#157) 2 | 3 | Code 4 | to_lst(given, allow_null = FALSE) 5 | Condition 6 | Error: 7 | ! `given` must not be . 8 | 9 | --- 10 | 11 | Code 12 | wrapped_to_lst(given, allow_null = FALSE) 13 | Condition 14 | Error in `wrapped_to_lst()`: 15 | ! `val` must not be . 16 | 17 | # to_lst() errors by default for functions (#157) 18 | 19 | Code 20 | to_lst(given) 21 | Condition 22 | Error: 23 | ! `given` must not be a . 24 | 25 | --- 26 | 27 | Code 28 | wrapped_to_lst(given) 29 | Condition 30 | Error in `wrapped_to_lst()`: 31 | ! `val` must not be a . 32 | 33 | # to_lst() errors informatively for primitives (#157) 34 | 35 | Code 36 | to_lst(given, coerce_function = TRUE) 37 | Condition 38 | Error: 39 | ! Can't coerce `given` to . 40 | 41 | --- 42 | 43 | Code 44 | wrapped_to_lst(given, coerce_function = TRUE) 45 | Condition 46 | Error in `wrapped_to_lst()`: 47 | ! Can't coerce `val` to . 48 | 49 | -------------------------------------------------------------------------------- /man/dot-coerce_fct_levels_impl.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/to_fct.R 3 | \name{.coerce_fct_levels_impl} 4 | \alias{.coerce_fct_levels_impl} 5 | \title{Core implementation for applying factor levels} 6 | \usage{ 7 | .coerce_fct_levels_impl( 8 | x, 9 | levels = NULL, 10 | to_na = character(), 11 | x_arg = caller_arg(x), 12 | call = caller_env() 13 | ) 14 | } 15 | \arguments{ 16 | \item{x}{The argument to stabilize.} 17 | 18 | \item{levels}{\code{(character)} The desired factor levels.} 19 | 20 | \item{to_na}{\code{(character)} Values to convert to \code{NA}.} 21 | 22 | \item{x_arg}{\verb{(length-1 character)} An argument name for x. The automatic 23 | value will work in most cases, or pass it through from higher-level 24 | functions to make error messages clearer in unexported functions.} 25 | 26 | \item{call}{\code{(environment)} The execution environment to mention as the 27 | source of error messages.} 28 | } 29 | \value{ 30 | \code{x} as a factor with the specified levels. 31 | } 32 | \description{ 33 | Checks for values in \code{x} that are not present in \code{levels} and throws an error 34 | if any are found. 35 | } 36 | \keyword{internal} 37 | -------------------------------------------------------------------------------- /man/dot-stop_null.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/aaa-conditions.R 3 | \name{.stop_null} 4 | \alias{.stop_null} 5 | \title{Abort because an argument must not be NULL} 6 | \usage{ 7 | .stop_null(x_arg, call, parent = NULL, ...) 8 | } 9 | \arguments{ 10 | \item{x_arg}{\verb{(length-1 character)} An argument name for x. The automatic 11 | value will work in most cases, or pass it through from higher-level 12 | functions to make error messages clearer in unexported functions.} 13 | 14 | \item{call}{\code{(environment)} The execution environment to mention as the 15 | source of error messages.} 16 | 17 | \item{parent}{A parent condition, as you might create during a 18 | \code{\link[rlang:try_fetch]{rlang::try_fetch()}}. See \code{\link[rlang:abort]{rlang::abort()}} for additional information.} 19 | 20 | \item{...}{Additional parameters passed to \code{\link[cli:cli_abort]{cli::cli_abort()}} and on to 21 | \code{\link[rlang:abort]{rlang::abort()}}.} 22 | } 23 | \value{ 24 | This function is called for its side effect of throwing an error and 25 | does not return a value. 26 | } 27 | \description{ 28 | Abort because an argument must not be NULL 29 | } 30 | \keyword{internal} 31 | -------------------------------------------------------------------------------- /man/dot-stop_bad_levels.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/to_fct.R 3 | \name{.stop_bad_levels} 4 | \alias{.stop_bad_levels} 5 | \title{Stop for bad factor levels} 6 | \usage{ 7 | .stop_bad_levels(x, bad_casts, levels, to_na, x_arg, call) 8 | } 9 | \arguments{ 10 | \item{x}{The argument to stabilize.} 11 | 12 | \item{bad_casts}{\code{(logical)} A logical vector indicating which elements of 13 | \code{x} are not in the allowed levels.} 14 | 15 | \item{levels}{\code{(character)} The desired factor levels.} 16 | 17 | \item{to_na}{\code{(character)} Values to convert to \code{NA}.} 18 | 19 | \item{x_arg}{\verb{(length-1 character)} An argument name for x. The automatic 20 | value will work in most cases, or pass it through from higher-level 21 | functions to make error messages clearer in unexported functions.} 22 | 23 | \item{call}{\code{(environment)} The execution environment to mention as the 24 | source of error messages.} 25 | } 26 | \value{ 27 | This function is called for its side effect of throwing an error and 28 | does not return a value. 29 | } 30 | \description{ 31 | Throws a standardized error when values are not found in the provided factor 32 | levels. 33 | } 34 | \keyword{internal} 35 | -------------------------------------------------------------------------------- /tests/testthat/_snaps/pkg_abort.md: -------------------------------------------------------------------------------- 1 | # pkg_abort() throws the expected error (#136) 2 | 3 | Code 4 | wrapped_abort("A message.", "a_subclass") 5 | Condition 6 | Error in `wrapped_abort()`: 7 | ! A message. 8 | 9 | # pkg_abort() uses parent when provided (#136) 10 | 11 | Code 12 | wrapped_abort("child message", "child_class", parent = parent_cnd) 13 | Condition 14 | Error in `wrapped_abort()`: 15 | ! child message 16 | Caused by error: 17 | ! parent message 18 | 19 | # pkg_abort() passes dots to cli_abort() (#136) 20 | 21 | Code 22 | wrapped_abort("A message.", "a_subclass", .internal = TRUE) 23 | Condition 24 | Error in `wrapped_abort()`: 25 | ! A message. 26 | i This is an internal error that was detected in the stbl package. 27 | Please report it at with a reprex () and the full backtrace. 28 | 29 | # pkg_abort() uses message_env when provided (#136) 30 | 31 | Code 32 | wrapped_abort("This message comes from {var}.", "subclass", message_env = msg_env) 33 | Condition 34 | Error in `wrapped_abort()`: 35 | ! This message comes from a custom environment. 36 | 37 | -------------------------------------------------------------------------------- /man/dot-check_value_dbl.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/stabilize_dbl.R 3 | \name{.check_value_dbl} 4 | \alias{.check_value_dbl} 5 | \title{Check double values against min and max values} 6 | \usage{ 7 | .check_value_dbl( 8 | x, 9 | min_value, 10 | max_value, 11 | x_arg = caller_arg(x), 12 | call = caller_env() 13 | ) 14 | } 15 | \arguments{ 16 | \item{x}{The argument to stabilize.} 17 | 18 | \item{min_value}{\verb{(length-1 numeric)} The lowest allowed value for \code{x}. If 19 | \code{NULL} (default) values are not checked.} 20 | 21 | \item{max_value}{\verb{(length-1 numeric)} The highest allowed value for \code{x}. If 22 | \code{NULL} (default) values are not checked.} 23 | 24 | \item{x_arg}{\verb{(length-1 character)} An argument name for x. The automatic 25 | value will work in most cases, or pass it through from higher-level 26 | functions to make error messages clearer in unexported functions.} 27 | 28 | \item{call}{\code{(environment)} The execution environment to mention as the 29 | source of error messages.} 30 | } 31 | \value{ 32 | \code{NULL}, invisibly, if \code{x} passes all checks. 33 | } 34 | \description{ 35 | Check double values against min and max values 36 | } 37 | \keyword{internal} 38 | -------------------------------------------------------------------------------- /tests/testthat/_snaps/stabilize_fct.md: -------------------------------------------------------------------------------- 1 | # stabilize_fct() throws errors for bad levels (#62) 2 | 3 | Code 4 | stabilize_fct(letters[1:5], levels = c("a", "c"), to_na = "b") 5 | Condition 6 | Error: 7 | ! Each value of `letters[1:5]` must be in the expected levels. 8 | i Allowed levels: "a" and "c". 9 | i Values that are converted to `NA`: "b". 10 | x Unexpected values: "d" and "e". 11 | 12 | --- 13 | 14 | Code 15 | wrapped_stabilize_fct(letters[1:5], levels = c("a", "c"), to_na = "b") 16 | Condition 17 | Error in `wrapped_stabilize_fct()`: 18 | ! Each value of `val` must be in the expected levels. 19 | i Allowed levels: "a" and "c". 20 | i Values that are converted to `NA`: "b". 21 | x Unexpected values: "d" and "e". 22 | 23 | # stabilize_fct_scalar() errors for non-scalars (#62) 24 | 25 | Code 26 | stabilize_fct_scalar(given) 27 | Condition 28 | Error: 29 | ! `given` must be a single . 30 | x `given` has 26 values. 31 | 32 | --- 33 | 34 | Code 35 | wrapped_stabilize_fct_scalar(given) 36 | Condition 37 | Error in `wrapped_stabilize_fct_scalar()`: 38 | ! `val` must be a single . 39 | x `val` has 26 values. 40 | 41 | -------------------------------------------------------------------------------- /tests/testthat/helper-wrap.R: -------------------------------------------------------------------------------- 1 | wrap <- function(fn) { 2 | force(fn) 3 | function(val, ...) fn(val, ...) 4 | } 5 | 6 | wrapped_stabilize_arg <- wrap(stabilize_arg) 7 | wrapped_stabilize_arg_scalar <- wrap(stabilize_arg_scalar) 8 | wrapped_stabilize_chr <- wrap(stabilize_chr) 9 | wrapped_stabilize_chr_scalar <- wrap(stabilize_chr_scalar) 10 | wrapped_stabilize_dbl <- wrap(stabilize_dbl) 11 | wrapped_stabilize_dbl_scalar <- wrap(stabilize_dbl_scalar) 12 | wrapped_stabilize_fct <- wrap(stabilize_fct) 13 | wrapped_stabilize_fct_scalar <- wrap(stabilize_fct_scalar) 14 | wrapped_stabilize_int <- wrap(stabilize_int) 15 | wrapped_stabilize_int_scalar <- wrap(stabilize_int_scalar) 16 | wrapped_stabilize_lgl <- wrap(stabilize_lgl) 17 | wrapped_stabilize_lgl_scalar <- wrap(stabilize_lgl_scalar) 18 | wrapped_to_chr <- wrap(to_chr) 19 | wrapped_to_chr_scalar <- wrap(to_chr_scalar) 20 | wrapped_to_dbl <- wrap(to_dbl) 21 | wrapped_to_dbl_scalar <- wrap(to_dbl_scalar) 22 | wrapped_to_fct <- wrap(to_fct) 23 | wrapped_to_fct_scalar <- wrap(to_fct_scalar) 24 | wrapped_to_int <- wrap(to_int) 25 | wrapped_to_int_scalar <- wrap(to_int_scalar) 26 | wrapped_to_lgl <- wrap(to_lgl) 27 | wrapped_to_lgl_scalar <- wrap(to_lgl_scalar) 28 | wrapped_to_lst <- wrap(to_lst) 29 | wrapped_to_null <- wrap(.to_null) 30 | -------------------------------------------------------------------------------- /tests/testthat/test-utils.R: -------------------------------------------------------------------------------- 1 | test_that(".glue2() works with custom delimiters", { 2 | local_var <- "test" 3 | expect_equal(.glue2("A {local_var} message."), "A {local_var} message.") 4 | expect_equal(.glue2("A [local_var] message."), "A test message.") 5 | }) 6 | 7 | test_that(".cli_escape() escapes curly braces", { 8 | expect_equal(.cli_escape("{message}"), "{{message}}") 9 | expect_equal(.cli_escape("{{message}}"), "{{{{message}}}}") 10 | expect_equal(.cli_escape("no braces"), "no braces") 11 | }) 12 | 13 | test_that(".cli_mark() wraps text in cli markup", { 14 | expect_equal(.cli_mark("text", "val"), "{.val text}") 15 | expect_equal(.cli_mark("text", "var"), "{.var text}") 16 | }) 17 | 18 | test_that("%&&% works as expected", { 19 | expect_null(NULL %&&% "value") 20 | expect_equal("not null" %&&% "value", "value") 21 | expect_equal(TRUE %&&% "value", "value") 22 | }) 23 | 24 | test_that(".find_failures() works", { 25 | x <- c("a", "b", "c") 26 | check_fn <- function(vec, val) { 27 | vec == val 28 | } 29 | 30 | expect_null(.find_failures(x, NULL, check_fn)) 31 | expect_null(.find_failures(x, "d", check_fn)) 32 | expect_equal(.find_failures(x, "b", check_fn), 2) 33 | expect_equal(.find_failures(c(x, "b"), "b", check_fn), c(2, 4)) 34 | }) 35 | -------------------------------------------------------------------------------- /man/specify_cls.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/specify_cls.R 3 | \name{specify_cls} 4 | \alias{specify_cls} 5 | \title{Create a specified stabilizer function} 6 | \usage{ 7 | specify_cls( 8 | stabilizer, 9 | factory_args = list(), 10 | scalar = FALSE, 11 | call = rlang::caller_env() 12 | ) 13 | } 14 | \arguments{ 15 | \item{stabilizer}{\verb{(length-1 character)} Name of the stabilizer function to 16 | call.} 17 | 18 | \item{factory_args}{\code{(list)} Arguments to include in the call to the 19 | stabilizer function.} 20 | 21 | \item{scalar}{\verb{(length-1 logical)} Whether to call the scalar version of the 22 | stabilizer.} 23 | 24 | \item{call}{\code{(environment)} The environment to use as the parent of the 25 | generated function. Defaults to the caller's environment.} 26 | } 27 | \value{ 28 | A function of class \code{"stbl_specified_fn"} that calls the specified 29 | stabilizer function with the provided arguments. The generated function 30 | will also accept \code{...} for additional arguments to pass to the stabilizer 31 | function. You can copy/paste the body of the resulting function if you want 32 | to provide additional context or functionality. 33 | } 34 | \description{ 35 | Create a specified stabilizer function 36 | } 37 | \keyword{internal} 38 | -------------------------------------------------------------------------------- /man/dot-check_size.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/check.R 3 | \name{.check_size} 4 | \alias{.check_size} 5 | \title{Check the size of an object} 6 | \usage{ 7 | .check_size(x, min_size, max_size, x_arg = caller_arg(x), call = caller_env()) 8 | } 9 | \arguments{ 10 | \item{x}{The object to check.} 11 | 12 | \item{min_size}{\verb{(length-1 integer)} The minimum size of the object. Object 13 | size will be tested using \code{\link[vctrs:vec_size]{vctrs::vec_size()}}.} 14 | 15 | \item{max_size}{\verb{(length-1 integer)} The maximum size of the object. Object 16 | size will be tested using \code{\link[vctrs:vec_size]{vctrs::vec_size()}}.} 17 | 18 | \item{x_arg}{\verb{(length-1 character)} An argument name for x. The automatic 19 | value will work in most cases, or pass it through from higher-level 20 | functions to make error messages clearer in unexported functions.} 21 | 22 | \item{call}{\code{(environment)} The execution environment to mention as the 23 | source of error messages.} 24 | } 25 | \value{ 26 | \code{NULL}, invisibly, if \code{x} passes the check. 27 | } 28 | \description{ 29 | Checks if the size of \code{x} (from \code{\link[vctrs:vec_size]{vctrs::vec_size()}}) is within the bounds of 30 | \code{min_size} and \code{max_size}. 31 | } 32 | \keyword{internal} 33 | -------------------------------------------------------------------------------- /man/dot-check_cast_failures.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/check.R 3 | \name{.check_cast_failures} 4 | \alias{.check_cast_failures} 5 | \title{Check for coercion failures and stop if any are found} 6 | \usage{ 7 | .check_cast_failures(failures, x_class, to, due_to, x_arg, call) 8 | } 9 | \arguments{ 10 | \item{failures}{\code{(logical)} A logical vector where \code{TRUE} indicates a 11 | coercion failure.} 12 | 13 | \item{x_class}{\verb{(length-1 character)} The class name of \code{x} to use in error 14 | messages. Use this if you remove a special class from \code{x} before checking 15 | its coercion, but want the error message to match the original class.} 16 | 17 | \item{to}{The target object for the coercion.} 18 | 19 | \item{due_to}{\verb{(length-1 character)} A string describing the reason for the 20 | failure.} 21 | 22 | \item{x_arg}{\verb{(length-1 character)} An argument name for x. The automatic 23 | value will work in most cases, or pass it through from higher-level 24 | functions to make error messages clearer in unexported functions.} 25 | 26 | \item{call}{\code{(environment)} The execution environment to mention as the 27 | source of error messages.} 28 | } 29 | \description{ 30 | Check for coercion failures and stop if any are found 31 | } 32 | \keyword{internal} 33 | -------------------------------------------------------------------------------- /tests/testthat/_snaps/to_null.md: -------------------------------------------------------------------------------- 1 | # .to_null() errors when NULL isn't allowed 2 | 3 | Code 4 | .to_null(given, allow_null = FALSE) 5 | Condition 6 | Error: 7 | ! `given` must not be . 8 | 9 | --- 10 | 11 | Code 12 | wrapped_to_null(given, allow_null = FALSE) 13 | Condition 14 | Error in `wrapped_to_null()`: 15 | ! `val` must not be . 16 | 17 | # .to_null() errors for bad allow_null 18 | 19 | Code 20 | .to_null(NULL, allow_null = NULL) 21 | Condition 22 | Error: 23 | ! `allow_null` must not be . 24 | 25 | --- 26 | 27 | Code 28 | .to_null(NULL, allow_null = "fish") 29 | Condition 30 | Error: 31 | ! `allow_null` must be coercible to 32 | x Can't convert some values due to incompatible values. 33 | * Locations: 1 34 | 35 | --- 36 | 37 | Code 38 | wrapped_to_null(NULL, allow_null = "fish") 39 | Condition 40 | Error in `wrapped_to_null()`: 41 | ! `allow_null` must be coercible to 42 | x Can't convert some values due to incompatible values. 43 | * Locations: 1 44 | 45 | # .to_null() errors informatively for missing value 46 | 47 | Code 48 | .to_null() 49 | Condition 50 | Error: 51 | ! `unknown arg` must not be missing. 52 | 53 | -------------------------------------------------------------------------------- /man/expect_pkg_error_classes.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/pkg_abort.R 3 | \name{expect_pkg_error_classes} 4 | \alias{expect_pkg_error_classes} 5 | \title{Test package error classes} 6 | \usage{ 7 | expect_pkg_error_classes(object, package, ...) 8 | } 9 | \arguments{ 10 | \item{object}{An expression that is expected to throw an error.} 11 | 12 | \item{package}{\verb{(length-1 character)} The name of the package to use in 13 | classes.} 14 | 15 | \item{...}{\code{(character)} Components of the class name, from least-specific to 16 | most.} 17 | } 18 | \value{ 19 | The classes of the error invisibly on success or the error on 20 | failure. Unlike most testthat expectations, this expectation cannot be 21 | usefully chained. 22 | } 23 | \description{ 24 | When you use \code{\link[=pkg_abort]{pkg_abort()}} to signal errors, you can use this function to 25 | test that those errors are generated as expected. 26 | } 27 | \examples{ 28 | \dontshow{if (rlang::is_installed("testthat")) withAutoprint(\{ # examplesIf} 29 | expect_pkg_error_classes( 30 | pkg_abort("stbl", "This is a test error", "test_subclass"), 31 | "stbl", 32 | "test_subclass" 33 | ) 34 | try( 35 | expect_pkg_error_classes( 36 | pkg_abort("stbl", "This is a test error", "test_subclass"), 37 | "stbl", 38 | "different_subclass" 39 | ) 40 | ) 41 | \dontshow{\}) # examplesIf} 42 | } 43 | -------------------------------------------------------------------------------- /man/dot-stbl_abort.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/aaa-conditions.R 3 | \name{.stbl_abort} 4 | \alias{.stbl_abort} 5 | \title{Signal an error with standards applied} 6 | \usage{ 7 | .stbl_abort( 8 | message, 9 | subclass, 10 | call = caller_env(), 11 | message_env = call, 12 | parent = NULL, 13 | ... 14 | ) 15 | } 16 | \arguments{ 17 | \item{message}{(\code{character}) The message for the new error. Messages will be 18 | formatted with \code{\link[cli:cli_bullets]{cli::cli_bullets()}}.} 19 | 20 | \item{subclass}{(\code{character}) Class(es) to assign to the error. Will be 21 | prefixed by "stbl-error-".} 22 | 23 | \item{call}{\code{(environment)} The execution environment to mention as the 24 | source of error messages.} 25 | 26 | \item{message_env}{(\code{environment}) The execution environment to use to 27 | evaluate variables in error messages.} 28 | 29 | \item{parent}{A parent condition, as you might create during a 30 | \code{\link[rlang:try_fetch]{rlang::try_fetch()}}. See \code{\link[rlang:abort]{rlang::abort()}} for additional information.} 31 | 32 | \item{...}{Additional parameters passed to \code{\link[cli:cli_abort]{cli::cli_abort()}} and on to 33 | \code{\link[rlang:abort]{rlang::abort()}}.} 34 | } 35 | \description{ 36 | A wrapper around \code{\link[cli:cli_abort]{cli::cli_abort()}} to throw classed errors. 37 | } 38 | \keyword{internal} 39 | -------------------------------------------------------------------------------- /man/dot-to_cls_from_list.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/cls_unexported.R 3 | \name{.to_cls_from_list} 4 | \alias{.to_cls_from_list} 5 | \title{Coerce a list to a specific class} 6 | \usage{ 7 | .to_cls_from_list( 8 | x, 9 | to_cls_fn, 10 | to_class, 11 | ..., 12 | x_arg = caller_arg(x), 13 | call = caller_env(), 14 | x_class = object_type(x) 15 | ) 16 | } 17 | \arguments{ 18 | \item{x}{The argument to stabilize.} 19 | 20 | \item{to_cls_fn}{\verb{(function)} The \verb{to_*()} function to use for coercion.} 21 | 22 | \item{to_class}{\verb{(length-1 character)} The name of the class to coerce to.} 23 | 24 | \item{...}{Arguments passed to methods.} 25 | 26 | \item{x_arg}{\verb{(length-1 character)} An argument name for x. The automatic 27 | value will work in most cases, or pass it through from higher-level 28 | functions to make error messages clearer in unexported functions.} 29 | 30 | \item{call}{\code{(environment)} The execution environment to mention as the 31 | source of error messages.} 32 | 33 | \item{x_class}{\verb{(length-1 character)} The class name of \code{x} to use in error 34 | messages. Use this if you remove a special class from \code{x} before checking 35 | its coercion, but want the error message to match the original class.} 36 | } 37 | \value{ 38 | \code{x} coerced to the target class. 39 | } 40 | \description{ 41 | Coerce a list to a specific class 42 | } 43 | \keyword{internal} 44 | -------------------------------------------------------------------------------- /man/regex_must_match.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/regex.R 3 | \name{regex_must_match} 4 | \alias{regex_must_match} 5 | \alias{regex_must_not_match} 6 | \title{Create a regex matching rule} 7 | \usage{ 8 | regex_must_match(regex) 9 | 10 | regex_must_not_match(regex) 11 | } 12 | \arguments{ 13 | \item{regex}{\code{(character)} The regular expression pattern.} 14 | } 15 | \value{ 16 | For \code{regex_must_match}, the \code{regex} value with \code{\link[=names]{names()}} equal to 17 | the generated error message. 18 | 19 | For \code{regex_must_not_match()}, the \code{regex} value with a \code{negate} 20 | attribute and with \code{\link[=names]{names()}} equal to the generated "must not match" error 21 | message. 22 | } 23 | \description{ 24 | Attach a standardized error message to a \code{regex} argument. By default, the 25 | message will be "must match the regex pattern \{regex\}". If the input 26 | \code{regex} has a \code{negate} attribute set to \code{TRUE} (set automatically by 27 | \code{regex_must_not_match()}), the message will instead be "must not match...". 28 | This message can be used with \code{\link[=stabilize_chr]{stabilize_chr()}} and \code{\link[=stabilize_chr_scalar]{stabilize_chr_scalar()}}. 29 | } 30 | \examples{ 31 | regex_must_match("[aeiou]") 32 | 33 | # With negation: 34 | regex <- "[aeiou]" 35 | attr(regex, "negate") <- TRUE 36 | regex_must_match(regex) 37 | regex_must_not_match("[aeiou]") 38 | } 39 | -------------------------------------------------------------------------------- /man/dot-check_scalar.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/check.R 3 | \name{.check_scalar} 4 | \alias{.check_scalar} 5 | \title{Check if an object is a scalar} 6 | \usage{ 7 | .check_scalar( 8 | x, 9 | allow_null = TRUE, 10 | allow_zero_length = TRUE, 11 | x_arg = caller_arg(x), 12 | call = caller_env(), 13 | x_class = object_type(x) 14 | ) 15 | } 16 | \arguments{ 17 | \item{x}{The object to check.} 18 | 19 | \item{allow_null}{\verb{(length-1 logical)} Is NULL an acceptable value?} 20 | 21 | \item{allow_zero_length}{\verb{(length-1 logical)} Are zero-length vectors 22 | acceptable?} 23 | 24 | \item{x_arg}{\verb{(length-1 character)} An argument name for x. The automatic 25 | value will work in most cases, or pass it through from higher-level 26 | functions to make error messages clearer in unexported functions.} 27 | 28 | \item{call}{\code{(environment)} The execution environment to mention as the 29 | source of error messages.} 30 | 31 | \item{x_class}{\verb{(length-1 character)} The class name of \code{x} to use in error 32 | messages. Use this if you remove a special class from \code{x} before checking 33 | its coercion, but want the error message to match the original class.} 34 | } 35 | \value{ 36 | \code{NULL}, invisibly, if \code{x} passes the check. 37 | } 38 | \description{ 39 | Checks if an object is a scalar, allowing for \code{NULL} and zero-length vectors 40 | if specified. 41 | } 42 | \keyword{internal} 43 | -------------------------------------------------------------------------------- /man/dot-construct_specification_fn.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/specify_cls.R 3 | \name{.construct_specification_fn} 4 | \alias{.construct_specification_fn} 5 | \title{Construct a specified stabilizer function} 6 | \usage{ 7 | .construct_specification_fn( 8 | check_dupes, 9 | stabilizer, 10 | factory_args, 11 | ..., 12 | call = rlang::caller_env() 13 | ) 14 | } 15 | \arguments{ 16 | \item{check_dupes}{\code{(list)} An empty list, or a list containing an expression 17 | that checks for duplicate arguments.} 18 | 19 | \item{stabilizer}{\verb{(length-1 character)} Name of the stabilizer function to 20 | call.} 21 | 22 | \item{factory_args}{Arguments passed to \code{\link[=specify_cls]{specify_cls()}} as \code{...}.} 23 | 24 | \item{...}{Not used. Included to avoid confusion in R CMD check.} 25 | 26 | \item{call}{\code{(environment)} The environment to use as the parent of the 27 | generated function. Defaults to the caller's environment.} 28 | } 29 | \value{ 30 | A function of class \code{"stbl_specified_fn"} that calls the specified 31 | stabilizer function with the provided arguments. The generated function 32 | will also accept \code{...} for additional arguments to pass to the stabilizer 33 | function. You can copy/paste the body of the resulting function if you want 34 | to provide additional context or functionality. 35 | } 36 | \description{ 37 | Construct a specified stabilizer function 38 | } 39 | \keyword{internal} 40 | -------------------------------------------------------------------------------- /man/dot-has_regex_pattern.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/stabilize_chr.R 3 | \name{.has_regex_pattern} 4 | \alias{.has_regex_pattern} 5 | \title{Detect a regex pattern in a character vector} 6 | \usage{ 7 | .has_regex_pattern(x, regex) 8 | } 9 | \arguments{ 10 | \item{x}{The argument to stabilize.} 11 | 12 | \item{regex}{\verb{(character, list, or stringr_pattern)} One or more optional 13 | regular expressions to test against the values of \code{x}. This can be a 14 | character vector, a list of character vectors, or a pattern object from the 15 | \{stringr\} package (e.g., \code{stringr::fixed("a.b")}). The default error 16 | message for non-matching values will include the pattern itself (see 17 | \code{\link[=regex_must_match]{regex_must_match()}}). To provide a custom message, supply a named 18 | character vector where the value is the regex pattern and the name is the 19 | message that should be displayed. To check that a pattern is \emph{not} matched, 20 | attach a \code{negate} attribute set to \code{TRUE}. If a complex regex pattern 21 | throws an error, try installing the stringi package.} 22 | } 23 | \value{ 24 | A logical vector of matches in \code{x} to \code{regex}. 25 | } 26 | \description{ 27 | A wrapper around \code{\link[stringi:stri_detect]{stringi::stri_detect_regex()}} and \code{\link[base:grep]{base::grepl()}} that 28 | prefers the \code{stringi} implementation if the package is available. 29 | } 30 | \keyword{internal} 31 | -------------------------------------------------------------------------------- /.github/workflows/qcthis.yaml: -------------------------------------------------------------------------------- 1 | on: 2 | push: 3 | branches: [main] 4 | pull_request: 5 | workflow_dispatch: 6 | 7 | name: Package QC Report 8 | 9 | permissions: read-all 10 | 11 | jobs: 12 | qc-report: 13 | runs-on: ubuntu-latest 14 | env: 15 | GITHUB_PAT: ${{ secrets.GITHUB_TOKEN }} 16 | 17 | steps: 18 | - uses: actions/checkout@v4 19 | 20 | - uses: r-lib/actions/setup-r@v2 21 | with: 22 | use-public-rspm: true 23 | 24 | - uses: r-lib/actions/setup-r-dependencies@v2 25 | with: 26 | extra-packages: local::., any::pak, any::dplyr 27 | 28 | - name: Install qcthat 29 | run: | 30 | # This is separated out to make it easy to update the tag/PR/etc 31 | pak::pak("Gilead-BioStats/qcthat@dev") 32 | shell: Rscript {0} 33 | 34 | - name: QC report 35 | run: | 36 | IssueTestMatrix <- qcthat::CompileIssueTestMatrix( 37 | qcthat::FetchRepoIssues() |> 38 | dplyr::filter( 39 | .data$State == "closed", 40 | .data$StateReason == "completed", 41 | .data$Type %in% c("Feature", "Bug") 42 | ), 43 | qcthat::CompileTestResults( 44 | testthat::test_local(stop_on_failure = FALSE, reporter = "silent") 45 | ) |> 46 | dplyr::filter(lengths(.data$Issues) > 0) 47 | ) 48 | print(IssueTestMatrix) 49 | shell: Rscript {0} 50 | -------------------------------------------------------------------------------- /man/dot-to_num_from_complex.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/cls_unexported.R 3 | \name{.to_num_from_complex} 4 | \alias{.to_num_from_complex} 5 | \title{Coerce an object from a complex to a numeric class} 6 | \usage{ 7 | .to_num_from_complex( 8 | x, 9 | cast_fn, 10 | to_type_obj, 11 | x_arg = caller_arg(x), 12 | call = caller_env(), 13 | x_class = object_type(x) 14 | ) 15 | } 16 | \arguments{ 17 | \item{x}{The argument to stabilize.} 18 | 19 | \item{cast_fn}{\verb{(function)} The \verb{as.*()} function to use for coercion.} 20 | 21 | \item{to_type_obj}{An empty object of the target type (e.g., \code{integer()}).} 22 | 23 | \item{x_arg}{\verb{(length-1 character)} An argument name for x. The automatic 24 | value will work in most cases, or pass it through from higher-level 25 | functions to make error messages clearer in unexported functions.} 26 | 27 | \item{call}{\code{(environment)} The execution environment to mention as the 28 | source of error messages.} 29 | 30 | \item{x_class}{\verb{(length-1 character)} The class name of \code{x} to use in error 31 | messages. Use this if you remove a special class from \code{x} before checking 32 | its coercion, but want the error message to match the original class.} 33 | } 34 | \value{ 35 | \code{x} coerced to the target class. 36 | } 37 | \description{ 38 | A helper that wraps around a \verb{to_*()} function to provide a standard way to 39 | coerce complex numbers. 40 | } 41 | \keyword{internal} 42 | -------------------------------------------------------------------------------- /R/regex.R: -------------------------------------------------------------------------------- 1 | #' Create a regex matching rule 2 | #' 3 | #' Attach a standardized error message to a `regex` argument. By default, the 4 | #' message will be "must match the regex pattern \{regex\}". If the input 5 | #' `regex` has a `negate` attribute set to `TRUE` (set automatically by 6 | #' `regex_must_not_match()`), the message will instead be "must not match...". 7 | #' This message can be used with [stabilize_chr()] and [stabilize_chr_scalar()]. 8 | #' 9 | #' @param regex `(character)` The regular expression pattern. 10 | #' 11 | #' @returns For `regex_must_match`, the `regex` value with [names()] equal to 12 | #' the generated error message. 13 | #' @export 14 | #' 15 | #' @examples 16 | #' regex_must_match("[aeiou]") 17 | #' 18 | #' # With negation: 19 | #' regex <- "[aeiou]" 20 | #' attr(regex, "negate") <- TRUE 21 | #' regex_must_match(regex) 22 | regex_must_match <- function(regex) { 23 | verb <- if (isTRUE(attr(regex, "negate"))) { 24 | "must not match" 25 | } else { 26 | "must match" 27 | } 28 | 29 | rlang::set_names( 30 | regex, 31 | paste(verb, "the regex pattern", .cli_mark(.cli_escape(regex), "val")) 32 | ) 33 | } 34 | 35 | #' @returns For `regex_must_not_match()`, the `regex` value with a `negate` 36 | #' attribute and with [names()] equal to the generated "must not match" error 37 | #' message. 38 | #' @export 39 | #' 40 | #' @examples 41 | #' regex_must_not_match("[aeiou]") 42 | #' @rdname regex_must_match 43 | regex_must_not_match <- function(regex) { 44 | attr(regex, "negate") <- TRUE 45 | regex_must_match(regex) 46 | } 47 | -------------------------------------------------------------------------------- /.github/workflows/pkgdown.yaml: -------------------------------------------------------------------------------- 1 | # Workflow derived from https://github.com/r-lib/actions/tree/v2/examples 2 | # Need help debugging build failures? Start at https://github.com/r-lib/actions#where-to-find-help 3 | on: 4 | push: 5 | branches: [main, master] 6 | paths-ignore: 7 | - '.github/ai/issues.json' 8 | pull_request: 9 | branches: [main, master] 10 | paths-ignore: 11 | - '.github/ai/issues.json' 12 | release: 13 | types: [published] 14 | workflow_dispatch: 15 | 16 | name: pkgdown 17 | 18 | jobs: 19 | pkgdown: 20 | runs-on: ubuntu-latest 21 | # Only restrict concurrency for non-PR jobs 22 | concurrency: 23 | group: pkgdown-${{ github.event_name != 'pull_request' || github.run_id }} 24 | env: 25 | GITHUB_PAT: ${{ secrets.GITHUB_TOKEN }} 26 | permissions: 27 | contents: write 28 | steps: 29 | - uses: actions/checkout@v4 30 | 31 | - uses: r-lib/actions/setup-pandoc@v2 32 | 33 | - uses: r-lib/actions/setup-r@v2 34 | with: 35 | use-public-rspm: true 36 | 37 | - uses: r-lib/actions/setup-r-dependencies@v2 38 | with: 39 | extra-packages: any::pkgdown, local::. 40 | needs: website 41 | 42 | - name: Build site 43 | run: pkgdown::build_site_github_pages(new_process = FALSE, install = FALSE) 44 | shell: Rscript {0} 45 | 46 | - name: Deploy to GitHub pages 🚀 47 | if: github.event_name != 'pull_request' 48 | uses: JamesIves/github-pages-deploy-action@v4.4.1 49 | with: 50 | clean: false 51 | branch: gh-pages 52 | folder: docs 53 | -------------------------------------------------------------------------------- /tests/testthat/test-regex.R: -------------------------------------------------------------------------------- 1 | test_that("regex_must_match() works as expected (#52, #89)", { 2 | rule <- regex_must_match("^a") 3 | expect_type(rule, "character") 4 | expect_named(rule, "must match the regex pattern {.val ^a}") 5 | expect_equal(unname(rule), "^a") 6 | }) 7 | 8 | test_that("regex_must_match() deals with characters for glue (#52, #89)", { 9 | rule <- regex_must_match("a{1,3}") 10 | expect_type(rule, "character") 11 | expect_named(rule, "must match the regex pattern {.val a{{1,3}}}") 12 | expect_equal(unname(rule), "a{1,3}") 13 | }) 14 | 15 | test_that("regex_must_match() handles negation (#85, #89)", { 16 | regex <- "^a" 17 | attr(regex, "negate") <- TRUE 18 | rule <- regex_must_match(regex) 19 | expect_type(rule, "character") 20 | expect_named(rule, "must not match the regex pattern {.val ^a}") 21 | expect_equal(unname(rule), regex) 22 | expect_true(attr(rule, "negate")) 23 | }) 24 | 25 | test_that("regex_must_not_match() works as expected (#85, #89)", { 26 | rule <- regex_must_not_match("^a") 27 | expect_type(rule, "character") 28 | expect_named(rule, "must not match the regex pattern {.val ^a}") 29 | expect_equal(rule, "^a", ignore_attr = TRUE) 30 | expect_true(attr(rule, "negate")) 31 | }) 32 | 33 | test_that("regex_must_not_match() doesn't freak out about pre-set negation (#85, #89)", { 34 | regex <- "^a" 35 | attr(regex, "negate") <- TRUE 36 | rule <- regex_must_not_match(regex) 37 | expect_type(rule, "character") 38 | expect_named(rule, "must not match the regex pattern {.val ^a}") 39 | expect_equal(rule, regex, ignore_attr = TRUE) 40 | expect_true(attr(rule, "negate")) 41 | }) 42 | -------------------------------------------------------------------------------- /tests/testthat/test-to_null.R: -------------------------------------------------------------------------------- 1 | test_that(".to_null() works on the happy path", { 2 | expect_null(.to_null(NULL)) 3 | }) 4 | 5 | test_that(".to_null() errors when NULL isn't allowed", { 6 | given <- NULL 7 | expect_error( 8 | .to_null(given, allow_null = FALSE), 9 | class = .compile_dash("stbl", "error", "bad_null") 10 | ) 11 | expect_snapshot( 12 | .to_null(given, allow_null = FALSE), 13 | error = TRUE 14 | ) 15 | expect_snapshot( 16 | wrapped_to_null(given, allow_null = FALSE), 17 | error = TRUE 18 | ) 19 | }) 20 | 21 | test_that(".to_null() coerces anything to NULL", { 22 | expect_null(.to_null(1L)) 23 | expect_null(.to_null(mean)) 24 | expect_null(.to_null(TRUE)) 25 | expect_null(.to_null(letters)) 26 | }) 27 | 28 | test_that(".to_null() errors for bad allow_null", { 29 | expect_error( 30 | .to_null(NULL, allow_null = NULL), 31 | class = .compile_dash("stbl", "error", "bad_null") 32 | ) 33 | expect_snapshot( 34 | .to_null(NULL, allow_null = NULL), 35 | error = TRUE 36 | ) 37 | 38 | expect_error( 39 | .to_null(NULL, allow_null = "fish"), 40 | class = .compile_dash("stbl", "error", "incompatible_type") 41 | ) 42 | expect_snapshot( 43 | .to_null(NULL, allow_null = "fish"), 44 | error = TRUE 45 | ) 46 | expect_snapshot( 47 | wrapped_to_null(NULL, allow_null = "fish"), 48 | error = TRUE 49 | ) 50 | }) 51 | 52 | test_that(".to_null() errors informatively for missing value", { 53 | expect_error( 54 | .to_null(), 55 | class = .compile_dash("stbl", "error", "must") 56 | ) 57 | expect_snapshot( 58 | .to_null(), 59 | error = TRUE 60 | ) 61 | }) 62 | -------------------------------------------------------------------------------- /.github/workflows/R-CMD-check.yaml: -------------------------------------------------------------------------------- 1 | # Workflow derived from https://github.com/r-lib/actions/tree/v2/examples 2 | # Need help debugging build failures? Start at https://github.com/r-lib/actions#where-to-find-help 3 | on: 4 | push: 5 | branches: [main, master] 6 | paths-ignore: 7 | - '.github/ai/issues.json' 8 | pull_request: 9 | branches: [main, master] 10 | paths-ignore: 11 | - '.github/ai/issues.json' 12 | 13 | name: R-CMD-check 14 | 15 | jobs: 16 | R-CMD-check: 17 | runs-on: ${{ matrix.config.os }} 18 | 19 | name: ${{ matrix.config.os }} (${{ matrix.config.r }}) 20 | 21 | strategy: 22 | fail-fast: false 23 | matrix: 24 | config: 25 | - {os: macos-latest, r: 'release'} 26 | - {os: windows-latest, r: 'release'} 27 | - {os: ubuntu-latest, r: 'devel', http-user-agent: 'release'} 28 | - {os: ubuntu-latest, r: 'release'} 29 | - {os: ubuntu-latest, r: 'oldrel-1'} 30 | 31 | env: 32 | GITHUB_PAT: ${{ secrets.GITHUB_TOKEN }} 33 | R_KEEP_PKG_SOURCE: yes 34 | 35 | steps: 36 | - uses: actions/checkout@v4 37 | 38 | - uses: r-lib/actions/setup-pandoc@v2 39 | 40 | - uses: r-lib/actions/setup-r@v2 41 | with: 42 | r-version: ${{ matrix.config.r }} 43 | http-user-agent: ${{ matrix.config.http-user-agent }} 44 | use-public-rspm: true 45 | 46 | - uses: r-lib/actions/setup-r-dependencies@v2 47 | with: 48 | extra-packages: any::rcmdcheck 49 | needs: check 50 | 51 | - uses: r-lib/actions/check-r-package@v2 52 | with: 53 | upload-snapshots: true 54 | -------------------------------------------------------------------------------- /.github/workflows/test-coverage.yaml: -------------------------------------------------------------------------------- 1 | # Workflow derived from https://github.com/r-lib/actions/tree/v2/examples 2 | # Need help debugging build failures? Start at https://github.com/r-lib/actions#where-to-find-help 3 | on: 4 | push: 5 | branches: [main, master] 6 | paths-ignore: 7 | - '.github/ai/issues.json' 8 | pull_request: 9 | branches: [main, master] 10 | paths-ignore: 11 | - '.github/ai/issues.json' 12 | 13 | name: test-coverage 14 | 15 | jobs: 16 | test-coverage: 17 | runs-on: ubuntu-latest 18 | env: 19 | GITHUB_PAT: ${{ secrets.GITHUB_TOKEN }} 20 | 21 | steps: 22 | - uses: actions/checkout@v4 23 | 24 | - uses: r-lib/actions/setup-r@v2 25 | with: 26 | use-public-rspm: true 27 | 28 | - uses: r-lib/actions/setup-r-dependencies@v2 29 | with: 30 | extra-packages: any::covr 31 | needs: coverage 32 | 33 | - name: Test coverage 34 | run: | 35 | covr::codecov( 36 | quiet = FALSE, 37 | clean = FALSE, 38 | install_path = file.path(Sys.getenv("RUNNER_TEMP"), "package") 39 | ) 40 | shell: Rscript {0} 41 | 42 | - name: Show testthat output 43 | if: always() 44 | run: | 45 | ## -------------------------------------------------------------------- 46 | find ${{ runner.temp }}/package -name 'testthat.Rout*' -exec cat '{}' \; || true 47 | shell: bash 48 | 49 | - name: Upload test results 50 | if: failure() 51 | uses: actions/upload-artifact@v4 52 | with: 53 | name: coverage-test-failures 54 | path: ${{ runner.temp }}/package 55 | -------------------------------------------------------------------------------- /.github/scripts/check-workflow-runs.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # This script checks if any *other* runs of this same workflow 4 | # were created *after* the start time of the current run. 5 | # This is used to safely stop redundant runs during an event flurry. 6 | 7 | # Exit immediately if a command exits with a non-zero status. 8 | set -e 9 | 10 | # GITHUB_RUN_ID is provided by the GitHub Actions environment 11 | if [ -z "$GITHUB_RUN_ID" ]; then 12 | echo "Error: GITHUB_RUN_ID env var not set. Running update." >&2 13 | echo "changes=true" 14 | exit 0 15 | fi 16 | 17 | # GITHUB_TOKEN must be passed as an env var to the 'gh' command 18 | echo "Fetching recent workflow runs..." >&2 19 | RAW_RUNS_JSON=$(gh run list --workflow=update-ai.yaml --json databaseId,createdAt --limit 10) 20 | 21 | # Find the 'createdAt' time for the *current* run ID 22 | CURRENT_RUN_CREATED_AT=$(echo "$RAW_RUNS_JSON" | jq -r ".[] | select(.databaseId == $GITHUB_RUN_ID) | .createdAt") 23 | 24 | if [ -z "$CURRENT_RUN_CREATED_AT" ]; then 25 | echo "Error: Could not find current run ID $GITHUB_RUN_ID in API response." >&2 26 | echo "changes=true" 27 | exit 0 28 | fi 29 | 30 | JQ_QUERY=".[] | select(.createdAt > \"$CURRENT_RUN_CREATED_AT\" and .databaseId != $GITHUB_RUN_ID)" 31 | NEWER_RUNS=$(echo "$RAW_RUNS_JSON" | jq -c "$JQ_QUERY") # -c for compact output 32 | 33 | if [ -n "$NEWER_RUNS" ]; then 34 | # Found a newer run. This current run is obsolete and should stop. 35 | echo "Found newer workflow run(s) started after $CURRENT_RUN_CREATED_AT. Stopping this run." >&2 36 | echo "changes=false" 37 | else 38 | # No newer runs found. This run should proceed. 39 | echo "No newer runs found. Proceeding." >&2 40 | echo "changes=true" 41 | fi 42 | 43 | -------------------------------------------------------------------------------- /man/dot-check_value_chr.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/stabilize_chr.R 3 | \name{.check_value_chr} 4 | \alias{.check_value_chr} 5 | \title{Check character values against one or more regex patterns} 6 | \usage{ 7 | .check_value_chr(x, regex, x_arg = caller_arg(x), call = caller_env()) 8 | } 9 | \arguments{ 10 | \item{x}{The argument to stabilize.} 11 | 12 | \item{regex}{\verb{(character, list, or stringr_pattern)} One or more optional 13 | regular expressions to test against the values of \code{x}. This can be a 14 | character vector, a list of character vectors, or a pattern object from the 15 | \{stringr\} package (e.g., \code{stringr::fixed("a.b")}). The default error 16 | message for non-matching values will include the pattern itself (see 17 | \code{\link[=regex_must_match]{regex_must_match()}}). To provide a custom message, supply a named 18 | character vector where the value is the regex pattern and the name is the 19 | message that should be displayed. To check that a pattern is \emph{not} matched, 20 | attach a \code{negate} attribute set to \code{TRUE}. If a complex regex pattern 21 | throws an error, try installing the stringi package.} 22 | 23 | \item{x_arg}{\verb{(length-1 character)} An argument name for x. The automatic 24 | value will work in most cases, or pass it through from higher-level 25 | functions to make error messages clearer in unexported functions.} 26 | 27 | \item{call}{\code{(environment)} The execution environment to mention as the 28 | source of error messages.} 29 | } 30 | \value{ 31 | \code{NULL}, invisibly, if \code{x} passes all checks. 32 | } 33 | \description{ 34 | Check character values against one or more regex patterns 35 | } 36 | \keyword{internal} 37 | -------------------------------------------------------------------------------- /man/dot-stop_must.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/aaa-conditions.R 3 | \name{.stop_must} 4 | \alias{.stop_must} 5 | \title{Abort with a standardized "must" message} 6 | \usage{ 7 | .stop_must( 8 | msg, 9 | x_arg, 10 | call, 11 | additional_msg = NULL, 12 | subclass = "must", 13 | message_env = call, 14 | parent = NULL, 15 | ... 16 | ) 17 | } 18 | \arguments{ 19 | \item{msg}{\code{(character)} The core error message describing the requirement.} 20 | 21 | \item{x_arg}{\verb{(length-1 character)} An argument name for x. The automatic 22 | value will work in most cases, or pass it through from higher-level 23 | functions to make error messages clearer in unexported functions.} 24 | 25 | \item{call}{\code{(environment)} The execution environment to mention as the 26 | source of error messages.} 27 | 28 | \item{additional_msg}{\code{(character)} Optional, additional cli-formatted 29 | messages.} 30 | 31 | \item{subclass}{(\code{character}) Class(es) to assign to the error. Will be 32 | prefixed by "stbl-error-".} 33 | 34 | \item{message_env}{(\code{environment}) The execution environment to use to 35 | evaluate variables in error messages.} 36 | 37 | \item{parent}{A parent condition, as you might create during a 38 | \code{\link[rlang:try_fetch]{rlang::try_fetch()}}. See \code{\link[rlang:abort]{rlang::abort()}} for additional information.} 39 | 40 | \item{...}{Additional parameters passed to \code{\link[cli:cli_abort]{cli::cli_abort()}} and on to 41 | \code{\link[rlang:abort]{rlang::abort()}}.} 42 | } 43 | \value{ 44 | This function is called for its side effect of throwing an error and 45 | does not return a value. 46 | } 47 | \description{ 48 | Abort with a standardized "must" message 49 | } 50 | \keyword{internal} 51 | -------------------------------------------------------------------------------- /tests/testthat/_snaps/stabilize_int.md: -------------------------------------------------------------------------------- 1 | # stabilize_int() checks min_value (#2, #6, #176) 2 | 3 | Code 4 | stabilize_int(given, min_value = 11) 5 | Condition 6 | Error: 7 | ! `given` must be >= 11. 8 | i Some values are too low. 9 | x Locations: 1, 2, 3, 4, 5, 6, 7, 8, 9, and 10 10 | x Values: 1, 2, 3, 4, 5, 6, 7, 8, 9, and 10 11 | 12 | --- 13 | 14 | Code 15 | wrapped_stabilize_int(given, min_value = 11) 16 | Condition 17 | Error in `wrapped_stabilize_int()`: 18 | ! `val` must be >= 11. 19 | i Some values are too low. 20 | x Locations: 1, 2, 3, 4, 5, 6, 7, 8, 9, and 10 21 | x Values: 1, 2, 3, 4, 5, 6, 7, 8, 9, and 10 22 | 23 | # stabilize_int() checks max_value (#5, #176) 24 | 25 | Code 26 | stabilize_int(given, max_value = 4) 27 | Condition 28 | Error: 29 | ! `given` must be <= 4. 30 | i Some values are too high. 31 | x Locations: 5, 6, 7, 8, 9, and 10 32 | x Values: 5, 6, 7, 8, 9, and 10 33 | 34 | --- 35 | 36 | Code 37 | wrapped_stabilize_int(given, max_value = 4) 38 | Condition 39 | Error in `wrapped_stabilize_int()`: 40 | ! `val` must be <= 4. 41 | i Some values are too high. 42 | x Locations: 5, 6, 7, 8, 9, and 10 43 | x Values: 5, 6, 7, 8, 9, and 10 44 | 45 | # stabilize_int_scalar() errors on non-scalars (#12) 46 | 47 | Code 48 | stabilize_int_scalar(given) 49 | Condition 50 | Error: 51 | ! `given` must be a single . 52 | x `given` has 10 values. 53 | 54 | --- 55 | 56 | Code 57 | wrapped_stabilize_int_scalar(given) 58 | Condition 59 | Error in `wrapped_stabilize_int_scalar()`: 60 | ! `val` must be a single . 61 | x `val` has 10 values. 62 | 63 | -------------------------------------------------------------------------------- /man/dot-stop_cant_coerce.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/aaa-conditions.R 3 | \name{.stop_cant_coerce} 4 | \alias{.stop_cant_coerce} 5 | \title{Abort with a standardized "can't coerce" message} 6 | \usage{ 7 | .stop_cant_coerce( 8 | from_class, 9 | to_class, 10 | x_arg, 11 | call, 12 | additional_msg = NULL, 13 | message_env = call, 14 | parent = NULL, 15 | ... 16 | ) 17 | } 18 | \arguments{ 19 | \item{from_class}{\verb{(length-1 character)} The class of the object that failed 20 | coercion.} 21 | 22 | \item{to_class}{\verb{(length-1 character)} The target class for the coercion.} 23 | 24 | \item{x_arg}{\verb{(length-1 character)} An argument name for x. The automatic 25 | value will work in most cases, or pass it through from higher-level 26 | functions to make error messages clearer in unexported functions.} 27 | 28 | \item{call}{\code{(environment)} The execution environment to mention as the 29 | source of error messages.} 30 | 31 | \item{additional_msg}{\verb{(length-1 character)} Optional, additional 32 | cli-formatted messages.} 33 | 34 | \item{message_env}{(\code{environment}) The execution environment to use to 35 | evaluate variables in error messages.} 36 | 37 | \item{parent}{A parent condition, as you might create during a 38 | \code{\link[rlang:try_fetch]{rlang::try_fetch()}}. See \code{\link[rlang:abort]{rlang::abort()}} for additional information.} 39 | 40 | \item{...}{Additional parameters passed to \code{\link[cli:cli_abort]{cli::cli_abort()}} and on to 41 | \code{\link[rlang:abort]{rlang::abort()}}.} 42 | } 43 | \value{ 44 | This function is called for its side effect of throwing an error and 45 | does not return a value. 46 | } 47 | \description{ 48 | Abort with a standardized "can't coerce" message 49 | } 50 | \keyword{internal} 51 | -------------------------------------------------------------------------------- /tests/testthat/_snaps/stabilize_lgl.md: -------------------------------------------------------------------------------- 1 | # stabilize_lgl() checks NAs (#28) 2 | 3 | Code 4 | stabilize_lgl(given, allow_na = FALSE) 5 | Condition 6 | Error: 7 | ! `given` must not contain NA values. 8 | * NA locations: 2 9 | 10 | --- 11 | 12 | Code 13 | wrapped_stabilize_lgl(given, allow_na = FALSE) 14 | Condition 15 | Error in `wrapped_stabilize_lgl()`: 16 | ! `val` must not contain NA values. 17 | * NA locations: 2 18 | 19 | # stabilize_lgl() checks min_size (#28) 20 | 21 | Code 22 | stabilize_lgl(given, min_size = 5) 23 | Condition 24 | Error: 25 | ! `given` must have size >= 5. 26 | x 4 is too small. 27 | 28 | --- 29 | 30 | Code 31 | wrapped_stabilize_lgl(given, min_size = 5) 32 | Condition 33 | Error in `wrapped_stabilize_lgl()`: 34 | ! `val` must have size >= 5. 35 | x 4 is too small. 36 | 37 | # stabilize_lgl() checks max_size (#28) 38 | 39 | Code 40 | stabilize_lgl(given, max_size = 3) 41 | Condition 42 | Error: 43 | ! `given` must have size <= 3. 44 | x 4 is too big. 45 | 46 | --- 47 | 48 | Code 49 | wrapped_stabilize_lgl(given, max_size = 3) 50 | Condition 51 | Error in `wrapped_stabilize_lgl()`: 52 | ! `val` must have size <= 3. 53 | x 4 is too big. 54 | 55 | # stabilize_lgl_scalar() errors on non-scalars (#28) 56 | 57 | Code 58 | stabilize_lgl_scalar(given) 59 | Condition 60 | Error: 61 | ! `given` must be a single . 62 | x `given` has 3 values. 63 | 64 | --- 65 | 66 | Code 67 | wrapped_stabilize_lgl_scalar(given) 68 | Condition 69 | Error in `wrapped_stabilize_lgl_scalar()`: 70 | ! `val` must be a single . 71 | x `val` has 3 values. 72 | 73 | -------------------------------------------------------------------------------- /man/dot-stop_incompatible.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/aaa-conditions.R 3 | \name{.stop_incompatible} 4 | \alias{.stop_incompatible} 5 | \title{Abort with an "incompatible type" message} 6 | \usage{ 7 | .stop_incompatible( 8 | x_class, 9 | to, 10 | failures, 11 | due_to, 12 | x_arg, 13 | call, 14 | parent = NULL, 15 | ... 16 | ) 17 | } 18 | \arguments{ 19 | \item{x_class}{\verb{(length-1 character)} The class name of \code{x} to use in error 20 | messages. Use this if you remove a special class from \code{x} before checking 21 | its coercion, but want the error message to match the original class.} 22 | 23 | \item{to}{The target object for the coercion.} 24 | 25 | \item{failures}{\code{(logical)} A logical vector indicating which elements 26 | failed.} 27 | 28 | \item{due_to}{\verb{(length-1 character)} A string describing the reason for the 29 | failure.} 30 | 31 | \item{x_arg}{\verb{(length-1 character)} An argument name for x. The automatic 32 | value will work in most cases, or pass it through from higher-level 33 | functions to make error messages clearer in unexported functions.} 34 | 35 | \item{call}{\code{(environment)} The execution environment to mention as the 36 | source of error messages.} 37 | 38 | \item{parent}{A parent condition, as you might create during a 39 | \code{\link[rlang:try_fetch]{rlang::try_fetch()}}. See \code{\link[rlang:abort]{rlang::abort()}} for additional information.} 40 | 41 | \item{...}{Additional parameters passed to \code{\link[cli:cli_abort]{cli::cli_abort()}} and on to 42 | \code{\link[rlang:abort]{rlang::abort()}}.} 43 | } 44 | \value{ 45 | This function is called for its side effect of throwing an error and 46 | does not return a value. 47 | } 48 | \description{ 49 | Abort with an "incompatible type" message 50 | } 51 | \keyword{internal} 52 | -------------------------------------------------------------------------------- /tests/testthat/_snaps/check.md: -------------------------------------------------------------------------------- 1 | # .check_na() works 2 | 3 | Code 4 | .check_na(c(1, NA), allow_na = FALSE) 5 | Condition 6 | Error: 7 | ! `c(1, NA)` must not contain NA values. 8 | * NA locations: 2 9 | 10 | # .check_size() works 11 | 12 | Code 13 | .check_size(1:5, 6, 10) 14 | Condition 15 | Error: 16 | ! `1:5` must have size >= 6. 17 | x 5 is too small. 18 | 19 | --- 20 | 21 | Code 22 | .check_size(1:5, 1, 4) 23 | Condition 24 | Error: 25 | ! `1:5` must have size <= 4. 26 | x 5 is too big. 27 | 28 | # .check_scalar() works 29 | 30 | Code 31 | .check_scalar(1:2) 32 | Condition 33 | Error: 34 | ! `1:2` must be a single . 35 | x `1:2` has 2 values. 36 | 37 | --- 38 | 39 | Code 40 | .check_scalar(NULL, allow_null = FALSE) 41 | Condition 42 | Error: 43 | ! `NULL` must be a single . 44 | x `NULL` has no values. 45 | 46 | --- 47 | 48 | Code 49 | .check_scalar(character(), allow_zero_length = FALSE) 50 | Condition 51 | Error: 52 | ! `character()` must be a single . 53 | x `character()` has no values. 54 | 55 | # .check_x_no_more_than_y() works 56 | 57 | Code 58 | .check_x_no_more_than_y(2, 1) 59 | Condition 60 | Error: 61 | ! `2` can't be larger than `1`. 62 | * `2` = 2 63 | * `1` = 1 64 | 65 | # .check_cast_failures() works 66 | 67 | Code 68 | .check_cast_failures(failures = failures, x_class = "character", to = logical(), 69 | due_to = "incompatible values", x_arg = "test_arg", call = rlang::current_env()) 70 | Condition 71 | Error: 72 | ! `test_arg` must be coercible to 73 | x Can't convert some values due to incompatible values. 74 | * Locations: 2 and 4 75 | 76 | -------------------------------------------------------------------------------- /tests/testthat/test-stabilize_fct.R: -------------------------------------------------------------------------------- 1 | test_that("stabilize_fct() works (#62)", { 2 | expect_identical(stabilize_fct(letters), factor(letters)) 3 | }) 4 | 5 | test_that("stabilize_fct() throws errors for bad levels (#62)", { 6 | expect_error( 7 | stabilize_fct(letters[1:5], levels = c("a", "c"), to_na = "b"), 8 | class = .compile_dash("stbl", "error", "fct_levels") 9 | ) 10 | expect_snapshot( 11 | stabilize_fct(letters[1:5], levels = c("a", "c"), to_na = "b"), 12 | error = TRUE 13 | ) 14 | expect_snapshot( 15 | wrapped_stabilize_fct(letters[1:5], levels = c("a", "c"), to_na = "b"), 16 | error = TRUE 17 | ) 18 | }) 19 | 20 | test_that("stabilize_fct_scalar() works (#62)", { 21 | expect_identical(stabilize_fct_scalar("a"), factor("a")) 22 | }) 23 | 24 | test_that("stabilize_fct_scalar() errors for non-scalars (#62)", { 25 | given <- letters 26 | expect_error( 27 | stabilize_fct_scalar(given), 28 | class = .compile_dash("stbl", "error", "non_scalar") 29 | ) 30 | expect_snapshot( 31 | stabilize_fct_scalar(given), 32 | error = TRUE 33 | ) 34 | expect_snapshot( 35 | wrapped_stabilize_fct_scalar(given), 36 | error = TRUE 37 | ) 38 | }) 39 | 40 | test_that("stabilise_fct() exists (#167)", { 41 | expect_no_error(stabilise_fct(TRUE)) 42 | }) 43 | 44 | test_that("stabilize_factor() exists (#164)", { 45 | expect_no_error(stabilize_factor(TRUE)) 46 | }) 47 | 48 | test_that("stabilise_factor() exists (#167)", { 49 | expect_no_error(stabilise_factor(TRUE)) 50 | }) 51 | 52 | test_that("stabilise_fct_scalar() exists (#167)", { 53 | expect_no_error(stabilise_fct_scalar(TRUE)) 54 | }) 55 | 56 | test_that("stabilize_factor_scalar() exists (#164)", { 57 | expect_no_error(stabilize_factor_scalar(TRUE)) 58 | }) 59 | 60 | test_that("stabilise_factor_scalar() exists (#167)", { 61 | expect_no_error(stabilise_factor_scalar(TRUE)) 62 | }) 63 | -------------------------------------------------------------------------------- /man/dot-to_cls_scalar.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/cls_unexported.R 3 | \name{.to_cls_scalar} 4 | \alias{.to_cls_scalar} 5 | \title{Coerce an object to a specific scalar class} 6 | \usage{ 7 | .to_cls_scalar( 8 | x, 9 | is_rlang_cls_scalar, 10 | to_cls_fn, 11 | to_cls_args = list(), 12 | allow_null = TRUE, 13 | allow_zero_length = TRUE, 14 | x_arg = caller_arg(x), 15 | call = caller_env(), 16 | x_class = object_type(x) 17 | ) 18 | } 19 | \arguments{ 20 | \item{x}{The argument to stabilize.} 21 | 22 | \item{is_rlang_cls_scalar}{\verb{(function)} An \verb{is_scalar_*()} function from 23 | rlang, used for a fast path if \code{x} is already the right type.} 24 | 25 | \item{to_cls_fn}{\verb{(function)} The \verb{to_*()} function to use for coercion.} 26 | 27 | \item{to_cls_args}{\code{(list)} A list of additional arguments to pass to 28 | \code{to_cls_fn()}.} 29 | 30 | \item{allow_null}{\verb{(length-1 logical)} Is NULL an acceptable value?} 31 | 32 | \item{allow_zero_length}{\verb{(length-1 logical)} Are zero-length vectors 33 | acceptable?} 34 | 35 | \item{x_arg}{\verb{(length-1 character)} An argument name for x. The automatic 36 | value will work in most cases, or pass it through from higher-level 37 | functions to make error messages clearer in unexported functions.} 38 | 39 | \item{call}{\code{(environment)} The execution environment to mention as the 40 | source of error messages.} 41 | 42 | \item{x_class}{\verb{(length-1 character)} The class name of \code{x} to use in error 43 | messages. Use this if you remove a special class from \code{x} before checking 44 | its coercion, but want the error message to match the original class.} 45 | } 46 | \value{ 47 | \code{x} as a scalar of the target class. 48 | } 49 | \description{ 50 | A helper that wraps around a \verb{to_*_scalar()} function to provide a standard 51 | set of checks. 52 | } 53 | \keyword{internal} 54 | -------------------------------------------------------------------------------- /man/dot-to_cls_from_fct.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/cls_unexported.R 3 | \name{.to_cls_from_fct} 4 | \alias{.to_cls_from_fct} 5 | \title{Coerce an object from a factor to a specific class} 6 | \usage{ 7 | .to_cls_from_fct( 8 | x, 9 | to_cls_fn, 10 | to_cls_args, 11 | to_class, 12 | coerce_factor = TRUE, 13 | x_arg = caller_arg(x), 14 | call = caller_env(), 15 | x_class = object_type(x) 16 | ) 17 | } 18 | \arguments{ 19 | \item{x}{The argument to stabilize.} 20 | 21 | \item{to_cls_fn}{\verb{(function)} The \verb{to_*()} function to use for coercion.} 22 | 23 | \item{to_cls_args}{\code{(list)} A list of additional arguments to pass to 24 | \code{to_cls_fn()}.} 25 | 26 | \item{to_class}{\verb{(length-1 character)} The name of the class to coerce to.} 27 | 28 | \item{coerce_factor}{\verb{(length-1 logical)} Should factors with values such as 29 | "1" and "2.0" be considered numeric-ish? Note that this package uses the 30 | character value from the factor, while \code{\link[=as.integer]{as.integer()}} and \code{\link[=as.double]{as.double()}} use 31 | the integer index of the factor.} 32 | 33 | \item{x_arg}{\verb{(length-1 character)} An argument name for x. The automatic 34 | value will work in most cases, or pass it through from higher-level 35 | functions to make error messages clearer in unexported functions.} 36 | 37 | \item{call}{\code{(environment)} The execution environment to mention as the 38 | source of error messages.} 39 | 40 | \item{x_class}{\verb{(length-1 character)} The class name of \code{x} to use in error 41 | messages. Use this if you remove a special class from \code{x} before checking 42 | its coercion, but want the error message to match the original class.} 43 | } 44 | \value{ 45 | \code{x} coerced to the target class. 46 | } 47 | \description{ 48 | A helper that wraps around a \verb{to_*()} function to provide a standard way to 49 | coerce factors. 50 | } 51 | \keyword{internal} 52 | -------------------------------------------------------------------------------- /man/pkg_abort.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/pkg_abort.R 3 | \name{pkg_abort} 4 | \alias{pkg_abort} 5 | \title{Signal an error with standards applied} 6 | \usage{ 7 | pkg_abort( 8 | package, 9 | message, 10 | subclass, 11 | call = caller_env(), 12 | message_env = call, 13 | parent = NULL, 14 | ... 15 | ) 16 | } 17 | \arguments{ 18 | \item{package}{\verb{(length-1 character)} The name of the package to use in 19 | classes.} 20 | 21 | \item{message}{(\code{character}) The message for the new error. Messages will be 22 | formatted with \code{\link[cli:cli_bullets]{cli::cli_bullets()}}.} 23 | 24 | \item{subclass}{(\code{character}) Class(es) to assign to the error. Will be 25 | prefixed by "\{package\}-error-".} 26 | 27 | \item{call}{\code{(environment)} The execution environment to mention as the 28 | source of error messages.} 29 | 30 | \item{message_env}{(\code{environment}) The execution environment to use to 31 | evaluate variables in error messages.} 32 | 33 | \item{parent}{A parent condition, as you might create during a 34 | \code{\link[rlang:try_fetch]{rlang::try_fetch()}}. See \code{\link[rlang:abort]{rlang::abort()}} for additional information.} 35 | 36 | \item{...}{Additional parameters passed to \code{\link[cli:cli_abort]{cli::cli_abort()}} and on to 37 | \code{\link[rlang:abort]{rlang::abort()}}.} 38 | } 39 | \description{ 40 | A wrapper around \code{\link[cli:cli_abort]{cli::cli_abort()}} to throw classed errors, with an 41 | opinionated framework of error classes. 42 | } 43 | \examples{ 44 | try(pkg_abort("stbl", "This is a test error", "test_subclass")) 45 | tryCatch( 46 | pkg_abort("stbl", "This is a test error", "test_subclass"), 47 | `stbl-error` = function(e) { 48 | "Caught a generic stbl error." 49 | } 50 | ) 51 | tryCatch( 52 | pkg_abort("stbl", "This is a test error", "test_subclass"), 53 | `stbl-error-test_subclass` = function(e) { 54 | "Caught a specific subclass of stbl error." 55 | } 56 | ) 57 | 58 | } 59 | -------------------------------------------------------------------------------- /tests/testthat/_snaps/stabilize_dbl.md: -------------------------------------------------------------------------------- 1 | # stabilize_dbl() checks min_value (#23, #176) 2 | 3 | Code 4 | stabilize_dbl(given, min_value = 11.1) 5 | Condition 6 | Error: 7 | ! `given` must be >= 11.1. 8 | i Some values are too low. 9 | x Locations: 1, 2, 3, 4, 5, 6, 7, 8, 9, and 10 10 | x Values: 1.1, 2.1, 3.1, 4.1, 5.1, 6.1, 7.1, 8.1, 9.1, and 10.1 11 | 12 | --- 13 | 14 | Code 15 | stabilize_dbl(given[[1]], min_value = 11.1) 16 | Condition 17 | Error: 18 | ! `given[[1]]` must be >= 11.1. 19 | x 1.1 is too low. 20 | 21 | --- 22 | 23 | Code 24 | wrapped_stabilize_dbl(given, min_value = 11.1) 25 | Condition 26 | Error in `wrapped_stabilize_dbl()`: 27 | ! `val` must be >= 11.1. 28 | i Some values are too low. 29 | x Locations: 1, 2, 3, 4, 5, 6, 7, 8, 9, and 10 30 | x Values: 1.1, 2.1, 3.1, 4.1, 5.1, 6.1, 7.1, 8.1, 9.1, and 10.1 31 | 32 | # stabilize_dbl() checks max_value (#23, #176) 33 | 34 | Code 35 | stabilize_dbl(given, max_value = 4.1) 36 | Condition 37 | Error: 38 | ! `given` must be <= 4.1. 39 | i Some values are too high. 40 | x Locations: 5, 6, 7, 8, 9, and 10 41 | x Values: 5.1, 6.1, 7.1, 8.1, 9.1, and 10.1 42 | 43 | --- 44 | 45 | Code 46 | wrapped_stabilize_dbl(given, max_value = 4.1) 47 | Condition 48 | Error in `wrapped_stabilize_dbl()`: 49 | ! `val` must be <= 4.1. 50 | i Some values are too high. 51 | x Locations: 5, 6, 7, 8, 9, and 10 52 | x Values: 5.1, 6.1, 7.1, 8.1, 9.1, and 10.1 53 | 54 | # stabilize_dbl_scalar() errors on non-scalars (#23) 55 | 56 | Code 57 | stabilize_dbl_scalar(given) 58 | Condition 59 | Error: 60 | ! `given` must be a single . 61 | x `given` has 10 values. 62 | 63 | --- 64 | 65 | Code 66 | wrapped_stabilize_dbl_scalar(given) 67 | Condition 68 | Error in `wrapped_stabilize_dbl_scalar()`: 69 | ! `val` must be a single . 70 | x `val` has 10 values. 71 | 72 | -------------------------------------------------------------------------------- /man/are_chr_ish.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/are_chr_ish.R 3 | \name{are_chr_ish} 4 | \alias{are_chr_ish} 5 | \alias{is_chr_ish} 6 | \alias{are_character_ish} 7 | \alias{is_character_ish} 8 | \alias{are_chr_ish.default} 9 | \title{Check if an object can be safely coerced to character} 10 | \usage{ 11 | are_chr_ish(x, ...) 12 | 13 | is_chr_ish(x, ...) 14 | 15 | are_character_ish(x, ...) 16 | 17 | is_character_ish(x, ...) 18 | 19 | \method{are_chr_ish}{default}(x, ..., depth = 1) 20 | } 21 | \arguments{ 22 | \item{x}{The object to check.} 23 | 24 | \item{...}{Arguments passed to methods.} 25 | 26 | \item{depth}{\verb{(length-1 integer)} Current recursion depth. Do not manually 27 | set this parameter.} 28 | } 29 | \value{ 30 | \code{are_chr_ish()} returns a logical vector with the same length as the 31 | input. \code{is_chr_ish()} returns a \verb{length-1 logical} (\code{TRUE} or \code{FALSE}) for 32 | the entire vector. 33 | } 34 | \description{ 35 | \code{are_chr_ish()} is a vectorized predicate function that checks 36 | whether each element of its input can be safely coerced to a character 37 | vector. \code{are_character_ish()} is a synonym of \code{are_chr_ish()}. 38 | 39 | \code{is_chr_ish()} is a scalar predicate function that checks if all elements 40 | of its input can be safely coerced to a character vector. 41 | \code{is_character_ish()} is a synonym of \code{is_chr_ish()}. 42 | } 43 | \examples{ 44 | are_chr_ish(letters) 45 | is_chr_ish(letters) 46 | 47 | are_chr_ish(1:10) 48 | is_chr_ish(1:10) 49 | 50 | are_chr_ish(list("a", 1, TRUE)) 51 | is_chr_ish(list("a", 1, TRUE)) 52 | 53 | are_chr_ish(list("a", 1, list(1, 2))) 54 | is_chr_ish(list("a", 1, list(1, 2))) 55 | } 56 | \seealso{ 57 | Other character functions: 58 | \code{\link{specify_chr}()}, 59 | \code{\link{stabilize_chr}()} 60 | 61 | Other check functions: 62 | \code{\link{are_dbl_ish}()}, 63 | \code{\link{are_fct_ish}()}, 64 | \code{\link{are_int_ish}()}, 65 | \code{\link{are_lgl_ish}()} 66 | } 67 | \concept{character functions} 68 | \concept{check functions} 69 | -------------------------------------------------------------------------------- /R/are_chr_ish.R: -------------------------------------------------------------------------------- 1 | #' Check if an object can be safely coerced to character 2 | #' 3 | #' @description `are_chr_ish()` is a vectorized predicate function that checks 4 | #' whether each element of its input can be safely coerced to a character 5 | #' vector. `are_character_ish()` is a synonym of `are_chr_ish()`. 6 | #' 7 | #' `is_chr_ish()` is a scalar predicate function that checks if all elements 8 | #' of its input can be safely coerced to a character vector. 9 | #' `is_character_ish()` is a synonym of `is_chr_ish()`. 10 | #' 11 | #' @inheritParams .shared-params-check 12 | #' @inheritParams .shared-params 13 | #' 14 | #' @returns `are_chr_ish()` returns a logical vector with the same length as the 15 | #' input. `is_chr_ish()` returns a `length-1 logical` (`TRUE` or `FALSE`) for 16 | #' the entire vector. 17 | #' @family character functions 18 | #' @family check functions 19 | #' @export 20 | #' 21 | #' @examples 22 | #' are_chr_ish(letters) 23 | #' is_chr_ish(letters) 24 | #' 25 | #' are_chr_ish(1:10) 26 | #' is_chr_ish(1:10) 27 | #' 28 | #' are_chr_ish(list("a", 1, TRUE)) 29 | #' is_chr_ish(list("a", 1, TRUE)) 30 | #' 31 | #' are_chr_ish(list("a", 1, list(1, 2))) 32 | #' is_chr_ish(list("a", 1, list(1, 2))) 33 | are_chr_ish <- function(x, ...) { 34 | UseMethod("are_chr_ish") 35 | } 36 | 37 | #' @export 38 | #' @rdname are_chr_ish 39 | is_chr_ish <- function(x, ...) { 40 | all(are_chr_ish(x, ...)) 41 | } 42 | 43 | #' @export 44 | #' @rdname are_chr_ish 45 | are_character_ish <- are_chr_ish 46 | 47 | #' @export 48 | #' @rdname are_chr_ish 49 | is_character_ish <- is_chr_ish 50 | 51 | #' @export 52 | are_chr_ish.character <- function(x, ...) { 53 | rep(TRUE, length(x)) 54 | } 55 | 56 | #' @export 57 | are_chr_ish.NULL <- function(x, ...) { 58 | logical(0) 59 | } 60 | 61 | #' @export 62 | #' @rdname are_chr_ish 63 | are_chr_ish.default <- function(x, ..., depth = 1) { 64 | if (rlang::is_atomic(x)) { 65 | return(rep(TRUE, length(x))) 66 | } 67 | if (!rlang::is_vector(x) || depth != 1) { 68 | return(FALSE) 69 | } 70 | .elements_are_cls_ish(x, are_chr_ish, ...) 71 | } 72 | -------------------------------------------------------------------------------- /tests/testthat/test-to_lst.R: -------------------------------------------------------------------------------- 1 | test_that("to_lst() works for lists (#157, #166)", { 2 | given <- list("a", 1L, TRUE) 3 | expect_identical(to_lst(given), given) 4 | }) 5 | 6 | test_that("to_lst() works for NULL (#157)", { 7 | given <- NULL 8 | expect_identical( 9 | to_lst(given), 10 | given 11 | ) 12 | }) 13 | 14 | test_that("to_lst() respects allow_null (#157)", { 15 | given <- NULL 16 | expect_error( 17 | to_lst(given, allow_null = FALSE), 18 | class = .compile_dash("stbl", "error", "bad_null") 19 | ) 20 | expect_snapshot( 21 | to_lst(given, allow_null = FALSE), 22 | error = TRUE 23 | ) 24 | expect_snapshot( 25 | wrapped_to_lst(given, allow_null = FALSE), 26 | error = TRUE 27 | ) 28 | }) 29 | 30 | test_that("to_lst() works for character vectors (#157)", { 31 | given <- c("a", "b", "c") 32 | expected <- list("a", "b", "c") 33 | expect_identical(to_lst(given), expected) 34 | }) 35 | 36 | test_that("to_lst() errors by default for functions (#157)", { 37 | given <- function(x) x + 1 38 | expect_error( 39 | to_lst(given), 40 | class = .compile_dash("stbl", "error", "bad_function") 41 | ) 42 | expect_snapshot( 43 | to_lst(given), 44 | error = TRUE 45 | ) 46 | expect_snapshot( 47 | wrapped_to_lst(given), 48 | error = TRUE 49 | ) 50 | }) 51 | 52 | test_that("to_lst() works for functions with coerce_function = TRUE (#157)", { 53 | given <- function(x) x + 1 54 | expected <- as.list(given) 55 | expect_identical( 56 | to_lst(given, coerce_function = TRUE), 57 | expected 58 | ) 59 | }) 60 | 61 | test_that("to_lst() errors informatively for primitives (#157)", { 62 | given <- is.na 63 | expect_error( 64 | to_lst(given, coerce_function = TRUE), 65 | class = .compile_dash("stbl", "error", "coerce", "list") 66 | ) 67 | expect_snapshot( 68 | to_lst(given, coerce_function = TRUE), 69 | error = TRUE 70 | ) 71 | expect_snapshot( 72 | wrapped_to_lst(given, coerce_function = TRUE), 73 | error = TRUE 74 | ) 75 | }) 76 | 77 | test_that("to_list() exists (#157, #166)", { 78 | expect_no_error(to_list(TRUE)) 79 | }) 80 | -------------------------------------------------------------------------------- /.github/SUPPORT.md: -------------------------------------------------------------------------------- 1 | # Getting help with stbl 2 | 3 | Thanks for using stbl! 4 | Before filing an issue, there are a few places to explore and pieces to put together to make the process as smooth as possible. 5 | 6 | ## Make a reprex 7 | 8 | Start by making a minimal **repr**oducible **ex**ample using the [reprex](https://reprex.tidyverse.org/) package. 9 | If you haven't heard of or used reprex before, you're in for a treat! 10 | Seriously, reprex will make all of your R-question-asking endeavors easier (which is a pretty insane ROI for the five to ten minutes it'll take you to learn what it's all about). 11 | For additional reprex pointers, check out the [Get help!](https://www.tidyverse.org/help/) section of the tidyverse site. 12 | 13 | ## Where to ask? 14 | 15 | Armed with your reprex, the next step is to figure out where to ask. 16 | 17 | * If it's a question: It's best to ask on the [Data Science Learning Community Slack](https://dslc.io). Other options include [Posit Community](https://community.rstudio.com/), and StackOverflow. There are more people there to answer questions. 18 | 19 | * If it's a bug: you're in the right place, [file an issue](https://github.com/wranglezone/stbl/issues/new). 20 | 21 | * If you're not sure: let the community help you figure it out! 22 | If your problem _is_ a bug or a feature request, you can easily return here and report it. 23 | 24 | Before opening a new issue, be sure to [search issues and pull requests](https://github.com/wranglezone/stbl/issues) to make sure the bug hasn't been reported and/or already fixed in the development version. 25 | By default, the search will be pre-populated with `is:issue is:open`. 26 | You can [edit the qualifiers](https://help.github.com/articles/searching-issues-and-pull-requests/) (e.g. `is:pr`, `is:closed`) as needed. 27 | For example, you'd simply remove `is:open` to search _all_ issues in the repo, open or closed. 28 | 29 | ## What happens next? 30 | 31 | We'll try to look at your issue as soon as we can, but these packages are maintained by volunteers. 32 | A good reprex is particularly important because it might be weeks or months between your initial report and when we start working on it. 33 | If we can’t reproduce the bug, we can’t fix it! 34 | -------------------------------------------------------------------------------- /tests/testthat/_snaps/specify_cls.md: -------------------------------------------------------------------------------- 1 | # specify_cls builds the expected function snapshot with no args (#150) 2 | 3 | Code 4 | baseline 5 | Output 6 | function (x, ..., x_arg = rlang::caller_arg(x), call = rlang::caller_env(), 7 | x_class = stbl::object_type(x)) 8 | { 9 | stbl::stabilize_chr(x, ..., x_arg = x_arg, call = call, x_class = x_class) 10 | } 11 | attr(,"class") 12 | [1] "stbl_specified_fn" "function" 13 | 14 | # specify_cls builds the expected function snapshot with at least one arg (#150, #161) 15 | 16 | Code 17 | no_null 18 | Output 19 | function (x, ..., x_arg = rlang::caller_arg(x), call = rlang::caller_env(), 20 | x_class = stbl::object_type(x)) 21 | { 22 | { 23 | duplicated_args <- intersect(...names(), "allow_null") 24 | if (length(duplicated_args)) { 25 | stbl::pkg_abort("stbl", message = c("Arguments passed via `...` cannot duplicate specification.", 26 | i = "Duplicated arguments: {.arg {duplicated_args}}"), 27 | subclass = "duplicate_args") 28 | } 29 | } 30 | stbl::stabilize_chr(x, allow_null = FALSE, ..., x_arg = x_arg, 31 | call = call, x_class = x_class) 32 | } 33 | attr(,"class") 34 | [1] "stbl_specified_fn" "function" 35 | 36 | # The function built via specify_cls errors informatively for duplicated args (#150, #161) 37 | 38 | Code 39 | no_null(NULL, allow_null = FALSE) 40 | Condition 41 | Error in `no_null()`: 42 | ! Arguments passed via `...` cannot duplicate specification. 43 | i Duplicated arguments: `allow_null` 44 | 45 | # specify_cls builds the expected scalar function snapshot (#150) 46 | 47 | Code 48 | scalar_checker 49 | Output 50 | function (x, ..., x_arg = rlang::caller_arg(x), call = rlang::caller_env(), 51 | x_class = stbl::object_type(x)) 52 | { 53 | stbl::stabilize_chr_scalar(x, ..., x_arg = x_arg, call = call, 54 | x_class = x_class) 55 | } 56 | attr(,"class") 57 | [1] "stbl_specified_fn" "function" 58 | 59 | -------------------------------------------------------------------------------- /tests/testthat/test-stabilize_int.R: -------------------------------------------------------------------------------- 1 | test_that("stabilize_int() checks min_value (#2, #6, #176)", { 2 | given <- 1:10 3 | expect_identical( 4 | stabilize_int(given, min_value = 1, max_value = 10), 5 | given 6 | ) 7 | expect_error( 8 | stabilize_int(given, min_value = 11), 9 | class = .compile_dash("stbl", "error", "outside_range") 10 | ) 11 | expect_snapshot( 12 | stabilize_int(given, min_value = 11), 13 | error = TRUE 14 | ) 15 | expect_snapshot( 16 | wrapped_stabilize_int(given, min_value = 11), 17 | error = TRUE 18 | ) 19 | }) 20 | 21 | test_that("stabilize_int() checks max_value (#5, #176)", { 22 | given <- 1:10 23 | expect_error( 24 | stabilize_int(given, max_value = 4), 25 | class = .compile_dash("stbl", "error", "outside_range") 26 | ) 27 | expect_snapshot( 28 | stabilize_int(given, max_value = 4), 29 | error = TRUE 30 | ) 31 | expect_snapshot( 32 | wrapped_stabilize_int(given, max_value = 4), 33 | error = TRUE 34 | ) 35 | }) 36 | 37 | test_that("stabilize_int_scalar() allows length-1 ints through (#12)", { 38 | given <- 1L 39 | expect_identical(stabilize_int_scalar(given), given) 40 | }) 41 | 42 | test_that("stabilize_int_scalar() errors on non-scalars (#12)", { 43 | given <- 1:10 44 | expect_error( 45 | stabilize_int_scalar(given), 46 | class = .compile_dash("stbl", "error", "non_scalar") 47 | ) 48 | expect_snapshot( 49 | stabilize_int_scalar(given), 50 | error = TRUE 51 | ) 52 | expect_snapshot( 53 | wrapped_stabilize_int_scalar(given), 54 | error = TRUE 55 | ) 56 | }) 57 | 58 | test_that("stabilise_int() exists (#167)", { 59 | expect_no_error(stabilise_int(TRUE)) 60 | }) 61 | 62 | test_that("stabilize_integer() exists (#164)", { 63 | expect_no_error(stabilize_integer(TRUE)) 64 | }) 65 | 66 | test_that("stabilise_integer() exists (#167)", { 67 | expect_no_error(stabilise_integer(TRUE)) 68 | }) 69 | 70 | test_that("stabilise_int_scalar() exists (#167)", { 71 | expect_no_error(stabilise_int_scalar(TRUE)) 72 | }) 73 | 74 | test_that("stabilize_integer_scalar() exists (#164)", { 75 | expect_no_error(stabilize_integer_scalar(TRUE)) 76 | }) 77 | 78 | test_that("stabilise_integer_scalar() exists (#167)", { 79 | expect_no_error(stabilise_integer_scalar(TRUE)) 80 | }) 81 | -------------------------------------------------------------------------------- /man/dot-stabilize_cls_scalar.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/cls_unexported.R 3 | \name{.stabilize_cls_scalar} 4 | \alias{.stabilize_cls_scalar} 5 | \title{Stabilize a scalar object of a specific class} 6 | \usage{ 7 | .stabilize_cls_scalar( 8 | x, 9 | to_cls_scalar_fn, 10 | ..., 11 | to_cls_scalar_args = list(), 12 | check_cls_value_fn = NULL, 13 | check_cls_value_fn_args = list(), 14 | allow_null = TRUE, 15 | allow_zero_length = TRUE, 16 | allow_na = TRUE, 17 | x_arg = caller_arg(x), 18 | call = caller_env(), 19 | x_class = object_type(x) 20 | ) 21 | } 22 | \arguments{ 23 | \item{x}{The argument to stabilize.} 24 | 25 | \item{to_cls_scalar_fn}{\verb{(function)} The \verb{to_*_scalar()} function to use for 26 | coercion.} 27 | 28 | \item{...}{Arguments passed to methods.} 29 | 30 | \item{to_cls_scalar_args}{\code{(list)} A list of additional arguments to pass to 31 | \code{to_cls_scalar_fn()}.} 32 | 33 | \item{check_cls_value_fn}{\verb{(function)} A function to check the values of \code{x} 34 | after coercion.} 35 | 36 | \item{check_cls_value_fn_args}{\code{(list)} A list of additional arguments to 37 | pass to \code{check_cls_value_fn()}.} 38 | 39 | \item{allow_null}{\verb{(length-1 logical)} Is NULL an acceptable value?} 40 | 41 | \item{allow_zero_length}{\verb{(length-1 logical)} Are zero-length vectors 42 | acceptable?} 43 | 44 | \item{allow_na}{\verb{(length-1 logical)} Are NA values ok?} 45 | 46 | \item{x_arg}{\verb{(length-1 character)} An argument name for x. The automatic 47 | value will work in most cases, or pass it through from higher-level 48 | functions to make error messages clearer in unexported functions.} 49 | 50 | \item{call}{\code{(environment)} The execution environment to mention as the 51 | source of error messages.} 52 | 53 | \item{x_class}{\verb{(length-1 character)} The class name of \code{x} to use in error 54 | messages. Use this if you remove a special class from \code{x} before checking 55 | its coercion, but want the error message to match the original class.} 56 | } 57 | \value{ 58 | \code{x} as a scalar of the target class with all checks passed. 59 | } 60 | \description{ 61 | A helper used by the \verb{stabilize_*_scalar()} functions to provide a standard 62 | set of checks. 63 | } 64 | \keyword{internal} 65 | -------------------------------------------------------------------------------- /R/stabilize_arg.R: -------------------------------------------------------------------------------- 1 | #' Ensure an argument meets expectations 2 | #' 3 | #' @description 4 | #' `stabilize_arg()` is used by other functions such as [stabilize_int()]. Use 5 | #' `stabilize_arg()` if the type-specific functions will not work for your use 6 | #' case, but you would still like to check things like size or whether the 7 | #' argument is NULL. 8 | #' 9 | #' `stabilize_arg_scalar()` is optimized to check for length-1 vectors. 10 | #' 11 | #' @inheritParams .shared-params 12 | #' 13 | #' @returns `x`, unless one of the checks fails. 14 | #' @family stabilization functions 15 | #' @export 16 | #' 17 | #' @examples 18 | #' wrapper <- function(this_arg, ...) { 19 | #' stabilize_arg(this_arg, ...) 20 | #' } 21 | #' wrapper(1) 22 | #' wrapper(NULL) 23 | #' wrapper(NA) 24 | #' try(wrapper(NULL, allow_null = FALSE)) 25 | #' try(wrapper(NA, allow_na = FALSE)) 26 | #' try(wrapper(1, min_size = 2)) 27 | #' try(wrapper(1:10, max_size = 5)) 28 | #' stabilize_arg_scalar("a") 29 | #' stabilize_arg_scalar(1L) 30 | #' try(stabilize_arg_scalar(1:10)) 31 | stabilize_arg <- function( 32 | x, 33 | ..., 34 | allow_null = TRUE, 35 | allow_na = TRUE, 36 | min_size = NULL, 37 | max_size = NULL, 38 | x_arg = caller_arg(x), 39 | call = caller_env(), 40 | x_class = object_type(x) 41 | ) { 42 | check_dots_empty0(..., call = call) 43 | 44 | if (is.null(x)) { 45 | return( 46 | .to_null(x, allow_null = allow_null, x_arg = x_arg, call = call) 47 | ) 48 | } 49 | 50 | .check_na(x, allow_na = allow_na, x_arg = x_arg, call = call) 51 | .check_size( 52 | x, 53 | min_size = min_size, 54 | max_size = max_size, 55 | x_arg = x_arg, 56 | call = call 57 | ) 58 | return(x) 59 | } 60 | 61 | #' @rdname stabilize_arg 62 | #' @export 63 | stabilize_arg_scalar <- function( 64 | x, 65 | ..., 66 | allow_null = TRUE, 67 | allow_zero_length = TRUE, 68 | allow_na = TRUE, 69 | x_arg = caller_arg(x), 70 | call = caller_env(), 71 | x_class = object_type(x) 72 | ) { 73 | check_dots_empty0(..., call = call) 74 | .check_scalar( 75 | x, 76 | allow_null = allow_null, 77 | allow_zero_length = allow_zero_length, 78 | x_arg = x_arg, 79 | call = call, 80 | x_class = x_class 81 | ) 82 | .check_na(x, allow_na = allow_na, x_arg = x_arg, call = call) 83 | return(x) 84 | } 85 | -------------------------------------------------------------------------------- /man/are_lgl_ish.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/are_lgl_ish.R 3 | \name{are_lgl_ish} 4 | \alias{are_lgl_ish} 5 | \alias{is_lgl_ish} 6 | \alias{are_logical_ish} 7 | \alias{is_logical_ish} 8 | \alias{are_lgl_ish.default} 9 | \title{Check if an object can be safely coerced to logical} 10 | \usage{ 11 | are_lgl_ish(x, ...) 12 | 13 | is_lgl_ish(x, ...) 14 | 15 | are_logical_ish(x, ...) 16 | 17 | is_logical_ish(x, ...) 18 | 19 | \method{are_lgl_ish}{default}(x, ..., depth = 1) 20 | } 21 | \arguments{ 22 | \item{x}{The object to check.} 23 | 24 | \item{...}{Arguments passed to methods.} 25 | 26 | \item{depth}{\verb{(length-1 integer)} Current recursion depth. Do not manually 27 | set this parameter.} 28 | } 29 | \value{ 30 | \code{are_lgl_ish()} returns a logical vector with the same length as the 31 | input. \code{is_lgl_ish()} returns a \verb{length-1 logical} (\code{TRUE} or \code{FALSE}) for 32 | the entire vector. 33 | } 34 | \description{ 35 | \code{are_lgl_ish()} is a vectorized predicate function that checks 36 | whether each element of its input can be safely coerced to a logical vector. 37 | \code{are_logical_ish()} is a synonym of \code{are_lgl_ish()}. 38 | 39 | \code{is_lgl_ish()} is a scalar predicate function that checks if all elements of 40 | its input can be safely coerced to a logical vector. \code{is_logical_ish()} is a 41 | synonym of \code{is_lgl_ish()}. 42 | } 43 | \examples{ 44 | are_lgl_ish(c(TRUE, FALSE, NA)) 45 | is_lgl_ish(c(TRUE, FALSE, NA)) 46 | 47 | are_lgl_ish(c(1, 0, 1.0, NA)) 48 | is_lgl_ish(c(1, 0, 1.0, NA)) 49 | 50 | are_lgl_ish(c("T", "F", "TRUE", "FALSE", "true", "false", "1", "0")) 51 | is_lgl_ish(c("T", "F", "TRUE", "FALSE", "true", "false", "1", "0")) 52 | 53 | are_lgl_ish(c("T", "F", "a", "1.1")) 54 | is_lgl_ish(c("T", "F", "a", "1.1")) 55 | 56 | are_lgl_ish(factor(c("T", "a"))) 57 | is_lgl_ish(factor(c("T", "a"))) 58 | 59 | are_lgl_ish(list(TRUE, 0, "F", "a")) 60 | is_lgl_ish(list(TRUE, 0, "F", "a")) 61 | } 62 | \seealso{ 63 | Other logical functions: 64 | \code{\link{specify_lgl}()}, 65 | \code{\link{stabilize_lgl}()} 66 | 67 | Other check functions: 68 | \code{\link{are_chr_ish}()}, 69 | \code{\link{are_dbl_ish}()}, 70 | \code{\link{are_fct_ish}()}, 71 | \code{\link{are_int_ish}()} 72 | } 73 | \concept{check functions} 74 | \concept{logical functions} 75 | -------------------------------------------------------------------------------- /tests/testthat/test-are_fct_ish.R: -------------------------------------------------------------------------------- 1 | test_that("are_fct_ish() is TRUE for atomics when levels is NULL (#93)", { 2 | expect_true(is_fct_ish(letters)) 3 | expect_true(is_fct_ish(factor(letters))) 4 | expect_true(is_fct_ish(1:10)) 5 | expect_true(is_fct_ish(c(TRUE, FALSE))) 6 | }) 7 | 8 | test_that("are_fct_ish() works for NULL (#93)", { 9 | expect_identical(are_fct_ish(NULL), logical(0)) 10 | }) 11 | 12 | test_that("are_fct_ish() works when levels are provided (#93)", { 13 | expect_identical( 14 | are_fct_ish(letters[1:3], levels = c("a", "b", "c")), 15 | rep(TRUE, 3) 16 | ) 17 | expect_identical( 18 | are_fct_ish(letters[1:3], levels = c("a", "b")), 19 | c(TRUE, TRUE, FALSE) 20 | ) 21 | expect_identical( 22 | are_fct_ish(1:3, levels = c("1", "2")), 23 | c(TRUE, TRUE, FALSE) 24 | ) 25 | }) 26 | 27 | test_that("are_fct_ish() works with to_na (#93)", { 28 | expect_identical( 29 | are_fct_ish(letters[1:3], levels = "a", to_na = c("b", "c")), 30 | rep(TRUE, 3) 31 | ) 32 | }) 33 | 34 | test_that(".are_not_fct_ish_chr() works (#93)", { 35 | expect_identical( 36 | .are_not_fct_ish_chr(letters[1:3], levels = c("a", "b")), 37 | c(FALSE, FALSE, TRUE) 38 | ) 39 | expect_identical( 40 | .are_not_fct_ish_chr(letters[1:3], levels = "a", to_na = c("b", "c")), 41 | rep(FALSE, 3) 42 | ) 43 | expect_identical( 44 | .are_not_fct_ish_chr(letters, levels = NULL), 45 | rep(FALSE, 26) 46 | ) 47 | }) 48 | 49 | test_that("are_fct_ish() works for lists (#93)", { 50 | expect_identical( 51 | are_fct_ish(list("a", 1, TRUE), levels = c("a", "1", "TRUE")), 52 | rep(TRUE, 3) 53 | ) 54 | expect_identical( 55 | are_fct_ish(list("a", NULL, list(1)), levels = "a"), 56 | c(TRUE, FALSE, FALSE) 57 | ) 58 | }) 59 | 60 | test_that("are_fct_ish() returns FALSE for non-vectors (#93)", { 61 | expect_false(are_fct_ish(mean)) 62 | }) 63 | 64 | test_that("are_fct_ish() deals with factor-ish S3 objects (#93)", { 65 | expect_true(is_fct_ish(Sys.Date())) 66 | }) 67 | 68 | test_that("is_fct_ish() works (#93)", { 69 | expect_true(is_fct_ish(letters)) 70 | expect_true(is_fct_ish(NULL)) 71 | expect_false(is_fct_ish(letters, levels = "a")) 72 | }) 73 | 74 | test_that("are_factor_ish() exists (#164)", { 75 | expect_no_error(are_factor_ish()) 76 | }) 77 | 78 | test_that("is_factor_ish() exists (#164)", { 79 | expect_no_error(is_factor_ish("a")) 80 | }) 81 | -------------------------------------------------------------------------------- /tests/testthat/test-stabilize_dbl.R: -------------------------------------------------------------------------------- 1 | test_that("stabilize_dbl() checks min_value (#23, #176)", { 2 | given <- 1.1:10.1 3 | expect_identical( 4 | stabilize_dbl(given, min_value = 1.1, max_value = 10.1), 5 | given 6 | ) 7 | expect_error( 8 | stabilize_dbl(given, min_value = 11.1), 9 | class = .compile_dash("stbl", "error", "outside_range") 10 | ) 11 | expect_snapshot( 12 | stabilize_dbl(given, min_value = 11.1), 13 | error = TRUE 14 | ) 15 | expect_snapshot( 16 | stabilize_dbl(given[[1]], min_value = 11.1), 17 | error = TRUE 18 | ) 19 | expect_snapshot( 20 | wrapped_stabilize_dbl(given, min_value = 11.1), 21 | error = TRUE 22 | ) 23 | }) 24 | 25 | test_that("stabilize_dbl() checks max_value (#23, #176)", { 26 | given <- 1.1:10.1 27 | expect_error( 28 | stabilize_dbl(given, max_value = 4.1), 29 | class = .compile_dash("stbl", "error", "outside_range") 30 | ) 31 | expect_snapshot( 32 | stabilize_dbl(given, max_value = 4.1), 33 | error = TRUE 34 | ) 35 | expect_snapshot( 36 | wrapped_stabilize_dbl(given, max_value = 4.1), 37 | error = TRUE 38 | ) 39 | }) 40 | 41 | test_that("stabilize_dbl_scalar() allows length-1 dbls through (#23)", { 42 | given <- 1.1 43 | expect_identical(stabilize_dbl_scalar(given), given) 44 | }) 45 | 46 | test_that("stabilize_dbl_scalar() errors on non-scalars (#23)", { 47 | given <- 1.1:10.1 48 | expect_error( 49 | stabilize_dbl_scalar(given), 50 | class = .compile_dash("stbl", "error", "non_scalar") 51 | ) 52 | expect_snapshot( 53 | stabilize_dbl_scalar(given), 54 | error = TRUE 55 | ) 56 | expect_snapshot( 57 | wrapped_stabilize_dbl_scalar(given), 58 | error = TRUE 59 | ) 60 | }) 61 | 62 | test_that("stabilise_dbl() exists (#167)", { 63 | expect_no_error(stabilise_dbl(TRUE)) 64 | }) 65 | 66 | test_that("stabilize_double() exists (#164)", { 67 | expect_no_error(stabilize_double(TRUE)) 68 | }) 69 | 70 | test_that("stabilise_double() exists (#167)", { 71 | expect_no_error(stabilise_double(TRUE)) 72 | }) 73 | 74 | test_that("stabilise_dbl_scalar() exists (#167)", { 75 | expect_no_error(stabilise_dbl_scalar(TRUE)) 76 | }) 77 | 78 | test_that("stabilize_double_scalar() exists (#164)", { 79 | expect_no_error(stabilize_double_scalar(TRUE)) 80 | }) 81 | 82 | test_that("stabilise_double_scalar() exists (#167)", { 83 | expect_no_error(stabilise_double_scalar(TRUE)) 84 | }) 85 | -------------------------------------------------------------------------------- /tests/testthat/test-are_chr_ish.R: -------------------------------------------------------------------------------- 1 | test_that("are_chr_ish() returns TRUE for every element of a chr (#93)", { 2 | expect_identical(are_chr_ish(letters), rep(TRUE, 26)) 3 | given <- c(letters, NA) 4 | expect_identical(are_chr_ish(given), rep(TRUE, 27)) 5 | }) 6 | 7 | test_that("are_chr_ish() works for NULL (#93)", { 8 | expect_identical(are_chr_ish(NULL), logical(0)) 9 | }) 10 | 11 | test_that("are_chr_ish() returns TRUE for every element of other atomics (#93)", { 12 | expect_identical(are_chr_ish(1:10), rep(TRUE, 10)) 13 | expect_identical(are_chr_ish(c(TRUE, FALSE)), c(TRUE, TRUE)) 14 | expect_identical(are_chr_ish(factor(letters)), rep(TRUE, 26)) 15 | expect_identical(are_chr_ish(as.raw(1:10)), rep(TRUE, 10)) 16 | expect_identical(are_chr_ish(as.complex(1:10)), rep(TRUE, 10)) 17 | }) 18 | 19 | test_that("are_chr_ish() works for lists and data.frames (#93, #128)", { 20 | expect_identical(are_chr_ish(list("a", 1, TRUE)), c(TRUE, TRUE, TRUE)) 21 | expect_identical(are_chr_ish(list("a", NULL, "b")), c(TRUE, FALSE, TRUE)) 22 | expect_identical(are_chr_ish(list(a = 1, b = 1:5)), c(TRUE, FALSE)) 23 | expect_identical(are_chr_ish(list(a = character(0))), c(FALSE)) 24 | expect_identical(are_chr_ish(mtcars), rep(FALSE, length(mtcars))) 25 | expect_identical(are_chr_ish(list(list(1), 2)), c(TRUE, TRUE)) 26 | }) 27 | 28 | test_that("are_chr_ish() returns unnamed vectors (#93)", { 29 | expect_named(are_chr_ish(list(a = 1, b = "c")), NULL) 30 | }) 31 | 32 | test_that("are_chr_ish() returns FALSE for non-vectors (#93)", { 33 | expect_false(are_chr_ish(mean)) 34 | }) 35 | 36 | test_that("is_chr_ish() returns a single TRUE for coercible objects (#93)", { 37 | expect_true(is_chr_ish("a")) 38 | expect_true(is_chr_ish(1:10)) 39 | expect_true(is_chr_ish(NULL)) 40 | expect_true(is_chr_ish(list("a", 1, TRUE))) 41 | }) 42 | 43 | test_that("is_chr_ish() works for NULL (#93)", { 44 | expect_true(is_chr_ish(NULL)) 45 | }) 46 | 47 | test_that("is_chr_ish() returns FALSE for uncoercibles (#93)", { 48 | expect_false(is_chr_ish(mean)) 49 | expect_false(is_chr_ish(list("a", NULL, "b"))) 50 | expect_false(is_chr_ish(list(a = 1, b = 1:5))) 51 | expect_false(is_chr_ish(list(a = character(0)))) 52 | expect_false(is_chr_ish(mtcars)) 53 | }) 54 | 55 | test_that("are_character_ish() exists (#164)", { 56 | expect_no_error(are_character_ish()) 57 | }) 58 | 59 | test_that("is_character_ish() exists (#164)", { 60 | expect_no_error(is_character_ish("a")) 61 | }) 62 | -------------------------------------------------------------------------------- /man/dot-stabilize_cls.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/cls_unexported.R 3 | \name{.stabilize_cls} 4 | \alias{.stabilize_cls} 5 | \title{Stabilize an object of a specific class} 6 | \usage{ 7 | .stabilize_cls( 8 | x, 9 | to_cls_fn, 10 | ..., 11 | to_cls_args = list(), 12 | check_cls_value_fn = NULL, 13 | check_cls_value_fn_args = list(), 14 | allow_null = TRUE, 15 | allow_na = TRUE, 16 | min_size = NULL, 17 | max_size = NULL, 18 | x_arg = caller_arg(x), 19 | call = caller_env(), 20 | x_class = object_type(x) 21 | ) 22 | } 23 | \arguments{ 24 | \item{x}{The argument to stabilize.} 25 | 26 | \item{to_cls_fn}{\verb{(function)} The \verb{to_*()} function to use for coercion.} 27 | 28 | \item{...}{Arguments passed to methods.} 29 | 30 | \item{to_cls_args}{\code{(list)} A list of additional arguments to pass to 31 | \code{to_cls_fn()}.} 32 | 33 | \item{check_cls_value_fn}{\verb{(function)} A function to check the values of \code{x} 34 | after coercion.} 35 | 36 | \item{check_cls_value_fn_args}{\code{(list)} A list of additional arguments to 37 | pass to \code{check_cls_value_fn()}.} 38 | 39 | \item{allow_null}{\verb{(length-1 logical)} Is NULL an acceptable value?} 40 | 41 | \item{allow_na}{\verb{(length-1 logical)} Are NA values ok?} 42 | 43 | \item{min_size}{\verb{(length-1 integer)} The minimum size of the object. Object 44 | size will be tested using \code{\link[vctrs:vec_size]{vctrs::vec_size()}}.} 45 | 46 | \item{max_size}{\verb{(length-1 integer)} The maximum size of the object. Object 47 | size will be tested using \code{\link[vctrs:vec_size]{vctrs::vec_size()}}.} 48 | 49 | \item{x_arg}{\verb{(length-1 character)} An argument name for x. The automatic 50 | value will work in most cases, or pass it through from higher-level 51 | functions to make error messages clearer in unexported functions.} 52 | 53 | \item{call}{\code{(environment)} The execution environment to mention as the 54 | source of error messages.} 55 | 56 | \item{x_class}{\verb{(length-1 character)} The class name of \code{x} to use in error 57 | messages. Use this if you remove a special class from \code{x} before checking 58 | its coercion, but want the error message to match the original class.} 59 | } 60 | \value{ 61 | \code{x} as a vector of the target class with all checks passed. 62 | } 63 | \description{ 64 | A helper used by the \verb{stabilize_*()} functions to provide a standard set of 65 | checks. 66 | } 67 | \keyword{internal} 68 | -------------------------------------------------------------------------------- /tests/testthat/test-are_lgl_ish.R: -------------------------------------------------------------------------------- 1 | test_that("are_lgl_ish() works for logicals (#93)", { 2 | expect_identical(are_lgl_ish(c(TRUE, FALSE, NA)), rep(TRUE, 3)) 3 | }) 4 | 5 | test_that("are_lgl_ish() works for NULL (#93)", { 6 | expect_identical(are_lgl_ish(NULL), logical(0)) 7 | }) 8 | 9 | test_that("are_lgl_ish() works for numerics (#93)", { 10 | expect_identical(are_lgl_ish(c(1, 0, 1.1, NA)), rep(TRUE, 4)) 11 | }) 12 | 13 | test_that("are_lgl_ish() works for characters (#93, #30)", { 14 | expect_identical( 15 | are_lgl_ish(c("TRUE", "FALSE", "T", "F", "true", "false", NA)), 16 | rep(TRUE, 7) 17 | ) 18 | expect_identical(are_lgl_ish(c("a", "")), c(FALSE, FALSE)) 19 | expect_identical(are_lgl_ish(c("0", "1", "-1", "1.0")), rep(TRUE, 4)) 20 | expect_identical(are_lgl_ish("1.1"), TRUE) 21 | }) 22 | 23 | test_that("are_lgl_ish() works for factors (#93, #30)", { 24 | expect_identical( 25 | are_lgl_ish(factor(c("TRUE", "FALSE", "T", "F", "true", "false", NA))), 26 | rep(TRUE, 7) 27 | ) 28 | expect_identical(are_lgl_ish(factor(c("a", ""))), c(FALSE, FALSE)) 29 | }) 30 | 31 | test_that("are_lgl_ish() works for lists (#93, #30)", { 32 | expect_identical( 33 | are_lgl_ish(list(TRUE, 1, 0, "false", NA)), 34 | rep(TRUE, 5) 35 | ) 36 | expect_identical( 37 | are_lgl_ish(list("a", NULL, list(1))), 38 | c(FALSE, FALSE, TRUE) 39 | ) 40 | expect_identical( 41 | are_lgl_ish(list("a", NULL, list(1, 0))), 42 | c(FALSE, FALSE, FALSE) 43 | ) 44 | }) 45 | 46 | test_that("are_lgl_ish() returns FALSE for non-vectors (#93)", { 47 | expect_false(are_lgl_ish(mean)) 48 | }) 49 | 50 | test_that("are_lgl_ish() returns FALSE for unhandled S3 objects (#93)", { 51 | expect_false(is_lgl_ish(Sys.Date())) 52 | expect_identical( 53 | are_lgl_ish(as.Date(c("2025-01-01", "2025-01-02"))), 54 | c(FALSE, FALSE) 55 | ) 56 | expect_identical( 57 | are_lgl_ish(list(TRUE, Sys.Date())), 58 | c(TRUE, FALSE) 59 | ) 60 | }) 61 | 62 | test_that("is_lgl_ish() works (#93)", { 63 | expect_true(is_lgl_ish(TRUE)) 64 | expect_true(is_lgl_ish(c(1, 0, NA))) 65 | expect_true(is_lgl_ish(NULL)) 66 | expect_true(is_lgl_ish(list(TRUE, 0, "false"))) 67 | 68 | expect_false(is_lgl_ish("a")) 69 | expect_false(is_lgl_ish(list(TRUE, "a"))) 70 | }) 71 | 72 | test_that("are_logical_ish() exists (#164)", { 73 | expect_no_error(are_logical_ish()) 74 | }) 75 | 76 | test_that("is_logical_ish() exists (#164)", { 77 | expect_no_error(is_logical_ish(TRUE)) 78 | }) 79 | -------------------------------------------------------------------------------- /man/to_lst.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/to_lst.R 3 | \name{to_lst} 4 | \alias{to_lst} 5 | \alias{to_list} 6 | \alias{to_lst.list} 7 | \alias{to_lst.default} 8 | \alias{to_lst.NULL} 9 | \alias{to_lst.function} 10 | \title{Ensure a list argument meets expectations} 11 | \usage{ 12 | to_lst(x, ..., x_arg = caller_arg(x), call = caller_env()) 13 | 14 | to_list(x, ..., x_arg = caller_arg(x), call = caller_env()) 15 | 16 | \method{to_lst}{list}(x, ...) 17 | 18 | \method{to_lst}{default}(x, ...) 19 | 20 | \method{to_lst}{`NULL`}(x, ..., allow_null = TRUE, x_arg = caller_arg(x), call = caller_env()) 21 | 22 | \method{to_lst}{`function`}( 23 | x, 24 | ..., 25 | coerce_function = FALSE, 26 | x_arg = caller_arg(x), 27 | call = caller_env() 28 | ) 29 | } 30 | \arguments{ 31 | \item{x}{The argument to stabilize.} 32 | 33 | \item{...}{Arguments passed to \code{\link[base:list]{base::as.list()}} or other methods.} 34 | 35 | \item{x_arg}{\verb{(length-1 character)} An argument name for x. The automatic 36 | value will work in most cases, or pass it through from higher-level 37 | functions to make error messages clearer in unexported functions.} 38 | 39 | \item{call}{\code{(environment)} The execution environment to mention as the 40 | source of error messages.} 41 | 42 | \item{allow_null}{\verb{(length-1 logical)} Is NULL an acceptable value?} 43 | 44 | \item{coerce_function}{\verb{(length-1 logical)} Should functions be coerced?} 45 | } 46 | \value{ 47 | The argument as a list. 48 | } 49 | \description{ 50 | \code{to_list()} checks whether an argument can be coerced to a list 51 | without losing information, returning it silently if so. Otherwise an 52 | informative error message is signaled. \code{to_lst()} is a synonym of 53 | \code{to_list()}. 54 | } 55 | \details{ 56 | This function has three important distinctions from 57 | \code{\link[base:list]{base::as.list()}}: 58 | \itemize{ 59 | \item Functions can be rejected as part of the call to this function (with 60 | \code{coerce_function = FALSE}, the default). If they are allowed, they'll be 61 | coerced to a list concatenating their formals and body (as with 62 | \code{\link[base:list]{base::as.list()}}. 63 | \item Primitive functions (such as \code{\link[base:NA]{base::is.na()}} or \code{\link[base:list]{base::is.list()}}) always 64 | throw an error, rather than returning \code{list(NULL)}. 65 | \item \code{NULL} values can be rejected as part of the call to this function (with 66 | \code{allow_null = FALSE}). 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /R/to_chr.R: -------------------------------------------------------------------------------- 1 | #' @export 2 | #' @rdname stabilize_chr 3 | to_chr <- function( 4 | x, 5 | ..., 6 | x_arg = caller_arg(x), 7 | call = caller_env(), 8 | x_class = object_type(x) 9 | ) { 10 | UseMethod("to_chr") 11 | } 12 | 13 | #' @export 14 | #' @rdname stabilize_chr 15 | to_character <- to_chr 16 | 17 | #' @export 18 | to_chr.character <- function(x, ...) { 19 | return(x) 20 | } 21 | 22 | #' @export 23 | #' @rdname stabilize_chr 24 | to_chr.NULL <- function( 25 | x, 26 | ..., 27 | allow_null = TRUE, 28 | x_arg = caller_arg(x), 29 | call = caller_env() 30 | ) { 31 | .to_null(x, allow_null = allow_null, x_arg = x_arg, call = call) 32 | } 33 | 34 | #' @export 35 | to_chr.list <- function( 36 | x, 37 | ..., 38 | x_arg = caller_arg(x), 39 | call = caller_env(), 40 | x_class = object_type(x) 41 | ) { 42 | .to_cls_from_list( 43 | x, 44 | to_chr, 45 | "character", 46 | ..., 47 | x_arg = x_arg, 48 | call = call, 49 | x_class = x_class 50 | ) 51 | } 52 | 53 | #' @export 54 | to_chr.data.frame <- function( 55 | x, 56 | ..., 57 | x_arg = caller_arg(x), 58 | call = caller_env(), 59 | x_class = object_type(x) 60 | ) { 61 | .stop_cant_coerce( 62 | from_class = x_class, 63 | to_class = "character", 64 | x_arg = x_arg, 65 | call = call 66 | ) 67 | } 68 | 69 | #' @export 70 | to_chr.default <- function( 71 | x, 72 | ..., 73 | x_arg = caller_arg(x), 74 | call = caller_env(), 75 | x_class = object_type(x) 76 | ) { 77 | try_fetch( 78 | as.character(x), 79 | error = function(cnd) { 80 | .stop_cant_coerce( 81 | from_class = x_class, 82 | to_class = "character", 83 | x_arg = x_arg, 84 | call = call 85 | ) 86 | } 87 | ) 88 | } 89 | 90 | #' @export 91 | #' @rdname stabilize_chr 92 | to_chr_scalar <- function( 93 | x, 94 | ..., 95 | allow_null = TRUE, 96 | allow_zero_length = TRUE, 97 | x_arg = caller_arg(x), 98 | call = caller_env(), 99 | x_class = object_type(x) 100 | ) { 101 | .to_cls_scalar( 102 | x, 103 | is_rlang_cls_scalar = is_scalar_character, 104 | to_cls_fn = to_chr, 105 | to_cls_args = list(...), 106 | allow_null = allow_null, 107 | allow_zero_length = allow_zero_length, 108 | x_arg = x_arg, 109 | call = call, 110 | x_class = x_class 111 | ) 112 | } 113 | 114 | #' @export 115 | #' @rdname stabilize_chr 116 | to_character_scalar <- to_chr_scalar 117 | -------------------------------------------------------------------------------- /.github/scripts/check-timestamp.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Exit immediately if a command exits with a non-zero status. 4 | set -e 5 | 6 | WORKFLOW_START_TIME="$1" 7 | # Define a temp file name and ensure it's cleaned up on exit 8 | TEMP_JSON_FILE=".github-ai-issues.tmp.json" 9 | trap 'rm -f "$TEMP_JSON_FILE"' EXIT 10 | 11 | if [ -z "$WORKFLOW_START_TIME" ]; then 12 | echo "Error: WORKFLOW_START_TIME argument is missing. Running update." >&2 13 | echo "changes=true" 14 | exit 0 15 | fi 16 | 17 | # Try to fetch the 'ai' branch. 18 | # If it fails (e.g., branch doesn't exist), we definitely need to run the update. 19 | if ! git fetch origin ai >/dev/null 2>&1; then 20 | echo "Could not fetch 'ai' branch (might not exist yet). Running update." >&2 21 | echo "changes=true" 22 | exit 0 23 | fi 24 | 25 | # Default to running the update 26 | CHANGES="true" 27 | 28 | # Check if the remote branch origin/ai actually exists 29 | # We've successfully fetched, so we can check origin/ai 30 | if git rev-parse --verify origin/ai >/dev/null 2>&1; then 31 | # Branch exists. Try to get the file from it. 32 | # '2>/dev/null' hides errors if the file doesn't exist on the branch. 33 | if git show origin/ai:.github/ai/issues.json > "$TEMP_JSON_FILE" 2>/dev/null; then 34 | # File exists on the 'ai' branch and we've copied it to our temp file. 35 | JSON_TIMESTAMP=$(jq -r '._metadata.updated_at // empty' "$TEMP_JSON_FILE") 36 | 37 | if [ -z "$JSON_TIMESTAMP" ]; then 38 | echo "No timestamp in issues.json on 'ai' branch. Running update." >&2 39 | CHANGES="true" 40 | else 41 | # Compare ISO 8601 timestamps as strings 42 | if [[ "$JSON_TIMESTAMP" < "$WORKFLOW_START_TIME" ]]; then 43 | echo "issues.json ($JSON_TIMESTAMP) is older than workflow start time ($WORKFLOW_START_TIME). Running update." >&2 44 | CHANGES="true" 45 | else 46 | echo "issues.json ($JSON_TIMESTAMP) is up-to-date (workflow start: $WORKFLOW_START_TIME). No changes needed." >&2 47 | CHANGES="false" # This is the only path where we skip the update 48 | fi 49 | fi 50 | else 51 | # File .github/ai/issues.json does not exist on 'ai' branch. 52 | echo "issues.json not found on 'ai' branch. Running update." >&2 53 | CHANGES="true" 54 | fi 55 | else 56 | # This case should be redundant now because of the fetch check, 57 | # but we'll keep it as a safeguard. 58 | echo "Branch 'ai' not found on remote. Running update." >&2 59 | CHANGES="true" 60 | fi 61 | 62 | # This is the string that will be captured by $GITHUB_OUTPUT 63 | # It is the *only* line that should go to stdout. 64 | echo "changes=$CHANGES" 65 | -------------------------------------------------------------------------------- /.github/scripts/fetch-issues.R: -------------------------------------------------------------------------------- 1 | library(gh) 2 | library(glue) 3 | library(purrr) 4 | library(jsonlite) 5 | 6 | repo_full_name <- Sys.getenv("GITHUB_REPOSITORY") 7 | if (isTRUE(grepl("/", repo_full_name))) { 8 | repo_parts <- strsplit(repo_full_name, "/")[[1]] 9 | } else { 10 | repo_parts <- unname(gh::gh_tree_remote()) 11 | } 12 | 13 | if (length(repo_parts) != 2) { 14 | stop( 15 | "Error: GITHUB_REPOSITORY environment variable not set correctly.\n", 16 | "Please set it for local testing (e.g., Sys.setenv(GITHUB_REPOSITORY = 'wranglezone/stbl')).", 17 | call. = FALSE 18 | ) 19 | } 20 | 21 | org_name <- repo_parts[[1]] 22 | repo_name <- repo_parts[[2]] 23 | 24 | output_path <- ".github/ai/issues.json" 25 | 26 | check_timestamp <- format(Sys.time(), tz = "UTC", "%Y-%m-%dT%H:%M:%SZ") 27 | issues_raw <- gh::gh( 28 | "/repos/{owner}/{repo}/issues", 29 | owner = org_name, 30 | repo = repo_name, 31 | state = "open", 32 | .limit = Inf 33 | ) 34 | 35 | max_issue_number <- if (length(issues_raw)) { 36 | max(purrr::map_int(issues_raw, "number")) 37 | } else { 38 | 0 39 | } 40 | 41 | all_issues <- list() 42 | 43 | if (max_issue_number > 0) { 44 | all_issues <- stats::setNames( 45 | replicate(max_issue_number, list(), simplify = FALSE), 46 | seq_len(max_issue_number) 47 | ) 48 | 49 | for (issue in issues_raw) { 50 | comments <- if (issue$comments > 0) { 51 | tryCatch( 52 | { 53 | gh::gh(issue$comments_url, .limit = Inf) |> 54 | purrr::map_chr("body") 55 | }, 56 | error = function(e) { 57 | character(0) 58 | } 59 | ) 60 | } else { 61 | character(0) 62 | } 63 | 64 | all_issues[[issue$number]] <- list( 65 | title = issue$title, 66 | type = issue$type, 67 | milestone = issue$milestone$number, 68 | body = issue$body, 69 | comments = comments 70 | ) 71 | } 72 | } 73 | 74 | issue_collection <- list( 75 | `_metadata` = list( 76 | description = glue::glue( 77 | "A collection of GitHub issues for the {repo_name} repository." 78 | ), 79 | lookup_key = "issue_number", 80 | comment = "Each key in the 'issues' object is a string representation of the GitHub issue number. Empty objects are placeholders so that positions and ids match. Empty objects should be ignored.", 81 | updated_at = check_timestamp 82 | ), 83 | issues = all_issues 84 | ) 85 | 86 | if (!dir.exists(dirname(output_path))) { 87 | dir.create(dirname(output_path), recursive = TRUE) 88 | } 89 | 90 | jsonlite::write_json( 91 | issue_collection, 92 | output_path, 93 | auto_unbox = TRUE, 94 | pretty = TRUE 95 | ) 96 | -------------------------------------------------------------------------------- /R/are_lgl_ish.R: -------------------------------------------------------------------------------- 1 | #' Check if an object can be safely coerced to logical 2 | #' 3 | #' @description `are_lgl_ish()` is a vectorized predicate function that checks 4 | #' whether each element of its input can be safely coerced to a logical vector. 5 | #' `are_logical_ish()` is a synonym of `are_lgl_ish()`. 6 | #' 7 | #' `is_lgl_ish()` is a scalar predicate function that checks if all elements of 8 | #' its input can be safely coerced to a logical vector. `is_logical_ish()` is a 9 | #' synonym of `is_lgl_ish()`. 10 | #' 11 | #' @inheritParams .shared-params-check 12 | #' @inheritParams .shared-params 13 | #' 14 | #' @returns `are_lgl_ish()` returns a logical vector with the same length as the 15 | #' input. `is_lgl_ish()` returns a `length-1 logical` (`TRUE` or `FALSE`) for 16 | #' the entire vector. 17 | #' @family logical functions 18 | #' @family check functions 19 | #' @export 20 | #' 21 | #' @examples 22 | #' are_lgl_ish(c(TRUE, FALSE, NA)) 23 | #' is_lgl_ish(c(TRUE, FALSE, NA)) 24 | #' 25 | #' are_lgl_ish(c(1, 0, 1.0, NA)) 26 | #' is_lgl_ish(c(1, 0, 1.0, NA)) 27 | #' 28 | #' are_lgl_ish(c("T", "F", "TRUE", "FALSE", "true", "false", "1", "0")) 29 | #' is_lgl_ish(c("T", "F", "TRUE", "FALSE", "true", "false", "1", "0")) 30 | #' 31 | #' are_lgl_ish(c("T", "F", "a", "1.1")) 32 | #' is_lgl_ish(c("T", "F", "a", "1.1")) 33 | #' 34 | #' are_lgl_ish(factor(c("T", "a"))) 35 | #' is_lgl_ish(factor(c("T", "a"))) 36 | #' 37 | #' are_lgl_ish(list(TRUE, 0, "F", "a")) 38 | #' is_lgl_ish(list(TRUE, 0, "F", "a")) 39 | are_lgl_ish <- function(x, ...) { 40 | UseMethod("are_lgl_ish") 41 | } 42 | 43 | #' @export 44 | #' @rdname are_lgl_ish 45 | is_lgl_ish <- function(x, ...) { 46 | all(are_lgl_ish(x, ...)) 47 | } 48 | 49 | #' @export 50 | #' @rdname are_lgl_ish 51 | are_logical_ish <- are_lgl_ish 52 | 53 | #' @export 54 | #' @rdname are_lgl_ish 55 | is_logical_ish <- is_lgl_ish 56 | 57 | #' @export 58 | are_lgl_ish.logical <- function(x, ...) { 59 | rep(TRUE, length(x)) 60 | } 61 | 62 | #' @export 63 | are_lgl_ish.NULL <- function(x, ...) { 64 | logical(0) 65 | } 66 | 67 | #' @export 68 | are_lgl_ish.numeric <- function(x, ...) { 69 | rep(TRUE, length(x)) 70 | } 71 | 72 | #' @export 73 | are_lgl_ish.character <- function(x, ...) { 74 | cast <- as.logical(toupper(x)) 75 | !xor(is.na(x), is.na(cast)) | are_dbl_ish(x, ...) 76 | } 77 | 78 | #' @export 79 | are_lgl_ish.factor <- function(x, ...) { 80 | are_lgl_ish(as.character(x), ...) 81 | } 82 | 83 | #' @export 84 | #' @rdname are_lgl_ish 85 | are_lgl_ish.default <- function(x, ..., depth = 1) { 86 | if (!rlang::is_vector(x) || depth != 1) { 87 | return(FALSE) 88 | } 89 | .elements_are_cls_ish(x, are_lgl_ish, ...) 90 | } 91 | -------------------------------------------------------------------------------- /.github/workflows/pr-commands.yaml: -------------------------------------------------------------------------------- 1 | # Workflow derived from https://github.com/r-lib/actions/tree/v2/examples 2 | # Need help debugging build failures? Start at https://github.com/r-lib/actions#where-to-find-help 3 | on: 4 | issue_comment: 5 | types: [created] 6 | 7 | name: Commands 8 | 9 | jobs: 10 | document: 11 | if: ${{ github.event.issue.pull_request && (github.event.comment.author_association == 'MEMBER' || github.event.comment.author_association == 'OWNER') && startsWith(github.event.comment.body, '/document') }} 12 | name: document 13 | runs-on: ubuntu-latest 14 | env: 15 | GITHUB_PAT: ${{ secrets.GITHUB_TOKEN }} 16 | steps: 17 | - uses: actions/checkout@v4 18 | 19 | - uses: r-lib/actions/pr-fetch@v2 20 | with: 21 | repo-token: ${{ secrets.GITHUB_TOKEN }} 22 | 23 | - uses: r-lib/actions/setup-r@v2 24 | with: 25 | use-public-rspm: true 26 | 27 | - uses: r-lib/actions/setup-r-dependencies@v2 28 | with: 29 | extra-packages: any::roxygen2 30 | needs: pr-document 31 | 32 | - name: Document 33 | run: roxygen2::roxygenise() 34 | shell: Rscript {0} 35 | 36 | - name: commit 37 | run: | 38 | git config --local user.name "$GITHUB_ACTOR" 39 | git config --local user.email "$GITHUB_ACTOR@users.noreply.github.com" 40 | git add man/\* NAMESPACE 41 | git commit -m 'Document' 42 | 43 | - uses: r-lib/actions/pr-push@v2 44 | with: 45 | repo-token: ${{ secrets.GITHUB_TOKEN }} 46 | 47 | style: 48 | if: ${{ github.event.issue.pull_request && (github.event.comment.author_association == 'MEMBER' || github.event.comment.author_association == 'OWNER') && startsWith(github.event.comment.body, '/style') }} 49 | name: style 50 | runs-on: ubuntu-latest 51 | env: 52 | GITHUB_PAT: ${{ secrets.GITHUB_TOKEN }} 53 | steps: 54 | - uses: actions/checkout@v4 55 | 56 | - uses: r-lib/actions/pr-fetch@v2 57 | with: 58 | repo-token: ${{ secrets.GITHUB_TOKEN }} 59 | 60 | - uses: r-lib/actions/setup-r@v2 61 | 62 | - name: Install dependencies 63 | run: install.packages("styler") 64 | shell: Rscript {0} 65 | 66 | - name: Style 67 | run: styler::style_pkg() 68 | shell: Rscript {0} 69 | 70 | - name: commit 71 | run: | 72 | git config --local user.name "$GITHUB_ACTOR" 73 | git config --local user.email "$GITHUB_ACTOR@users.noreply.github.com" 74 | git add \*.R 75 | git commit -m 'Style' 76 | 77 | - uses: r-lib/actions/pr-push@v2 78 | with: 79 | repo-token: ${{ secrets.GITHUB_TOKEN }} 80 | -------------------------------------------------------------------------------- /tests/testthat/_snaps/aaa-conditions.md: -------------------------------------------------------------------------------- 1 | # .stbl_abort() throws the expected error 2 | 3 | Code 4 | .stbl_abort("A message.", "a_subclass") 5 | Condition 6 | Error: 7 | ! A message. 8 | 9 | # .stop_cant_coerce() throws the expected error 10 | 11 | Code 12 | .stop_cant_coerce("character", "integer", "my_arg", rlang::current_env()) 13 | Condition 14 | Error: 15 | ! Can't coerce `my_arg` to . 16 | 17 | # .stop_cant_coerce() uses additional_msg when provided 18 | 19 | Code 20 | .stop_cant_coerce("character", "integer", "my_arg", rlang::current_env(), 21 | additional_msg = c(x = "An extra message.")) 22 | Condition 23 | Error: 24 | ! Can't coerce `my_arg` to . 25 | x An extra message. 26 | 27 | # .stop_must() throws the expected error 28 | 29 | Code 30 | .stop_must("must be a foo.", "my_arg", rlang::current_env()) 31 | Condition 32 | Error: 33 | ! `my_arg` must be a foo. 34 | 35 | # .stop_must() handles subclasses 36 | 37 | Code 38 | .stop_must("must be a foo.", "my_arg", rlang::current_env(), subclass = "my_custom_class") 39 | Condition 40 | Error: 41 | ! `my_arg` must be a foo. 42 | 43 | # .stop_must() uses additional_msg when provided 44 | 45 | Code 46 | .stop_must("must be a foo.", "my_arg", rlang::current_env(), additional_msg = c( 47 | `*` = "Some details.")) 48 | Condition 49 | Error: 50 | ! `my_arg` must be a foo. 51 | * Some details. 52 | 53 | # .stop_null() throws the expected error 54 | 55 | Code 56 | .stop_null("my_arg", rlang::current_env()) 57 | Condition 58 | Error: 59 | ! `my_arg` must not be . 60 | 61 | # .stop_incompatible() throws the expected error 62 | 63 | Code 64 | .stop_incompatible("character", integer(), c(FALSE, TRUE, FALSE, TRUE), 65 | "some reason", "my_arg", rlang::current_env()) 66 | Condition 67 | Error: 68 | ! `my_arg` must be coercible to 69 | x Can't convert some values due to some reason. 70 | * Locations: 2 and 4 71 | 72 | # .stop_incompatible() passes dots 73 | 74 | Code 75 | .stop_incompatible("character", integer(), c(FALSE, TRUE, FALSE, TRUE), 76 | "some reason", "my_arg", rlang::current_env(), .internal = TRUE) 77 | Condition 78 | Error: 79 | ! `my_arg` must be coercible to 80 | x Can't convert some values due to some reason. 81 | * Locations: 2 and 4 82 | i This is an internal error that was detected in the stbl package. 83 | Please report it at with a reprex () and the full backtrace. 84 | 85 | -------------------------------------------------------------------------------- /.github/CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing to stbl 2 | 3 | This outlines how to propose a change to stbl. 4 | 5 | ## Fixing typos 6 | 7 | You can fix typos, spelling mistakes, or grammatical errors in the documentation directly using the GitHub web interface, as long as the changes are made in the _source_ file. 8 | This generally means you'll need to edit [roxygen2 comments](https://roxygen2.r-lib.org/articles/roxygen2.html) in an `.R`, not a `.Rd` file. 9 | You can find the `.R` file that generates the `.Rd` by reading the comment in the first line. 10 | 11 | ## Bigger changes 12 | 13 | If you want to make a bigger change, it's a good idea to first file an issue and make sure someone from the team agrees that it’s needed. 14 | If you’ve found a bug, please file an issue that illustrates the bug with a minimal 15 | [reprex](https://www.tidyverse.org/help/#reprex) (this will also help you write a unit test, if needed). 16 | 17 | ### Pull request process 18 | 19 | * Fork the package and clone onto your computer. If you haven't done this before, we recommend using `usethis::create_from_github("wranglezone/stbl", fork = TRUE)`. 20 | 21 | * Install all development dependencies with `devtools::install_dev_deps()`, and then make sure the package passes R CMD check by running `devtools::check()`. 22 | If R CMD check doesn't pass cleanly, it's a good idea to ask for help before continuing. 23 | * Create a Git branch for your pull request (PR). We recommend using `usethis::pr_init("brief-description-of-change")`. 24 | 25 | * Make your changes, commit to git, and then create a PR by running `usethis::pr_push()`, and following the prompts in your browser. 26 | The title of your PR should briefly describe the change. 27 | The body of your PR should contain `Fixes #issue-number`. 28 | 29 | * For user-facing changes, add a bullet to the top of `NEWS.md` (i.e. just below the first header). Follow the style described in . 30 | 31 | ### Code style 32 | 33 | * New code should follow the tidyverse [style guide](https://style.tidyverse.org). 34 | You can use the [styler](https://CRAN.R-project.org/package=styler) package to apply these styles, but please don't restyle code that has nothing to do with your PR. 35 | 36 | * We use [roxygen2](https://cran.r-project.org/package=roxygen2), with [Markdown syntax](https://cran.r-project.org/web/packages/roxygen2/vignettes/rd-formatting.html), for documentation. 37 | 38 | * We use [testthat](https://cran.r-project.org/package=testthat) for unit tests. 39 | Contributions with test cases included are easier to accept. 40 | 41 | ## Code of Conduct 42 | 43 | Please note that the stbl project is released with a 44 | [Contributor Code of Conduct](CODE_OF_CONDUCT.md). By contributing to this 45 | project you agree to abide by its terms. 46 | -------------------------------------------------------------------------------- /man/are_fct_ish.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/are_fct_ish.R 3 | \name{are_fct_ish} 4 | \alias{are_fct_ish} 5 | \alias{is_fct_ish} 6 | \alias{are_factor_ish} 7 | \alias{is_factor_ish} 8 | \alias{are_fct_ish.default} 9 | \title{Check if an object can be safely coerced to a factor} 10 | \usage{ 11 | are_fct_ish(x, ..., levels = NULL, to_na = character()) 12 | 13 | is_fct_ish(x, ...) 14 | 15 | are_factor_ish(x, ..., levels = NULL, to_na = character()) 16 | 17 | is_factor_ish(x, ...) 18 | 19 | \method{are_fct_ish}{default}(x, ..., levels = NULL, to_na = character(), depth = 1) 20 | } 21 | \arguments{ 22 | \item{x}{The object to check.} 23 | 24 | \item{...}{Arguments passed to methods.} 25 | 26 | \item{levels}{\code{(character)} The desired factor levels.} 27 | 28 | \item{to_na}{\code{(character)} Values to convert to \code{NA}.} 29 | 30 | \item{depth}{\verb{(length-1 integer)} Current recursion depth. Do not manually 31 | set this parameter.} 32 | } 33 | \value{ 34 | \code{are_fct_ish()} returns a logical vector with the same length as the 35 | input. \code{is_fct_ish()} returns a \verb{length-1 logical} (\code{TRUE} or \code{FALSE}) for 36 | the entire vector. 37 | } 38 | \description{ 39 | \code{are_fct_ish()} is a vectorized predicate function that checks 40 | whether each element of its input can be safely coerced to a factor. 41 | \code{are_factor_ish()} is a synonym of \code{are_fct_ish()}. 42 | 43 | \code{is_fct_ish()} is a scalar predicate function that checks if all elements 44 | of its input can be safely coerced to a factor. \code{is_factor_ish()} is a 45 | synonym of \code{is_fct_ish()}. 46 | } 47 | \examples{ 48 | # When `levels` is `NULL`, atomic vectors are fct_ish, but nested lists are not. 49 | are_fct_ish(c("a", 1, NA)) 50 | is_fct_ish(c("a", 1, NA)) 51 | are_fct_ish(list("a", list("b", "c"))) 52 | is_fct_ish(list("a", list("b", "c"))) 53 | 54 | # When `levels` is specified, values must be in `levels` or `to_na`. 55 | are_fct_ish(c("a", "b", "c"), levels = c("a", "b")) 56 | is_fct_ish(c("a", "b", "c"), levels = c("a", "b")) 57 | 58 | # The `to_na` argument allows some values to be treated as `NA`. 59 | are_fct_ish(c("a", "b", "z"), levels = c("a", "b"), to_na = "z") 60 | is_fct_ish(c("a", "b", "z"), levels = c("a", "b"), to_na = "z") 61 | 62 | # Factors are also checked against the specified levels. 63 | are_fct_ish(factor(c("a", "b", "c")), levels = c("a", "b")) 64 | is_fct_ish(factor(c("a", "b", "c")), levels = c("a", "b")) 65 | } 66 | \seealso{ 67 | Other factor functions: 68 | \code{\link{specify_fct}()}, 69 | \code{\link{stabilize_fct}()} 70 | 71 | Other check functions: 72 | \code{\link{are_chr_ish}()}, 73 | \code{\link{are_dbl_ish}()}, 74 | \code{\link{are_int_ish}()}, 75 | \code{\link{are_lgl_ish}()} 76 | } 77 | \concept{check functions} 78 | \concept{factor functions} 79 | -------------------------------------------------------------------------------- /man/are_dbl_ish.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/are_dbl_ish.R 3 | \name{are_dbl_ish} 4 | \alias{are_dbl_ish} 5 | \alias{is_dbl_ish} 6 | \alias{are_double_ish} 7 | \alias{is_double_ish} 8 | \alias{are_dbl_ish.character} 9 | \alias{are_dbl_ish.factor} 10 | \alias{are_dbl_ish.default} 11 | \title{Check if an object can be safely coerced to double} 12 | \usage{ 13 | are_dbl_ish(x, ...) 14 | 15 | is_dbl_ish(x, ...) 16 | 17 | are_double_ish(x, ...) 18 | 19 | is_double_ish(x, ...) 20 | 21 | \method{are_dbl_ish}{character}(x, ..., coerce_character = TRUE) 22 | 23 | \method{are_dbl_ish}{factor}(x, ..., coerce_factor = TRUE) 24 | 25 | \method{are_dbl_ish}{default}(x, ..., depth = 1) 26 | } 27 | \arguments{ 28 | \item{x}{The object to check.} 29 | 30 | \item{...}{Arguments passed to methods.} 31 | 32 | \item{coerce_character}{\verb{(length-1 logical)} Should character vectors such as 33 | "1" and "2.0" be considered numeric-ish?} 34 | 35 | \item{coerce_factor}{\verb{(length-1 logical)} Should factors with values such as 36 | "1" and "2.0" be considered numeric-ish? Note that this package uses the 37 | character value from the factor, while \code{\link[=as.integer]{as.integer()}} and \code{\link[=as.double]{as.double()}} use 38 | the integer index of the factor.} 39 | 40 | \item{depth}{\verb{(length-1 integer)} Current recursion depth. Do not manually 41 | set this parameter.} 42 | } 43 | \value{ 44 | \code{are_dbl_ish()} returns a logical vector with the same length as the 45 | input. \code{is_dbl_ish()} returns a \verb{length-1 logical} (\code{TRUE} or \code{FALSE}) for 46 | the entire vector. 47 | } 48 | \description{ 49 | \code{are_dbl_ish()} is a vectorized predicate function that checks 50 | whether each element of its input can be safely coerced to a double vector. 51 | \code{are_double_ish()} is a synonym of \code{are_dbl_ish()}. 52 | 53 | \code{is_dbl_ish()} is a scalar predicate function that checks if all elements of 54 | its input can be safely coerced to a double vector. \code{is_double_ish()} is a 55 | synonym of \code{is_dbl_ish()}. 56 | } 57 | \examples{ 58 | are_dbl_ish(c(1.0, 2.2, 3.14)) 59 | is_dbl_ish(c(1.0, 2.2, 3.14)) 60 | 61 | are_dbl_ish(1:3) 62 | is_dbl_ish(1:3) 63 | 64 | are_dbl_ish(c("1.1", "2.2", NA)) 65 | is_dbl_ish(c("1.1", "2.2", NA)) 66 | 67 | are_dbl_ish(c("a", "1.0")) 68 | is_dbl_ish(c("a", "1.0")) 69 | 70 | are_dbl_ish(list(1, "2.2", "c")) 71 | is_dbl_ish(list(1, "2.2", "c")) 72 | 73 | are_dbl_ish(c(1 + 1i, 1 + 0i, NA)) 74 | is_dbl_ish(c(1 + 1i, 1 + 0i, NA)) 75 | } 76 | \seealso{ 77 | Other double functions: 78 | \code{\link{specify_dbl}()}, 79 | \code{\link{stabilize_dbl}()} 80 | 81 | Other check functions: 82 | \code{\link{are_chr_ish}()}, 83 | \code{\link{are_fct_ish}()}, 84 | \code{\link{are_int_ish}()}, 85 | \code{\link{are_lgl_ish}()} 86 | } 87 | \concept{check functions} 88 | \concept{double functions} 89 | -------------------------------------------------------------------------------- /man/specify_lgl.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/specify_cls.R 3 | \name{specify_lgl} 4 | \alias{specify_lgl} 5 | \alias{specify_lgl_scalar} 6 | \alias{specify_logical} 7 | \alias{specify_logical_scalar} 8 | \title{Create a specified logical stabilizer function} 9 | \usage{ 10 | specify_lgl( 11 | allow_null = TRUE, 12 | allow_na = TRUE, 13 | min_size = NULL, 14 | max_size = NULL 15 | ) 16 | 17 | specify_lgl_scalar( 18 | allow_null = TRUE, 19 | allow_zero_length = TRUE, 20 | allow_na = TRUE 21 | ) 22 | 23 | specify_logical( 24 | allow_null = TRUE, 25 | allow_na = TRUE, 26 | min_size = NULL, 27 | max_size = NULL 28 | ) 29 | 30 | specify_logical_scalar( 31 | allow_null = TRUE, 32 | allow_zero_length = TRUE, 33 | allow_na = TRUE 34 | ) 35 | } 36 | \arguments{ 37 | \item{allow_null}{\verb{(length-1 logical)} Is NULL an acceptable value?} 38 | 39 | \item{allow_na}{\verb{(length-1 logical)} Are NA values ok?} 40 | 41 | \item{min_size}{\verb{(length-1 integer)} The minimum size of the object. Object 42 | size will be tested using \code{\link[vctrs:vec_size]{vctrs::vec_size()}}.} 43 | 44 | \item{max_size}{\verb{(length-1 integer)} The maximum size of the object. Object 45 | size will be tested using \code{\link[vctrs:vec_size]{vctrs::vec_size()}}.} 46 | 47 | \item{allow_zero_length}{\verb{(length-1 logical)} Are zero-length vectors 48 | acceptable?} 49 | } 50 | \value{ 51 | A function of class \code{"stbl_specified_fn"} that calls 52 | \code{\link[=stabilize_lgl]{stabilize_lgl()}} or \code{\link[=stabilize_lgl_scalar]{stabilize_lgl_scalar()}} with the provided arguments. 53 | The generated function will also accept \code{...} for additional arguments to 54 | pass to \code{stabilize_lgl()} or \code{stabilize_lgl_scalar()}. You can copy/paste 55 | the body of the resulting function if you want to provide additional 56 | context or functionality. 57 | } 58 | \description{ 59 | \code{specify_lgl()} creates a function that will call \code{\link[=stabilize_lgl]{stabilize_lgl()}} with the 60 | provided arguments. \code{specify_lgl_scalar()} creates a function that will call 61 | \code{\link[=stabilize_lgl_scalar]{stabilize_lgl_scalar()}} with the provided arguments. \code{specify_logical()} is 62 | a synonym of \code{specify_lgl()}, and \code{specify_logical_scalar()} is a synonym of 63 | \code{specify_lgl_scalar()}. 64 | } 65 | \examples{ 66 | stabilize_few_lgl <- specify_lgl(max_size = 5) 67 | stabilize_few_lgl(c(TRUE, "False", TRUE)) 68 | try(stabilize_few_lgl(rep(TRUE, 10))) 69 | } 70 | \seealso{ 71 | Other logical functions: 72 | \code{\link{are_lgl_ish}()}, 73 | \code{\link{stabilize_lgl}()} 74 | 75 | Other specification functions: 76 | \code{\link{specify_chr}()}, 77 | \code{\link{specify_dbl}()}, 78 | \code{\link{specify_fct}()}, 79 | \code{\link{specify_int}()} 80 | } 81 | \concept{logical functions} 82 | \concept{specification functions} 83 | -------------------------------------------------------------------------------- /man/are_int_ish.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/are_int_ish.R 3 | \name{are_int_ish} 4 | \alias{are_int_ish} 5 | \alias{is_int_ish} 6 | \alias{are_integer_ish} 7 | \alias{is_integer_ish} 8 | \alias{are_int_ish.character} 9 | \alias{are_int_ish.factor} 10 | \alias{are_int_ish.default} 11 | \title{Check if an object can be safely coerced to integer} 12 | \usage{ 13 | are_int_ish(x, ...) 14 | 15 | is_int_ish(x, ...) 16 | 17 | are_integer_ish(x, ...) 18 | 19 | is_integer_ish(x, ...) 20 | 21 | \method{are_int_ish}{character}(x, ..., coerce_character = TRUE) 22 | 23 | \method{are_int_ish}{factor}(x, ..., coerce_factor = TRUE) 24 | 25 | \method{are_int_ish}{default}(x, ..., depth = 1) 26 | } 27 | \arguments{ 28 | \item{x}{The object to check.} 29 | 30 | \item{...}{Arguments passed to methods.} 31 | 32 | \item{coerce_character}{\verb{(length-1 logical)} Should character vectors such as 33 | "1" and "2.0" be considered numeric-ish?} 34 | 35 | \item{coerce_factor}{\verb{(length-1 logical)} Should factors with values such as 36 | "1" and "2.0" be considered numeric-ish? Note that this package uses the 37 | character value from the factor, while \code{\link[=as.integer]{as.integer()}} and \code{\link[=as.double]{as.double()}} use 38 | the integer index of the factor.} 39 | 40 | \item{depth}{\verb{(length-1 integer)} Current recursion depth. Do not manually 41 | set this parameter.} 42 | } 43 | \value{ 44 | \code{are_int_ish()} returns a logical vector with the same length as the 45 | input. \code{is_int_ish()} returns a \verb{length-1 logical} (\code{TRUE} or \code{FALSE}) for 46 | the entire vector. 47 | } 48 | \description{ 49 | \code{are_int_ish()} is a vectorized predicate function that checks 50 | whether each element of its input can be safely coerced to an integer 51 | vector. \code{are_integer_ish()} is a synonym of \code{are_int_ish()}. 52 | 53 | \code{is_int_ish()} is a scalar predicate function that checks if all elements 54 | of its input can be safely coerced to an integer vector. \code{is_integer_ish()} 55 | is a synonym of \code{is_int_ish()}. 56 | } 57 | \examples{ 58 | are_int_ish(1:4) 59 | is_int_ish(1:4) 60 | 61 | are_int_ish(c(1.0, 2.0, 3.00000)) 62 | is_int_ish(c(1.0, 2.0, 3.00000)) 63 | 64 | are_int_ish(c("1.0", "2.0", "3.00000")) 65 | is_int_ish(c("1.0", "2.0", "3.00000")) 66 | 67 | are_int_ish(c(1, 2.2, NA)) 68 | is_int_ish(c(1, 2.2, NA)) 69 | 70 | are_int_ish(c("1", "1.0", "1.1", "a")) 71 | is_int_ish(c("1", "1.0", "1.1", "a")) 72 | 73 | are_int_ish(factor(c("1", "a"))) 74 | is_int_ish(factor(c("1", "a"))) 75 | } 76 | \seealso{ 77 | Other integer functions: 78 | \code{\link{specify_int}()}, 79 | \code{\link{stabilize_int}()} 80 | 81 | Other check functions: 82 | \code{\link{are_chr_ish}()}, 83 | \code{\link{are_dbl_ish}()}, 84 | \code{\link{are_fct_ish}()}, 85 | \code{\link{are_lgl_ish}()} 86 | } 87 | \concept{check functions} 88 | \concept{integer functions} 89 | -------------------------------------------------------------------------------- /tests/testthat/_snaps/to_fct.md: -------------------------------------------------------------------------------- 1 | # to_fct() throws errors for bad levels (#62, #177) 2 | 3 | Code 4 | to_fct(letters[1:5], levels = c("a", "c"), to_na = "b") 5 | Condition 6 | Error: 7 | ! Each value of `letters[1:5]` must be in the expected levels. 8 | i Allowed levels: "a" and "c". 9 | i Values that are converted to `NA`: "b". 10 | x Unexpected values: "d" and "e". 11 | 12 | --- 13 | 14 | Code 15 | wrapped_to_fct(letters[1:5], levels = c("a", "c"), to_na = "b") 16 | Condition 17 | Error in `wrapped_to_fct()`: 18 | ! Each value of `val` must be in the expected levels. 19 | i Allowed levels: "a" and "c". 20 | i Values that are converted to `NA`: "b". 21 | x Unexpected values: "d" and "e". 22 | 23 | # to_fct() respects allow_null (#62) 24 | 25 | Code 26 | to_fct(given, allow_null = FALSE) 27 | Condition 28 | Error: 29 | ! `given` must not be . 30 | 31 | --- 32 | 33 | Code 34 | wrapped_to_fct(given, allow_null = FALSE) 35 | Condition 36 | Error in `wrapped_to_fct()`: 37 | ! `val` must not be . 38 | 39 | # to_fct() errors for things that can't be coerced (#62) 40 | 41 | Code 42 | to_fct(given) 43 | Condition 44 | Error: 45 | ! Can't coerce `given` to . 46 | 47 | --- 48 | 49 | Code 50 | wrapped_to_fct(given) 51 | Condition 52 | Error in `wrapped_to_fct()`: 53 | ! Can't coerce `val` to . 54 | 55 | --- 56 | 57 | Code 58 | to_fct(given) 59 | Condition 60 | Error: 61 | ! Can't coerce `given` to . 62 | 63 | --- 64 | 65 | Code 66 | wrapped_to_fct(given) 67 | Condition 68 | Error in `wrapped_to_fct()`: 69 | ! Can't coerce `val` to . 70 | 71 | --- 72 | 73 | Code 74 | to_fct(given) 75 | Condition 76 | Error: 77 | ! Can't coerce `given` to . 78 | 79 | --- 80 | 81 | Code 82 | wrapped_to_fct(given) 83 | Condition 84 | Error in `wrapped_to_fct()`: 85 | ! Can't coerce `val` to . 86 | 87 | # to_fct_scalar() provides informative error messages (#62) 88 | 89 | Code 90 | to_fct_scalar(given) 91 | Condition 92 | Error: 93 | ! `given` must be a single . 94 | x `given` has 26 values. 95 | 96 | --- 97 | 98 | Code 99 | wrapped_to_fct_scalar(given) 100 | Condition 101 | Error in `wrapped_to_fct_scalar()`: 102 | ! `val` must be a single . 103 | x `val` has 26 values. 104 | 105 | # to_fct_scalar respects allow_zero_length (#62) 106 | 107 | Code 108 | to_fct_scalar(given, allow_zero_length = FALSE) 109 | Condition 110 | Error: 111 | ! `given` must be a single . 112 | x `given` has no values. 113 | 114 | -------------------------------------------------------------------------------- /tests/testthat/test-are_dbl_ish.R: -------------------------------------------------------------------------------- 1 | test_that("are_dbl_ish() works for dbls (#23)", { 2 | expect_identical( 3 | are_dbl_ish(c(1.0, 2.1, NA, Inf, -Inf)), 4 | rep(TRUE, 5) 5 | ) 6 | }) 7 | 8 | test_that("are_dbl_ish() works for ints (#23)", { 9 | expect_identical(are_dbl_ish(1:10), rep(TRUE, 10)) 10 | }) 11 | 12 | test_that("are_dbl_ish() works for NULL (#23)", { 13 | expect_identical(are_dbl_ish(NULL), logical(0)) 14 | }) 15 | 16 | test_that("are_dbl_ish() works for logicals (#23)", { 17 | expect_identical(are_dbl_ish(c(TRUE, FALSE, NA)), rep(TRUE, 3)) 18 | }) 19 | 20 | test_that("are_dbl_ish() works for characters (#23)", { 21 | expect_identical( 22 | are_dbl_ish(c("1", "2.0", "Inf", NA)), 23 | c(TRUE, TRUE, TRUE, TRUE) 24 | ) 25 | expect_identical( 26 | are_dbl_ish(c("a", "")), 27 | c(FALSE, FALSE) 28 | ) 29 | }) 30 | 31 | test_that("are_dbl_ish() respects coerce_character (#23)", { 32 | expect_identical( 33 | are_dbl_ish(c("1", "2.0"), coerce_character = TRUE), 34 | c(TRUE, TRUE) 35 | ) 36 | expect_identical( 37 | are_dbl_ish(c("1", "2.0"), coerce_character = FALSE), 38 | c(FALSE, FALSE) 39 | ) 40 | }) 41 | 42 | test_that("are_dbl_ish() works for factors (#23)", { 43 | expect_identical(are_dbl_ish(factor(c(1, 2.2, NA))), rep(TRUE, 3)) 44 | expect_identical(are_dbl_ish(factor(c("a"))), c(FALSE)) 45 | }) 46 | 47 | test_that("are_dbl_ish() respects coerce_factor (#23)", { 48 | expect_identical( 49 | are_dbl_ish(factor(1:2), coerce_factor = TRUE), 50 | c(TRUE, TRUE) 51 | ) 52 | expect_identical( 53 | are_dbl_ish(factor(1:2), coerce_factor = FALSE), 54 | c(FALSE, FALSE) 55 | ) 56 | }) 57 | 58 | test_that("are_dbl_ish() works for complex (#23)", { 59 | expect_identical(are_dbl_ish(c(1 + 0i, 2.0 + 0i, NA)), rep(TRUE, 3)) 60 | expect_identical(are_dbl_ish(c(1 + 1i)), c(FALSE)) 61 | }) 62 | 63 | test_that("are_dbl_ish() works for lists (#23)", { 64 | expect_identical( 65 | are_dbl_ish(list(1, 2L, "3.3", NA, 4.0)), 66 | rep(TRUE, 5) 67 | ) 68 | expect_identical( 69 | are_dbl_ish(list("a", NULL, list(1))), 70 | c(FALSE, FALSE, TRUE) 71 | ) 72 | expect_identical( 73 | are_dbl_ish(list("a", NULL, list(1, 2))), 74 | c(FALSE, FALSE, FALSE) 75 | ) 76 | expect_identical( 77 | are_dbl_ish(list("a", NULL, 1)), 78 | c(FALSE, FALSE, TRUE) 79 | ) 80 | }) 81 | 82 | test_that("are_dbl_ish() returns FALSE for non-vectors (#23)", { 83 | expect_false(are_dbl_ish(mean)) 84 | }) 85 | 86 | test_that("is_dbl_ish() works (#23)", { 87 | expect_true(is_dbl_ish(1.0)) 88 | expect_true(is_dbl_ish(c(1, 2.0, NA))) 89 | expect_true(is_dbl_ish(NULL)) 90 | expect_true(is_dbl_ish(list(1, 2L, "3.3"))) 91 | 92 | expect_false(is_dbl_ish("a")) 93 | expect_false(is_dbl_ish(list(1, "a"))) 94 | }) 95 | 96 | test_that("are_double_ish() exists (#164)", { 97 | expect_no_error(are_double_ish()) 98 | }) 99 | 100 | test_that("is_double_ish() exists (#164)", { 101 | expect_no_error(is_double_ish(1)) 102 | }) 103 | -------------------------------------------------------------------------------- /R/utils.R: -------------------------------------------------------------------------------- 1 | #' A wrapper for `glue::glue` with custom delimiters 2 | #' 3 | #' This wrapper sets the `.open` and `.close` arguments of [glue::glue()] to `[` 4 | #' and `]`, respectively. This allows for safe use of glue interpolation 5 | #' within messages that will be processed by [cli::cli_abort()], which uses `{` 6 | #' and `}` for its own styling. 7 | #' 8 | #' @param ... Arguments passed on to [glue::glue()]. Usually expects unnamed 9 | #' arguments but named arguments other than `.envir`, `.open`, and `.close` 10 | #' are acceptable. 11 | #' @param env The environment in which to evaluate the expressions. 12 | #' 13 | #' @returns A character string with evaluated expressions. 14 | #' @keywords internal 15 | .glue2 <- function(..., env = caller_env()) { 16 | glue(..., .envir = env, .open = "[", .close = "]") 17 | } 18 | 19 | #' Escape curly braces for safe printing with cli 20 | #' 21 | #' Replaces single curly braces (`{`, `}`) with double curly braces (`{{`, 22 | #' `}}`) so that they are interpreted as literal characters by 23 | #' [cli::cli_abort()] and not as expressions to be evaluated. 24 | #' 25 | #' @param msg `(character)` The messages to escape. 26 | #' 27 | #' @returns The messages with curly braces escaped. 28 | #' @keywords internal 29 | .cli_escape <- function(msg) { 30 | msg <- gsub("{", "{{", msg, fixed = TRUE) 31 | gsub("}", "}}", msg, fixed = TRUE) 32 | } 33 | 34 | #' Wrap text in cli markup 35 | #' 36 | #' @param x `(character)` The string to wrap. 37 | #' @param tag `(character)` The cli class to apply (e.g., "val", "var"). 38 | #' 39 | #' @returns A character vector the same length as `x` with cli markup. 40 | #' @keywords internal 41 | .cli_mark <- function(x, tag) { 42 | paste0("{.", tag, " ", x, "}") 43 | } 44 | 45 | #' NULL-coalescing-like operator 46 | #' 47 | #' If the left-hand side is not `NULL`, returns the right-hand side. Otherwise, 48 | #' returns `NULL`. This is useful for guarding expressions that should only be 49 | #' executed if a value is not `NULL`. Meant to be similar to the `%||%` operator 50 | #' (which returns `y` if `x` is `NULL`). 51 | #' 52 | #' @param x The object to check for `NULL`. 53 | #' @param y The value to return if `x` is not `NULL`. 54 | #' 55 | #' @returns `NULL` or the value of `y`. 56 | #' @keywords internal 57 | `%&&%` <- function(x, y) { 58 | if (is.null(x)) { 59 | NULL 60 | } else { 61 | y 62 | } 63 | } 64 | 65 | #' Safely find failure locations in a vector 66 | #' 67 | #' Run `check_fn(x, check_value)` if `check_value` isn't `NULL`. 68 | #' 69 | #' @param x The vector to check. 70 | #' @param check_value The value to check against (e.g., a regex pattern). If 71 | #' `NULL`, the check is skipped. 72 | #' @param check_fn The function to use for checking. 73 | #' 74 | #' @returns An integer vector of failure locations, or `NULL` if there are no 75 | #' failures or the check is skipped. 76 | #' @keywords internal 77 | .find_failures <- function(x, check_value, check_fn) { 78 | failures <- check_value %&&% check_fn(x, check_value) 79 | 80 | if (any(failures)) { 81 | return(which(failures)) 82 | } 83 | 84 | return(NULL) 85 | } 86 | -------------------------------------------------------------------------------- /man/stabilize_arg.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/stabilize_arg.R 3 | \name{stabilize_arg} 4 | \alias{stabilize_arg} 5 | \alias{stabilize_arg_scalar} 6 | \title{Ensure an argument meets expectations} 7 | \usage{ 8 | stabilize_arg( 9 | x, 10 | ..., 11 | allow_null = TRUE, 12 | allow_na = TRUE, 13 | min_size = NULL, 14 | max_size = NULL, 15 | x_arg = caller_arg(x), 16 | call = caller_env(), 17 | x_class = object_type(x) 18 | ) 19 | 20 | stabilize_arg_scalar( 21 | x, 22 | ..., 23 | allow_null = TRUE, 24 | allow_zero_length = TRUE, 25 | allow_na = TRUE, 26 | x_arg = caller_arg(x), 27 | call = caller_env(), 28 | x_class = object_type(x) 29 | ) 30 | } 31 | \arguments{ 32 | \item{x}{The argument to stabilize.} 33 | 34 | \item{...}{Arguments passed to methods.} 35 | 36 | \item{allow_null}{\verb{(length-1 logical)} Is NULL an acceptable value?} 37 | 38 | \item{allow_na}{\verb{(length-1 logical)} Are NA values ok?} 39 | 40 | \item{min_size}{\verb{(length-1 integer)} The minimum size of the object. Object 41 | size will be tested using \code{\link[vctrs:vec_size]{vctrs::vec_size()}}.} 42 | 43 | \item{max_size}{\verb{(length-1 integer)} The maximum size of the object. Object 44 | size will be tested using \code{\link[vctrs:vec_size]{vctrs::vec_size()}}.} 45 | 46 | \item{x_arg}{\verb{(length-1 character)} An argument name for x. The automatic 47 | value will work in most cases, or pass it through from higher-level 48 | functions to make error messages clearer in unexported functions.} 49 | 50 | \item{call}{\code{(environment)} The execution environment to mention as the 51 | source of error messages.} 52 | 53 | \item{x_class}{\verb{(length-1 character)} The class name of \code{x} to use in error 54 | messages. Use this if you remove a special class from \code{x} before checking 55 | its coercion, but want the error message to match the original class.} 56 | 57 | \item{allow_zero_length}{\verb{(length-1 logical)} Are zero-length vectors 58 | acceptable?} 59 | } 60 | \value{ 61 | \code{x}, unless one of the checks fails. 62 | } 63 | \description{ 64 | \code{stabilize_arg()} is used by other functions such as \code{\link[=stabilize_int]{stabilize_int()}}. Use 65 | \code{stabilize_arg()} if the type-specific functions will not work for your use 66 | case, but you would still like to check things like size or whether the 67 | argument is NULL. 68 | 69 | \code{stabilize_arg_scalar()} is optimized to check for length-1 vectors. 70 | } 71 | \examples{ 72 | wrapper <- function(this_arg, ...) { 73 | stabilize_arg(this_arg, ...) 74 | } 75 | wrapper(1) 76 | wrapper(NULL) 77 | wrapper(NA) 78 | try(wrapper(NULL, allow_null = FALSE)) 79 | try(wrapper(NA, allow_na = FALSE)) 80 | try(wrapper(1, min_size = 2)) 81 | try(wrapper(1:10, max_size = 5)) 82 | stabilize_arg_scalar("a") 83 | stabilize_arg_scalar(1L) 84 | try(stabilize_arg_scalar(1:10)) 85 | } 86 | \seealso{ 87 | Other stabilization functions: 88 | \code{\link{stabilize_chr}()}, 89 | \code{\link{stabilize_dbl}()}, 90 | \code{\link{stabilize_fct}()}, 91 | \code{\link{stabilize_int}()}, 92 | \code{\link{stabilize_lgl}()} 93 | } 94 | \concept{stabilization functions} 95 | -------------------------------------------------------------------------------- /tests/testthat/test-stabilize_lgl.R: -------------------------------------------------------------------------------- 1 | test_that("stabilize_lgl() works on happy path (#28)", { 2 | given <- TRUE 3 | expect_true(stabilize_lgl(given)) 4 | given <- FALSE 5 | expect_false(stabilize_lgl(given)) 6 | 7 | given <- c("TRUE", "FALSE", "true", "fALSE") 8 | expect_identical( 9 | stabilize_lgl(given), 10 | c(TRUE, FALSE, TRUE, FALSE) 11 | ) 12 | }) 13 | 14 | test_that("stabilize_lgl() checks NAs (#28)", { 15 | given <- c("TRUE", NA, "true", "fALSE") 16 | expect_identical( 17 | stabilize_lgl(given), 18 | c(TRUE, NA, TRUE, FALSE) 19 | ) 20 | expect_error( 21 | stabilize_lgl(given, allow_na = FALSE), 22 | class = .compile_dash("stbl", "error", "bad_na") 23 | ) 24 | expect_snapshot( 25 | stabilize_lgl(given, allow_na = FALSE), 26 | error = TRUE 27 | ) 28 | expect_snapshot( 29 | wrapped_stabilize_lgl(given, allow_na = FALSE), 30 | error = TRUE 31 | ) 32 | }) 33 | 34 | test_that("stabilize_lgl() checks min_size (#28)", { 35 | given <- c("TRUE", NA, "true", "fALSE") 36 | expect_error( 37 | stabilize_lgl(given, min_size = 5), 38 | class = .compile_dash("stbl", "error", "size_too_small") 39 | ) 40 | expect_snapshot( 41 | stabilize_lgl(given, min_size = 5), 42 | error = TRUE 43 | ) 44 | expect_snapshot( 45 | wrapped_stabilize_lgl(given, min_size = 5), 46 | error = TRUE 47 | ) 48 | }) 49 | 50 | test_that("stabilize_lgl() checks max_size (#28)", { 51 | given <- c("TRUE", NA, "true", "fALSE") 52 | expect_error( 53 | stabilize_lgl(given, max_size = 3), 54 | class = .compile_dash("stbl", "error", "size_too_large") 55 | ) 56 | expect_snapshot( 57 | stabilize_lgl(given, max_size = 3), 58 | error = TRUE 59 | ) 60 | expect_snapshot( 61 | wrapped_stabilize_lgl(given, max_size = 3), 62 | error = TRUE 63 | ) 64 | }) 65 | 66 | test_that("stabilize_lgl_scalar() allows length-1 lgls through (#28)", { 67 | expect_true(stabilize_lgl_scalar(TRUE)) 68 | }) 69 | 70 | test_that("stabilize_lgl_scalar() errors on non-scalars (#28)", { 71 | given <- c(TRUE, FALSE, TRUE) 72 | expect_error( 73 | stabilize_lgl_scalar(given), 74 | class = .compile_dash("stbl", "error", "non_scalar") 75 | ) 76 | expect_snapshot( 77 | stabilize_lgl_scalar(given), 78 | error = TRUE 79 | ) 80 | expect_snapshot( 81 | wrapped_stabilize_lgl_scalar(given), 82 | error = TRUE 83 | ) 84 | }) 85 | 86 | test_that("stabilise_lgl() exists (#167)", { 87 | expect_no_error(stabilise_lgl(TRUE)) 88 | }) 89 | 90 | test_that("stabilize_logical() exists (#164)", { 91 | expect_no_error(stabilize_logical(TRUE)) 92 | }) 93 | 94 | test_that("stabilise_logical() exists (#167)", { 95 | expect_no_error(stabilise_logical(TRUE)) 96 | }) 97 | 98 | test_that("stabilise_lgl_scalar() exists (#167)", { 99 | expect_no_error(stabilise_lgl_scalar(TRUE)) 100 | }) 101 | 102 | test_that("stabilize_logical_scalar() exists (#164)", { 103 | expect_no_error(stabilize_logical_scalar(TRUE)) 104 | }) 105 | 106 | test_that("stabilise_logical_scalar() exists (#167)", { 107 | expect_no_error(stabilize_logical_scalar(TRUE)) 108 | }) 109 | -------------------------------------------------------------------------------- /R/to_lgl.R: -------------------------------------------------------------------------------- 1 | #' @export 2 | #' @rdname stabilize_lgl 3 | to_lgl <- function( 4 | x, 5 | ..., 6 | x_arg = caller_arg(x), 7 | call = caller_env(), 8 | x_class = object_type(x) 9 | ) { 10 | UseMethod("to_lgl") 11 | } 12 | 13 | #' @export 14 | #' @rdname stabilize_lgl 15 | to_logical <- to_lgl 16 | 17 | #' @export 18 | to_lgl.logical <- function(x, ...) { 19 | return(x) 20 | } 21 | 22 | #' @export 23 | #' @rdname stabilize_lgl 24 | to_lgl.NULL <- function( 25 | x, 26 | ..., 27 | allow_null = TRUE, 28 | x_arg = caller_arg(x), 29 | call = caller_env() 30 | ) { 31 | .to_null(x, allow_null = allow_null, x_arg = x_arg, call = call) 32 | } 33 | 34 | #' @export 35 | to_lgl.numeric <- function(x, ..., x_arg = caller_arg(x), call = caller_env()) { 36 | return(as.logical(x)) 37 | } 38 | 39 | #' @export 40 | to_lgl.character <- function( 41 | x, 42 | ..., 43 | x_arg = caller_arg(x), 44 | call = caller_env(), 45 | x_class = object_type(x) 46 | ) { 47 | failures <- !are_lgl_ish.character(x) 48 | .check_cast_failures( 49 | failures, 50 | x_class, 51 | logical(), 52 | "incompatible values", 53 | x_arg, 54 | call 55 | ) 56 | 57 | res <- as.logical(toupper(x)) 58 | not_coerced <- is.na(res) & !is.na(x) 59 | res[not_coerced] <- as.logical(to_dbl( 60 | x[not_coerced], 61 | x_arg = x_arg, 62 | call = call, 63 | x_class = x_class, 64 | ... 65 | )) 66 | 67 | return(res) 68 | } 69 | 70 | #' @export 71 | to_lgl.factor <- function( 72 | x, 73 | ..., 74 | x_arg = caller_arg(x), 75 | call = caller_env(), 76 | x_class = object_type(x) 77 | ) { 78 | return( 79 | to_lgl.character( 80 | as.character(x), 81 | ..., 82 | x_arg = x_arg, 83 | call = call, 84 | x_class = x_class 85 | ) 86 | ) 87 | } 88 | 89 | #' @export 90 | to_lgl.list <- function( 91 | x, 92 | ..., 93 | x_arg = caller_arg(x), 94 | call = caller_env(), 95 | x_class = object_type(x) 96 | ) { 97 | .to_cls_from_list( 98 | x, 99 | to_lgl, 100 | "logical", 101 | ..., 102 | x_arg = x_arg, 103 | call = call, 104 | x_class = x_class 105 | ) 106 | } 107 | 108 | #' @export 109 | to_lgl.default <- function( 110 | x, 111 | ..., 112 | x_arg = caller_arg(x), 113 | call = caller_env(), 114 | x_class = object_type(x) 115 | ) { 116 | .stop_cant_coerce( 117 | from_class = x_class, 118 | to_class = "logical", 119 | x_arg = x_arg, 120 | call = call 121 | ) 122 | } 123 | 124 | #' @export 125 | #' @rdname stabilize_lgl 126 | to_lgl_scalar <- function( 127 | x, 128 | ..., 129 | allow_null = TRUE, 130 | allow_zero_length = TRUE, 131 | x_arg = caller_arg(x), 132 | call = caller_env(), 133 | x_class = object_type(x) 134 | ) { 135 | .to_cls_scalar( 136 | x, 137 | is_rlang_cls_scalar = is_scalar_logical, 138 | to_cls_fn = to_lgl, 139 | to_cls_args = list(...), 140 | allow_null = allow_null, 141 | allow_zero_length = allow_zero_length, 142 | x_arg = x_arg, 143 | call = call, 144 | x_class = x_class 145 | ) 146 | } 147 | 148 | #' @export 149 | #' @rdname stabilize_lgl 150 | to_logical_scalar <- to_lgl_scalar 151 | -------------------------------------------------------------------------------- /man/specify_fct.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/specify_cls.R 3 | \name{specify_fct} 4 | \alias{specify_fct} 5 | \alias{specify_fct_scalar} 6 | \alias{specify_factor} 7 | \alias{specify_factor_scalar} 8 | \title{Create a specified factor stabilizer function} 9 | \usage{ 10 | specify_fct( 11 | allow_null = TRUE, 12 | allow_na = TRUE, 13 | min_size = NULL, 14 | max_size = NULL, 15 | levels = NULL, 16 | to_na = character() 17 | ) 18 | 19 | specify_fct_scalar( 20 | allow_null = TRUE, 21 | allow_zero_length = TRUE, 22 | allow_na = TRUE, 23 | levels = NULL, 24 | to_na = character() 25 | ) 26 | 27 | specify_factor( 28 | allow_null = TRUE, 29 | allow_na = TRUE, 30 | min_size = NULL, 31 | max_size = NULL, 32 | levels = NULL, 33 | to_na = character() 34 | ) 35 | 36 | specify_factor_scalar( 37 | allow_null = TRUE, 38 | allow_zero_length = TRUE, 39 | allow_na = TRUE, 40 | levels = NULL, 41 | to_na = character() 42 | ) 43 | } 44 | \arguments{ 45 | \item{allow_null}{\verb{(length-1 logical)} Is NULL an acceptable value?} 46 | 47 | \item{allow_na}{\verb{(length-1 logical)} Are NA values ok?} 48 | 49 | \item{min_size}{\verb{(length-1 integer)} The minimum size of the object. Object 50 | size will be tested using \code{\link[vctrs:vec_size]{vctrs::vec_size()}}.} 51 | 52 | \item{max_size}{\verb{(length-1 integer)} The maximum size of the object. Object 53 | size will be tested using \code{\link[vctrs:vec_size]{vctrs::vec_size()}}.} 54 | 55 | \item{levels}{\code{(character)} Expected levels. If \code{NULL} (default), the levels 56 | will be computed by \code{\link[base:factor]{base::factor()}}.} 57 | 58 | \item{to_na}{\code{(character)} Values to convert to \code{NA}.} 59 | 60 | \item{allow_zero_length}{\verb{(length-1 logical)} Are zero-length vectors 61 | acceptable?} 62 | } 63 | \value{ 64 | A function of class \code{"stbl_specified_fn"} that calls 65 | \code{\link[=stabilize_fct]{stabilize_fct()}} or \code{\link[=stabilize_fct_scalar]{stabilize_fct_scalar()}} with the provided arguments. 66 | The generated function will also accept \code{...} for additional arguments to 67 | pass to \code{stabilize_fct()} or \code{stabilize_fct_scalar()}. You can copy/paste 68 | the body of the resulting function if you want to provide additional 69 | context or functionality. 70 | } 71 | \description{ 72 | \code{specify_fct()} creates a function that will call \code{\link[=stabilize_fct]{stabilize_fct()}} with the 73 | provided arguments. \code{specify_fct_scalar()} creates a function that will call 74 | \code{\link[=stabilize_fct_scalar]{stabilize_fct_scalar()}} with the provided arguments. \code{specify_factor()} is a 75 | synonym of \code{specify_fct()}, and \code{specify_factor_scalar()} is a synonym of 76 | \code{specify_fct_scalar()}. 77 | } 78 | \examples{ 79 | stabilize_lowercase_letter <- specify_fct(levels = letters) 80 | stabilize_lowercase_letter(c("s", "t", "b", "l")) 81 | try(stabilize_lowercase_letter("A")) 82 | } 83 | \seealso{ 84 | Other factor functions: 85 | \code{\link{are_fct_ish}()}, 86 | \code{\link{stabilize_fct}()} 87 | 88 | Other specification functions: 89 | \code{\link{specify_chr}()}, 90 | \code{\link{specify_dbl}()}, 91 | \code{\link{specify_int}()}, 92 | \code{\link{specify_lgl}()} 93 | } 94 | \concept{factor functions} 95 | \concept{specification functions} 96 | -------------------------------------------------------------------------------- /.github/workflows/rhub.yaml: -------------------------------------------------------------------------------- 1 | # R-hub's generic GitHub Actions workflow file. It's canonical location is at 2 | # https://github.com/r-hub/actions/blob/v1/workflows/rhub.yaml 3 | # You can update this file to a newer version using the rhub2 package: 4 | # 5 | # rhub::rhub_setup() 6 | # 7 | # It is unlikely that you need to modify this file manually. 8 | 9 | name: R-hub 10 | run-name: "${{ github.event.inputs.id }}: ${{ github.event.inputs.name || format('Manually run by {0}', github.triggering_actor) }}" 11 | 12 | on: 13 | workflow_dispatch: 14 | inputs: 15 | config: 16 | description: 'A comma separated list of R-hub platforms to use.' 17 | type: string 18 | default: 'linux,windows,macos' 19 | name: 20 | description: 'Run name. You can leave this empty now.' 21 | type: string 22 | id: 23 | description: 'Unique ID. You can leave this empty now.' 24 | type: string 25 | 26 | jobs: 27 | 28 | setup: 29 | runs-on: ubuntu-latest 30 | outputs: 31 | containers: ${{ steps.rhub-setup.outputs.containers }} 32 | platforms: ${{ steps.rhub-setup.outputs.platforms }} 33 | 34 | steps: 35 | # NO NEED TO CHECKOUT HERE 36 | - uses: r-hub/actions/setup@v1 37 | with: 38 | config: ${{ github.event.inputs.config }} 39 | id: rhub-setup 40 | 41 | linux-containers: 42 | needs: setup 43 | if: ${{ needs.setup.outputs.containers != '[]' }} 44 | runs-on: ubuntu-latest 45 | name: ${{ matrix.config.label }} 46 | strategy: 47 | fail-fast: false 48 | matrix: 49 | config: ${{ fromJson(needs.setup.outputs.containers) }} 50 | container: 51 | image: ${{ matrix.config.container }} 52 | 53 | steps: 54 | - uses: r-hub/actions/checkout@v1 55 | - uses: r-hub/actions/platform-info@v1 56 | with: 57 | token: ${{ secrets.RHUB_TOKEN }} 58 | job-config: ${{ matrix.config.job-config }} 59 | - uses: r-hub/actions/setup-deps@v1 60 | with: 61 | token: ${{ secrets.RHUB_TOKEN }} 62 | job-config: ${{ matrix.config.job-config }} 63 | - uses: r-hub/actions/run-check@v1 64 | with: 65 | token: ${{ secrets.RHUB_TOKEN }} 66 | job-config: ${{ matrix.config.job-config }} 67 | 68 | other-platforms: 69 | needs: setup 70 | if: ${{ needs.setup.outputs.platforms != '[]' }} 71 | runs-on: ${{ matrix.config.os }} 72 | name: ${{ matrix.config.label }} 73 | strategy: 74 | fail-fast: false 75 | matrix: 76 | config: ${{ fromJson(needs.setup.outputs.platforms) }} 77 | 78 | steps: 79 | - uses: r-hub/actions/checkout@v1 80 | - uses: r-hub/actions/setup-r@v1 81 | with: 82 | job-config: ${{ matrix.config.job-config }} 83 | token: ${{ secrets.RHUB_TOKEN }} 84 | - uses: r-hub/actions/platform-info@v1 85 | with: 86 | token: ${{ secrets.RHUB_TOKEN }} 87 | job-config: ${{ matrix.config.job-config }} 88 | - uses: r-hub/actions/setup-deps@v1 89 | with: 90 | job-config: ${{ matrix.config.job-config }} 91 | token: ${{ secrets.RHUB_TOKEN }} 92 | - uses: r-hub/actions/run-check@v1 93 | with: 94 | job-config: ${{ matrix.config.job-config }} 95 | token: ${{ secrets.RHUB_TOKEN }} 96 | -------------------------------------------------------------------------------- /NEWS.md: -------------------------------------------------------------------------------- 1 | # stbl (development version) 2 | 3 | ## New features 4 | 5 | * New condition functions: `pkg_abort()` throws errors with a standardized, opinionated collection of classes, and `expect_pkg_error_classes()` checks that an error with the expected set of classes is thrown (#136). 6 | * New specification functions: `specify_*()` creates a "stbl-specified function" (class `"stbl_specified_fn"`), a call to the corresponding `stabilize_*()` function with arguments pre-filled. For example, `stabilize_email <- specify_chr(regex = "^[^@]+@[^@]+\\.[^@]+$")` creates a `stabilize_email()` function that calls `stabilize_chr()` with `regex = "^[^@]+@[^@]+\\.[^@]+$"`, which could then be used to stabilize email addresses (#147, #148, #149, #150, #151). 7 | * New function `to_lst()` (and synonym `to_list()`) to coerce an object to a list, with conditional checks for `NULL` and functions (#157, #166). 8 | * New synonym functions for all class functions. For example `to_character()` is now a synonym for `to_chr()`, and `specify_logical()` is a synonym for `specify_lgl()` (#164). In addition, `stabilise_*()` synonyms have been added for all `stabilize_*()` functions (#167). 9 | 10 | ## Potential breaking changes 11 | 12 | * Several conditions that formerly included a subclass of "stbl-error-must" no longer include that subclass. This only occurs when "stbl-error-must" was not the most specific subclass (i.e., when a more specific subclass was already included), and therefore should not impact most if any code (#136). 13 | 14 | ## Other changes 15 | 16 | * Revised the "Getting started with stbl" vignette to clarify what happens at each step (#139, #143, #144). 17 | * Clarified error messages (#176, #177). 18 | 19 | # stbl 0.2.0 20 | 21 | ## New features 22 | 23 | * New predicate functions check if an object can be safely coerced to a specific type. The `is_*_ish()` family (`is_chr_ish()`, `is_dbl_ish()`, `is_fct_ish()`, `is_int_ish()`, and `is_lgl_ish()`) checks the entire object at once. The `are_*_ish()` family (`are_chr_ish()`, `are_dbl_ish()`, `are_fct_ish()`, `are_int_ish()`, and `are_lgl_ish()`) checks each element of a vector individually (#23, #93). 24 | * New functions for working with doubles are available: `to_dbl()`, `to_dbl_scalar()`, `stabilize_dbl()`, and `stabilize_dbl_scalar()` (#23). 25 | * `stabilize_chr()` now accepts patterns from `stringr::regex()`, `stringr::fixed()`, and `stringr::coll()` (#87), and can generate more informative error messages for regex failures via the new `regex_must_match()` and `regex_must_not_match()` helper functions (#52, #85, #86, #89). 26 | 27 | ## Minor improvements and fixes 28 | 29 | * Error messages are now clearer and more standardized throughout the package (#95). 30 | * `to_*()` functions now consistently flatten list-like inputs when no information would be lost in the process (#128). 31 | * `to_fct()` now lists the allowed values in its error message when a value is not in the expected set, making it easier to debug (#67). 32 | * `to_lgl()` now coerces character representations of numbers (e.g., "0" and "1") to `FALSE` and `TRUE` respectively (#30). 33 | 34 | ## Documentation 35 | 36 | * The purpose of and vision for this package are now more clearly described in documentation (#56, #77). 37 | * New `vignette("stbl")` provides an overview of the package and its functions (#42). 38 | 39 | # stbl 0.1.1 40 | 41 | * Update formatting in DESCRIPTION and examples. 42 | 43 | # stbl 0.1.0 44 | 45 | * Initial CRAN submission. 46 | -------------------------------------------------------------------------------- /tests/testthat/test-are_int_ish.R: -------------------------------------------------------------------------------- 1 | test_that("are_int_ish() works for ints (#93)", { 2 | expect_identical(are_int_ish(1:10), rep(TRUE, 10)) 3 | }) 4 | 5 | test_that("are_int_ish() works for NULL (#93)", { 6 | expect_identical(are_int_ish(NULL), logical(0)) 7 | }) 8 | 9 | test_that("are_int_ish() works for logicals (#93)", { 10 | expect_identical(are_int_ish(c(TRUE, FALSE, NA)), rep(TRUE, 3)) 11 | }) 12 | 13 | test_that("are_int_ish() works for doubles (#93)", { 14 | expect_identical(are_int_ish(c(1, 2.0, NA)), c(TRUE, TRUE, TRUE)) 15 | expect_identical(are_int_ish(c(1.1, Inf, -Inf)), c(FALSE, FALSE, FALSE)) 16 | }) 17 | 18 | test_that("are_int_ish() works for characters (#93)", { 19 | expect_identical( 20 | are_int_ish(c("1", "2.0", NA)), 21 | c(TRUE, TRUE, TRUE) 22 | ) 23 | expect_identical( 24 | are_int_ish(c("1.1", "a", "")), 25 | c(FALSE, FALSE, FALSE) 26 | ) 27 | }) 28 | 29 | test_that("are_int_ish() respects coerce_character (#93)", { 30 | expect_identical( 31 | are_int_ish(c("1", "2.0"), coerce_character = TRUE), 32 | c(TRUE, TRUE) 33 | ) 34 | expect_identical( 35 | are_int_ish(c("1", "2.0"), coerce_character = FALSE), 36 | c(FALSE, FALSE) 37 | ) 38 | }) 39 | 40 | test_that("are_int_ish() works for factors (#93)", { 41 | expect_identical(are_int_ish(factor(c(1, 2, NA))), rep(TRUE, 3)) 42 | expect_identical(are_int_ish(factor(c("1.1", "a"))), c(FALSE, FALSE)) 43 | }) 44 | 45 | test_that("are_int_ish() respects coerce_factor (#93)", { 46 | expect_identical( 47 | are_int_ish(factor(1:2), coerce_factor = TRUE), 48 | c(TRUE, TRUE) 49 | ) 50 | expect_identical( 51 | are_int_ish(factor(1:2), coerce_factor = FALSE), 52 | c(FALSE, FALSE) 53 | ) 54 | }) 55 | 56 | test_that("are_int_ish() works for complex (#93)", { 57 | expect_identical(are_int_ish(c(1 + 0i, 2.0 + 0i, NA)), rep(TRUE, 3)) 58 | expect_identical(are_int_ish(c(1 + 1i, 1.1 + 0i)), c(FALSE, FALSE)) 59 | }) 60 | 61 | test_that("are_int_ish() works for lists (#93, #128)", { 62 | expect_identical( 63 | are_int_ish(list(1, 2L, "3", NA, 4.0)), 64 | rep(TRUE, 5) 65 | ) 66 | expect_identical( 67 | are_int_ish(list(1.1, "a", NULL, list(1))), 68 | c(FALSE, FALSE, FALSE, TRUE) 69 | ) 70 | expect_identical(are_int_ish(list(list(1), 2)), c(TRUE, TRUE)) 71 | expect_identical(are_int_ish(list(1, 1:5)), c(TRUE, FALSE)) 72 | }) 73 | 74 | test_that("are_int_ish() returns FALSE for non-vectors (#93)", { 75 | expect_false(are_int_ish(mean)) 76 | }) 77 | 78 | test_that("are_int_ish() returns FALSE for unhandled S3 objects (#93)", { 79 | expect_false(is_int_ish(Sys.Date())) 80 | expect_identical( 81 | are_int_ish(as.Date(c("2025-01-01", "2025-01-02"))), 82 | c(FALSE, FALSE) 83 | ) 84 | expect_identical( 85 | are_int_ish(list(1L, Sys.Date())), 86 | c(TRUE, FALSE) 87 | ) 88 | }) 89 | 90 | test_that("is_int_ish() works (#93)", { 91 | expect_true(is_int_ish(1L)) 92 | expect_true(is_int_ish(c(1, 2.0, NA))) 93 | expect_true(is_int_ish(NULL)) 94 | expect_true(is_int_ish(list(1, 2L, "3"))) 95 | 96 | expect_false(is_int_ish(1.1)) 97 | expect_false(is_int_ish("a")) 98 | expect_false(is_int_ish(list(1, "a"))) 99 | }) 100 | 101 | test_that("are_integer_ish() exists (#164)", { 102 | expect_no_error(are_integer_ish()) 103 | }) 104 | 105 | test_that("is_integer_ish() exists (#164)", { 106 | expect_no_error(is_integer_ish(1)) 107 | }) 108 | -------------------------------------------------------------------------------- /R/are_dbl_ish.R: -------------------------------------------------------------------------------- 1 | #' Check if an object can be safely coerced to double 2 | #' 3 | #' @description `are_dbl_ish()` is a vectorized predicate function that checks 4 | #' whether each element of its input can be safely coerced to a double vector. 5 | #' `are_double_ish()` is a synonym of `are_dbl_ish()`. 6 | #' 7 | #' `is_dbl_ish()` is a scalar predicate function that checks if all elements of 8 | #' its input can be safely coerced to a double vector. `is_double_ish()` is a 9 | #' synonym of `is_dbl_ish()`. 10 | #' 11 | #' @inheritParams .shared-params-check 12 | #' @inheritParams .shared-params 13 | #' 14 | #' @returns `are_dbl_ish()` returns a logical vector with the same length as the 15 | #' input. `is_dbl_ish()` returns a `length-1 logical` (`TRUE` or `FALSE`) for 16 | #' the entire vector. 17 | #' @family double functions 18 | #' @family check functions 19 | #' @export 20 | #' 21 | #' @examples 22 | #' are_dbl_ish(c(1.0, 2.2, 3.14)) 23 | #' is_dbl_ish(c(1.0, 2.2, 3.14)) 24 | #' 25 | #' are_dbl_ish(1:3) 26 | #' is_dbl_ish(1:3) 27 | #' 28 | #' are_dbl_ish(c("1.1", "2.2", NA)) 29 | #' is_dbl_ish(c("1.1", "2.2", NA)) 30 | #' 31 | #' are_dbl_ish(c("a", "1.0")) 32 | #' is_dbl_ish(c("a", "1.0")) 33 | #' 34 | #' are_dbl_ish(list(1, "2.2", "c")) 35 | #' is_dbl_ish(list(1, "2.2", "c")) 36 | #' 37 | #' are_dbl_ish(c(1 + 1i, 1 + 0i, NA)) 38 | #' is_dbl_ish(c(1 + 1i, 1 + 0i, NA)) 39 | are_dbl_ish <- function(x, ...) { 40 | UseMethod("are_dbl_ish") 41 | } 42 | 43 | #' @export 44 | #' @rdname are_dbl_ish 45 | is_dbl_ish <- function(x, ...) { 46 | all(are_dbl_ish(x, ...)) 47 | } 48 | 49 | #' @export 50 | #' @rdname are_dbl_ish 51 | are_double_ish <- are_dbl_ish 52 | 53 | #' @export 54 | #' @rdname are_dbl_ish 55 | is_double_ish <- is_dbl_ish 56 | 57 | #' @export 58 | are_dbl_ish.double <- function(x, ...) { 59 | rep(TRUE, length(x)) 60 | } 61 | 62 | #' @export 63 | are_dbl_ish.integer <- function(x, ...) { 64 | rep(TRUE, length(x)) 65 | } 66 | 67 | #' @export 68 | are_dbl_ish.NULL <- function(x, ...) { 69 | logical(0) 70 | } 71 | 72 | #' @export 73 | are_dbl_ish.logical <- function(x, ...) { 74 | rep(TRUE, length(x)) 75 | } 76 | 77 | #' @export 78 | #' @rdname are_dbl_ish 79 | are_dbl_ish.character <- function(x, ..., coerce_character = TRUE) { 80 | if (!to_lgl_scalar(coerce_character)) { 81 | return(rep(FALSE, length(x))) 82 | } 83 | 84 | !.are_not_dbl_ish_chr(x) 85 | } 86 | 87 | #' Check for character to double coercion failures 88 | #' 89 | #' @inheritParams .shared-params-check 90 | #' @returns A logical vector where `TRUE` indicates a failure. 91 | #' @keywords internal 92 | .are_not_dbl_ish_chr <- function(x) { 93 | cast_dbl <- suppressWarnings(as.double(x)) 94 | x_na <- is.na(x) 95 | xor(x_na, is.na(cast_dbl)) 96 | } 97 | 98 | 99 | #' @export 100 | #' @rdname are_dbl_ish 101 | are_dbl_ish.factor <- function(x, ..., coerce_factor = TRUE) { 102 | if (!to_lgl_scalar(coerce_factor)) { 103 | return(rep(FALSE, length(x))) 104 | } 105 | are_dbl_ish(as.character(x), ...) 106 | } 107 | 108 | #' @export 109 | are_dbl_ish.complex <- function(x, ...) { 110 | # The imaginary part must be zero. The real part is already a double. 111 | is.na(x) | (Im(x) == 0) 112 | } 113 | 114 | #' @export 115 | #' @rdname are_dbl_ish 116 | are_dbl_ish.default <- function(x, ..., depth = 1) { 117 | if (!rlang::is_vector(x) || depth != 1) { 118 | return(FALSE) 119 | } 120 | .elements_are_cls_ish(x, are_dbl_ish, ...) 121 | } 122 | -------------------------------------------------------------------------------- /tests/testthat/_snaps/to_dbl.md: -------------------------------------------------------------------------------- 1 | # to_dbl() respects allow_null (#23) 2 | 3 | Code 4 | to_dbl(given, allow_null = FALSE) 5 | Condition 6 | Error: 7 | ! `given` must not be . 8 | 9 | --- 10 | 11 | Code 12 | wrapped_to_dbl(given, allow_null = FALSE) 13 | Condition 14 | Error in `wrapped_to_dbl()`: 15 | ! `val` must not be . 16 | 17 | # to_dbl() respects coerce_character (#23) 18 | 19 | Code 20 | to_dbl(given, coerce_character = FALSE) 21 | Condition 22 | Error: 23 | ! Can't coerce `given` to . 24 | 25 | --- 26 | 27 | Code 28 | wrapped_to_dbl(given, coerce_character = FALSE) 29 | Condition 30 | Error in `wrapped_to_dbl()`: 31 | ! Can't coerce `val` to . 32 | 33 | # to_dbl() errors informatively for bad chrs (#23) 34 | 35 | Code 36 | to_dbl(given) 37 | Condition 38 | Error: 39 | ! `given` must be coercible to 40 | x Can't convert some values due to incompatible values. 41 | * Locations: 2 42 | 43 | --- 44 | 45 | Code 46 | wrapped_to_dbl(given) 47 | Condition 48 | Error in `wrapped_to_dbl()`: 49 | ! `val` must be coercible to 50 | x Can't convert some values due to incompatible values. 51 | * Locations: 2 52 | 53 | # to_dbl() errors informatively for bad complexes (#23) 54 | 55 | Code 56 | to_dbl(given) 57 | Condition 58 | Error: 59 | ! `given` must be coercible to 60 | x Can't convert some values due to non-zero complex components. 61 | * Locations: 1 62 | 63 | --- 64 | 65 | Code 66 | wrapped_to_dbl(given) 67 | Condition 68 | Error in `wrapped_to_dbl()`: 69 | ! `val` must be coercible to 70 | x Can't convert some values due to non-zero complex components. 71 | * Locations: 1 72 | 73 | # to_dbl() respects coerce_factor (#23) 74 | 75 | Code 76 | to_dbl(given, coerce_factor = FALSE) 77 | Condition 78 | Error: 79 | ! Can't coerce `given` to . 80 | 81 | --- 82 | 83 | Code 84 | wrapped_to_dbl(given, coerce_factor = FALSE) 85 | Condition 86 | Error in `wrapped_to_dbl()`: 87 | ! Can't coerce `val` to . 88 | 89 | # to_dbl() errors informatively for bad factors (#23) 90 | 91 | Code 92 | to_dbl(given) 93 | Condition 94 | Error: 95 | ! `given` must be coercible to 96 | x Can't convert some values due to incompatible values. 97 | * Locations: 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, ..., 25, and 26 98 | 99 | --- 100 | 101 | Code 102 | wrapped_to_dbl(given) 103 | Condition 104 | Error in `wrapped_to_dbl()`: 105 | ! `val` must be coercible to 106 | x Can't convert some values due to incompatible values. 107 | * Locations: 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, ..., 25, and 26 108 | 109 | # to_dbl_scalar() provides informative error messages (#23) 110 | 111 | Code 112 | to_dbl_scalar(given) 113 | Condition 114 | Error: 115 | ! `given` must be a single . 116 | x `given` has 2 values. 117 | 118 | --- 119 | 120 | Code 121 | wrapped_to_dbl_scalar(given) 122 | Condition 123 | Error in `wrapped_to_dbl_scalar()`: 124 | ! `val` must be a single . 125 | x `val` has 2 values. 126 | 127 | -------------------------------------------------------------------------------- /R/are_fct_ish.R: -------------------------------------------------------------------------------- 1 | #' Check if an object can be safely coerced to a factor 2 | #' 3 | #' @description `are_fct_ish()` is a vectorized predicate function that checks 4 | #' whether each element of its input can be safely coerced to a factor. 5 | #' `are_factor_ish()` is a synonym of `are_fct_ish()`. 6 | #' 7 | #' `is_fct_ish()` is a scalar predicate function that checks if all elements 8 | #' of its input can be safely coerced to a factor. `is_factor_ish()` is a 9 | #' synonym of `is_fct_ish()`. 10 | #' 11 | #' @inheritParams .shared-params-check 12 | #' @inheritParams .shared-params 13 | #' 14 | #' @returns `are_fct_ish()` returns a logical vector with the same length as the 15 | #' input. `is_fct_ish()` returns a `length-1 logical` (`TRUE` or `FALSE`) for 16 | #' the entire vector. 17 | #' @family factor functions 18 | #' @family check functions 19 | #' @export 20 | #' 21 | #' @examples 22 | #' # When `levels` is `NULL`, atomic vectors are fct_ish, but nested lists are not. 23 | #' are_fct_ish(c("a", 1, NA)) 24 | #' is_fct_ish(c("a", 1, NA)) 25 | #' are_fct_ish(list("a", list("b", "c"))) 26 | #' is_fct_ish(list("a", list("b", "c"))) 27 | #' 28 | #' # When `levels` is specified, values must be in `levels` or `to_na`. 29 | #' are_fct_ish(c("a", "b", "c"), levels = c("a", "b")) 30 | #' is_fct_ish(c("a", "b", "c"), levels = c("a", "b")) 31 | #' 32 | #' # The `to_na` argument allows some values to be treated as `NA`. 33 | #' are_fct_ish(c("a", "b", "z"), levels = c("a", "b"), to_na = "z") 34 | #' is_fct_ish(c("a", "b", "z"), levels = c("a", "b"), to_na = "z") 35 | #' 36 | #' # Factors are also checked against the specified levels. 37 | #' are_fct_ish(factor(c("a", "b", "c")), levels = c("a", "b")) 38 | #' is_fct_ish(factor(c("a", "b", "c")), levels = c("a", "b")) 39 | are_fct_ish <- function(x, ..., levels = NULL, to_na = character()) { 40 | UseMethod("are_fct_ish") 41 | } 42 | 43 | #' @export 44 | #' @rdname are_fct_ish 45 | is_fct_ish <- function(x, ...) { 46 | all(are_fct_ish(x, ...)) 47 | } 48 | 49 | #' @export 50 | #' @rdname are_fct_ish 51 | are_factor_ish <- are_fct_ish 52 | 53 | #' @export 54 | #' @rdname are_fct_ish 55 | is_factor_ish <- is_fct_ish 56 | 57 | #' @export 58 | are_fct_ish.factor <- function(x, ..., levels = NULL, to_na = character()) { 59 | are_fct_ish(as.character(x), ..., levels = levels, to_na = to_na) 60 | } 61 | 62 | #' @export 63 | are_fct_ish.character <- function(x, ..., levels = NULL, to_na = character()) { 64 | !.are_not_fct_ish_chr(x, levels, to_na) 65 | } 66 | 67 | #' Check for values that would be lost during factor coercion 68 | #' 69 | #' @inheritParams .shared-params-check 70 | #' @inheritParams .shared-params 71 | #' @returns A logical vector where `TRUE` indicates a failure. 72 | #' @keywords internal 73 | .are_not_fct_ish_chr <- function(x, levels, to_na = character()) { 74 | if (is.null(levels)) { 75 | return(rep(FALSE, length(x))) 76 | } 77 | if (length(to_na)) { 78 | x[x %in% to_na] <- NA 79 | } 80 | was_na <- is.na(x) 81 | cast <- factor(x, levels = levels) 82 | xor(is.na(cast), was_na) 83 | } 84 | 85 | #' @export 86 | are_fct_ish.NULL <- function(x, ...) { 87 | logical(0) 88 | } 89 | 90 | #' @export 91 | #' @rdname are_fct_ish 92 | are_fct_ish.default <- function( 93 | x, 94 | ..., 95 | levels = NULL, 96 | to_na = character(), 97 | depth = 1 98 | ) { 99 | if (rlang::is_atomic(x)) { 100 | return(are_fct_ish(as.character(x), ..., levels = levels, to_na = to_na)) 101 | } 102 | 103 | if (!rlang::is_vector(x) || depth != 1) { 104 | return(FALSE) 105 | } 106 | 107 | .elements_are_cls_ish(x, are_fct_ish, ..., levels = levels, to_na = to_na) 108 | } 109 | -------------------------------------------------------------------------------- /tests/testthat/_snaps/to_chr.md: -------------------------------------------------------------------------------- 1 | # to_chr() respects allow_null (#22) 2 | 3 | Code 4 | to_chr(given, allow_null = FALSE) 5 | Condition 6 | Error: 7 | ! `given` must not be . 8 | 9 | --- 10 | 11 | Code 12 | wrapped_to_chr(given, allow_null = FALSE) 13 | Condition 14 | Error in `wrapped_to_chr()`: 15 | ! `val` must not be . 16 | 17 | # to_chr() fails gracefully for weird cases (#22) 18 | 19 | Code 20 | to_chr(given) 21 | Condition 22 | Error: 23 | ! Can't coerce `given` to . 24 | 25 | --- 26 | 27 | Code 28 | wrapped_to_chr(given) 29 | Condition 30 | Error in `wrapped_to_chr()`: 31 | ! Can't coerce `val` to . 32 | 33 | --- 34 | 35 | Code 36 | to_chr(given) 37 | Condition 38 | Error: 39 | ! Can't coerce `given` to . 40 | 41 | --- 42 | 43 | Code 44 | wrapped_to_chr(given) 45 | Condition 46 | Error in `wrapped_to_chr()`: 47 | ! Can't coerce `val` to . 48 | 49 | --- 50 | 51 | Code 52 | to_chr(given) 53 | Condition 54 | Error: 55 | ! Can't coerce `given` to . 56 | 57 | --- 58 | 59 | Code 60 | wrapped_to_chr(given) 61 | Condition 62 | Error in `wrapped_to_chr()`: 63 | ! Can't coerce `val` to . 64 | 65 | --- 66 | 67 | Code 68 | to_chr(given) 69 | Condition 70 | Error: 71 | ! Can't coerce `given` to . 72 | 73 | --- 74 | 75 | Code 76 | wrapped_to_chr(given) 77 | Condition 78 | Error in `wrapped_to_chr()`: 79 | ! Can't coerce `val` to . 80 | 81 | --- 82 | 83 | Code 84 | to_chr(given) 85 | Condition 86 | Error: 87 | ! Can't coerce `given` to . 88 | 89 | --- 90 | 91 | Code 92 | wrapped_to_chr(given) 93 | Condition 94 | Error in `wrapped_to_chr()`: 95 | ! Can't coerce `val` to . 96 | 97 | # to_chr_scalar() errors for non-scalars (#22) 98 | 99 | Code 100 | to_chr_scalar(given) 101 | Condition 102 | Error: 103 | ! `given` must be a single . 104 | x `given` has 26 values. 105 | 106 | --- 107 | 108 | Code 109 | wrapped_to_chr_scalar(given) 110 | Condition 111 | Error in `wrapped_to_chr_scalar()`: 112 | ! `val` must be a single . 113 | x `val` has 26 values. 114 | 115 | # to_chr_scalar() errors for uncoerceable types (#22) 116 | 117 | Code 118 | to_chr_scalar(given) 119 | Condition 120 | Error: 121 | ! Can't coerce `given` to . 122 | 123 | --- 124 | 125 | Code 126 | wrapped_to_chr_scalar(given) 127 | Condition 128 | Error in `wrapped_to_chr_scalar()`: 129 | ! Can't coerce `val` to . 130 | 131 | # to_chr_scalar() respects allow_null (#22) 132 | 133 | Code 134 | to_chr_scalar(given, allow_null = FALSE) 135 | Condition 136 | Error: 137 | ! `given` must not be . 138 | 139 | --- 140 | 141 | Code 142 | wrapped_to_chr_scalar(given, allow_null = FALSE) 143 | Condition 144 | Error in `wrapped_to_chr_scalar()`: 145 | ! `val` must not be . 146 | 147 | # to_chr_scalar respects allow_zero_length (#22) 148 | 149 | Code 150 | to_chr_scalar(given, allow_zero_length = FALSE) 151 | Condition 152 | Error: 153 | ! `given` must be a single . 154 | x `given` has no values. 155 | 156 | -------------------------------------------------------------------------------- /R/stabilize_lgl.R: -------------------------------------------------------------------------------- 1 | #' Ensure a logical argument meets expectations 2 | #' 3 | #' @description `to_lgl()` checks whether an argument can be coerced to logical 4 | #' without losing information, returning it silently if so. Otherwise an 5 | #' informative error message is signaled. `to_logical` is a synonym of 6 | #' `to_lgl()`. 7 | #' 8 | #' `stabilize_lgl()` can check more details about the argument, but is slower 9 | #' than `to_lgl()`. `stabilise_lgl()`, `stabilize_logical()`, and 10 | #' `stabilise_logical()` are synonyms of `stabilize_lgl()`. 11 | #' 12 | #' `stabilize_lgl_scalar()` and `to_lgl_scalar()` are optimized to check for 13 | #' length-1 logical vectors. `stabilise_lgl_scalar()`, 14 | #' `stabilize_logical_scalar()`, and `stabilise_logical_scalar()` are synonyms 15 | #' of `stabilize_lgl_scalar()`, and `to_logical_scalar()` is a synonym of 16 | #' `to_lgl_scalar()`. 17 | #' 18 | #' @inheritParams .shared-params 19 | #' 20 | #' @returns The argument as a logical vector. 21 | #' @family logical functions 22 | #' @family stabilization functions 23 | #' @export 24 | #' 25 | #' @examples 26 | #' to_lgl(TRUE) 27 | #' to_lgl("TRUE") 28 | #' to_lgl(1:10) 29 | #' to_lgl(NULL) 30 | #' try(to_lgl(NULL, allow_null = FALSE)) 31 | #' try(to_lgl(letters)) 32 | #' try(to_lgl(list(TRUE))) 33 | #' 34 | #' to_lgl_scalar("TRUE") 35 | #' try(to_lgl_scalar(c(TRUE, FALSE))) 36 | #' 37 | #' stabilize_lgl(c(TRUE, FALSE, TRUE)) 38 | #' stabilize_lgl("true") 39 | #' stabilize_lgl(NULL) 40 | #' try(stabilize_lgl(NULL, allow_null = FALSE)) 41 | #' try(stabilize_lgl(c(TRUE, NA), allow_na = FALSE)) 42 | #' try(stabilize_lgl(letters)) 43 | #' try(stabilize_lgl(c(TRUE, FALSE, TRUE), min_size = 5)) 44 | #' try(stabilize_lgl(c(TRUE, FALSE, TRUE), max_size = 2)) 45 | #' 46 | #' stabilize_lgl_scalar(TRUE) 47 | #' stabilize_lgl_scalar("TRUE") 48 | #' try(stabilize_lgl_scalar(c(TRUE, FALSE, TRUE))) 49 | #' stabilize_lgl_scalar(NULL) 50 | #' try(stabilize_lgl_scalar(NULL, allow_null = FALSE)) 51 | stabilize_lgl <- function( 52 | x, 53 | ..., 54 | allow_null = TRUE, 55 | allow_na = TRUE, 56 | min_size = NULL, 57 | max_size = NULL, 58 | x_arg = caller_arg(x), 59 | call = caller_env(), 60 | x_class = object_type(x) 61 | ) { 62 | .stabilize_cls( 63 | x, 64 | to_cls_fn = to_lgl, 65 | allow_null = allow_null, 66 | allow_na = allow_na, 67 | min_size = min_size, 68 | max_size = max_size, 69 | x_arg = x_arg, 70 | call = call, 71 | x_class = x_class, 72 | ... 73 | ) 74 | } 75 | 76 | #' @export 77 | #' @rdname stabilize_lgl 78 | stabilize_logical <- stabilize_lgl 79 | 80 | #' @export 81 | #' @rdname stabilize_lgl 82 | stabilise_lgl <- stabilize_lgl 83 | 84 | #' @export 85 | #' @rdname stabilize_lgl 86 | stabilise_logical <- stabilize_lgl 87 | 88 | #' @export 89 | #' @rdname stabilize_lgl 90 | stabilize_lgl_scalar <- function( 91 | x, 92 | ..., 93 | allow_null = TRUE, 94 | allow_zero_length = TRUE, 95 | allow_na = TRUE, 96 | x_arg = caller_arg(x), 97 | call = caller_env(), 98 | x_class = object_type(x) 99 | ) { 100 | .stabilize_cls_scalar( 101 | x, 102 | to_cls_scalar_fn = to_lgl_scalar, 103 | allow_null = allow_null, 104 | allow_zero_length = allow_zero_length, 105 | allow_na = allow_na, 106 | x_arg = x_arg, 107 | call = call, 108 | x_class = x_class, 109 | ... 110 | ) 111 | } 112 | 113 | #' @export 114 | #' @rdname stabilize_lgl 115 | stabilize_logical_scalar <- stabilize_lgl_scalar 116 | 117 | #' @export 118 | #' @rdname stabilize_lgl 119 | stabilise_lgl_scalar <- stabilize_lgl_scalar 120 | 121 | #' @export 122 | #' @rdname stabilize_lgl 123 | stabilise_logical_scalar <- stabilize_lgl_scalar 124 | --------------------------------------------------------------------------------