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

353 lines
10KB

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