🔍 An RStudio addin slash regex utility belt
Vous ne pouvez pas sélectionner plus de 25 sujets Les noms de sujets doivent commencer par une lettre ou un nombre, peuvent contenir des tirets ('-') et peuvent comporter jusqu'à 35 caractères.

235 lines
6.7KB

  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. ui <- miniPage(
  13. shiny::includeCSS(system.file("styles", "style.css", package = "regexplain")),
  14. shiny::includeCSS(system.file("styles", "gadget.css", package = "regexplain")),
  15. gadgetTitleBar(
  16. "regexplain",
  17. right = miniTitleBarButton("done", "Send Regex To Console", TRUE)
  18. ),
  19. miniTabstripPanel(
  20. selected = match.arg(start_page, c("Text", "Regex", "Output", "Help")),
  21. miniTabPanel(
  22. "Text", icon = icon('file-text-o'),
  23. miniContentPanel(
  24. fillCol(
  25. textAreaInputAlt('text',
  26. label = "Text to search or parse",
  27. value = paste(text, collapse = "\n"),
  28. resize = "both",
  29. width = "100%",
  30. height="90%",
  31. placeholder = "Paste, enter, or edit your sample text here.")
  32. )
  33. )
  34. ),
  35. miniTabPanel(
  36. "Regex", icon = icon('terminal'),
  37. miniContentPanel(
  38. fillCol(
  39. flex = c(1, 3),
  40. fillCol(
  41. flex = c(1, 1),
  42. textInputCode('pattern', 'Regex', width = "100%",
  43. placeholder = "Enter regex, single \\ okay"),
  44. checkboxGroupInput(
  45. 'regex_options',
  46. label = "",
  47. inline = TRUE,
  48. width = "90%",
  49. choices = c("Break Lines" = "text_break_lines",
  50. "Ignore Case" = "ignore.case",
  51. "Perl Style" = "perl",
  52. "Fixed" = "fixed",
  53. "Use Bytes" = "useBytes"
  54. # , "Invert" = "invert"
  55. ),
  56. selected = c('text_break_lines')
  57. )
  58. ),
  59. tags$div(
  60. class = "gadget-result",
  61. style = "overflow-y: scroll; height: 100%;",
  62. htmlOutput('result')
  63. )
  64. )
  65. )
  66. ),
  67. miniTabPanel(
  68. "Output", icon = icon("table"),
  69. miniContentPanel(
  70. fillCol(
  71. flex = c(1, 3),
  72. inputPanel(
  73. width = "100%;",
  74. selectInput('regexFn', label = 'Apply Function',
  75. choices = regexFn_choices)
  76. ),
  77. # verbatimTextOutput('output_result', placeholder = TRUE)
  78. tags$pre(
  79. id = "output_result",
  80. class = "shiny-text-output",
  81. style = "overflow-y: scroll; height: 100%;"
  82. )
  83. )
  84. )
  85. ),
  86. miniTabPanel(
  87. "Help", icon = icon("support"),
  88. help_ui("help")
  89. )
  90. )
  91. )
  92. server <- function(input, output, session) {
  93. rtext <- reactive({
  94. x <- if ('text_break_lines' %in% input$regex_options) {
  95. strsplit(input$text, "\n")[[1]]
  96. } else input$text
  97. x
  98. })
  99. pattern <- reactive({
  100. sanitize_text_input(input$pattern)
  101. })
  102. alert_result <- function(msg, type = "danger") {
  103. msg <- gsub("\n", "<br>", msg)
  104. msg <- gsub("\t", "&nbsp;&nbsp;", msg)
  105. paste0("<pre class='alert alert-", type, "' ",
  106. "style='padding: 4px; margin-top: 1px; margin-bottom: 4px;'>",
  107. paste(msg, collapse = "<br>"),
  108. "</pre>")
  109. }
  110. output$result <- renderUI({
  111. if (is.null(rtext())) return(NULL)
  112. if (pattern() == "") {
  113. return(toHTML(paste('<p class="results">', escape_html(rtext()), "</p>", collapse = "")))
  114. }
  115. res <- NULL
  116. error_message <- NULL
  117. warning_message <- NULL
  118. tryCatch({
  119. res <- paste(
  120. view_regex(
  121. rtext(),
  122. pattern(),
  123. ignore.case = 'ignore.case' %in% input$regex_options,
  124. perl = 'perl' %in% input$regex_options,
  125. fixed = 'fixed' %in% input$regex_options,
  126. useBytes = 'useBytes' %in% input$regex_options,
  127. # invert = 'invert' %in% input$regex_options,
  128. render = FALSE,
  129. escape = TRUE,
  130. exact = FALSE),
  131. collapse = ""
  132. )
  133. },
  134. error = function(e) {
  135. error_message <<- alert_result(e$message, "danger")
  136. },
  137. warning = function(w) {
  138. warning_message <<- alert_result(w$message, "warning")
  139. })
  140. if (is.null(res)) res <- toHTML(
  141. paste('<p class="results">', escape_html(rtext()), "</p>", collapse = "")
  142. )
  143. toHTML(paste(error_message, warning_message, res))
  144. })
  145. output$output_result <- renderPrint({
  146. req(input$regexFn)
  147. regexPkg <- get_pkg_namespace(input$regexFn)
  148. regexFn <- getFromNamespace(input$regexFn, regexPkg)
  149. x <- if (regexPkg == "base") {
  150. regexFn(pattern(), rtext())
  151. } else if (regexPkg == "stringr") {
  152. regexFn(rtext(), pattern())
  153. } else {
  154. "Um. Not sure how I got here."
  155. }
  156. print(x)
  157. })
  158. # ---- Help Section ---- #
  159. help_text <- callModule(help_server, "help")
  160. observeEvent(input$done, {
  161. # browser()
  162. if (pattern() != "") {
  163. pattern <- paste0('pattern <- "', escape_backslash(pattern()), '"')
  164. rstudioapi::sendToConsole(pattern, FALSE)
  165. }
  166. stopApp()
  167. })
  168. observeEvent(input$cancel, {
  169. stopApp()
  170. })
  171. }
  172. viewer <- shiny::paneViewer(700)
  173. runGadget(ui, server, viewer = viewer)
  174. }
  175. sanitize_text_input <- function(x) {
  176. if (grepl("\\u|\\x|\\N|\\a|\\o", x)) {
  177. try({
  178. y <- stringi::stri_unescape_unicode(x)
  179. }, silent = TRUE)
  180. if (!is.na(y)) x <- y
  181. }
  182. # x <- gsub("\u201C|\u201D", '"', x)
  183. # x <- gsub("\u2018|\u2019", "'", x)
  184. x
  185. }
  186. toHTML <- function(...) {
  187. x <- paste(..., collapse = "")
  188. x <- gsub("\n", "\\\\n", x)
  189. x <- gsub("\t", "\\\\t", x)
  190. x <- gsub("\r", "\\\\r", x)
  191. HTML(x)
  192. }
  193. regexFn_choices <- list(
  194. "Choose a function" = "",
  195. base = c(
  196. "grep",
  197. "grepl",
  198. "regexpr",
  199. "gregexpr",
  200. "regexec"
  201. ),
  202. stringr = c(
  203. "str_detect",
  204. "str_locate",
  205. "str_locate_all",
  206. "str_extract",
  207. "str_extract_all",
  208. "str_match",
  209. "str_match_all",
  210. "str_split"
  211. )
  212. )
  213. get_pkg_namespace <- function(fn) {
  214. x <- names(purrr::keep(regexFn_choices, ~ (fn %in% .)))
  215. if (length(x) > 1) warning(fn, " matches multiple functions in regexFn_choices, please review.")
  216. x
  217. }