diff --git a/.Rbuildignore b/.Rbuildignore index 285646b..6316df3 100644 --- a/.Rbuildignore +++ b/.Rbuildignore @@ -19,3 +19,5 @@ ^pkgdown$ ^cran-comments\.md$ ^CRAN-SUBMISSION$ +^[.]?air[.]toml$ +^\.vscode$ diff --git a/.github/CODE_OF_CONDUCT.md b/.github/CODE_OF_CONDUCT.md new file mode 100644 index 0000000..3ac34c8 --- /dev/null +++ b/.github/CODE_OF_CONDUCT.md @@ -0,0 +1,126 @@ +# Contributor Covenant Code of Conduct + +## Our Pledge + +We as members, contributors, and leaders pledge to make participation in our +community a harassment-free experience for everyone, regardless of age, body +size, visible or invisible disability, ethnicity, sex characteristics, gender +identity and expression, level of experience, education, socio-economic status, +nationality, personal appearance, race, caste, color, religion, or sexual +identity and orientation. + +We pledge to act and interact in ways that contribute to an open, welcoming, +diverse, inclusive, and healthy community. + +## Our Standards + +Examples of behavior that contributes to a positive environment for our +community include: + +* Demonstrating empathy and kindness toward other people +* Being respectful of differing opinions, viewpoints, and experiences +* Giving and gracefully accepting constructive feedback +* Accepting responsibility and apologizing to those affected by our mistakes, + and learning from the experience +* Focusing on what is best not just for us as individuals, but for the overall + community + +Examples of unacceptable behavior include: + +* The use of sexualized language or imagery, and sexual attention or advances of + any kind +* Trolling, insulting or derogatory comments, and personal or political attacks +* Public or private harassment +* Publishing others' private information, such as a physical or email address, + without their explicit permission +* Other conduct which could reasonably be considered inappropriate in a + professional setting + +## Enforcement Responsibilities + +Community leaders are responsible for clarifying and enforcing our standards of +acceptable behavior and will take appropriate and fair corrective action in +response to any behavior that they deem inappropriate, threatening, offensive, +or harmful. + +Community leaders have the right and responsibility to remove, edit, or reject +comments, commits, code, wiki edits, issues, and other contributions that are +not aligned to this Code of Conduct, and will communicate reasons for moderation +decisions when appropriate. + +## Scope + +This Code of Conduct applies within all community spaces, and also applies when +an individual is officially representing the community in public spaces. +Examples of representing our community include using an official e-mail address, +posting via an official social media account, or acting as an appointed +representative at an online or offline event. + +## Enforcement + +Instances of abusive, harassing, or otherwise unacceptable behavior may be +reported to the community leaders responsible for enforcement at codeofconduct@posit.co. +All complaints will be reviewed and investigated promptly and fairly. + +All community leaders are obligated to respect the privacy and security of the +reporter of any incident. + +## Enforcement Guidelines + +Community leaders will follow these Community Impact Guidelines in determining +the consequences for any action they deem in violation of this Code of Conduct: + +### 1. Correction + +**Community Impact**: Use of inappropriate language or other behavior deemed +unprofessional or unwelcome in the community. + +**Consequence**: A private, written warning from community leaders, providing +clarity around the nature of the violation and an explanation of why the +behavior was inappropriate. A public apology may be requested. + +### 2. Warning + +**Community Impact**: A violation through a single incident or series of +actions. + +**Consequence**: A warning with consequences for continued behavior. No +interaction with the people involved, including unsolicited interaction with +those enforcing the Code of Conduct, for a specified period of time. This +includes avoiding interactions in community spaces as well as external channels +like social media. Violating these terms may lead to a temporary or permanent +ban. + +### 3. Temporary Ban + +**Community Impact**: A serious violation of community standards, including +sustained inappropriate behavior. + +**Consequence**: A temporary ban from any sort of interaction or public +communication with the community for a specified period of time. No public or +private interaction with the people involved, including unsolicited interaction +with those enforcing the Code of Conduct, is allowed during this period. +Violating these terms may lead to a permanent ban. + +### 4. Permanent Ban + +**Community Impact**: Demonstrating a pattern of violation of community +standards, including sustained inappropriate behavior, harassment of an +individual, or aggression toward or disparagement of classes of individuals. + +**Consequence**: A permanent ban from any sort of public interaction within the +community. + +## Attribution + +This Code of Conduct is adapted from the [Contributor Covenant][homepage], +version 2.1, available at +. + +Community Impact Guidelines were inspired by +[Mozilla's code of conduct enforcement ladder][https://github.com/mozilla/inclusion]. + +For answers to common questions about this code of conduct, see the FAQ at +. Translations are available at . + +[homepage]: https://www.contributor-covenant.org diff --git a/.github/workflows/R-CMD-check.yaml b/.github/workflows/R-CMD-check.yaml index 4d46a36..9dc64a4 100644 --- a/.github/workflows/R-CMD-check.yaml +++ b/.github/workflows/R-CMD-check.yaml @@ -1,11 +1,14 @@ # Workflow derived from https://github.com/r-lib/actions/tree/v2/examples # Need help debugging build failures? Start at https://github.com/r-lib/actions#where-to-find-help +# +# NOTE: This workflow is overkill for most R packages and +# check-standard.yaml is likely a better choice. +# usethis::use_github_action("check-standard") will install it. on: push: - branches: main + branches: [main, master] pull_request: - branches: main - + name: R-CMD-check.yaml permissions: read-all @@ -21,10 +24,14 @@ jobs: matrix: config: - {os: macos-latest, r: 'release'} + - {os: windows-latest, r: 'release'} - - {os: ubuntu-latest, r: 'devel', http-user-agent: 'release'} - - {os: ubuntu-latest, r: 'release'} - - {os: ubuntu-latest, r: 'oldrel-1'} + # use 4.0 or 4.1 to check with rtools40's older compiler + - {os: windows-latest, r: 'oldrel-4'} + + - {os: ubuntu-latest, r: 'devel', http-user-agent: 'release'} + - {os: ubuntu-latest, r: 'release'} + - {os: ubuntu-latest, r: 'oldrel-1'} env: GITHUB_PAT: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/format-suggest.yaml b/.github/workflows/format-suggest.yaml new file mode 100644 index 0000000..8c4f117 --- /dev/null +++ b/.github/workflows/format-suggest.yaml @@ -0,0 +1,46 @@ +# Workflow derived from https://github.com/posit-dev/setup-air/tree/main/examples + +on: + # Using `pull_request_target` over `pull_request` for elevated `GITHUB_TOKEN` + # privileges, otherwise we can't set `pull-requests: write` when the pull + # request comes from a fork, which is our main use case (external contributors). + # + # `pull_request_target` runs in the context of the target branch (`main`, usually), + # rather than in the context of the pull request like `pull_request` does. Due + # to this, we must explicitly checkout `ref: ${{ github.event.pull_request.head.sha }}`. + # This is typically frowned upon by GitHub, as it exposes you to potentially running + # untrusted code in a context where you have elevated privileges, but they explicitly + # call out the use case of reformatting and committing back / commenting on the PR + # as a situation that should be safe (because we aren't actually running the untrusted + # code, we are just treating it as passive data). + # https://securitylab.github.com/resources/github-actions-preventing-pwn-requests/ + pull_request_target: + +name: format-suggest.yaml + +jobs: + format-suggest: + name: format-suggest + runs-on: ubuntu-latest + + permissions: + # Required to push suggestion comments to the PR + pull-requests: write + + steps: + - uses: actions/checkout@v4 + with: + ref: ${{ github.event.pull_request.head.sha }} + + - name: Install + uses: posit-dev/setup-air@v1 + + - name: Format + run: air format . + + - name: Suggest + uses: reviewdog/action-suggester@v1 + with: + level: error + fail_level: error + tool_name: air diff --git a/.github/workflows/test-coverage.yaml b/.github/workflows/test-coverage.yaml index 837425d..0ab748d 100644 --- a/.github/workflows/test-coverage.yaml +++ b/.github/workflows/test-coverage.yaml @@ -2,7 +2,7 @@ # Need help debugging build failures? Start at https://github.com/r-lib/actions#where-to-find-help on: push: - branches: main + branches: [main, master] pull_request: name: test-coverage.yaml @@ -34,15 +34,16 @@ jobs: clean = FALSE, install_path = file.path(normalizePath(Sys.getenv("RUNNER_TEMP"), winslash = "/"), "package") ) + print(cov) covr::to_cobertura(cov) shell: Rscript {0} - - uses: codecov/codecov-action@v4 + - uses: codecov/codecov-action@v5 with: # Fail if error if not on PR, or if on PR and token is given fail_ci_if_error: ${{ github.event_name != 'pull_request' || secrets.CODECOV_TOKEN }} - file: ./cobertura.xml - plugin: noop + files: ./cobertura.xml + plugins: noop disable_search: true token: ${{ secrets.CODECOV_TOKEN }} diff --git a/.vscode/extensions.json b/.vscode/extensions.json new file mode 100644 index 0000000..344f76e --- /dev/null +++ b/.vscode/extensions.json @@ -0,0 +1,5 @@ +{ + "recommendations": [ + "Posit.air-vscode" + ] +} diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..a9f69fe --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,10 @@ +{ + "[r]": { + "editor.formatOnSave": true, + "editor.defaultFormatter": "Posit.air-vscode" + }, + "[quarto]": { + "editor.formatOnSave": true, + "editor.defaultFormatter": "quarto.quarto" + } +} diff --git a/DESCRIPTION b/DESCRIPTION index a7ffc2f..536bc7f 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -1,27 +1,35 @@ Package: lang Title: Translates R Help Documentation using Large Language Models Version: 0.1.0 -Authors@R: - person("Edgar", "Ruiz", , "edgar@posit.co", role = c("aut", "cre")) -Description: Translates R help documentation on the fly by using a Large - Language model of your choice. If you are using 'RStudio' or 'Positron' - the translated help will appear in the help pane. +Authors@R: c( + person("Edgar", "Ruiz", , "edgar@posit.co", role = c("aut", "cre")), + person("Posit Software, PBC", role = c("cph", "fnd"), + comment = c(ROR = "03wc8by49")) + ) +Description: Translates R help documentation on the fly by using a Large + Language model of your choice. If you are using 'RStudio' or + 'Positron' the translated help will appear in the help pane. License: MIT + file LICENSE -Encoding: UTF-8 -Roxygen: list(markdown = TRUE) -RoxygenNote: 7.3.3 +URL: https://mlverse.github.io/lang/, https://github.com/mlverse/lang +BugReports: https://github.com/mlverse/lang/issues +Depends: + R (>= 4.1) Imports: callr, cli, fs, glue, + lifecycle, mall, - rlang, + rlang (>= 1.1.0), rstudioapi, tools, withr Suggests: - testthat (>= 3.0.0), - ellmer + ellmer, + testthat (>= 3.0.0) Config/testthat/edition: 3 -URL: https://mlverse.github.io/lang/ +Config/usethis/last-upkeep: 2025-11-10 +Encoding: UTF-8 +Roxygen: list(markdown = TRUE) +RoxygenNote: 7.3.3 diff --git a/NAMESPACE b/NAMESPACE index 20534ba..5ee25bb 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -10,6 +10,8 @@ import(mall) import(rlang) import(rstudioapi) import(tools) +importFrom(glue,glue) +importFrom(lifecycle,deprecated) importFrom(utils,browseURL) importFrom(utils,capture.output) importFrom(utils,head) diff --git a/R/help-shims.R b/R/help-shims.R index 1bbf193..4af4260 100644 --- a/R/help-shims.R +++ b/R/help-shims.R @@ -133,9 +133,10 @@ which_lang <- function(lang = NULL, choose = FALSE) { env_lang <- Sys.getenv("LANG", unset = NA) env_language <- Sys.getenv("LANGUAGE", unset = NA) lang <- c(LANG = env_lang, LANGUAGE = env_language) - lang <- lang[!is.na(lang)] - lang <- lang[lang != "C"] - lang <- lang[!startsWith(lang, "C.")] + lang <- lang |> + discard(is.na) |> + discard(\(x) x == "C") |> + discard(\(x) startsWith(x, "C.")) if (length(lang) > 1 && choose) { if (unique(length(lang) > 1) && is.null(.lang_env$choose)) { cli_bullets( diff --git a/R/import-standalone-obj-type.R b/R/import-standalone-obj-type.R new file mode 100644 index 0000000..f839641 --- /dev/null +++ b/R/import-standalone-obj-type.R @@ -0,0 +1,370 @@ +# Standalone file: do not edit by hand +# Source: https://github.com/r-lib/rlang/blob/HEAD/R/standalone-obj-type.R +# Generated by: usethis::use_standalone("r-lib/rlang", "obj-type") +# ---------------------------------------------------------------------- +# +# --- +# repo: r-lib/rlang +# file: standalone-obj-type.R +# last-updated: 2025-10-02 +# license: https://unlicense.org +# imports: rlang (>= 1.1.0) +# --- +# +# ## Changelog +# +# 2025-10-02: +# - `obj_type_friendly()` now shows the dimensionality of arrays. +# +# 2024-02-14: +# - `obj_type_friendly()` now works for S7 objects. +# +# 2023-05-01: +# - `obj_type_friendly()` now only displays the first class of S3 objects. +# +# 2023-03-30: +# - `stop_input_type()` now handles `I()` input literally in `arg`. +# +# 2022-10-04: +# - `obj_type_friendly(value = TRUE)` now shows numeric scalars +# literally. +# - `stop_friendly_type()` now takes `show_value`, passed to +# `obj_type_friendly()` as the `value` argument. +# +# 2022-10-03: +# - Added `allow_na` and `allow_null` arguments. +# - `NULL` is now backticked. +# - Better friendly type for infinities and `NaN`. +# +# 2022-09-16: +# - Unprefixed usage of rlang functions with `rlang::` to +# avoid onLoad issues when called from rlang (#1482). +# +# 2022-08-11: +# - Prefixed usage of rlang functions with `rlang::`. +# +# 2022-06-22: +# - `friendly_type_of()` is now `obj_type_friendly()`. +# - Added `obj_type_oo()`. +# +# 2021-12-20: +# - Added support for scalar values and empty vectors. +# - Added `stop_input_type()` +# +# 2021-06-30: +# - Added support for missing arguments. +# +# 2021-04-19: +# - Added support for matrices and arrays (#141). +# - Added documentation. +# - Added changelog. +# +# nocov start + +#' Return English-friendly type +#' @param x Any R object. +#' @param value Whether to describe the value of `x`. Special values +#' like `NA` or `""` are always described. +#' @param length Whether to mention the length of vectors and lists. +#' @return A string describing the type. Starts with an indefinite +#' article, e.g. "an integer vector". +#' @noRd +obj_type_friendly <- function(x, value = TRUE) { + if (is_missing(x)) { + return("absent") + } + + if (is.object(x)) { + if (inherits(x, "quosure")) { + type <- "quosure" + } else { + type <- class(x)[[1L]] + } + return(sprintf("a <%s> object", type)) + } + + if (!is_vector(x)) { + return(.rlang_as_friendly_type(typeof(x))) + } + + n_dim <- length(dim(x)) + + if (!n_dim) { + if (!is_list(x) && length(x) == 1) { + if (is_na(x)) { + return(switch( + typeof(x), + logical = "`NA`", + integer = "an integer `NA`", + double = if (is.nan(x)) { + "`NaN`" + } else { + "a numeric `NA`" + }, + complex = "a complex `NA`", + character = "a character `NA`", + .rlang_stop_unexpected_typeof(x) + )) + } + + show_infinites <- function(x) { + if (x > 0) { + "`Inf`" + } else { + "`-Inf`" + } + } + str_encode <- function(x, width = 30, ...) { + if (nchar(x) > width) { + x <- substr(x, 1, width - 3) + x <- paste0(x, "...") + } + encodeString(x, ...) + } + + if (value) { + if (is.numeric(x) && is.infinite(x)) { + return(show_infinites(x)) + } + + if (is.numeric(x) || is.complex(x)) { + number <- as.character(round(x, 2)) + what <- if (is.complex(x)) "the complex number" else "the number" + return(paste(what, number)) + } + + return(switch( + typeof(x), + logical = if (x) "`TRUE`" else "`FALSE`", + character = { + what <- if (nzchar(x)) "the string" else "the empty string" + paste(what, str_encode(x, quote = "\"")) + }, + raw = paste("the raw value", as.character(x)), + .rlang_stop_unexpected_typeof(x) + )) + } + + return(switch( + typeof(x), + logical = "a logical value", + integer = "an integer", + double = if (is.infinite(x)) show_infinites(x) else "a number", + complex = "a complex number", + character = if (nzchar(x)) "a string" else "\"\"", + raw = "a raw value", + .rlang_stop_unexpected_typeof(x) + )) + } + + if (length(x) == 0) { + return(switch( + typeof(x), + logical = "an empty logical vector", + integer = "an empty integer vector", + double = "an empty numeric vector", + complex = "an empty complex vector", + character = "an empty character vector", + raw = "an empty raw vector", + list = "an empty list", + .rlang_stop_unexpected_typeof(x) + )) + } + } + + vec_type_friendly(x) +} + +vec_type_friendly <- function(x, length = FALSE) { + if (!is_vector(x)) { + abort("`x` must be a vector.") + } + type <- typeof(x) + n_dim <- length(dim(x)) + + add_length <- function(type) { + if (length && !n_dim) { + paste0(type, sprintf(" of length %s", length(x))) + } else { + type + } + } + + if (type == "list") { + if (n_dim == 0) { + return(add_length("a list")) + } else if (n_dim == 2) { + if (is.data.frame(x)) { + return("a data frame") + } else { + return("a list matrix") + } + } else { + return(sprintf("a list %sD array", n_dim)) + } + } + + type <- switch( + type, + logical = "a logical %s", + integer = "an integer %s", + numeric = , + double = "a double %s", + complex = "a complex %s", + character = "a character %s", + raw = "a raw %s", + type = paste0("a ", type, " %s") + ) + + if (n_dim == 0) { + kind <- "vector" + } else if (n_dim == 2) { + kind <- "matrix" + } else { + kind <- sprintf("%sD array", n_dim) + } + out <- sprintf(type, kind) + + if (n_dim >= 2) { + out + } else { + add_length(out) + } +} + +.rlang_as_friendly_type <- function(type) { + switch( + type, + + list = "a list", + + NULL = "`NULL`", + environment = "an environment", + externalptr = "a pointer", + weakref = "a weak reference", + S4 = "an S4 object", + + name = , + symbol = "a symbol", + language = "a call", + pairlist = "a pairlist node", + expression = "an expression vector", + + char = "an internal string", + promise = "an internal promise", + ... = "an internal dots object", + any = "an internal `any` object", + bytecode = "an internal bytecode object", + + primitive = , + builtin = , + special = "a primitive function", + closure = "a function", + + type + ) +} + +.rlang_stop_unexpected_typeof <- function(x, call = caller_env()) { + abort( + sprintf("Unexpected type <%s>.", typeof(x)), + call = call + ) +} + +#' Return OO type +#' @param x Any R object. +#' @return One of `"bare"` (for non-OO objects), `"S3"`, `"S4"`, +#' `"R6"`, or `"S7"`. +#' @noRd +obj_type_oo <- function(x) { + if (!is.object(x)) { + return("bare") + } + + class <- inherits(x, c("R6", "S7_object"), which = TRUE) + + if (class[[1]]) { + "R6" + } else if (class[[2]]) { + "S7" + } else if (isS4(x)) { + "S4" + } else { + "S3" + } +} + +#' @param x The object type which does not conform to `what`. Its +#' `obj_type_friendly()` is taken and mentioned in the error message. +#' @param what The friendly expected type as a string. Can be a +#' character vector of expected types, in which case the error +#' message mentions all of them in an "or" enumeration. +#' @param show_value Passed to `value` argument of `obj_type_friendly()`. +#' @param ... Arguments passed to [abort()]. +#' @inheritParams args_error_context +#' @noRd +stop_input_type <- function( + x, + what, + ..., + allow_na = FALSE, + allow_null = FALSE, + show_value = TRUE, + arg = caller_arg(x), + call = caller_env() +) { + # From standalone-cli.R + cli <- env_get_list( + nms = c("format_arg", "format_code"), + last = topenv(), + default = function(x) sprintf("`%s`", x), + inherit = TRUE + ) + + if (allow_na) { + what <- c(what, cli$format_code("NA")) + } + if (allow_null) { + what <- c(what, cli$format_code("NULL")) + } + if (length(what)) { + what <- oxford_comma(what) + } + if (inherits(arg, "AsIs")) { + format_arg <- identity + } else { + format_arg <- cli$format_arg + } + + message <- sprintf( + "%s must be %s, not %s.", + format_arg(arg), + what, + obj_type_friendly(x, value = show_value) + ) + + abort(message, ..., call = call, arg = arg) +} + +oxford_comma <- function(chr, sep = ", ", final = "or") { + n <- length(chr) + + if (n < 2) { + return(chr) + } + + head <- chr[seq_len(n - 1)] + last <- chr[n] + + head <- paste(head, collapse = sep) + + # Write a or b. But a, b, or c. + if (n > 2) { + paste0(head, sep, final, " ", last) + } else { + paste0(head, " ", final, " ", last) + } +} + +# nocov end diff --git a/R/import-standalone-purrr.R b/R/import-standalone-purrr.R new file mode 100644 index 0000000..85a185f --- /dev/null +++ b/R/import-standalone-purrr.R @@ -0,0 +1,246 @@ +# Standalone file: do not edit by hand +# Source: https://github.com/r-lib/rlang/blob/HEAD/R/standalone-purrr.R +# Generated by: usethis::use_standalone("r-lib/rlang", "purrr") +# ---------------------------------------------------------------------- +# +# --- +# repo: r-lib/rlang +# file: standalone-purrr.R +# last-updated: 2023-02-23 +# license: https://unlicense.org +# imports: rlang +# --- +# +# This file provides a minimal shim to provide a purrr-like API on top of +# base R functions. They are not drop-in replacements but allow a similar style +# of programming. +# +# ## Changelog +# +# 2023-02-23: +# * Added `list_c()` +# +# 2022-06-07: +# * `transpose()` is now more consistent with purrr when inner names +# are not congruent (#1346). +# +# 2021-12-15: +# * `transpose()` now supports empty lists. +# +# 2021-05-21: +# * Fixed "object `x` not found" error in `imap()` (@mgirlich) +# +# 2020-04-14: +# * Removed `pluck*()` functions +# * Removed `*_cpl()` functions +# * Used `as_function()` to allow use of `~` +# * Used `.` prefix for helpers +# +# nocov start + +map <- function(.x, .f, ...) { + .f <- as_function(.f, env = global_env()) + lapply(.x, .f, ...) +} +walk <- function(.x, .f, ...) { + map(.x, .f, ...) + invisible(.x) +} + +map_lgl <- function(.x, .f, ...) { + .rlang_purrr_map_mold(.x, .f, logical(1), ...) +} +map_int <- function(.x, .f, ...) { + .rlang_purrr_map_mold(.x, .f, integer(1), ...) +} +map_dbl <- function(.x, .f, ...) { + .rlang_purrr_map_mold(.x, .f, double(1), ...) +} +map_chr <- function(.x, .f, ...) { + .rlang_purrr_map_mold(.x, .f, character(1), ...) +} +.rlang_purrr_map_mold <- function(.x, .f, .mold, ...) { + .f <- as_function(.f, env = global_env()) + out <- vapply(.x, .f, .mold, ..., USE.NAMES = FALSE) + names(out) <- names(.x) + out +} + +map2 <- function(.x, .y, .f, ...) { + .f <- as_function(.f, env = global_env()) + out <- mapply(.f, .x, .y, MoreArgs = list(...), SIMPLIFY = FALSE) + if (length(out) == length(.x)) { + set_names(out, names(.x)) + } else { + set_names(out, NULL) + } +} +map2_lgl <- function(.x, .y, .f, ...) { + as.vector(map2(.x, .y, .f, ...), "logical") +} +map2_int <- function(.x, .y, .f, ...) { + as.vector(map2(.x, .y, .f, ...), "integer") +} +map2_dbl <- function(.x, .y, .f, ...) { + as.vector(map2(.x, .y, .f, ...), "double") +} +map2_chr <- function(.x, .y, .f, ...) { + as.vector(map2(.x, .y, .f, ...), "character") +} +imap <- function(.x, .f, ...) { + map2(.x, names(.x) %||% seq_along(.x), .f, ...) +} + +pmap <- function(.l, .f, ...) { + .f <- as.function(.f) + args <- .rlang_purrr_args_recycle(.l) + do.call( + "mapply", + c( + FUN = list(quote(.f)), + args, + MoreArgs = quote(list(...)), + SIMPLIFY = FALSE, + USE.NAMES = FALSE + ) + ) +} +.rlang_purrr_args_recycle <- function(args) { + lengths <- map_int(args, length) + n <- max(lengths) + + stopifnot(all(lengths == 1L | lengths == n)) + to_recycle <- lengths == 1L + args[to_recycle] <- map(args[to_recycle], function(x) rep.int(x, n)) + + args +} + +keep <- function(.x, .f, ...) { + .x[.rlang_purrr_probe(.x, .f, ...)] +} +discard <- function(.x, .p, ...) { + sel <- .rlang_purrr_probe(.x, .p, ...) + .x[is.na(sel) | !sel] +} +map_if <- function(.x, .p, .f, ...) { + matches <- .rlang_purrr_probe(.x, .p) + .x[matches] <- map(.x[matches], .f, ...) + .x +} +.rlang_purrr_probe <- function(.x, .p, ...) { + if (is_logical(.p)) { + stopifnot(length(.p) == length(.x)) + .p + } else { + .p <- as_function(.p, env = global_env()) + map_lgl(.x, .p, ...) + } +} + +compact <- function(.x) { + .x[as.logical(lengths(.x))] +} + +transpose <- function(.l) { + if (!length(.l)) { + return(.l) + } + + inner_names <- names(.l[[1]]) + + if (is.null(inner_names)) { + fields <- seq_along(.l[[1]]) + } else { + fields <- set_names(inner_names) + .l <- map(.l, function(x) { + if (is.null(names(x))) { + set_names(x, inner_names) + } else { + x + } + }) + } + + # This way missing fields are subsetted as `NULL` instead of causing + # an error + .l <- map(.l, as.list) + + map(fields, function(i) { + map(.l, .subset2, i) + }) +} + +every <- function(.x, .p, ...) { + .p <- as_function(.p, env = global_env()) + + for (i in seq_along(.x)) { + if (!rlang::is_true(.p(.x[[i]], ...))) return(FALSE) + } + TRUE +} +some <- function(.x, .p, ...) { + .p <- as_function(.p, env = global_env()) + + for (i in seq_along(.x)) { + if (rlang::is_true(.p(.x[[i]], ...))) return(TRUE) + } + FALSE +} +negate <- function(.p) { + .p <- as_function(.p, env = global_env()) + function(...) !.p(...) +} + +reduce <- function(.x, .f, ..., .init) { + f <- function(x, y) .f(x, y, ...) + Reduce(f, .x, init = .init) +} +reduce_right <- function(.x, .f, ..., .init) { + f <- function(x, y) .f(y, x, ...) + Reduce(f, .x, init = .init, right = TRUE) +} +accumulate <- function(.x, .f, ..., .init) { + f <- function(x, y) .f(x, y, ...) + Reduce(f, .x, init = .init, accumulate = TRUE) +} +accumulate_right <- function(.x, .f, ..., .init) { + f <- function(x, y) .f(y, x, ...) + Reduce(f, .x, init = .init, right = TRUE, accumulate = TRUE) +} + +detect <- function(.x, .f, ..., .right = FALSE, .p = is_true) { + .p <- as_function(.p, env = global_env()) + .f <- as_function(.f, env = global_env()) + + for (i in .rlang_purrr_index(.x, .right)) { + if (.p(.f(.x[[i]], ...))) { + return(.x[[i]]) + } + } + NULL +} +detect_index <- function(.x, .f, ..., .right = FALSE, .p = is_true) { + .p <- as_function(.p, env = global_env()) + .f <- as_function(.f, env = global_env()) + + for (i in .rlang_purrr_index(.x, .right)) { + if (.p(.f(.x[[i]], ...))) { + return(i) + } + } + 0L +} +.rlang_purrr_index <- function(x, right = FALSE) { + idx <- seq_along(x) + if (right) { + idx <- rev(idx) + } + idx +} + +list_c <- function(x) { + inject(c(!!!x)) +} + +# nocov end diff --git a/R/import-standalone-types-check.R b/R/import-standalone-types-check.R new file mode 100644 index 0000000..82668e6 --- /dev/null +++ b/R/import-standalone-types-check.R @@ -0,0 +1,607 @@ +# Standalone file: do not edit by hand +# Source: https://github.com/r-lib/rlang/blob/HEAD/R/standalone-types-check.R +# Generated by: usethis::use_standalone("r-lib/rlang", "types-check") +# ---------------------------------------------------------------------- +# +# --- +# repo: r-lib/rlang +# file: standalone-types-check.R +# last-updated: 2023-03-13 +# license: https://unlicense.org +# dependencies: standalone-obj-type.R +# imports: rlang (>= 1.1.0) +# --- +# +# ## Changelog +# +# 2025-09-19: +# - `check_logical()` gains an `allow_na` argument (@jonthegeek, #1724) +# +# 2024-08-15: +# - `check_character()` gains an `allow_na` argument (@martaalcalde, #1724) +# +# 2023-03-13: +# - Improved error messages of number checkers (@teunbrand) +# - Added `allow_infinite` argument to `check_number_whole()` (@mgirlich). +# - Added `check_data_frame()` (@mgirlich). +# +# 2023-03-07: +# - Added dependency on rlang (>= 1.1.0). +# +# 2023-02-15: +# - Added `check_logical()`. +# +# - `check_bool()`, `check_number_whole()`, and +# `check_number_decimal()` are now implemented in C. +# +# - For efficiency, `check_number_whole()` and +# `check_number_decimal()` now take a `NULL` default for `min` and +# `max`. This makes it possible to bypass unnecessary type-checking +# and comparisons in the default case of no bounds checks. +# +# 2022-10-07: +# - `check_number_whole()` and `_decimal()` no longer treat +# non-numeric types such as factors or dates as numbers. Numeric +# types are detected with `is.numeric()`. +# +# 2022-10-04: +# - Added `check_name()` that forbids the empty string. +# `check_string()` allows the empty string by default. +# +# 2022-09-28: +# - Removed `what` arguments. +# - Added `allow_na` and `allow_null` arguments. +# - Added `allow_decimal` and `allow_infinite` arguments. +# - Improved errors with absent arguments. +# +# +# 2022-09-16: +# - Unprefixed usage of rlang functions with `rlang::` to +# avoid onLoad issues when called from rlang (#1482). +# +# 2022-08-11: +# - Added changelog. +# +# nocov start + +# Scalars ----------------------------------------------------------------- + +.standalone_types_check_dot_call <- .Call + +check_bool <- function( + x, + ..., + allow_na = FALSE, + allow_null = FALSE, + arg = caller_arg(x), + call = caller_env() +) { + if ( + !missing(x) && + .standalone_types_check_dot_call( + ffi_standalone_is_bool_1.0.7, + x, + allow_na, + allow_null + ) + ) { + return(invisible(NULL)) + } + + stop_input_type( + x, + c("`TRUE`", "`FALSE`"), + ..., + allow_na = allow_na, + allow_null = allow_null, + arg = arg, + call = call + ) +} + +check_string <- function( + x, + ..., + allow_empty = TRUE, + allow_na = FALSE, + allow_null = FALSE, + arg = caller_arg(x), + call = caller_env() +) { + if (!missing(x)) { + is_string <- .rlang_check_is_string( + x, + allow_empty = allow_empty, + allow_na = allow_na, + allow_null = allow_null + ) + if (is_string) { + return(invisible(NULL)) + } + } + + stop_input_type( + x, + "a single string", + ..., + allow_na = allow_na, + allow_null = allow_null, + arg = arg, + call = call + ) +} + +.rlang_check_is_string <- function(x, allow_empty, allow_na, allow_null) { + if (is_string(x)) { + if (allow_empty || !is_string(x, "")) { + return(TRUE) + } + } + + if (allow_null && is_null(x)) { + return(TRUE) + } + + if (allow_na && (identical(x, NA) || identical(x, na_chr))) { + return(TRUE) + } + + FALSE +} + +check_name <- function( + x, + ..., + allow_null = FALSE, + arg = caller_arg(x), + call = caller_env() +) { + if (!missing(x)) { + is_string <- .rlang_check_is_string( + x, + allow_empty = FALSE, + allow_na = FALSE, + allow_null = allow_null + ) + if (is_string) { + return(invisible(NULL)) + } + } + + stop_input_type( + x, + "a valid name", + ..., + allow_na = FALSE, + allow_null = allow_null, + arg = arg, + call = call + ) +} + +IS_NUMBER_true <- 0 +IS_NUMBER_false <- 1 +IS_NUMBER_oob <- 2 + +check_number_decimal <- function( + x, + ..., + min = NULL, + max = NULL, + allow_infinite = TRUE, + allow_na = FALSE, + allow_null = FALSE, + arg = caller_arg(x), + call = caller_env() +) { + if (missing(x)) { + exit_code <- IS_NUMBER_false + } else if ( + 0 == + (exit_code <- .standalone_types_check_dot_call( + ffi_standalone_check_number_1.0.7, + x, + allow_decimal = TRUE, + min, + max, + allow_infinite, + allow_na, + allow_null + )) + ) { + return(invisible(NULL)) + } + + .stop_not_number( + x, + ..., + exit_code = exit_code, + allow_decimal = TRUE, + min = min, + max = max, + allow_na = allow_na, + allow_null = allow_null, + arg = arg, + call = call + ) +} + +check_number_whole <- function( + x, + ..., + min = NULL, + max = NULL, + allow_infinite = FALSE, + allow_na = FALSE, + allow_null = FALSE, + arg = caller_arg(x), + call = caller_env() +) { + if (missing(x)) { + exit_code <- IS_NUMBER_false + } else if ( + 0 == + (exit_code <- .standalone_types_check_dot_call( + ffi_standalone_check_number_1.0.7, + x, + allow_decimal = FALSE, + min, + max, + allow_infinite, + allow_na, + allow_null + )) + ) { + return(invisible(NULL)) + } + + .stop_not_number( + x, + ..., + exit_code = exit_code, + allow_decimal = FALSE, + min = min, + max = max, + allow_na = allow_na, + allow_null = allow_null, + arg = arg, + call = call + ) +} + +.stop_not_number <- function( + x, + ..., + exit_code, + allow_decimal, + min, + max, + allow_na, + allow_null, + arg, + call +) { + if (allow_decimal) { + what <- "a number" + } else { + what <- "a whole number" + } + + if (exit_code == IS_NUMBER_oob) { + min <- min %||% -Inf + max <- max %||% Inf + + if (min > -Inf && max < Inf) { + what <- sprintf("%s between %s and %s", what, min, max) + } else if (x < min) { + what <- sprintf("%s larger than or equal to %s", what, min) + } else if (x > max) { + what <- sprintf("%s smaller than or equal to %s", what, max) + } else { + abort("Unexpected state in OOB check", .internal = TRUE) + } + } + + stop_input_type( + x, + what, + ..., + allow_na = allow_na, + allow_null = allow_null, + arg = arg, + call = call + ) +} + +check_symbol <- function( + x, + ..., + allow_null = FALSE, + arg = caller_arg(x), + call = caller_env() +) { + if (!missing(x)) { + if (is_symbol(x)) { + return(invisible(NULL)) + } + if (allow_null && is_null(x)) { + return(invisible(NULL)) + } + } + + stop_input_type( + x, + "a symbol", + ..., + allow_na = FALSE, + allow_null = allow_null, + arg = arg, + call = call + ) +} + +check_arg <- function( + x, + ..., + allow_null = FALSE, + arg = caller_arg(x), + call = caller_env() +) { + if (!missing(x)) { + if (is_symbol(x)) { + return(invisible(NULL)) + } + if (allow_null && is_null(x)) { + return(invisible(NULL)) + } + } + + stop_input_type( + x, + "an argument name", + ..., + allow_na = FALSE, + allow_null = allow_null, + arg = arg, + call = call + ) +} + +check_call <- function( + x, + ..., + allow_null = FALSE, + arg = caller_arg(x), + call = caller_env() +) { + if (!missing(x)) { + if (is_call(x)) { + return(invisible(NULL)) + } + if (allow_null && is_null(x)) { + return(invisible(NULL)) + } + } + + stop_input_type( + x, + "a defused call", + ..., + allow_na = FALSE, + allow_null = allow_null, + arg = arg, + call = call + ) +} + +check_environment <- function( + x, + ..., + allow_null = FALSE, + arg = caller_arg(x), + call = caller_env() +) { + if (!missing(x)) { + if (is_environment(x)) { + return(invisible(NULL)) + } + if (allow_null && is_null(x)) { + return(invisible(NULL)) + } + } + + stop_input_type( + x, + "an environment", + ..., + allow_na = FALSE, + allow_null = allow_null, + arg = arg, + call = call + ) +} + +check_function <- function( + x, + ..., + allow_null = FALSE, + arg = caller_arg(x), + call = caller_env() +) { + if (!missing(x)) { + if (is_function(x)) { + return(invisible(NULL)) + } + if (allow_null && is_null(x)) { + return(invisible(NULL)) + } + } + + stop_input_type( + x, + "a function", + ..., + allow_na = FALSE, + allow_null = allow_null, + arg = arg, + call = call + ) +} + +check_closure <- function( + x, + ..., + allow_null = FALSE, + arg = caller_arg(x), + call = caller_env() +) { + if (!missing(x)) { + if (is_closure(x)) { + return(invisible(NULL)) + } + if (allow_null && is_null(x)) { + return(invisible(NULL)) + } + } + + stop_input_type( + x, + "an R function", + ..., + allow_na = FALSE, + allow_null = allow_null, + arg = arg, + call = call + ) +} + +check_formula <- function( + x, + ..., + allow_null = FALSE, + arg = caller_arg(x), + call = caller_env() +) { + if (!missing(x)) { + if (is_formula(x)) { + return(invisible(NULL)) + } + if (allow_null && is_null(x)) { + return(invisible(NULL)) + } + } + + stop_input_type( + x, + "a formula", + ..., + allow_na = FALSE, + allow_null = allow_null, + arg = arg, + call = call + ) +} + + +# Vectors ----------------------------------------------------------------- + +# TODO: Figure out what to do with logical `NA` and `allow_na = TRUE` + +check_character <- function( + x, + ..., + allow_na = TRUE, + allow_null = FALSE, + arg = caller_arg(x), + call = caller_env() +) { + if (!missing(x)) { + if (is_character(x)) { + if (!allow_na && any(is.na(x))) { + abort( + sprintf("`%s` can't contain NA values.", arg), + arg = arg, + call = call + ) + } + + return(invisible(NULL)) + } + + if (allow_null && is_null(x)) { + return(invisible(NULL)) + } + } + + stop_input_type( + x, + "a character vector", + ..., + allow_null = allow_null, + arg = arg, + call = call + ) +} + +check_logical <- function( + x, + ..., + allow_na = TRUE, + allow_null = FALSE, + arg = caller_arg(x), + call = caller_env() +) { + if (!missing(x)) { + if (is_logical(x)) { + if (!allow_na && any(is.na(x))) { + abort( + sprintf("`%s` can't contain NA values.", arg), + arg = arg, + call = call + ) + } + return(invisible(NULL)) + } + if (allow_null && is_null(x)) { + return(invisible(NULL)) + } + } + + stop_input_type( + x, + "a logical vector", + ..., + allow_na = FALSE, + allow_null = allow_null, + arg = arg, + call = call + ) +} + +check_data_frame <- function( + x, + ..., + allow_null = FALSE, + arg = caller_arg(x), + call = caller_env() +) { + if (!missing(x)) { + if (is.data.frame(x)) { + return(invisible(NULL)) + } + if (allow_null && is_null(x)) { + return(invisible(NULL)) + } + } + + stop_input_type( + x, + "a data frame", + ..., + allow_null = allow_null, + arg = arg, + call = call + ) +} + +# nocov end diff --git a/R/lang-package.R b/R/lang-package.R new file mode 100644 index 0000000..2120609 --- /dev/null +++ b/R/lang-package.R @@ -0,0 +1,12 @@ +#' @keywords internal +"_PACKAGE" + +## usethis namespace: start +#' @import rlang +#' @importFrom glue glue +#' @importFrom lifecycle deprecated +## usethis namespace: end + +## mockable bindings: start +## mockable bindings: end +NULL diff --git a/R/lang-use.R b/R/lang-use.R index 8df2b95..80c07fd 100644 --- a/R/lang-use.R +++ b/R/lang-use.R @@ -48,12 +48,13 @@ #' #' @export lang_use <- function( - backend = NULL, - model = NULL, - .cache = NULL, - .lang = NULL, - .silent = FALSE, - ...) { + backend = NULL, + model = NULL, + .cache = NULL, + .lang = NULL, + .silent = FALSE, + ... +) { lang_use_impl( backend = backend, model = model, @@ -66,13 +67,14 @@ lang_use <- function( } lang_use_impl <- function( - backend = NULL, - model = NULL, - .cache = NULL, - .is_internal = FALSE, - .lang = NULL, - .silent = FALSE, - ...) { + backend = NULL, + model = NULL, + .cache = NULL, + .is_internal = FALSE, + .lang = NULL, + .silent = FALSE, + ... +) { args <- list(...) ca <- .lang_env$session if (!is.null(getOption(".lang_chat"))) { diff --git a/R/rd-translate.R b/R/rd-translate.R index 073c777..782164a 100644 --- a/R/rd-translate.R +++ b/R/rd-translate.R @@ -20,13 +20,15 @@ rd_translate <- function(rd_content, lang) { "\\details", "\\seealso", "\\note", "\\author" ) non_standard_tags <- c("\\section", "\\examples") - all_tags <- as.character(lapply(rd_content, function(x) attr(x, "Rd_tag"))) - filter_obj <- lapply( - c(standard_tags, non_standard_tags), - function(x) rd_content[all_tags == x] - ) + all_tags <- rd_content |> + map_chr(\(x) attr(x, "Rd_tag")) + filter_obj <- standard_tags |> + c(non_standard_tags) |> + map(\(x) rd_content[all_tags == x]) section_no <- 0 - obj_total <- as.integer(object.size(filter_obj)) + obj_total <- filter_obj |> + object.size() |> + as.integer() progress_bar_init( total = obj_total, format = "[{section_no}/{length(filter_obj)}] {pb_bar} {pb_percent} | {tag_label}" @@ -41,7 +43,7 @@ rd_translate <- function(rd_content, lang) { tag_label <- tag_name if (tag_name %in% standard_tags) { progress_bar_update(tag_to_label(tag_name)) - if (any(lapply(rd_i, attr, "Rd_tag") == "\\item")) { + if (any(map(rd_i, attr, "Rd_tag") == "\\item")) { for (k in seq_along(rd_i)) { rd_k <- rd_i[[k]] if (length(rd_k) > 1) { @@ -80,7 +82,7 @@ rd_translate <- function(rd_content, lang) { k_attrs <- attributes(rd_k) rd_char <- as.character(rd_k) if (inherits(rd_k, "list")) { - rd_k <- lapply(rd_char, rd_comment_translate, lang, rs) + rd_k <- map(rd_char, rd_comment_translate, lang, rs) } if (inherits(rd_k, "character")) { rd_k <- rd_comment_translate(rd_char, lang, rs) @@ -118,9 +120,7 @@ rd_comment_translate <- function(x, lang, rs) { args = list(x = rd_char, language = lang) ) rd_char <- paste0("# ", rd_char, "\n") - } else { - - } + } else {} rd_char <- gsub("%", "\\\\%", rd_char) attributes(rd_char) <- attributes(x) x <- rd_char @@ -143,11 +143,13 @@ rd_prep_translate <- function(x, lang, rs) { }, args = list(x = rd_text, y = lang, z = add_prompt) ) - funcs <- unlist(strsplit(txt, "\U2018")) - funcs <- lapply(funcs, strsplit, "\U2019") - funcs <- lapply(funcs, unlist) - funcs <- funcs[as.numeric(lapply(funcs, length)) == 2] - funcs <- lapply(funcs, head, 1) + funcs <- txt |> + strsplit("\U2018") |> + unlist() |> + map(strsplit, "\U2019") |> + map(unlist) |> + keep(\(x) length(x) == 2) |> + map(head, 1) for (func in funcs) { func <- sub("\\(", "\\\\(", func) func <- sub("\\)", "\\\\)", func) @@ -180,16 +182,18 @@ rd_extract_text <- function(x, rs) { } to_title <- function(x) { - split_x <- strsplit(x, " ")[[1]] - split_title <- lapply( - split_x, - function(x) { - up <- toupper(x) - lo <- tolower(x) - paste0(substr(up, 1, 1), substr(lo, 2, nchar(lo))) - } - ) - paste0(as.character(split_title), collapse = " ") + x |> + strsplit(" ") |> + unlist() |> + map( + \(x) { + up <- toupper(x) + lo <- tolower(x) + paste0(substr(up, 1, 1), substr(lo, 2, nchar(lo))) + } + ) |> + as.character() |> + paste0(collapse = " ") } tag_to_label <- function(x) { diff --git a/README.Rmd b/README.Rmd index d4cb31d..29c3a92 100644 --- a/README.Rmd +++ b/README.Rmd @@ -18,13 +18,14 @@ chat <- chat_openai(model = "gpt-4o") lang_use(backend = chat, .lang = "spanish") ``` - +lang's hex logo # lang [![R-CMD-check](https://github.com/mlverse/lang/actions/workflows/R-CMD-check.yaml/badge.svg)](https://github.com/mlverse/lang/actions/workflows/R-CMD-check.yaml) [![Codecov test coverage](https://codecov.io/gh/mlverse/lang/branch/main/graph/badge.svg)](https://app.codecov.io/gh/mlverse/lang?branch=main) [![CRAN status](https://www.r-pkg.org/badges/version/lang)](https://CRAN.R-project.org/package=lang) +[![Codecov test coverage](https://codecov.io/gh/mlverse/lang/graph/badge.svg)](https://app.codecov.io/gh/mlverse/lang) Use an **LLM to translate a function's help documentation on the fly**. `lang` @@ -34,6 +35,12 @@ pane. ## Installing +To install the CRAN version of `lang` use: + +```r +install.packages("lang") +``` + To install the GitHub version of `lang`, use: ```r diff --git a/README.md b/README.md index a294a89..104188b 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ - +lang's hex logo # lang @@ -12,6 +12,8 @@ coverage](https://codecov.io/gh/mlverse/lang/branch/main/graph/badge.svg)](https://app.codecov.io/gh/mlverse/lang?branch=main) [![CRAN status](https://www.r-pkg.org/badges/version/lang)](https://CRAN.R-project.org/package=lang) +[![Codecov test +coverage](https://codecov.io/gh/mlverse/lang/graph/badge.svg)](https://app.codecov.io/gh/mlverse/lang) Use an **LLM to translate a function’s help documentation on the fly**. @@ -21,6 +23,12 @@ in the ‘Help’ pane. ## Installing +To install the CRAN version of `lang` use: + +``` r +install.packages("lang") +``` + To install the GitHub version of `lang`, use: ``` r diff --git a/air.toml b/air.toml new file mode 100644 index 0000000..e69de29 diff --git a/codecov.yml b/codecov.yml index f698adf..04c5585 100644 --- a/codecov.yml +++ b/codecov.yml @@ -1,5 +1,14 @@ +comment: false + coverage: status: - project: off - patch: off - \ No newline at end of file + project: + default: + target: auto + threshold: 1% + informational: true + patch: + default: + target: auto + threshold: 1% + informational: true diff --git a/man/figures/lifecycle-deprecated.svg b/man/figures/lifecycle-deprecated.svg new file mode 100644 index 0000000..b61c57c --- /dev/null +++ b/man/figures/lifecycle-deprecated.svg @@ -0,0 +1,21 @@ + + lifecycle: deprecated + + + + + + + + + + + + + + + lifecycle + + deprecated + + diff --git a/man/figures/lifecycle-experimental.svg b/man/figures/lifecycle-experimental.svg new file mode 100644 index 0000000..5d88fc2 --- /dev/null +++ b/man/figures/lifecycle-experimental.svg @@ -0,0 +1,21 @@ + + lifecycle: experimental + + + + + + + + + + + + + + + lifecycle + + experimental + + diff --git a/man/figures/lifecycle-stable.svg b/man/figures/lifecycle-stable.svg new file mode 100644 index 0000000..9bf21e7 --- /dev/null +++ b/man/figures/lifecycle-stable.svg @@ -0,0 +1,29 @@ + + lifecycle: stable + + + + + + + + + + + + + + + + lifecycle + + + + stable + + + diff --git a/man/figures/lifecycle-superseded.svg b/man/figures/lifecycle-superseded.svg new file mode 100644 index 0000000..db8d757 --- /dev/null +++ b/man/figures/lifecycle-superseded.svg @@ -0,0 +1,21 @@ + + lifecycle: superseded + + + + + + + + + + + + + + + lifecycle + + superseded + + diff --git a/man/lang-package.Rd b/man/lang-package.Rd new file mode 100644 index 0000000..be06ec3 --- /dev/null +++ b/man/lang-package.Rd @@ -0,0 +1,31 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/lang-package.R +\docType{package} +\name{lang-package} +\alias{lang} +\alias{lang-package} +\title{lang: Translates R Help Documentation using Large Language Models} +\description{ +\if{html}{\figure{logo.png}{options: style='float: right' alt='logo' width='120'}} + +Translates R help documentation on the fly by using a Large Language model of your choice. If you are using 'RStudio' or 'Positron' the translated help will appear in the help pane. +} +\seealso{ +Useful links: +\itemize{ + \item \url{https://mlverse.github.io/lang/} + \item \url{https://github.com/mlverse/lang} + \item Report bugs at \url{https://github.com/mlverse/lang/issues} +} + +} +\author{ +\strong{Maintainer}: Edgar Ruiz \email{edgar@posit.co} + +Other contributors: +\itemize{ + \item Posit Software, PBC (\href{https://ror.org/03wc8by49}{ROR}) [copyright holder, funder] +} + +} +\keyword{internal} diff --git a/tests/testthat/_snaps/help-shims.md b/tests/testthat/_snaps/help-shims.md index 26f0733..4aace33 100644 --- a/tests/testthat/_snaps/help-shims.md +++ b/tests/testthat/_snaps/help-shims.md @@ -9,3 +9,11 @@ Output [1] "spanish" +# shim_lang_question works + + Code + shim_lang_question(1) + Condition + Error in `shim_lang_question()`: + ! Unknown input. + diff --git a/tests/testthat/_snaps/lang-help.md b/tests/testthat/_snaps/lang-help.md index b9cc0d2..3d5d77d 100644 --- a/tests/testthat/_snaps/lang-help.md +++ b/tests/testthat/_snaps/lang-help.md @@ -88,16 +88,36 @@ --- - Could not find `nothere` - i Tip: Make sure the containing package is loaded, and the topic is spelled correctly + Code + lang_help("llm_classify", "mall", lang = "english", type = "text") + Condition + Error in `lang_help()`: + ! Language already set to English, use `help()` --- - Package `notpkg` not found - i Tip: Make sure package name is spelled correctly + Code + lang_help("nothere", lang = "spanish", type = "text") + Condition + Error in `lang_help()`: + ! Could not find `nothere` + i Tip: Make sure the containing package is loaded, and the topic is spelled correctly --- - `nothere` could not be found in `mall` - i Tip: Make sure both are spelled correctly + Code + lang_help("nothere", "notpkg", lang = "spanish", type = "text") + Condition + Error in `lang_help()`: + ! Package `notpkg` not found + i Tip: Make sure package name is spelled correctly + +--- + + Code + lang_help("nothere", "mall", lang = "spanish", type = "text") + Condition + Error in `lang_help()`: + ! `nothere` could not be found in `mall` + i Tip: Make sure both are spelled correctly diff --git a/tests/testthat/test-help-shims.R b/tests/testthat/test-help-shims.R index a73b383..c410768 100644 --- a/tests/testthat/test-help-shims.R +++ b/tests/testthat/test-help-shims.R @@ -64,7 +64,7 @@ test_that("shim_lang_question works", { expect_message(shim_lang_question(mtcars())) expect_message(shim_lang_question("mtcars")) expect_message(shim_lang_question("mtcars", "stats")) - expect_error(shim_lang_question(1), "Unknown input") + expect_snapshot(shim_lang_question(1), error = TRUE) expect_message(shim_lang_question(NULL)) expect_message(shim_lang_question(base::`NULL`)) expect_identical( diff --git a/tests/testthat/test-lang-help.R b/tests/testthat/test-lang-help.R index 049d955..f2c1949 100644 --- a/tests/testthat/test-lang-help.R +++ b/tests/testthat/test-lang-help.R @@ -5,17 +5,21 @@ test_that("Interaction with LLM works", { expect_snapshot( lang_help("llm_classify", "mall", lang = "spanish", type = "text") ) - expect_error( - lang_help("llm_classify", "mall", lang = "english", type = "text") + expect_snapshot( + lang_help("llm_classify", "mall", lang = "english", type = "text"), + error = TRUE ) - expect_snapshot_error( - lang_help("nothere", lang = "spanish", type = "text") + expect_snapshot( + lang_help("nothere", lang = "spanish", type = "text"), + error = TRUE ) - expect_snapshot_error( - lang_help("nothere", "notpkg", lang = "spanish", type = "text") + expect_snapshot( + lang_help("nothere", "notpkg", lang = "spanish", type = "text"), + error = TRUE ) - expect_snapshot_error( - lang_help("nothere", "mall", lang = "spanish", type = "text") + expect_snapshot( + lang_help("nothere", "mall", lang = "spanish", type = "text"), + error = TRUE ) skip_on_os("windows") expect_message(