├── .github ├── .gitignore └── workflows │ └── pkgdown.yaml ├── LICENSE ├── man ├── figures │ ├── logo.png │ └── gistillery-logo.png ├── reexports.Rd ├── carbon_fonts.Rd ├── carbon_themes.Rd ├── gist_comment.Rd ├── gist_append_img.Rd ├── reprex_shot.Rd ├── gist_upload.Rd ├── git_auth.Rd └── gist_to_carbon.Rd ├── data ├── carbon_fonts.rda └── carbon_themes.rda ├── pkgdown └── favicon │ ├── favicon.ico │ ├── favicon-16x16.png │ ├── favicon-32x32.png │ ├── apple-touch-icon.png │ ├── apple-touch-icon-60x60.png │ ├── apple-touch-icon-76x76.png │ ├── apple-touch-icon-120x120.png │ ├── apple-touch-icon-152x152.png │ └── apple-touch-icon-180x180.png ├── .gitignore ├── R ├── reexports.R ├── gist_comment.R ├── reprex_shot.R ├── git_auth.R ├── gist_upload.R └── gist_to_carbon.R ├── .Rbuildignore ├── NEWS.md ├── gistillery.Rproj ├── NAMESPACE ├── _pkgdown.yml ├── DESCRIPTION ├── LICENSE.md ├── data-raw └── carbon_constants.R ├── _build_logo.R └── README.md /.github/.gitignore: -------------------------------------------------------------------------------- 1 | *.html 2 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | YEAR: 2022 2 | COPYRIGHT HOLDER: gistillery authors 3 | -------------------------------------------------------------------------------- /man/figures/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jthomasmock/gistillery/HEAD/man/figures/logo.png -------------------------------------------------------------------------------- /data/carbon_fonts.rda: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jthomasmock/gistillery/HEAD/data/carbon_fonts.rda -------------------------------------------------------------------------------- /data/carbon_themes.rda: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jthomasmock/gistillery/HEAD/data/carbon_themes.rda -------------------------------------------------------------------------------- /pkgdown/favicon/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jthomasmock/gistillery/HEAD/pkgdown/favicon/favicon.ico -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .Rproj.user 2 | .Rhistory 3 | .RData 4 | .Ruserdata 5 | .Rdata 6 | .httr-oauth 7 | .DS_Store 8 | docs 9 | -------------------------------------------------------------------------------- /man/figures/gistillery-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jthomasmock/gistillery/HEAD/man/figures/gistillery-logo.png -------------------------------------------------------------------------------- /pkgdown/favicon/favicon-16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jthomasmock/gistillery/HEAD/pkgdown/favicon/favicon-16x16.png -------------------------------------------------------------------------------- /pkgdown/favicon/favicon-32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jthomasmock/gistillery/HEAD/pkgdown/favicon/favicon-32x32.png -------------------------------------------------------------------------------- /pkgdown/favicon/apple-touch-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jthomasmock/gistillery/HEAD/pkgdown/favicon/apple-touch-icon.png -------------------------------------------------------------------------------- /pkgdown/favicon/apple-touch-icon-60x60.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jthomasmock/gistillery/HEAD/pkgdown/favicon/apple-touch-icon-60x60.png -------------------------------------------------------------------------------- /pkgdown/favicon/apple-touch-icon-76x76.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jthomasmock/gistillery/HEAD/pkgdown/favicon/apple-touch-icon-76x76.png -------------------------------------------------------------------------------- /pkgdown/favicon/apple-touch-icon-120x120.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jthomasmock/gistillery/HEAD/pkgdown/favicon/apple-touch-icon-120x120.png -------------------------------------------------------------------------------- /pkgdown/favicon/apple-touch-icon-152x152.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jthomasmock/gistillery/HEAD/pkgdown/favicon/apple-touch-icon-152x152.png -------------------------------------------------------------------------------- /pkgdown/favicon/apple-touch-icon-180x180.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jthomasmock/gistillery/HEAD/pkgdown/favicon/apple-touch-icon-180x180.png -------------------------------------------------------------------------------- /R/reexports.R: -------------------------------------------------------------------------------- 1 | 2 | 3 | # httr2 ----------------------------------------------------------------------- 4 | #' @export 5 | #' @importFrom httr2 %>% 6 | httr2::`%>%` 7 | -------------------------------------------------------------------------------- /.Rbuildignore: -------------------------------------------------------------------------------- 1 | ^.*\.Rproj$ 2 | ^\.Rproj\.user$ 3 | ^LICENSE\.md$ 4 | ^_pkgdown\.yml$ 5 | ^docs$ 6 | ^pkgdown$ 7 | ^\.github$ 8 | ^_build_logo.R 9 | ^data-raw$ 10 | ^\.httr-oauth$ 11 | -------------------------------------------------------------------------------- /NEWS.md: -------------------------------------------------------------------------------- 1 | # gistillery 0.2.5 2 | 3 | * Added a `NEWS.md` file to track changes to the package. 4 | * Removed `gistr` as a dependency, refactored all `gistr` code into custom `httr2` 5 | * Added support for `gitcreds::gitcreds_get()` token per [Issue #3](https://github.com/jthomasmock/gistillery/issues/3) 6 | -------------------------------------------------------------------------------- /man/reexports.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/reexports.R 3 | \docType{import} 4 | \name{reexports} 5 | \alias{reexports} 6 | \alias{\%>\%} 7 | \title{Objects exported from other packages} 8 | \keyword{internal} 9 | \description{ 10 | These objects are imported from other packages. Follow the links 11 | below to see their documentation. 12 | 13 | \describe{ 14 | \item{httr2}{\code{\link[httr2:pipe]{\%>\%}}} 15 | }} 16 | 17 | -------------------------------------------------------------------------------- /gistillery.Rproj: -------------------------------------------------------------------------------- 1 | Version: 1.0 2 | 3 | RestoreWorkspace: Default 4 | SaveWorkspace: Default 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 | PackageCleanBeforeInstall: Yes 21 | PackageInstallArgs: --no-multiarch --with-keep.source 22 | -------------------------------------------------------------------------------- /NAMESPACE: -------------------------------------------------------------------------------- 1 | # Generated by roxygen2: do not edit by hand 2 | 3 | export("%>%") 4 | export(gist_append_img) 5 | export(gist_comment) 6 | export(gist_to_carbon) 7 | export(gist_upload) 8 | export(git_auth) 9 | export(reprex_shot) 10 | import(chromote) 11 | import(cli) 12 | import(glue) 13 | import(httr) 14 | import(httr2) 15 | importFrom(gitcreds,gitcreds_get) 16 | importFrom(glue,glue) 17 | importFrom(httr2,"%>%") 18 | importFrom(reprex,reprex) 19 | importFrom(utils,packageVersion) 20 | importFrom(webshot2,webshot) 21 | -------------------------------------------------------------------------------- /man/carbon_fonts.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/gist_to_carbon.R 3 | \docType{data} 4 | \name{carbon_fonts} 5 | \alias{carbon_fonts} 6 | \title{Carbon fonts} 7 | \format{ 8 | An object of class \code{character} of length 13. 9 | } 10 | \usage{ 11 | carbon_fonts 12 | } 13 | \description{ 14 | A list of available fonts that carbon supports. 15 | } 16 | \details{ 17 | Sourced from: 18 | } 19 | \examples{ 20 | { 21 | carbon_fonts 22 | } 23 | } 24 | \keyword{datasets} 25 | -------------------------------------------------------------------------------- /man/carbon_themes.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/gist_to_carbon.R 3 | \docType{data} 4 | \name{carbon_themes} 5 | \alias{carbon_themes} 6 | \title{Carbon themes} 7 | \format{ 8 | An object of class \code{character} of length 29. 9 | } 10 | \usage{ 11 | carbon_themes 12 | } 13 | \description{ 14 | A list of available themes supported by Carbon. 15 | } 16 | \details{ 17 | Sourced from 18 | } 19 | \examples{ 20 | { 21 | carbon_themes 22 | } 23 | } 24 | \keyword{datasets} 25 | -------------------------------------------------------------------------------- /man/gist_comment.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/gist_comment.R 3 | \name{gist_comment} 4 | \alias{gist_comment} 5 | \title{Add a comment to an existing gist} 6 | \usage{ 7 | gist_comment( 8 | gist_id = "73b34a7d484360036fee122c5a745f44", 9 | comment = "![](https://pbs.twimg.com/media/FBGfjADUYAUxiPz?format=png)" 10 | ) 11 | } 12 | \arguments{ 13 | \item{gist_id}{Character string indicating the gist ID} 14 | 15 | \item{comment}{The comment text} 16 | } 17 | \value{ 18 | A string of the comment text 19 | } 20 | \description{ 21 | Add a comment to an existing gist 22 | } 23 | -------------------------------------------------------------------------------- /man/gist_append_img.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/gist_to_carbon.R 3 | \name{gist_append_img} 4 | \alias{gist_append_img} 5 | \title{Add imgur-hosted URL to existing gist as a "comment" to bottom of script.} 6 | \usage{ 7 | gist_append_img(imgur_url, gist_id = NULL) 8 | } 9 | \arguments{ 10 | \item{imgur_url}{Existing URL from imgur, typically as created with gistillery::gist_to_carbon()} 11 | 12 | \item{gist_id}{Unique ID for an existing Github Gist - this is where the comment will be added.} 13 | } 14 | \value{ 15 | Adds a commented line to bottom of existing Gist code 16 | } 17 | \description{ 18 | Add imgur-hosted URL to existing gist as a "comment" to bottom of script. 19 | } 20 | -------------------------------------------------------------------------------- /_pkgdown.yml: -------------------------------------------------------------------------------- 1 | url: https://jthomasmock.github.io/gistillery 2 | home: 3 | title: Upload code to Github Gists and to Carbon 4 | description: Easily create gists and save beautiful code screenshots with Carbon 5 | template: 6 | bootstrap: 5 7 | bootswatch: litera 8 | opengraph: 9 | image: 10 | src: https://raw.githubusercontent.com/jthomasmock/gistillery/main/man/figures/gistillery-logo.png 11 | alt: The package hex logo, it is pixel art of a swan neck copper distillation pot with a octopus off to one side. 12 | twitter: 13 | creator: '@thomas_mock' 14 | image: https://raw.githubusercontent.com/jthomasmock/gistillery/main/man/figures/gistillery-logo.png 15 | site: https://jthomasmock.github.io/distillery 16 | card: summary 17 | -------------------------------------------------------------------------------- /man/reprex_shot.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/reprex_shot.R 3 | \name{reprex_shot} 4 | \alias{reprex_shot} 5 | \title{Execute a reprex::reprex() and take a local screenshot of the reprex output} 6 | \usage{ 7 | reprex_shot(filename = NULL, ..., open_file = TRUE, imgur = FALSE, venue = "r") 8 | } 9 | \arguments{ 10 | \item{filename}{a filename, ending in .png} 11 | 12 | \item{...}{additional arguments passed to reprex::reprex()} 13 | 14 | \item{open_file}{A logical, should the file be opened once saved} 15 | 16 | \item{imgur}{A logical, should the image be uploaded to imgur also} 17 | 18 | \item{venue}{A character string indicating the venue (r, gh, rtf) - see reprex::reprex() for full options} 19 | } 20 | \value{ 21 | a screenshot of the reprex on disk 22 | } 23 | \description{ 24 | reprex_shot() will first take a reprex and then capture the 25 | HTML output into an on-disk image, optionally uploading the image to Imgur. 26 | } 27 | -------------------------------------------------------------------------------- /man/gist_upload.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/gist_upload.R 3 | \name{gist_upload} 4 | \alias{gist_upload} 5 | \title{Take local code and upload to a named gist} 6 | \usage{ 7 | gist_upload( 8 | content = NULL, 9 | gist_name = NULL, 10 | description = "", 11 | public = TRUE, 12 | browse = TRUE 13 | ) 14 | } 15 | \arguments{ 16 | \item{content}{the code, either the currently highlighted file or manually indicated code} 17 | 18 | \item{gist_name}{a valid filename ie my-code.R} 19 | 20 | \item{description}{a brief description of the gist or it's purpose} 21 | 22 | \item{public}{a logical, defaults to TRUE, indicates whether to make gist public or not} 23 | 24 | \item{browse}{a logical, defaults to TRUE, indicates whether to open the new gist in browser or not} 25 | } 26 | \value{ 27 | gist id and the gist URL to clipboard, can be piped directly into gist_to_carbon 28 | } 29 | \description{ 30 | Take local code and upload to a named gist 31 | } 32 | -------------------------------------------------------------------------------- /DESCRIPTION: -------------------------------------------------------------------------------- 1 | Package: gistillery 2 | Type: Package 3 | Title: Upload Code to Github Gists and Take Code Screenshots via Carbon 4 | Version: 0.2.6 5 | Author: Thomas Mock 6 | Maintainer: Thomas Mock 7 | Description: Easily upload code to Github Gists from local files, the clipboard, or even a reprex. 8 | Quickly take customized and beautiful screenshots of your code from Carbon. 9 | License: MIT + file LICENSE 10 | Encoding: UTF-8 11 | LazyData: true 12 | Imports: 13 | chromote (>= 0.1), 14 | cli (>= 3.3.0), 15 | gitcreds (>= 0.1.1), 16 | glue (>= 1.6.2), 17 | httr (>= 1.4.2), 18 | httr2 (>= 0.2.1), 19 | reprex (>= 2.0.1), 20 | rstudioapi (>= 0.13), 21 | webshot2 (>= 0.1), 22 | xml2 (>= 1.3.0) 23 | Suggests: 24 | clipr 25 | URL: https://github.com/jthomasmock/gistillery, 26 | https://jthomasmock.github.io/gistillery/ 27 | BugReports: https://github.com/jthomasmock/gistillery/issues 28 | Remotes: MilesMcBain/gistfo 29 | RoxygenNote: 7.1.2 30 | Depends: 31 | R (>= 2.10) 32 | -------------------------------------------------------------------------------- /R/gist_comment.R: -------------------------------------------------------------------------------- 1 | #' Add a comment to an existing gist 2 | #' 3 | #' @param gist_id Character string indicating the gist ID 4 | #' @param comment The comment text 5 | #' 6 | #' @return A string of the comment text 7 | #' @export 8 | #' @import httr2 9 | #' @importFrom httr2 %>% 10 | #' 11 | gist_comment <- function(gist_id="73b34a7d484360036fee122c5a745f44", comment="![](https://pbs.twimg.com/media/FBGfjADUYAUxiPz?format=png)"){ 12 | 13 | # consider moving to token-based auth via httr2 14 | # token <- ifelse(!is.null(token), token, Sys.getenv("GITHUB_PAT")) 15 | 16 | req_built <- "https://api.github.com" %>% 17 | request() %>% 18 | req_url_path_append("gists") %>% 19 | req_url_path_append(gist_id) %>% 20 | req_url_path_append("comments") %>% 21 | req_headers( 22 | Authorization = git_auth(), 23 | "User-Agent" = "gistr", 24 | Accept = "application/vnd.github.v3+json" 25 | ) %>% 26 | httr2::req_body_json( 27 | list(body = comment) 28 | ) %>% 29 | req_error(body = gist_error_body) 30 | 31 | httr2::req_perform(req_built) 32 | 33 | } 34 | 35 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | # MIT License 2 | 3 | Copyright (c) 2022 gistillery authors 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /data-raw/carbon_constants.R: -------------------------------------------------------------------------------- 1 | #' Carbon fonts and themes 2 | #' via: https://github.com/carbon-app/carbon/blob/main/lib/constants.js 3 | 4 | library(tibble) 5 | library(dplyr) 6 | library(tidyr) 7 | library(stringr) 8 | 9 | raw_constants <- tibble::tibble( 10 | x = readLines("https://github.com/carbon-app/carbon/raw/main/lib/constants.js") 11 | ) |> 12 | mutate(constant_name = str_match(x,"export const ([A-Z]+)")[,2]) |> 13 | fill(constant_name) 14 | 15 | carbon_fonts <- raw_constants |> 16 | filter(constant_name == "FONTS") |> 17 | mutate(font_id = str_match(x,"id\\: ['|\"](.+)['|\"],")[,2]) |> 18 | filter(!is.na(font_id)) |> 19 | pull(font_id) |> 20 | str_replace_all(" ","+") 21 | 22 | carbon_themes <- raw_constants |> 23 | filter(constant_name == "THEMES") |> 24 | mutate(theme_id = str_match(x,"id\\: ['|\"](.+)['|\"],")[,2]) |> 25 | filter(!is.na(theme_id)) |> 26 | pull(theme_id) 27 | 28 | carbon_langs <- raw_constants |> 29 | filter(constant_name == "LANGUAGES") |> 30 | mutate(language_id = str_match(x,"mode\\: ['|\"](.+)['|\"],")[,2]) |> 31 | filter(!is.na(language_id)) |> 32 | pull(language_id) |> 33 | unique() |> 34 | sort() 35 | 36 | usethis::use_data(carbon_fonts, carbon_themes) 37 | -------------------------------------------------------------------------------- /man/git_auth.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/git_auth.R 3 | \name{git_auth} 4 | \alias{git_auth} 5 | \title{Authorize with GitHub.} 6 | \usage{ 7 | git_auth(app = gistr_app, reauth = FALSE) 8 | } 9 | \arguments{ 10 | \item{app}{An [httr::oauth_app()] for GitHub. The default uses an 11 | application `gistr_oauth` created by Scott Chamberlain.} 12 | 13 | \item{reauth}{(logical) Force re-authorization?} 14 | } 15 | \value{ 16 | a character string - used downstream for auth in various functions 17 | } 18 | \description{ 19 | This function is run automatically to allow gistillery to access your GitHub 20 | account. It is adapted from gistr::gist_auth 21 | } 22 | \details{ 23 | There are two ways to authorise gistillery to work with your GitHub account: 24 | 25 | - Generate a personal access token with the gist scope selected, and set it 26 | as the `GITHUB_PAT` environment variable per session using `Sys.setenv` 27 | or across sessions by adding it to your `.Renviron` file or similar. 28 | See 29 | https://help.github.com/articles/creating-an-access-token-for-command-line-use 30 | for help 31 | - Interactively login into your GitHub account and authorise with OAuth. 32 | 33 | Using `GITHUB_PAT` is recommended but it should also work with `gitcreds::gitcreds_get()` 34 | if you're storing your credentials there. 35 | } 36 | -------------------------------------------------------------------------------- /.github/workflows/pkgdown.yaml: -------------------------------------------------------------------------------- 1 | # Workflow derived from https://github.com/r-lib/actions/tree/v2/examples 2 | # Need help debugging build failures? Start at https://github.com/r-lib/actions#where-to-find-help 3 | on: 4 | push: 5 | branches: [main, master] 6 | pull_request: 7 | branches: [main, master] 8 | release: 9 | types: [published] 10 | workflow_dispatch: 11 | 12 | name: pkgdown 13 | 14 | jobs: 15 | pkgdown: 16 | runs-on: ubuntu-latest 17 | # Only restrict concurrency for non-PR jobs 18 | concurrency: 19 | group: pkgdown-${{ github.event_name != 'pull_request' || github.run_id }} 20 | env: 21 | GITHUB_PAT: ${{ secrets.GITHUB_TOKEN }} 22 | steps: 23 | - uses: actions/checkout@v2 24 | 25 | - uses: r-lib/actions/setup-pandoc@v2 26 | 27 | - uses: r-lib/actions/setup-r@v2 28 | with: 29 | use-public-rspm: true 30 | 31 | - uses: r-lib/actions/setup-r-dependencies@v2 32 | with: 33 | extra-packages: any::pkgdown, local::. 34 | needs: website 35 | 36 | - name: Build site 37 | run: pkgdown::build_site_github_pages(new_process = FALSE, install = FALSE) 38 | shell: Rscript {0} 39 | 40 | - name: Deploy to GitHub pages 🚀 41 | if: github.event_name != 'pull_request' 42 | uses: JamesIves/github-pages-deploy-action@4.1.4 43 | with: 44 | clean: false 45 | branch: gh-pages 46 | folder: docs 47 | -------------------------------------------------------------------------------- /_build_logo.R: -------------------------------------------------------------------------------- 1 | library(magick) 2 | library(dplyr) 3 | 4 | url <- "https://m.media-amazon.com/images/I/61qtsKcMz6L._AC_SL1001_.jpg" 5 | 6 | raw_octo <- "https://freesvg.org/img/Kid-Octopi-Redrawn.png" |> image_read() 7 | raw_img <- magick::image_read(url) 8 | 9 | octo_pixel <- raw_octo |> 10 | image_scale("10%") |> 11 | image_scale("1000%") |> 12 | image_scale("45%") 13 | 14 | pot_pixel <- raw_img |> 15 | magick::image_scale("8%") |> 16 | magick::image_scale("1000%") 17 | ?image_flatten(c(pot_pixel, octo_pixel)) 18 | 19 | image_combo <- image_composite(pot_pixel, octo_pixel, offset = "+325+165") |> 20 | image_annotate("gistillery", font = 'Fira Code', 21 | color = "black", 22 | size = 85, location = "+88+550") |> 23 | image_annotate("gistillery", font = 'Fira Code', 24 | color = "black", 25 | size = 85, location = "+92+550")|> 26 | image_annotate("gistillery", font = 'Fira Code', 27 | color = "black", 28 | size = 85, location = "+90+552")|> 29 | image_annotate("gistillery", font = 'Fira Code', 30 | color = "black", 31 | size = 85, location = "+90+548") |> 32 | image_annotate("gistillery", font = 'Fira Code', 33 | color = "black", 34 | size = 85, location = "+86+550") |> 35 | image_annotate("gistillery", font = 'Fira Code', 36 | color = "black", 37 | size = 85, location = "+94+550")|> 38 | image_annotate("gistillery", font = 'Fira Code', 39 | color = "black", 40 | size = 85, location = "+90+554")|> 41 | image_annotate("gistillery", font = 'Fira Code', 42 | color = "black", 43 | size = 85, location = "+90+546") |> 44 | image_annotate("gistillery", font = 'Fira Code', 45 | color = "white", 46 | size = 85, location = "+90+550") 47 | 48 | image_combo |> 49 | image_write("gistillery-logo.png") 50 | -------------------------------------------------------------------------------- /man/gist_to_carbon.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/gist_to_carbon.R 3 | \name{gist_to_carbon} 4 | \alias{gist_to_carbon} 5 | \title{Take an existing gist, send to carbon, and save the image locally} 6 | \usage{ 7 | gist_to_carbon( 8 | gist_id, 9 | file = "code.png", 10 | bg = getOption("gistillery.bg", default = "#4A90E2"), 11 | theme = getOption("gistillery.theme", default = "night-owl"), 12 | font = getOption("gistillery.font", default = "Hack"), 13 | lang = "auto", 14 | imgur = TRUE, 15 | drop_shadow = TRUE, 16 | width = 680, 17 | width_auto_adjust = TRUE 18 | ) 19 | } 20 | \arguments{ 21 | \item{gist_id}{the unique id for your existing gist, can be piped from gist_upload() or passed manually.} 22 | 23 | \item{file}{the name of the file for printing, eg code.png} 24 | 25 | \item{bg}{A valid hex code for color, ie #D3D3D3} 26 | 27 | \item{theme}{A valid theme, such as "cobalt", "nord", "night-owl", "monokai" - for all available themes, see `carbon_themes`} 28 | 29 | \item{font}{A valid font ID such as "IBM+Plex+Mono", "Hack", "Fira+Code" - for all available fonts, see `carbon_fonts`} 30 | 31 | \item{lang}{A language for syntax highlighting, ie one of "python", "r", "yaml", "markdown", "text", "auto"} 32 | 33 | \item{imgur}{A logical, should the image also be uploaded to imgur.} 34 | 35 | \item{drop_shadow}{Logical indicating whether to include drop shadow for the screenshot.} 36 | 37 | \item{width}{a number, indicating the width in pixels for screenshot} 38 | 39 | \item{width_auto_adjust}{Logical indicating whether to auto adjust the width for better code-printing} 40 | } 41 | \value{ 42 | Saves an image to disk and optionally returns the uploaded imgur URL 43 | } 44 | \description{ 45 | Take an existing gist, send to carbon, and save the image locally 46 | } 47 | -------------------------------------------------------------------------------- /R/reprex_shot.R: -------------------------------------------------------------------------------- 1 | #' Execute a reprex::reprex() and take a local screenshot of the reprex output 2 | #' @description reprex_shot() will first take a reprex and then capture the 3 | #' HTML output into an on-disk image, optionally uploading the image to Imgur. 4 | #' @param filename a filename, ending in .png 5 | #' @param open_file A logical, should the file be opened once saved 6 | #' @param imgur A logical, should the image be uploaded to imgur also 7 | #' @param ... additional arguments passed to reprex::reprex() 8 | #' @param venue A character string indicating the venue (r, gh, rtf) - see reprex::reprex() for full options 9 | #' @return a screenshot of the reprex on disk 10 | #' @importFrom reprex reprex 11 | #' @import cli 12 | #' @importFrom webshot2 webshot 13 | #' @export 14 | reprex_shot <- function(filename = NULL, ..., open_file = TRUE, 15 | imgur = FALSE, venue = "r") { 16 | reprex::reprex(..., venue = venue) 17 | 18 | # get tempfiles 19 | temp_fs <- dir(tempdir(), full.names = TRUE) 20 | reprex_fs <- temp_fs[grepl(x = temp_fs, pattern = "reprex_preview.html")] 21 | 22 | # check for missing reprex 23 | if (identical(character(0), reprex_fs)) stop("No reprex found. Run reprex() on some code.") 24 | 25 | # get tempfile times 26 | time_fs <- file.info(reprex_fs)$ctime 27 | 28 | # grab the latest reprex 29 | temp_reprex <- reprex_fs[time_fs == max(time_fs)] 30 | 31 | # check for missing reprex 32 | if (is.na(temp_reprex)) stop("No reprex found. Run reprex() on some code.") 33 | 34 | if (is.null(filename)) { 35 | filename <- basename(temp_reprex) 36 | filename <- paste0(gsub(x = filename, pattern = "_reprex_preview.html", replacement = ""), ".png") 37 | } 38 | webshot2::webshot(temp_reprex, file = filename, zoom = 3) 39 | cli::cli_alert_success("Screenshot saved as {.path {filename}}.") 40 | 41 | # print number of lines, a basic output so that you can 42 | # pipe reprex::reprex() directly into reprex_shot() 43 | cli::cli_alert_info("Used most recent reprex {.field {max(time_fs)}} at {.path {temp_reprex}}") 44 | 45 | # optionally auto-open new file 46 | if (open_file) rstudioapi::viewer(filename) 47 | 48 | # optionally upload to imgur 49 | if (imgur) { 50 | imgur_out <- imgur_upload(filename) 51 | 52 | cli::cli_alert_success("Screenshot uploaded to {.url {as.character(imgur_out)}}") 53 | } 54 | } 55 | 56 | #' @importFrom utils packageVersion 57 | # vendored from knitr 58 | # https://github.com/yihui/knitr/blob/3237add034368a3018ff26fa9f4d0ca89a4afd78/R/utils-upload.R#L37-L51 59 | imgur_upload <- function(file) { 60 | key <- "9f3460e67f308f6" 61 | if (!is.character(key)) { 62 | stop("The Imgur API Key must be a character string!") 63 | } 64 | resp <- httr::POST("https://api.imgur.com/3/image.xml", config = httr::add_headers(Authorization = paste( 65 | "Client-ID", 66 | key 67 | )), body = list(image = httr::upload_file(file))) 68 | httr::stop_for_status(resp, "upload to imgur") 69 | res <- httr::content(resp, as = "raw") 70 | res <- if (length(res)) { 71 | xml2::as_list(xml2::read_xml(res)) 72 | } 73 | if (utils::packageVersion("xml2") >= "1.2.0") { 74 | res <- res[[1L]] 75 | } 76 | if (is.null(res$link[[1]])) { 77 | stop("failed to upload ", file) 78 | } 79 | structure(res$link[[1]], XML = res) 80 | } 81 | -------------------------------------------------------------------------------- /R/git_auth.R: -------------------------------------------------------------------------------- 1 | #' Authorize with GitHub. 2 | #' 3 | #' This function is run automatically to allow gistillery to access your GitHub 4 | #' account. It is adapted from gistr::gist_auth 5 | #' 6 | #' There are two ways to authorise gistillery to work with your GitHub account: 7 | #' 8 | #' - Generate a personal access token with the gist scope selected, and set it 9 | #' as the `GITHUB_PAT` environment variable per session using `Sys.setenv` 10 | #' or across sessions by adding it to your `.Renviron` file or similar. 11 | #' See 12 | #' https://help.github.com/articles/creating-an-access-token-for-command-line-use 13 | #' for help 14 | #' - Interactively login into your GitHub account and authorise with OAuth. 15 | #' 16 | #' Using `GITHUB_PAT` is recommended but it should also work with `gitcreds::gitcreds_get()` 17 | #' if you're storing your credentials there. 18 | #' 19 | #' @export 20 | #' @param app An [httr::oauth_app()] for GitHub. The default uses an 21 | #' application `gistr_oauth` created by Scott Chamberlain. 22 | #' @param reauth (logical) Force re-authorization? 23 | #' @importFrom gitcreds gitcreds_get 24 | #' @import httr 25 | #' @return a character string - used downstream for auth in various functions 26 | 27 | git_auth <- function(app = gistr_app, reauth = FALSE) { 28 | 29 | # code adapted from gistr 30 | # https://github.com/ropensci/gistr/blob/master/R/gist_auth.R 31 | if (exists("auth_config", envir = cache) && !reauth) { 32 | return(auth_header(cache$auth_config$auth_token$credentials$access_token)) 33 | } 34 | 35 | pat <- Sys.getenv("GITHUB_PAT", "") 36 | 37 | if (!identical(pat, "")) { 38 | auth_config <- list(auth_token = list(credentials = list(access_token = pat))) 39 | } else if (!interactive()) { 40 | stop("In non-interactive environments, please set GITHUB_PAT env to a GitHub", 41 | " access token (https://help.github.com/articles/creating-an-access-token-for-command-line-use)", 42 | call. = FALSE 43 | ) 44 | } else { 45 | endpt <- httr::oauth_endpoints("github") 46 | 47 | # try gitcreds 48 | token <- gitcreds::gitcreds_get() 49 | if (nzchar(token$password)) { 50 | token <- paste0("token ", token$password) 51 | return(token) 52 | auth_config <- httr::config(token = token) 53 | } else { 54 | # try oauth direct 55 | token <- httr::oauth2.0_token(endpt, app, scope = "gist", cache = !reauth) 56 | auth_config <- httr::config(token = token) 57 | } 58 | } 59 | 60 | cache$auth_config <- auth_config 61 | 62 | if (grepl("token", token, ignore.case = TRUE)) { 63 | return(token) 64 | } else { 65 | auth_header(auth_config$auth_token$credentials$access_token) 66 | } 67 | 68 | if (nchar(token) <= 7) { 69 | cli::cli_alert_danger("Github Auth Token appears to be missing.") 70 | cli::cli_alert_warning("Please set {.field GITHUB_PAT} in your {.file .Renviron} or use the {.pkg gitcreds} package.") 71 | } 72 | } 73 | 74 | auth_header <- function(x) paste0("token ", x) 75 | 76 | cache <- new.env(parent = emptyenv()) 77 | 78 | gistr_app <- httr::oauth_app( 79 | "gistr_oauth", 80 | "89ecf04527f70e0f9730", 81 | "77b5970cdeda925513b2cdec40c309ea384b74b7" 82 | ) 83 | 84 | msg <- function(wh) { 85 | msgs <- c( 86 | no_git = paste0( 87 | "No git installation found. You need to install git and set up ", 88 | "your GitHub Personal Access token using `gitcreds::gitcreds_set()`." 89 | ), 90 | no_creds = paste0( 91 | "No git credentials found. Please set up your GitHub Personal Access ", 92 | "token using `gitcreds::gitcreds_set()`." 93 | ) 94 | ) 95 | msgs[wh] 96 | } 97 | -------------------------------------------------------------------------------- /R/gist_upload.R: -------------------------------------------------------------------------------- 1 | #' Take local code and upload to a named gist 2 | #' 3 | #' @param content the code, either the currently highlighted file or manually indicated code 4 | #' @param gist_name a valid filename ie my-code.R 5 | #' @param description a brief description of the gist or it's purpose 6 | #' @param public a logical, defaults to TRUE, indicates whether to make gist public or not 7 | #' @param browse a logical, defaults to TRUE, indicates whether to open the new gist in browser or not 8 | #' @import httr2 9 | #' @importFrom glue glue 10 | #' @importFrom httr2 %>% 11 | #' @return gist id and the gist URL to clipboard, can be piped directly into gist_to_carbon 12 | #' @export 13 | 14 | gist_upload <- function(content = NULL, gist_name = NULL, description = "", 15 | public = TRUE, browse = TRUE) { 16 | 17 | # Some code adapted from: https://github.com/MilesMcBain/gistfo 18 | stopifnot("Please give a filename with a valid file extension, like 'code.R' or `slither.py`" = !is.null(gist_name)) 19 | stopifnot("Please give a filename with a valid file extension, like 'code.R' or `slither.py`" = nzchar(tools::file_ext(gist_name))) 20 | 21 | if (is.null(content)) { 22 | message("Using currently opened file") 23 | 24 | source_context <- rstudioapi::getSourceEditorContext() 25 | 26 | gist_content <- source_context$selection[[1]]$text 27 | 28 | if (gist_content == "") { 29 | gist_content <- paste0(source_context$contents, collapse = "\n") 30 | } 31 | } else { 32 | gist_content <- paste0(content, collapse = "\n") 33 | } 34 | 35 | # core request 36 | req_gist <- "https://api.github.com" %>% 37 | httr2::request() %>% 38 | httr2::req_url_path_append("gists") %>% 39 | httr2::req_headers( 40 | Authorization = git_auth(), 41 | "User-Agent" = "gistr", 42 | Accept = "application/vnd.github.v3+json" 43 | ) 44 | 45 | # build the file for upload 46 | built_body <- list( 47 | description = description, 48 | files = list(gist_name = list(content = gist_content)), 49 | public = public 50 | ) 51 | 52 | # fill with gist_name 53 | names(built_body$files) <- gist_name 54 | 55 | req_add_body <- req_gist %>% 56 | httr2::req_body_json(built_body) %>% 57 | httr2::req_error(body = gist_error_body) 58 | 59 | req_out <- httr2::req_perform(req_add_body) 60 | 61 | # return id and URL for returning/other use 62 | id <- httr2::resp_body_json(req_out)$id 63 | gist_url <- httr2::resp_body_json(req_out)$html_url 64 | 65 | # Add URL to gist as comment at bottom of gist 66 | url_comment <- glue::glue("\n\n# Gist URL {gist_url}\n", .trim = FALSE) 67 | add_url_content <- paste0(gist_content, url_comment, collapse = "") 68 | 69 | # rename the element with gist_name 70 | 71 | comment_body <- list( 72 | files = list(gist_name = list(content = add_url_content)) 73 | ) 74 | names(comment_body$files) <- gist_name 75 | 76 | # build the request 77 | req_update <- req_gist %>% 78 | httr2::req_url_path_append(id) %>% 79 | httr2::req_body_json(comment_body) %>% 80 | httr2::req_method("PATCH") 81 | 82 | # send the add url request 83 | gist_with_url <- httr2::req_perform(req_update) 84 | 85 | # open file in browser 86 | if(browse) utils::browseURL(gist_url) 87 | 88 | # try to put it out to clipboard 89 | maybe_clip(gist_url) 90 | # return id for piping to gist_to_carbon 91 | return(id) 92 | 93 | # print the URL as well 94 | print(gist_url) 95 | } 96 | 97 | # vendored from: https://github.com/MilesMcBain/gistfo/blob/master/R/gistfo.R#L155-L159 98 | maybe_clip <- function(text) { 99 | has_clipr <- requireNamespace("clipr", quietly = TRUE) 100 | if (has_clipr && clipr::clipr_available()) { 101 | clipr::write_clip(text) 102 | } 103 | text 104 | } 105 | 106 | gist_error_body <- function(resp) { 107 | body <- httr2::resp_body_json(resp) 108 | 109 | message <- body$message 110 | if (!is.null(body$documentation_url)) { 111 | message <- c(message, paste0("See docs at <", body$documentation_url, ">")) 112 | } 113 | message 114 | } 115 | -------------------------------------------------------------------------------- /R/gist_to_carbon.R: -------------------------------------------------------------------------------- 1 | #' Take an existing gist, send to carbon, and save the image locally 2 | #' 3 | #' @param gist_id the unique id for your existing gist, can be piped from gist_upload() or passed manually. 4 | #' @param file the name of the file for printing, eg code.png 5 | #' @param bg A valid hex code for color, ie #D3D3D3 6 | #' @param theme A valid theme, such as "cobalt", "nord", "night-owl", "monokai" - for all available themes, see `carbon_themes` 7 | #' @param font A valid font ID such as "IBM+Plex+Mono", "Hack", "Fira+Code" - for all available fonts, see `carbon_fonts` 8 | #' @param lang A language for syntax highlighting, ie one of "python", "r", "yaml", "markdown", "text", "auto" 9 | #' @param imgur A logical, should the image also be uploaded to imgur. 10 | #' @param width a number, indicating the width in pixels for screenshot 11 | #' @param drop_shadow Logical indicating whether to include drop shadow for the screenshot. 12 | #' @param width_auto_adjust Logical indicating whether to auto adjust the width for better code-printing 13 | #' @import glue chromote 14 | #' @return Saves an image to disk and optionally returns the uploaded imgur URL 15 | #' @export 16 | 17 | gist_to_carbon <- function( 18 | gist_id, file = "code.png", 19 | bg = getOption("gistillery.bg", default = "#4A90E2"), 20 | theme = getOption("gistillery.theme", default = "night-owl"), 21 | font = getOption("gistillery.font", default = "Hack"), 22 | lang = "auto", 23 | imgur = TRUE, 24 | drop_shadow = TRUE, 25 | width = 680, 26 | width_auto_adjust = TRUE) { 27 | 28 | # currently available fonts/themes/langs 29 | fonts <- gistillery::carbon_fonts 30 | themes <- gistillery::carbon_themes 31 | langs <- c("python", "r", "yaml", "markdown", "text", "auto", "sql", "dockerfile", "javascript", "julia", "shell", "css", "htmlmixed") 32 | 33 | if (!(nchar(bg) == 7 && grepl("#", bg))) stop("The background must be a 6 unit hex value preceded by #, like #4A90E2", call. = FALSE) 34 | if (!(lang %in% langs)) stop(paste("Language must be one of", langs), call. = FALSE) 35 | if (!(theme %in% themes)) stop(paste("Theme must be one of the ones found in `carbon_themes`."), call. = FALSE) 36 | if (!(font %in% fonts)) stop(paste("Font must be one of the ones found in `carbon_fonts`."), call. = FALSE) 37 | 38 | bcol <- grDevices::col2rgb(bg) 39 | # convert to their RGBA format, dropping the various components 40 | # into their correct boxes - also note that alpha is on a 0 to 1 scale 41 | bg_txt <- glue::glue("rgba%28{bcol[1]}%2C{bcol[2]}%2C{bcol[3]}%2C{1}%29") 42 | 43 | drop_shadow <- if (isTRUE(drop_shadow)) { 44 | "&ds=true&dsyoff=20px&dsblur=68px" 45 | } else if (identical(drop_shadow, FALSE)) { 46 | "&ds=false" 47 | } else if (length(drop_shadow) > 0) { 48 | ds_values <- c(20, 68) 49 | for (i in seq_along(drop_shadow)) ds_values[i] <- drop_shadow[i] 50 | glue::glue("&ds=true&dsyoff={ds_values[1]}px&dsblur={ds_values[2]}px") 51 | } else { 52 | "&ds=false" 53 | } 54 | 55 | width_auto_adjust <- if (isTRUE(width_auto_adjust)) "true" else "false" 56 | 57 | carbon_query <- glue::glue("bg={bg_txt}&t={theme}&fm={font}&lang={lang}{drop_shadow}&width={width}&wa={width_auto_adjust}") 58 | carbon_url <- glue::glue("https://carbon.now.sh/embed/{gist_id}?{carbon_query}") 59 | cli::cli_alert_success("Carbon.now.sh used {.url {carbon_url}}") 60 | 61 | b <- chromote::ChromoteSession$new( 62 | # set the screen size to avoid clipped code 63 | width = width * 1.5, 64 | height = width * 10 65 | ) 66 | on.exit(b$close()) 67 | 68 | # Navigate to carbon url 69 | b$Page$navigate(carbon_url) 70 | b$Page$loadEventFired() 71 | # Enable background transparency in the screenshot 72 | b$Emulation$setDefaultBackgroundColorOverride(color = list(r = 0, g = 0, b = 0, a = 0)) 73 | # Hide the copy button 74 | b$Runtime$evaluate("document.querySelector('.copy-button').style.display = 'none'") 75 | # Screenshot time! 76 | b$screenshot(filename = file, selector = "#export-container", scale = 3) 77 | 78 | if (!imgur) { 79 | return(file) 80 | } 81 | 82 | imgur_url <- as.character(imgur_upload(file)) 83 | 84 | gist_append_img(imgur_url = imgur_url, gist_id = gist_id) 85 | cli::cli_alert_info("imgur url added to {.field {gist_id}}") 86 | cli::cli_alert_success("imgur link at {.url {imgur_url}}") 87 | } 88 | 89 | 90 | #' Add imgur-hosted URL to existing gist as a "comment" to bottom of script. 91 | #' 92 | #' @param imgur_url Existing URL from imgur, typically as created with gistillery::gist_to_carbon() 93 | #' @param gist_id Unique ID for an existing Github Gist - this is where the comment will be added. 94 | #' @import glue 95 | #' @return Adds a commented line to bottom of existing Gist code 96 | #' @export 97 | gist_append_img <- function(imgur_url, gist_id = NULL) { 98 | 99 | req_gist <- "https://api.github.com" %>% 100 | httr2::request() %>% 101 | httr2::req_url_path_append("gists") %>% 102 | httr2::req_url_path_append(gist_id) %>% 103 | httr2::req_headers( 104 | Authorization = git_auth(), 105 | "User-Agent" = "gistr", 106 | Accept = "application/vnd.github.v3+json" 107 | ) 108 | 109 | gist_resp <- httr2::req_perform(req_gist) 110 | 111 | gist_content_raw <- resp_body_json(gist_resp) 112 | 113 | gist_name <- names(gist_content_raw[["files"]])[1] 114 | 115 | gist_extract <- gist_content_raw[["files"]][[gist_name]] 116 | 117 | # get the raw code 118 | gist_content <- gist_extract[["content"]] 119 | 120 | # populate the imgur link into added text 121 | content_plus_comment <- glue::glue("{gist_content}\n# Code image at: ![]({imgur_url})\n\n") 122 | 123 | # rename the element with gist_name 124 | comment_body <- list( 125 | files = list(gist_name = list(content = content_plus_comment)) 126 | ) 127 | names(comment_body$files) <- gist_name 128 | 129 | # build the request 130 | req_update <- req_gist %>% 131 | httr2::req_body_json(comment_body) %>% 132 | httr2::req_method("PATCH") 133 | 134 | # send the add url request 135 | gist_with_url <- httr2::req_perform(req_update) 136 | 137 | } 138 | 139 | #' Carbon themes 140 | #' 141 | #' A list of available themes supported by Carbon. 142 | #' 143 | #' Sourced from 144 | #' 145 | #' @examples { 146 | #' carbon_themes 147 | #' } 148 | "carbon_themes" 149 | 150 | #' Carbon fonts 151 | #' 152 | #' A list of available fonts that carbon supports. 153 | #' 154 | #' Sourced from: 155 | #' 156 | #' @examples { 157 | #' carbon_fonts 158 | #' } 159 | "carbon_fonts" 160 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # gistillery 2 | 3 | 4 | 5 | 6 | 7 | The goal of `gistillery` is to make it outrageously simple to take local code, send it to a [Github gist](https://gist.github.com/), get a beautiful image from [Carbon.now.sh](https://carbon.now.sh/), and make it ready to share! 8 | 9 | Other packages that operate in the same space: 10 | 11 | - [`gistr`](https://github.com/ropensci/gistr) from ROpenSci - it provides a powerful and general interface to Gists. I have taken a different approach internally with `httr2`, but inspired by this interface. 12 | - [`gistfo`](https://github.com/MilesMcBain/gistfo) from Miles McBain/Garrick Aden-Buie, this is a usefully opinionated "Get It Somewhere The F\*\*\* Online" package. I also adapted some concepts from this package.. 13 | - [`carbonate`](https://github.com/yonicd/carbonate) from Jonathan Sidi. A robust approach to a similar problem. `carbonate` uses R6 classes (object-oriented programming) and RSelenium. Rather than using `RSelenium`, `gistillery` uses `webshot2` to take a screenshot of the code with `chromote`. 14 | - [`carbonace`](https://github.com/yonicd/carbonace) from Jonathan Sidi. "A shiny app that converts the ace editor as high resolution images to share". This package provides a nice, local-only, offline ability to screenshot. 15 | 16 | The difference in `{gistillery}` from the above packages is the intention to have both a Github Gist and a screenshot. `gistr` allows for uploading Gists but no screenshots, `gistfo` takes an entire file/text selection and uploads to gist and then to Carbon, but doesn't take programmatic screenshots. `carbonate`/`carbonace` take screenshots via Carbon/Ace but don't have a Gist component. 17 | 18 | When sharing screenshots of code I believe it is vitally important to include a copy-pasteable/screen reader friendly option, thus `{gistillery}` requires a Gist to be created or an existing Gist to be used for taking screenshots. You can then include a link to the Gist wherever the screenshot goes, whether Twitter, LinkedIn, a personal website, or some other location. The other packages are still awesome - this is just a slightly different approach. 19 | 20 | ## Installation 21 | 22 | You can install the development version of gistillery from [GitHub](https://github.com/) with: 23 | 24 | ``` r 25 | # install.packages("remotes") 26 | remotes::install_github("jthomasmock/gistillery") 27 | ``` 28 | 29 | ## Core Workflow 30 | 31 | There are three core functions, providing three steps in the process. Take code and upload to a Gist, take a screenshot of it, and then add a image url to the Gist. Importantly, all the steps are not *required* to be completed via this workflow. You can take existing Gists and use components of these functions rather than having to stick to the end-to-end workflow. 32 | 33 | Please note that for Github Authentication which is required to affect your Gists, you'll need to reference the [`gistr` docs](https://docs.ropensci.org/gistr/reference/gist_auth.html) 34 | 35 | > Generate a personal access token with the gist scope selected, and set it as the GITHUB_PAT environment variable per session using Sys.setenv or across sessions by adding it to your .Renviron file or similar. See for help 36 | 37 | ### Step 1 38 | 39 | We can use `gist_upload()` to take code from a file (via `readLines`), from a `reprex`/clipboard via `clipr::read_clip()`, or from a unsaved file via `rstudioapi`. Note that it also attaches the Gist URL to the bottom of the code snippet, so when you eventually share the code as an image people can still access copy-pastable code! (This is borrowed from `gistfo`, not an original idea) 40 | 41 | ``` r 42 | # Load the functions 43 | library(gistillery) 44 | ``` 45 | 46 | ``` r 47 | # this will use rstudioapi to take ALL the code from the currently 48 | # highlighted file inside RStudio 49 | # Workflow similar to core gistfo 50 | 51 | gist_upload(content = NULL, gist_name = "unsaved15.R") 52 | ``` 53 | 54 | ``` r 55 | # We can take an existing file, and throw it up as a Gist quickly 56 | gist_upload(content = readLines("mylocal-file.R"), gist_name = "local-file.R") 57 | 58 | # Or we can take some code from the clipboard 59 | gist_upload(content = clipr::read_clip(), gist_name = "copy-pasted-code.R") 60 | # or even a reprex 61 | gist_upload(reprex::reprex_r(), gist_name = "test-prex.R") 62 | 63 | # or save the reprex to an object first 64 | test_reprex <- reprex::reprex() 65 | gist_upload(test_reprex, gist_name = "reprex-object.R") 66 | ``` 67 | 68 | ### Step 2 69 | 70 | Regardless of *how* you got the code to a Gist, you can then move on to step 2 and get the code over to [carbon.now.sh](https://carbon.now.sh) for beautiful screenshots. It takes the unique id for a Gist and then returns a lovely screenshot. Note that if you want to share that screenshot it also appends the gist URL to the bottom of the image. You should also include alt-text linking out to the Gist! 71 | 72 | ``` r 73 | # core workflow 74 | # specify the gist id to get code from 75 | # specify the name of the output png 76 | gist_to_carbon( 77 | gist_id = "17adcd1a401bec0e41cbd671048ff0b4", 78 | file = "my-screenshot.png" 79 | ) 80 | ``` 81 | 82 | ![A screenshot of code, with the full code available at: ](https://i.imgur.com/CwhrqKy.png) 83 | 84 | If you want to go further with customization, you can change the background color with `bg`, the code theme with `theme`, the monospace font with `font`, the programming language with `lang` and optionally turn on/off the "upload to Imgur" feature. The `imgur=TRUE` option will give you an immediate URL so that you can embed the code elsewhere without having to actually upload the full image again. 85 | 86 | You can also set some of these parameters via `options` like so: `options(gistillery.bg = "#d3d3d3", gistillery.theme = "cobalt", gistillery.font = "Fira+Code")` 87 | 88 | ### Step 3 89 | 90 | Now that you have a local image and the Imgur link, you can use the third function. `add_gist_img` will take an existing gist and append the Imgur link to the code itself, that way you can programmatically add the screenshot URL back to your specific Gists. 91 | 92 | ``` r 93 | gist_append_img( 94 | imgur_url = "https://i.imgur.com/UEkGyx7.png", 95 | gist_id = "17adcd1a401bec0e41cbd671048ff0b4" 96 | ) 97 | ``` 98 | 99 | Alternatively, you can use the Imgur link to include your code in places where it's inconvenient to use local image files or when you can't format code properly. 100 | 101 | You can also use `gist_comment()` to upload a markdown-styled image into the comments of an existing Gist, like below: 102 | 103 | ``` r 104 | gist_comment(gist_id, "![](some-valid-imgur-url.png)") 105 | ``` 106 | 107 | That will add a comment to the existing gist, adding a markdown image. 108 | 109 | ### Step N + 1 110 | 111 | Now for the next step, you may want to post it to Twitter or somewhere else. My ask is that you use alt-text and link out to Github so that you can both assist screen-reader users and folks who just want to copy-paste your code! 112 | 113 | As of 2022-05-09, you can use the GitHub version of `rtweet::post_tweet()` to post tweets, images, and alt-text. 114 | 115 | IE: 116 | 117 | ``` r 118 | rtweet::post_tweet( 119 | status = "My cool code screenshot", 120 | media = "my-screenshot.png", 121 | media_alt_text = "This is a screenshot of some R code. The code is available at https://gist.github.com/jthomasmock/17adcd1a401bec0e41cbd671048ff0b4. I have also copy-pasted the code below: 122 | 123 | # core workflow 124 | gist_to_carbon( 125 | gist_id = '17adcd1a401bec0e41cbd671048ff0b4', 126 | file = 'my-screenshot.png' 127 | ) 128 | " 129 | ) 130 | ``` 131 | 132 | ### `reprex_shot()` 133 | 134 | The last function, `reprex_shot()` is more of a local workflow. Calling this function will execute `reprex::reprex()` and then save the HTML to an image on disk. This is fine if you want to quickly get a local screenshot but it will not have the gist url attached to the screenshot. 135 | 136 | ``` r 137 | reprex_shot(filename = "my-local-reprex.png") 138 | ``` 139 | 140 | ### Altogether 141 | 142 | If you wanted, you could used a pipe based workflow to get a seamless `reprex` -\> upload to Gist -\> screenshot from Carbon. 143 | 144 | ``` r 145 | reprex::reprex_r() |> 146 | gistillery::gist_upload(gist_name = "new-test-reprex.R") |> 147 | gistillery::gist_to_carbon(file = "new-test-reprex.png") 148 | ``` 149 | --------------------------------------------------------------------------------