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

385 lines
11KB

  1. #' regexplain_addin
  2. #'
  3. #' @keywords internal
  4. regexplain_addin <- function() {
  5. # Get the document context.
  6. context <- rstudioapi::getActiveDocumentContext()
  7. # Get context text
  8. ctx_text <- context$selection[[1]]$text
  9. # If it is one line and evaluates to something, use that
  10. # Otherwise treat as text
  11. obj <- tryCatch({
  12. if (grepl("\n", ctx_text)) {
  13. ctx_text[1:min(length(ctx_text), 100)]
  14. } else {
  15. x <- eval(parse(text = ctx_text))
  16. x <- as.character(x)
  17. if (length(x) == 1 && grepl("\n", x))
  18. x <- strsplit(x, "\n")[[1]]
  19. if (length(x) > 10) {
  20. message(ctx_text, " gave ", length(x), " lines, limiting to first 10 unique lines.")
  21. x <- unique(x)
  22. x[1:min(length(x), 10)]
  23. } else x
  24. }
  25. },
  26. error = function(e) {as.character(ctx_text[1:min(length(ctx_text), 100)])})
  27. regex_gadget(if (length(obj) && obj != "") obj)
  28. }
  29. #' regexplain gadget
  30. #'
  31. #' @import miniUI
  32. #' @import shiny
  33. #' @param text Text to explore in gadget (editable using interface)
  34. #' @param start_page Open gadget to this tab, one of `"Text"`, `"Regex"`,
  35. #' `"Output"`, or `"Help"`
  36. #' @export
  37. regex_gadget <- function(text = NULL,
  38. start_page = if (is.null(text)) "Text" else "Regex") {
  39. stopifnot(requireNamespace("miniUI"), requireNamespace("shiny"))
  40. ui <- miniPage(
  41. shiny::includeCSS(system.file("style.css", package = "regexplain")),
  42. shiny::includeCSS(system.file("gadget.css", package = "regexplain")),
  43. gadgetTitleBar(
  44. "regexplain",
  45. right = miniTitleBarButton("done", "Send Regex To Console", TRUE)
  46. ),
  47. miniTabstripPanel(
  48. selected = match.arg(start_page, c("Text", "Regex", "Output", "Help")),
  49. miniTabPanel(
  50. "Text", icon = icon('file-text-o'),
  51. miniContentPanel(
  52. fillCol(
  53. textAreaInputAlt('text',
  54. label = "Text to search or parse",
  55. value = paste(text, collapse = "\n"),
  56. resize = "both",
  57. width = "100%",
  58. height="90%",
  59. placeholder = "Paste, enter, or edit your sample text here.")
  60. )
  61. )
  62. ),
  63. miniTabPanel(
  64. "Regex", icon = icon('terminal'),
  65. miniContentPanel(
  66. fillCol(
  67. flex = c(1, 3),
  68. fillCol(
  69. flex = c(1, 1),
  70. textInputCode('pattern', 'Regex', width = "100%",
  71. placeholder = "Enter regex, single \\ okay"),
  72. checkboxGroupInput(
  73. 'regex_options',
  74. label = "",
  75. inline = TRUE,
  76. width = "90%",
  77. choices = c("Break Lines" = "text_break_lines",
  78. "Ignore Case" = "ignore.case",
  79. "Perl Style" = "perl",
  80. "Fixed" = "fixed",
  81. "Use Bytes" = "useBytes"
  82. # , "Invert" = "invert"
  83. ),
  84. selected = c('text_break_lines')
  85. )
  86. ),
  87. tags$div(
  88. class = "gadget-result",
  89. style = "overflow-y: scroll; height: 100%;",
  90. htmlOutput('result')
  91. )
  92. )
  93. )
  94. ),
  95. miniTabPanel(
  96. "Output", icon = icon("table"),
  97. miniContentPanel(
  98. fillCol(
  99. flex = c(1, 3),
  100. inputPanel(
  101. width = "100%;",
  102. selectInput('regexFn', label = 'Apply Function',
  103. choices = regexFn_choices)
  104. ),
  105. # verbatimTextOutput('output_result', placeholder = TRUE)
  106. tags$pre(
  107. id = "output_result",
  108. class = "shiny-text-output",
  109. style = "overflow-y: scroll; height: 100%;"
  110. )
  111. )
  112. )
  113. ),
  114. miniTabPanel(
  115. "Help", icon = icon("support"),
  116. miniContentPanel(
  117. fillRow(
  118. flex = c(1, 4),
  119. tagList(
  120. # selectInput("help_category", "Category", c("", unique(cheatsheet$category))),
  121. # uiOutput("help_group"),
  122. tags$ul(
  123. id = "help-sidebar",
  124. tags$li("Character Classes", class = "header"),
  125. tags$ul(
  126. class = "subgroup",
  127. tags$li(actionLink("help_cat_character_classes_regular", "Regular")),
  128. tags$li(actionLink("help_cat_character_classes_prebuilt", "Pre-Built"))
  129. ),
  130. tags$li(actionLink("help_cat_anchors", "Anchors")),
  131. tags$li("Escaped Characters", class = "header"),
  132. tags$ul(
  133. class = "subgroup",
  134. tags$li(actionLink("help_cat_escaped_general", "General")),
  135. tags$li(actionLink("help_cat_escaped_hex", "Hex")),
  136. tags$li(actionLink("help_cat_escaped_control", "Control Characters"))
  137. ),
  138. tags$li(actionLink("help_cat_groups", "Groups")),
  139. tags$li(actionLink("help_cat_quantifiers", "Quantifiers"))
  140. )
  141. ),
  142. tags$div(
  143. style = "width: 100%; padding-left: 10px;",
  144. uiOutput('help_text_selected')
  145. )
  146. )
  147. )
  148. )
  149. )
  150. )
  151. server <- function(input, output, session) {
  152. rtext <- reactive({
  153. x <- if ('text_break_lines' %in% input$regex_options) {
  154. strsplit(input$text, "\n")[[1]]
  155. } else input$text
  156. x
  157. })
  158. pattern <- reactive({
  159. sanitize_text_input(input$pattern)
  160. })
  161. alert_result <- function(msg, type = "danger") {
  162. msg <- gsub("\n", "<br>", msg)
  163. msg <- gsub("\t", "&nbsp;&nbsp;", msg)
  164. paste0("<pre class='alert alert-", type, "' ",
  165. "style='padding: 4px; margin-top: 1px; margin-bottom: 4px;'>",
  166. paste(msg, collapse = "<br>"),
  167. "</pre>")
  168. }
  169. output$result <- renderUI({
  170. if (is.null(rtext())) return(NULL)
  171. if (pattern() == "") {
  172. return(toHTML(paste('<p class="results">', escape_html(rtext()), "</p>", collapse = "")))
  173. }
  174. res <- NULL
  175. error_message <- NULL
  176. warning_message <- NULL
  177. tryCatch({
  178. res <- paste(
  179. view_regex(
  180. rtext(),
  181. pattern(),
  182. ignore.case = 'ignore.case' %in% input$regex_options,
  183. perl = 'perl' %in% input$regex_options,
  184. fixed = 'fixed' %in% input$regex_options,
  185. useBytes = 'useBytes' %in% input$regex_options,
  186. # invert = 'invert' %in% input$regex_options,
  187. render = FALSE,
  188. escape = TRUE),
  189. collapse = ""
  190. )
  191. },
  192. error = function(e) {
  193. error_message <<- alert_result(e$message, "danger")
  194. },
  195. warning = function(w) {
  196. warning_message <<- alert_result(w$message, "warning")
  197. })
  198. if (is.null(res)) res <- toHTML(
  199. paste('<p class="results">', escape_html(rtext()), "</p>", collapse = "")
  200. )
  201. toHTML(paste(error_message, warning_message, res))
  202. })
  203. output$output_result <- renderPrint({
  204. req(input$regexFn)
  205. regexPkg <- get_pkg_namespace(input$regexFn)
  206. regexFn <- getFromNamespace(input$regexFn, regexPkg)
  207. x <- if (regexPkg == "base") {
  208. regexFn(pattern(), rtext())
  209. } else if (regexPkg == "stringr") {
  210. regexFn(rtext(), pattern())
  211. } else {
  212. "Um. Not sure how I got here."
  213. }
  214. # if (inherits(x, 'logical') || inherits(x, 'character')) {
  215. # if (length(x) < 25) print(x) else print(head(x, 25))
  216. # } else if (inherits(x, 'matrix') | inherits(x, "data.frame")) {
  217. # if (nrow(x) < 15) { print(x)
  218. # } else glimpse(x)
  219. # } else {
  220. # str(x, max.level = 3)
  221. # }
  222. print(x)
  223. })
  224. # output$help_group <- renderUI({
  225. # req(input$help_category)
  226. # groups <- unique(cheatsheet[cheatsheet$category == input$help_category, ]$group)
  227. # if (is.na(groups[1])) {
  228. # NULL
  229. # } else {
  230. # selectInput("help_group", "Group", groups)
  231. # }
  232. # })
  233. # ---- Help Section ---- #
  234. help_text <- reactiveVal("<p>Select a category from the left sidebar.</p>")
  235. output$help_text_selected <- renderUI({
  236. HTML(help_text())
  237. })
  238. make_html_table <- function(x) {
  239. select(x, .data$regexp, .data$text) %>%
  240. knitr::kable(
  241. col.names = c("Regexp", "Text"),
  242. escape = FALSE,
  243. format = "html")
  244. }
  245. observeEvent(input$help_cat_character_classes_regular, {
  246. cheatsheet %>%
  247. filter(category == "character classes", group == "regular") %>%
  248. make_html_table %>%
  249. help_text
  250. })
  251. observeEvent(input$help_cat_character_classes_prebuilt, {
  252. cheatsheet %>%
  253. filter(category == "character classes", group == "pre-built") %>%
  254. make_html_table %>%
  255. help_text
  256. })
  257. observeEvent(input$help_cat_anchors, {
  258. cheatsheet %>%
  259. filter(category == "anchors") %>%
  260. make_html_table %>%
  261. help_text
  262. })
  263. observeEvent(input$help_cat_escaped_general, {
  264. cheatsheet %>%
  265. filter(category == "escaped characters", group == "general") %>%
  266. make_html_table %>%
  267. help_text
  268. })
  269. observeEvent(input$help_cat_escaped_hex, {
  270. cheatsheet %>%
  271. filter(category == "escaped characters", group == "hex") %>%
  272. make_html_table %>%
  273. help_text
  274. })
  275. observeEvent(input$help_cat_escaped_control, {
  276. cheatsheet %>%
  277. filter(category == "escaped characters", group == "control characters") %>%
  278. make_html_table %>%
  279. help_text
  280. })
  281. observeEvent(input$help_cat_groups, {
  282. cheatsheet %>%
  283. filter(category == "groups") %>%
  284. make_html_table %>%
  285. help_text
  286. })
  287. observeEvent(input$help_cat_quantifiers, {
  288. cheatsheet %>%
  289. filter(category == "quantifiers") %>%
  290. make_html_table %>%
  291. help_text
  292. })
  293. observeEvent(input$done, {
  294. # browser()
  295. if (pattern() != "") {
  296. pattern <- paste0('regex <- "', escape_backslash(pattern()), '"')
  297. rstudioapi::sendToConsole(pattern, FALSE)
  298. }
  299. stopApp()
  300. })
  301. observeEvent(input$cancel, {
  302. stopApp()
  303. })
  304. }
  305. viewer <- shiny::paneViewer(700)
  306. runGadget(ui, server, viewer = viewer)
  307. }
  308. sanitize_text_input <- function(x) {
  309. if (grepl("\\u|\\x|\\N|\\a|\\o", x)) {
  310. try({
  311. y <- stringi::stri_unescape_unicode(x)
  312. }, silent = TRUE)
  313. if (!is.na(y)) x <- y
  314. }
  315. # x <- gsub("\u201C|\u201D", '"', x)
  316. # x <- gsub("\u2018|\u2019", "'", x)
  317. x
  318. }
  319. toHTML <- function(...) {
  320. x <- paste(..., collapse = "")
  321. x <- gsub("\n", "\\\\n", x)
  322. x <- gsub("\t", "\\\\t", x)
  323. x <- gsub("\r", "\\\\r", x)
  324. HTML(x)
  325. }
  326. regexFn_choices <- list(
  327. "Choose a function" = "",
  328. base = c(
  329. "grep",
  330. "grepl",
  331. "regexpr",
  332. "gregexpr",
  333. "regexec"
  334. ),
  335. stringr = c(
  336. "str_detect",
  337. "str_locate",
  338. "str_locate_all",
  339. "str_extract",
  340. "str_extract_all",
  341. "str_match",
  342. "str_match_all",
  343. "str_split"
  344. )
  345. )
  346. get_pkg_namespace <- function(fn) {
  347. x <- names(purrr::keep(regexFn_choices, ~ (fn %in% .)))
  348. if (length(x) > 1) warning(fn, " matches multiple functions in regexFn_choices, please review.")
  349. x
  350. }