🔍 An RStudio addin slash regex utility belt
Ви не можете вибрати більше 25 тем Теми мають розпочинатися з літери або цифри, можуть містити дефіси (-) і не повинні перевищувати 35 символів.

210 lines
6.0KB

  1. #' RegExplain gadget
  2. #'
  3. #' The function behind the RegExplain Selection and RegExplain File
  4. #' addins. Opens the RegExplain gadget interface in an RStudio viewer
  5. #' pane.
  6. #'
  7. #' @examples
  8. #' \dontrun{
  9. #' regexplain_gadget(text = month.name, pattern = "(Ma|Ju)|(er)")
  10. #' regexplain_web(text = month.name, pattern = "(Ma|Ju)|(er)")
  11. #' regexplain_file()
  12. #' }
  13. #'
  14. #' @import miniUI
  15. #' @import shiny
  16. #' @param text Text to explore in gadget (editable using interface)
  17. #' @param pattern Regular Expression to edit or visualize using RegExplain
  18. #' @param start_page Open gadget to this tab, one of `"Text"`, `"RegEx"`,
  19. #' `"Output"`, or `"Help"`
  20. #'
  21. #' @return The regular expression built in the app is returned as a character
  22. #' string.
  23. #' @export
  24. regexplain_gadget <- function(
  25. text = NULL,
  26. pattern = NULL,
  27. start_page = if (is.null(text)) "Text" else "RegEx"
  28. ) {
  29. stopifnot(requireNamespace("miniUI"), requireNamespace("shiny"))
  30. viewer <- shiny::paneViewer(minHeight = 800)
  31. runGadget(
  32. regexplain_gadget_ui(text, pattern, start_page),
  33. regexplain_gadget_server(check_version()),
  34. viewer = viewer
  35. )
  36. }
  37. #' @describeIn regexplain_gadget Launches the RegExplain gadget in a browser or an
  38. #' RStduio viewer pane.
  39. #' @inheritDotParams shiny::shinyApp
  40. #' @export
  41. regexplain_web <- function(text = NULL, pattern = NULL, start_page = "Text", ...) {
  42. stopifnot(requireNamespace("miniUI"), requireNamespace("shiny"))
  43. shinyApp(
  44. regexplain_gadget_ui(text, pattern, start_page),
  45. regexplain_gadget_server(check_version()),
  46. ...
  47. )
  48. }
  49. # ---- Gadget Helper Functions and Variables ----
  50. sanitize_text_input <- function(x) {
  51. if (is.null(x) || !nchar(x)) {
  52. return(x)
  53. }
  54. rx_unicode <- "\\\\u[0-9a-f]{4,8}"
  55. rx_hex <- "\\\\x[0-9a-f]{2}|\\\\x\\{[0-9a-f]{1,6}\\}"
  56. rx_octal <- "\\\\[0][0-7]{1,3}"
  57. rx_escape <- paste(rx_unicode, rx_hex, rx_octal, sep = "|")
  58. if (grepl(rx_escape, x, ignore.case = TRUE)) {
  59. try(
  60. {
  61. y <- stringi::stri_unescape_unicode(x)
  62. },
  63. silent = TRUE
  64. )
  65. if (!is.na(y)) x <- y
  66. }
  67. # x <- gsub("\u201C|\u201D", '"', x)
  68. # x <- gsub("\u2018|\u2019", "'", x)
  69. x
  70. }
  71. toHTML <- function(...) {
  72. x <- paste(..., collapse = "")
  73. x <- gsub("\n", "\\\\n", x)
  74. x <- gsub("\t", "\\\\t", x)
  75. x <- gsub("\r", "\\\\r", x)
  76. HTML(x)
  77. }
  78. regexFn_choices <- list(
  79. "Choose a function" = "",
  80. base = c(
  81. "grep",
  82. "grepl",
  83. "sub", #<<
  84. "gsub", #<<
  85. "regexpr",
  86. "gregexpr",
  87. "regexec"
  88. ),
  89. stringr = c(
  90. "str_detect",
  91. "str_locate",
  92. "str_locate_all",
  93. "str_extract",
  94. "str_extract_all",
  95. "str_match",
  96. "str_match_all",
  97. "str_replace", #<<
  98. "str_replace_all", #<<
  99. "str_split"
  100. ),
  101. "rematch2" = c(
  102. "re_match",
  103. "re_match_all",
  104. "re_exec",
  105. "re_exec_all"
  106. )
  107. )
  108. available_regex_functions <- function() {
  109. pkgs <- c("stringr", "rematch2")
  110. has_pkg <- vapply(pkgs, requireNamespace, quietly = TRUE, logical(1))
  111. regexFn_choices[c("base", pkgs[has_pkg])]
  112. }
  113. regexFn_substitute <- c(
  114. paste0(c("", "g"), "sub"),
  115. paste0("str_replace", c("", "_all"))
  116. )
  117. get_pkg_namespace <- function(fn) {
  118. x <- names(purrr::keep(regexFn_choices, ~ (fn %in% .)))
  119. if (length(x) > 1) warning(fn, " matches multiple functions in regexFn_choices, please review.")
  120. x
  121. }
  122. #' Check if an updated version is available
  123. #'
  124. #' I included this because it can be difficult to tell if your RStudio Addins
  125. #' are up to date. I may add new features that you want but you won't hear about
  126. #' the updates. This function checks if an update is available, using GitHub
  127. #' tags. If an update is available, a modal dialog is shown when you start
  128. #' the regexplain gadget. This only happens once per R session, though, so feel
  129. #' free to ignore the message.
  130. #'
  131. #' @param gh_user GitHub user account
  132. #' @param gh_repo GitHub repo name
  133. #' @param this_version The currently installed version of the package
  134. #' @keywords internal
  135. check_version <- function(
  136. gh_user = "gadenbuie",
  137. gh_repo = "regexplain",
  138. this_version = packageVersion("regexplain")
  139. ) {
  140. ok_to_check <- getOption("regexplain.no.check.version", TRUE)
  141. if (!isTRUE(ok_to_check)) {
  142. return(NULL)
  143. }
  144. if (!requireNamespace("jsonlite", quietly = TRUE)) {
  145. return(NULL)
  146. }
  147. get_json <- purrr::possibly(jsonlite::fromJSON, NULL)
  148. gh_tags <- get_json(
  149. paste0("https://api.github.com/repos/", gh_user, "/", gh_repo, "/git/refs/tags"),
  150. simplifyDataFrame = TRUE
  151. )
  152. if (!is.null(gh_tags)) {
  153. gh_tags$tag <- sub("refs/tags/", "", gh_tags$ref, fixed = TRUE)
  154. gh_tags$version <- sub("^v\\.?", "", gh_tags$tag)
  155. }
  156. if (!is.null(gh_tags) && any(gh_tags$version > this_version)) {
  157. max_version <- max(gh_tags$version)
  158. max_tag <- gh_tags$tag[gh_tags$version == max_version]
  159. options(regexplain.no.check.version = FALSE)
  160. return(
  161. list(
  162. version = max_version,
  163. link = paste("https://github.com", gh_user, gh_repo, "releases/tag", max_tag, sep = "/")
  164. )
  165. )
  166. } else {
  167. return(NULL)
  168. }
  169. }
  170. #' Loads Regex Pattern Library
  171. #'
  172. #' Patterns sourced from [Regex Hub](https://projects.lukehaas.me/regexhub)
  173. #' are available at <https://github.com/lukehaas/RegexHub> and are copyright
  174. #' Luke Haas licensed under the MIT license available at
  175. #' <https://github.com/lukehaas/RegexHub/commit/3ab87b5a4fd2817b42e2e45dcf040d4f0164ea37>.
  176. #' Patterns source from [qdapRegex](https://github.com/trinker/qdapRegex) are
  177. #' copyright Tyler Rinker and Jason Gray, licensed under the GPL-2 license.
  178. #'
  179. #' @keywords internal
  180. get_regex_library <- function() {
  181. if (!requireNamespace("jsonlite", quietly = TRUE)) {
  182. warning("Please install the `jsonlite` package to use template features")
  183. return(NULL)
  184. }
  185. f_patterns <- system.file("extdata", "patterns.json", package = "regexplain")
  186. if (!file.exists(f_patterns)) {
  187. return(NULL)
  188. }
  189. patterns <- jsonlite::fromJSON(
  190. f_patterns,
  191. simplifyVector = FALSE,
  192. simplifyDataFrame = FALSE,
  193. simplifyMatrix = FALSE
  194. )
  195. patterns <- purrr::keep(patterns, ~ .$name != "")
  196. patterns[order(purrr::map_chr(patterns, "name"))]
  197. }