├── .Rbuildignore ├── .gitignore ├── DESCRIPTION ├── LICENSE ├── NAMESPACE ├── R ├── gistfo.R ├── sync.R └── utils.R ├── README.md ├── gistfo.Rproj ├── inst ├── media │ ├── carbon.png │ ├── gistfo-app.png │ └── gistfo.gif └── rstudio │ └── addins.dcf └── man └── gistfo.Rd /.Rbuildignore: -------------------------------------------------------------------------------- 1 | ^.*\.Rproj$ 2 | ^\.Rproj\.user$ 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .Rproj.user 2 | .Rhistory 3 | .RData 4 | .Ruserdata 5 | .DS_Store 6 | -------------------------------------------------------------------------------- /DESCRIPTION: -------------------------------------------------------------------------------- 1 | Package: gistfo 2 | Title: Get it Somewhere The F Online 3 | Version: 0.0.2.9000 4 | Authors@R: 5 | c(person(given = "Miles", 6 | family = "McBain", 7 | role = "aut", 8 | email = "miles.mcbain@gmail.com"), 9 | person(given = "Garrick", 10 | family = "Aden-Buie", 11 | role = c("aut", "cre"), 12 | email = "garrick@adenbuie.com", 13 | comment = c(ORCID = "0000-0002-7111-0077"))) 14 | Description: Sends the currently active RStudio tab or selection 15 | to Github as a private gist. Can also create a public gist and send 16 | to carbon.now.sh for easy Tweeting of saucy code pic. 17 | License: MIT + file LICENSE 18 | URL: https://github.com/milesmcbain/gistfo 19 | BugReports: https://github.com/milesmcbain/gistfo/issues 20 | Depends: 21 | R (>= 3.4.0) 22 | Imports: 23 | gistr (>= 0.4.0), 24 | glue (>= 1.1.1), 25 | R6, 26 | rstudioapi (>= 0.6) 27 | Suggests: 28 | clipr (>= 0.3.0), 29 | curl (>= 1.0), 30 | gh, 31 | miniUI, 32 | prettyunits, 33 | reactable (>= 0.2.0), 34 | shiny 35 | Encoding: UTF-8 36 | LazyData: true 37 | Roxygen: list(markdown = TRUE) 38 | RoxygenNote: 7.1.1 39 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | YEAR: 2017 2 | COPYRIGHT HOLDER: Your name goes here 3 | -------------------------------------------------------------------------------- /NAMESPACE: -------------------------------------------------------------------------------- 1 | # Generated by roxygen2: do not edit by hand 2 | 3 | export(gistfo) 4 | export(gistfoc) 5 | -------------------------------------------------------------------------------- /R/gistfo.R: -------------------------------------------------------------------------------- 1 | CARBON_URL <- "https://carbon.now.sh/{gist_id}" 2 | 3 | #' Share your code as a gist or on carbon.now.sh 4 | #' 5 | #' Creates a private or public gist containing the active text selection or the 6 | #' active RStudio source file. Creating a public gist also prompts you to ask if 7 | #' you want to share the code to [carbon](https://carbon.now.sh), where it is 8 | #' converted into a beautiful screen shot. 9 | #' 10 | #' The default file name of the [GitHub gist](https://gist.github.com) is: 11 | #' `__`, where `file_id` is a unique 12 | #' id for untitled files. It does not relate to the untitled number. You'll be 13 | #' asked to confirm or edit the file name before uploading. 14 | #' 15 | #' @return The URL of the gist on GitHub. Also opens browser windows to the 16 | #' GitHub gist. Another browser window will open if you choose to open your 17 | #' gist in Carbon and the URL of the gist will be copied to the clipboard. 18 | #' @name gistfo 19 | NULL 20 | 21 | #' @describeIn gistfo Create a private gist and browse to it 22 | #' @export 23 | gistfo <- function() gistfo_base(mode = "gistfo") 24 | 25 | #' @describeIn gistfo Create a public gist and optionally share with 26 | #' [carbon](https://carbon.now.sh) 27 | #' @export 28 | gistfoc <- function() gistfo_base(mode = "carbon") 29 | 30 | gistfo_base <- function(mode = c("gistfo", "carbon")) { 31 | mode <- match.arg(mode) 32 | 33 | source_context <- rstudioapi::getSourceEditorContext() 34 | 35 | name <- if (source_context$path == "") { 36 | paste0("untitled-", source_context$id, ".R") 37 | } else { 38 | last(strsplit(x = source_context$path, split = "/")[[1]]) 39 | } 40 | project <- rstudioapi::getActiveProject() 41 | project <- if (!is.null(project)) { 42 | last(strsplit(x = project, split = "/")[[1]]) 43 | } else { 44 | "RStudio" 45 | } 46 | gist_content <- source_context$selection[[1]]$text 47 | if (gist_content == "") { 48 | gist_name <- paste(project, name, sep = "_") 49 | gist_content <- paste0(source_context$contents, collapse = "\n") 50 | } else { 51 | gist_name <- paste(project, "selection", name, sep = "_") 52 | } 53 | 54 | # User prompts 55 | gist_name <- ask_for_filename(gist_name) 56 | if (is.null(gist_name)) { 57 | message("Upload cancelled by user") 58 | return() 59 | } 60 | open_in_carbon <- if (identical(mode, "carbon")) ask_if_carbon() else FALSE 61 | 62 | gist_file <- file.path(tempdir(), gist_name) 63 | cat(gist_content, file = gist_file) 64 | the_gist <- gistr::gist_create( 65 | files = gist_file, 66 | public = identical(mode, "carbon"), 67 | browse = FALSE 68 | ) 69 | if (!(identical(mode, "carbon") && open_in_carbon)) { 70 | utils::browseURL(the_gist$html_url) 71 | return(the_gist$html_url) 72 | } 73 | 74 | # send to carbon ---- 75 | # Add URL to gist as comment at bottom of gist 76 | if (is_file_ext(gist_name, "r", "html", "r?md", "js", "cpp", "py")) { 77 | gist_url <- url_git_io(the_gist$html_url) 78 | comment <- comment_single_line(gist_name, gist_url) 79 | cat(comment, file = gist_file, append = TRUE) 80 | the_gist <- gistr::update_files(the_gist, gist_file) 81 | gistr::update(the_gist) 82 | } 83 | 84 | utils::browseURL(the_gist$html_url) 85 | utils::browseURL(glue::glue(CARBON_URL, gist_id = the_gist$id)) 86 | maybe_clip(gist_url) 87 | } 88 | 89 | # Create Shortlink for URL using git.io 90 | url_git_io <- function(url) { 91 | if (!requireNamespace("curl", quietly = TRUE)) { 92 | return(url) 93 | } 94 | h <- curl::new_handle() 95 | curl::handle_setform(h, url = url) 96 | r <- curl::curl_fetch_memory("https://git.io", h) 97 | if (!r$status_code %in% 200:203) { 98 | return(url) 99 | } 100 | short_url <- curl::parse_headers_list(r$headers)$location 101 | if (!is.null(short_url) && grepl("git\\.io", short_url)) short_url else url 102 | } 103 | 104 | is_file_ext <- function(path, ...) { 105 | exts <- paste(tolower(c(...)), collapse = "|") 106 | grepl(glue::glue("[.]({exts})$"), tolower(path)) 107 | } 108 | 109 | comment_single_line <- function(path, comment) { 110 | comment <- trimws(comment) 111 | if (grepl("\n", comment)) { 112 | stop("`comment` must be single-line") 113 | } 114 | if (is_file_ext(path, "r", "py")) { 115 | glue::glue("\n\n# {comment}\n", .trim = FALSE) 116 | } else if (is_file_ext(path, "html", "r?md")) { 117 | glue::glue("\n\n\n", .trim = FALSE) 118 | } else if (is_file_ext(path, "js", "cpp")) { 119 | glue::glue("\n\n// {comment}\n", .trim = FALSE) 120 | } else "" 121 | } 122 | 123 | last <- function(x) { 124 | x[[length(x)]] 125 | } 126 | 127 | ask_for_filename <- function(name) { 128 | if (!rstudioapi::hasFun("showPrompt")) { 129 | return(name) 130 | } 131 | 132 | rstudioapi::showPrompt( 133 | title = "Gist Name", 134 | message = "Gist Filename (including extension)", 135 | default = name 136 | ) 137 | } 138 | 139 | ask_if_carbon <- function() { 140 | if (!rstudioapi::hasFun("showQuestion")) { 141 | return(TRUE) 142 | } 143 | 144 | rstudioapi::showQuestion( 145 | title = "Open on carbon.now.sh?", 146 | message = paste( 147 | "Do you want to open the gist on Carbon for a beautiful,", 148 | "shareable source code image?" 149 | ), 150 | ok = "Yes", 151 | cancel = "No" 152 | ) 153 | } 154 | 155 | maybe_clip <- function(text) { 156 | has_clipr <- requireNamespace("clipr", quietly = TRUE) 157 | if (has_clipr && clipr::clipr_available()) { 158 | clipr::write_clip(text) 159 | } 160 | text 161 | } 162 | -------------------------------------------------------------------------------- /R/sync.R: -------------------------------------------------------------------------------- 1 | GithubGists <- R6::R6Class( 2 | "GithubGists", 3 | public = list( 4 | user = NULL, 5 | complete = FALSE, 6 | initialize = function(user = NULL) { 7 | requires_pkg("gh") 8 | self$user <- if (is.null(user)) { 9 | gh::gh_whoami()$login 10 | } else user 11 | 12 | first_page <- gh::gh("/users/:user/gists", user = self$user, per_page = 10) 13 | private$is_complete(first_page) 14 | private$pages <- list(first_page) 15 | }, 16 | next_page = function() { 17 | next_page <- gh::gh_next(private$pages[[length(private$pages)]]) 18 | private$is_complete(next_page) 19 | private$pages <- c(private$pages, list(next_page)) 20 | self$view(length(private$pages)) 21 | }, 22 | n_pages = function() { 23 | length(private$pages) 24 | }, 25 | gist = function(id) { 26 | stopifnot(is.character(id), length(id) == 1) 27 | for (page in private$pages) { 28 | for (gist in page) { 29 | if (gist$id == id) return(gist) 30 | } 31 | } 32 | stop("Gist not found: ", id) 33 | }, 34 | view = function(page = NULL, raw = FALSE) { 35 | if (is.null(page)) { 36 | pages <- lapply(seq_along(private$pages), self$view, raw = raw) 37 | out <- list() 38 | for (item in pages) { 39 | out <- c(out, item) 40 | } 41 | return(out) 42 | } 43 | 44 | if (page < 1) return(NULL) 45 | 46 | if (page > length(private$pages)) { 47 | if (!self$complete) { 48 | return(self$next_page()) 49 | } else { 50 | return(NULL) 51 | } 52 | } 53 | 54 | gists <- private$pages[[page]] 55 | if (raw) return(gists) 56 | 57 | lapply(gists, function(g) { 58 | c( 59 | g[c("id", "html_url", "public", "created_at", "updated_at", "description")], 60 | list(files = paste(names(g$files), collapse = ", ")) 61 | ) 62 | }) 63 | }, 64 | df = function(page = NULL, icon_link = TRUE) { 65 | x <- self$view(page) 66 | x <- lapply(x, function(g) { 67 | g$public <- ifelse(g$public, "Public", "Private") 68 | if (icon_link) { 69 | g$html_url <- paste0( 70 | '', 74 | '', 75 | '', 76 | '' 77 | ) 78 | } 79 | g$updated_at <- sub("([\\d-]{10})T([\\d:]{5}).+", "\\1 \\2", g$updated_at, perl = TRUE) 80 | g$created_at <- sub("([\\d-]{10})T([\\d:]{5}).+", "\\1 \\2", g$created_at, perl = TRUE) 81 | g 82 | }) 83 | if (length(x) == 1) { 84 | as.data.frame(x) 85 | } else { 86 | as.data.frame(do.call("rbind", x)) 87 | } 88 | } 89 | ), 90 | private = list( 91 | pages = list(), 92 | is_complete = function(res) { 93 | link <- attributes(res)$response$link 94 | has_next <- !is.null(link) && grepl('rel="next"', link, fixed = TRUE) 95 | self$complete <- !has_next 96 | !has_next 97 | } 98 | ) 99 | ) 100 | 101 | 102 | gistfo_app <- function(user = NULL, preload_all_gists = FALSE) { 103 | requires_pkg("gh") 104 | requires_pkg("shiny") 105 | requires_pkg("miniUI") 106 | requires_pkg("reactable") 107 | 108 | if (is.null(user)) user <- getOption("github.username", NULL) # ?gistr::gists 109 | gh_user <- gh::gh_whoami() 110 | user_has_gist_scope <- if (!is.null(gh_user)) { 111 | any(grepl("gist", gh_user$scopes)) 112 | } else { 113 | FALSE 114 | } 115 | if (is.null(user)) { 116 | user <- gh_user$login 117 | if (!user_has_gist_scope) { 118 | warning( 119 | "You do not have the 'gist' scope enabled for your GitHub PAT for user ", 120 | '"', user, '". See ?gh::gh_whoami for more information on how to set ', 121 | "up your GitHub PAT and be sure to include at least the gist and user ", 122 | "scopes for {gistfo} to work properly.", 123 | call. = FALSE, 124 | immediate. = TRUE 125 | ) 126 | } 127 | } 128 | 129 | if (is.null(user) || !nzchar(user)) { 130 | stop( 131 | "Couldn't guess user name. Please set your GitHub user name via ", 132 | "`options(\"github.username\" = \"ghuser\")` or set up a GitHub PAT ", 133 | "(see ?gh::gh_whoami for more information). Finally, you can manually call ", 134 | 'the app with your username: `gistfo:::gistfo_app("ghuser")`.' 135 | ) 136 | } 137 | 138 | gists <- GithubGists$new(user = user) 139 | if (!gists$complete) gists$next_page() 140 | if (isTRUE(preload_all_gists)) { 141 | cat("Loading gists..") 142 | while(!gists$complete) { 143 | gists$next_page() 144 | cat(".") 145 | } 146 | } 147 | 148 | owns_gist <- function(id) { 149 | if (is.null(gh_user$login)) return(FALSE) 150 | gh_user$login == gists$gist(id)$owner$login 151 | } 152 | 153 | theme <- gistfo_app_theme() 154 | 155 | ui <- miniUI::miniPage( 156 | title = "GitHub Gists", 157 | shiny::tags$style(shiny::HTML( 158 | sprintf(paste( 159 | sep = "\n", 160 | "body, .ReactTable { background-color: %s; color: %s; }", 161 | ".rt-search { color: %s; }", 162 | ".gadget-title { background-color: %s; border-bottom: none; }" 163 | ), theme$background, theme$color, theme$search, theme$title_bar_background) 164 | )), 165 | miniUI::miniTitleBar( 166 | title = "GitHub Gists", 167 | left = shiny::div( 168 | shiny::uiOutput("left_buttons") 169 | ), 170 | right = shiny::div( 171 | id = "gist-action-buttons", 172 | miniUI::miniTitleBarButton("save_gist", "Save Gist"), 173 | miniUI::miniTitleBarButton("open_gist", "Open Gist", TRUE) 174 | ) 175 | ), 176 | miniUI::miniContentPanel( 177 | reactable::reactableOutput("gists", height = "100%"), 178 | scrollable = TRUE, 179 | height = "100%" 180 | ), 181 | shiny::tags$script(shiny::HTML( 182 | "Shiny.addCustomMessageHandler('toggleActions', function(state) { 183 | const btns = document.querySelectorAll('#gist-action-buttons button'); 184 | for (let i = 0; i < btns.length; i++) { 185 | btns[i].classList.toggle('disabled', !state); 186 | state ? btns[i].removeAttribute('disabled') : btns[i].setAttribute('disabled', true); 187 | } 188 | });" 189 | )) 190 | ) 191 | 192 | server <- function(input, output, session) { 193 | trigger_table_update <- shiny::reactiveVal(NULL) 194 | 195 | if (user_has_gist_scope) { 196 | # if the user has the gist scope, then they can write gists back to their account 197 | # and we check each gist to make sure they're the owner (idk, maybe forks?) 198 | # then we poll RStudio for the currently open tab and if it's a gist 199 | # that the app opened, we return the gist id grabbed from the temp dir 200 | # where the gist is stored. Finally, we use a dynamic html output to 201 | # update the buttons on the left side of the app: "Update gist-file.R" 202 | rstudio_open_gist_file <- shiny::reactivePoll( 203 | 1000, session, 204 | checkFunc = function() { 205 | rstudioapi::getSourceEditorContext()$path 206 | }, 207 | valueFunc = function() { 208 | open_tab <- rstudioapi::getSourceEditorContext()$path 209 | if (is.null(open_tab) || open_tab == "") return(NULL) 210 | id <- basename(dirname(open_tab)) 211 | ids <- sapply(gists$view(), function(x) x$id) 212 | if (id %in% ids) { 213 | if (!owns_gist(id)) return(NULL) 214 | basename(open_tab) 215 | } 216 | } 217 | ) 218 | 219 | output$left_buttons <- shiny::renderUI({ 220 | if (shiny::isTruthy(rstudio_open_gist_file())) { 221 | filename <- rstudio_open_gist_file() 222 | if (nchar(filename) > 16) { 223 | filename <- paste0(substr(filename, 1, 16), "...") 224 | } 225 | shiny::tagList( 226 | miniUI::miniTitleBarCancelButton(label = "Quit"), 227 | miniUI::miniTitleBarButton( 228 | "update_gist", 229 | shiny::HTML( 230 | paste0("Update ", filename, "") 231 | ) 232 | ), 233 | ) 234 | } else { 235 | miniUI::miniTitleBarCancelButton(label = "Quit") 236 | } 237 | }) 238 | } else { 239 | # User doesn't have "gist" scope, so can only quit (or open gists) 240 | output$left_buttons <- shiny::renderUI({ 241 | miniUI::miniTitleBarCancelButton(label = "Quit") 242 | }) 243 | } 244 | 245 | output$gists <- reactable::renderReactable({ 246 | tbl <- gists$df()[, c("description", "files", "created_at", "updated_at", "public", "html_url")] 247 | names(tbl) <- c("Description", "Files", "Created", "Updated", "Public", "Link") 248 | reactable::reactable( 249 | tbl, 250 | selection = "single", 251 | onClick = "select", 252 | borderless = TRUE, 253 | searchable = TRUE, 254 | pagination = TRUE, 255 | paginationType = "numbers", 256 | theme = reactable::reactableTheme( 257 | rowSelectedStyle = list( 258 | backgroundColor = theme$highlight_background, 259 | boxShadow = "inset 2px 0 0 0 #337ab7" 260 | ), 261 | rowStyle = list(verticalAlign = "middle") 262 | ), 263 | columns = list( 264 | Description = reactable::colDef(minWidth = 125), 265 | Files = reactable::colDef(minWidth = 125), 266 | Created = reactable::colDef(minWidth = 80, cell = vague_time_since), 267 | Updated = reactable::colDef(minWidth = 80, cell = vague_time_since), 268 | Link = reactable::colDef(minWidth = 50, html = TRUE, align = "center", sortable = FALSE), 269 | Public = reactable::colDef( 270 | minWidth = 60, 271 | cell = function(value) if (value == "Public") "" else "\U1F512", 272 | align = "center" 273 | ) 274 | ) 275 | ) 276 | }) 277 | 278 | shiny::observeEvent(trigger_table_update(), { 279 | tbl <- gists$df()[, c("description", "files", "created_at", "updated_at", "public", "html_url")] 280 | names(tbl) <- c("Description", "Files", "Created", "Updated", "Public", "Link") 281 | reactable::updateReactable("gists", data = tbl, page = reactable::getReactableState("gists", "page")) 282 | }) 283 | 284 | gists_selected <- shiny::reactive({ 285 | sel_id <- reactable::getReactableState("gists")$selected 286 | shiny::req(sel_id) 287 | gists$df()[, "id"][[sel_id]] 288 | }) 289 | 290 | shiny::observe({ 291 | has_sel <- !is.null(reactable::getReactableState("gists")$selected) 292 | session$sendCustomMessage('toggleActions', has_sel) 293 | }) 294 | 295 | shiny::observeEvent(reactable::getReactableState("gists"), { 296 | state <- reactable::getReactableState("gists") 297 | shiny::req(state) 298 | if (state$page != 1 && state$page == state$pages && !gists$complete) { 299 | gists$next_page() 300 | trigger_table_update(Sys.time()) 301 | } 302 | }) 303 | 304 | shiny::observeEvent(input$cancel, shiny::stopApp()) 305 | shiny::observeEvent(input$open_gist, gist_open_rstudio(gists_selected())) 306 | shiny::observeEvent(input$save_gist, gist_open_rstudio(gists_selected(), NULL, NULL)) 307 | shiny::observeEvent(input$update_gist, update_gist()) 308 | } 309 | 310 | shiny::runGadget(ui, server, viewer = shiny::paneViewer(400)) 311 | } 312 | 313 | gist_open_rstudio <- function(id, dir = tempdir(), open = TRUE) { 314 | if (is.null(dir)) { 315 | dir <- rstudioapi::selectDirectory(label = "Choose a parent directory for the gist") 316 | } 317 | if (is.null(dir)) { 318 | message("Cancelled by user") 319 | return(NULL) 320 | } 321 | if (is.null(open)) { 322 | open <- rstudioapi::showQuestion("Open Gist Files", "Do you want to open the gist files?", "Yes", "No") 323 | } 324 | g <- gistr::gist_save(id, dir) 325 | files <- list.files(file.path(dir, id), full.names = TRUE, all.files = TRUE) 326 | files <- files[!grepl("/[.]{1,2}$", files)] 327 | lapply(files, rstudioapi::navigateToFile) 328 | invisible(id) 329 | } 330 | 331 | update_gist <- function(path = NULL, id = NULL) { 332 | if (is.null(path)) { 333 | path <- rstudioapi::getSourceEditorContext()$path 334 | path <- dirname(path) 335 | } 336 | if (!dir.exists(path)) { 337 | stop("`path` should be a directory containing a gist") 338 | } 339 | if (is.null(id)) { 340 | id <- basename(path) 341 | } 342 | g <- gistr::gist(id) 343 | g$update_files <- as.list(list.files( 344 | path, full.names = TRUE, all.files = TRUE, no.. = TRUE, include.dirs = FALSE 345 | )) 346 | g <- gistr::update(g) 347 | message("Updated gist ", id) 348 | invisible(g) 349 | } 350 | 351 | vague_time_since <- function(value) { 352 | if (!requireNamespace("prettyunits", quietly = TRUE)) { 353 | return(value) 354 | } 355 | prettyunits::vague_dt( 356 | difftime(Sys.time(), strptime(value, "%F %H:%M")) 357 | ) 358 | } 359 | 360 | gistfo_app_theme <- function() { 361 | theme <- list( 362 | background = "#FFFFFF", 363 | color = "#333333", 364 | search = "#333333", 365 | highlight_background = "#eeeeee", 366 | title_bar_background = "#e5e5e5", 367 | dark = FALSE 368 | ) 369 | 370 | if (!rstudioapi::hasFun("getThemeInfo")) return(theme) 371 | 372 | rstheme <- rstudioapi::getThemeInfo() 373 | 374 | theme$dark <- rstheme$dark 375 | if (is.null(rstheme$foreground)) return(theme) 376 | 377 | theme$background <- rstheme$background 378 | theme$color <- rstheme$foreground 379 | theme$search <- if (theme$dark) rstheme$background else rstheme$foreground 380 | theme$highlight_background <- alpha_rgb(rstheme$foreground, 0.05) 381 | theme$title_bar_background <- alpha_rgb(rstheme$foreground, 0.10) 382 | 383 | theme 384 | } 385 | 386 | alpha_rgb <- function(x, alpha = 0.5) { 387 | if (grepl("^rgb", x)) { 388 | x <- sub( 389 | "rgb\\((\\d+, ?\\d+, ?\\d+)\\)", 390 | sprintf("rgba(\\1, %s)", alpha), 391 | x 392 | ) 393 | } 394 | x 395 | } 396 | -------------------------------------------------------------------------------- /R/utils.R: -------------------------------------------------------------------------------- 1 | requires_pkg <- function(pkg) { 2 | if (!requireNamespace(pkg, quietly = TRUE)) { 3 | stop(pkg, " is required: install.packages('", pkg, "')") 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Get It Somewhere The F*** Online 2 | Turn your RStudio untitled tabs into gists. You monsters. 3 | 4 | ![wat](https://raw.githubusercontent.com/MilesMcBain/gistfo/master/inst/media/gistfo.gif) 5 | 6 | ### Carbon Edition 7 | 8 | Carbon mode opens the code on https://carbon.now.sh and copies gist url to your clipboard so you can easily paste into a carbon tweet. 9 | 10 | ![very niiice](https://raw.githubusercontent.com/MilesMcBain/gistfo/master/inst/media/carbon.png) 11 | 12 | ### Gistfo The App 13 | 14 | Also includes **Gistfo App**, an RStudio addin for opening, editing, and updating gists in RStudio. 15 | 16 | ![gistfo_app() preview image](https://raw.githubusercontent.com/MilesMcBain/gistfo/master/inst/media/gistfo-app.png) 17 | 18 | ## Installation 19 | 20 | ```r 21 | # install.packages("remotes") 22 | remotes::install_github("MilesMcBain/gistfo") 23 | 24 | # To install app dependencies as well 25 | remotes::install_github("MilesMcBain/gistfo", dependencies = TRUE) 26 | ``` 27 | 28 | ## Usage 29 | 30 | ### As an addin 31 | 32 | Select the text or source file tab you would like to turn into a Gist. 33 | Then, from the RStudio Addins menu select either _Make Private Gist from Text or Tab_ or _Make Public Gist and Send to Carbon_. 34 | 35 | ### From the console 36 | 37 | ```r 38 | library(gistfo) 39 | ``` 40 | 41 | Make sure an RStudio tab is active or select text in a source file, then run `gistfo()`. It will open a browser window asking you to authenticate a third-party OAuth application. Should be all good from there. 42 | 43 | Carbon mode: `gistfoc()` 44 | -------------------------------------------------------------------------------- /gistfo.Rproj: -------------------------------------------------------------------------------- 1 | Version: 1.0 2 | 3 | RestoreWorkspace: No 4 | SaveWorkspace: No 5 | AlwaysSaveHistory: Default 6 | 7 | EnableCodeIndexing: Yes 8 | UseSpacesForTab: Yes 9 | NumSpacesForTab: 2 10 | Encoding: UTF-8 11 | 12 | RnwWeave: Sweave 13 | LaTeX: pdfLaTeX 14 | 15 | AutoAppendNewline: Yes 16 | StripTrailingWhitespace: Yes 17 | 18 | BuildType: Package 19 | PackageUseDevtools: Yes 20 | PackageInstallArgs: --no-multiarch --with-keep.source 21 | PackageRoxygenize: rd,collate,namespace 22 | -------------------------------------------------------------------------------- /inst/media/carbon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MilesMcBain/gistfo/6fc8111c4b064067f199cbb600edd6f0518e72b3/inst/media/carbon.png -------------------------------------------------------------------------------- /inst/media/gistfo-app.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MilesMcBain/gistfo/6fc8111c4b064067f199cbb600edd6f0518e72b3/inst/media/gistfo-app.png -------------------------------------------------------------------------------- /inst/media/gistfo.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MilesMcBain/gistfo/6fc8111c4b064067f199cbb600edd6f0518e72b3/inst/media/gistfo.gif -------------------------------------------------------------------------------- /inst/rstudio/addins.dcf: -------------------------------------------------------------------------------- 1 | Name: Make Private Gist from Text or Tab 2 | Description: Sends the active selection or RStudio source editor to Github as a private gist. 3 | Binding: gistfo 4 | Interactive: true 5 | 6 | Name: Make Public Gist and Send to Carbon 7 | Description: Sends the active selection or RStudio source editor to Github as a public gist. Opens in carbon.sh. 8 | Binding: gistfoc 9 | Interactive: true 10 | 11 | Name: Gistfo App 12 | Description: Open, Edit, Save, and Update Gists in RStudio 13 | Binding: gistfo_app 14 | Interactive: true 15 | -------------------------------------------------------------------------------- /man/gistfo.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/gistfo.R 3 | \name{gistfo} 4 | \alias{gistfo} 5 | \alias{gistfoc} 6 | \title{Share your code as a gist or on carbon.now.sh} 7 | \usage{ 8 | gistfo() 9 | 10 | gistfoc() 11 | } 12 | \value{ 13 | The URL of the gist on GitHub. Also opens browser windows to the 14 | GitHub gist. Another browser window will open if you choose to open your 15 | gist in Carbon and the URL of the gist will be copied to the clipboard. 16 | } 17 | \description{ 18 | Creates a private or public gist containing the active text selection or the 19 | active RStudio source file. Creating a public gist also prompts you to ask if 20 | you want to share the code to \href{https://carbon.now.sh}{carbon}, where it is 21 | converted into a beautiful screen shot. 22 | } 23 | \details{ 24 | The default file name of the \href{https://gist.github.com}{GitHub gist} is: 25 | \verb{__}, where \code{file_id} is a unique 26 | id for untitled files. It does not relate to the untitled number. You'll be 27 | asked to confirm or edit the file name before uploading. 28 | } 29 | \section{Functions}{ 30 | \itemize{ 31 | \item \code{gistfo}: Create a private gist and browse to it 32 | 33 | \item \code{gistfoc}: Create a public gist and optionally share with 34 | \href{https://carbon.now.sh}{carbon} 35 | }} 36 | 37 | --------------------------------------------------------------------------------