🔍 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.

357 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. output$result <- renderUI({
  159. if (is.null(rtext())) return(NULL)
  160. if (input$pattern == "") {
  161. return(toHTML(paste('<p class="results">', escape_html(rtext()), "</p>", collapse = "")))
  162. }
  163. tryCatch({
  164. toHTML(
  165. paste(
  166. view_regex(
  167. rtext(),
  168. sanitize_text_input(input$pattern),
  169. ignore.case = 'ignore.case' %in% input$regex_options,
  170. perl = 'perl' %in% input$regex_options,
  171. fixed = 'fixed' %in% input$regex_options,
  172. useBytes = 'useBytes' %in% input$regex_options,
  173. # invert = 'invert' %in% input$regex_options,
  174. render = FALSE,
  175. escape = TRUE),
  176. collapse = ""
  177. )
  178. )
  179. },
  180. error = function(e) {
  181. toHTML(paste0("<pre class='alert alert-danger' style='padding: 4px; margin-top: 1px; margin-bottom: 4px;'>", paste(e$message, collapse = "<br>"), "</pre>"),
  182. paste('<p class="results">', escape_html(rtext()), "</p>", collapse = ""))
  183. })
  184. })
  185. output$output_result <- renderPrint({
  186. req(input$regexFn)
  187. regexPkg <- get_pkg_namespace(input$regexFn)
  188. regexFn <- getFromNamespace(input$regexFn, regexPkg)
  189. x <- if (regexPkg == "base") {
  190. regexFn(input$pattern, rtext())
  191. } else if (regexPkg == "stringr") {
  192. regexFn(rtext(), input$pattern)
  193. } else {
  194. "Um. Not sure how I got here."
  195. }
  196. # if (inherits(x, 'logical') || inherits(x, 'character')) {
  197. # if (length(x) < 25) print(x) else print(head(x, 25))
  198. # } else if (inherits(x, 'matrix') | inherits(x, "data.frame")) {
  199. # if (nrow(x) < 15) { print(x)
  200. # } else glimpse(x)
  201. # } else {
  202. # str(x, max.level = 3)
  203. # }
  204. print(x)
  205. })
  206. # output$help_group <- renderUI({
  207. # req(input$help_category)
  208. # groups <- unique(cheatsheet[cheatsheet$category == input$help_category, ]$group)
  209. # if (is.na(groups[1])) {
  210. # NULL
  211. # } else {
  212. # selectInput("help_group", "Group", groups)
  213. # }
  214. # })
  215. # ---- Help Section ---- #
  216. help_text <- reactiveVal("<p>Select a category from the left sidebar.</p>")
  217. output$help_text_selected <- renderUI({
  218. HTML(help_text())
  219. })
  220. make_html_table <- function(x) {
  221. select(x, .data$regexp, .data$text) %>%
  222. knitr::kable(
  223. col.names = c("Regexp", "Text"),
  224. escape = FALSE,
  225. format = "html")
  226. }
  227. observeEvent(input$help_cat_character_classes_regular, {
  228. cheatsheet %>%
  229. filter(category == "character classes", group == "regular") %>%
  230. make_html_table %>%
  231. help_text
  232. })
  233. observeEvent(input$help_cat_character_classes_prebuilt, {
  234. cheatsheet %>%
  235. filter(category == "character classes", group == "pre-built") %>%
  236. make_html_table %>%
  237. help_text
  238. })
  239. observeEvent(input$help_cat_anchors, {
  240. cheatsheet %>%
  241. filter(category == "anchors") %>%
  242. make_html_table %>%
  243. help_text
  244. })
  245. observeEvent(input$help_cat_escaped_general, {
  246. cheatsheet %>%
  247. filter(category == "escaped characters", group == "general") %>%
  248. make_html_table %>%
  249. help_text
  250. })
  251. observeEvent(input$help_cat_escaped_hex, {
  252. cheatsheet %>%
  253. filter(category == "escaped characters", group == "hex") %>%
  254. make_html_table %>%
  255. help_text
  256. })
  257. observeEvent(input$help_cat_escaped_control, {
  258. cheatsheet %>%
  259. filter(category == "escaped characters", group == "control characters") %>%
  260. make_html_table %>%
  261. help_text
  262. })
  263. observeEvent(input$help_cat_groups, {
  264. cheatsheet %>%
  265. filter(category == "groups") %>%
  266. make_html_table %>%
  267. help_text
  268. })
  269. observeEvent(input$help_cat_quantifiers, {
  270. cheatsheet %>%
  271. filter(category == "quantifiers") %>%
  272. make_html_table %>%
  273. help_text
  274. })
  275. observeEvent(input$done, {
  276. # browser()
  277. if (input$pattern != "") {
  278. pattern <- paste0('regex <- "', escape_backslash(sanitize_text_input(input$pattern)), '"')
  279. rstudioapi::sendToConsole(pattern, FALSE)
  280. }
  281. stopApp()
  282. })
  283. observeEvent(input$cancel, {
  284. stopApp()
  285. })
  286. }
  287. viewer <- shiny::paneViewer(700)
  288. runGadget(ui, server, viewer = viewer)
  289. }
  290. sanitize_text_input <- function(x) {
  291. x <- gsub("(“|”)", '"', x)
  292. x <- gsub("‘|’", "'", x)
  293. x
  294. }
  295. toHTML <- function(...) {
  296. x <- paste(..., collapse = "")
  297. x <- gsub("\n", "\\\\n", x)
  298. x <- gsub("\t", "\\\\t", x)
  299. x <- gsub("\r", "\\\\r", x)
  300. HTML(x)
  301. }
  302. regexFn_choices <- list(
  303. "Choose a function" = "",
  304. base = c(
  305. "grep",
  306. "grepl",
  307. "regexpr",
  308. "gregexpr",
  309. "regexec"
  310. ),
  311. stringr = c(
  312. "str_detect",
  313. "str_locate",
  314. "str_locate_all",
  315. "str_extract",
  316. "str_extract_all",
  317. "str_match",
  318. "str_match_all",
  319. "str_split"
  320. )
  321. )
  322. get_pkg_namespace <- function(fn) {
  323. x <- names(purrr::keep(regexFn_choices, ~ (fn %in% .)))
  324. if (length(x) > 1) warning(fn, " matches multiple functions in regexFn_choices, please review.")
  325. x
  326. }