🔍 An RStudio addin slash regex utility belt
Du kannst nicht mehr als 25 Themen auswählen Themen müssen entweder mit einem Buchstaben oder einer Ziffer beginnen. Sie können Bindestriche („-“) enthalten und bis zu 35 Zeichen lang sein.

691 Zeilen
26KB

  1. #' regexplain gadget
  2. #'
  3. #' @import miniUI
  4. #' @import shiny
  5. #' @param text Text to explore in gadget (editable using interface)
  6. #' @param start_page Open gadget to this tab, one of `"Text"`, `"RegEx"`,
  7. #' `"Output"`, or `"Help"`
  8. #' @export
  9. regex_gadget <- function(text = NULL,
  10. start_page = if (is.null(text)) "Text" else "RegEx") {
  11. stopifnot(requireNamespace("miniUI"), requireNamespace("shiny"))
  12. update_available <- check_version()
  13. # ---- UI ----
  14. ui <- miniPage(
  15. shiny::includeCSS(system.file("styles", "style.css", package = "regexplain")),
  16. shiny::includeCSS(system.file("styles", "gadget.css", package = "regexplain")),
  17. gadgetTitleBar(
  18. "regexplain",
  19. right = miniTitleBarButton("done", "Send RegEx To Console", TRUE)
  20. ),
  21. miniTabstripPanel(
  22. selected = match.arg(start_page, c("Text", "RegEx", "Output", "Help")),
  23. # --- UI - Tab - Text ----
  24. miniTabPanel(
  25. "Text", icon = icon('file-text-o'),
  26. miniContentPanel(
  27. fillCol(
  28. textAreaInputAlt('text',
  29. label = "Text to search or parse",
  30. value = paste(text, collapse = "\n"),
  31. resize = "both",
  32. width = "100%",
  33. height="90%",
  34. placeholder = "Paste, enter, or edit your sample text here.")
  35. )
  36. )
  37. ),
  38. # ---- UI - Tab - Regex ----
  39. miniTabPanel(
  40. "RegEx", icon = icon('terminal'),
  41. miniContentPanel(
  42. fillCol(
  43. flex = c(1, 3),
  44. fillCol(
  45. flex = c(1, 1),
  46. textInputCode('pattern', 'RegEx', width = "100%",
  47. placeholder = "Standard RegEx, e.g. \\w+_\\d{2,4}\\s+"),
  48. checkboxGroupInput(
  49. 'regex_options',
  50. label = HTML(
  51. '<div style="font-size: 1.25rem;">',
  52. 'Option Groups: ',
  53. '<span style="color: #337ab7;">regexplain</span>,',
  54. '<span style="color: #5cb85c;">all</span>, ',
  55. '<span style="color: #f0ad4e;">base only</span>',
  56. '</div>'
  57. ),
  58. inline = TRUE,
  59. width = "90%",
  60. choiceValues = list(
  61. "text_break_lines",
  62. "ignore.case",
  63. "fixed",
  64. "perl",
  65. "useBytes"),
  66. choiceNames = list(
  67. HTML('<span style="color: #337ab7;">Break Lines</span>'),
  68. HTML('<span style="color: #5cb85c;">Ignore Case</span>'),
  69. HTML('<span style="color: #5cb85c;">Fixed/Literal</span>'),
  70. HTML('<span style="color: #f0ad4e;">Perl Style</span>'),
  71. HTML('<span style="color: #f0ad4e;">Use Bytes</span>')),
  72. selected = c('text_break_lines')
  73. )
  74. ),
  75. tags$div(
  76. class = "gadget-result",
  77. style = "overflow-y: scroll; height: 100%;",
  78. htmlOutput('result')
  79. )
  80. )
  81. )
  82. ),
  83. # ---- UI - Tab - Output ----
  84. miniTabPanel(
  85. "Output", icon = icon("table"),
  86. miniContentPanel(
  87. fillCol(
  88. flex = c(1, 3),
  89. inputPanel(
  90. tags$div(
  91. width = "100%;",
  92. selectInput('regexFn', label = 'Apply Function',
  93. choices = regexFn_choices),
  94. tags$span(class = "help-block",
  95. style = "font-size:1.25rem; margin-top:-10px; margin-bottom:0px; margin-left:4px;",
  96. "Adjust options in RegEx tab")
  97. ),
  98. uiOutput("output_sub")
  99. ),
  100. # verbatimTextOutput('output_result', placeholder = TRUE)
  101. tags$pre(
  102. id = "output_result",
  103. class = "shiny-text-output",
  104. style = "overflow-y: scroll; height: 100%;"
  105. )
  106. )
  107. )
  108. ),
  109. # ---- UI - Tab - Help ----
  110. miniTabPanel(
  111. "Help", icon = icon("support"),
  112. generate_help_ui(cheatsheet_only = FALSE)
  113. )
  114. )
  115. )
  116. # ---- Server ----
  117. server <- function(input, output, session) {
  118. if (!is.null(update_available)) {
  119. showModal(
  120. modalDialog(
  121. title = "Update Available \U1F389",
  122. easyClose = TRUE,
  123. footer = modalButton("OK"),
  124. tagList(
  125. tags$p(
  126. "Version", update_available$version, "is",
  127. tags$a(href = update_available$link,
  128. "available on GitHub.")
  129. ),
  130. if ("devtools" %in% installed.packages()) tags$p(
  131. "The fastest way to update is with devtools:",
  132. tags$pre(
  133. "devtools::update_packages(\"gadenbuie/regexplain\")"
  134. )
  135. ),
  136. tags$p(
  137. class = 'help-block',
  138. "This message won't be shown again during this R session."
  139. )
  140. )
  141. )
  142. )
  143. }
  144. # ---- Server - Global ----
  145. rtext <- reactive({
  146. x <- if ('text_break_lines' %in% input$regex_options) {
  147. strsplit(input$text, "\n")[[1]]
  148. } else input$text
  149. x
  150. })
  151. pattern <- reactive({
  152. sanitize_text_input(input$pattern)
  153. })
  154. alert_result <- function(msg, type = "danger") {
  155. msg <- gsub("\n", "<br>", msg)
  156. msg <- gsub("\t", "&nbsp;&nbsp;", msg)
  157. paste0("<pre class='alert alert-", type, "' ",
  158. "style='padding: 4px; margin-top: 1px; margin-bottom: 4px;'>",
  159. paste(msg, collapse = "<br>"),
  160. "</pre>")
  161. }
  162. # ---- Server - Tab - Regex ----
  163. output$result <- renderUI({
  164. if (is.null(rtext())) return(NULL)
  165. if (pattern() == "") {
  166. return(toHTML(paste('<p class="results">', escape_html(rtext()), "</p>", collapse = "")))
  167. }
  168. res <- NULL
  169. error_message <- NULL
  170. warning_message <- NULL
  171. tryCatch({
  172. res <- paste(
  173. view_regex(
  174. rtext(),
  175. pattern(),
  176. ignore.case = 'ignore.case' %in% input$regex_options,
  177. perl = 'perl' %in% input$regex_options,
  178. fixed = 'fixed' %in% input$regex_options,
  179. useBytes = 'useBytes' %in% input$regex_options,
  180. # invert = 'invert' %in% input$regex_options,
  181. render = FALSE,
  182. escape = TRUE,
  183. exact = FALSE),
  184. collapse = ""
  185. )
  186. },
  187. error = function(e) {
  188. error_message <<- alert_result(e$message, "danger")
  189. },
  190. warning = function(w) {
  191. warning_message <<- alert_result(w$message, "warning")
  192. })
  193. if (is.null(res)) res <- toHTML(
  194. paste('<p class="results">', escape_html(rtext()), "</p>", collapse = "")
  195. )
  196. toHTML(paste(error_message, warning_message, res))
  197. })
  198. # ---- Server - Tab - Output ----
  199. regexFn_replacement_val <- NULL
  200. output$output_sub <- renderUI({
  201. req(input$regexFn)
  202. if (!input$regexFn %in% regexFn_substitute) return(NULL)
  203. textInputCode('regexFn_replacement', 'Subsitution',
  204. value = regexFn_replacement_val,
  205. placeholder = "Replacement Text")
  206. })
  207. replacement <- reactive({
  208. req(input$regexFn)
  209. if (!input$regexFn %in% regexFn_substitute) {
  210. NULL
  211. } else {
  212. regexFn_replacement_val <<- input$regexFn_replacement
  213. sanitize_text_input(input$regexFn_replacement)
  214. }
  215. })
  216. output$output_result <- renderPrint({
  217. req(input$regexFn)
  218. regexPkg <- get_pkg_namespace(input$regexFn)
  219. if (!requireNamespace(regexPkg, quietly = TRUE)) {
  220. return(cat(
  221. paste0(
  222. "The package `", regexPkg, "` is not installed.\n",
  223. "To preview results from this package, please run\n\n",
  224. " install.packages(\"", regexPkg, "\")"
  225. )
  226. ))
  227. }
  228. regexFn <- getFromNamespace(input$regexFn, regexPkg)
  229. req_sub_arg <- input$regexFn %in% regexFn_substitute
  230. x <- if (regexPkg == "base") {
  231. if (req_sub_arg) {
  232. req(replacement())
  233. regexFn(pattern(), replacement(), rtext(),
  234. ignore.case = 'ignore.case' %in% input$regex_options,
  235. perl = 'perl' %in% input$regex_options,
  236. fixed = 'fixed' %in% input$regex_options,
  237. useBytes = 'useBytes' %in% input$regex_options)
  238. } else {
  239. regexFn(pattern(), rtext(),
  240. ignore.case = 'ignore.case' %in% input$regex_options,
  241. perl = 'perl' %in% input$regex_options,
  242. fixed = 'fixed' %in% input$regex_options,
  243. useBytes = 'useBytes' %in% input$regex_options)
  244. }
  245. } else if (regexPkg == "stringr") {
  246. if (req_sub_arg) {
  247. req(replacement())
  248. regexFn(
  249. rtext(),
  250. stringr::regex(
  251. pattern(),
  252. ignore_case = 'ignore.case' %in% input$regex_options,
  253. literal = 'fixed' %in% input$regex_options
  254. ),
  255. replacement()
  256. )
  257. } else {
  258. regexFn(
  259. rtext(),
  260. stringr::regex(
  261. pattern(),
  262. ignore_case = 'ignore.case' %in% input$regex_options,
  263. literal = 'fixed' %in% input$regex_options
  264. )
  265. )
  266. }
  267. } else if (regexPkg == "rematch2") {
  268. regexFn(rtext(), pattern(),
  269. ignore.case = 'ignore.case' %in% input$regex_options,
  270. perl = 'perl' %in% input$regex_options,
  271. fixed = 'fixed' %in% input$regex_options,
  272. useBytes = 'useBytes' %in% input$regex_options)
  273. } else {
  274. "Um. Not sure how I got here."
  275. }
  276. print(x)
  277. })
  278. # ---- Server - Tab - Help ----
  279. HELP_DEFAULT_TEXT <- paste0(
  280. "<h3>Welcome to RegExplain</h3>",
  281. "<p>Choose a category from the menu on the left.</p>"
  282. )
  283. source(system.file("shiny", "help_server.R", package = "regexplain"), local = TRUE)
  284. load_buttons <- function(..., extra_btns = NULL) {
  285. prefix <- paste(..., sep = "_")
  286. btns <- c(
  287. list(c("text", "Load Text", "btn-success"),
  288. c("pattern", "Load Pattern", "btn-primary")),
  289. extra_btns
  290. )
  291. tags$span(
  292. style = "display: inline-block;",
  293. purrr::map(
  294. btns,
  295. ~ actionButton(paste0(prefix, "_", .[1]), .[2], class = paste("btn-xs", if (!is.na(.[3])) .[3]))
  296. )
  297. )
  298. }
  299. observeEvent(input$help_try_this, {
  300. tagList(
  301. tags$h3("Try These Examples"),
  302. tags$p("Here are a couple interesting text extraction challenges you can try",
  303. "with this gadget."),
  304. tags$h4("Harvard Sentences"),
  305. tags$p("These examples come from the",
  306. tags$a(href = "http://r4ds.had.co.nz/strings.html", "R for Data Science"),
  307. "book and are based on a collection of short sentences called the Harvard Sentences."),
  308. tags$ol(
  309. tags$li(tags$p(
  310. "Find sentences that contain a color (i.e. red, orange, yellow, green, blue, purple).",
  311. load_buttons("help_try_this", "hs", "colors"))),
  312. tags$li(tags$p(
  313. "Use the text from Exercise 1 and make sure that only full words that are colors are found.",
  314. HTML("E.g. <code>red</code> and not <code>flickered</code>."),
  315. tags$span(style = "display: inline-block;",
  316. actionButton("help_try_this_hs_colors_word", "Load Pattern", class = "btn-xs btn-primary"),
  317. actionButton("help_try_this_hs_colors_hint", "Show Hint", class = "btn-xs"))
  318. )),
  319. tags$li(tags$p(
  320. "Extract nouns from sentences by finding any word that comes after \"a\" or \"the\".",
  321. "Use", actionLink("help_try_this_hs_words_go2_groups", "Groups"),
  322. "to extract the article and possible noun separately and check your results with",
  323. HTML("<code>stringr::str_match()</code>:"),
  324. load_buttons("help_try_this", "hs", "words",
  325. extra_btns = list(c("output", "Check str_match()")))
  326. )),
  327. tags$li(tags$p(
  328. "Switch the order of the two words following the articles", '"a" or "the"',
  329. "using", actionLink("help_try_this_hs_refs_go2_groups", "backreferences,"),
  330. "so that", tags$code("the birch canoe"), "would read",
  331. HTML("<code>the canoe birch</code>. Use <code>sub</code>"),
  332. "in the", tags$strong("Output"), "tab to replace the matched pattern.",
  333. load_buttons("help_try_this", "hs", "refs",
  334. extra_btns = list(c("output", "Load Replacement")))
  335. ))
  336. ),
  337. tags$h4("Phone Numbers"),
  338. tags$p("This example is also from the",
  339. tags$a(href = "http://r4ds.had.co.nz/strings.html#other-types-of-pattern",
  340. "R for Data Science"),
  341. "book. Phone numbers in the United States start with a 3-digit area code,",
  342. "followed by another 3 digits and a final 4-digit group.",
  343. "Sometimes the area code is wrapped in parenthesis, or sometimes dots or dashes",
  344. "are used to separate the digit groups. Try to extract each digit group from these phone numbers:",
  345. load_buttons("help_try_this", "phone",
  346. extra_btns = list(c("output", "Check str_match()")))),
  347. tags$h4("CSS Unit Validation"),
  348. tags$p("This example is used in", tags$code("validateCssUnit()"),
  349. "in the", tags$a(href="https://www.r-pkg.org/pkg/htmltools", "htmltools package."),
  350. "CSS units can be integer or decimal numbers with units such as",
  351. "in, cm, mm, em, ex, pt, px, etc. (see the list",
  352. HTML('<a href="https://www.w3.org/Style/Examples/007/units.en.html">here</a>).'),
  353. "Try to extract the number and unit from these units:",
  354. load_buttons("help_try_this", "css")),
  355. tags$h4("Parse Github Repos"),
  356. tags$p("This example is from the",
  357. tags$a(href = "https://www.r-pkg.org/pkg/rematch2", "rematch2 package."),
  358. "Github repositories are often specified in like",
  359. HTML("<code>user/repo/subdir@ref*release</code> or <code>user/repo/subdir#PR</code>"),
  360. "where only", tags$code("user"), "and", tags$code("repo"), "are required elements.",
  361. "Try to extract each piece of the repo text and use",
  362. tags$code("rematch2::re_match()"), "to extract a tidy tibble of matches:",
  363. load_buttons("help_try_this", "github",
  364. extra_btns = list(c("output", "Check re_match()"))))
  365. ) %>%
  366. as.character() %>%
  367. help_text()
  368. })
  369. observeEvent(input$help_try_this_hs_colors_text, {
  370. color_match <- "\\b(red|orange|yellow|green|blue|purple|he)\\b|red"
  371. color_text <- stringr::sentences[grepl(color_match, stringr::sentences)]
  372. color_text <- sample(color_text, 25)
  373. updateTextAreaInput(session, "text", value = paste(color_text, collapse = "\n"))
  374. showNotification("Text loaded! View it in Text tab", type = 'message')
  375. })
  376. observeEvent(input$help_try_this_hs_colors_pattern, {
  377. color_match <- "red|orange|yellow|green|blue|purple"
  378. updateTextInput(session, 'pattern', value = color_match)
  379. updateCheckboxGroupInput(session, "regex_options", selected = c('text_break_lines', 'perl'))
  380. showNotification("Pattern loaded! View it in RegEx tab", type = 'message')
  381. })
  382. observeEvent(input$help_try_this_hs_colors_word, {
  383. color_match <- "\\b(red|orange|yellow|green|blue|purple)\\b"
  384. updateTextInput(session, 'pattern', value = color_match)
  385. updateCheckboxGroupInput(session, "regex_options", selected = c('text_break_lines', 'perl'))
  386. showNotification("Pattern loaded! View it in RegEx tab", type = 'message')
  387. })
  388. observeEvent(input$help_try_this_hs_colors_hint, {
  389. showModal(
  390. modalDialog(title = "Hint \U0001f575", footer = NULL, easyClose = TRUE,
  391. tags$p("Try using the", tags$strong("word boundary"), "anchor."))
  392. )
  393. })
  394. observeEvent(input$help_try_this_hs_words_go2_groups, {
  395. make_help_tab_text("groups")
  396. })
  397. observeEvent(input$help_try_this_hs_words_output, {
  398. updateSelectInput(session, 'regexFn', selected = 'str_match')
  399. showNotification("Go to Output tab to see results from str_match()", type = "message")
  400. })
  401. observeEvent(input$help_try_this_hs_words_text, {
  402. hs_text <- sample(stringr::sentences, 25)
  403. updateTextAreaInput(session, "text", value = paste(hs_text, collapse = "\n"))
  404. showNotification("Text loaded! View it in Text tab", type = 'message')
  405. })
  406. observeEvent(input$help_try_this_hs_words_pattern, {
  407. noun_pattern <- "(a|the) ([^ ]+)"
  408. updateTextInput(session, 'pattern', value = noun_pattern)
  409. updateCheckboxGroupInput(session, "regex_options", selected = c('text_break_lines'))
  410. updateSelectInput(session, 'regexFn', selected = "str_match")
  411. showNotification("Pattern loaded! View it in RegEx and Output tabs", type = 'message')
  412. })
  413. observeEvent(input$help_try_this_hs_refs_go2_groups, {
  414. make_help_tab_text("groups")
  415. })
  416. observeEvent(input$help_try_this_hs_refs_output, {
  417. regexFn_replacement_val <<- "\\1 \\3 \\2"
  418. updateSelectInput(session, 'regexFn', selected = 'sub')
  419. showNotification("Replacement loaded! Go to Output tab to see results", type = "message")
  420. })
  421. observeEvent(input$help_try_this_hs_refs_text, {
  422. hs_text <- sample(stringr::sentences, 25)
  423. updateTextAreaInput(session, "text", value = paste(hs_text, collapse = "\n"))
  424. showNotification("Text loaded! View it in Text tab", type = 'message')
  425. })
  426. observeEvent(input$help_try_this_hs_refs_pattern, {
  427. noun_pattern <- "(a|the) ([^ ]+) ([^ ]+)"
  428. updateTextInput(session, 'pattern', value = noun_pattern)
  429. updateCheckboxGroupInput(session, "regex_options", selected = c('text_break_lines'))
  430. showNotification("Pattern loaded! View it in RegEx tab", type = 'message')
  431. })
  432. observeEvent(input$help_try_this_phone_output, {
  433. updateSelectInput(session, 'regexFn', selected = 'str_match')
  434. showNotification("Go to Output tab to see results from str_match()", type = "message")
  435. })
  436. observeEvent(input$help_try_this_phone_text, {
  437. phone_number <- function() {
  438. first <- function() sample(2:9, 1)
  439. others <- function(n) sample(1:9, n, replace = TRUE)
  440. wrap_types <- c("parens", "dash", "space", "dot", "nothing")
  441. wrap <- function(x, type) {
  442. switch(
  443. match.arg(type, choices = wrap_types),
  444. parens = paste0("(", x, ")"),
  445. dash = paste0(x, "-"),
  446. space = paste0(x, " "),
  447. dot = paste0(x, "."),
  448. x
  449. )
  450. }
  451. area_code <- paste0(c(first(), others(2)), collapse = "")
  452. group1 <- paste0(c(first(), others(2)), collapse = "")
  453. group2 <- paste0(c(first(), others(3)), collapse = "")
  454. area_wrap <- sample(wrap_types, 1)
  455. other_wrap <- if (area_wrap == "parens") sample(wrap_types[-1], 1) else area_wrap
  456. paste0(wrap(area_code, area_wrap), wrap(group1, other_wrap), group2)
  457. }
  458. phone_numbers <- replicate(25, phone_number())
  459. updateTextAreaInput(session, "text", value = paste(phone_numbers, collapse = "\n"))
  460. showNotification("Text loaded! View it in Text tab", type = 'message')
  461. })
  462. observeEvent(input$help_try_this_phone_pattern, {
  463. phone_pattern <- "\\(?(\\d{3})[-). ]?(\\d{3})[- .]?(\\d{4})"
  464. updateTextInput(session, 'pattern', value = phone_pattern)
  465. updateCheckboxGroupInput(session, "regex_options", selected = c('text_break_lines'))
  466. showNotification("Pattern loaded! View it in RegEx tab", type = 'message')
  467. })
  468. observeEvent(input$help_try_this_github_text, {
  469. github_repos <- c(
  470. "metacran/crandb",
  471. "jeroenooms/curl@v0.9.3",
  472. "jimhester/covr#47",
  473. "hadley/dplyr@*release",
  474. "r-lib/remotes@550a3c7d3f9e1493a2ba"
  475. )
  476. updateTextAreaInput(session, "text", value = paste(github_repos, collapse = "\n"))
  477. showNotification("Text loaded! Go to RegEx Tab", type = 'message')
  478. })
  479. observeEvent(input$help_try_this_github_pattern, {
  480. owner_rx <- "(?:(?<owner>[^/]+)/)?"
  481. repo_rx <- "(?<repo>[^/@#]+)"
  482. subdir_rx <- "(?:/(?<subdir>[^@#]*[^@#/]))?"
  483. ref_rx <- "(?:@(?<ref>[^*].*))"
  484. pull_rx <- "(?:#(?<pull>[0-9]+))"
  485. release_rx <- "(?:@(?<release>[*]release))"
  486. subtype_rx <- sprintf("(?:%s|%s|%s)?", ref_rx, pull_rx, release_rx)
  487. github_rx <- sprintf(
  488. "^(?:%s%s%s%s|(?<catchall>.*))$",
  489. owner_rx, repo_rx, subdir_rx, subtype_rx
  490. )
  491. updateTextInput(session, 'pattern', value = github_rx)
  492. updateCheckboxGroupInput(session, "regex_options", selected = c('text_break_lines', 'perl'))
  493. showNotification("Pattern loaded! Go to RegEx Tab", type = 'message')
  494. })
  495. observeEvent(input$help_try_this_github_output, {
  496. updateSelectInput(session, 'regexFn', selected = 're_match')
  497. showNotification("Go to Output tab to see results from re_match()", type = "message")
  498. })
  499. observeEvent(input$help_try_this_css_text, {
  500. css_units <- c(
  501. "125%","16pt","2cm","7em","3ex","24pt",
  502. ".15in","20pc","5.9vw","3.0vh","2vmin"
  503. )
  504. showNotification("Example text loaded! Go to RegEx tab", type = "message")
  505. updateTextAreaInput(session, "text", value = paste(css_units, collapse = "\n"))
  506. })
  507. observeEvent(input$help_try_this_css_pattern, {
  508. pattern <- "^(auto|inherit|((\\.\\d+)|(\\d+(\\.\\d+)?))(%|in|cm|mm|em|ex|pt|pc|px|vh|vw|vmin|vmax))$"
  509. updateTextInput(session, "pattern", value = pattern)
  510. updateCheckboxGroupInput(session, 'regex_options', selected = c('text_break_lines', 'perl'))
  511. showNotification("Pattern loaded! Go to RegEx tab", type = "message")
  512. })
  513. # ---- Server - Tab - Exit ----
  514. observeEvent(input$done, {
  515. if (pattern() != "") {
  516. pattern <- paste0('pattern <- "', escape_backslash(pattern()), '"')
  517. if ("regexFn_replacement" %in% names(input) && replacement() != "") {
  518. pattern <- paste0(
  519. pattern, "\n",
  520. 'replacement <- "', escape_backslash(replacement()), '"'
  521. )
  522. }
  523. rstudioapi::sendToConsole(pattern, FALSE)
  524. }
  525. stopApp()
  526. })
  527. observeEvent(input$cancel, {
  528. stopApp()
  529. })
  530. }
  531. viewer <- shiny::paneViewer(minHeight = 1000)
  532. runGadget(ui, server, viewer = viewer)
  533. }
  534. # ---- Gadget Helper Functions and Variables ----
  535. sanitize_text_input <- function(x) {
  536. if (is.null(x) || !nchar(x)) return(x)
  537. rx_unicode <- "\\\\u[0-9a-f]{4,8}"
  538. rx_hex <- "\\\\x[0-9a-f]{2}|\\\\x\\{[0-9a-f]{1,6}\\}"
  539. rx_octal <- "\\\\[0][0-7]{1,3}"
  540. rx_escape <- paste(rx_unicode, rx_hex, rx_octal, sep = "|")
  541. if (grepl(rx_escape, x, ignore.case = TRUE)) {
  542. try({
  543. y <- stringi::stri_unescape_unicode(x)
  544. }, silent = TRUE)
  545. if (!is.na(y)) x <- y
  546. }
  547. # x <- gsub("\u201C|\u201D", '"', x)
  548. # x <- gsub("\u2018|\u2019", "'", x)
  549. x
  550. }
  551. toHTML <- function(...) {
  552. x <- paste(..., collapse = "")
  553. x <- gsub("\n", "\\\\n", x)
  554. x <- gsub("\t", "\\\\t", x)
  555. x <- gsub("\r", "\\\\r", x)
  556. HTML(x)
  557. }
  558. regexFn_choices <- list(
  559. "Choose a function" = "",
  560. base = c(
  561. "grep",
  562. "grepl",
  563. "sub", #<<
  564. "gsub", #<<
  565. "regexpr",
  566. "gregexpr",
  567. "regexec"
  568. ),
  569. stringr = c(
  570. "str_detect",
  571. "str_locate",
  572. "str_locate_all",
  573. "str_extract",
  574. "str_extract_all",
  575. "str_match",
  576. "str_match_all",
  577. "str_replace", #<<
  578. "str_replace_all", #<<
  579. "str_split"
  580. ),
  581. "rematch2" = c(
  582. "re_match",
  583. "re_match_all",
  584. "re_exec",
  585. "re_exec_all"
  586. )
  587. )
  588. regexFn_substitute <- c(
  589. paste0(c("", "g"), "sub"),
  590. paste0("str_replace", c("", "_all"))
  591. )
  592. get_pkg_namespace <- function(fn) {
  593. x <- names(purrr::keep(regexFn_choices, ~ (fn %in% .)))
  594. if (length(x) > 1) warning(fn, " matches multiple functions in regexFn_choices, please review.")
  595. x
  596. }
  597. #' Check if an updated version is available
  598. #'
  599. #' I included this because it can be difficult to tell if your RStudio Addins
  600. #' are up to date. I may add new features that you want but you won't hear about
  601. #' the updates. This function checks if an update is available, using GitHub
  602. #' tags. If an update is available, a modal dialog is shown when you start
  603. #' the regexplain gadget. This only happens once per R session, though, so feel
  604. #' free to ignore the message.
  605. #'
  606. #' @param gh_user GitHub user account
  607. #' @param gh_repo GitHub repo name
  608. #' @param this_version The currently installed version of the package
  609. #' @keywords internal
  610. check_version <- function(
  611. gh_user = "gadenbuie",
  612. gh_repo = "regexplain",
  613. this_version = packageVersion('regexplain')
  614. ) {
  615. ok_to_check <- getOption("regexplain.no.check.version", TRUE)
  616. if (!ok_to_check) return(NULL)
  617. if (!requireNamespace('jsonlite', quietly = TRUE)) return(NULL)
  618. get_json <- purrr::possibly(jsonlite::fromJSON, NULL)
  619. gh_tags <- get_json(
  620. paste0("https://api.github.com/repos/", gh_user, "/", gh_repo, "/git/refs/tags"),
  621. simplifyDataFrame = TRUE
  622. )
  623. if (!is.null(gh_tags)) {
  624. gh_tags$tag <- sub("refs/tags/", "", gh_tags$ref, fixed = TRUE)
  625. gh_tags$version <- sub("^v\\.?", "", gh_tags$tag)
  626. }
  627. if (!is.null(gh_tags) && any(gh_tags$version > this_version)) {
  628. max_version <- max(gh_tags$version)
  629. max_tag <- gh_tags$tag[gh_tags$version == max_version]
  630. options(regexplain.no.check.version = FALSE)
  631. return(
  632. list(
  633. version = max_version,
  634. link = paste("https://github.com", gh_user, gh_repo, "releases/tag", max_tag, sep = "/")
  635. )
  636. )
  637. } else return(NULL)
  638. }