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

277 lines
7.8KB

  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. uiOutput("output_sub")
  77. ),
  78. # verbatimTextOutput('output_result', placeholder = TRUE)
  79. tags$pre(
  80. id = "output_result",
  81. class = "shiny-text-output",
  82. style = "overflow-y: scroll; height: 100%;"
  83. )
  84. )
  85. )
  86. ),
  87. miniTabPanel(
  88. "Help", icon = icon("support"),
  89. help_ui("help")
  90. )
  91. )
  92. )
  93. server <- function(input, output, session) {
  94. rtext <- reactive({
  95. x <- if ('text_break_lines' %in% input$regex_options) {
  96. strsplit(input$text, "\n")[[1]]
  97. } else input$text
  98. x
  99. })
  100. pattern <- reactive({
  101. sanitize_text_input(input$pattern)
  102. })
  103. alert_result <- function(msg, type = "danger") {
  104. msg <- gsub("\n", "<br>", msg)
  105. msg <- gsub("\t", "&nbsp;&nbsp;", msg)
  106. paste0("<pre class='alert alert-", type, "' ",
  107. "style='padding: 4px; margin-top: 1px; margin-bottom: 4px;'>",
  108. paste(msg, collapse = "<br>"),
  109. "</pre>")
  110. }
  111. output$result <- renderUI({
  112. if (is.null(rtext())) return(NULL)
  113. if (pattern() == "") {
  114. return(toHTML(paste('<p class="results">', escape_html(rtext()), "</p>", collapse = "")))
  115. }
  116. res <- NULL
  117. error_message <- NULL
  118. warning_message <- NULL
  119. tryCatch({
  120. res <- paste(
  121. view_regex(
  122. rtext(),
  123. pattern(),
  124. ignore.case = 'ignore.case' %in% input$regex_options,
  125. perl = 'perl' %in% input$regex_options,
  126. fixed = 'fixed' %in% input$regex_options,
  127. useBytes = 'useBytes' %in% input$regex_options,
  128. # invert = 'invert' %in% input$regex_options,
  129. render = FALSE,
  130. escape = TRUE,
  131. exact = FALSE),
  132. collapse = ""
  133. )
  134. },
  135. error = function(e) {
  136. error_message <<- alert_result(e$message, "danger")
  137. },
  138. warning = function(w) {
  139. warning_message <<- alert_result(w$message, "warning")
  140. })
  141. if (is.null(res)) res <- toHTML(
  142. paste('<p class="results">', escape_html(rtext()), "</p>", collapse = "")
  143. )
  144. toHTML(paste(error_message, warning_message, res))
  145. })
  146. regexFn_replacement_val <- NULL
  147. output$output_sub <- renderUI({
  148. req(input$regexFn)
  149. if (!input$regexFn %in% regexFn_substitute) return(NULL)
  150. textInputCode('regexFn_replacement', 'Subsitution',
  151. value = regexFn_replacement_val,
  152. placeholder = "Replacement Text")
  153. })
  154. replacement <- reactive({
  155. req(input$regexFn)
  156. if (!input$regexFn %in% regexFn_substitute) {
  157. NULL
  158. } else {
  159. regexFn_replacement_val <<- input$regexFn_replacement
  160. sanitize_text_input(input$regexFn_replacement)
  161. }
  162. })
  163. output$output_result <- renderPrint({
  164. req(input$regexFn)
  165. regexPkg <- get_pkg_namespace(input$regexFn)
  166. regexFn <- getFromNamespace(input$regexFn, regexPkg)
  167. req_sub_arg <- input$regexFn %in% regexFn_substitute
  168. x <- if (regexPkg == "base") {
  169. if (req_sub_arg) {
  170. req(replacement())
  171. regexFn(pattern(), replacement(), rtext())
  172. } else {
  173. regexFn(pattern(), rtext())
  174. }
  175. } else if (regexPkg == "stringr") {
  176. if (req_sub_arg) {
  177. req(replacement())
  178. regexFn(rtext(), pattern(), replacement())
  179. } else {
  180. regexFn(rtext(), pattern())
  181. }
  182. } else {
  183. "Um. Not sure how I got here."
  184. }
  185. print(x)
  186. })
  187. # ---- Help Section ---- #
  188. help_text <- callModule(help_server, "help")
  189. observeEvent(input$done, {
  190. # browser()
  191. if (pattern() != "") {
  192. pattern <- paste0('pattern <- "', escape_backslash(pattern()), '"')
  193. rstudioapi::sendToConsole(pattern, FALSE)
  194. }
  195. stopApp()
  196. })
  197. observeEvent(input$cancel, {
  198. stopApp()
  199. })
  200. }
  201. viewer <- shiny::paneViewer(700)
  202. runGadget(ui, server, viewer = viewer)
  203. }
  204. sanitize_text_input <- function(x) {
  205. if (is.null(x) || !nchar(x)) return(x)
  206. if (grepl("\\u|\\x|\\N|\\a|\\o", x)) {
  207. try({
  208. y <- stringi::stri_unescape_unicode(x)
  209. }, silent = TRUE)
  210. if (!is.na(y)) x <- y
  211. }
  212. # x <- gsub("\u201C|\u201D", '"', x)
  213. # x <- gsub("\u2018|\u2019", "'", x)
  214. x
  215. }
  216. toHTML <- function(...) {
  217. x <- paste(..., collapse = "")
  218. x <- gsub("\n", "\\\\n", x)
  219. x <- gsub("\t", "\\\\t", x)
  220. x <- gsub("\r", "\\\\r", x)
  221. HTML(x)
  222. }
  223. regexFn_choices <- list(
  224. "Choose a function" = "",
  225. base = c(
  226. "grep",
  227. "grepl",
  228. "sub", #<<
  229. "gsub", #<<
  230. "regexpr",
  231. "gregexpr",
  232. "regexec"
  233. ),
  234. stringr = c(
  235. "str_detect",
  236. "str_locate",
  237. "str_locate_all",
  238. "str_extract",
  239. "str_extract_all",
  240. "str_match",
  241. "str_match_all",
  242. "str_replace", #<<
  243. "str_replace_all", #<<
  244. "str_split"
  245. )
  246. )
  247. regexFn_substitute <- c(
  248. paste0(c("", "g"), "sub"),
  249. paste0("str_replace", c("", "_all"))
  250. )
  251. get_pkg_namespace <- function(fn) {
  252. x <- names(purrr::keep(regexFn_choices, ~ (fn %in% .)))
  253. if (length(x) > 1) warning(fn, " matches multiple functions in regexFn_choices, please review.")
  254. x
  255. }