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

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