|
| 1 | +# Standalone file: do not edit by hand |
| 2 | +# Source: <https://github.com/elipousson/standaloner/blob/main/R/standalone-settoken.R> |
| 3 | +# ---------------------------------------------------------------------- |
| 4 | +# |
| 5 | +# --- |
| 6 | +# repo: elipousson/standaloner |
| 7 | +# file: standalone-settoken.R |
| 8 | +# last-updated: 2023-10-10 |
| 9 | +# license: https://opensource.org/license/mit/ |
| 10 | +# imports: [rlang (>= 1.0.0), cli (>= 2.5.0)] |
| 11 | +# --- |
| 12 | +# |
| 13 | +# ## Changelog |
| 14 | +# |
| 15 | +# 2023-10-10: |
| 16 | +# * Rename package from settoken to more general name: standaloner. |
| 17 | +# |
| 18 | +# 2023-08-13: |
| 19 | +# * Create file with `set_r_environ_token()` and `get_r_environ_token()` |
| 20 | +# |
| 21 | +# nocov start |
| 22 | +# |
| 23 | +# set_r_environ_token is based on the MIT-licensed tidycensus::census_api_key() |
| 24 | +# function. |
| 25 | +# |
| 26 | +# <https://github.com/walkerke/tidycensus/blob/master/LICENSE> |
| 27 | +# |
| 28 | +# YEAR: 2017 |
| 29 | +# COPYRIGHT HOLDER: Kyle Walker |
| 30 | +# |
| 31 | +#' Set or get a token from your `.Renviron` file |
| 32 | +#' |
| 33 | +#' @author Kyle Walker \email{kyle@walker-data.com} |
| 34 | +#' |
| 35 | +#' Eli Pousson \email{eli.pousson@gmail.com} |
| 36 | +#' ([ORCID](https://orcid.org/0000-0001-8280-1706)) |
| 37 | +#' |
| 38 | +#' [set_r_environ_token()] can set an API key or personal access token (PAT) as |
| 39 | +#' a local environment variable temporarily for the current session or saved for |
| 40 | +#' future sessions. |
| 41 | +#' |
| 42 | +#' [get_r_environ_token()] can return an environment variable or error if the |
| 43 | +#' token is missing or if the token does not match a supplied pattern. |
| 44 | +#' |
| 45 | +#' @param token A personal access token, API key, or other environment variable. |
| 46 | +#' Optional for [get_r_environ_token()]. |
| 47 | +#' @param install If `TRUE`, this function adds your token to your `.Renviron` |
| 48 | +#' for use in future sessions. Defaults to `FALSE`. |
| 49 | +#' @param overwrite If `TRUE`, overwrite any existing token in `.Renviron` using |
| 50 | +#' the same environment variable name. Defaults to `FALSE`. |
| 51 | +#' @param default Default name used for environment variable where the token |
| 52 | +#' is saved. |
| 53 | +#' @param quiet If `TRUE`, suppress messages when setting token by locally |
| 54 | +#' setting the `cli.default_handler` option to [suppressMessages()]. Defaults |
| 55 | +#' to `FALSE`. |
| 56 | +#' @inheritParams rlang::args_error_context |
| 57 | +#' @returns [set_r_environ_token()] invisibly returns a string supplied to |
| 58 | +#' `token`. |
| 59 | +#' |
| 60 | +#' @source Adapted from the [tidycensus](https://walker-data.com/tidycensus/) |
| 61 | +#' function [tidycensus::census_api_key()]. |
| 62 | +#' |
| 63 | +#' @keywords internal |
| 64 | +#' |
| 65 | +#' @importFrom rlang caller_env is_true local_options current_env is_false |
| 66 | +#' caller_call call_name is_null |
| 67 | +#' @importFrom cli cli_bullets cli_alert_success |
| 68 | +#' @importFrom utils read.table write.table |
| 69 | +set_r_environ_token <- function(token, |
| 70 | + install = FALSE, |
| 71 | + overwrite = FALSE, |
| 72 | + default = "TOKEN", |
| 73 | + quiet = FALSE, |
| 74 | + call = caller_env()) { |
| 75 | + if (is_true(quiet)) { |
| 76 | + local_options( |
| 77 | + "cli.default_handler" = suppressMessages, |
| 78 | + .frame = current_env() |
| 79 | + ) |
| 80 | + } |
| 81 | + |
| 82 | + settoken_check_string(default, call = call) |
| 83 | + |
| 84 | + if (is_false(install)) { |
| 85 | + caller_name <- "set_r_environ_token" |
| 86 | + caller <- caller_call() |
| 87 | + if (!is_null(caller)) { |
| 88 | + caller_name <- call_name(caller) |
| 89 | + } |
| 90 | + |
| 91 | + cli_bullets( |
| 92 | + c( |
| 93 | + "v" = "{.envvar {default}} set to {.val {token}} with {.fn Sys.setenv}.", |
| 94 | + "*" = "To use this token in future sessions, call |
| 95 | + {.fn {caller_name}} using {.arg install = TRUE}." |
| 96 | + ) |
| 97 | + ) |
| 98 | + Sys.setenv(default = token) |
| 99 | + return(invisible(token)) |
| 100 | + } |
| 101 | + |
| 102 | + home <- Sys.getenv("HOME") |
| 103 | + renv <- file.path(home, ".Renviron") |
| 104 | + |
| 105 | + if (file.exists(renv)) { |
| 106 | + default_match <- grepl(paste0("^", default, "(?=\\=)"), |
| 107 | + readLines(renv), |
| 108 | + perl = TRUE |
| 109 | + ) |
| 110 | + |
| 111 | + has_default <- any(default_match) |
| 112 | + |
| 113 | + if (has_default && !overwrite) { |
| 114 | + cli_abort( |
| 115 | + c("{.envvar {default}} already exists in your {.file .Renviron}.", |
| 116 | + "*" = "Set {.arg overwrite = TRUE} to replace this token." |
| 117 | + ), |
| 118 | + call = call |
| 119 | + ) |
| 120 | + } |
| 121 | + backup <- file.path(home, ".Renviron_backup") |
| 122 | + file.copy(renv, backup) |
| 123 | + cli_alert_success("{.file .Renviron} backed up to {.path {backup}}.") |
| 124 | + |
| 125 | + if (has_default) { |
| 126 | + oldenv <- utils::read.table(renv, stringsAsFactors = FALSE) |
| 127 | + newenv <- oldenv[!default_match, ] |
| 128 | + utils::write.table( |
| 129 | + newenv, renv, |
| 130 | + quote = FALSE, |
| 131 | + sep = "\n", col.names = FALSE, row.names = FALSE |
| 132 | + ) |
| 133 | + } |
| 134 | + } else { |
| 135 | + file.create(renv) |
| 136 | + } |
| 137 | + |
| 138 | + write(paste0(default, '="', token, '"'), renv, sep = "\n", append = TRUE) |
| 139 | + |
| 140 | + cli_bullets( |
| 141 | + c( |
| 142 | + "v" = "{.val {token}} saved to {.file .Renviron} variable {.envvar {default}}.", |
| 143 | + "*" = "Restart R or run {.code readRenviron(\"~/.Renviron\")} then use |
| 144 | + {.code Sys.getenv(\"{default}\")} to access the token." |
| 145 | + ) |
| 146 | + ) |
| 147 | + |
| 148 | + invisible(token) |
| 149 | +} |
| 150 | + |
| 151 | +#' @rdname set_r_environ_token |
| 152 | +#' @name get_r_environ_token |
| 153 | +#' @param message Optional error message to use if token can't be found. |
| 154 | +#' @param pattern Optional pattern passed to [grepl()] and used to validate the |
| 155 | +#' stored token. If pattern is supplied, the returned token must be a string. |
| 156 | +#' @param perl Should Perl-compatible regexps be used when checking `pattern`? |
| 157 | +#' Defaults to `TRUE`. |
| 158 | +#' @param strict If `TRUE` (default), error if no environment variable with the |
| 159 | +#' supplied name is found. If `FALSE`, warn instead of error. |
| 160 | +#' @returns [get_r_environ_token()] returns a string supplied to `token` or |
| 161 | +#' obtained from the environment variable named with `default`. |
| 162 | +#' |
| 163 | +#' @keywords internal |
| 164 | +#' |
| 165 | +#' @importFrom rlang caller_arg %||% is_empty is_null |
| 166 | +#' @importFrom cli cli_abort |
| 167 | +get_r_environ_token <- function(token = NULL, |
| 168 | + default = "TOKEN", |
| 169 | + message = NULL, |
| 170 | + pattern = NULL, |
| 171 | + perl = TRUE, |
| 172 | + strict = TRUE, |
| 173 | + call = caller_env(), |
| 174 | + ...) { |
| 175 | + settoken_check_string(default, call = call) |
| 176 | + |
| 177 | + token <- token %||% Sys.getenv(default) |
| 178 | + |
| 179 | + if (!is_empty(token) && !identical(token, "")) { |
| 180 | + if (is_null(pattern)) { |
| 181 | + return(token) |
| 182 | + } |
| 183 | + |
| 184 | + settoken_check_string(pattern, call = call) |
| 185 | + settoken_check_string(token, call = call) |
| 186 | + |
| 187 | + if (grepl(pattern, token, perl = perl)) { |
| 188 | + return(token) |
| 189 | + } |
| 190 | + |
| 191 | + message <- "{.arg token} must match the supplied pattern: {.val {pattern}}" |
| 192 | + } |
| 193 | + |
| 194 | + message <- message %||% |
| 195 | + "{.arg token} is empty and {.envvar {default}} can't be found in {.file .Renviron}" |
| 196 | + |
| 197 | + if (!strict) { |
| 198 | + cli_warn( |
| 199 | + message = message, |
| 200 | + ... |
| 201 | + ) |
| 202 | + |
| 203 | + return(invisible(NULL)) |
| 204 | + } |
| 205 | + |
| 206 | + cli_abort( |
| 207 | + message = message, |
| 208 | + ..., |
| 209 | + call = call |
| 210 | + ) |
| 211 | +} |
| 212 | + |
| 213 | +#' Check if x object is a string and error if not |
| 214 | +#' |
| 215 | +#' @noRd |
| 216 | +#' @importFrom rlang caller_arg caller_env is_string |
| 217 | +#' @importFrom cli cli_abort |
| 218 | +settoken_check_string <- function(x, |
| 219 | + ..., |
| 220 | + allow_empty = FALSE, |
| 221 | + arg = caller_arg(x), |
| 222 | + call = caller_env()) { |
| 223 | + if (is_string(x) && (allow_empty || !is_string(x, ""))) { |
| 224 | + return(invisible(NULL)) |
| 225 | + } |
| 226 | + |
| 227 | + cli_abort( |
| 228 | + "{.arg {arg}} must be a string, not {.obj_type_friendly {x}}", |
| 229 | + ..., |
| 230 | + call = call |
| 231 | + ) |
| 232 | +} |
0 commit comments