Skip to content

Commit 28e4b42

Browse files
authored
Merge pull request #21 from brshallo/fix/gists-api-robust
Make gist API handling robust to non-200 responses / zero gists
2 parents 59a481f + 352ac5c commit 28e4b42

1 file changed

Lines changed: 37 additions & 63 deletions

File tree

R/list-files-gh-gists.R

Lines changed: 37 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -1,82 +1,65 @@
1+
get_gist_pages <- function(user) {
12

2-
get_gist_pages <- function(user){
3+
req_user <- httr::GET(glue::glue("https://api.github.com/users/{user}"))
34

4-
req_user <- httr::GET(
5-
glue::glue("https://api.github.com/users/{user}")
6-
)
5+
# Check HTTP response first
6+
if (httr::status_code(req_user) != 200) {
7+
warning("Failed to fetch GitHub user info for '\", user,\"' (status ", httr::status_code(req_user), "). Returning 0 pages.")
8+
return(0L)
9+
}
710

8-
num_gists <- httr::content(req_user)$public_gists
11+
body <- httr::content(req_user)
12+
num_gists <- body$public_gists
913

10-
# Number of pages to do (assuming 100 per page)
11-
num_pages <- num_gists %/% 100
12-
remainder <- num_gists %% 100
13-
if(remainder > 0) num_pages <- num_pages + 1
14+
# Defensive handling: ensure we have a single non-negative integer
15+
if (is.null(num_gists) || length(num_gists) == 0) return(0L)
16+
num_gists <- as.integer(num_gists)
17+
if (is.na(num_gists) || num_gists <= 0L) return(0L)
1418

19+
# Compute pages using ceiling to avoid remainder logic problems
20+
num_pages <- ceiling(num_gists / 100)
1521
num_pages
1622
}
1723

18-
get_gist_content <- function(user){
24+
get_gist_content <- function(user) {
1925

2026
num_pages <- get_gist_pages(user)
2127

28+
# If there are no pages, return an empty list
29+
if (num_pages == 0L) return(list())
30+
2231
# gists themselves
2332
req_content <- vector("list", num_pages)
2433

25-
for(i in seq_len(num_pages)){
34+
for (i in seq_len(num_pages)) {
35+
36+
req <- httr::GET(
37+
glue::glue("https://api.github.com/users/{user}/gists"),
38+
query = list(per_page = 100, page = i)
39+
)
2640

27-
req <- httr::GET(glue::glue("https://api.github.com/users/{user}/gists"),
28-
query = list(per_page = 100,
29-
page = i))
41+
if (httr::status_code(req) != 200) {
42+
warning("Failed to fetch gists for page ", i, " (status ", httr::status_code(req), "). Skipping page.")
43+
req_content[[i]] <- list()
44+
next
45+
}
3046

3147
req_content[[i]] <- httr::content(req)
3248
}
3349

3450
purrr::flatten(req_content)
35-
3651
}
3752

38-
#' List Github Gists of User
39-
#'
40-
#' Given a username, return a dataframe with paths to all the gists by that user.
41-
#'
42-
#' @param user Character string of username whose github gists you want to pull.
43-
#' @param pattern Regex pattern to keep only matching files. Default is
44-
#' `stringr::regex("(r|rmd|rmarkdown|qmd)$", ignore_case = TRUE)` which will
45-
#' keep only R, Rmarkdown and Quarto documents. If you have a lot of .md gists
46-
#' that can be converted to .R files you may want to edit this argument. To
47-
#' keep all files use `"."`.
48-
#'
49-
#' @return Dataframe with `relative_paths` and `absolute_paths` of file paths.
50-
#' Because gists do not exist in a folder structure `relative_paths` will
51-
#' generally just be a file name. `absolute_paths` a url to the raw file. See
52-
#' `unnest_results()` for helper to put into an easier to read format.
53-
#'
54-
#' @seealso [list_files_github_repo()], [list_files_wd()]
55-
#' @export
56-
#'
57-
#' @examples
58-
#' \donttest{
59-
#' library(dplyr)
60-
#' library(funspotr)
61-
#'
62-
#' # pulling and analyzing my R file github gists
63-
#' gists_urls <- list_files_github_gists("brshallo", pattern = ".")
64-
#'
65-
#' # Will just parse the first 2 files/gists
66-
#' # Note that is easy to hit the API limit if have lots of gists
67-
#' contents <- filter(gists_urls, str_detect_r_docs(absolute_paths)) %>%
68-
#' slice(1:2) %>%
69-
#' spot_funs_files()
70-
#'
71-
#'
72-
#' contents %>%
73-
#' unnest_results()
74-
#' }
7553
list_files_github_gists <- function(user,
76-
pattern = stringr::regex("(r|rmd|rmarkdown|qmd)$", ignore_case = TRUE)){
54+
pattern = stringr::regex("(r|rmd|rmarkdown|qmd)$", ignore_case = TRUE)) {
7755

7856
content <- get_gist_content(user)
7957

58+
# If nothing returned, return an empty tibble with the same columns
59+
if (length(content) == 0) {
60+
return(tibble::tibble(relative_paths = character(), absolute_paths = character()))
61+
}
62+
8063
raw_urls <- content %>%
8164
map("files") %>%
8265
map(1) %>%
@@ -85,13 +68,4 @@ list_files_github_gists <- function(user,
8568
output <- tibble(relative_paths = fs::path_file(raw_urls), absolute_paths = raw_urls)
8669

8770
filter(output, str_detect_r_docs(.data$relative_paths, pattern = pattern, rmv_index = FALSE))
88-
}
89-
90-
91-
#' #' `r lifecycle::badge("deprecated")`
92-
#' #'
93-
#' #' This function was deprecated with updates to the API that improved
94-
#' #' consistency of naming conventions.
95-
#' github_gists <- function(user, drop_non_r = TRUE){
96-
#' list_files_github_gists(user, drop_non_r)
97-
#' }
71+
}

0 commit comments

Comments
 (0)