🔍 An RStudio addin slash regex utility belt
選択できるのは25トピックまでです。 トピックは、先頭が英数字で、英数字とダッシュ('-')を使用した35文字以内のものにしてください。

289 行
8.2KB

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