From d5e1f2e0024761effbc966f31668f140245c270a Mon Sep 17 00:00:00 2001 From: "james.balamuta@gmail.com" Date: Mon, 26 May 2025 17:09:44 -0700 Subject: [PATCH 1/4] Enable reserved words being converted. Add check for NA and enforce character Avoid appending suffix is it's empty --- R/utilities.R | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/R/utilities.R b/R/utilities.R index 2cda026..61ac75a 100644 --- a/R/utilities.R +++ b/R/utilities.R @@ -73,7 +73,7 @@ open_web_browser = function(url) { # nocov start #' [utils::URLencode()] #' @noRd encode_url = function(base, unencoded_query, encoded_query = "") { - paste0(base, utils::URLencode(unencoded_query), encoded_query) + paste0(base, utils::URLencode(unencoded_query, reserved = TRUE), encoded_query) } #' Validate search query @@ -94,10 +94,10 @@ encode_url = function(base, unencoded_query, encoded_query = "") { #' valid_query() #' valid_query(NULL) #' valid_query("") -#' valid_query(c(1,2)) +#' valid_query(c(1, 2)) #' @noRd valid_query = function(query) { - if(missing(query) || is.null(query) || length(query) != 1 || query == "") { + if(missing(query) || anyNA(query) || !is.character(query) || length(query) != 1 || query == "") { FALSE } else { TRUE @@ -126,7 +126,7 @@ valid_query = function(query) { #' @noRd append_search_term_suffix = function(query, rlang = TRUE, suffix = "r programming") { - if (rlang && !is.null(suffix)) + if (rlang && !is.null(suffix) && suffix != "") paste(query, suffix) else query From 37015ce065771de6738fd9abf1f480f1c00f874b Mon Sep 17 00:00:00 2001 From: "james.balamuta@gmail.com" Date: Mon, 26 May 2025 17:10:22 -0700 Subject: [PATCH 2/4] Improve unit tests --- tests/testthat/test-utilities.R | 137 +++++++++++++++++++++++++++++--- 1 file changed, 127 insertions(+), 10 deletions(-) diff --git a/tests/testthat/test-utilities.R b/tests/testthat/test-utilities.R index 627b80d..d39eec7 100644 --- a/tests/testthat/test-utilities.R +++ b/tests/testthat/test-utilities.R @@ -1,14 +1,131 @@ -test_that("Detect RStudio", { - # Obtain value - before = Sys.getenv("RSTUDIO") +# Tests for Utility Functions - # Modify check environment variable - Sys.setenv("RSTUDIO" = 1) - expect_true(is_rstudio(), info = "Within RStudio") +## Test is_rstudio() ---- +test_that("is_rstudio(): detects RStudio environment correctly", { + # Save original environment variable + original_rstudio <- Sys.getenv("RSTUDIO") - Sys.setenv("RSTUDIO" = 0) - expect_false(is_rstudio(), info = "Not in RStudio") + # Test when in RStudio + Sys.setenv("RSTUDIO" = "1") + expect_true(is_rstudio(), info = "Should detect RStudio when RSTUDIO=1") - # Restore - Sys.setenv("RSTUDIO" = before) + # Test when not in RStudio + Sys.setenv("RSTUDIO" = "0") + expect_false(is_rstudio(), info = "Should not detect RStudio when RSTUDIO=0") + + # Test with empty environment variable + Sys.setenv("RSTUDIO" = "") + expect_false(is_rstudio(), info = "Should not detect RStudio when RSTUDIO is empty") + + # Restore original environment + Sys.setenv("RSTUDIO" = original_rstudio) +}) + + +## Test valid_query() ---- +test_that("valid_query validates queries correctly", { + # Valid queries + expect_true(valid_query("valid query")) + expect_true(valid_query("a")) + expect_true(valid_query("query with spaces and numbers 123")) + expect_true(valid_query("special chars: !@#$%^&*()")) + + # Invalid queries + expect_false(valid_query("")) + expect_false(valid_query(NULL)) + expect_false(valid_query(c("multiple", "elements"))) + expect_false(valid_query(character(0))) + expect_false(valid_query(NA_character_)) + expect_false(valid_query(123)) # Not a character + + # Test missing argument + expect_false(valid_query()) +}) + +## Test encode_url() ---- +test_that("encode_url() creates properly formatted URLs", { + base_url <- "https://example.com/search?q=" + + # Test basic encoding + result <- encode_url(base_url, "simple query") + expect_equal(result, "https://example.com/search?q=simple%20query") + + # Test with special characters + result <- encode_url(base_url, "query with & and + symbols") + expect_true(grepl("%26", result)) # & becomes %26 + expect_true(grepl("%2B", result)) # + becomes %2B + expect_true(grepl("%20", result)) # space becomes %20 + + # Test with encoded query suffix + result <- encode_url(base_url, "query", "&type=test") + expect_equal(result, "https://example.com/search?q=query&type=test") + + # Test with empty query + result <- encode_url(base_url, "") + expect_equal(result, "https://example.com/search?q=") + + # Test with complex characters + result <- encode_url(base_url, "R [language] test") + expect_true(grepl("%5B", result)) # [ becomes %5B + expect_true(grepl("%5D", result)) # ] becomes %5D +}) + +# Test append_search_term_suffix() ---- +test_that("append_search_term_suffix(): works correctly", { + # Test with rlang = TRUE (default behavior) + result <- append_search_term_suffix("test query", rlang = TRUE, suffix = "r programming") + expect_equal(result, "test query r programming") + + # Test with rlang = FALSE + result <- append_search_term_suffix("test query", rlang = FALSE, suffix = "r programming") + expect_equal(result, "test query") + + # Test with NULL suffix + result <- append_search_term_suffix("test query", rlang = TRUE, suffix = NULL) + expect_equal(result, "test query") + + # Test with empty suffix + result <- append_search_term_suffix("test query", rlang = TRUE, suffix = "") + expect_equal(result, "test query") + + # Test with custom suffix + result <- append_search_term_suffix("test", rlang = TRUE, suffix = "custom") + expect_equal(result, "test custom") + + # Test edge cases + result <- append_search_term_suffix("", rlang = TRUE, suffix = "r programming") + expect_equal(result, " r programming") + + result <- append_search_term_suffix("test", rlang = FALSE, suffix = "ignored") + expect_equal(result, "test") +}) + +# Test browse_url() ---- +test_that("browse_url(): generates correct URLs without opening browser", { + base_url <- "https://example.com/search?q=" + query <- "test query" + + # Test basic URL generation (open_browser = FALSE) + expect_message( + result <- browse_url(base_url, query, open_browser = FALSE), + "Please type into your browser" + ) + expect_equal(result, "https://example.com/search?q=test%20query") + + # Test with encoded suffix + expect_message( + result <- browse_url(base_url, query, "&type=issues", open_browser = FALSE), + "Please type into your browser" + ) + expect_equal(result, "https://example.com/search?q=test%20query&type=issues") + + # Test with special characters + special_query <- "R [language] & symbols" + expect_message( + result <- browse_url(base_url, special_query, open_browser = FALSE), + "Please type into your browser" + ) + expect_true(grepl("%5B", result)) # [ is encoded + expect_true(grepl("%5D", result)) # ] is encoded + expect_true(grepl("%26", result)) # & is encoded }) From bb7b6807a5c34af44492d220a0469af682daf013 Mon Sep 17 00:00:00 2001 From: "james.balamuta@gmail.com" Date: Mon, 26 May 2025 17:10:32 -0700 Subject: [PATCH 3/4] Update website URLs based on new results --- tests/testthat/test-search-functions.R | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/testthat/test-search-functions.R b/tests/testthat/test-search-functions.R index 708741b..677e7e4 100644 --- a/tests/testthat/test-search-functions.R +++ b/tests/testthat/test-search-functions.R @@ -72,7 +72,7 @@ test_that("Check link generation - rseek", { expect_identical( search_rseek("toad"), - "https://rseek.org/?q=toad%20" + "https://rseek.org/?q=toad" ) expect_identical( @@ -174,7 +174,7 @@ test_that("Check link generation - stackoverflow", { expect_identical( search_stackoverflow("toad"), - "https://stackoverflow.com/search?q=toad%20[r]" + "https://stackoverflow.com/search?q=toad%20%5Br%5D" ) expect_identical( @@ -188,7 +188,7 @@ test_that("Check link generation - github", { expect_identical( search_github("toad"), - "https://github.com/search?q=toad%20language:r%20type:issue&type=Issues" + "https://github.com/search?q=toad%20language%3Ar%20type%3Aissue&type=Issues" ) expect_identical( @@ -202,7 +202,7 @@ test_that("Check link generation - bitbucket", { expect_identical( search_bitbucket("toad"), - "https://bitbucket.com/search?q=toad%20lang:r" + "https://bitbucket.com/search?q=toad%20lang%3Ar" ) expect_identical( From 7400ba51f76173fbc3c8b00ea9ee4fcf877dffab Mon Sep 17 00:00:00 2001 From: "james.balamuta@gmail.com" Date: Tue, 27 May 2025 12:16:27 -0700 Subject: [PATCH 4/4] Add AI service prompts to README --- README.Rmd | 14 ++++++++------ README.md | 25 +++++++++++++++---------- 2 files changed, 23 insertions(+), 16 deletions(-) diff --git a/README.Rmd b/README.Rmd index 994bf03..8e343c1 100644 --- a/README.Rmd +++ b/README.Rmd @@ -269,12 +269,14 @@ Presently, the following options are available: viewer pane instead of a web browser. Default is `FALSE`. - `searcher.default_keyword`: Suffix keyword to focus search results between either `"base"` or `"tidyverse"`. Default is `"base"`. -- `searcher.chatgpt_prompt`: Default prompt for ChatGPT queries, used if no specific prompt is provided. -- `searcher.claude_prompt`: Default prompt for Claude queries. -- `searcher.perplexity_prompt`: Default prompt for Perplexity queries. -- `searcher.mistral_prompt`: Default prompt for Mistral AI queries. -- `searcher.bing_copilot_prompt`: Default prompt for Bing Copilot queries. -- `searcher.meta_ai_prompt`: Default prompt for Meta AI queries. +- AI service prompts: + - `searcher.chatgpt_prompt`: Default prompt for OpenAI's ChatGPT queries. + - `searcher.claude_prompt`: Default prompt for Anthropic's Claude queries. + - `searcher.perplexity_prompt`: Default prompt for Perplexity AI queries. + - `searcher.mistral_prompt`: Default prompt for Mistral AI queries. + - `searcher.bing_copilot_prompt`: Default prompt for Bing Copilot queries. + - `searcher.grok_prompt`: Default prompt for xAI's Grok AI queries. + - `searcher.meta_ai_prompt`: Default prompt for Meta AI queries. To set one of these options, please create the `.Rprofile` by typing into _R_: diff --git a/README.md b/README.md index dfb4ab5..acab381 100644 --- a/README.md +++ b/README.md @@ -165,8 +165,9 @@ ask_chatgpt( ) ``` -See `vignette("search-with-ai-assistants", package = "searcher")` for -more details on using AI assistants in searches through `searcher`. +See +`vignette("using-ai-assistants-with-searcher", package = "searcher")` +for more details on using AI assistants in searches through `searcher`. ## AI Prompt Management @@ -280,14 +281,18 @@ Presently, the following options are available: viewer pane instead of a web browser. Default is `FALSE`. - `searcher.default_keyword`: Suffix keyword to focus search results between either `"base"` or `"tidyverse"`. Default is `"base"`. -- `searcher.chatgpt_prompt`: Default prompt for ChatGPT queries, used if - no specific prompt is provided. -- `searcher.claude_prompt`: Default prompt for Claude queries. -- `searcher.perplexity_prompt`: Default prompt for Perplexity queries. -- `searcher.mistral_prompt`: Default prompt for Mistral AI queries. -- `searcher.bing_copilot_prompt`: Default prompt for Bing Copilot - queries. -- `searcher.meta_ai_prompt`: Default prompt for Meta AI queries. +- AI service prompts: + - `searcher.chatgpt_prompt`: Default prompt for OpenAI’s ChatGPT + queries. + - `searcher.claude_prompt`: Default prompt for Anthropic’s Claude + queries. + - `searcher.perplexity_prompt`: Default prompt for Perplexity AI + queries. + - `searcher.mistral_prompt`: Default prompt for Mistral AI queries. + - `searcher.bing_copilot_prompt`: Default prompt for Bing Copilot + queries. + - `searcher.grok_prompt`: Default prompt for xAI’s Grok AI queries. + - `searcher.meta_ai_prompt`: Default prompt for Meta AI queries. To set one of these options, please create the `.Rprofile` by typing into *R*: