diff --git a/DESCRIPTION b/DESCRIPTION index 4f74667..78f083a 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -14,4 +14,5 @@ Config/rextendr/version: 0.3.1.9000 SystemRequirements: Cargo (rustc package manager) Imports: blob, + cli, rlang diff --git a/NAMESPACE b/NAMESPACE index b5e751a..6d19436 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -1,12 +1,14 @@ # Generated by roxygen2: do not edit by hand -S3method(format,alphabet) S3method(print,alphabet) +S3method(print,b64_config) +S3method(print,engine) export(alphabet) -export(decode) export(decode_file) export(encode) export(encode_file) export(engine) +export(new_config) +export(new_engine) importFrom(blob,blob) useDynLib(b64, .registration = TRUE) diff --git a/R/alphabet.R b/R/alphabet.R index 8e4e1a8..348f3bd 100644 --- a/R/alphabet.R +++ b/R/alphabet.R @@ -1,26 +1,34 @@ -#' @export -format.alphabet <- function(x, ...) { - cat("\n") - cat(get_alphabet_(x)) -} - -#' @export -print.alphabet <- function(x, ...) { - format(x, ...) -} - - #' Standard base64 alphabets #' +#' Create an alphabet from a set of standard base64 alphabets, or use your own. +#' #' @param which default `"standard"`. Which base64 alphabet to use. +#' See details for other values. +#' @param chars a character scalar contains 64 unique characters. #' #' @details #' -#' - `"bcrypt"`: the bcrypt alphabet +#' - `"bcrypt"`: bcrypt alphabet +#' - `"bin_hex"`: alphabet used in BinHex 4.0 files +#' - `"crypt"`: crypt(3) alphabet (with . and / as the first two characters) +#' - `"imap_mutf7"`: alphabet used in IMAP-modified UTF-7 (with + and ,) +#' - `"standard"`: standard alphabet (with + and /) specified in RFC 4648 +#' - `"url_safe"`: URL-safe alphabet (with - and _) specified in RFC 4648 #' #' See [base64 crate](https://docs.rs/base64/latest/base64/alphabet/index.html#constants) -#' for more details. +#' from where these definitions come. +#' #' @export +#' @examples +#' alphabet("standard") +#' alphabet("bcrypt") +#' alphabet("bin_hex") +#' alphabet("crypt") +#' alphabet("imap_mutf7") +#' alphabet("url_safe") +#' +#' new_alphabet("qwertyuiop[]asdfghjklzxcvbnmQWERTYUIOPASDFGHJKLZXCVBNM1234567890") +#' @returns an object of class `alphabet` alphabet <- function(which = "standard") { rlang::arg_match( which, @@ -29,3 +37,25 @@ alphabet <- function(which = "standard") { structure(alphabet_(which), class = "alphabet") } + +new_alphabet <- function(chars) { + n <- nchar(chars) + if (nchar(chars) != 64) { + cli::cli_abort( + c( + "{.arg chars} must be 64 unique characters", + "i" = "{n} characters provided" + ) + ) + } + + structure(new_alphabet_(chars), class = "alphabet") +} + + +#' @export +print.alphabet <- function(x, ...) { + cat("\n") + cat(get_alphabet_(x)) + invisible(x) +} diff --git a/R/config.R b/R/config.R new file mode 100644 index 0000000..00cdd42 --- /dev/null +++ b/R/config.R @@ -0,0 +1,54 @@ +#' Create a custom encoding engine +#' +#' @details +#' +#' See [base64 crate](https://docs.rs/base64/latest/base64/engine/general_purpose/struct.GeneralPurposeConfig.html#method.with_encode_padding) for more details. +#' +#' ## Decode Padding Modes +#' +#' There are three modes that can be used for `decode_padding_mode` argument. +#' +#' - `"canonical"`: padding must consist of 0, 1, or 2 `=` characters +#' - `"none"`: there must be no padding characters present +#' - `"indifferent"`: canonical padding is used, but omitted padding +#' characters are also permitted +#' +#' @param encode_padding default `TRUE` add 1-2 trailing `=` to pad results +#' @param decode_padding_trailing_bits default `FALSE`. "If invalid trailing bits are present and this is true, those bits will be silently ignored." (See details for reference). +#' @param decode_padding_mode default `"canonical"`. Other values are `"indifferent"` and `"none"`. See details for more. +#' @export +#' @return an object of class `b64_config` +new_config <- function( + encode_padding = TRUE, + decode_padding_trailing_bits = FALSE, + decode_padding_mode = c("canonical", "indifferent", "none") +) { + + padding_mode <- rlang::arg_match0( + decode_padding_mode, + values = c("canonical", "indifferent", "none") + ) + + res <- new_config_( + encode_padding, + decode_padding_trailing_bits, + padding_mode + ) + + structure(res, class = "b64_config") +} + +# shoddy print method for the time being + +#' @export +print.b64_config <- function(x, ...) { + y <- print_config_(x) + + z <- trimws(strsplit(y, "\n")[[1]][2:4]) + + cat("\n") + cat(gsub(",", "", z), sep = "\n") + invisible(x) +} + + diff --git a/R/decode.R b/R/decode.R deleted file mode 100644 index 8f2928b..0000000 --- a/R/decode.R +++ /dev/null @@ -1,20 +0,0 @@ -#' Decode base64 encodings -#' -#' @inheritParams encode -#' @export -#' @returns -#' `decode()` always returns a `blob` object. `decode_file()` returns a `raw` vector. -decode <- function(what, eng = engine()) { - n <- length(what) - if (inherits(what, "raw") || (n == 1 & inherits(what, "character"))) { - decode_(what, eng) - } else { - decode_vectorized_(what, eng) - } -} - -#' @export -#' @rdname decode -decode_file <- function(path, eng = engine()) { - decode_file_(path, eng) -} diff --git a/R/encode.R b/R/encode.R index bd6ba87..ca89cd4 100644 --- a/R/encode.R +++ b/R/encode.R @@ -1,12 +1,12 @@ -#' Encode as base64 +#' Encode and decode using base64 #' -#' Perform base 64 encoding. +#' @param what a character, raw, or blob vector +#' @param eng a base64 engine. See [engine()] for details. +#' @param path a path to a base64 encoded file. #' -#' @param what a scalar character or a raw vector. -#' @param engine a base64 engine. See [engine()] for details. #' @return -#' `encode()` is vectorized and will return a character vector of the same -#' lenght as `what`. +#' Both `encode()` and `decode()` are vectorized. They will return a character +#' and blob vector the same length as `what`, respectively. #' @export #' @name encode encode <- function(what, eng = engine()) { @@ -18,8 +18,34 @@ encode <- function(what, eng = engine()) { } } +#' @rdname encode +decode <- function(what, eng = engine()) { + n <- length(what) + if (inherits(what, "raw") || (n == 1 & inherits(what, "character"))) { + decode_(what, eng) + } else { + decode_vectorized_(what, eng) + } +} + + #' @export #' @name encode encode_file <- function(path, eng = engine()) { + if (!file.exists(path)) { + cli::cli_abort("{.arg path} does not exist") + } + encode_file_(path, eng) } + + +#' @export +#' @rdname encode +decode_file <- function(path, eng = engine()) { + if (!file.exists(path)) { + cli::cli_abort("{.arg path} does not exist") + } + + decode_file_(path, eng) +} diff --git a/R/engine.R b/R/engine.R index 8ce64fc..01a6725 100644 --- a/R/engine.R +++ b/R/engine.R @@ -1,7 +1,8 @@ -#' Choose an encoding engine +#' Create an encoding engine #' #' @param which default `"standard"`. The base64 encoding engine to be used. #' See details for more. +#' @param .alphabet an object of class `alphabet` as created with [`alphabet()`] or [`new_alphabet()`] #' @details #' #' ## Engines @@ -20,8 +21,39 @@ #' @export #' @examples #' engine() +#' new_engine(alphabet("bcrypt"), new_config()) engine <- function(which = "standard") { provided <- c("standard", "standard_no_pad", "url_safe", "url_safe_no_pad") rlang::arg_match0(which, provided) structure(engine_(which), class = "engine") } + +#' @export +#' @rdname engine +new_engine <- function(.alphabet = alphabet(), .config = new_config()) { + + if (!rlang::inherits_only(.alphabet, "alphabet")) { + cli::cli_abort( + c( + "{.arg .alphabet} is not an object of class {.cls alphabet}", + "*" = "use {.fn alphabet} for a standard base64 alphabet" + ) + ) + } else if (!rlang::inherits_only(.config, "b64_config")) { + cli::cli_abort( + c( + "{.arg config} is not a {.cls b64_config} object", + "*" = "create one with {.fn new_config}" + ) + ) + } + + res <- new_engine_(.alphabet, .config) + structure(res, class = "engine") +} + +#' @export +print.engine <- function(x, ...) { + cat("") + invisible(x) +} diff --git a/R/extendr-wrappers.R b/R/extendr-wrappers.R index 8ba0d14..76ad2f4 100644 --- a/R/extendr-wrappers.R +++ b/R/extendr-wrappers.R @@ -25,7 +25,7 @@ decode_vectorized_ <- function(what, engine) .Call(wrap__decode_vectorized_, wha alphabet_ <- function(which) .Call(wrap__alphabet_, which) -new_alphabet <- function(chars) .Call(wrap__new_alphabet, chars) +new_alphabet_ <- function(chars) .Call(wrap__new_alphabet_, chars) get_alphabet_ <- function(alphabet) .Call(wrap__get_alphabet_, alphabet) @@ -33,10 +33,12 @@ new_engine_ <- function(alphabet, config) .Call(wrap__new_engine_, alphabet, con engine_ <- function(which) .Call(wrap__engine_, which) -print_engine_ <- function(`_engine`) invisible(.Call(wrap__print_engine_, `_engine`)) +print_engine_ <- function(engine) .Call(wrap__print_engine_, engine) new_config_ <- function(encode_padding, decode_padding_trailing_bits, decode_padding_mode) .Call(wrap__new_config_, encode_padding, decode_padding_trailing_bits, decode_padding_mode) +print_config_ <- function(config) .Call(wrap__print_config_, config) + chunk_b64 <- function(encoded, size) .Call(wrap__chunk_b64, encoded, size) line_wrap <- function(chunks, newline) .Call(wrap__line_wrap, chunks, newline) diff --git a/README.Rmd b/README.Rmd index a6f755a..d08d3cf 100644 --- a/README.Rmd +++ b/README.Rmd @@ -109,10 +109,50 @@ bench::mark( Out of the box, `b64` provides a number of pre-configured engines that can be used. The function `engine()` allows you to choose one of these different engines For example, `engine("url_safe")` provides a standard engine that uses a url-safe alphabet with padding. + +```{r} +url_engine <- engine("url_safe") +url_safe_encoded <- encode("\xfa\xec U", url_engine) +url_safe_encoded +``` + +If we try to decode this using the standard engine, we will encounter an error. + +```{r error=TRUE} +decode(url_safe_encoded) +``` + +We can use our new engine to decode it. + ```{r} -unsafe_chars <- charToRaw("-uwgVQA=") +decode(url_safe_encoded, url_engine) +``` + +### Custom Engines + +We can create custom engines with `new_engine()`. This allows us to provide our on alphabet and configuration. + +We can use one of the many predefined alphabets or create one our selves with `new_alphabet()`. We can also specify our engine config using `new_config()` which lets us choose whether or not to pad and how to handle decoding. -decode(unsafe_chars, engine("url_safe")) +```{r} +my_eng <- new_engine( + alphabet("crypt"), + new_config(TRUE, TRUE, "none") +) +``` + +This engine can be used to encode or decode text. + +```{r} +txt <- "lorem ipsum sit dolor amet" + +encode(txt, my_eng) +``` + +Compare this to the standard encoder: + +```{r} +encode(txt) ``` diff --git a/README.md b/README.md index 6502035..78b011d 100644 --- a/README.md +++ b/README.md @@ -52,17 +52,19 @@ Both `encode()` and `decode()` are vectorized. ``` r lorem <- unlist(lorem::ipsum(5, 1, 5)) lorem -#> [1] "Sit eu eleifend id fringilla." "Dolor ad neque metus metus." -#> [3] "Dolor at curae proin." "Elit vivamus torquent taciti." -#> [5] "Dolor eget velit cum vehicula." +#> [1] "Amet diam nascetur nisi ad pharetra ante?" +#> [2] "Sit massa eu morbi nostra mi." +#> [3] "Dolor erat dui eu faucibus." +#> [4] "Sit volutpat per ridiculus donec massa lacus duis?" +#> [5] "Elit tempus neque phasellus laoreet maecenas ad?" encoded <- encode(lorem) encoded -#> [1] "U2l0IGV1IGVsZWlmZW5kIGlkIGZyaW5naWxsYS4=" -#> [2] "RG9sb3IgYWQgbmVxdWUgbWV0dXMgbWV0dXMu" -#> [3] "RG9sb3IgYXQgY3VyYWUgcHJvaW4u" -#> [4] "RWxpdCB2aXZhbXVzIHRvcnF1ZW50IHRhY2l0aS4=" -#> [5] "RG9sb3IgZWdldCB2ZWxpdCBjdW0gdmVoaWN1bGEu" +#> [1] "QW1ldCBkaWFtIG5hc2NldHVyIG5pc2kgYWQgcGhhcmV0cmEgYW50ZT8=" +#> [2] "U2l0IG1hc3NhIGV1IG1vcmJpIG5vc3RyYSBtaS4=" +#> [3] "RG9sb3IgZXJhdCBkdWkgZXUgZmF1Y2lidXMu" +#> [4] "U2l0IHZvbHV0cGF0IHBlciByaWRpY3VsdXMgZG9uZWMgbWFzc2EgbGFjdXMgZHVpcz8=" +#> [5] "RWxpdCB0ZW1wdXMgbmVxdWUgcGhhc2VsbHVzIGxhb3JlZXQgbWFlY2VuYXMgYWQ/" ``` We can decode all of these using `decode()` as well. This will always @@ -71,7 +73,7 @@ return a `blob` object. ``` r decode(encoded) #> -#> [1] blob[29 B] blob[27 B] blob[21 B] blob[29 B] blob[30 B] +#> [1] blob[41 B] blob[29 B] blob[27 B] blob[50 B] blob[48 B] ``` ## Encoding and decoding files @@ -95,8 +97,8 @@ bench::mark( #> # A tibble: 2 × 6 #> expression min median `itr/sec` mem_alloc `gc/sec` #> -#> 1 b64 40.3ms 41.5ms 23.1 24MB 0 -#> 2 base64enc 110.4ms 113.2ms 8.72 66.5MB 17.4 +#> 1 b64 40.1ms 41.1ms 24.3 24MB 0 +#> 2 base64enc 112.5ms 112.8ms 8.81 66.5MB 17.6 ``` While the encoding is very impressive, better yet is the decoding @@ -118,8 +120,8 @@ bench::mark( #> # A tibble: 2 × 6 #> expression min median `itr/sec` mem_alloc `gc/sec` #> -#> 1 b64 17.8ms 20.2ms 49.3 18MB 9.39 -#> 2 base64enc 206.4ms 206.7ms 4.83 18MB 0 +#> 1 b64 16.3ms 16.8ms 59.3 18MB 9.49 +#> 2 base64enc 207.6ms 207.6ms 4.82 18MB 2.41 ``` ## Alternative engines @@ -130,11 +132,59 @@ different engines For example, `engine("url_safe")` provides a standard engine that uses a url-safe alphabet with padding. ``` r -unsafe_chars <- charToRaw("-uwgVQA=") +url_engine <- engine("url_safe") +url_safe_encoded <- encode("\xfa\xec U", url_engine) +url_safe_encoded +#> [1] "-uwgVQ==" +``` + +If we try to decode this using the standard engine, we will encounter an +error. + +``` r +decode(url_safe_encoded) +#> Error in decode_(what, eng): Invalid byte 45, offset 0. +``` + +We can use our new engine to decode it. -decode(unsafe_chars, engine("url_safe")) +``` r +decode(url_safe_encoded, url_engine) #> -#> [1] blob[5 B] +#> [1] blob[4 B] +``` + +### Custom Engines + +We can create custom engines with `new_engine()`. This allows us to +provide our on alphabet and configuration. + +We can use one of the many predefined alphabets or create one our selves +with `new_alphabet()`. We can also specify our engine config using +`new_config()` which lets us choose whether or not to pad and how to +handle decoding. + +``` r +my_eng <- new_engine( + alphabet("crypt"), + new_config(TRUE, TRUE, "none") +) +``` + +This engine can be used to encode or decode text. + +``` r +txt <- "lorem ipsum sit dolor amet" + +encode(txt, my_eng) +#> [1] "P4xmNKoUOL/nRKoUQqZo64FjP4xm643hNLE=" +``` + +Compare this to the standard encoder: + +``` r +encode(txt) +#> [1] "bG9yZW0gaXBzdW0gc2l0IGRvbG9yIGFtZXQ=" ``` ## TODO diff --git a/man/alphabet.Rd b/man/alphabet.Rd index ff32e09..f5ecfd7 100644 --- a/man/alphabet.Rd +++ b/man/alphabet.Rd @@ -7,16 +7,37 @@ alphabet(which = "standard") } \arguments{ -\item{which}{default \code{"standard"}. Which base64 alphabet to use.} +\item{which}{default \code{"standard"}. Which base64 alphabet to use. +See details for other values.} + +\item{chars}{a character scalar contains 64 unique characters.} +} +\value{ +an object of class \code{alphabet} } \description{ -Standard base64 alphabets +Create an alphabet from a set of standard base64 alphabets, or use your own. } \details{ \itemize{ -\item \code{"bcrypt"}: the bcrypt alphabet +\item \code{"bcrypt"}: bcrypt alphabet +\item \code{"bin_hex"}: alphabet used in BinHex 4.0 files +\item \code{"crypt"}: crypt(3) alphabet (with . and / as the first two characters) +\item \code{"imap_mutf7"}: alphabet used in IMAP-modified UTF-7 (with + and ,) +\item \code{"standard"}: standard alphabet (with + and /) specified in RFC 4648 +\item \code{"url_safe"}: URL-safe alphabet (with - and _) specified in RFC 4648 } See \href{https://docs.rs/base64/latest/base64/alphabet/index.html#constants}{base64 crate} -for more details. +from where these definitions come. +} +\examples{ +alphabet("standard") +alphabet("bcrypt") +alphabet("bin_hex") +alphabet("crypt") +alphabet("imap_mutf7") +alphabet("url_safe") + +new_alphabet("qwertyuiop[]asdfghjklzxcvbnmQWERTYUIOPASDFGHJKLZXCVBNM1234567890") } diff --git a/man/decode.Rd b/man/decode.Rd deleted file mode 100644 index 081727b..0000000 --- a/man/decode.Rd +++ /dev/null @@ -1,20 +0,0 @@ -% Generated by roxygen2: do not edit by hand -% Please edit documentation in R/decode.R -\name{decode} -\alias{decode} -\alias{decode_file} -\title{Decode base64 encodings} -\usage{ -decode(what, eng = engine()) - -decode_file(path, eng = engine()) -} -\arguments{ -\item{what}{a scalar character or a raw vector.} -} -\value{ -\code{decode()} always returns a \code{blob} object. \code{decode_file()} returns a \code{raw} vector. -} -\description{ -Decode base64 encodings -} diff --git a/man/encode.Rd b/man/encode.Rd index 691af67..efcfb6c 100644 --- a/man/encode.Rd +++ b/man/encode.Rd @@ -2,22 +2,30 @@ % Please edit documentation in R/encode.R \name{encode} \alias{encode} +\alias{decode} \alias{encode_file} -\title{Encode as base64} +\alias{decode_file} +\title{Encode and decode using base64} \usage{ encode(what, eng = engine()) +decode(what, eng = engine()) + encode_file(path, eng = engine()) + +decode_file(path, eng = engine()) } \arguments{ -\item{what}{a scalar character or a raw vector.} +\item{what}{a character, raw, or blob vector} + +\item{eng}{a base64 engine. See \code{\link[=engine]{engine()}} for details.} -\item{engine}{a base64 engine. See \code{\link[=engine]{engine()}} for details.} +\item{path}{a path to a base64 encoded file.} } \value{ -\code{encode()} is vectorized and will return a character vector of the same -lenght as \code{what}. +Both \code{encode()} and \code{decode()} are vectorized. They will return a character +and blob vector the same length as \code{what}, respectively. } \description{ -Perform base 64 encoding. +Encode and decode using base64 } diff --git a/man/engine.Rd b/man/engine.Rd index 81fc3b1..552749b 100644 --- a/man/engine.Rd +++ b/man/engine.Rd @@ -2,16 +2,21 @@ % Please edit documentation in R/engine.R \name{engine} \alias{engine} -\title{Choose an encoding engine} +\alias{new_engine} +\title{Create an encoding engine} \usage{ engine(which = "standard") + +new_engine(.alphabet = alphabet(), .config = new_config()) } \arguments{ \item{which}{default \code{"standard"}. The base64 encoding engine to be used. See details for more.} + +\item{.alphabet}{an object of class \code{alphabet} as created with \code{\link[=alphabet]{alphabet()}} or \code{\link[=new_alphabet]{new_alphabet()}}} } \description{ -Choose an encoding engine +Create an encoding engine } \details{ \subsection{Engines}{ @@ -31,4 +36,5 @@ See \href{https://docs.rs/base64/latest/base64/engine/general_purpose/index.html } \examples{ engine() +new_engine(alphabet("bcrypt"), new_config()) } diff --git a/man/new_config.Rd b/man/new_config.Rd new file mode 100644 index 0000000..080f9cd --- /dev/null +++ b/man/new_config.Rd @@ -0,0 +1,38 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/config.R +\name{new_config} +\alias{new_config} +\title{Create a custom encoding engine} +\usage{ +new_config( + encode_padding = TRUE, + decode_padding_trailing_bits = FALSE, + decode_padding_mode = c("canonical", "indifferent", "none") +) +} +\arguments{ +\item{encode_padding}{default \code{TRUE} add 1-2 trailing \code{=} to pad results} + +\item{decode_padding_trailing_bits}{default \code{FALSE}. "If invalid trailing bits are present and this is true, those bits will be silently ignored." (See details for reference).} + +\item{decode_padding_mode}{default \code{"canonical"}. Other values are \code{"indifferent"} and \code{"none"}. See details for more.} +} +\value{ +an object of class \code{b64_config} +} +\description{ +Create a custom encoding engine +} +\details{ +See \href{https://docs.rs/base64/latest/base64/engine/general_purpose/struct.GeneralPurposeConfig.html#method.with_encode_padding}{base64 crate} for more details. +\subsection{Decode Padding Modes}{ + +There are three modes that can be used for \code{decode_padding_mode} argument. +\itemize{ +\item \code{"canonical"}: padding must consist of 0, 1, or 2 \code{=} characters +\item \code{"none"}: there must be no padding characters present +\item \code{"indifferent"}: canonical padding is used, but omitted padding +characters are also permitted +} +} +} diff --git a/src/rust/src/lib.rs b/src/rust/src/lib.rs index f105dcf..0f5f845 100644 --- a/src/rust/src/lib.rs +++ b/src/rust/src/lib.rs @@ -86,8 +86,19 @@ fn line_wrap(chunks: Strings, newline: &str) -> String { fn decode_(input: Either, engine: Robj) -> Robj { let eng: ExternalPtr = engine.try_into().unwrap(); let res = match input { - Either::Left(s) => eng.decode(s).unwrap(), - Either::Right(r) => eng.decode(r.as_slice()).unwrap(), + Either::Left(s) => { + let res = eng.decode(s); + match res { + Ok(d) => d, + Err(e) => throw_r_error(e.to_string().as_str()), + } + }, + Either::Right(r) => { + match eng.decode(r.as_slice()) { + Ok(d) => d, + Err(e) => throw_r_error(e.to_string().as_str()), + } + }, }; list!(Raw::from_bytes(&res)) @@ -171,9 +182,14 @@ fn alphabet_(which: &str) -> ExternalPtr { // Create new alphabet #[extendr] -fn new_alphabet(chars: &str) -> ExternalPtr { - let res = alphabet::Alphabet::new(chars).unwrap(); - ExternalPtr::new(res) +fn new_alphabet_(chars: &str) -> ExternalPtr { + let res = alphabet::Alphabet::new(chars); + + match res { + Ok(r) => ExternalPtr::new(r), + Err(e) => extendr_api::throw_r_error(&format!("Error creating alphabet: {}", e)), + } + } // get alphabet as a string for printing @@ -208,6 +224,12 @@ fn new_config_( ExternalPtr::new(config) } +#[extendr] +fn print_config_(config: Robj) -> String { + let conf: ExternalPtr = config.try_into().unwrap(); + format!("{:#?}", conf) +} + #[extendr] fn engine_(which: &str) -> ExternalPtr { match which { @@ -221,8 +243,9 @@ fn engine_(which: &str) -> ExternalPtr { // need to figure out a nice print pattern here #[extendr] -fn print_engine_(_engine: Robj) { - // let eng: ExternalPtr = engine.try_into().unwrap(); +fn print_engine_(engine: Robj) -> String { + let eng: ExternalPtr = engine.try_into().unwrap(); + format!("{:#?}", eng) } #[extendr] @@ -251,7 +274,7 @@ extendr_module! { // alphabets fn alphabet_; - fn new_alphabet; + fn new_alphabet_; fn get_alphabet_; // engines @@ -261,6 +284,7 @@ extendr_module! { // config fn new_config_; + fn print_config_; // helpers fn chunk_b64;