From 47f8e4e054e93aff18af51075b64f11b97073179 Mon Sep 17 00:00:00 2001 From: jennybc Date: Thu, 28 May 2015 23:09:35 -0700 Subject: [PATCH 1/7] refactor data consumption and editing --- NAMESPACE | 24 +- R/alpha-inspect.R | 2 +- R/cell-specification-googlesheets.R | 20 - R/consume-data.R | 594 ------------------ R/gs_auth.R | 8 +- R/gs_cell-specification.R | 80 +++ R/{edit-data.R => gs_edit_cells.R} | 111 ++-- ...mple_sheets.R => gs_example-sheet-setup.R} | 0 R/gs_new.R | 31 +- R/gs_read.R | 66 ++ R/gs_read_cellfeed.R | 176 ++++++ R/gs_read_csv.R | 95 +++ R/gs_read_listfeed.R | 76 +++ R/gs_reshape_cellfeed.R | 76 +++ R/gs_simplify_cellfeed.R | 81 +++ R/gs_upload.R | 2 +- R/gs_ws.R | 28 +- README.Rmd | 56 +- README.md | 142 ++--- man-roxygen/cellranger.R | 4 + man/cell-specification.Rd | 27 + man/example-sheets.Rd | 2 +- man/get_cells.Rd | 42 -- man/get_col.Rd | 41 -- man/get_row.Rd | 41 -- man/get_via_cf.Rd | 75 --- man/get_via_csv.Rd | 52 -- man/get_via_lf.Rd | 52 -- man/{edit_cells.Rd => gs_edit_cells.Rd} | 34 +- man/gs_inspect.Rd | 2 +- man/gs_new.Rd | 22 +- man/gs_read.Rd | 74 +++ man/gs_read_cellfeed.Rd | 71 +++ man/gs_read_csv.Rd | 58 ++ man/gs_read_listfeed.Rd | 56 ++ man/gs_reshape_cellfeed.Rd | 40 ++ man/gs_simplify_cellfeed.Rd | 66 ++ man/gs_upload.Rd | 2 +- man/gs_ws_delete.Rd | 4 +- man/gs_ws_new.Rd | 12 +- man/gs_ws_resize.Rd | 10 +- man/reshape_cf.Rd | 31 - man/simplify_cf.Rd | 57 -- .../testthat/gap_sheet5_gs_read_cellfeed.rds | Bin 0 -> 1469 bytes .../testthat/gap_sheet5_gs_read_listfeed.rds | Bin 0 -> 647 bytes tests/testthat/iris_identify_ss.rds | Bin 372 -> 0 bytes tests/testthat/iris_pvt_get_via_cf.rds | Bin 490 -> 0 bytes tests/testthat/iris_pvt_get_via_lf.rds | Bin 253 -> 252 bytes tests/testthat/iris_pvt_gs_read_cellfeed.rds | Bin 0 -> 482 bytes tests/testthat/iris_pvt_gs_read_listfeed.rds | Bin 0 -> 252 bytes tests/testthat/test-cell-edit.R | 62 +- tests/testthat/test-consume-data-private.R | 10 +- tests/testthat/test-gs-create-delete-copy.R | 4 +- tests/testthat/test-inspect.R | 16 +- tests/testthat/test-ws-edits.R | 2 +- .../test-yy-consume-data-public-selective.R | 80 +-- .../test-yy-consume-data-public-tricky.R | 39 +- ...test-yy-consume-data-public-whole-sheets.R | 20 +- vignettes/basic-usage.R | 24 +- vignettes/basic-usage.Rmd | 28 +- vignettes/basic-usage.html | 82 ++- vignettes/basic-usage.md | 85 ++- 62 files changed, 1520 insertions(+), 1475 deletions(-) delete mode 100644 R/cell-specification-googlesheets.R delete mode 100644 R/consume-data.R create mode 100644 R/gs_cell-specification.R rename R/{edit-data.R => gs_edit_cells.R} (66%) rename R/{gs_example_sheets.R => gs_example-sheet-setup.R} (100%) create mode 100644 R/gs_read.R create mode 100644 R/gs_read_cellfeed.R create mode 100644 R/gs_read_csv.R create mode 100644 R/gs_read_listfeed.R create mode 100644 R/gs_reshape_cellfeed.R create mode 100644 R/gs_simplify_cellfeed.R create mode 100644 man-roxygen/cellranger.R create mode 100644 man/cell-specification.Rd delete mode 100644 man/get_cells.Rd delete mode 100644 man/get_col.Rd delete mode 100644 man/get_row.Rd delete mode 100644 man/get_via_cf.Rd delete mode 100644 man/get_via_csv.Rd delete mode 100644 man/get_via_lf.Rd rename man/{edit_cells.Rd => gs_edit_cells.Rd} (66%) create mode 100644 man/gs_read.Rd create mode 100644 man/gs_read_cellfeed.Rd create mode 100644 man/gs_read_csv.Rd create mode 100644 man/gs_read_listfeed.Rd create mode 100644 man/gs_reshape_cellfeed.Rd create mode 100644 man/gs_simplify_cellfeed.Rd delete mode 100644 man/reshape_cf.Rd delete mode 100644 man/simplify_cf.Rd create mode 100644 tests/testthat/gap_sheet5_gs_read_cellfeed.rds create mode 100644 tests/testthat/gap_sheet5_gs_read_listfeed.rds delete mode 100644 tests/testthat/iris_identify_ss.rds delete mode 100644 tests/testthat/iris_pvt_get_via_cf.rds create mode 100644 tests/testthat/iris_pvt_gs_read_cellfeed.rds create mode 100644 tests/testthat/iris_pvt_gs_read_listfeed.rds diff --git a/NAMESPACE b/NAMESPACE index 3c15987..b41e907 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -2,18 +2,16 @@ S3method(print,googlesheet) S3method(print,googlesheet_ls) -export(edit_cells) +export(anchored) +export(cell_cols) +export(cell_limits) +export(cell_rows) export(extract_key_from_url) -export(get_cells) -export(get_col) -export(get_row) -export(get_via_cf) -export(get_via_csv) -export(get_via_lf) export(gs_auth) export(gs_copy) export(gs_delete) export(gs_download) +export(gs_edit_cells) export(gs_gap) export(gs_gap_key) export(gs_gap_url) @@ -23,6 +21,12 @@ export(gs_inspect) export(gs_key) export(gs_ls) export(gs_new) +export(gs_read) +export(gs_read_cellfeed) +export(gs_read_csv) +export(gs_read_listfeed) +export(gs_reshape_cellfeed) +export(gs_simplify_cellfeed) export(gs_title) export(gs_upload) export(gs_url) @@ -33,6 +37,8 @@ export(gs_ws_feed) export(gs_ws_ls) export(gs_ws_new) export(gs_ws_rename) -export(reshape_cf) -export(simplify_cf) +importFrom(cellranger,anchored) +importFrom(cellranger,cell_cols) +importFrom(cellranger,cell_limits) +importFrom(cellranger,cell_rows) importFrom(dplyr,"%>%") diff --git a/R/alpha-inspect.R b/R/alpha-inspect.R index ac1be07..91d0043 100644 --- a/R/alpha-inspect.R +++ b/R/alpha-inspect.R @@ -18,7 +18,7 @@ #' # data recorded from a game of ultimate frisbee #' ulti_key <- "1223dpf3vnjZUYUnCM8rBSig3JlGrAu1Qu6VmPvdEn4M" #' ulti_ss <- ulti_key %>% gs_key() -#' ulti_csv <- ulti_ss %>% get_col(ws = 2, col = 1:6) %>% reshape_cf() +#' ulti_csv <- ulti_ss %>% get_col(ws = 2, col = 1:6) %>% gs_reshape_cellfeed() #' gs_inspect(ulti_csv) #' #' } diff --git a/R/cell-specification-googlesheets.R b/R/cell-specification-googlesheets.R deleted file mode 100644 index 9996fb5..0000000 --- a/R/cell-specification-googlesheets.R +++ /dev/null @@ -1,20 +0,0 @@ -## cell specification functions that are specific to googlesheets, i.e. stuff -## not handled in cellranger - -## basically boils down to: -## cell_limits object <--> limits in the list form I need for Sheets API query - -limit_list <- function(x) { - - stopifnot(inherits(x, "cell_limits")) - - list(`min-row` = x$rows[1], `max-row` = x$rows[2], - `min-col` = x$cols[1], `max-col` = x$cols[2]) -} - -un_limit_list <- function(x) { - - cellranger::cell_limits(rows = c(x[['min-row']], x[['max-row']]), - cols = c(x[['min-col']], x[['max-col']])) - -} diff --git a/R/consume-data.R b/R/consume-data.R deleted file mode 100644 index 4624414..0000000 --- a/R/consume-data.R +++ /dev/null @@ -1,594 +0,0 @@ -#' Get all data from a rectangular worksheet as a tbl_df or data.frame -#' -#' This function consumes data using the \code{exportcsv} links found in the -#' worksheets feed. Don't be spooked by the "csv" thing -- the data is NOT -#' actually written to file during this process. In fact, this is much, much -#' faster than consumption via the list feed. Unlike using the list feed, this -#' method does not assume that the populated cells form a neat rectangle. All -#' cells within the "data rectangle", i.e. spanned by the maximal row and column -#' extent of the data, are returned. Empty cells will be assigned NA. Also, the -#' header row, potentially containing column or variable names, is not -#' transformed/mangled, as it is via the list feed. If you want all of your -#' data, this is the fastest way to get it. -#' -#' @template ss -#' @template ws -#' @param ... Further arguments to be passed to the csv parser. This is -#' currently \code{\link{read.csv}}, but expect a switch to -#' \code{readr::read_csv} in the not-too-distant future! Note that by default -#' \code{\link{read.csv}} is called with \code{stringsAsFactors = FALSE}. -#' @template verbose -#' -#' @family data consumption functions -#' -#' @return a tbl_df -#' -#' @examples -#' \dontrun{ -#' gap_ss <- gs_gap() # register the Gapminder example sheet -#' oceania_csv <- get_via_csv(gap_ss, ws = "Oceania") -#' str(oceania_csv) -#' oceania_csv -#' } -#' @export -get_via_csv <- function(ss, ws = 1, ..., verbose = TRUE) { - - stopifnot(ss %>% inherits("googlesheet")) - - this_ws <- gs_ws(ss, ws, verbose) - - if(is.null(this_ws$exportcsv)) { - stop(paste("This appears to be an \"old\" Google Sheet. The old Sheets do", - "not offer the API access required by this function.", - "Consider converting it from an old Sheet to a new Sheet.", - "Or use another data consumption function, such as get_via_lf()", - "or get_via_cf(). Or use gs_download() to export it to a local", - "file and then read it into R.")) - } - - req <- gsheets_GET(this_ws$exportcsv, to_xml = FALSE, - use_auth = !ss$is_public) - - if(req$headers$`content-type` != "text/csv") { - stop1 <- "Cannot access this sheet via csv." - stop2 <- "Are you sure you have permission to access this Sheet?" - stop3 <- "If this Sheet is supposed to be public, make sure it is \"published to the web\", which is NOT the same as \"public on the web\"." - stop4 <- sprintf("status_code: %s", req$status_code) - stop5 <- sprintf("content-type: %s", req$headers$`content-type`) - stop(paste(stop1, stop2, stop3, stop4, stop5, sep = "\n")) - } - - if(httr::content(req) %>% is.null()) { - sprintf("Worksheet \"%s\" is empty.", this_ws$ws_title) %>% - message() - dplyr::data_frame() - } else { - ## for empty cells, numeric columns returned as NA vs "" for chr - ## columns so set all "" to NA - req %>% - httr::content(type = "text/csv", na.strings = c("", "NA"), - encoding = "UTF-8", ...) %>% - dplyr::as_data_frame() - } -} - -#' Get data from a rectangular worksheet as a tbl_df or data.frame -#' -#' Gets data via the list feed, which assumes populated cells form a neat -#' rectangle. The list feed consumes data row by row. First row regarded as -#' header row of variable or column names. The related function, -#' \code{get_via_csv}, also returns data from a neat rectangle of cells, so you -#' probably want to use that (unless you are dealing with an "old" Google Sheet, -#' which \code{get_via_csv} does not support). -#' -#' @template ss -#' @template ws -#' @template verbose -#' -#' @note When you use the listfeed, the Sheets API transforms the variable or -#' column names like so: 'The column names are the header values of the -#' worksheet lowercased and with all non-alpha-numeric characters removed. For -#' example, if the cell A1 contains the value "Time 2 Eat!" the column name -#' would be "time2eat".' If this is intolerable to you, consume the data via -#' the cell feed or csv download. Or, at least, consume the first row via the -#' cell feed and manually restore the variable names post hoc. -#' -#' @family data consumption functions -#' -#' @return a tbl_df -#' -#' @examples -#' \dontrun{ -#' gap_ss <- gs_gap() # register the Gapminder example sheet -#' oceania_lf <- get_via_lf(gap_ss, ws = "Oceania") -#' str(oceania_lf) -#' oceania_lf -#' } -#' -#' @export -get_via_lf <- function(ss, ws = 1, verbose = TRUE) { - - stopifnot(ss %>% inherits("googlesheet")) - - this_ws <- gs_ws(ss, ws, verbose) - req <- gsheets_GET(this_ws$listfeed) - - ns <- xml2::xml_ns_rename(xml2::xml_ns(req$content), d1 = "feed") - - var_names <- req$content %>% - xml2::xml_find_all("(//feed:entry)[1]", ns) %>% - xml2::xml_find_all(".//gsx:*", ns) %>% - xml2::xml_name() - - values <- req$content %>% - xml2::xml_find_all("//feed:entry//gsx:*", ns) %>% - xml2::xml_text() - - dat <- matrix(values, ncol = length(var_names), byrow = TRUE, - dimnames = list(NULL, var_names)) %>% - ## convert to integer, numeric, etc. but w/ stringsAsFactors = FALSE - ## empty cells returned as empty string "" - plyr::alply(2, type.convert, na.strings = c("NA", ""), as.is = TRUE) %>% - ## get rid of attributes that are non-standard for tbl_dfs or data.frames - ## and that are an artefact of the above (specifically, I think, the use of - ## alply?); if I don't do this, the output is fugly when you str() it - `attr<-`("split_type", NULL) %>% - `attr<-`("split_labels", NULL) %>% - `attr<-`("dim", NULL) %>% - ## for some reason removing the non-standard dim attributes clobbers the - ## variable names, so those must be restored - `names<-`(var_names) %>% - ## convert to data.frame (tbl_df, actually) - dplyr::as_data_frame() - - dat - -} - -#' Create a data.frame of the non-empty cells in a rectangular region of a -#' worksheet -#' -#' This function consumes data via the cell feed, which, as the name suggests, -#' retrieves data cell by cell. No attempt is made here to shape the returned -#' data, but you can do that with \code{\link{reshape_cf}} and -#' \code{\link{simplify_cf}}). The output data.frame of \code{get_via_cf} will -#' have one row per cell. -#' -#' Use the limits, e.g. min_row or max_col, to delineate the rectangular region -#' of interest. You can specify any subset of the limits or none at all. If -#' limits are provided, validity will be checked as well as internal consistency -#' and compliance with known extent of the worksheet. If no limits are provided, -#' all cells will be returned but realize that \code{\link{get_via_csv}} and -#' \code{\link{get_via_lf}} are much faster ways to consume data from a -#' rectangular worksheet. -#' -#' Empty cells, even if "embedded" in a rectangular region of populated cells, -#' are not normally returned by the cell feed. This function won't return them -#' either when \code{return_empty = FALSE} (default), but will if you set -#' \code{return_empty = TRUE}. If you don't specify any limits AND you set -#' \code{return_empty = TRUE}, you could be in for several minutes wait, as the -#' feed will return all cells, which defaults to 1000 rows and 26 columns. -#' -#' @template ss -#' @template ws -#' @param min_row positive integer, optional -#' @param max_row positive integer, optional -#' @param min_col positive integer, optional -#' @param max_col positive integer, optional -#' @param limits list, with named components holding the min and max for rows -#' and columns; intended primarily for internal use -#' @param return_empty logical; indicates whether to return empty cells -#' @param return_links logical; indicates whether to return the edit and self -#' links (used internally in cell editing workflow) -#' @template verbose -#' -#' @examples -#' \dontrun{ -#' gap_ss <- gs_gap() # register the Gapminder example sheet -#' get_via_cf(gap_ss, "Asia", max_row = 4) -#' reshape_cf(get_via_cf(gap_ss, "Asia", max_row = 4)) -#' reshape_cf(get_via_cf(gap_ss, "Asia", -#' limits = list(max_row = 4, min_col = 3))) -#' } -#' @family data consumption functions -#' -#' @export -get_via_cf <- - function(ss, ws = 1, - min_row = NULL, max_row = NULL, min_col = NULL, max_col = NULL, - limits = NULL, return_empty = FALSE, return_links = FALSE, - verbose = TRUE) { - - stopifnot(ss %>% inherits("googlesheet")) - - this_ws <- gs_ws(ss, ws, verbose) - - if(is.null(limits)) { - limits <- list("min-row" = min_row, "max-row" = max_row, - "min-col" = min_col, "max-col" = max_col) - } else{ - names(limits) <- names(limits) %>% stringr::str_replace("_", "-") - } - limits <- limits %>% - validate_limits(this_ws$row_extent, this_ws$col_extent) - - query <- limits - if(return_empty) { - ## the return-empty parameter is not documented in current sheets API, but - ## is discussed in older internet threads re: the older gdata API; so if - ## this stops working, consider that they finally stopped supporting this - ## query parameter - query <- query %>% c(list("return-empty" = "true")) - } - - ## to prevent appending of "?=" to url when query elements are all NULL - if(query %>% unlist() %>% is.null()) { - query <- NULL - } - - req <- gsheets_GET(this_ws$cellsfeed, query = query) - - ns <- xml2::xml_ns_rename(xml2::xml_ns(req$content), d1 = "feed") - - x <- req$content %>% - xml2::xml_find_all("//feed:entry", ns) - - if(length(x) == 0L) { - # the pros outweighed the cons re: setting up a zero row data.frame that, - # at least, has the correct variables - x <- dplyr::data_frame(cell = character(), - cell_alt = character(), - row = integer(), - col = integer(), - cell_text = character(), - edit_link = character(), - cell_id = character()) - } else { - edit_links <- x %>% - xml2::xml_find_all(".//feed:link[@rel='edit']", ns) %>% - xml2::xml_attr("href") - - ## this will be true if user does not have permission to edit - if(length(edit_links) == 0) { - edit_links <- NA - } - - x <- dplyr::data_frame_( - list(cell = ~ xml2::xml_find_all(x, ".//feed:title", ns) %>% - xml2::xml_text(), - edit_link = ~ edit_links, - cell_id = ~ xml2::xml_find_all(x, ".//feed:id", ns) %>% - xml2::xml_text(), - cell_alt = ~ cell_id %>% basename(), - row = ~ xml2::xml_find_all(x, ".//gs:cell", ns) %>% - xml2::xml_attr("row") %>% - as.integer(), - col = ~ xml2::xml_find_all(x, ".//gs:cell", ns) %>% - xml2::xml_attr("col") %>% - as.integer(), - cell_text = ~ xml2::xml_find_all(x, ".//gs:cell", ns) %>% - xml2::xml_text() - )) - # see issue #19 about all the places cell data is (mostly redundantly) - # stored in the XML, such as: content_text = x$content$text, - # cell_inputValue = x$cell$.attrs["inputValue"], cell_numericValue = - # x$cell$.attrs["numericValue"], when/if we think about formulas - # explicitly, we will want to come back and distinguish between inputValue - # and numericValue - } - - x <- x %>% - dplyr::select_(~ cell, ~ cell_alt, ~ row, ~ col, ~ cell_text, - ~ edit_link, ~ cell_id) %>% - dplyr::as_data_frame() - - attr(x, "ws_title") <- this_ws$ws_title - - if(return_links) { - x - } else { - x %>% - dplyr::select_(~ -edit_link, ~ -cell_id) - } - - } - -#' Get data from a row or range of rows -#' -#' Get data via the cell feed for one row or for a range of rows. -#' -#' @template ss -#' @template ws -#' @param row vector of positive integers, possibly of length one, specifying -#' which rows to retrieve; only contiguous ranges of rows are supported, i.e. -#' if \code{row = c(2, 8)}, you will get rows 2 through 8 -#' @template verbose -#' -#' @family data consumption functions -#' @seealso \code{\link{reshape_cf}} to reshape the retrieved data into a more -#' usable data.frame -#' -#' @examples -#' \dontrun{ -#' gap_ss <- gs_gap() # register the Gapminder example sheet -#' get_row(gap_ss, "Europe", row = 1) -#' simplify_cf(get_row(gap_ss, "Europe", row = 1)) -#' } -#' -#' @export -get_row <- function(ss, ws = 1, row, verbose = TRUE) { - get_via_cf(ss, ws, min_row = min(row), max_row = max(row), verbose = verbose) -} - -#' Get data from a column or range of columns -#' -#' Get data via the cell feed for one column or for a range of columns. -#' -#' @template ss -#' @template ws -#' @param col vector of positive integers, possibly of length one, specifying -#' which columns to retrieve; only contiguous ranges of columns are supported, -#' i.e. if \code{col = c(2, 8)}, you will get columns 2 through 8 -#' @template verbose -#' -#' @family data consumption functions -#' @seealso \code{\link{reshape_cf}} to reshape the retrieved data into a more -#' usable data.frame -#' -#' @examples -#' \dontrun{ -#' gap_ss <- gs_gap() # register the Gapminder example sheet -#' get_col(gap_ss, "Oceania", col = 1:2) -#' reshape_cf(get_col(gap_ss, "Oceania", col = 1:2)) -#' } -#' -#' @export -get_col <- function(ss, ws = 1, col, verbose = TRUE) { - get_via_cf(ss, ws, min_col = min(col), max_col = max(col), verbose = verbose) -} - -#' Get data from a cell or range of cells -#' -#' Get data via the cell feed for a rectangular range of cells -#' -#' @template ss -#' @template ws -#' @param range single character string specifying which cell or range of cells -#' to retrieve; positioning notation can be either "A1" or "R1C1"; a single -#' cell can be requested, e.g. "B4" or "R4C2" or a rectangular range can be -#' requested, e.g. "B2:D4" or "R2C2:R4C4" -#' @template verbose -#' -#' @family data consumption functions -#' @seealso \code{\link{reshape_cf}} to reshape the retrieved data into a more -#' usable data.frame -#' -#' @examples -#' \dontrun{ -#' gap_ss <- gs_gap() # register the Gapminder example sheet -#' get_cells(gap_ss, "Europe", range = "B3:D7") -#' simplify_cf(get_cells(gap_ss, "Europe", range = "A1:F1")) -#' } -#' -#' @export -get_cells <- function(ss, ws = 1, range, verbose = TRUE) { - - limits <- range %>% - cellranger::as.cell_limits() %>% - limit_list() - get_via_cf(ss, ws, limits = limits, verbose = verbose) - -} - -#' Reshape cell-level data and convert to data.frame -#' -#' @param x a data.frame returned by \code{\link{get_via_cf}} -#' @param header logical indicating whether first row should be taken as -#' variable names -#' -#' @family data consumption functions -#' -#' @examples -#' \dontrun{ -#' gap_ss <- gs_gap() # register the Gapminder example sheet -#' get_via_cf(gap_ss, "Asia", max_row = 4) -#' reshape_cf(get_via_cf(gap_ss, "Asia", max_row = 4)) -#' } -#' @export -reshape_cf <- function(x, header = TRUE) { - - limits <- x %>% - dplyr::summarise_each_(dplyr::funs(min, max), list(~ row, ~ col)) - all_possible_cells <- - with(limits, - expand.grid(row = row_min:row_max, col = col_min:col_max)) %>% - dplyr::as.tbl() - suppressMessages( - x_augmented <- all_possible_cells %>% dplyr::left_join(x) - ) - ## tidyr::spread(), used below, could do something similar as this join, but - ## it would handle completely missing rows and columns differently; still - ## thinking about this - - if(header) { - - if(x_augmented$row %>% dplyr::n_distinct() < 2) { - message("No data to reshape!") - if(header) { - message("Perhaps retry with `header = FALSE`?") - } - return(NULL) - } - - row_one <- x_augmented %>% - dplyr::filter_(~ (row == min(row))) %>% - dplyr::mutate_(cell_text = ~ ifelse(cell_text == "", NA, cell_text)) - var_names <- ifelse(is.na(row_one$cell_text), - stringr::str_c("C", row_one$col), - row_one$cell_text) %>% make.names() - x_augmented <- x_augmented %>% - dplyr::filter_(~ row > min(row)) - } else { - var_names <- limits$col_min:limits$col_max %>% make.names() - } - - x_augmented %>% - dplyr::select_(~ row, ~ col, ~ cell_text) %>% - tidyr::spread_("col", "cell_text", convert = TRUE) %>% - dplyr::select_(~ -row) %>% - stats::setNames(var_names) -} - -#' Simplify data from the cell feed -#' -#' In some cases, you might not want to convert the data retrieved from the cell -#' feed into a data.frame via \code{\link{reshape_cf}}. You might prefer it as -#' an atomic vector. That's what this function does. Note that, unlike -#' \code{\link{reshape_cf}}, empty cells will NOT necessarily appear in this -#' result. By default, the API does not transmit data for these cells; -#' \code{googlesheets} inserts these cells in \code{\link{reshape_cf}} because -#' it is necessary to give the data rectangular shape. In contrast, empty cells -#' will only appear in the output of \code{simplify_cf} if they were already -#' present in the data from the cell feed, i.e. if the original call to -#' \code{\link{get_via_cf}} had argument \code{return_empty} set to \code{TRUE}. -#' -#' @inheritParams reshape_cf -#' @param convert logical, indicating whether to attempt to convert the result -#' vector from character to something more appropriate, such as logical, -#' integer, or numeric; if TRUE, result is passed through \code{type.convert}; -#' if FALSE, result will be character -#' @param as.is logical, passed through to the \code{as.is} argument of -#' \code{type.convert} -#' @param notation character; the result vector will have names that reflect -#' which cell the data came from; this argument selects the positioning -#' notation, i.e. "A1" vs. "R1C1" -#' -#' @return a named vector -#' -#' @examples -#' \dontrun{ -#' gap_ss <- gs_gap() # register the Gapminder example sheet -#' get_row(gap_ss, row = 1) -#' simplify_cf(get_row(gap_ss, row = 1)) -#' simplify_cf(get_row(gap_ss, row = 1), notation = "R1C1") -#' } -#' -#' @family data consumption functions -#' -#' @export -simplify_cf <- function(x, convert = TRUE, as.is = TRUE, - notation = c("A1", "R1C1"), header = NULL) { - - ## TO DO: If the input contains empty cells, maybe this function should have a - ## way to request that cell entry "" be converted to NA? - - notation <- match.arg(notation) - - if(is.null(header) && - x$row %>% min() == 1 && - x$col %>% dplyr::n_distinct() == 1) { - header <- TRUE - } else { - header <- FALSE - } - - if(header) { - x <- x %>% - dplyr::filter_(~ row > min(row)) - } - - y <- x$cell_text - names(y) <- switch(notation, - A1 = x$cell, - R1C1 = x$cell_alt) - if(convert) { - y %>% type.convert(as.is = as.is) - } else { - y - } -} - -## argument validity checks and transformation - -## re: min_row, max_row, min_col, max_col = query params for cell feed -validate_limits <- - function(limits, ws_row_extent = NULL, ws_col_extent = NULL) { - - ## limits must be length one vector, holding a positive integer - - ## why do I proceed this way? - ## [1] want to preserve original invalid limits for use in error message - ## [2] want to be able to say which element(s) of limits is/are invalid - tmp_limits <- limits %>% plyr::llply(affirm_not_factor) - tmp_limits <- tmp_limits %>% plyr::llply(make_integer) - tmp_limits <- tmp_limits %>% plyr::llply(affirm_length_one) - tmp_limits <- tmp_limits %>% plyr::llply(affirm_positive) - if(any(oops <- is.na(tmp_limits))) { - mess <- sprintf(paste0("A row or column limit must be a single positive", - "integer (or not given at all).\nInvalid input:\n", - "%s"), - paste(capture.output(limits[oops]), collapse = "\n")) - stop(mess) - } else { - limits <- tmp_limits - } - - ## min must be <= max, min and max must be <= nominal worksheet extent - jfun <- function(x, upper_bound) { - x_name <- deparse(substitute(x)) - ub_name <- deparse(substitute(upper_bound)) - if(!is.null(x) && !is.null(upper_bound) && x > upper_bound) { - mess <- - sprintf("%s must be less than or equal to %s\n%s = %d, %s = %d\n", - x_name, ub_name, x_name, x, ub_name, upper_bound) - stop(mess) - } - } - - jfun(limits[["min-row"]], limits[["max-row"]]) - jfun(limits[["min-row"]], ws_row_extent) - jfun(limits[["max-row"]], ws_row_extent) - jfun(limits[["min-col"]], limits[["max-col"]]) - jfun(limits[["min-col"]], ws_col_extent) - jfun(limits[["max-col"]], ws_col_extent) - - limits - } - -affirm_not_factor <- function(x) { - if(is.null(x) || !inherits(x, "factor")) { - x - } else { - NA - } -} - -make_integer <- function(x) { - suppressWarnings(try({ - if(!is.null(x)) { - storage.mode(x) <- "integer" - ## why not use as.integer? because names are lost :( - ## must use this method based on storage.mode - ## if coercion fails, x is NA - ## note this will "succeed" and coerce, eg, 4.7 to 4L - } - x - }, silent = FALSE)) -} - -affirm_length_one <- function(x) { - if(is.null(x) || length(x) == 1L || is.na(x)) { - x - } else { - NA - } -} - -affirm_positive <- function(x) { - if(is.null(x) || x > 0 || is.na(x)) { - x - } else { - NA - } -} - diff --git a/R/gs_auth.R b/R/gs_auth.R index f260c04..5407044 100644 --- a/R/gs_auth.R +++ b/R/gs_auth.R @@ -138,13 +138,13 @@ gs_user <- function(verbose = TRUE) { } if(verbose) { - sprintf(" displayName: %s\n", + sprintf(" displayName: %s", ret$displayName) %>% message() - sprintf(" emailAddress: %s\n", + sprintf(" emailAddress: %s", ret$emailAddress) %>% message() - sprintf("Date-time of session authorization: %s\n", + sprintf("Date-time of session authorization: %s", ret$auth_date) %>% message() - sprintf(" Date-time of access token expiry: %s\n", + sprintf(" Date-time of access token expiry: %s", ret$exp_date) %>% message() if(token_ok) { message("Access token is valid.") diff --git a/R/gs_cell-specification.R b/R/gs_cell-specification.R new file mode 100644 index 0000000..b53fa99 --- /dev/null +++ b/R/gs_cell-specification.R @@ -0,0 +1,80 @@ +## the real work is done by the cellranger package +## https://github.com/jennybc/cellranger +## http://cran.r-project.org/web/packages/cellranger/index.html + +## here we +## [1] import + export specific cellranger functions to expose them for +## googlesheets users +## [2] further below, define unexported utility functions for translating +## between a cell_limits object and data structures we need in this pkg + +#' Specify cells for reading or writing +#' +#' If you aren't targetting all the cells in a worksheet, you can request that +#' \code{googlesheets} limit a read or write operation to a specific rectangle +#' of cells. Any function that offers this flexibility will have a \code{range} +#' argument. The simplest usage is to specify an Excel-like cell range, such as +#' \code{range = "D12:F15"} or \code{range = "R1C12:R6C15"}. The cell rectangle +#' can be specified in various other ways, using helper functions. In all cases, +#' cell range processing is handled by the \code{\link[=cellranger]{cellranger}} +#' package, where you can find full documentation for the functions used in the +#' examples below. +#' +#' @template cellranger +#' @name cell-specification +NULL + +#' @importFrom cellranger cell_limits +#' @name cell_limits +#' @export +#' @rdname cell-specification +NULL + +#' @importFrom cellranger cell_rows +#' @name cell_rows +#' @export +#' @rdname cell-specification +NULL + +#' @importFrom cellranger cell_cols +#' @name cell_cols +#' @export +#' @rdname cell-specification +NULL + +#' @importFrom cellranger anchored +#' @name anchored +#' @export +#' @rdname cell-specification +NULL + +## cell specification functions that are specific to googlesheets, i.e. stuff +## not handled in cellranger + +## basically boils down to: +## cell_limits object <--> limits in the list form I need for Sheets API query + +limit_list <- function(x) { + + stopifnot(inherits(x, "cell_limits") || is.null(x)) + + if(inherits(x, "cell_limits")) { + retval <- list(`min-row` = x$rows[1], `max-row` = x$rows[2], + `min-col` = x$cols[1], `max-col` = x$cols[2]) + retval[is.na(retval)] <- NULL + } + + if(length(retval)) { + retval + } else { + NULL + } + +} + +un_limit_list <- function(x) { + + cellranger::cell_limits(rows = c(x[['min-row']], x[['max-row']]), + cols = c(x[['min-col']], x[['max-col']])) + +} diff --git a/R/edit-data.R b/R/gs_edit_cells.R similarity index 66% rename from R/edit-data.R rename to R/gs_edit_cells.R index fdb736f..185e861 100644 --- a/R/edit-data.R +++ b/R/gs_edit_cells.R @@ -18,7 +18,7 @@ #' @param byrow logical; should we fill cells across a row (\code{byrow = #' TRUE}) or down a column (\code{byrow = FALSE}, default); consulted only #' when \code{input} is a vector, i.e. \code{dim(input)} is \code{NULL} -#' @param header logical; indicates whether column names of input should be +#' @param col_names logical; indicates whether column names of input should be #' included in the edit, i.e. prepended to the input; consulted only when #' \code{length(dim(input))} equals 2, i.e. \code{input} is a matrix or #' data.frame @@ -29,40 +29,56 @@ #' @examples #' \dontrun{ #' yo <- gs_new("yo") -#' yo <- edit_cells(yo, input = head(iris), header = TRUE, trim = TRUE) -#' get_via_csv(yo) +#' yo <- gs_edit_cells(yo, input = head(iris), trim = TRUE) +#' gs_read(yo) #' -#' yo <- gs_ws_new(yo, "byrow_FALSE") -#' yo <- edit_cells(yo, ws = "byrow_FALSE", LETTERS[1:5], "A8") -#' get_via_cf(yo, ws = "byrow_FALSE", min_row = 7) %>% simplify_cf() +#' yo <- gs_ws_new(yo, ws = "byrow_FALSE") +#' yo <- gs_edit_cells(yo, ws = "byrow_FALSE", +#' input = LETTERS[1:5], anchor = "A8") +#' gs_read_cellfeed(yo, ws = "byrow_FALSE", range = "A8:A12") %>% +#' gs_simplify_cellfeed() #' -#' yo <- gs_ws_new(yo, "byrow_TRUE") -#' yo <- edit_cells(yo, ws = "byrow_TRUE", LETTERS[1:5], "A8", byrow = TRUE) -#' get_via_cf(yo, ws = "byrow_TRUE", min_row = 7) %>% simplify_cf() +#' yo <- gs_ws_new(yo, ws = "byrow_TRUE") +#' yo <- gs_edit_cells(yo, ws = "byrow_TRUE", input = LETTERS[1:5], +#' anchor = "A8", byrow = TRUE) +#' gs_read_cellfeed(yo, ws = "byrow_TRUE", range = "A8:E8") %>% +#' gs_simplify_cellfeed() +#' +#' gs_delete(yo) #' } #' #' @export -edit_cells <- function(ss, ws = 1, input = '', anchor = 'A1', - byrow = FALSE, header = FALSE, trim = FALSE, - verbose = TRUE) { +gs_edit_cells <- function(ss, ws = 1, input = '', anchor = 'A1', + byrow = FALSE, col_names = NULL, trim = FALSE, + verbose = TRUE) { + + sleep <- 1 ## we must backoff or operations below don't complete before + ## next one starts; believe it or not, shorter sleeps cause + ## problems fairly regularly :( catch_hopeless_input(input) this_ws <- gs_ws(ss, ws, verbose = FALSE) - ## TO DO: I need to let the cellranger::anchored defaults rule the day here - ## (vs the ones above) - limits <- - cellranger::anchored(anchor, input = input, header = header, - byrow = byrow) %>% - limit_list() + ## I can edit/remove this once cellranger updates on CRAN and the default + ## behavior of anchored() is smarter + if(is.null(dim(input))) { + col_names <- FALSE + } else if(is.null(col_names)) { + col_names <- !is.null(colnames(input)) + } + + limits <- ## change header to col_names after cellranger updates + cellranger::anchored(anchor, input = input, header = col_names, + byrow = byrow) ## TO DO: if I were really nice, I would use the positioning notation from the ## user, i.e. learn it from anchor, instead of defaulting to A1 range <- limits %>% - un_limit_list() %>% cellranger::as.range() if(verbose) { message(sprintf("Range affected by the update: \"%s\"", range)) } + limits <- limits %>% + limit_list() if(limits$`max-row` > this_ws$row_extent || limits$`max-col` > this_ws$col_extent) { @@ -70,16 +86,17 @@ edit_cells <- function(ss, ws = 1, input = '', anchor = 'A1', gs_ws_resize(this_ws$ws_title, max(this_ws$row_extent, limits$`max-row`), max(this_ws$col_extent, limits$`max-col`), - verbose) - Sys.sleep(1) + verbose = verbose) + Sys.sleep(sleep) } - input <- input %>% as_character_vector(header = header) + input <- input %>% as_character_vector(col_names = col_names) cells_df <- ss %>% - get_via_cf(ws, limits = limits, - return_empty = TRUE, return_links = TRUE, verbose = FALSE) %>% + gs_read_cellfeed( + ws, range = range, return_empty = TRUE, + return_links = TRUE, verbose = FALSE) %>% dplyr::mutate_(update_value = ~ input) update_entries <- @@ -115,34 +132,35 @@ edit_cells <- function(ss, ws = 1, input = '', anchor = 'A1', req <- gsheets_POST(paste(this_ws$cellsfeed, "batch", sep = "/"), update_feed) - ## proactive check for successful update cell_status <- req$content %>% xml2::xml_find_all("atom:entry//batch:status", xml2::xml_ns(.)) %>% xml2::xml_attr("code") - if(all(cell_status == "200")) { - sprintf("Worksheet \"%s\" successfully updated with %d new value(s).", - this_ws$ws_title, length(input)) %>% message() - } else { - sprintf(paste("Problems updating cells in worksheet \"%s\".", - "Statuses returned:\n"), - this_ws$ws_title, - cell_status %>% - unique() %>% - paste(sep = ",")) %>% - message() + if(verbose) { + if(all(cell_status == "200")) { + sprintf("Worksheet \"%s\" successfully updated with %d new value(s).", + this_ws$ws_title, length(input)) %>% message() + } else { + sprintf(paste("Problems updating cells in worksheet \"%s\".", + "Statuses returned:\n%s"), + this_ws$ws_title, + paste(unique(cell_status), collapse = ",")) %>% + message() + } } - if(trim) { + if(trim && + (limits$`max-row` < this_ws$row_extent || + limits$`max-col` < this_ws$col_extent)) { - Sys.sleep(1) + Sys.sleep(sleep) ss <- ss %>% gs_ws_resize(this_ws$ws_title, limits$`max-row`, - limits$`max-col`, verbose = FALSE) + limits$`max-col`, verbose = verbose) } - Sys.sleep(1) + Sys.sleep(sleep) ss <- ss$sheet_key %>% gs_key(verbose = FALSE) invisible(ss) } @@ -163,8 +181,9 @@ catch_hopeless_input <- function(x) { ## deeply pragmatic function to turn input destined for upload into cells ## into a character vector -## header controls whether column names are prepended, when x has 2 dimensions -as_character_vector <- function(x, header = FALSE) { +## col_names controls whether column names are prepended, when x has 2 +## dimensions +as_character_vector <- function(x, col_names) { catch_hopeless_input(x) @@ -172,10 +191,10 @@ as_character_vector <- function(x, header = FALSE) { ## instead of fiddly tests on x (see comments below), just go with it, if x ## can be turned into a character vector - if(dim(x) %>% is.null()) { - y <- try(x %>% as.character(), silent = TRUE) + if(is.null(dim(x))) { + y <- try(as.character(x), silent = TRUE) } else if(length(dim(x)) == 2L) { - x_colnames <- x %>% colnames() + x_colnames <- colnames(x) y <- try(x %>% t() %>% as.character() %>% drop(), silent = TRUE) } @@ -183,7 +202,7 @@ as_character_vector <- function(x, header = FALSE) { stop("Input cannot be converted to character vector.") } - if(header) { + if(col_names) { y <- c(x_colnames, y) } diff --git a/R/gs_example_sheets.R b/R/gs_example-sheet-setup.R similarity index 100% rename from R/gs_example_sheets.R rename to R/gs_example-sheet-setup.R diff --git a/R/gs_new.R b/R/gs_new.R index 1d65836..09bd750 100644 --- a/R/gs_new.R +++ b/R/gs_new.R @@ -14,28 +14,28 @@ #' #' We anticipate that \strong{if} the user wants to control the extent of the #' new worksheet, it will be by providing input data and specifying `trim = -#' TRUE` (see \code{\link{edit_cells}}) or by specifying \code{row_extent} and -#' \code{col_extent} directly. But not both ... although we won't stop you. In -#' that case, note that explicit worksheet sizing occurs before data insertion. -#' If data insertion triggers any worksheet resizing, that will override any -#' usage of \code{row_extent} or \code{col_extent}. +#' TRUE` (see \code{\link{gs_edit_cells}}) or by specifying \code{row_extent} +#' and \code{col_extent} directly. But not both ... although we won't stop you. +#' In that case, note that explicit worksheet sizing occurs before data +#' insertion. If data insertion triggers any worksheet resizing, that will +#' override any usage of \code{row_extent} or \code{col_extent}. #' #' @param title the title for the new spreadsheet #' @param ws_title the title for the new, sole worksheet; if unspecified, the #' Google Sheets default is "Sheet1" #' @template row_extent #' @template col_extent -#' @param ... optional arguments passed along to \code{\link{edit_cells}} in +#' @param ... optional arguments passed along to \code{\link{gs_edit_cells}} in #' order to populate the new worksheet with data #' @template verbose #' #' @return a \code{\link{googlesheet}} object #' -#' @seealso \code{\link{edit_cells}} for specifics on populating the new sheet -#' with some data and \code{\link{gs_upload}} for creating a new spreadsheet -#' by uploading a local file. Note that \code{\link{gs_upload}} is likely much -#' faster than using \code{gs_new} and \code{\link{edit_cells}}, so try both -#' if speed is a concern. +#' @seealso \code{\link{gs_edit_cells}} for specifics on populating the new +#' sheet with some data and \code{\link{gs_upload}} for creating a new +#' spreadsheet by uploading a local file. Note that \code{\link{gs_upload}} is +#' likely much faster than using \code{\link{gs_new}} and/or +#' \code{\link{gs_edit_cells}}, so try both if speed is a concern. #' #' @examples #' \dontrun{ @@ -89,7 +89,7 @@ gs_new <- function(title = "my_sheet", ws_title = NULL, gs_ws_modify(from = 1, to = ws_title, new_dim = c(row_extent = row_extent, col_extent = col_extent), verbose = FALSE) - if(verbose) { + if(verbose && !is.null(ws_title)) { if(ws_title %in% ss$ws$ws_title) { sprintf("Worksheet \"%s\" renamed to \"%s\".", "Sheet1", ws_title) %>% message() @@ -103,9 +103,10 @@ gs_new <- function(title = "my_sheet", ws_title = NULL, dotdotdot <- list(...) if(length(dotdotdot)) { - edit_cells_arg_list <- c(list(ss = ss), dotdotdot, list(verbose = verbose)) - #print(edit_cells_arg_list) - ss <- do.call(edit_cells, edit_cells_arg_list) + gs_edit_cells_arg_list <- + c(list(ss = ss), dotdotdot, list(verbose = verbose)) + #print(gs_edit_cells_arg_list) + ss <- do.call(gs_edit_cells, gs_edit_cells_arg_list) } if(verbose) { diff --git a/R/gs_read.R b/R/gs_read.R new file mode 100644 index 0000000..5190568 --- /dev/null +++ b/R/gs_read.R @@ -0,0 +1,66 @@ +#' Read data +#' +#' This function reads data from a worksheet and returns it as a \code{tbl_df} +#' or \code{data.frame}. It wraps up the most common usage of other, lower-level +#' functions for data consumption and transformation, but you can call always +#' call them directly for finer control. +#' +#' If the \code{range} argument is not specified, all data will be read via +#' \code{\link{gs_read_csv}}. In this case, you can pass additional arguments to +#' the csv parser via \code{...}; see \code{\link{gs_read_cellfeed}} for more +#' details. Don't worry -- no intermediate \code{*.csv} files were written in +#' the reading of your data! We just request the data from the Sheets API via +#' the \code{exportcsv} link. +#' +#' If the \code{range} argument is specified, data will be read for the +#' targetted cells via \code{\link{gs_read_cellfeed}}, then reshaped with +#' \code{\link{gs_reshape_cellfeed}}. In this case, you can pass additional +#' arguments to \code{\link{gs_reshape_cellfeed}} via \code{...}. +#' +#' @template ss +#' @template ws +#' @param range blah blah +#' @param ... optional arguments passed on to functions that control reading and +#' transforming the data +#' @template verbose +#' +#' @return a tbl_df +#' +#' @family data consumption functions +#' +#' @seealso The \code{\link{cell-specification}} topic for more about targetting +#' specific cells. +#' +#' @examples +#' \dontrun{ +#' gap_ss <- gs_gap() +#' oceania_csv <- gs_read(gap_ss, ws = "Oceania") +#' str(oceania_csv) +#' oceania_csv +#' +#' gs_read(gap_ss, ws = "Oceania", range = "A1:C4") +#' gs_read(gap_ss, ws = "Oceania", range = "R1C1:R4C3") +#' gs_read(gap_ss, ws = "Oceania", range = "R2C1:R4C3", col_names = FALSE) +#' gs_read(gap_ss, ws = "Oceania", range = "R2C5:R4C6", +#' col_names = c("thing_one", "thing_two")) +#' gs_read(gap_ss, ws = "Oceania", range = cell_limits(c(1, 4), c(1, 3))) +#' gs_read(gap_ss, ws = "Oceania", range = cell_rows(1:5)) +#' gs_read(gap_ss, ws = "Oceania", range = cell_cols(4:6)) +#' gs_read(gap_ss, ws = "Oceania", range = cell_cols("A:D")) +#' gs_read(gap_ss, ws = "Oceania", range = cell_rows(1), col_names = FALSE) +#' } +#' +#' @export +gs_read <- function( + ss, ws = 1, + range = NULL, + ..., verbose = TRUE) { + + if(is.null(range)) { + gs_read_csv(ss, ws = ws, ..., verbose = verbose) + } else { + gs_read_cellfeed(ss, ws = ws, range = range, verbose = verbose) %>% + gs_reshape_cellfeed(..., verbose = verbose) + } + +} diff --git a/R/gs_read_cellfeed.R b/R/gs_read_cellfeed.R new file mode 100644 index 0000000..387e4ef --- /dev/null +++ b/R/gs_read_cellfeed.R @@ -0,0 +1,176 @@ +#' Read data from cells +#' +#' This function consumes data via the "cell feed", which, as the name suggests, +#' retrieves data cell by cell. Note that the output is a \code{tbl_df} or +#' \code{data.frame} with \strong{one row per cell}. +#' +#' Use the \code{range} argument to specify which cells you want to read. See +#' the examples and the help file for the \link[=cell-specification]{cell +#' specification functions} for various ways to limit consumption to, e.g., a +#' rectangle or certain columns. If \code{range} is specified, the associated +#' cell limits will be checked for internal consistency and compliance with the +#' known extent of the worksheet. If no limits are provided, all cells will be +#' returned but consider that \code{\link{gs_read_csv}} and +#' \code{\link{gs_read_listfeed}} are much faster ways to consume all the data +#' from a rectangular worksheet. +#' +#' Empty cells, even if "embedded" in a rectangular region of populated cells, +#' are not normally returned by the cell feed. This function won't return them +#' either when \code{return_empty = FALSE} (default), but will if you set +#' \code{return_empty = TRUE}. If you don't specify any limits AND you set +#' \code{return_empty = TRUE}, you could be in for a bit of a wait, as the feed +#' will return all cells, which defaults to 1000 rows and 26 columns. +#' +#' @template ss +#' @template ws +#' @param range blah blah +#' @param return_empty logical; indicates whether to return empty cells +#' @param return_links logical; indicates whether to return the edit and self +#' links (used internally in cell editing workflow) +#' @template verbose +#' +#' @seealso \code{\link{gs_reshape_cellfeed}} or +#' \code{\link{gs_simplify_cellfeed}} to perform reshaping or simplification, +#' respectively; \code{\link{gs_read}} is a pre-made wrapper that combines +#' \code{gs_read_cellfeed} and \code{\link{gs_reshape_cellfeed}} +#' +#' @examples +#' \dontrun{ +#' gap_ss <- gs_gap() # register the Gapminder example sheet +#' first_4_rows <- +#' gs_read_cellfeed(gap_ss, "Asia", range = cell_limits(c(NA, 4))) +#' first_4_rows +#' gs_reshape_cellfeed(first_4_rows) +#' gs_reshape_cellfeed(gs_read_cellfeed(gap_ss, "Asia", +#' range = cell_limits(c(NA, 4), c(3, NA)))) +#' } +#' @family data consumption functions +#' +#' @export +gs_read_cellfeed <- function( + ss, ws = 1, range = NULL, + return_empty = FALSE, return_links = FALSE, + verbose = TRUE) { + + stopifnot(inherits(ss, "googlesheet")) + this_ws <- gs_ws(ss, ws, verbose) + + if(is.null(range)) { + ## once cellranger updates on CRAN, can remove this + ## because as.cell_limits() will natively do the right thing for NULL + ## https://github.com/jennybc/cellranger/commit/c9c9080ca0fdf1db97c8ea935f00c6bcc10d6e0b + range <- cell_limits() + } + limits <- range %>% + cellranger::as.cell_limits() %>% + limit_list() + limits <- limits %>% + validate_limits(this_ws$row_extent, this_ws$col_extent) + + query <- limits + if(return_empty) { + ## the return-empty parameter is not documented in current sheets API, but + ## is discussed in older internet threads re: the older gdata API; so if + ## this stops working, consider that they finally stopped supporting this + ## query parameter + query <- query %>% c(list("return-empty" = "true")) + } + + ## to prevent appending of "?=" to url when query elements are all NULL + if(query %>% unlist() %>% is.null()) { + query <- NULL + ## I think this can be eliminated upon next CRAN release of httr + ## https://github.com/hadley/httr/commit/6d06ad571316dcba5944a5e545c374b64d6979d6 + } + + req <- gsheets_GET(this_ws$cellsfeed, query = query) + + ns <- xml2::xml_ns_rename(xml2::xml_ns(req$content), d1 = "feed") + + x <- req$content %>% + xml2::xml_find_all("feed:entry", ns) + + if(length(x) == 0L) { + # the pros outweighed the cons re: setting up a zero row data.frame that, + # at least, has the correct variables + x <- dplyr::data_frame(cell = character(), + cell_alt = character(), + row = integer(), + col = integer(), + cell_text = character(), + edit_link = character(), + cell_id = character()) + } else { + edit_links <- x %>% + xml2::xml_find_all(".//feed:link[@rel='edit']", ns) %>% + xml2::xml_attr("href") + + ## this will be true if user does not have permission to edit + if(length(edit_links) == 0) { + edit_links <- NA_character_ + } + + x <- dplyr::data_frame_( + list(cell = ~ xml2::xml_find_all(x, ".//feed:title", ns) %>% + xml2::xml_text(), + edit_link = ~ edit_links, + cell_id = ~ xml2::xml_find_all(x, ".//feed:id", ns) %>% + xml2::xml_text(), + cell_alt = ~ cell_id %>% basename(), + row = ~ xml2::xml_find_all(x, ".//gs:cell", ns) %>% + xml2::xml_attr("row") %>% + as.integer(), + col = ~ xml2::xml_find_all(x, ".//gs:cell", ns) %>% + xml2::xml_attr("col") %>% + as.integer(), + cell_text = ~ xml2::xml_find_all(x, ".//gs:cell", ns) %>% + xml2::xml_text() + )) + # see issue #19 about all the places cell data is (mostly redundantly) + # stored in the XML, such as: content_text = x$content$text, + # cell_inputValue = x$cell$.attrs["inputValue"], cell_numericValue = + # x$cell$.attrs["numericValue"], when/if we think about formulas + # explicitly, we will want to come back and distinguish between inputValue + # and numericValue + } + + x <- x %>% + dplyr::select_(~ cell, ~ cell_alt, ~ row, ~ col, ~ cell_text, + ~ edit_link, ~ cell_id) %>% + dplyr::as_data_frame() + + attr(x, "ws_title") <- this_ws$ws_title + + if(return_links) { + x + } else { + x %>% + dplyr::select_(~ -edit_link, ~ -cell_id) + } + +} + +validate_limits <- function( + limits, ws_row_extent = NULL, ws_col_extent = NULL) { + + if(is.null(limits)) return(NULL) + + ## min and max must be <= nominal worksheet extent + jfun <- function(x, upper_bound) { + x_name <- deparse(substitute(x)) + ub_name <- deparse(substitute(upper_bound)) + if(!is.null(x) && !is.null(upper_bound) && x > upper_bound) { + mess <- + sprintf("%s must be less than or equal to %s\n%s = %d, %s = %d\n", + x_name, ub_name, x_name, x, ub_name, upper_bound) + stop(mess) + } + } + jfun(limits[["min-row"]], ws_row_extent) + jfun(limits[["max-row"]], ws_row_extent) + jfun(limits[["min-col"]], ws_col_extent) + jfun(limits[["max-col"]], ws_col_extent) + + limits + +} diff --git a/R/gs_read_csv.R b/R/gs_read_csv.R new file mode 100644 index 0000000..4ff89e5 --- /dev/null +++ b/R/gs_read_csv.R @@ -0,0 +1,95 @@ +#' Read data via the \code{exportcsv} link +#' +#' This function reads all data from a worksheet and returns it as a +#' \code{tbl_df} or \code{data.frame}. Don't be spooked by the "csv" thing -- +#' the data is NOT actually written to file during this process. Data is read +#' from the "maximal data rectangle", i.e. the rectangle spanned by the maximal +#' row and column extent of the data. Empty cells within this rectangle will be +#' assigned NA. This is the fastest method of data consumption, so use it as +#' long as you can tolerate the lack of control re: which cells are being read. +#' +#' How does this compare to consumption via the list feed, implemented by +#' \code{\link{gs_read_listfeed}}? First, \code{gs_read_csv} is much, much +#' faster. Second, the first row, potentially containing column or variable +#' names, is NOT transformed/mangled, as it is via the list feed. Finally, +#' consumption via the \code{exportcsv} link is more tolerant of data that does +#' not form a perfect, neat rectangle, e.g. the read does NOT stop upon +#' encountering an empty row. +#' +#' @template ss +#' @template ws +#' @param ... Further arguments to be passed to the csv parser. This is +#' currently \code{\link{read.csv}}, but expect a switch to +#' \code{readr::read_csv} in the not-too-distant future! Note that by default +#' \code{\link{read.csv}} is called with \code{stringsAsFactors = FALSE}. +#' @template verbose +#' +#' @family data consumption functions +#' +#' @return a tbl_df +#' +#' @examples +#' \dontrun{ +#' gap_ss <- gs_gap() # register the Gapminder example sheet +#' oceania_csv <- gs_read_csv(gap_ss, ws = "Oceania") +#' str(oceania_csv) +#' oceania_csv +#' } +#' @export +gs_read_csv <- function(ss, ws = 1, ..., verbose = TRUE) { + + stopifnot(inherits(ss, "googlesheet")) + + this_ws <- gs_ws(ss, ws, verbose) + + if(is.null(this_ws$exportcsv)) { + stop(paste("This appears to be an \"old\" Google Sheet. The old Sheets do", + "not offer the API access required by this function.", + "Consider converting it from an old Sheet to a new Sheet.", + "Or use another data consumption function, such as", + "gs_read_listfeed() or gs_read_cellfeed(). Or use gs_download()", + "to export it to a local file and then read it into R.")) + } + + req <- gsheets_GET(this_ws$exportcsv, to_xml = FALSE, + use_auth = !ss$is_public) + + if(req$headers$`content-type` != "text/csv") { + stop1 <- "Cannot access this sheet via csv." + stop2 <- "Are you sure you have permission to access this Sheet?" + stop3 <- paste("If this Sheet is supposed to be public, make sure it is", + "\"published to the web\", which is NOT the same as", + "\"public on the web\".") + stop4 <- sprintf("status_code: %s", req$status_code) + stop5 <- sprintf("content-type: %s", req$headers$`content-type`) + stop(paste(stop1, stop2, stop3, stop4, stop5, sep = "\n")) + } + + if(is.null(req$content) || length(req$content) == 0L) { + message(sprintf("Worksheet \"%s\" is empty.", this_ws$ws_title)) + dplyr::data_frame() + } else { + ## numeric columns have an NA for empty cells + ## character columns have "" for empty cells + ## hence the value of na.strings + req %>% + httr::content(type = "text/csv", na.strings = c("", "NA"), + encoding = "UTF-8", ...) %>% + dplyr::as_data_frame() + ## in future, I'm interested in using readr::read_csv(), either directly + ## or indirectly, if httr make it the parser when MIME type is text/csv + ## won't do it know because doesn't support vector valued na.strings, + ## comment.char, etc. + ## track progress on these issues: + ## https://github.com/hadley/readr/issues/167 + ## https://github.com/hadley/readr/issues/114 + ## https://github.com/hadley/readr/issues/125 + ## parsing content "by hand" with readr_csv() might look like so: + ## req %>% + ## httr::content(type = "text", encoding = "UTF-8") %>% + ## readr::read_csv() + } + +} + + diff --git a/R/gs_read_listfeed.R b/R/gs_read_listfeed.R new file mode 100644 index 0000000..92c8464 --- /dev/null +++ b/R/gs_read_listfeed.R @@ -0,0 +1,76 @@ +#' Read data via the "list feed" +#' +#' Gets data via the "list feed", which assumes populated cells form a neat +#' rectangle. The list feed consumes data row by row. The first row is assumed +#' to hold variable or column names. The related function, +#' \code{\link{gs_read_csv}}, also returns data from a rectangle of cells, +#' but it is generally faster and more resilient to, e.g. empty rows, so use it +#' if you can. However, you may need to use this function if you are dealing +#' with an "old" Google Sheet, which \code{\link{gs_read_csv}} does not +#' support). Consult the Google Sheets API documentation for more details about +#' \href{https://developers.google.com/google-apps/spreadsheets/data#work_with_list-based_feeds}{the +#' "list feed"}. +#' +#' @template ss +#' @template ws +#' @template verbose +#' +#' @note When you use the "list feed", the Sheets API transforms the variable or +#' column names like so: 'The column names are the header values of the +#' worksheet lowercased and with all non-alpha-numeric characters removed. For +#' example, if the cell A1 contains the value "Time 2 Eat!" the column name +#' would be "time2eat".' If this is intolerable to you, use a different +#' function to read the data. Or, at least, consume the first row via the cell +#' feed and manually restore the variable names \emph{post hoc}. +#' +#' @family data consumption functions +#' +#' @return a tbl_df +#' +#' @examples +#' \dontrun{ +#' gap_ss <- gs_gap() # register the Gapminder example sheet +#' oceania_lf <- gs_read_listfeed(gap_ss, ws = "Oceania") +#' str(oceania_lf) +#' oceania_lf +#' } +#' +#' @export +gs_read_listfeed <- function(ss, ws = 1, verbose = TRUE) { + + stopifnot(inherits(ss, "googlesheet")) + + this_ws <- gs_ws(ss, ws, verbose) + req <- gsheets_GET(this_ws$listfeed) + + ns <- xml2::xml_ns_rename(xml2::xml_ns(req$content), d1 = "feed") + + var_names <- req$content %>% + xml2::xml_find_all("(//feed:entry)[1]", ns) %>% + xml2::xml_find_all(".//gsx:*", ns) %>% + xml2::xml_name() + + values <- req$content %>% + xml2::xml_find_all("//feed:entry//gsx:*", ns) %>% + xml2::xml_text() + + dat <- matrix(values, ncol = length(var_names), byrow = TRUE, + dimnames = list(NULL, var_names)) %>% + ## convert to integer, numeric, etc. but w/ stringsAsFactors = FALSE + ## empty cells returned as empty string "" + plyr::alply(2, type.convert, na.strings = c("NA", ""), as.is = TRUE) %>% + ## get rid of attributes that are non-standard for tbl_dfs or data.frames + ## and that are an artefact of the above (specifically, I think, the use of + ## alply?); if I don't do this, the output is fugly when you str() it + `attr<-`("split_type", NULL) %>% + `attr<-`("split_labels", NULL) %>% + `attr<-`("dim", NULL) %>% + ## for some reason removing the non-standard dim attributes clobbers the + ## variable names, so those must be restored + `names<-`(var_names) %>% + ## convert to data.frame (tbl_df, actually) + dplyr::as_data_frame() + + dat + +} diff --git a/R/gs_reshape_cellfeed.R b/R/gs_reshape_cellfeed.R new file mode 100644 index 0000000..a65ad46 --- /dev/null +++ b/R/gs_reshape_cellfeed.R @@ -0,0 +1,76 @@ +#' Reshape data from the "cell feed" +#' +#' Reshape data from the "cell feed" and convert to a \code{tbl_df} +#' +#' @param x a data.frame returned by \code{\link{gs_read_cellfeed}} +#' @param col_names if \code{TRUE}, the first row of the input will be used as +#' the column names; if \code{FALSE}, column names will be X1, X2, etc.; if a +#' character vector, vector will be used as the column names +#' @template verbose +#' +#' @family data consumption functions +#' +#' @examples +#' \dontrun{ +#' gap_ss <- gs_gap() # register the Gapminder example sheet +#' gs_read_cellfeed(gap_ss, "Asia", range = cell_rows(1:4)) +#' gs_reshape_cellfeed(gs_read_cellfeed(gap_ss, "Asia", range = cell_rows(1:4))) +#' gs_reshape_cellfeed(gs_read_cellfeed(gap_ss, "Asia", +#' range = cell_rows(2:4)), +#' col_names = FALSE) +#' gs_reshape_cellfeed(gs_read_cellfeed(gap_ss, "Asia", +#' range = cell_rows(2:4)), +#' col_names = paste0("yo", 1:6)) +#' } +#' @export +gs_reshape_cellfeed <- function(x, col_names = TRUE, verbose = TRUE) { + + limits <- x %>% + dplyr::summarise_each_(dplyr::funs(min, max), list(~ row, ~ col)) + all_possible_cells <- + with(limits, + expand.grid(row = row_min:row_max, col = col_min:col_max)) %>% + dplyr::as.tbl() + suppressMessages( + x_augmented <- all_possible_cells %>% dplyr::left_join(x) + ) + ## tidyr::spread(), used below, could do something similar as this join, but + ## it would handle completely missing rows and columns differently; still + ## thinking about this + + if(is.character(col_names)) { + n_cols <- dplyr::n_distinct(x_augmented$col) + stopifnot(length(col_names) == n_cols) + var_names <- col_names + } else { + stopifnot(identical(col_names, TRUE) || identical(col_names, FALSE)) + if(col_names) { + row_one <- x_augmented %>% + dplyr::filter_(~ (row == min(row))) %>% + dplyr::mutate_(cell_text = ~ ifelse(cell_text == "", NA, cell_text)) + var_names <- ifelse(is.na(row_one$cell_text), + stringr::str_c("X", row_one$col), + row_one$cell_text) %>% make.names() + x_augmented <- x_augmented %>% + dplyr::filter_(~ row > min(row)) + } else { + var_names <- limits$col_min:limits$col_max %>% make.names() + } + } + + if(x_augmented$row %>% dplyr::n_distinct() < 1) { + if(verbose) { + message("No data to reshape!") + if(isTRUE(col_names)) { + message("Perhaps retry with `col_names = FALSE`?") + } + } + return(dplyr::data_frame() %>% dplyr::as.tbl()) + } + + x_augmented %>% + dplyr::select_(~ row, ~ col, ~ cell_text) %>% + tidyr::spread_("col", "cell_text", convert = TRUE) %>% + dplyr::select_(~ -row) %>% + stats::setNames(var_names) +} diff --git a/R/gs_simplify_cellfeed.R b/R/gs_simplify_cellfeed.R new file mode 100644 index 0000000..c8385b8 --- /dev/null +++ b/R/gs_simplify_cellfeed.R @@ -0,0 +1,81 @@ +#' Simplify data from the cell feed +#' +#' In some cases, you do not want to convert the data retrieved from the cell +#' feed into a data.frame via \code{\link{gs_reshape_cellfeed}}. Instead, you +#' want the data as an atomic vector. That's what this function does. Note that, +#' unlike \code{\link{gs_reshape_cellfeed}}, embedded empty cells will NOT +#' necessarily appear in this result. By default, the API does not transmit data +#' for these cells; \code{googlesheets} inserts these cells in +#' \code{\link{gs_reshape_cellfeed}} because it is necessary to give the data +#' rectangular shape. In contrast, empty cells will only appear in the output of +#' \code{gs_simplify_cellfeed} if they were already present in the data from the +#' cell feed, i.e. if the original call to \code{\link{gs_read_cellfeed}} had +#' argument \code{return_empty} set to \code{TRUE}. +#' +#' @param x a data.frame returned by \code{\link{gs_read_cellfeed}} +#' @param convert logical, indicating whether to attempt to convert the result +#' vector from character to something more appropriate, such as logical, +#' integer, or numeric; if TRUE, result is passed through \code{type.convert}; +#' if FALSE, result will be character +#' @param as.is logical, passed through to the \code{as.is} argument of +#' \code{type.convert} +#' @param na.strings a character vector of strings which are to be interpreted +#' as \code{NA} values +#' @param notation character; the result vector can have names that reflect +#' which cell the data came from; this argument selects between the "A1" and +#' "R1C1" positioning notations; specify "none" to suppress names +#' @param col_names blah blah +#' +#' @return a named vector +#' +#' @examples +#' \dontrun{ +#' gap_ss <- gs_gap() # register the Gapminder example sheet +#' gs_read_cellfeed(gap_ss, range = cell_rows(1)) +#' gs_simplify_cellfeed(gs_read_cellfeed(gap_ss, range = cell_rows(1))) +#' gs_simplify_cellfeed( +#' gs_read_cellfeed(gap_ss, range = cell_rows(1)), notation = "R1C1") +#' +#' gs_read_cellfeed(gap_ss, range = "A1:A10") +#' gs_simplify_cellfeed(gs_read_cellfeed(gap_ss, range = "A1:A10")) +#' gs_simplify_cellfeed(gs_read_cellfeed(gap_ss, range = "A1:A10"), +#' col_names = FALSE) +#' } +#' +#' @family data consumption functions +#' +#' @export +gs_simplify_cellfeed <- function( + x, convert = TRUE, as.is = TRUE, na.strings = "NA", + notation = c("A1", "R1C1", "none"), col_names = NULL) { + + notation <- match.arg(notation) + + if(is.null(col_names) && + min(x$row) == 1 && + max(x$row) > 1 && + dplyr::n_distinct(x$col) == 1) { + col_names <- TRUE + } else { + col_names <- FALSE + } + stopifnot(identical(col_names, TRUE) || identical(col_names, FALSE)) + + if(col_names) { + x <- x %>% + dplyr::filter_(~ row > min(row)) + } + + y <- x$cell_text + y[match(na.strings, y)] <- NA_character_ + if(notation != "none") { + names(y) <- switch(notation, + A1 = x$cell, + R1C1 = x$cell_alt) + } + if(convert) { + y %>% type.convert(as.is = as.is) + } else { + y + } +} diff --git a/R/gs_upload.R b/R/gs_upload.R index 84f55f1..111fcee 100644 --- a/R/gs_upload.R +++ b/R/gs_upload.R @@ -16,7 +16,7 @@ #' write.csv(head(iris, 5), "iris.csv", row.names = FALSE) #' iris_ss <- gs_upload("iris.csv") #' iris_ss -#' get_via_lf(iris_ss) +#' gs_read_listfeed(iris_ss) #' file.remove("iris.csv") #' gs_delete(iris_ss) #' } diff --git a/R/gs_ws.R b/R/gs_ws.R index 893ffc4..24a3e95 100644 --- a/R/gs_ws.R +++ b/R/gs_ws.R @@ -14,11 +14,11 @@ #' #' We anticipate that \strong{if} the user wants to control the extent of the #' new worksheet, it will be by providing input data and specifying `trim = -#' TRUE` (see \code{\link{edit_cells}}) or by specifying \code{row_extent} and -#' \code{col_extent} directly. But not both ... although we won't stop you. In -#' that case, note that explicit worksheet sizing occurs before data insertion. -#' If data insertion triggers any worksheet resizing, that will override any -#' usage of \code{row_extent} or \code{col_extent}. +#' TRUE` (see \code{\link{gs_edit_cells}}) or by specifying \code{row_extent} +#' and \code{col_extent} directly. But not both ... although we won't stop you. +#' In that case, note that explicit worksheet sizing occurs before data +#' insertion. If data insertion triggers any worksheet resizing, that will +#' override any usage of \code{row_extent} or \code{col_extent}. #' #' @template ss #' @inheritParams gs_new @@ -84,10 +84,10 @@ gs_ws_new <- function(ss, ws_title = "Sheet1", dotdotdot <- list(...) if(length(dotdotdot)) { - edit_cells_arg_list <- + gs_edit_cells_arg_list <- c(list(ss = ss), list(ws = this_ws$ws_title), dotdotdot, list(verbose = verbose)) - ss <- do.call(edit_cells, edit_cells_arg_list) + ss <- do.call(gs_edit_cells, gs_edit_cells_arg_list) } if(verbose) { @@ -115,8 +115,8 @@ gs_ws_new <- function(ss, ws_title = "Sheet1", #' gap_ss <- gs_copy(gs_gap(), to = "gap_copy") #' gs_ws_ls(gap_ss) #' gap_ss <- gs_ws_new(gap_ss, "new_stuff") -#' gap_ss <- edit_cells(gap_ss, "new_stuff", input = head(iris), header = TRUE, -#' trim = TRUE) +#' gap_ss <- gs_edit_cells(gap_ss, "new_stuff", input = head(iris), +#' header = TRUE, trim = TRUE) #' gap_ss #' gap_ss <- gs_ws_delete(gap_ss, "new_stuff") #' gs_ws_ls(gap_ss) @@ -234,14 +234,14 @@ gs_ws_rename <- function(ss, from = 1, to, verbose = TRUE) { #' @examples #' \dontrun{ #' yo <- gs_new("yo") -#' yo <- edit_cells(yo, input = head(iris), header = TRUE, trim = TRUE) -#' get_via_csv(yo) +#' yo <- gs_edit_cells(yo, input = head(iris), header = TRUE, trim = TRUE) +#' gs_read_csv(yo) #' yo <- gs_ws_resize(yo, ws = "Sheet1", row_extent = 5, col_extent = 4) -#' get_via_csv(yo) +#' gs_read_csv(yo) #' yo <- gs_ws_resize(yo, ws = 1, row_extent = 3, col_extent = 3) -#' get_via_csv(yo) +#' gs_read_csv(yo) #' yo <- gs_ws_resize(yo, row_extent = 2, col_extent = 2) -#' get_via_csv(yo) +#' gs_read_csv(yo) #' gs_delete(yo) #' } #' diff --git a/README.Rmd b/README.Rmd index 8be5e0b..22dbe7b 100644 --- a/README.Rmd +++ b/README.Rmd @@ -149,67 +149,67 @@ The registration functions `gs_title()`, `gs_key()`, and `gs_url()` return a reg There are three ways to consume data from a worksheet within a Google spreadsheet. The order goes from fastest-but-more-limited to slowest-but-most-flexible: - * `get_via_csv()`: Don't let the name scare you! Nothing is written to file during this process. The name just reflects that, under the hood, we request the data via the "exportcsv" link. For cases where `get_via_csv()` and `get_via_lf()` both work, we see that `get_via_csv()` is around __50 times faster__. Use this when your data occupies a nice rectangle in the sheet and you're willing to consume all of it. You will get a `tbl_df` back, which is basically just a `data.frame`. - * `get_via_lf()`: Gets data via the ["list feed"](https://developers.google.com/google-apps/spreadsheets/#working_with_list-based_feeds), which consumes data row-by-row. Like `get_via_csv()`, this is appropriate when your data occupies a nice rectangle. You will again get a `tbl_df` back, but your variable names may have been mangled (by Google, not us!). Specifically, variable names will be forcefully lowercased and all non-alpha-numeric characters will be removed. Why do we even have this function? The list feed supports some query parameters for sorting and filtering the data, which we plan to support (#17). - * `get_via_cf()`: Get data via the ["cell feed"](https://developers.google.com/google-apps/spreadsheets/#working_with_cell-based_feeds), which consumes data cell-by-cell. This is appropriate when you want to consume arbitrary cells, rows, columns, and regions of the sheet. It works great for small amounts of data but can be rather slow otherwise. `get_via_cf()` returns a `tbl_df` with __one row per cell__. You can specify cell limits directly in `get_via_cf()` or use convenience wrappers `get_row()`, `get_col()` or `get_cells()` for some common special cases. See below for demos of `reshape_cf()` and `simplify_cf()` which help with post-processing. + * `gs_read_csv()`: Don't let the name scare you! Nothing is written to file during this process. The name just reflects that, under the hood, we request the data via the "exportcsv" link. For cases where `gs_read_csv()` and `gs_read_listfeed()` both work, we see that `gs_read_csv()` is around __50 times faster__. Use this when your data occupies a nice rectangle in the sheet and you're willing to consume all of it. You will get a `tbl_df` back, which is basically just a `data.frame`. + * `gs_read_listfeed()`: Gets data via the ["list feed"](https://developers.google.com/google-apps/spreadsheets/#working_with_list-based_feeds), which consumes data row-by-row. Like `gs_read_csv()`, this is appropriate when your data occupies a nice rectangle. You will again get a `tbl_df` back, but your variable names may have been mangled (by Google, not us!). Specifically, variable names will be forcefully lowercased and all non-alpha-numeric characters will be removed. Why do we even have this function? The list feed supports some query parameters for sorting and filtering the data, which we plan to support (#17). + * `gs_read_cellfeed()`: Get data via the ["cell feed"](https://developers.google.com/google-apps/spreadsheets/#working_with_cell-based_feeds), which consumes data cell-by-cell. This is appropriate when you want to consume arbitrary cells, rows, columns, and regions of the sheet. It works great for small amounts of data but can be rather slow otherwise. `gs_read_cellfeed()` returns a `tbl_df` with __one row per cell__. You can specify cell limits in `gs_read_cellfeed()` via the `range` argument. See below for demos of `gs_reshape_cellfeed()` and `gs_simplify_cellfeed()` which help with post-processing. ```{r csv-list-and-cell-feed} # Get the data for worksheet "Oceania": the super-fast csv way -oceania_csv <- gap %>% get_via_csv(ws = "Oceania") +oceania_csv <- gap %>% gs_read_csv(ws = "Oceania") str(oceania_csv) oceania_csv # Get the data for worksheet "Oceania": the fast tabular way ("list feed") -oceania_list_feed <- gap %>% get_via_lf(ws = "Oceania") +oceania_list_feed <- gap %>% gs_read_listfeed(ws = "Oceania") str(oceania_list_feed) oceania_list_feed # Get the data for worksheet "Oceania": the slower cell-by-cell way ("cell feed") -oceania_cell_feed <- gap %>% get_via_cf(ws = "Oceania") +oceania_cell_feed <- gap %>% gs_read_cellfeed(ws = "Oceania") str(oceania_cell_feed) -head(oceania_cell_feed, 10) +oceania_cell_feed ``` #### Convenience wrappers and post-processing the data -There are a few ways to limit the data you're consuming. You can put direct limits into `get_via_cf()`, but there are also convenience functions to get a row (`get_row()`), a column (`get_col()`), or a range (`get_cells()`). Also, when you consume data via the cell feed (which these wrappers are doing under the hood), you will often want to reshape it or simplify it (`reshape_cf()` and `simplify_cf()`). +There are a few ways to limit the data you're consuming. You can put direct limits into `gs_read_cellfeed()`, ~~but there are also convenience functions to get a row (`get_row()`), a column (`get_col()`), or a range (`get_cells()`)~~. Also, when you consume data via the cell feed (which these wrappers are doing under the hood), you will often want to reshape it or simplify it (`gs_reshape_cellfeed()` and `gs_simplify_cellfeed()`). ```{r wrappers-and-post-processing} # Reshape: instead of one row per cell, make a nice rectangular data.frame -oceania_reshaped <- oceania_cell_feed %>% reshape_cf() +oceania_reshaped <- oceania_cell_feed %>% gs_reshape_cellfeed() str(oceania_reshaped) -head(oceania_reshaped, 10) +oceania_reshaped # Limit data retrieval to certain cells # Example: first 3 rows -gap_3rows <- gap %>% get_row("Europe", row = 1:3) +gap_3rows <- gap %>% gs_read_cellfeed("Europe", range = cell_rows(1:3)) gap_3rows %>% head() # convert to a data.frame (first row treated as header by default) -gap_3rows %>% reshape_cf() +gap_3rows %>% gs_reshape_cellfeed() # Example: first row only -gap_1row <- gap %>% get_row("Europe", row = 1) +gap_1row <- gap %>% gs_read_cellfeed("Europe", range = cell_rows(1)) gap_1row # convert to a named character vector -gap_1row %>% simplify_cf() +gap_1row %>% gs_simplify_cellfeed() # just 2 columns, converted to data.frame gap %>% - get_col("Oceania", col = 3:4) %>% - reshape_cf() + gs_read_cellfeed("Oceania", range = cell_cols(3:4)) %>% + gs_reshape_cellfeed() # arbitrary cell range gap %>% - get_cells("Oceania", range = "D12:F15") %>% - reshape_cf(header = FALSE) + gs_read_cellfeed("Oceania", range = "D12:F15") %>% + gs_reshape_cellfeed(col_names = FALSE) # arbitrary cell range, alternative specification gap %>% - get_via_cf("Oceania", max_row = 5, min_col = 1, max_col = 3) %>% - reshape_cf() + gs_read_cellfeed("Oceania", range = cell_limits(c(NA, 5), c(1, 3))) %>% + gs_reshape_cellfeed() ``` ### Create sheets @@ -225,21 +225,21 @@ By default, there will be an empty worksheet called "Sheet1", but you can contro ### Edit cells -You can modify the data in sheet cells via `edit_cells()`. We'll work on the completely empty sheet created above, `foo`. If your edit populates the sheet with everything it should have, set `trim = TRUE` and we will resize the sheet to match the data. Then the nominal worksheet extent is much more informative (vs. the default of 1000 rows and 26 columns) and any future consumption via the cell feed will be much faster. +You can modify the data in sheet cells via `gs_edit_cells()`. We'll work on the completely empty sheet created above, `foo`. If your edit populates the sheet with everything it should have, set `trim = TRUE` and we will resize the sheet to match the data. Then the nominal worksheet extent is much more informative (vs. the default of 1000 rows and 26 columns) and any future consumption via the cell feed will be much faster. ```{r edit-cells} -foo <- foo %>% edit_cells(input = head(iris), header = TRUE, trim = TRUE) +foo <- foo %>% gs_edit_cells(input = head(iris), trim = TRUE) ``` Go to [your Google Sheets home screen](https://docs.google.com/spreadsheets/u/0/), find the new sheet `foo` and look at it. You should see some iris data in the first (and only) worksheet. We'll also take a look at it here, by consuming `foo` via the list feed. -Note how we always store the returned value from `edit_cells()` (and all other sheet editing functions). That's because the registration info changes whenever we edit the sheet and we re-register it inside these functions, so this idiom will help you make sequential edits and queries to the same sheet. +Note how we always store the returned value from `gs_edit_cells()` (and all other sheet editing functions). That's because the registration info changes whenever we edit the sheet and we re-register it inside these functions, so this idiom will help you make sequential edits and queries to the same sheet. ```{r consume-edited-cells} -foo %>% get_via_lf() +foo %>% gs_read() ``` -Read the function documentation for `edit_cells()` for how to specify where the data goes, via an anchor cell, and in which direction, via the shape of the input or the `byrow =` argument. +Read the function documentation for `gs_edit_cells()` for how to specify where the data goes, via an anchor cell, and in which direction, via the shape of the input or the `byrow =` argument. ### Delete sheets @@ -259,7 +259,7 @@ Here's how we can create a new spreadsheet from a suitable local file. First, we iris %>% head(5) %>% write.csv("iris.csv", row.names = FALSE) iris_ss <- gs_upload("iris.csv") iris_ss -iris_ss %>% get_via_lf() +iris_ss %>% gs_read_listfeed() file.remove("iris.csv") ``` @@ -268,7 +268,7 @@ Now we'll upload a multi-sheet Excel workbook. Slowly. ```{r new-sheet-from-xlsx} gap_xlsx <- gs_upload(system.file("mini-gap.xlsx", package = "googlesheets")) gap_xlsx -gap_xlsx %>% get_via_lf(ws = "Oceania") +gap_xlsx %>% gs_read_listfeed(ws = "Oceania") ``` And we clean up after ourselves on Google Drive. @@ -326,4 +326,4 @@ user_session_info In March 2014 [Google introduced "new" Sheets](https://support.google.com/docs/answer/3541068?hl=en). "New" Sheets and "old" sheets behave quite differently with respect to access via API and present a big headache for us. Recently, we've noted that Google is forcibly converting sheets: [all "old" Sheets will be switched over the "new" sheets during 2015](https://support.google.com/docs/answer/6082736?p=new_sheets_migrate&rd=1). However there are still "old" sheets lying around, so we've made some effort to support them, when it's easy to do so. But keep your expectations low. -In particular, `get_via_csv()` does not and indeed __cannot__ work for "old" sheets. +In particular, `gs_read_csv()` does not and indeed __cannot__ work for "old" sheets. diff --git a/README.md b/README.md index 1bce17e..9cb275b 100644 --- a/README.md +++ b/README.md @@ -74,36 +74,36 @@ The `gs_ls()` function returns the sheets you would see in your Google Sheets ho ``` r (my_sheets <- gs_ls()) -#> Source: local data frame [37 x 10] +#> Source: local data frame [40 x 10] #> #> sheet_title author perm version updated -#> 1 test-gs-jenny-149357b38… gspreadr rw new 2015-05-23 05:35:41 -#> 2 EasyTweetSheet - Shared m.hawksey r new 2015-05-23 05:33:43 -#> 3 Ari's Anchor Text Scrap… anahmani r old 2015-05-22 23:01:31 -#> 4 #rhizo15 #tw m.hawksey r new 2015-05-22 19:43:33 -#> 5 All R Phylo Functions omeara.brian r new 2015-05-20 18:34:43 -#> 6 ari copy gspreadr rw old 2015-05-19 23:00:13 -#> 7 gas_mileage woo.kara r new 2015-05-17 00:00:12 -#> 8 2014-05-10_seaRM-at-van… gspreadr rw new 2015-05-11 04:19:08 -#> 9 2014-05-10_seaRM-at-van… jenny r new 2015-05-11 03:51:57 -#> 10 test-gs-permissions gspreadr rw new 2015-05-08 23:08:59 +#> 1 Copy of Twitter Archive… joannazhaoo r new 2015-05-30 17:31:25 +#> 2 TAGS v6.0ns m.hawksey r new 2015-05-30 10:46:47 +#> 3 EasyTweetSheet - Shared m.hawksey r new 2015-05-30 16:44:08 +#> 4 #rhizo15 #tw m.hawksey r new 2015-05-30 07:53:02 +#> 5 Ari's Anchor Text Scrap… anahmani r new 2015-05-29 07:18:48 +#> 6 Tweet Collector (TAGS v… gspreadr rw new 2015-05-28 17:43:29 +#> 7 test-gs-cars-private gspreadr rw new 2015-05-27 17:48:34 +#> 8 All R Phylo Functions omeara.brian r new 2015-05-20 18:34:43 +#> 9 test-gs-public-testing-… rpackagetest r new 2015-05-20 01:32:27 +#> 10 ari copy gspreadr rw new 2015-05-19 23:00:13 #> .. ... ... ... ... ... #> Variables not shown: sheet_key (chr), ws_feed (chr), alternate (chr), self #> (chr), alt_key (chr) # (expect a prompt to authenticate with Google interactively HERE) my_sheets %>% glimpse() -#> Observations: 37 +#> Observations: 40 #> Variables: -#> $ sheet_title (chr) "test-gs-jenny-149357b38e46a-pts-copy", "EasyTweet... -#> $ author (chr) "gspreadr", "m.hawksey", "anahmani", "m.hawksey", ... -#> $ perm (chr) "rw", "r", "r", "r", "r", "rw", "r", "rw", "r", "r... -#> $ version (chr) "new", "new", "old", "new", "new", "old", "new", "... -#> $ updated (time) 2015-05-23 05:35:41, 2015-05-23 05:33:43, 2015-05... -#> $ sheet_key (chr) "1ymL1PCFrFMHuQ-gIE0aK5mtkBbhZxv-FUFV-ww5uAGc", "1... +#> $ sheet_title (chr) "Copy of Twitter Archiver v2.1", "TAGS v6.0ns", "E... +#> $ author (chr) "joannazhaoo", "m.hawksey", "m.hawksey", "m.hawkse... +#> $ perm (chr) "r", "r", "r", "r", "r", "rw", "rw", "r", "r", "rw... +#> $ version (chr) "new", "new", "new", "new", "new", "new", "new", "... +#> $ updated (time) 2015-05-30 17:31:25, 2015-05-30 10:46:47, 2015-05... +#> $ sheet_key (chr) "1DoMXh2m3FGPoZAle9vnzg763D9FESTU506iqWkUTwtE", "1... #> $ ws_feed (chr) "https://spreadsheets.google.com/feeds/worksheets/... -#> $ alternate (chr) "https://docs.google.com/spreadsheets/d/1ymL1PCFrF... +#> $ alternate (chr) "https://docs.google.com/spreadsheets/d/1DoMXh2m3F... #> $ self (chr) "https://spreadsheets.google.com/feeds/spreadsheet... -#> $ alt_key (chr) NA, NA, "0Av8m6X4cYe9hdFFLU1lWUndCWHNzVWZZRWFNZHQt... +#> $ alt_key (chr) NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA... ``` ### Get a Google spreadsheet to practice with @@ -130,7 +130,7 @@ gap <- gs_title("Gapminder") #> Sheet successfully identifed: "Gapminder" gap #> Spreadsheet title: Gapminder -#> Date of googlesheets registration: 2015-05-23 05:44:13 GMT +#> Date of googlesheets registration: 2015-05-30 18:00:05 GMT #> Date of last spreadsheet update: 2015-03-23 20:34:08 GMT #> visibility: private #> permissions: rw @@ -182,13 +182,13 @@ The registration functions `gs_title()`, `gs_key()`, and `gs_url()` return a reg There are three ways to consume data from a worksheet within a Google spreadsheet. The order goes from fastest-but-more-limited to slowest-but-most-flexible: -- `get_via_csv()`: Don't let the name scare you! Nothing is written to file during this process. The name just reflects that, under the hood, we request the data via the "exportcsv" link. For cases where `get_via_csv()` and `get_via_lf()` both work, we see that `get_via_csv()` is around **50 times faster**. Use this when your data occupies a nice rectangle in the sheet and you're willing to consume all of it. You will get a `tbl_df` back, which is basically just a `data.frame`. -- `get_via_lf()`: Gets data via the ["list feed"](https://developers.google.com/google-apps/spreadsheets/#working_with_list-based_feeds), which consumes data row-by-row. Like `get_via_csv()`, this is appropriate when your data occupies a nice rectangle. You will again get a `tbl_df` back, but your variable names may have been mangled (by Google, not us!). Specifically, variable names will be forcefully lowercased and all non-alpha-numeric characters will be removed. Why do we even have this function? The list feed supports some query parameters for sorting and filtering the data, which we plan to support (\#17). -- `get_via_cf()`: Get data via the ["cell feed"](https://developers.google.com/google-apps/spreadsheets/#working_with_cell-based_feeds), which consumes data cell-by-cell. This is appropriate when you want to consume arbitrary cells, rows, columns, and regions of the sheet. It works great for small amounts of data but can be rather slow otherwise. `get_via_cf()` returns a `tbl_df` with **one row per cell**. You can specify cell limits directly in `get_via_cf()` or use convenience wrappers `get_row()`, `get_col()` or `get_cells()` for some common special cases. See below for demos of `reshape_cf()` and `simplify_cf()` which help with post-processing. +- `gs_read_csv()`: Don't let the name scare you! Nothing is written to file during this process. The name just reflects that, under the hood, we request the data via the "exportcsv" link. For cases where `gs_read_csv()` and `gs_read_listfeed()` both work, we see that `gs_read_csv()` is around **50 times faster**. Use this when your data occupies a nice rectangle in the sheet and you're willing to consume all of it. You will get a `tbl_df` back, which is basically just a `data.frame`. +- `gs_read_listfeed()`: Gets data via the ["list feed"](https://developers.google.com/google-apps/spreadsheets/#working_with_list-based_feeds), which consumes data row-by-row. Like `gs_read_csv()`, this is appropriate when your data occupies a nice rectangle. You will again get a `tbl_df` back, but your variable names may have been mangled (by Google, not us!). Specifically, variable names will be forcefully lowercased and all non-alpha-numeric characters will be removed. Why do we even have this function? The list feed supports some query parameters for sorting and filtering the data, which we plan to support (\#17). +- `gs_read_cellfeed()`: Get data via the ["cell feed"](https://developers.google.com/google-apps/spreadsheets/#working_with_cell-based_feeds), which consumes data cell-by-cell. This is appropriate when you want to consume arbitrary cells, rows, columns, and regions of the sheet. It works great for small amounts of data but can be rather slow otherwise. `gs_read_cellfeed()` returns a `tbl_df` with **one row per cell**. You can specify cell limits in `gs_read_cellfeed()` via the `range` argument. See below for demos of `gs_reshape_cellfeed()` and `gs_simplify_cellfeed()` which help with post-processing. ``` r # Get the data for worksheet "Oceania": the super-fast csv way -oceania_csv <- gap %>% get_via_csv(ws = "Oceania") +oceania_csv <- gap %>% gs_read_csv(ws = "Oceania") #> Accessing worksheet titled "Oceania" str(oceania_csv) #> Classes 'tbl_df', 'tbl' and 'data.frame': 24 obs. of 6 variables: @@ -215,7 +215,7 @@ oceania_csv #> .. ... ... ... ... ... ... # Get the data for worksheet "Oceania": the fast tabular way ("list feed") -oceania_list_feed <- gap %>% get_via_lf(ws = "Oceania") +oceania_list_feed <- gap %>% gs_read_listfeed(ws = "Oceania") #> Accessing worksheet titled "Oceania" str(oceania_list_feed) #> Classes 'tbl_df', 'tbl' and 'data.frame': 24 obs. of 6 variables: @@ -242,7 +242,7 @@ oceania_list_feed #> .. ... ... ... ... ... ... # Get the data for worksheet "Oceania": the slower cell-by-cell way ("cell feed") -oceania_cell_feed <- gap %>% get_via_cf(ws = "Oceania") +oceania_cell_feed <- gap %>% gs_read_cellfeed(ws = "Oceania") #> Accessing worksheet titled "Oceania" str(oceania_cell_feed) #> Classes 'tbl_df', 'tbl' and 'data.frame': 150 obs. of 5 variables: @@ -252,8 +252,8 @@ str(oceania_cell_feed) #> $ col : int 1 2 3 4 5 6 1 2 3 4 ... #> $ cell_text: chr "country" "continent" "year" "lifeExp" ... #> - attr(*, "ws_title")= chr "Oceania" -head(oceania_cell_feed, 10) -#> Source: local data frame [10 x 5] +oceania_cell_feed +#> Source: local data frame [150 x 5] #> #> cell cell_alt row col cell_text #> 1 A1 R1C1 1 1 country @@ -266,15 +266,16 @@ head(oceania_cell_feed, 10) #> 8 B2 R2C2 2 2 Oceania #> 9 C2 R2C3 2 3 1952 #> 10 D2 R2C4 2 4 69.12 +#> .. ... ... ... ... ... ``` #### Convenience wrappers and post-processing the data -There are a few ways to limit the data you're consuming. You can put direct limits into `get_via_cf()`, but there are also convenience functions to get a row (`get_row()`), a column (`get_col()`), or a range (`get_cells()`). Also, when you consume data via the cell feed (which these wrappers are doing under the hood), you will often want to reshape it or simplify it (`reshape_cf()` and `simplify_cf()`). +There are a few ways to limit the data you're consuming. You can put direct limits into `gs_read_cellfeed()`, ~~but there are also convenience functions to get a row (`get_row()`), a column (`get_col()`), or a range (`get_cells()`)~~. Also, when you consume data via the cell feed (which these wrappers are doing under the hood), you will often want to reshape it or simplify it (`gs_reshape_cellfeed()` and `gs_simplify_cellfeed()`). ``` r # Reshape: instead of one row per cell, make a nice rectangular data.frame -oceania_reshaped <- oceania_cell_feed %>% reshape_cf() +oceania_reshaped <- oceania_cell_feed %>% gs_reshape_cellfeed() str(oceania_reshaped) #> Classes 'tbl_df', 'tbl' and 'data.frame': 24 obs. of 6 variables: #> $ country : chr "Australia" "Australia" "Australia" "Australia" ... @@ -283,8 +284,8 @@ str(oceania_reshaped) #> $ lifeExp : num 69.1 70.3 70.9 71.1 71.9 ... #> $ pop : int 8691212 9712569 10794968 11872264 13177000 14074100 15184200 16257249 17481977 18565243 ... #> $ gdpPercap: num 10040 10950 12217 14526 16789 ... -head(oceania_reshaped, 10) -#> Source: local data frame [10 x 6] +oceania_reshaped +#> Source: local data frame [24 x 6] #> #> country continent year lifeExp pop gdpPercap #> 1 Australia Oceania 1952 69.12 8691212 10039.60 @@ -297,11 +298,12 @@ head(oceania_reshaped, 10) #> 8 Australia Oceania 1987 76.32 16257249 21888.89 #> 9 Australia Oceania 1992 77.56 17481977 23424.77 #> 10 Australia Oceania 1997 78.83 18565243 26997.94 +#> .. ... ... ... ... ... ... # Limit data retrieval to certain cells # Example: first 3 rows -gap_3rows <- gap %>% get_row("Europe", row = 1:3) +gap_3rows <- gap %>% gs_read_cellfeed("Europe", range = cell_rows(1:3)) #> Accessing worksheet titled "Europe" gap_3rows %>% head() #> Source: local data frame [6 x 5] @@ -315,7 +317,7 @@ gap_3rows %>% head() #> 6 F1 R1C6 1 6 gdpPercap # convert to a data.frame (first row treated as header by default) -gap_3rows %>% reshape_cf() +gap_3rows %>% gs_reshape_cellfeed() #> Source: local data frame [2 x 6] #> #> country continent year lifeExp pop gdpPercap @@ -323,7 +325,7 @@ gap_3rows %>% reshape_cf() #> 2 Albania Europe 1957 59.28 1476505 1942.284 # Example: first row only -gap_1row <- gap %>% get_row("Europe", row = 1) +gap_1row <- gap %>% gs_read_cellfeed("Europe", range = cell_rows(1)) #> Accessing worksheet titled "Europe" gap_1row #> Source: local data frame [6 x 5] @@ -337,14 +339,14 @@ gap_1row #> 6 F1 R1C6 1 6 gdpPercap # convert to a named character vector -gap_1row %>% simplify_cf() +gap_1row %>% gs_simplify_cellfeed() #> A1 B1 C1 D1 E1 F1 #> "country" "continent" "year" "lifeExp" "pop" "gdpPercap" # just 2 columns, converted to data.frame gap %>% - get_col("Oceania", col = 3:4) %>% - reshape_cf() + gs_read_cellfeed("Oceania", range = cell_cols(3:4)) %>% + gs_reshape_cellfeed() #> Accessing worksheet titled "Oceania" #> Source: local data frame [24 x 2] #> @@ -363,8 +365,8 @@ gap %>% # arbitrary cell range gap %>% - get_cells("Oceania", range = "D12:F15") %>% - reshape_cf(header = FALSE) + gs_read_cellfeed("Oceania", range = "D12:F15") %>% + gs_reshape_cellfeed(col_names = FALSE) #> Accessing worksheet titled "Oceania" #> Source: local data frame [4 x 3] #> @@ -376,8 +378,8 @@ gap %>% # arbitrary cell range, alternative specification gap %>% - get_via_cf("Oceania", max_row = 5, min_col = 1, max_col = 3) %>% - reshape_cf() + gs_read_cellfeed("Oceania", range = cell_limits(c(NA, 5), c(1, 3))) %>% + gs_reshape_cellfeed() #> Accessing worksheet titled "Oceania" #> Source: local data frame [4 x 3] #> @@ -398,8 +400,8 @@ foo <- gs_new("foo") #> Worksheet dimensions: 1000 x 26. foo #> Spreadsheet title: foo -#> Date of googlesheets registration: 2015-05-23 05:44:19 GMT -#> Date of last spreadsheet update: 2015-05-23 05:44:18 GMT +#> Date of googlesheets registration: 2015-05-30 18:00:16 GMT +#> Date of last spreadsheet update: 2015-05-30 18:00:14 GMT #> visibility: private #> permissions: rw #> version: new @@ -408,31 +410,31 @@ foo #> (Title): (Nominal worksheet extent as rows x columns) #> Sheet1: 1000 x 26 #> -#> Key: 1mwkD126wE0hxGvXAK4J_uS3TDkmSeSXPlU3WQA0CLJQ +#> Key: 1QRqPObeTYiaddr2bRBo2M16a6hLrCG6e2H2_NqovNlQ ``` By default, there will be an empty worksheet called "Sheet1", but you can control it's title, extent, and initial data with additional arguments to `gs_new()`. You can also add, rename, and delete worksheets within an existing sheet via `gs_ws_new()`, `gs_ws_rename()`, and `gs_ws_delete()`. Copy an entire spreadsheet with `gs_copy()`. ### Edit cells -You can modify the data in sheet cells via `edit_cells()`. We'll work on the completely empty sheet created above, `foo`. If your edit populates the sheet with everything it should have, set `trim = TRUE` and we will resize the sheet to match the data. Then the nominal worksheet extent is much more informative (vs. the default of 1000 rows and 26 columns) and any future consumption via the cell feed will be much faster. +You can modify the data in sheet cells via `gs_edit_cells()`. We'll work on the completely empty sheet created above, `foo`. If your edit populates the sheet with everything it should have, set `trim = TRUE` and we will resize the sheet to match the data. Then the nominal worksheet extent is much more informative (vs. the default of 1000 rows and 26 columns) and any future consumption via the cell feed will be much faster. ``` r -foo <- foo %>% edit_cells(input = head(iris), header = TRUE, trim = TRUE) +foo <- foo %>% gs_edit_cells(input = head(iris), trim = TRUE) #> Range affected by the update: "A1:E7" #> Worksheet "Sheet1" successfully updated with 35 new value(s). ``` Go to [your Google Sheets home screen](https://docs.google.com/spreadsheets/u/0/), find the new sheet `foo` and look at it. You should see some iris data in the first (and only) worksheet. We'll also take a look at it here, by consuming `foo` via the list feed. -Note how we always store the returned value from `edit_cells()` (and all other sheet editing functions). That's because the registration info changes whenever we edit the sheet and we re-register it inside these functions, so this idiom will help you make sequential edits and queries to the same sheet. +Note how we always store the returned value from `gs_edit_cells()` (and all other sheet editing functions). That's because the registration info changes whenever we edit the sheet and we re-register it inside these functions, so this idiom will help you make sequential edits and queries to the same sheet. ``` r -foo %>% get_via_lf() +foo %>% gs_read() #> Accessing worksheet titled "Sheet1" #> Source: local data frame [6 x 5] #> -#> sepal.length sepal.width petal.length petal.width species +#> Sepal.Length Sepal.Width Petal.Length Petal.Width Species #> 1 5.1 3.5 1.4 0.2 setosa #> 2 4.9 3.0 1.4 0.2 setosa #> 3 4.7 3.2 1.3 0.2 setosa @@ -441,7 +443,7 @@ foo %>% get_via_lf() #> 6 5.4 3.9 1.7 0.4 setosa ``` -Read the function documentation for `edit_cells()` for how to specify where the data goes, via an anchor cell, and in which direction, via the shape of the input or the `byrow =` argument. +Read the function documentation for `gs_edit_cells()` for how to specify where the data goes, via an anchor cell, and in which direction, via the shape of the input or the `byrow =` argument. ### Delete sheets @@ -464,8 +466,8 @@ iris_ss <- gs_upload("iris.csv") #> "iris.csv" uploaded to Google Drive and converted to a Google Sheet named "iris" iris_ss #> Spreadsheet title: iris -#> Date of googlesheets registration: 2015-05-23 05:44:31 GMT -#> Date of last spreadsheet update: 2015-05-23 05:44:30 GMT +#> Date of googlesheets registration: 2015-05-30 18:00:31 GMT +#> Date of last spreadsheet update: 2015-05-30 18:00:29 GMT #> visibility: private #> permissions: rw #> version: new @@ -474,8 +476,8 @@ iris_ss #> (Title): (Nominal worksheet extent as rows x columns) #> iris: 6 x 5 #> -#> Key: 1R2-u63CKekinElKD4Ne3lUagyq-9wwmXsNSK2geZiRQ -iris_ss %>% get_via_lf() +#> Key: 1JnFpHmc2Wj-Ai8IgbJD_QOrEcDXMrMW6gQa9K6YZsCk +iris_ss %>% gs_read_listfeed() #> Accessing worksheet titled "iris" #> Source: local data frame [5 x 5] #> @@ -496,22 +498,22 @@ gap_xlsx <- gs_upload(system.file("mini-gap.xlsx", package = "googlesheets")) #> "mini-gap.xlsx" uploaded to Google Drive and converted to a Google Sheet named "mini-gap" gap_xlsx #> Spreadsheet title: mini-gap -#> Date of googlesheets registration: 2015-05-23 05:44:35 GMT -#> Date of last spreadsheet update: 2015-05-23 05:44:33 GMT +#> Date of googlesheets registration: 2015-05-30 18:00:37 GMT +#> Date of last spreadsheet update: 2015-05-30 18:00:34 GMT #> visibility: private #> permissions: rw #> version: new #> #> Contains 5 worksheets: #> (Title): (Nominal worksheet extent as rows x columns) -#> Africa: 20 x 6 -#> Americas: 20 x 6 -#> Asia: 20 x 6 -#> Europe: 20 x 6 -#> Oceania: 20 x 6 +#> Africa: 1000 x 26 +#> Americas: 1000 x 26 +#> Asia: 1000 x 26 +#> Europe: 1000 x 26 +#> Oceania: 1000 x 26 #> -#> Key: 1WRMrs6XIyauX0sDjMy1lLL4Scp8fnKlpjJ6VVb1s3eU -gap_xlsx %>% get_via_lf(ws = "Oceania") +#> Key: 1088N7_IdkHaqe-m1g2-U9Vg6ZQiH1z--aH7LQFxYbU4 +gap_xlsx %>% gs_read_listfeed(ws = "Oceania") #> Accessing worksheet titled "Oceania" #> Source: local data frame [5 x 6] #> @@ -588,8 +590,8 @@ The function `gs_user()` will print and return some information about the curren user_session_info <- gs_user() #> displayName: google sheets #> emailAddress: gspreadr@gmail.com -#> Date-time of session authorization: 2015-05-23 05:44:09 -#> Date-time of access token expiry: 2015-05-22 23:03:17 +#> Date-time of session authorization: 2015-05-30 18:00:03 +#> Date-time of access token expiry: 2015-05-30 11:58:52 #> Access token is valid. user_session_info #> $displayName @@ -599,14 +601,14 @@ user_session_info #> [1] "gspreadr@gmail.com" #> #> $auth_date -#> [1] "2015-05-23 05:44:09 GMT" +#> [1] "2015-05-30 18:00:03 GMT" #> #> $exp_date -#> [1] "2015-05-22 23:03:17 PDT" +#> [1] "2015-05-30 11:58:52 PDT" ``` ### "Old" Google Sheets In March 2014 [Google introduced "new" Sheets](https://support.google.com/docs/answer/3541068?hl=en). "New" Sheets and "old" sheets behave quite differently with respect to access via API and present a big headache for us. Recently, we've noted that Google is forcibly converting sheets: [all "old" Sheets will be switched over the "new" sheets during 2015](https://support.google.com/docs/answer/6082736?p=new_sheets_migrate&rd=1). However there are still "old" sheets lying around, so we've made some effort to support them, when it's easy to do so. But keep your expectations low. -In particular, `get_via_csv()` does not and indeed **cannot** work for "old" sheets. +In particular, `gs_read_csv()` does not and indeed **cannot** work for "old" sheets. diff --git a/man-roxygen/cellranger.R b/man-roxygen/cellranger.R new file mode 100644 index 0000000..7643931 --- /dev/null +++ b/man-roxygen/cellranger.R @@ -0,0 +1,4 @@ +#' @seealso The \code{\link[=cellranger]{cellranger}} package has full +#' documentation on cell specification and offers additional functions for +#' manipulating "A1:D10" style spreadsheet ranges. See a full list of +#' functions in the \link[cellranger:00Index]{cellranger index}. diff --git a/man/cell-specification.Rd b/man/cell-specification.Rd new file mode 100644 index 0000000..1392fb3 --- /dev/null +++ b/man/cell-specification.Rd @@ -0,0 +1,27 @@ +% Generated by roxygen2 (4.1.1): do not edit by hand +% Please edit documentation in R/gs_cell-specification.R +\name{cell-specification} +\alias{anchored} +\alias{cell-specification} +\alias{cell_cols} +\alias{cell_limits} +\alias{cell_rows} +\title{Specify cells for reading or writing} +\description{ +If you aren't targetting all the cells in a worksheet, you can request that +\code{googlesheets} limit a read or write operation to a specific rectangle +of cells. Any function that offers this flexibility will have a \code{range} +argument. The simplest usage is to specify an Excel-like cell range, such as +\code{range = "D12:F15"} or \code{range = "R1C12:R6C15"}. The cell rectangle +can be specified in various other ways, using helper functions. In all cases, +cell range processing is handled by the \code{\link[=cellranger]{cellranger}} +package, where you can find full documentation for the functions used in the +examples below. +} +\seealso{ +The \code{\link[=cellranger]{cellranger}} package has full + documentation on cell specification and offers additional functions for + manipulating "A1:D10" style spreadsheet ranges. See a full list of + functions in the \link[cellranger:00Index]{cellranger index}. +} + diff --git a/man/example-sheets.Rd b/man/example-sheets.Rd index e025f61..73004a8 100644 --- a/man/example-sheets.Rd +++ b/man/example-sheets.Rd @@ -1,5 +1,5 @@ % Generated by roxygen2 (4.1.1): do not edit by hand -% Please edit documentation in R/gs_example_sheets.R +% Please edit documentation in R/gs_example-sheet-setup.R \name{example-sheets} \alias{example-sheets} \alias{gs_gap} diff --git a/man/get_cells.Rd b/man/get_cells.Rd deleted file mode 100644 index 16d0be7..0000000 --- a/man/get_cells.Rd +++ /dev/null @@ -1,42 +0,0 @@ -% Generated by roxygen2 (4.1.1): do not edit by hand -% Please edit documentation in R/consume-data.R -\name{get_cells} -\alias{get_cells} -\title{Get data from a cell or range of cells} -\usage{ -get_cells(ss, ws = 1, range, verbose = TRUE) -} -\arguments{ -\item{ss}{a registered Google spreadsheet, i.e. a \code{\link{googlesheet}} -object} - -\item{ws}{positive integer or character string specifying index or title, -respectively, of the worksheet} - -\item{range}{single character string specifying which cell or range of cells -to retrieve; positioning notation can be either "A1" or "R1C1"; a single -cell can be requested, e.g. "B4" or "R4C2" or a rectangular range can be -requested, e.g. "B2:D4" or "R2C2:R4C4"} - -\item{verbose}{logical; do you want informative messages?} -} -\description{ -Get data via the cell feed for a rectangular range of cells -} -\examples{ -\dontrun{ -gap_ss <- gs_gap() # register the Gapminder example sheet -get_cells(gap_ss, "Europe", range = "B3:D7") -simplify_cf(get_cells(gap_ss, "Europe", range = "A1:F1")) -} -} -\seealso{ -\code{\link{reshape_cf}} to reshape the retrieved data into a more - usable data.frame - -Other data.consumption.functions: \code{\link{get_col}}; - \code{\link{get_row}}; \code{\link{get_via_cf}}; - \code{\link{get_via_csv}}; \code{\link{get_via_lf}}; - \code{\link{reshape_cf}}; \code{\link{simplify_cf}} -} - diff --git a/man/get_col.Rd b/man/get_col.Rd deleted file mode 100644 index 95d07e8..0000000 --- a/man/get_col.Rd +++ /dev/null @@ -1,41 +0,0 @@ -% Generated by roxygen2 (4.1.1): do not edit by hand -% Please edit documentation in R/consume-data.R -\name{get_col} -\alias{get_col} -\title{Get data from a column or range of columns} -\usage{ -get_col(ss, ws = 1, col, verbose = TRUE) -} -\arguments{ -\item{ss}{a registered Google spreadsheet, i.e. a \code{\link{googlesheet}} -object} - -\item{ws}{positive integer or character string specifying index or title, -respectively, of the worksheet} - -\item{col}{vector of positive integers, possibly of length one, specifying -which columns to retrieve; only contiguous ranges of columns are supported, -i.e. if \code{col = c(2, 8)}, you will get columns 2 through 8} - -\item{verbose}{logical; do you want informative messages?} -} -\description{ -Get data via the cell feed for one column or for a range of columns. -} -\examples{ -\dontrun{ -gap_ss <- gs_gap() # register the Gapminder example sheet -get_col(gap_ss, "Oceania", col = 1:2) -reshape_cf(get_col(gap_ss, "Oceania", col = 1:2)) -} -} -\seealso{ -\code{\link{reshape_cf}} to reshape the retrieved data into a more - usable data.frame - -Other data.consumption.functions: \code{\link{get_cells}}; - \code{\link{get_row}}; \code{\link{get_via_cf}}; - \code{\link{get_via_csv}}; \code{\link{get_via_lf}}; - \code{\link{reshape_cf}}; \code{\link{simplify_cf}} -} - diff --git a/man/get_row.Rd b/man/get_row.Rd deleted file mode 100644 index 94b65eb..0000000 --- a/man/get_row.Rd +++ /dev/null @@ -1,41 +0,0 @@ -% Generated by roxygen2 (4.1.1): do not edit by hand -% Please edit documentation in R/consume-data.R -\name{get_row} -\alias{get_row} -\title{Get data from a row or range of rows} -\usage{ -get_row(ss, ws = 1, row, verbose = TRUE) -} -\arguments{ -\item{ss}{a registered Google spreadsheet, i.e. a \code{\link{googlesheet}} -object} - -\item{ws}{positive integer or character string specifying index or title, -respectively, of the worksheet} - -\item{row}{vector of positive integers, possibly of length one, specifying -which rows to retrieve; only contiguous ranges of rows are supported, i.e. -if \code{row = c(2, 8)}, you will get rows 2 through 8} - -\item{verbose}{logical; do you want informative messages?} -} -\description{ -Get data via the cell feed for one row or for a range of rows. -} -\examples{ -\dontrun{ -gap_ss <- gs_gap() # register the Gapminder example sheet -get_row(gap_ss, "Europe", row = 1) -simplify_cf(get_row(gap_ss, "Europe", row = 1)) -} -} -\seealso{ -\code{\link{reshape_cf}} to reshape the retrieved data into a more - usable data.frame - -Other data.consumption.functions: \code{\link{get_cells}}; - \code{\link{get_col}}; \code{\link{get_via_cf}}; - \code{\link{get_via_csv}}; \code{\link{get_via_lf}}; - \code{\link{reshape_cf}}; \code{\link{simplify_cf}} -} - diff --git a/man/get_via_cf.Rd b/man/get_via_cf.Rd deleted file mode 100644 index 38ff448..0000000 --- a/man/get_via_cf.Rd +++ /dev/null @@ -1,75 +0,0 @@ -% Generated by roxygen2 (4.1.1): do not edit by hand -% Please edit documentation in R/consume-data.R -\name{get_via_cf} -\alias{get_via_cf} -\title{Create a data.frame of the non-empty cells in a rectangular region of a -worksheet} -\usage{ -get_via_cf(ss, ws = 1, min_row = NULL, max_row = NULL, min_col = NULL, - max_col = NULL, limits = NULL, return_empty = FALSE, - return_links = FALSE, verbose = TRUE) -} -\arguments{ -\item{ss}{a registered Google spreadsheet, i.e. a \code{\link{googlesheet}} -object} - -\item{ws}{positive integer or character string specifying index or title, -respectively, of the worksheet} - -\item{min_row}{positive integer, optional} - -\item{max_row}{positive integer, optional} - -\item{min_col}{positive integer, optional} - -\item{max_col}{positive integer, optional} - -\item{limits}{list, with named components holding the min and max for rows -and columns; intended primarily for internal use} - -\item{return_empty}{logical; indicates whether to return empty cells} - -\item{return_links}{logical; indicates whether to return the edit and self -links (used internally in cell editing workflow)} - -\item{verbose}{logical; do you want informative messages?} -} -\description{ -This function consumes data via the cell feed, which, as the name suggests, -retrieves data cell by cell. No attempt is made here to shape the returned -data, but you can do that with \code{\link{reshape_cf}} and -\code{\link{simplify_cf}}). The output data.frame of \code{get_via_cf} will -have one row per cell. -} -\details{ -Use the limits, e.g. min_row or max_col, to delineate the rectangular region -of interest. You can specify any subset of the limits or none at all. If -limits are provided, validity will be checked as well as internal consistency -and compliance with known extent of the worksheet. If no limits are provided, -all cells will be returned but realize that \code{\link{get_via_csv}} and -\code{\link{get_via_lf}} are much faster ways to consume data from a -rectangular worksheet. - -Empty cells, even if "embedded" in a rectangular region of populated cells, -are not normally returned by the cell feed. This function won't return them -either when \code{return_empty = FALSE} (default), but will if you set -\code{return_empty = TRUE}. If you don't specify any limits AND you set -\code{return_empty = TRUE}, you could be in for several minutes wait, as the -feed will return all cells, which defaults to 1000 rows and 26 columns. -} -\examples{ -\dontrun{ -gap_ss <- gs_gap() # register the Gapminder example sheet -get_via_cf(gap_ss, "Asia", max_row = 4) -reshape_cf(get_via_cf(gap_ss, "Asia", max_row = 4)) -reshape_cf(get_via_cf(gap_ss, "Asia", - limits = list(max_row = 4, min_col = 3))) -} -} -\seealso{ -Other data.consumption.functions: \code{\link{get_cells}}; - \code{\link{get_col}}; \code{\link{get_row}}; - \code{\link{get_via_csv}}; \code{\link{get_via_lf}}; - \code{\link{reshape_cf}}; \code{\link{simplify_cf}} -} - diff --git a/man/get_via_csv.Rd b/man/get_via_csv.Rd deleted file mode 100644 index 7c7d217..0000000 --- a/man/get_via_csv.Rd +++ /dev/null @@ -1,52 +0,0 @@ -% Generated by roxygen2 (4.1.1): do not edit by hand -% Please edit documentation in R/consume-data.R -\name{get_via_csv} -\alias{get_via_csv} -\title{Get all data from a rectangular worksheet as a tbl_df or data.frame} -\usage{ -get_via_csv(ss, ws = 1, ..., verbose = TRUE) -} -\arguments{ -\item{ss}{a registered Google spreadsheet, i.e. a \code{\link{googlesheet}} -object} - -\item{ws}{positive integer or character string specifying index or title, -respectively, of the worksheet} - -\item{...}{Further arguments to be passed to the csv parser. This is -currently \code{\link{read.csv}}, but expect a switch to -\code{readr::read_csv} in the not-too-distant future! Note that by default -\code{\link{read.csv}} is called with \code{stringsAsFactors = FALSE}.} - -\item{verbose}{logical; do you want informative messages?} -} -\value{ -a tbl_df -} -\description{ -This function consumes data using the \code{exportcsv} links found in the -worksheets feed. Don't be spooked by the "csv" thing -- the data is NOT -actually written to file during this process. In fact, this is much, much -faster than consumption via the list feed. Unlike using the list feed, this -method does not assume that the populated cells form a neat rectangle. All -cells within the "data rectangle", i.e. spanned by the maximal row and column -extent of the data, are returned. Empty cells will be assigned NA. Also, the -header row, potentially containing column or variable names, is not -transformed/mangled, as it is via the list feed. If you want all of your -data, this is the fastest way to get it. -} -\examples{ -\dontrun{ -gap_ss <- gs_gap() # register the Gapminder example sheet -oceania_csv <- get_via_csv(gap_ss, ws = "Oceania") -str(oceania_csv) -oceania_csv -} -} -\seealso{ -Other data.consumption.functions: \code{\link{get_cells}}; - \code{\link{get_col}}; \code{\link{get_row}}; - \code{\link{get_via_cf}}; \code{\link{get_via_lf}}; - \code{\link{reshape_cf}}; \code{\link{simplify_cf}} -} - diff --git a/man/get_via_lf.Rd b/man/get_via_lf.Rd deleted file mode 100644 index ae2f3fd..0000000 --- a/man/get_via_lf.Rd +++ /dev/null @@ -1,52 +0,0 @@ -% Generated by roxygen2 (4.1.1): do not edit by hand -% Please edit documentation in R/consume-data.R -\name{get_via_lf} -\alias{get_via_lf} -\title{Get data from a rectangular worksheet as a tbl_df or data.frame} -\usage{ -get_via_lf(ss, ws = 1, verbose = TRUE) -} -\arguments{ -\item{ss}{a registered Google spreadsheet, i.e. a \code{\link{googlesheet}} -object} - -\item{ws}{positive integer or character string specifying index or title, -respectively, of the worksheet} - -\item{verbose}{logical; do you want informative messages?} -} -\value{ -a tbl_df -} -\description{ -Gets data via the list feed, which assumes populated cells form a neat -rectangle. The list feed consumes data row by row. First row regarded as -header row of variable or column names. The related function, -\code{get_via_csv}, also returns data from a neat rectangle of cells, so you -probably want to use that (unless you are dealing with an "old" Google Sheet, -which \code{get_via_csv} does not support). -} -\note{ -When you use the listfeed, the Sheets API transforms the variable or - column names like so: 'The column names are the header values of the - worksheet lowercased and with all non-alpha-numeric characters removed. For - example, if the cell A1 contains the value "Time 2 Eat!" the column name - would be "time2eat".' If this is intolerable to you, consume the data via - the cell feed or csv download. Or, at least, consume the first row via the - cell feed and manually restore the variable names post hoc. -} -\examples{ -\dontrun{ -gap_ss <- gs_gap() # register the Gapminder example sheet -oceania_lf <- get_via_lf(gap_ss, ws = "Oceania") -str(oceania_lf) -oceania_lf -} -} -\seealso{ -Other data.consumption.functions: \code{\link{get_cells}}; - \code{\link{get_col}}; \code{\link{get_row}}; - \code{\link{get_via_cf}}; \code{\link{get_via_csv}}; - \code{\link{reshape_cf}}; \code{\link{simplify_cf}} -} - diff --git a/man/edit_cells.Rd b/man/gs_edit_cells.Rd similarity index 66% rename from man/edit_cells.Rd rename to man/gs_edit_cells.Rd index c3227cc..48302a3 100644 --- a/man/edit_cells.Rd +++ b/man/gs_edit_cells.Rd @@ -1,11 +1,11 @@ % Generated by roxygen2 (4.1.1): do not edit by hand -% Please edit documentation in R/edit-data.R -\name{edit_cells} -\alias{edit_cells} +% Please edit documentation in R/gs_edit_cells.R +\name{gs_edit_cells} +\alias{gs_edit_cells} \title{Edit cells} \usage{ -edit_cells(ss, ws = 1, input = "", anchor = "A1", byrow = FALSE, - header = FALSE, trim = FALSE, verbose = TRUE) +gs_edit_cells(ss, ws = 1, input = "", anchor = "A1", byrow = FALSE, + col_names = NULL, trim = FALSE, verbose = TRUE) } \arguments{ \item{ss}{a registered Google spreadsheet, i.e. a \code{\link{googlesheet}} @@ -25,7 +25,7 @@ cell range to edit; positioning notation can be either "A1" or "R1C1"} TRUE}) or down a column (\code{byrow = FALSE}, default); consulted only when \code{input} is a vector, i.e. \code{dim(input)} is \code{NULL}} -\item{header}{logical; indicates whether column names of input should be +\item{col_names}{logical; indicates whether column names of input should be included in the edit, i.e. prepended to the input; consulted only when \code{length(dim(input))} equals 2, i.e. \code{input} is a matrix or data.frame} @@ -47,16 +47,22 @@ from the anchor across a row or down a column. \examples{ \dontrun{ yo <- gs_new("yo") -yo <- edit_cells(yo, input = head(iris), header = TRUE, trim = TRUE) -get_via_csv(yo) +yo <- gs_edit_cells(yo, input = head(iris), trim = TRUE) +gs_read(yo) -yo <- gs_ws_new(yo, "byrow_FALSE") -yo <- edit_cells(yo, ws = "byrow_FALSE", LETTERS[1:5], "A8") -get_via_cf(yo, ws = "byrow_FALSE", min_row = 7) \%>\% simplify_cf() +yo <- gs_ws_new(yo, ws = "byrow_FALSE") +yo <- gs_edit_cells(yo, ws = "byrow_FALSE", + input = LETTERS[1:5], anchor = "A8") +gs_read_cellfeed(yo, ws = "byrow_FALSE", range = "A8:A12") \%>\% + gs_simplify_cellfeed() -yo <- gs_ws_new(yo, "byrow_TRUE") -yo <- edit_cells(yo, ws = "byrow_TRUE", LETTERS[1:5], "A8", byrow = TRUE) -get_via_cf(yo, ws = "byrow_TRUE", min_row = 7) \%>\% simplify_cf() +yo <- gs_ws_new(yo, ws = "byrow_TRUE") +yo <- gs_edit_cells(yo, ws = "byrow_TRUE", input = LETTERS[1:5], + anchor = "A8", byrow = TRUE) +gs_read_cellfeed(yo, ws = "byrow_TRUE", range = "A8:E8") \%>\% + gs_simplify_cellfeed() + +gs_delete(yo) } } diff --git a/man/gs_inspect.Rd b/man/gs_inspect.Rd index 82e30b5..549e1f4 100644 --- a/man/gs_inspect.Rd +++ b/man/gs_inspect.Rd @@ -27,7 +27,7 @@ gs_inspect(iris) # data recorded from a game of ultimate frisbee ulti_key <- "1223dpf3vnjZUYUnCM8rBSig3JlGrAu1Qu6VmPvdEn4M" ulti_ss <- ulti_key \%>\% gs_key() -ulti_csv <- ulti_ss \%>\% get_col(ws = 2, col = 1:6) \%>\% reshape_cf() +ulti_csv <- ulti_ss \%>\% get_col(ws = 2, col = 1:6) \%>\% gs_reshape_cellfeed() gs_inspect(ulti_csv) } diff --git a/man/gs_new.Rd b/man/gs_new.Rd index e523b95..e80be7e 100644 --- a/man/gs_new.Rd +++ b/man/gs_new.Rd @@ -19,7 +19,7 @@ Sheets default is 1000} \item{col_extent}{integer for new column extent; if unspecified, the Google Sheets default is 26} -\item{...}{optional arguments passed along to \code{\link{edit_cells}} in +\item{...}{optional arguments passed along to \code{\link{gs_edit_cells}} in order to populate the new worksheet with data} \item{verbose}{logical; do you want informative messages?} @@ -43,11 +43,11 @@ API}. \details{ We anticipate that \strong{if} the user wants to control the extent of the new worksheet, it will be by providing input data and specifying `trim = -TRUE` (see \code{\link{edit_cells}}) or by specifying \code{row_extent} and -\code{col_extent} directly. But not both ... although we won't stop you. In -that case, note that explicit worksheet sizing occurs before data insertion. -If data insertion triggers any worksheet resizing, that will override any -usage of \code{row_extent} or \code{col_extent}. +TRUE` (see \code{\link{gs_edit_cells}}) or by specifying \code{row_extent} +and \code{col_extent} directly. But not both ... although we won't stop you. +In that case, note that explicit worksheet sizing occurs before data +insertion. If data insertion triggers any worksheet resizing, that will +override any usage of \code{row_extent} or \code{col_extent}. } \examples{ \dontrun{ @@ -65,10 +65,10 @@ gs_delete(foo) } } \seealso{ -\code{\link{edit_cells}} for specifics on populating the new sheet - with some data and \code{\link{gs_upload}} for creating a new spreadsheet - by uploading a local file. Note that \code{\link{gs_upload}} is likely much - faster than using \code{gs_new} and \code{\link{edit_cells}}, so try both - if speed is a concern. +\code{\link{gs_edit_cells}} for specifics on populating the new + sheet with some data and \code{\link{gs_upload}} for creating a new + spreadsheet by uploading a local file. Note that \code{\link{gs_upload}} is + likely much faster than using \code{\link{gs_new}} and/or + \code{\link{gs_edit_cells}}, so try both if speed is a concern. } diff --git a/man/gs_read.Rd b/man/gs_read.Rd new file mode 100644 index 0000000..6db2646 --- /dev/null +++ b/man/gs_read.Rd @@ -0,0 +1,74 @@ +% Generated by roxygen2 (4.1.1): do not edit by hand +% Please edit documentation in R/gs_read.R +\name{gs_read} +\alias{gs_read} +\title{Read data} +\usage{ +gs_read(ss, ws = 1, range = NULL, ..., verbose = TRUE) +} +\arguments{ +\item{ss}{a registered Google spreadsheet, i.e. a \code{\link{googlesheet}} +object} + +\item{ws}{positive integer or character string specifying index or title, +respectively, of the worksheet} + +\item{range}{blah blah} + +\item{...}{optional arguments passed on to functions that control reading and +transforming the data} + +\item{verbose}{logical; do you want informative messages?} +} +\value{ +a tbl_df +} +\description{ +This function reads data from a worksheet and returns it as a \code{tbl_df} +or \code{data.frame}. It wraps up the most common usage of other, lower-level +functions for data consumption and transformation, but you can call always +call them directly for finer control. +} +\details{ +If the \code{range} argument is not specified, all data will be read via +\code{\link{gs_read_csv}}. In this case, you can pass additional arguments to +the csv parser via \code{...}; see \code{\link{gs_read_cellfeed}} for more +details. Don't worry -- no intermediate \code{*.csv} files were written in +the reading of your data! We just request the data from the Sheets API via +the \code{exportcsv} link. + +If the \code{range} argument is specified, data will be read for the +targetted cells via \code{\link{gs_read_cellfeed}}, then reshaped with +\code{\link{gs_reshape_cellfeed}}. In this case, you can pass additional +arguments to \code{\link{gs_reshape_cellfeed}} via \code{...}. +} +\examples{ +\dontrun{ +gap_ss <- gs_gap() +oceania_csv <- gs_read(gap_ss, ws = "Oceania") +str(oceania_csv) +oceania_csv + +gs_read(gap_ss, ws = "Oceania", range = "A1:C4") +gs_read(gap_ss, ws = "Oceania", range = "R1C1:R4C3") +gs_read(gap_ss, ws = "Oceania", range = "R2C1:R4C3", col_names = FALSE) +gs_read(gap_ss, ws = "Oceania", range = "R2C5:R4C6", + col_names = c("thing_one", "thing_two")) +gs_read(gap_ss, ws = "Oceania", range = cell_limits(c(1, 4), c(1, 3))) +gs_read(gap_ss, ws = "Oceania", range = cell_rows(1:5)) +gs_read(gap_ss, ws = "Oceania", range = cell_cols(4:6)) +gs_read(gap_ss, ws = "Oceania", range = cell_cols("A:D")) +gs_read(gap_ss, ws = "Oceania", range = cell_rows(1), col_names = FALSE) +} +} +\seealso{ +The \code{\link{cell-specification}} topic for more about targetting + specific cells. + +Other data.consumption.functions: \code{\link{gs_read_cellfeed}}; + \code{\link{gs_read_csv}}; + \code{\link{gs_read_listfeed}}; + \code{\link{gs_reshape_cellfeed}}; + \code{\link{gs_simplify_cellfeed}} +} + diff --git a/man/gs_read_cellfeed.Rd b/man/gs_read_cellfeed.Rd new file mode 100644 index 0000000..829158a --- /dev/null +++ b/man/gs_read_cellfeed.Rd @@ -0,0 +1,71 @@ +% Generated by roxygen2 (4.1.1): do not edit by hand +% Please edit documentation in R/gs_read_cellfeed.R +\name{gs_read_cellfeed} +\alias{gs_read_cellfeed} +\title{Read data from cells} +\usage{ +gs_read_cellfeed(ss, ws = 1, range = NULL, return_empty = FALSE, + return_links = FALSE, verbose = TRUE) +} +\arguments{ +\item{ss}{a registered Google spreadsheet, i.e. a \code{\link{googlesheet}} +object} + +\item{ws}{positive integer or character string specifying index or title, +respectively, of the worksheet} + +\item{range}{blah blah} + +\item{return_empty}{logical; indicates whether to return empty cells} + +\item{return_links}{logical; indicates whether to return the edit and self +links (used internally in cell editing workflow)} + +\item{verbose}{logical; do you want informative messages?} +} +\description{ +This function consumes data via the "cell feed", which, as the name suggests, +retrieves data cell by cell. Note that the output is a \code{tbl_df} or +\code{data.frame} with \strong{one row per cell}. +} +\details{ +Use the \code{range} argument to specify which cells you want to read. See +the examples and the help file for the \link[=cell-specification]{cell +specification functions} for various ways to limit consumption to, e.g., a +rectangle or certain columns. If \code{range} is specified, the associated +cell limits will be checked for internal consistency and compliance with the +known extent of the worksheet. If no limits are provided, all cells will be +returned but consider that \code{\link{gs_read_csv}} and +\code{\link{gs_read_listfeed}} are much faster ways to consume all the data +from a rectangular worksheet. + +Empty cells, even if "embedded" in a rectangular region of populated cells, +are not normally returned by the cell feed. This function won't return them +either when \code{return_empty = FALSE} (default), but will if you set +\code{return_empty = TRUE}. If you don't specify any limits AND you set +\code{return_empty = TRUE}, you could be in for a bit of a wait, as the feed +will return all cells, which defaults to 1000 rows and 26 columns. +} +\examples{ +\dontrun{ +gap_ss <- gs_gap() # register the Gapminder example sheet +first_4_rows <- + gs_read_cellfeed(gap_ss, "Asia", range = cell_limits(c(NA, 4))) +first_4_rows +gs_reshape_cellfeed(first_4_rows) +gs_reshape_cellfeed(gs_read_cellfeed(gap_ss, "Asia", + range = cell_limits(c(NA, 4), c(3, NA)))) +} +} +\seealso{ +\code{\link{gs_reshape_cellfeed}} or + \code{\link{gs_simplify_cellfeed}} to perform reshaping or simplification, + respectively; \code{\link{gs_read}} is a pre-made wrapper that combines + \code{gs_read_cellfeed} and \code{\link{gs_reshape_cellfeed}} + +Other data.consumption.functions: \code{\link{gs_read_csv}}; + \code{\link{gs_read_listfeed}}; \code{\link{gs_read}}; + \code{\link{gs_reshape_cellfeed}}; + \code{\link{gs_simplify_cellfeed}} +} + diff --git a/man/gs_read_csv.Rd b/man/gs_read_csv.Rd new file mode 100644 index 0000000..15e4678 --- /dev/null +++ b/man/gs_read_csv.Rd @@ -0,0 +1,58 @@ +% Generated by roxygen2 (4.1.1): do not edit by hand +% Please edit documentation in R/gs_read_csv.R +\name{gs_read_csv} +\alias{gs_read_csv} +\title{Read data via the \code{exportcsv} link} +\usage{ +gs_read_csv(ss, ws = 1, ..., verbose = TRUE) +} +\arguments{ +\item{ss}{a registered Google spreadsheet, i.e. a \code{\link{googlesheet}} +object} + +\item{ws}{positive integer or character string specifying index or title, +respectively, of the worksheet} + +\item{...}{Further arguments to be passed to the csv parser. This is +currently \code{\link{read.csv}}, but expect a switch to +\code{readr::read_csv} in the not-too-distant future! Note that by default +\code{\link{read.csv}} is called with \code{stringsAsFactors = FALSE}.} + +\item{verbose}{logical; do you want informative messages?} +} +\value{ +a tbl_df +} +\description{ +This function reads all data from a worksheet and returns it as a +\code{tbl_df} or \code{data.frame}. Don't be spooked by the "csv" thing -- +the data is NOT actually written to file during this process. Data is read +from the "maximal data rectangle", i.e. the rectangle spanned by the maximal +row and column extent of the data. Empty cells within this rectangle will be +assigned NA. This is the fastest method of data consumption, so use it as +long as you can tolerate the lack of control re: which cells are being read. +} +\details{ +How does this compare to consumption via the list feed, implemented by +\code{\link{gs_read_listfeed}}? First, \code{gs_read_csv} is much, much +faster. Second, the first row, potentially containing column or variable +names, is NOT transformed/mangled, as it is via the list feed. Finally, +consumption via the \code{exportcsv} link is more tolerant of data that does +not form a perfect, neat rectangle, e.g. the read does NOT stop upon +encountering an empty row. +} +\examples{ +\dontrun{ +gap_ss <- gs_gap() # register the Gapminder example sheet +oceania_csv <- gs_read_csv(gap_ss, ws = "Oceania") +str(oceania_csv) +oceania_csv +} +} +\seealso{ +Other data.consumption.functions: \code{\link{gs_read_cellfeed}}; + \code{\link{gs_read_listfeed}}; \code{\link{gs_read}}; + \code{\link{gs_reshape_cellfeed}}; + \code{\link{gs_simplify_cellfeed}} +} + diff --git a/man/gs_read_listfeed.Rd b/man/gs_read_listfeed.Rd new file mode 100644 index 0000000..022528e --- /dev/null +++ b/man/gs_read_listfeed.Rd @@ -0,0 +1,56 @@ +% Generated by roxygen2 (4.1.1): do not edit by hand +% Please edit documentation in R/gs_read_listfeed.R +\name{gs_read_listfeed} +\alias{gs_read_listfeed} +\title{Read data via the "list feed"} +\usage{ +gs_read_listfeed(ss, ws = 1, verbose = TRUE) +} +\arguments{ +\item{ss}{a registered Google spreadsheet, i.e. a \code{\link{googlesheet}} +object} + +\item{ws}{positive integer or character string specifying index or title, +respectively, of the worksheet} + +\item{verbose}{logical; do you want informative messages?} +} +\value{ +a tbl_df +} +\description{ +Gets data via the "list feed", which assumes populated cells form a neat +rectangle. The list feed consumes data row by row. The first row is assumed +to hold variable or column names. The related function, +\code{\link{gs_read_csv}}, also returns data from a rectangle of cells, +but it is generally faster and more resilient to, e.g. empty rows, so use it +if you can. However, you may need to use this function if you are dealing +with an "old" Google Sheet, which \code{\link{gs_read_csv}} does not +support). Consult the Google Sheets API documentation for more details about +\href{https://developers.google.com/google-apps/spreadsheets/data#work_with_list-based_feeds}{the +"list feed"}. +} +\note{ +When you use the "list feed", the Sheets API transforms the variable or + column names like so: 'The column names are the header values of the + worksheet lowercased and with all non-alpha-numeric characters removed. For + example, if the cell A1 contains the value "Time 2 Eat!" the column name + would be "time2eat".' If this is intolerable to you, use a different + function to read the data. Or, at least, consume the first row via the cell + feed and manually restore the variable names \emph{post hoc}. +} +\examples{ +\dontrun{ +gap_ss <- gs_gap() # register the Gapminder example sheet +oceania_lf <- gs_read_listfeed(gap_ss, ws = "Oceania") +str(oceania_lf) +oceania_lf +} +} +\seealso{ +Other data.consumption.functions: \code{\link{gs_read_cellfeed}}; + \code{\link{gs_read_csv}}; \code{\link{gs_read}}; + \code{\link{gs_reshape_cellfeed}}; + \code{\link{gs_simplify_cellfeed}} +} + diff --git a/man/gs_reshape_cellfeed.Rd b/man/gs_reshape_cellfeed.Rd new file mode 100644 index 0000000..99a2e8d --- /dev/null +++ b/man/gs_reshape_cellfeed.Rd @@ -0,0 +1,40 @@ +% Generated by roxygen2 (4.1.1): do not edit by hand +% Please edit documentation in R/gs_reshape_cellfeed.R +\name{gs_reshape_cellfeed} +\alias{gs_reshape_cellfeed} +\title{Reshape data from the "cell feed"} +\usage{ +gs_reshape_cellfeed(x, col_names = TRUE, verbose = TRUE) +} +\arguments{ +\item{x}{a data.frame returned by \code{\link{gs_read_cellfeed}}} + +\item{col_names}{if \code{TRUE}, the first row of the input will be used as + the column names; if \code{FALSE}, column names will be X1, X2, etc.; if a +character vector, vector will be used as the column names} + +\item{verbose}{logical; do you want informative messages?} +} +\description{ +Reshape data from the "cell feed" and convert to a \code{tbl_df} +} +\examples{ +\dontrun{ +gap_ss <- gs_gap() # register the Gapminder example sheet +gs_read_cellfeed(gap_ss, "Asia", range = cell_rows(1:4)) +gs_reshape_cellfeed(gs_read_cellfeed(gap_ss, "Asia", range = cell_rows(1:4))) +gs_reshape_cellfeed(gs_read_cellfeed(gap_ss, "Asia", + range = cell_rows(2:4)), + col_names = FALSE) +gs_reshape_cellfeed(gs_read_cellfeed(gap_ss, "Asia", + range = cell_rows(2:4)), + col_names = paste0("yo", 1:6)) +} +} +\seealso{ +Other data.consumption.functions: \code{\link{gs_read_cellfeed}}; + \code{\link{gs_read_csv}}; + \code{\link{gs_read_listfeed}}; \code{\link{gs_read}}; + \code{\link{gs_simplify_cellfeed}} +} + diff --git a/man/gs_simplify_cellfeed.Rd b/man/gs_simplify_cellfeed.Rd new file mode 100644 index 0000000..d23b6a0 --- /dev/null +++ b/man/gs_simplify_cellfeed.Rd @@ -0,0 +1,66 @@ +% Generated by roxygen2 (4.1.1): do not edit by hand +% Please edit documentation in R/gs_simplify_cellfeed.R +\name{gs_simplify_cellfeed} +\alias{gs_simplify_cellfeed} +\title{Simplify data from the cell feed} +\usage{ +gs_simplify_cellfeed(x, convert = TRUE, as.is = TRUE, na.strings = "NA", + notation = c("A1", "R1C1", "none"), col_names = NULL) +} +\arguments{ +\item{x}{a data.frame returned by \code{\link{gs_read_cellfeed}}} + +\item{convert}{logical, indicating whether to attempt to convert the result +vector from character to something more appropriate, such as logical, +integer, or numeric; if TRUE, result is passed through \code{type.convert}; +if FALSE, result will be character} + +\item{as.is}{logical, passed through to the \code{as.is} argument of +\code{type.convert}} + +\item{na.strings}{a character vector of strings which are to be interpreted +as \code{NA} values} + +\item{notation}{character; the result vector can have names that reflect +which cell the data came from; this argument selects between the "A1" and +"R1C1" positioning notations; specify "none" to suppress names} + +\item{col_names}{blah blah} +} +\value{ +a named vector +} +\description{ +In some cases, you do not want to convert the data retrieved from the cell +feed into a data.frame via \code{\link{gs_reshape_cellfeed}}. Instead, you +want the data as an atomic vector. That's what this function does. Note that, +unlike \code{\link{gs_reshape_cellfeed}}, embedded empty cells will NOT +necessarily appear in this result. By default, the API does not transmit data +for these cells; \code{googlesheets} inserts these cells in +\code{\link{gs_reshape_cellfeed}} because it is necessary to give the data +rectangular shape. In contrast, empty cells will only appear in the output of +\code{gs_simplify_cellfeed} if they were already present in the data from the +cell feed, i.e. if the original call to \code{\link{gs_read_cellfeed}} had +argument \code{return_empty} set to \code{TRUE}. +} +\examples{ +\dontrun{ +gap_ss <- gs_gap() # register the Gapminder example sheet +gs_read_cellfeed(gap_ss, range = cell_rows(1)) +gs_simplify_cellfeed(gs_read_cellfeed(gap_ss, range = cell_rows(1))) +gs_simplify_cellfeed( + gs_read_cellfeed(gap_ss, range = cell_rows(1)), notation = "R1C1") + +gs_read_cellfeed(gap_ss, range = "A1:A10") +gs_simplify_cellfeed(gs_read_cellfeed(gap_ss, range = "A1:A10")) +gs_simplify_cellfeed(gs_read_cellfeed(gap_ss, range = "A1:A10"), + col_names = FALSE) +} +} +\seealso{ +Other data.consumption.functions: \code{\link{gs_read_cellfeed}}; + \code{\link{gs_read_csv}}; + \code{\link{gs_read_listfeed}}; \code{\link{gs_read}}; + \code{\link{gs_reshape_cellfeed}} +} + diff --git a/man/gs_upload.Rd b/man/gs_upload.Rd index 4319ba4..c71ffa7 100644 --- a/man/gs_upload.Rd +++ b/man/gs_upload.Rd @@ -26,7 +26,7 @@ This function calls the write.csv(head(iris, 5), "iris.csv", row.names = FALSE) iris_ss <- gs_upload("iris.csv") iris_ss -get_via_lf(iris_ss) +gs_read_listfeed(iris_ss) file.remove("iris.csv") gs_delete(iris_ss) } diff --git a/man/gs_ws_delete.Rd b/man/gs_ws_delete.Rd index e7efad9..e62f978 100644 --- a/man/gs_ws_delete.Rd +++ b/man/gs_ws_delete.Rd @@ -26,8 +26,8 @@ The worksheet and all of its contents will be removed from the spreadsheet. gap_ss <- gs_copy(gs_gap(), to = "gap_copy") gs_ws_ls(gap_ss) gap_ss <- gs_ws_new(gap_ss, "new_stuff") -gap_ss <- edit_cells(gap_ss, "new_stuff", input = head(iris), header = TRUE, - trim = TRUE) +gap_ss <- gs_edit_cells(gap_ss, "new_stuff", input = head(iris), + header = TRUE, trim = TRUE) gap_ss gap_ss <- gs_ws_delete(gap_ss, "new_stuff") gs_ws_ls(gap_ss) diff --git a/man/gs_ws_new.Rd b/man/gs_ws_new.Rd index c39b9a8..4d0f90a 100644 --- a/man/gs_ws_new.Rd +++ b/man/gs_ws_new.Rd @@ -20,7 +20,7 @@ Sheets default is 1000} \item{col_extent}{integer for new column extent; if unspecified, the Google Sheets default is 26} -\item{...}{optional arguments passed along to \code{\link{edit_cells}} in +\item{...}{optional arguments passed along to \code{\link{gs_edit_cells}} in order to populate the new worksheet with data} \item{verbose}{logical; do you want informative messages?} @@ -44,11 +44,11 @@ worksheet in the sheet. \details{ We anticipate that \strong{if} the user wants to control the extent of the new worksheet, it will be by providing input data and specifying `trim = -TRUE` (see \code{\link{edit_cells}}) or by specifying \code{row_extent} and -\code{col_extent} directly. But not both ... although we won't stop you. In -that case, note that explicit worksheet sizing occurs before data insertion. -If data insertion triggers any worksheet resizing, that will override any -usage of \code{row_extent} or \code{col_extent}. +TRUE` (see \code{\link{gs_edit_cells}}) or by specifying \code{row_extent} +and \code{col_extent} directly. But not both ... although we won't stop you. +In that case, note that explicit worksheet sizing occurs before data +insertion. If data insertion triggers any worksheet resizing, that will +override any usage of \code{row_extent} or \code{col_extent}. } \examples{ \dontrun{ diff --git a/man/gs_ws_resize.Rd b/man/gs_ws_resize.Rd index 2fbedc1..4307a9e 100644 --- a/man/gs_ws_resize.Rd +++ b/man/gs_ws_resize.Rd @@ -35,14 +35,14 @@ Setting rows and columns to less than the current worksheet dimensions \examples{ \dontrun{ yo <- gs_new("yo") -yo <- edit_cells(yo, input = head(iris), header = TRUE, trim = TRUE) -get_via_csv(yo) +yo <- gs_edit_cells(yo, input = head(iris), header = TRUE, trim = TRUE) +gs_read_csv(yo) yo <- gs_ws_resize(yo, ws = "Sheet1", row_extent = 5, col_extent = 4) -get_via_csv(yo) +gs_read_csv(yo) yo <- gs_ws_resize(yo, ws = 1, row_extent = 3, col_extent = 3) -get_via_csv(yo) +gs_read_csv(yo) yo <- gs_ws_resize(yo, row_extent = 2, col_extent = 2) -get_via_csv(yo) +gs_read_csv(yo) gs_delete(yo) } } diff --git a/man/reshape_cf.Rd b/man/reshape_cf.Rd deleted file mode 100644 index 0cd5ecd..0000000 --- a/man/reshape_cf.Rd +++ /dev/null @@ -1,31 +0,0 @@ -% Generated by roxygen2 (4.1.1): do not edit by hand -% Please edit documentation in R/consume-data.R -\name{reshape_cf} -\alias{reshape_cf} -\title{Reshape cell-level data and convert to data.frame} -\usage{ -reshape_cf(x, header = TRUE) -} -\arguments{ -\item{x}{a data.frame returned by \code{\link{get_via_cf}}} - -\item{header}{logical indicating whether first row should be taken as - variable names} -} -\description{ -Reshape cell-level data and convert to data.frame -} -\examples{ -\dontrun{ -gap_ss <- gs_gap() # register the Gapminder example sheet -get_via_cf(gap_ss, "Asia", max_row = 4) -reshape_cf(get_via_cf(gap_ss, "Asia", max_row = 4)) -} -} -\seealso{ -Other data.consumption.functions: \code{\link{get_cells}}; - \code{\link{get_col}}; \code{\link{get_row}}; - \code{\link{get_via_cf}}; \code{\link{get_via_csv}}; - \code{\link{get_via_lf}}; \code{\link{simplify_cf}} -} - diff --git a/man/simplify_cf.Rd b/man/simplify_cf.Rd deleted file mode 100644 index 14655cb..0000000 --- a/man/simplify_cf.Rd +++ /dev/null @@ -1,57 +0,0 @@ -% Generated by roxygen2 (4.1.1): do not edit by hand -% Please edit documentation in R/consume-data.R -\name{simplify_cf} -\alias{simplify_cf} -\title{Simplify data from the cell feed} -\usage{ -simplify_cf(x, convert = TRUE, as.is = TRUE, notation = c("A1", "R1C1"), - header = NULL) -} -\arguments{ -\item{x}{a data.frame returned by \code{\link{get_via_cf}}} - -\item{convert}{logical, indicating whether to attempt to convert the result -vector from character to something more appropriate, such as logical, -integer, or numeric; if TRUE, result is passed through \code{type.convert}; -if FALSE, result will be character} - -\item{as.is}{logical, passed through to the \code{as.is} argument of -\code{type.convert}} - -\item{notation}{character; the result vector will have names that reflect - which cell the data came from; this argument selects the positioning - notation, i.e. "A1" vs. "R1C1"} - -\item{header}{logical indicating whether first row should be taken as - variable names} -} -\value{ -a named vector -} -\description{ -In some cases, you might not want to convert the data retrieved from the cell -feed into a data.frame via \code{\link{reshape_cf}}. You might prefer it as -an atomic vector. That's what this function does. Note that, unlike -\code{\link{reshape_cf}}, empty cells will NOT necessarily appear in this -result. By default, the API does not transmit data for these cells; -\code{googlesheets} inserts these cells in \code{\link{reshape_cf}} because -it is necessary to give the data rectangular shape. In contrast, empty cells -will only appear in the output of \code{simplify_cf} if they were already -present in the data from the cell feed, i.e. if the original call to -\code{\link{get_via_cf}} had argument \code{return_empty} set to \code{TRUE}. -} -\examples{ -\dontrun{ -gap_ss <- gs_gap() # register the Gapminder example sheet -get_row(gap_ss, row = 1) -simplify_cf(get_row(gap_ss, row = 1)) -simplify_cf(get_row(gap_ss, row = 1), notation = "R1C1") -} -} -\seealso{ -Other data.consumption.functions: \code{\link{get_cells}}; - \code{\link{get_col}}; \code{\link{get_row}}; - \code{\link{get_via_cf}}; \code{\link{get_via_csv}}; - \code{\link{get_via_lf}}; \code{\link{reshape_cf}} -} - diff --git a/tests/testthat/gap_sheet5_gs_read_cellfeed.rds b/tests/testthat/gap_sheet5_gs_read_cellfeed.rds new file mode 100644 index 0000000000000000000000000000000000000000..0d54a2426fe6de0bfb7d8391f1e76c052565f450 GIT binary patch literal 1469 zcmbu(eK->c90zbA)D~kks|c5)=ZVsTf;lB%`^9H$A9&=sN$gN z<8=YMigwlTEjm2Qu+6sn>~PJeNX5u%+&N!T2CklGk>JUGb(iPH_OCl7E=sL-sq;u} z#Mbln%RFh=8?vBk|GJanVtT=w+&Xv4Rx;JFMN_-)DHI9>YG|m@sGL4ym=;i#_9Y-1 z09qEOA0OHRbATEEwKX(pG!ADv^c^f2iURJ@&~Z&S=>y@lVCqm+pe|j@HPfUY6q*e) zh3Wyf(A8ZtOgQ1SZc+yzF)fj;T1#BZ6gex3UvFeXvaA=6t)h z*-ZR*jv}(Mr>u6#OuUveBeQE_#W|zalhX*ov0;+zDDxNv+S^rLitMhGt)&0y zy5Iw!NDuRp$sZj`pVtmm?bEx%EGtPrH!-mLWs8vBnA6%6Wq9=#c8B&^2Le4>1gmah zj@BlJ7Irit?+P>NAv@ycrcBLwv5D6H9gc^u(_JGrCmlSrHRiClg-yZA=b7uwFcIzA zeJ@*|8bVo-(>2ZPmvQr2OCc*9#_QJ13z5Z>Ju`5|M4ZKZ_N|jkM|c9qj)WgtucZy$ z&vxezgg;;TFhP(s*kIL5L>H)FVU-E_{`EosMNr)IyLm1O5Vt=iulh{e+xl4FVUxl@Vx$SC(4;+{MH>;jF<4i#7r)#7}4O&oOq1VVa;k4L&6sBsurIn!j0L#h)E&OzFC(YZddf ZeRQg#Bal5_#6fSU{&J~2(Az*o8dU|?WkU}gf6%s?iyFpyvaVgVqQU|?b31k#+2rNt#h zi8+~xLtlW~FST4DDm5`DF)wBK38BUtyMJ)gf?qE0FQV zA@Eol2si}(hVX;pV>G@V>URiQ521sBx`0|8f|IXv*%mBu2;K_OA0iGhFT@`xy!g69 zU<6QgjHW~2gvCIGg${w|A^HO!f-QCk`~i^<;sYD#5L61}YJ77DngTUn6k=|0vM~ra z1RsZ(8^X&M#v(Bp9$qc;co?P_?_^lA{RYEU4GxBr0xKA<9r(iVe4_`$4}ph_TnBzK zN_$i@YB{Z7GzSKQT!9vY63cuB)gR9oG#6ZB(Aju~!NBVYgTdcf3`TR;F_@HOF<3}Q zGgvP?%3yca8XjJUXP*Irvkpg-E`!~3{LWmUbGRJN#Pb6^cG%%Uss_-x@(vfD_kh)1 zW_190+~I111<0cg*B%%Gy;bdSE4Bt44tKwQ0Gi9-@Zdr=IDC%G^8)+t`0LGJ^H1)E zgzp)9NI0DjX9dT{`7L+A`Y&2Yfz@B?KLg>vy8;f!D}A%T;@6fIgUz`y1MCh41{Pq! z%)kgucC2}cxv9m_OwI;Ni^=(=c_l@aumUeRKd&S+FEy_OD!@{inpgx=o0FNAnp#l+ z%;Pr@)1EdxIWQB3e>I2Me?;3J6-_@JmU+1oO<%U0H{ zOK%5(A1`(vjj-?_H|fVcYwtbhbWWELVvu&rAVynl+XFTA)wofkMLNV(>)q(v5f4`6 zO+PSsU2K-|OT^+gc|UW4d=gK_PcV91!gkEVPu{qDD*>eyqD2KoD-(3(Nl^F|sbc@^ zGLkaGXWPb1fEQo{AaNLS9x@pEJh2x50orRW;_91y3NgEGZZC4il+x>pK*!i)aON`) zv8H9z;Kl6yYwGy2PS49!HgC!;PoW{Gh@Zn&t=$RLjotBrC00at&&B_&g9FzIF?TAO zN>VQY?Lof=&C+l;hD{ChDnL@npoTiByGE^Qjc)N;I?b~{S+CPe0ijy}b>3Z3q;txs z)a6ZzZkEp(^|dcvE+Z~9M@T%%bl(g_q8qJ1Amyn($7mXBPR1WQk5yWZKS{6p+y_0{{Th2C4A? diff --git a/tests/testthat/iris_pvt_get_via_cf.rds b/tests/testthat/iris_pvt_get_via_cf.rds deleted file mode 100644 index 675377620261185dd6b49d72d15ea10a59551d4b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 490 zcmb2|=3oE==A)C(=3Oxmahtn8bGgV$q5cDQN`-SeQav^$H_l*vOFsBJ_z?k?78@J9FT$=?(-Q+U2yz=BpP+zS^Ie z9BE2(Iy2P7j0+8gYt-g{>-d-4xBunqsdX8d}Yu5r(ykk z$D5?D3(T$W@*j3Mx~{*mC$_p-rED(uy(8;(cE`=Gy}zqdr_OqT`uyjjkNaL`oT@kY ze9^}(`0v>-S6)0lZ@F43TP5!LtfLue!oe;7R<3;h=2PK+k^9^4ziFMn-{gOLtqfoL z{K1-(pvgJxl*?RrPDL&5Ie=Z(E!B__q7*4=Hm`FR#ljTei9Kf7_?-t8=fZ z&3yN&@7U6LYlZfooBA+uM$eu3doIqkKKgXx_1yG3jBig{-FXvx^;1adm-6D6Z{{-> Ly|B<_XJ7yT(z^BC diff --git a/tests/testthat/iris_pvt_get_via_lf.rds b/tests/testthat/iris_pvt_get_via_lf.rds index 3955dee103149813f5e616856dc48d55d5a7184e..5605babcc822467b22a19e9eb7e5c02724dce565 100644 GIT binary patch literal 252 zcmV8dU|?WkU}gf6%s?iyFpyvcVm=^da}Y@b0|()mATY~8 z=nM#)br6Dzi!gwJgD6BEu8#*Q&H<%4q53(X`gx!U#8dU|?WkU}j`uU}6R`nT3G_D-iPmF`I)(8W=bT&jf*4 z4nk)@;H-lXR9u7s3>-uu@^F1TP;m|@%?Z`d0oBg~rTLA)0H)9W8^l~V{RN`V{s+Vy zxcF~0eJ3I26Qpm#-6;Ul$H2nC38ITrOY(~osl;PoVFL0Pp`xsLiMgr8Pzzb1f;`2k z1&KL&IjMQ+B^gjLZirZUW(r)0ry#WiLm5O2Ss8nAL27a)$P!eWlXDV_VKy_v99fc- z6Q7a>`2( Def(#M diff --git a/tests/testthat/iris_pvt_gs_read_cellfeed.rds b/tests/testthat/iris_pvt_gs_read_cellfeed.rds new file mode 100644 index 0000000000000000000000000000000000000000..a80cc27886d8eb984f65c2b13359cd8326cbb29b GIT binary patch literal 482 zcmb2|=3oE==A)Ax^Jg1Kw8_p{;(Oh5>Nnvscjn{^eLOqf2AH;g729|uR(azxt!cX+ z|9kQ8l#%*D{q+);t_AbY|N7%`b$G{24j5JsfABoee&v)WpJ4G1PafIK6<+`+{wpH zNrKZ-=<>uFy+>+P&bc+67r7v}IsD8=^V*#z{iVWnX8iZx)K0H1K2>`&Z^EuUX5ZFt z3hq07Zd!lZUejFjsa9WVr)Yh7J2miywYq)FzMY@1-H46;&F=YQ<5#|a^@|r(>(`!)ERA3MVb{4cRmPGtlB8s2SDn(ES9boP^WVF%KVEus*X>H& zfB(Of$hm92celm9UK)S7E;nqp_5PW&ODi_6%$2=oyR()3^&I={+Pk;*zMS^y=g&{~ zB&2!Ezq;?Xs;jtrewSJA{TF`@tlqWpAJ4m5n|0;;vI^I1de&Y#`F=ga17_AE0_+S7 E0HLh$c>n+a literal 0 HcmV?d00001 diff --git a/tests/testthat/iris_pvt_gs_read_listfeed.rds b/tests/testthat/iris_pvt_gs_read_listfeed.rds new file mode 100644 index 0000000000000000000000000000000000000000..5605babcc822467b22a19e9eb7e5c02724dce565 GIT binary patch literal 252 zcmV8dU|?WkU}gf6%s?iyFpyvcVm=^da}Y@b0|()mATY~8 z=nM#)br6Dzi!gwJgD6BEu8#*Q&H<%4q53(X`gx!U#% as_character_vector(), "character") + expect_is(x %>% as_character_vector(col_names = FALSE), "character") } expect_ok_as_input(-3:3) @@ -23,23 +23,28 @@ test_that("Input converts to character vector (or not)", { tmp <- iris %>% head() expect_ok_as_input(tmp) - tmp2 <- tmp %>% as_character_vector() + tmp2 <- tmp %>% as_character_vector(col_names = FALSE) expect_equivalent(tmp2[seq_len(ncol(iris))], iris[1, ] %>% t() %>% drop()) - tmp3 <- tmp %>% as_character_vector(header = TRUE) + tmp3 <- tmp %>% as_character_vector(col_names = TRUE) expect_identical(tmp3[seq_len(ncol(iris))], names(iris)) - expect_error(rnorm %>% as_character_vector(), "not suitable as input") - expect_error(ss %>% as_character_vector(), "not suitable as input") - expect_error(array(1:9, dim = rep(3,3)) %>% as_character_vector(), + expect_error(rnorm %>% as_character_vector(col_names = FALSE), + "not suitable as input") + expect_error(ss %>% as_character_vector(col_names = FALSE), + "not suitable as input") + expect_error(array(1:9, dim = rep(3,3)) %>% + as_character_vector(col_names = FALSE), "Input has more than 2 dimensions") }) test_that("Single cell can be updated", { - expect_message(ss <- edit_cells(ss, ws, "eggplant", "A1"), + expect_message(ss <- gs_edit_cells(ss, ws, "eggplant", "A1"), "successfully updated") Sys.sleep(1) - tmp <- ss %>% get_cells(ws, "A1") %>% simplify_cf(header = FALSE) + tmp <- ss %>% + gs_read_cellfeed(ws, range = "A1") %>% + gs_simplify_cellfeed(col_names = FALSE) expect_identical(tmp, c(A1 = "eggplant")) }) @@ -51,7 +56,7 @@ test_that("Cell update can force resize of worksheet", { Sys.sleep(1) # force worksheet extent to be increased - expect_message(ss <- edit_cells(ss, ws, "Way out there!", "R1C30"), + expect_message(ss <- gs_edit_cells(ss, ws, "Way out there!", "R1C30"), "dimensions changed") Sys.sleep(1) expect_equal(ss %>% gs_ws(ws) %>% `[[`("col_extent"), 30) @@ -67,26 +72,26 @@ iris_ish$Species <- iris_ish$Species %>% as.character() test_that("2-dimensional things can be uploaded", { # update with empty strings to "clear" cells - tmp <- ss %>% get_via_cf(ws) + tmp <- ss %>% gs_read_cellfeed(ws) if(nrow(tmp) > 0) { input <- matrix("", nrow = max(tmp$row), ncol = max(tmp$col)) - ss <- ss %>% edit_cells(ws, input) + ss <- ss %>% gs_edit_cells(ws, input) Sys.sleep(1) - tmp <- ss %>% get_via_cf(ws) + tmp <- ss %>% gs_read_cellfeed(ws) } expect_equal(dim(tmp), c(0, 5)) - # update w/ a data.frame, header = FALSE - ss <- ss %>% edit_cells(ws, iris_ish) + # update w/ a data.frame, col_names = FALSE + ss <- ss %>% gs_edit_cells(ws, iris_ish, col_names = FALSE) Sys.sleep(1) - tmp <- ss %>% get_via_cf(ws) %>% reshape_cf(header = FALSE) + tmp <- ss %>% gs_read(ws, header = FALSE) # header goes to read.csv() names(tmp) <- names(iris_ish) # I know these disagree, so just equate them expect_equivalent(tmp, iris_ish) - # update w/ a data.frame, header = TRUE - ss <- ss %>% edit_cells(ws, iris_ish, header = TRUE) + # update w/ a data.frame, col_names = TRUE + ss <- ss %>% gs_edit_cells(ws, iris_ish) Sys.sleep(1) - tmp <- ss %>% get_via_cf(ws) %>% reshape_cf() + tmp <- ss %>% gs_read(ws) expect_identical(tmp, iris_ish) }) @@ -96,15 +101,17 @@ test_that("Vectors can be uploaded", { ss <- gs_key(ss$sheet_key) # byrow = FALSE - ss <- ss %>% edit_cells(ws, LETTERS[1:5], "A8") + ss <- ss %>% gs_edit_cells(ws, LETTERS[1:5], "A8") Sys.sleep(2) - tmp <- ss %>% get_via_cf(ws, min_row = 7) %>% simplify_cf() + tmp <- ss %>% gs_read_cellfeed(ws, range = "A8:A12") %>% + gs_simplify_cellfeed() expect_equivalent(tmp, LETTERS[1:5]) # byrow = TRUE - ss <- ss %>% edit_cells(ws, LETTERS[5:1], "A15", byrow = TRUE) + ss <- ss %>% gs_edit_cells(ws, LETTERS[5:1], "A15", byrow = TRUE) Sys.sleep(2) - tmp <- ss %>% get_via_cf(ws, min_row = 15) %>% simplify_cf() + tmp <- ss %>% gs_read_cellfeed(ws, range = "A15:E15") %>% + gs_simplify_cellfeed() expect_equivalent(tmp, LETTERS[5:1]) }) @@ -112,15 +119,10 @@ test_that("Vectors can be uploaded", { test_that("We can trim worksheet extent to fit uploaded data", { ws <- "for_resizing" - ss <- ss %>% edit_cells(ws, iris_ish, trim = TRUE) - expect_equal(nrow(iris_ish), ss$ws$row_extent[ss$ws$ws_title == ws]) + ss <- ss %>% gs_edit_cells(ws, iris_ish, trim = TRUE) + expect_equal(nrow(iris_ish) + 1, ss$ws$row_extent[ss$ws$ws_title == ws]) expect_equal(ncol(iris_ish), ss$ws$col_extent[ss$ws$ws_title == ws]) }) -delete_me <- gs_ls(regex = TEST, verbose = FALSE) -if(!is.null(delete_me)) { - lapply(delete_me$sheet_key, function(x) { - gs_delete(gs_key(x, verbose = FALSE), verbose = FALSE) - }) -} +gs_grepdel(TEST, verbose = FALSE) diff --git a/tests/testthat/test-consume-data-private.R b/tests/testthat/test-consume-data-private.R index 806b855..c4689c6 100644 --- a/tests/testthat/test-consume-data-private.R +++ b/tests/testthat/test-consume-data-private.R @@ -11,20 +11,22 @@ ss <- gs_ws_feed(iris_pvt_ws_feed, verbose = FALSE) test_that("We can get all data from the list feed (pvt)", { - expect_equal_to_reference(get_via_lf(ss), "iris_pvt_get_via_lf.rds") + expect_equal_to_reference(gs_read_listfeed(ss), + "iris_pvt_gs_read_listfeed.rds") }) test_that("We can get all data from the cell feed (pvt)", { - expect_equal_to_reference(get_via_cf(ss), "iris_pvt_get_via_cf.rds") + expect_equal_to_reference(gs_read_cellfeed(ss), + "iris_pvt_gs_read_cellfeed.rds") }) test_that("We can get all data from the exportcsv link (pvt)", { - dat1 <- get_via_csv(ss) + dat1 <- gs_read_csv(ss) names(dat1) <- dat1 %>% names() %>% tolower() - expect_equal_to_reference(dat1, "iris_pvt_get_via_lf.rds") + expect_equal_to_reference(dat1, "iris_pvt_gs_read_listfeed.rds") }) diff --git a/tests/testthat/test-gs-create-delete-copy.R b/tests/testthat/test-gs-create-delete-copy.R index 78bc1d2..96ada6e 100644 --- a/tests/testthat/test-gs-create-delete-copy.R +++ b/tests/testthat/test-gs-create-delete-copy.R @@ -37,8 +37,7 @@ test_that("Spreadsheet can be created and populated at once", { expect_message( new_ss <- - gs_new(sheet_title, "yo!", input = head(iris), - trim = TRUE, header = TRUE), "created") + gs_new(sheet_title, "yo!", input = head(iris), trim = TRUE), "created") expect_is(new_ss, "googlesheet") expect_identical(new_ss %>% gs_ws_ls(), "yo!") expect_identical(new_ss$ws$row_extent, 7L) @@ -87,5 +86,4 @@ test_that("gs_delete() throws error on non-googlesheet input", { }) - gs_grepdel(TEST, verbose = FALSE) diff --git a/tests/testthat/test-inspect.R b/tests/testthat/test-inspect.R index 86243f7..3025d43 100644 --- a/tests/testthat/test-inspect.R +++ b/tests/testthat/test-inspect.R @@ -1,14 +1,16 @@ context("inspect data frames") test_that("Data frames are plotted as ggplot objects", { - + df_with_empty <- data.frame(A = c(1:10), B = c(LETTERS[1:5], rep(NA, 5))) - + expect_is(gs_inspect(df_with_empty), "ggplot") - - expect_is(readRDS("gap_sheet5_get_via_cf.rds") %>% gs_inspect(), "ggplot") - expect_is(readRDS("gap_sheet5_get_via_lf.rds") %>% gs_inspect(), "ggplot") + + expect_is(readRDS("gap_sheet5_gs_read_cellfeed.rds") %>% gs_inspect(), + "ggplot") + expect_is(readRDS("gap_sheet5_gs_read_listfeed.rds") %>% gs_inspect(), + "ggplot") expect_is(readRDS("pts_special_chars.rds") %>% gs_inspect(), "ggplot") - + expect_error(gs_inspect(c(1:10)), "is not TRUE") -}) \ No newline at end of file +}) diff --git a/tests/testthat/test-ws-edits.R b/tests/testthat/test-ws-edits.R index 04a2560..5144efc 100644 --- a/tests/testthat/test-ws-edits.R +++ b/tests/testthat/test-ws-edits.R @@ -13,7 +13,7 @@ test_that("Add a new worksheet", { new_ws_index <- ss_before$n_ws + 1 expect_equal(new_ws_index, ss_after$n_ws) - expect_equal(ss_after$ws[new_ws_index, "row_extent"], 6L) + expect_equal(ss_after$ws[new_ws_index, "row_extent"], 7L) expect_equal(ss_after$ws[new_ws_index, "col_extent"], 5L) expect_equal(ss_after$ws[new_ws_index, "ws_title"], "Test Sheet") diff --git a/tests/testthat/test-yy-consume-data-public-selective.R b/tests/testthat/test-yy-consume-data-public-selective.R index 49f8218..ed71d2e 100644 --- a/tests/testthat/test-yy-consume-data-public-selective.R +++ b/tests/testthat/test-yy-consume-data-public-selective.R @@ -3,65 +3,59 @@ context("consume data with public visibility, selectively") ## consuming data owned by someone else, namely rpackagetest ss <- gs_ws_feed(GAP_WS_FEED, lookup = FALSE, verbose = FALSE) -test_that("We can get data from specific cells using limits", { +test_that("We can get data from specific cells using a limits", { - ## fully specify individual limits + ## fully specify limits foo <- ss %>% - get_via_cf(ws = 5, min_row = 3, max_row = 5, min_col = 1, max_col = 3) + gs_read_cellfeed(ws = 5, range = cell_limits(c(3, 5), c(1, 3))) expect_equal(foo$cell, paste0(LETTERS[1:3], rep(3:5, each = 3))) - ## same limits, but provided as list - foo2 <- ss %>% - get_via_cf(ws = 5, - limits = list(min_row = 3, max_row = 5, min_col = 1, max_col = 3)) - expect_equal(foo, foo2) - ## partially specified limits foo <- ss %>% - get_via_cf(ws = "Oceania", min_row = 2 , min_col = 4, max_col = 4) + gs_read_cellfeed(ws = "Oceania", range = cell_limits(c(2, NA), c(4, 4))) expect_true(all(grepl("^D", foo$cell))) ## partially specified limits foo <- ss %>% - get_via_cf(ws = "Oceania", max_col = 3) + gs_read_cellfeed(ws = "Oceania", range = cell_limits(cols = c(NA, 3))) expect_true(all(grepl("^[ABC][0-9]+$", foo$cell))) }) test_that("We can get data from specific cells using rows and columns", { - foo <- ss %>% get_row(ws = "Africa", row = 2:3) + foo <- ss %>% gs_read_cellfeed(ws = "Africa", range = cell_rows(2:3)) expect_true(all(foo$row %in% 2:3)) - foo <- ss %>% get_row(ws = "Africa", row = 1) + foo <- ss %>% gs_read_cellfeed(ws = "Africa", range = cell_rows(1)) expect_true(all(foo$row == 1)) - foo <- ss %>% get_col(ws = "Oceania", col = 3:6) + foo <- ss %>% gs_read_cellfeed(ws = "Oceania", range = cell_cols(3:6)) expect_true(all(foo$col %in% 3:6)) - foo <- ss %>% get_col(ws = "Oceania", col = 4) + foo <- ss %>% gs_read_cellfeed(ws = "Oceania", range = cell_cols(4)) expect_true(all(foo$col == 4)) }) test_that("We can get data from specific cells using a range", { - foo <- ss %>% get_cells(ws = "Europe", range = "B3:C7") + foo <- ss %>% gs_read_cellfeed(ws = "Europe", range = "B3:C7") expect_is(foo, "tbl_df") expect_true(all(foo$col %in% 2:3)) expect_true(all(foo$row %in% 3:7)) - foo <- ss %>% get_cells(ws = "Europe", range = "R3C2:R7C3") + foo <- ss %>% gs_read_cellfeed(ws = "Europe", range = "R3C2:R7C3") expect_is(foo, "tbl_df") expect_true(all(foo$col %in% 2:3)) expect_true(all(foo$row %in% 3:7)) - foo <- ss %>% get_cells(ws = "Europe", range = "C4") + foo <- ss %>% gs_read_cellfeed(ws = "Europe", range = "C4") expect_is(foo, "tbl_df") expect_equal(foo$col, 3) expect_equal(foo$row, 4) - foo <- ss %>% get_cells(ws = "Europe", range = "R4C3") + foo <- ss %>% gs_read_cellfeed(ws = "Europe", range = "R4C3") expect_is(foo, "tbl_df") expect_equal(foo$col, 3) expect_equal(foo$row, 4) @@ -70,59 +64,47 @@ test_that("We can get data from specific cells using a range", { test_that("We decline to reshape data if there is none", { - foo <- ss %>% get_row(ws = "Oceania", row = 1) - expect_message(tmp <- foo %>% reshape_cf(), "No data to reshape!") - expect_null(tmp) + foo <- ss %>% gs_read_cellfeed(ws = "Oceania", range = cell_rows(1)) + expect_message(tmp <- foo %>% gs_reshape_cellfeed(), "No data to reshape!") + expect_identical(dim(tmp), rep(0L, 2)) }) test_that("We can simplify data from the cell feed", { - foo <- ss %>% get_row(ws = "Africa", row = 2:3) - expect_equal_to_reference(foo %>% simplify_cf(), "gap_africa_simplify_A1.rds") - expect_equal_to_reference(foo %>% simplify_cf(notation = "R1C1"), + foo <- ss %>% gs_read_cellfeed(ws = "Africa", range = cell_rows(2:3)) + expect_equal_to_reference(foo %>% gs_simplify_cellfeed(), + "gap_africa_simplify_A1.rds") + expect_equal_to_reference(foo %>% gs_simplify_cellfeed(notation = "R1C1"), "gap_africa_simplify_R1C1.rds") - foo <- ss %>% get_col(ws = "Oceania", col = 3) - foo_simple <- foo %>% simplify_cf() + foo <- ss %>% gs_read_cellfeed(ws = "Oceania", range = cell_cols(3)) + foo_simple <- foo %>% gs_simplify_cellfeed() expect_equivalent(foo_simple, rep(seq(from = 1952, to = 2007, by = 5), 2)) expect_equal(names(foo_simple), paste0("C", 1:24 + 1)) - foo_simple2 <- foo %>% simplify_cf(header = FALSE) + foo_simple2 <- foo %>% gs_simplify_cellfeed(col_names = FALSE) expect_is(foo_simple2, "character") - foo_simple3 <- foo %>% simplify_cf(convert = FALSE) + foo_simple3 <- foo %>% gs_simplify_cellfeed(convert = FALSE) expect_equivalent(foo_simple3, rep(seq(from = 1952, to = 2007, by = 5), 2) %>% as.character()) - yo <- ss %>% get_col(ws = "Oceania", col = 1) - yo_simple <- yo %>% simplify_cf(as.is = FALSE, convert = TRUE) + yo <- ss %>% gs_read_cellfeed(ws = "Oceania", range = cell_cols(1)) + yo_simple <- yo %>% gs_simplify_cellfeed(as.is = FALSE, convert = TRUE) expect_is(yo_simple, "factor") }) test_that("Validation is in force for row / columns limits in the cell feed", { - expect_error(get_via_cf(ss, min_row = "eggplant"), "Invalid input") - expect_error(get_via_cf(ss, max_col = factor(1)), "Invalid input") - expect_error(get_via_cf(ss, max_row = 1:3), "Invalid input") - expect_error(get_via_cf(ss, min_col = -100), "Invalid input") - - ## internal consistency - ## get rid of these once we fully embrace cellranger, which checks this and is - ## under testing itself? - expect_error(get_via_cf(ss, min_row = 5, max_row = 3), - "less than or equal to") - expect_error(get_via_cf(ss, min_col = 5, max_col = 3), - "less than or equal to") - ## external validity ## Africa is first worksheet: 625 rows by 6 columns - expect_error(get_via_cf(ss, min_row = 1001), "less than or equal to") - expect_error(get_via_cf(ss, max_row = 1001), "less than or equal to") - expect_error(get_via_cf(ss, min_col = 27), "less than or equal to") - expect_error(get_via_cf(ss, max_col = 27), "less than or equal to") + mess <- "less than or equal to" + expect_error(gs_read_cellfeed(ss, range = cell_rows(1001:1003)), mess) + expect_error(gs_read_cellfeed(ss, range = cell_rows(999:1003)), mess) + expect_error(gs_read_cellfeed(ss, range = cell_cols(27)), mess) + expect_error(gs_read_cellfeed(ss, range = cell_cols(24:30)), mess) }) - diff --git a/tests/testthat/test-yy-consume-data-public-tricky.R b/tests/testthat/test-yy-consume-data-public-tricky.R index 565fbee..fe10a0d 100644 --- a/tests/testthat/test-yy-consume-data-public-tricky.R +++ b/tests/testthat/test-yy-consume-data-public-tricky.R @@ -4,7 +4,7 @@ ss <- gs_key(pts_key, lookup = FALSE, visibility = "public", verbose = FALSE) test_that("We can handle embedded empty cells via csv", { - dat_csv <- ss %>% get_via_csv("embedded_empty_cells") + dat_csv <- ss %>% gs_read_csv("embedded_empty_cells") expect_equal(dim(dat_csv), c(7L, 7L)) expect_equal(which(is.na(dat_csv$country)), c(3L, 5L)) expect_equal(which(is.na(dat_csv$year)), c(5L, 6L)) @@ -14,7 +14,7 @@ test_that("We can handle embedded empty cells via csv", { expect_equal(which(is.na(dat_csv$lifeExp)), 5L) expect_equal(which(is.na(dat_csv$gdpPercap)), 4:5) - expect_equal(sapply(dat_csv, class), + expect_equal(vapply(dat_csv, class, character(1)), c(country = "character", year = "integer", pop = "integer", X = "logical", continent = "character", lifeExp = "numeric", gdpPercap = "numeric")) @@ -23,7 +23,7 @@ test_that("We can handle embedded empty cells via csv", { test_that("We can handle embedded empty cells via list feed", { - dat <- ss %>% get_via_lf("embedded_empty_cells") + dat <- ss %>% gs_read_listfeed("embedded_empty_cells") ## compare with csv! ## the blank column is dropped ## data reading stops at the empty row @@ -33,7 +33,7 @@ test_that("We can handle embedded empty cells via list feed", { expect_equal(which(is.na(dat$continent)), 2L) expect_equal(which(is.na(dat$gdppercap)), 4L) # gdppercap has been lowercased - expect_equal(sapply(dat, class), + expect_equal(vapply(dat, class, character(1)), c(country = "character", year = "integer", pop = "integer", continent = "character", lifeexp = "numeric", gdppercap = "numeric")) @@ -43,24 +43,24 @@ test_that("We can handle embedded empty cells via list feed", { test_that("We can handle embedded empty cells via cell feed", { ## for comparison - dat_csv <- ss %>% get_via_csv("embedded_empty_cells") + dat_csv <- ss %>% gs_read_csv("embedded_empty_cells") - raw_cf <- ss %>% get_via_cf("embedded_empty_cells") + raw_cf <- ss %>% gs_read_cellfeed("embedded_empty_cells") expect_equal(dim(raw_cf), c(38L, 5L)) - dat_cf <- raw_cf %>% reshape_cf() + dat_cf <- raw_cf %>% gs_reshape_cellfeed() expect_equal(dim(dat_cf), c(7L, 7L)) ## converting to data.frames for test because of this ## https://github.com/hadley/dplyr/issues/1095 ## bug (now fixed) where NA_character_ mishandled by all.equal class(dat_cf) <- "data.frame" class(dat_csv) <- "data.frame" - expect_identical(dat_cf, dat_csv %>% dplyr::rename(C4 = X)) + expect_identical(dat_cf, dat_csv %>% dplyr::rename(X4 = X)) raw_cf <- ss %>% - get_via_cf("embedded_empty_cells", return_empty = TRUE) + gs_read_cellfeed("embedded_empty_cells", return_empty = TRUE) expect_equal(dim(raw_cf), c(56L, 5L)) - dat_cf <- raw_cf %>% reshape_cf() + dat_cf <- raw_cf %>% gs_reshape_cellfeed() class(dat_cf) <- "data.frame" ## when return_empty = TRUE, empty character cells show up as "", not @@ -69,7 +69,7 @@ test_that("We can handle embedded empty cells via cell feed", { dat_cf <- dat_cf %>% dplyr::mutate(country = ifelse(country == "", NA, country), continent = ifelse(continent == "", NA, continent)) %>% - dplyr::rename(X = C4) + dplyr::rename(X = X4) expect_identical(dat_cf, dat_csv) @@ -77,7 +77,7 @@ test_that("We can handle embedded empty cells via cell feed", { test_that("Special Characters can be imported correctly", { - expect_equal_to_reference(get_via_lf(ss, ws = "special_chars"), + expect_equal_to_reference(gs_read_listfeed(ss, ws = "special_chars"), "pts_special_chars.rds") }) @@ -87,7 +87,7 @@ test_that("We can cope with tricky column names", { ## FYI this is as much about documenting what happens with weird names, as it ## is about testing - diabolical <- get_via_csv(ss, "diabolical_column_names") + diabolical <- gs_read_csv(ss, "diabolical_column_names") expect_identical(dim(diabolical), c(3L, 8L)) expect_identical(names(diabolical), c("id", "content", "X4.3", "X", "lifeExp", "X.1", @@ -104,7 +104,7 @@ test_that("We can cope with tricky column names", { # 3 mar gamma tres fall drei wed ## empty cells will not be here ... - diabolical <- get_via_cf(ss, "diabolical_column_names") + diabolical <- gs_read_cellfeed(ss, "diabolical_column_names") expect_identical(dim(diabolical), c(30L, 5L)) expect_identical(diabolical$cell_text[diabolical$row == 1L], c("id", "content", "4.3", "lifeExp", @@ -112,23 +112,24 @@ test_that("We can cope with tricky column names", { ## but reshaping will create variables when data exists, even in absence of ## column name diabolical <- diabolical %>% - reshape_cf() + gs_reshape_cellfeed() expect_identical(dim(diabolical), c(3L, 8L)) expect_identical(names(diabolical), - c("id", "content", "X4.3", "C4", "lifeExp", "C6", + c("id", "content", "X4.3", "X4", "lifeExp", "X6", "Fahrvergnügen", "Hey.space")) ## empty cells WILL be here ... - diabolical <- get_via_cf(ss, "diabolical_column_names", return_empty = TRUE) + diabolical <- + gs_read_cellfeed(ss, "diabolical_column_names", return_empty = TRUE) expect_identical(dim(diabolical), c(32L, 5L)) expect_identical(diabolical$cell_text[diabolical$row == 1L], c("id", "content", "4.3", "", "lifeExp", "", "Fahrvergnügen", "Hey space")) diabolical <- diabolical %>% - reshape_cf() + gs_reshape_cellfeed() expect_identical(dim(diabolical), c(3L, 8L)) expect_identical(names(diabolical), - c("id", "content", "X4.3", "C4", "lifeExp", "C6", + c("id", "content", "X4.3", "X4", "lifeExp", "X6", "Fahrvergnügen", "Hey.space")) }) diff --git a/tests/testthat/test-yy-consume-data-public-whole-sheets.R b/tests/testthat/test-yy-consume-data-public-whole-sheets.R index 6a4066f..630b51f 100644 --- a/tests/testthat/test-yy-consume-data-public-whole-sheets.R +++ b/tests/testthat/test-yy-consume-data-public-whole-sheets.R @@ -5,31 +5,33 @@ ss <- gs_ws_feed(GAP_WS_FEED, lookup = FALSE, verbose = FALSE) test_that("We can get all data from the list feed (pub)", { - expect_equal_to_reference(get_via_lf(ss, ws = 5), "gap_sheet5_get_via_lf.rds") + expect_equal_to_reference(gs_read_listfeed(ss, ws = 5), + "gap_sheet5_gs_read_listfeed.rds") }) test_that("We can get all data from the cell feed (pub)", { - expect_equal_to_reference(get_via_cf(ss, ws = 5), "gap_sheet5_get_via_cf.rds") + expect_equal_to_reference(gs_read_cellfeed(ss, ws = 5), + "gap_sheet5_gs_read_cellfeed.rds") }) test_that("We can get all data from the exportcsv link (pub)", { - dat1 <- get_via_csv(ss, ws = 5) + dat1 <- gs_read_csv(ss, ws = 5) names(dat1) <- dat1 %>% names() %>% tolower() - expect_equal_to_reference(dat1, "gap_sheet5_get_via_lf.rds") + expect_equal_to_reference(dat1, "gap_sheet5_gs_read_listfeed.rds") }) test_that("We can reshape data from the cell feed", { - oceania <- ss %>% get_via_cf(ws = "Oceania") + oceania <- ss %>% gs_read_cellfeed(ws = "Oceania") expect_true(all(names(oceania) %in% c("cell", "cell_alt", "row", "col", "cell_text"))) - y <- reshape_cf(oceania) + y <- gs_reshape_cellfeed(oceania) expect_equal(dim(y), c(24L, 6L)) expect_is(oceania$cell, "character") expect_is(oceania$row, "integer") @@ -38,17 +40,17 @@ test_that("We can reshape data from the cell feed", { expect_equal(names(y), c("country", "continent", "year", "lifeExp", "pop", "gdpPercap")) - z <- reshape_cf(oceania, header = FALSE) + z <- gs_reshape_cellfeed(oceania, col_names = FALSE) expect_equal(dim(z), c(25L, 6L)) expect_true(all(grepl("^X[0-9]+", names(z)))) expect_equal(unique(sapply(z, class)), "character") }) -test_that("We get no error from get_via_csv on an empty sheet (pub)", { +test_that("We get no error from gs_read_csv on an empty sheet (pub)", { pts_ss <- pts_key %>% gs_key(lookup = FALSE) - expect_is(tmp <- pts_ss %>% get_via_csv(ws = "empty"), "data.frame") + expect_is(tmp <- pts_ss %>% gs_read_csv(ws = "empty"), "data.frame") expect_identical(dim(tmp), rep(0L, 2)) }) diff --git a/vignettes/basic-usage.R b/vignettes/basic-usage.R index d030cc8..553ec67 100644 --- a/vignettes/basic-usage.R +++ b/vignettes/basic-usage.R @@ -3,13 +3,11 @@ library(googlesheets) suppressMessages(library(dplyr)) ## ----auth, include = FALSE----------------------------------------------- - -## look for .httr-oauth in pwd (assuming pwd is googlesheets) or two levels up -## (assuming pwd is googlesheets/tests/testthat) +## look for .httr-oauth in pwd (assuming pwd is googlesheets) or one level up +## (assuming pwd is googlesheets/vignettes) pwd <- getwd() one_up <- pwd %>% dirname() -two_up <- pwd %>% dirname() %>% dirname() -HTTR_OAUTH <- c(two_up, one_up, pwd) %>% file.path(".httr-oauth") +HTTR_OAUTH <- c(one_up, pwd) %>% file.path(".httr-oauth") HTTR_OAUTH <- HTTR_OAUTH[HTTR_OAUTH %>% file.exists()] if(length(HTTR_OAUTH) > 0) { @@ -17,7 +15,6 @@ if(length(HTTR_OAUTH) > 0) { file.copy(from = HTTR_OAUTH, to = ".httr-oauth", overwrite = TRUE) } - ## ----pre-clean, include = FALSE------------------------------------------ ## if a previous compilation of this document leaves anything behind, i.e. if it ## aborts, clean up Google Drive first @@ -44,27 +41,27 @@ ss2 ## ------------------------------------------------------------------------ gap -oceania_list_feed <- get_via_lf(gap, ws = "Oceania") +oceania_list_feed <- gs_read_listfeed(gap, ws = "Oceania") str(oceania_list_feed) oceania_list_feed ## ------------------------------------------------------------------------ -oceania_cell_feed <- get_via_cf(gap, ws = "Oceania") +oceania_cell_feed <- gs_read_cellfeed(gap, ws = "Oceania") str(oceania_cell_feed) head(oceania_cell_feed, 10) -oceania_reshaped <- reshape_cf(oceania_cell_feed) +oceania_reshaped <- gs_reshape_cellfeed(oceania_cell_feed) str(oceania_reshaped) head(oceania_reshaped, 10) ## ----createspreadsheet--------------------------------------------------- # Create a new empty spreadsheet by title gs_new("hi I am new here") -gs_ls() %>% filter(sheet_title == "hi I am new here") +gs_ls("hi I am new here") ## ----delete spreadsheet-------------------------------------------------- # Move spreadsheet to trash -gs_delete(gs_title("hi I am new here")) -gs_ls() %>% filter(sheet_title == "hi I am new here") +gs_grepdel("hi I am new here") +gs_ls("hi I am new here") ## ----new-sheet-new-ws-delete-ws------------------------------------------ gs_new("hi I am new here") @@ -72,8 +69,7 @@ x <- gs_title("hi I am new here") x x <- gs_ws_new(x, ws_title = "foo", row_extent = 10, col_extent = 10) x -gs_ws_delete(x, ws = "foo") -x <- gs_title("hi I am new here") +x <- gs_ws_delete(x, ws = "foo") x ## ----new-ws-rename-ws-delete-ws------------------------------------------ diff --git a/vignettes/basic-usage.Rmd b/vignettes/basic-usage.Rmd index aa9d55d..f4a352b 100644 --- a/vignettes/basic-usage.Rmd +++ b/vignettes/basic-usage.Rmd @@ -12,7 +12,7 @@ vignette: > \usepackage[utf8]{inputenc} --- -__NOTE__: The vignette is still under development. Stuff here is not written in stone. The [README](https://github.com/jennybc/googlesheets) on GitHub has gotten __alot more love recently__, so you should read that instead or in addition to this (2015-05-08). Seriously, we've only been making sure this thing compiles, but not updating the text. +__NOTE__: The vignette is still under development. Stuff here is not written in stone. The [README](https://github.com/jennybc/googlesheets) on GitHub has gotten __alot more love recently__, so you should read that instead or in addition to this (2015-05-30). Seriously, we've only been making sure this thing compiles, but not updating the text. ```{r load package} library(googlesheets) @@ -32,20 +32,17 @@ If you want to switch to a different Google account, run `gs_auth(new_user = TRU *In a hidden chunk, we are logging into Google as a user associated with this package, so we can work with some Google spreadsheets later in this vignette.* ```{r auth, include = FALSE} - -## look for .httr-oauth in pwd (assuming pwd is googlesheets) or two levels up -## (assuming pwd is googlesheets/tests/testthat) +## look for .httr-oauth in pwd (assuming pwd is googlesheets) or one level up +## (assuming pwd is googlesheets/vignettes) pwd <- getwd() one_up <- pwd %>% dirname() -two_up <- pwd %>% dirname() %>% dirname() -HTTR_OAUTH <- c(two_up, one_up, pwd) %>% file.path(".httr-oauth") +HTTR_OAUTH <- c(one_up, pwd) %>% file.path(".httr-oauth") HTTR_OAUTH <- HTTR_OAUTH[HTTR_OAUTH %>% file.exists()] if(length(HTTR_OAUTH) > 0) { HTTR_OAUTH <- HTTR_OAUTH[1] file.copy(from = HTTR_OAUTH, to = ".httr-oauth", overwrite = TRUE) } - ``` ```{r pre-clean, include = FALSE} @@ -116,7 +113,7 @@ Example of getting nice tabular data from the "list feed": ```{r} gap -oceania_list_feed <- get_via_lf(gap, ws = "Oceania") +oceania_list_feed <- gs_read_listfeed(gap, ws = "Oceania") str(oceania_list_feed) oceania_list_feed ``` @@ -126,15 +123,15 @@ If you wish, go look at the [Oceania worksheet from the Gapminder spreadsheet](h Example of getting the same data from the "cell feed". ```{r} -oceania_cell_feed <- get_via_cf(gap, ws = "Oceania") +oceania_cell_feed <- gs_read_cellfeed(gap, ws = "Oceania") str(oceania_cell_feed) head(oceania_cell_feed, 10) -oceania_reshaped <- reshape_cf(oceania_cell_feed) +oceania_reshaped <- gs_reshape_cellfeed(oceania_cell_feed) str(oceania_reshaped) head(oceania_reshaped, 10) ``` -Note that data from the cell feed comes back as a data.frame with one row per cell. We provide the function `reshape_cf()` to reshape this data into something tabular. +Note that data from the cell feed comes back as a data.frame with one row per cell. We provide the function `gs_reshape_cellfeed()` to reshape this data into something tabular. *To add, using row and column limits on the cell feed. All covered in the README.* @@ -185,15 +182,15 @@ or `gs_delete()` ```{r createspreadsheet} # Create a new empty spreadsheet by title gs_new("hi I am new here") -gs_ls() %>% filter(sheet_title == "hi I am new here") +gs_ls("hi I am new here") ``` Delete a spreadsheet with `gs_delete()`. This function operates on a registered `googlesheet`, so enclose your sheet identifying information in a suitable function. Here we specify (and delete) the above sheet by title, then confirm it is no longer in our sheet listing. ```{r delete spreadsheet} # Move spreadsheet to trash -gs_delete(gs_title("hi I am new here")) -gs_ls() %>% filter(sheet_title == "hi I am new here") +gs_grepdel("hi I am new here") +gs_ls("hi I am new here") ``` ### Add, delete, or rename a worksheet @@ -206,8 +203,7 @@ x <- gs_title("hi I am new here") x x <- gs_ws_new(x, ws_title = "foo", row_extent = 10, col_extent = 10) x -gs_ws_delete(x, ws = "foo") -x <- gs_title("hi I am new here") +x <- gs_ws_delete(x, ws = "foo") x ``` diff --git a/vignettes/basic-usage.html b/vignettes/basic-usage.html index ab6e291..0029b30 100644 --- a/vignettes/basic-usage.html +++ b/vignettes/basic-usage.html @@ -10,7 +10,7 @@ - + googlesheets Basic Usage @@ -54,7 +54,7 @@
@@ -79,7 +79,7 @@

2015-05-22

-

NOTE: The vignette is still under development. Stuff here is not written in stone. The README on GitHub has gotten alot more love recently, so you should read that instead or in addition to this (2015-05-08). Seriously, we’ve only been making sure this thing compiles, but not updating the text.

+

NOTE: The vignette is still under development. Stuff here is not written in stone. The README on GitHub has gotten alot more love recently, so you should read that instead or in addition to this (2015-05-30). Seriously, we’ve only been making sure this thing compiles, but not updating the text.

library(googlesheets)
 suppressMessages(library(dplyr))

This vignette shows the basic functionality of googlesheets.

@@ -101,19 +101,19 @@

List your spreadsheets

As an authenticated user, you can get a (partial) listing of accessible sheets. If you have not yet authenticated, you will be prompted to do so. If it’s been a while since you authenticated, you’ll see a message about refreshing a stale OAuth token.

my_sheets <- gs_ls()
 my_sheets
-
## Source: local data frame [36 x 10]
+
## Source: local data frame [40 x 10]
 ## 
 ##                 sheet_title        author perm version             updated
-## 1   EasyTweetSheet - Shared     m.hawksey    r     new 2015-05-23 05:33:43
-## 2  Ari's Anchor Text Scrap…      anahmani    r     old 2015-05-22 23:01:31
-## 3              #rhizo15 #tw     m.hawksey    r     new 2015-05-22 19:43:33
-## 4     All R Phylo Functions  omeara.brian    r     new 2015-05-20 18:34:43
-## 5                  ari copy      gspreadr   rw     old 2015-05-19 23:00:13
-## 6               gas_mileage      woo.kara    r     new 2015-05-17 00:00:12
-## 7  2014-05-10_seaRM-at-van…      gspreadr   rw     new 2015-05-11 04:19:08
-## 8  2014-05-10_seaRM-at-van…         jenny    r     new 2015-05-11 03:51:57
-## 9       test-gs-permissions      gspreadr   rw     new 2015-05-08 23:08:59
-## 10          #TalkPay Tweets      iskaldur    r     new 2015-05-02 06:25:14
+## 1  Copy of Twitter Archive…   joannazhaoo    r     new 2015-05-30 19:16:25
+## 2               TAGS v6.0ns     m.hawksey    r     new 2015-05-30 10:46:47
+## 3   EasyTweetSheet - Shared     m.hawksey    r     new 2015-05-30 16:44:08
+## 4              #rhizo15 #tw     m.hawksey    r     new 2015-05-30 07:53:02
+## 5  Ari's Anchor Text Scrap…      anahmani    r     new 2015-05-29 07:18:48
+## 6  Tweet Collector (TAGS v…      gspreadr   rw     new 2015-05-28 17:43:29
+## 7      test-gs-cars-private      gspreadr   rw     new 2015-05-27 17:48:34
+## 8     All R Phylo Functions  omeara.brian    r     new 2015-05-20 18:34:43
+## 9  test-gs-public-testing-…  rpackagetest    r     new 2015-05-20 01:32:27
+## 10                 ari copy      gspreadr   rw     new 2015-05-19 23:00:13
 ## ..                      ...           ...  ...     ...                 ...
 ## Variables not shown: sheet_key (chr), ws_feed (chr), alternate (chr), self
 ##   (chr), alt_key (chr)
@@ -127,7 +127,7 @@

Register a spreadsheet

## Sheet successfully identifed: "Gapminder"
gap
##                   Spreadsheet title: Gapminder
-##   Date of googlesheets registration: 2015-05-23 05:54:27 GMT
+##   Date of googlesheets registration: 2015-05-30 19:22:45 GMT
 ##     Date of last spreadsheet update: 2015-03-23 20:34:08 GMT
 ##                          visibility: private
 ##                         permissions: rw
@@ -158,7 +158,7 @@ 

Register a spreadsheet

## Sheet successfully identifed: "Gapminder"
ss2
##                   Spreadsheet title: Gapminder
-##   Date of googlesheets registration: 2015-05-23 05:54:28 GMT
+##   Date of googlesheets registration: 2015-05-30 19:22:46 GMT
 ##     Date of last spreadsheet update: 2015-03-23 20:34:08 GMT
 ##                          visibility: private
 ##                         permissions: rw
@@ -186,7 +186,7 @@ 

Consuming data from a worksheet

Example of getting nice tabular data from the “list feed”:

gap
##                   Spreadsheet title: Gapminder
-##   Date of googlesheets registration: 2015-05-23 05:54:27 GMT
+##   Date of googlesheets registration: 2015-05-30 19:22:45 GMT
 ##     Date of last spreadsheet update: 2015-03-23 20:34:08 GMT
 ##                          visibility: private
 ##                         permissions: rw
@@ -201,7 +201,7 @@ 

Consuming data from a worksheet

## Oceania: 25 x 6 ## ## Key: 1HT5B8SgkKqHdqHJmn5xiuaC04Ngb7dG9Tv94004vezA
-
oceania_list_feed <- get_via_lf(gap, ws = "Oceania") 
+
oceania_list_feed <- gs_read_listfeed(gap, ws = "Oceania") 
## Accessing worksheet titled "Oceania"
str(oceania_list_feed)
## Classes 'tbl_df', 'tbl' and 'data.frame':    24 obs. of  6 variables:
@@ -228,7 +228,7 @@ 

Consuming data from a worksheet

## .. ... ... ... ... ... ...

If you wish, go look at the Oceania worksheet from the Gapminder spreadsheet for comparison.

Example of getting the same data from the “cell feed”.

-
oceania_cell_feed <- get_via_cf(gap, ws = "Oceania") 
+
oceania_cell_feed <- gs_read_cellfeed(gap, ws = "Oceania") 
## Accessing worksheet titled "Oceania"
str(oceania_cell_feed)
## Classes 'tbl_df', 'tbl' and 'data.frame':    150 obs. of  5 variables:
@@ -252,7 +252,7 @@ 

Consuming data from a worksheet

## 8 B2 R2C2 2 2 Oceania ## 9 C2 R2C3 2 3 1952 ## 10 D2 R2C4 2 4 69.12
-
oceania_reshaped <- reshape_cf(oceania_cell_feed)
+
oceania_reshaped <- gs_reshape_cellfeed(oceania_cell_feed)
 str(oceania_reshaped)
## Classes 'tbl_df', 'tbl' and 'data.frame':    24 obs. of  6 variables:
 ##  $ country  : chr  "Australia" "Australia" "Australia" "Australia" ...
@@ -275,7 +275,7 @@ 

Consuming data from a worksheet

## 8 Australia Oceania 1987 76.32 16257249 21888.89 ## 9 Australia Oceania 1992 77.56 17481977 23424.77 ## 10 Australia Oceania 1997 78.83 18565243 26997.94
-

Note that data from the cell feed comes back as a data.frame with one row per cell. We provide the function reshape_cf() to reshape this data into something tabular.

+

Note that data from the cell feed comes back as a data.frame with one row per cell. We provide the function gs_reshape_cellfeed() to reshape this data into something tabular.

To add, using row and column limits on the cell feed. All covered in the README.

Stuff below partialy redundant with above

Eventually, you may want to read parts of the Google Sheets API version 3.0 documentation. The types of data access supported by the API determine what is possible and also what is (relatively) fast vs slow in the googlesheets package. The Sheets API uses the term “feed” much like other APIs will refer to an “endpoint”.

@@ -319,24 +319,22 @@

Add or delete spreadsheet

gs_new("hi I am new here")
## Sheet "hi I am new here" created in Google Drive.
 ## Worksheet dimensions: 1000 x 26.
-
gs_ls() %>% filter(sheet_title == "hi I am new here")
+
gs_ls("hi I am new here")
## Source: local data frame [1 x 10]
 ## 
 ##        sheet_title   author perm version             updated
-## 1 hi I am new here gspreadr   rw     new 2015-05-23 05:54:30
+## 1 hi I am new here gspreadr   rw     new 2015-05-30 19:22:48
 ## Variables not shown: sheet_key (chr), ws_feed (chr), alternate (chr), self
 ##   (chr), alt_key (chr)

Delete a spreadsheet with gs_delete(). This function operates on a registered googlesheet, so enclose your sheet identifying information in a suitable function. Here we specify (and delete) the above sheet by title, then confirm it is no longer in our sheet listing.

# Move spreadsheet to trash
-gs_delete(gs_title("hi I am new here"))
-
## Sheet successfully identifed: "hi I am new here"
+gs_grepdel("hi I am new here")
+
## Authentication will be used.
+## Sheet successfully identifed: "hi I am new here"
 ## Success. "hi I am new here" moved to trash in Google Drive.
-
gs_ls() %>% filter(sheet_title == "hi I am new here")
-
## Source: local data frame [0 x 10]
-## 
-## Variables not shown: sheet_title (chr), author (chr), perm (chr), version
-##   (chr), updated (time), sheet_key (chr), ws_feed (chr), alternate (chr),
-##   self (chr), alt_key (chr)
+
## [1] TRUE
+
gs_ls("hi I am new here")
+
## No matching sheets found.

Add, delete, or rename a worksheet

@@ -348,8 +346,8 @@

Add, delete, or rename a worksheet

## Sheet successfully identifed: "hi I am new here"
x
##                   Spreadsheet title: hi I am new here
-##   Date of googlesheets registration: 2015-05-23 05:54:36 GMT
-##     Date of last spreadsheet update: 2015-05-23 05:54:34 GMT
+##   Date of googlesheets registration: 2015-05-30 19:23:00 GMT
+##     Date of last spreadsheet update: 2015-05-30 19:22:56 GMT
 ##                          visibility: private
 ##                         permissions: rw
 ##                             version: new
@@ -358,14 +356,14 @@ 

Add, delete, or rename a worksheet

## (Title): (Nominal worksheet extent as rows x columns) ## Sheet1: 1000 x 26 ## -## Key: 1UhIFltdnN2516Z9CZJYQAT9jLl6VEYhT1FQ4aHBpEoc
+## Key: 1fnJfX4NlhZOmSFkDe9WyuAmfjjcsqrSRcR560QI_0Cc
x <- gs_ws_new(x, ws_title = "foo", row_extent = 10, col_extent = 10)
## Worksheet "foo" added to sheet "hi I am new here".
 ## Worksheet dimensions: 10 x 10.
x
##                   Spreadsheet title: hi I am new here
-##   Date of googlesheets registration: 2015-05-23 05:54:37 GMT
-##     Date of last spreadsheet update: 2015-05-23 05:54:36 GMT
+##   Date of googlesheets registration: 2015-05-30 19:23:01 GMT
+##     Date of last spreadsheet update: 2015-05-30 19:23:00 GMT
 ##                          visibility: private
 ##                         permissions: rw
 ##                             version: new
@@ -375,16 +373,14 @@ 

Add, delete, or rename a worksheet

## Sheet1: 1000 x 26 ## foo: 10 x 10 ## -## Key: 1UhIFltdnN2516Z9CZJYQAT9jLl6VEYhT1FQ4aHBpEoc
-
gs_ws_delete(x, ws = "foo")
+## Key: 1fnJfX4NlhZOmSFkDe9WyuAmfjjcsqrSRcR560QI_0Cc
+
x <- gs_ws_delete(x, ws = "foo")
## Accessing worksheet titled "foo"
 ## Worksheet "foo" deleted from sheet "hi I am new here".
-
x <- gs_title("hi I am new here")
-
## Sheet successfully identifed: "hi I am new here"
x
##                   Spreadsheet title: hi I am new here
-##   Date of googlesheets registration: 2015-05-23 05:54:39 GMT
-##     Date of last spreadsheet update: 2015-05-23 05:54:37 GMT
+##   Date of googlesheets registration: 2015-05-30 19:23:02 GMT
+##     Date of last spreadsheet update: 2015-05-30 19:23:01 GMT
 ##                          visibility: private
 ##                         permissions: rw
 ##                             version: new
@@ -393,7 +389,7 @@ 

Add, delete, or rename a worksheet

## (Title): (Nominal worksheet extent as rows x columns) ## Sheet1: 1000 x 26 ## -## Key: 1UhIFltdnN2516Z9CZJYQAT9jLl6VEYhT1FQ4aHBpEoc
+## Key: 1fnJfX4NlhZOmSFkDe9WyuAmfjjcsqrSRcR560QI_0Cc

To rename a worksheet, pass in the spreadsheet object, the worksheet’s current name and the new name you want it to be.

gs_ws_rename(x, "Sheet1", "First Sheet")
## Accessing worksheet titled "Sheet1"
diff --git a/vignettes/basic-usage.md b/vignettes/basic-usage.md
index 6660ea7..1f95add 100644
--- a/vignettes/basic-usage.md
+++ b/vignettes/basic-usage.md
@@ -2,7 +2,7 @@
 Joanna Zhao, Jenny Bryan  
 `r Sys.Date()`  
 
-__NOTE__: The vignette is still under development. Stuff here is not written in stone. The [README](https://github.com/jennybc/googlesheets) on GitHub has gotten __alot more love recently__, so you should read that instead or in addition to this (2015-05-08). Seriously, we've only been making sure this thing compiles, but not updating the text.
+__NOTE__: The vignette is still under development. Stuff here is not written in stone. The [README](https://github.com/jennybc/googlesheets) on GitHub has gotten __alot more love recently__, so you should read that instead or in addition to this (2015-05-30). Seriously, we've only been making sure this thing compiles, but not updating the text.
 
 
 ```r
@@ -48,19 +48,19 @@ my_sheets
 ```
 
 ```
-## Source: local data frame [36 x 10]
+## Source: local data frame [40 x 10]
 ## 
 ##                 sheet_title        author perm version             updated
-## 1   EasyTweetSheet - Shared     m.hawksey    r     new 2015-05-23 05:33:43
-## 2  Ari's Anchor Text Scrap…      anahmani    r     old 2015-05-22 23:01:31
-## 3              #rhizo15 #tw     m.hawksey    r     new 2015-05-22 19:43:33
-## 4     All R Phylo Functions  omeara.brian    r     new 2015-05-20 18:34:43
-## 5                  ari copy      gspreadr   rw     old 2015-05-19 23:00:13
-## 6               gas_mileage      woo.kara    r     new 2015-05-17 00:00:12
-## 7  2014-05-10_seaRM-at-van…      gspreadr   rw     new 2015-05-11 04:19:08
-## 8  2014-05-10_seaRM-at-van…         jenny    r     new 2015-05-11 03:51:57
-## 9       test-gs-permissions      gspreadr   rw     new 2015-05-08 23:08:59
-## 10          #TalkPay Tweets      iskaldur    r     new 2015-05-02 06:25:14
+## 1  Copy of Twitter Archive…   joannazhaoo    r     new 2015-05-30 19:16:25
+## 2               TAGS v6.0ns     m.hawksey    r     new 2015-05-30 10:46:47
+## 3   EasyTweetSheet - Shared     m.hawksey    r     new 2015-05-30 16:44:08
+## 4              #rhizo15 #tw     m.hawksey    r     new 2015-05-30 07:53:02
+## 5  Ari's Anchor Text Scrap…      anahmani    r     new 2015-05-29 07:18:48
+## 6  Tweet Collector (TAGS v…      gspreadr   rw     new 2015-05-28 17:43:29
+## 7      test-gs-cars-private      gspreadr   rw     new 2015-05-27 17:48:34
+## 8     All R Phylo Functions  omeara.brian    r     new 2015-05-20 18:34:43
+## 9  test-gs-public-testing-…  rpackagetest    r     new 2015-05-20 01:32:27
+## 10                 ari copy      gspreadr   rw     new 2015-05-19 23:00:13
 ## ..                      ...           ...  ...     ...                 ...
 ## Variables not shown: sheet_key (chr), ws_feed (chr), alternate (chr), self
 ##   (chr), alt_key (chr)
@@ -89,7 +89,7 @@ gap
 
 ```
 ##                   Spreadsheet title: Gapminder
-##   Date of googlesheets registration: 2015-05-23 05:54:27 GMT
+##   Date of googlesheets registration: 2015-05-30 19:22:45 GMT
 ##     Date of last spreadsheet update: 2015-03-23 20:34:08 GMT
 ##                          visibility: private
 ##                         permissions: rw
@@ -140,7 +140,7 @@ ss2
 
 ```
 ##                   Spreadsheet title: Gapminder
-##   Date of googlesheets registration: 2015-05-23 05:54:28 GMT
+##   Date of googlesheets registration: 2015-05-30 19:22:46 GMT
 ##     Date of last spreadsheet update: 2015-03-23 20:34:08 GMT
 ##                          visibility: private
 ##                         permissions: rw
@@ -176,7 +176,7 @@ gap
 
 ```
 ##                   Spreadsheet title: Gapminder
-##   Date of googlesheets registration: 2015-05-23 05:54:27 GMT
+##   Date of googlesheets registration: 2015-05-30 19:22:45 GMT
 ##     Date of last spreadsheet update: 2015-03-23 20:34:08 GMT
 ##                          visibility: private
 ##                         permissions: rw
@@ -194,7 +194,7 @@ gap
 ```
 
 ```r
-oceania_list_feed <- get_via_lf(gap, ws = "Oceania") 
+oceania_list_feed <- gs_read_listfeed(gap, ws = "Oceania") 
 ```
 
 ```
@@ -242,7 +242,7 @@ Example of getting the same data from the "cell feed".
 
 
 ```r
-oceania_cell_feed <- get_via_cf(gap, ws = "Oceania") 
+oceania_cell_feed <- gs_read_cellfeed(gap, ws = "Oceania") 
 ```
 
 ```
@@ -284,7 +284,7 @@ head(oceania_cell_feed, 10)
 ```
 
 ```r
-oceania_reshaped <- reshape_cf(oceania_cell_feed)
+oceania_reshaped <- gs_reshape_cellfeed(oceania_cell_feed)
 str(oceania_reshaped)
 ```
 
@@ -318,7 +318,7 @@ head(oceania_reshaped, 10)
 ## 10 Australia   Oceania 1997   78.83 18565243  26997.94
 ```
 
-Note that data from the cell feed comes back as a data.frame with one row per cell. We provide the function `reshape_cf()` to reshape this data into something tabular.
+Note that data from the cell feed comes back as a data.frame with one row per cell. We provide the function `gs_reshape_cellfeed()` to reshape this data into something tabular.
 
 *To add, using row and column limits on the cell feed. All covered in the README.*
 
@@ -378,14 +378,14 @@ gs_new("hi I am new here")
 ```
 
 ```r
-gs_ls() %>% filter(sheet_title == "hi I am new here")
+gs_ls("hi I am new here")
 ```
 
 ```
 ## Source: local data frame [1 x 10]
 ## 
 ##        sheet_title   author perm version             updated
-## 1 hi I am new here gspreadr   rw     new 2015-05-23 05:54:30
+## 1 hi I am new here gspreadr   rw     new 2015-05-30 19:22:48
 ## Variables not shown: sheet_key (chr), ws_feed (chr), alternate (chr), self
 ##   (chr), alt_key (chr)
 ```
@@ -395,24 +395,25 @@ Delete a spreadsheet with `gs_delete()`. This function operates on a registered
 
 ```r
 # Move spreadsheet to trash
-gs_delete(gs_title("hi I am new here"))
+gs_grepdel("hi I am new here")
 ```
 
 ```
+## Authentication will be used.
 ## Sheet successfully identifed: "hi I am new here"
 ## Success. "hi I am new here" moved to trash in Google Drive.
 ```
 
+```
+## [1] TRUE
+```
+
 ```r
-gs_ls() %>% filter(sheet_title == "hi I am new here")
+gs_ls("hi I am new here")
 ```
 
 ```
-## Source: local data frame [0 x 10]
-## 
-## Variables not shown: sheet_title (chr), author (chr), perm (chr), version
-##   (chr), updated (time), sheet_key (chr), ws_feed (chr), alternate (chr),
-##   self (chr), alt_key (chr)
+## No matching sheets found.
 ```
 
 ### Add, delete, or rename a worksheet
@@ -443,8 +444,8 @@ x
 
 ```
 ##                   Spreadsheet title: hi I am new here
-##   Date of googlesheets registration: 2015-05-23 05:54:36 GMT
-##     Date of last spreadsheet update: 2015-05-23 05:54:34 GMT
+##   Date of googlesheets registration: 2015-05-30 19:23:00 GMT
+##     Date of last spreadsheet update: 2015-05-30 19:22:56 GMT
 ##                          visibility: private
 ##                         permissions: rw
 ##                             version: new
@@ -453,7 +454,7 @@ x
 ## (Title): (Nominal worksheet extent as rows x columns)
 ## Sheet1: 1000 x 26
 ## 
-## Key: 1UhIFltdnN2516Z9CZJYQAT9jLl6VEYhT1FQ4aHBpEoc
+## Key: 1fnJfX4NlhZOmSFkDe9WyuAmfjjcsqrSRcR560QI_0Cc
 ```
 
 ```r
@@ -471,8 +472,8 @@ x
 
 ```
 ##                   Spreadsheet title: hi I am new here
-##   Date of googlesheets registration: 2015-05-23 05:54:37 GMT
-##     Date of last spreadsheet update: 2015-05-23 05:54:36 GMT
+##   Date of googlesheets registration: 2015-05-30 19:23:01 GMT
+##     Date of last spreadsheet update: 2015-05-30 19:23:00 GMT
 ##                          visibility: private
 ##                         permissions: rw
 ##                             version: new
@@ -482,11 +483,11 @@ x
 ## Sheet1: 1000 x 26
 ## foo: 10 x 10
 ## 
-## Key: 1UhIFltdnN2516Z9CZJYQAT9jLl6VEYhT1FQ4aHBpEoc
+## Key: 1fnJfX4NlhZOmSFkDe9WyuAmfjjcsqrSRcR560QI_0Cc
 ```
 
 ```r
-gs_ws_delete(x, ws = "foo")
+x <- gs_ws_delete(x, ws = "foo")
 ```
 
 ```
@@ -494,22 +495,14 @@ gs_ws_delete(x, ws = "foo")
 ## Worksheet "foo" deleted from sheet "hi I am new here".
 ```
 
-```r
-x <- gs_title("hi I am new here")
-```
-
-```
-## Sheet successfully identifed: "hi I am new here"
-```
-
 ```r
 x
 ```
 
 ```
 ##                   Spreadsheet title: hi I am new here
-##   Date of googlesheets registration: 2015-05-23 05:54:39 GMT
-##     Date of last spreadsheet update: 2015-05-23 05:54:37 GMT
+##   Date of googlesheets registration: 2015-05-30 19:23:02 GMT
+##     Date of last spreadsheet update: 2015-05-30 19:23:01 GMT
 ##                          visibility: private
 ##                         permissions: rw
 ##                             version: new
@@ -518,7 +511,7 @@ x
 ## (Title): (Nominal worksheet extent as rows x columns)
 ## Sheet1: 1000 x 26
 ## 
-## Key: 1UhIFltdnN2516Z9CZJYQAT9jLl6VEYhT1FQ4aHBpEoc
+## Key: 1fnJfX4NlhZOmSFkDe9WyuAmfjjcsqrSRcR560QI_0Cc
 ```
 
 To rename a worksheet, pass in the spreadsheet object, the worksheet's current name and the new name you want it to be.  

From 4c7d22543db679c35f1d5eb50b3574f4e7d34a14 Mon Sep 17 00:00:00 2001
From: jennybc 
Date: Sat, 30 May 2015 12:53:16 -0700
Subject: [PATCH 2/7] temporary workaround while xml2 is not on cran ... travis
 you like me now?

---
 .travis.yml     | 1 +
 R/gs_read_csv.R | 3 ++-
 2 files changed, 3 insertions(+), 1 deletion(-)

diff --git a/.travis.yml b/.travis.yml
index 92e3d81..11a8e5c 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -12,6 +12,7 @@ install:
 - ./travis-tool.sh r_binary_install stringi
 - ./travis-tool.sh r_binary_install tidyr
 - ./travis-tool.sh r_binary_install ggplot2
+- ./travis-tool.sh github_package hadley/xml2
 - ./travis-tool.sh install_deps
 - ./travis-tool.sh install_r covr
 script: ./travis-tool.sh run_tests
diff --git a/R/gs_read_csv.R b/R/gs_read_csv.R
index 4ff89e5..d443979 100644
--- a/R/gs_read_csv.R
+++ b/R/gs_read_csv.R
@@ -75,7 +75,8 @@ gs_read_csv <- function(ss, ws = 1, ..., verbose = TRUE) {
     req %>%
       httr::content(type = "text/csv", na.strings = c("", "NA"),
                     encoding = "UTF-8", ...) %>%
-      dplyr::as_data_frame()
+      dplyr::as_data_frame() %>%
+      dplyr::as.tbl()
     ## in future, I'm interested in using readr::read_csv(), either directly
     ## or indirectly, if httr make it the parser when MIME type is text/csv
     ## won't do it know because doesn't support vector valued na.strings,

From 65cb57b93e878b07c039c0270a53f76b1e3ce076 Mon Sep 17 00:00:00 2001
From: jennybc 
Date: Sun, 31 May 2015 15:11:28 -0700
Subject: [PATCH 3/7] add gs_gs

---
 NAMESPACE                |  1 +
 R/gs_copy.R              |  3 ++
 R/gs_register.R          | 77 ++++++++++++++++++++++------------
 R/utils.R                |  4 ++
 README.Rmd               | 11 +++--
 README.md                | 91 ++++++++++++++++++++++++++--------------
 man-roxygen/visibility.R |  3 +-
 man/googlesheet.Rd       | 17 +++++---
 8 files changed, 139 insertions(+), 68 deletions(-)

diff --git a/NAMESPACE b/NAMESPACE
index b41e907..1aec2dc 100644
--- a/NAMESPACE
+++ b/NAMESPACE
@@ -17,6 +17,7 @@ export(gs_gap_key)
 export(gs_gap_url)
 export(gs_gap_ws_feed)
 export(gs_grepdel)
+export(gs_gs)
 export(gs_inspect)
 export(gs_key)
 export(gs_ls)
diff --git a/R/gs_copy.R b/R/gs_copy.R
index 3c1094c..9741d02 100644
--- a/R/gs_copy.R
+++ b/R/gs_copy.R
@@ -24,6 +24,9 @@ gs_copy <- function(from, to = NULL, verbose = TRUE) {
   stopifnot(inherits(from, "googlesheet"))
 
   key <- gs_get_alt_key(from)
+  if(is.null(to)) {
+    to <- paste("Copy of", from$sheet_title)
+  }
 
   the_body <- list("title" = to)
 
diff --git a/R/gs_register.R b/R/gs_register.R
index 3195405..1c8db6a 100644
--- a/R/gs_register.R
+++ b/R/gs_register.R
@@ -1,5 +1,3 @@
-## TO DO: gs_gs
-
 #' Register a Google Sheet
 #'
 #' The \code{googlesheets} package must gather information on a Google Sheet
@@ -9,9 +7,9 @@
 #' object. Note this object does not contain any sheet data, but rather contains
 #' metadata about the sheet. We populate a \code{googlesheet}
 #' object with information from the
-#' \href{https://developers.google.com/google-apps/spreadsheets/#working_with_worksheets}{worksheets
+#' \href{https://developers.google.com/google-apps/spreadsheets/worksheets}{worksheets
 #' feed} and, if available, also from the
-#' \href{https://developers.google.com/google-apps/spreadsheets/#retrieving_a_list_of_spreadsheets}{spreadsheets
+#' \href{https://developers.google.com/google-apps/spreadsheets/worksheets#retrieve_a_list_of_spreadsheets}{spreadsheets
 #' feed}. Choose from the functions below depending on the type of
 #' sheet-identifying input you will provide. Is it a sheet title, key,
 #' browser URL, or worksheets feed (another URL, mostly used internally)?
@@ -54,11 +52,14 @@
 #' @name googlesheet
 #'
 #' @param x sheet-identifying information; a character vector of length one
-#'   holding sheet title, key, browser URL or worksheets feed
+#'   holding sheet title, key, browser URL or worksheets feed OR, in the case of
+#'   \code{gs_gs} only, a \code{googlesheet} object
 #' @param lookup logical, optional. Controls whether \code{googlesheets} will
 #'   place authenticated API requests during registration. If unspecified, will
 #'   be set to \code{TRUE} if authentication has previously been used in this R
-#'   session or if working directory contains a file named \code{.httr-oauth}.
+#'   session, if working directory contains a file named \code{.httr-oauth}, or
+#'   if \code{x} is a worksheets feed or \code{googlesheet} object that
+#'   specifies "public" visibility.
 #' @template visibility
 #' @template verbose
 #'
@@ -84,7 +85,7 @@ gs_key <- function(x, lookup = NULL, visibility = NULL, verbose = TRUE) {
 
   stopifnot(length(x) == 1L, is.character(x))
 
-  lookup <- set_lookup(lookup, verbose)
+  lookup <- set_lookup(lookup, visibility, verbose)
   visibility <- set_visibility(visibility, lookup)
 
   if(lookup) {
@@ -92,7 +93,8 @@ gs_key <- function(x, lookup = NULL, visibility = NULL, verbose = TRUE) {
       gs_lookup("sheet_key", verbose)
     x <- ssf$ws_feed
   } else {
-    x <- x %>% construct_ws_feed_from_key(visibility)
+    x <- x %>%
+      construct_ws_feed_from_key(visibility)
     if(verbose) {
       sprintf("Worksheets feed constructed with %s visibility", visibility) %>%
         message()
@@ -113,9 +115,6 @@ gs_url <- function(x, lookup = NULL, visibility = NULL, verbose = TRUE) {
   stopifnot(length(x) == 1L, is.character(x),
             stringr::str_detect(x, "^https://"))
 
-  lookup <- set_lookup(lookup, verbose)
-  visibility <- set_visibility(visibility, lookup)
-
   if(verbose) {
     paste0("Sheet-identifying info appears to be a browser URL.\n",
            "googlesheets will attempt to extract sheet key from the URL.") %>%
@@ -123,8 +122,9 @@ gs_url <- function(x, lookup = NULL, visibility = NULL, verbose = TRUE) {
   }
 
   x <- extract_key_from_url(x)
+
   if(verbose) {
-    sprintf("Putative key: %s", x) %>% message()
+    message(sprintf("Putative key: %s", x))
   }
 
   x %>%
@@ -140,7 +140,8 @@ gs_ws_feed <- function(x, lookup = NULL, verbose = TRUE) {
   stopifnot(length(x) == 1L, is.character(x),
             stringr::str_detect(x, ws_feed_regex))
 
-  lookup <- set_lookup(lookup, verbose)
+  visibility <- if(grepl("public", x)) "public" else "private"
+  lookup <- set_lookup(lookup, visibility, verbose)
 
   if(lookup) {
     ssf <- x %>%
@@ -155,22 +156,42 @@ gs_ws_feed <- function(x, lookup = NULL, verbose = TRUE) {
 
 }
 
-## TO DO: decide how to handle googlesheets as input
-# as.googlesheet.googlesheet <- function(x, ssf = NULL, verbose = TRUE, ...) {
-#
-#   x <- structure(x$ws_feed, class = "ws_feed")
-#   x %>%
-#     as.googlesheet()
-#
-# }
+#' @rdname googlesheet
+#' @export
+gs_gs <- function(x, visibility = NULL, verbose = TRUE) {
 
-set_lookup <- function(lookup = NULL, verbose = TRUE) {
+  stopifnot(inherits(x, "googlesheet"))
 
-  if(is.null(lookup)) {
-    lookup <- !is.null(.state$token) || file.exists(".httr-oauth")
+  if(is.null(visibility)) {
+    visibility <- x$visibility
   } else {
-    stopifnot(is.logical(lookup))
+    stopifnot(identical(visibility, "public") ||
+                identical(visibility, "private"))
+  }
+
+  key <- extract_key_from_url(x$ws_feed)
+  key %>%
+    gs_key(visibility = visibility, verbose = verbose)
+
+}
+
+set_lookup <- function(lookup = NULL, visibility = NULL, verbose = TRUE) {
+
+  stopifnot(isTOGGLE(lookup))
+
+  auth_seems_possible <- !is.null(.state$token) || file.exists(".httr-oauth")
+
+  if(is.null(lookup)) {
+    if(is.null(visibility)) {
+      lookup <- auth_seems_possible
+    } else {
+      lookup <- switch(visibility,
+                       public = FALSE,
+                       private = TRUE,
+                       auth_seems_possible)
+    }
   }
+
   if(verbose) {
     sprintf("Authentication will %sbe used.", if(lookup) "" else "not ") %>%
       message()
@@ -189,14 +210,16 @@ set_visibility <- function(visibility = NULL, lookup = TRUE) {
       visibility <- "public"
     }
   } else {
-    stopifnot(visibility %in% c("public", "private"))
+    stopifnot(identical(visibility, "public") ||
+                identical(visibility, "private"))
   }
 
   visibility
 
 }
 
-## for internal use only x = character holding title | key | ws_feed
+## for internal use only
+## x = character holding title | key | ws_feed
 ##
 ## x will be sought in the variable named 'lvar' in the tbl_df returned by
 ## gs_ls(), which wraps the spreadsheets feed
diff --git a/R/utils.R b/R/utils.R
index 96a2448..139041d 100644
--- a/R/utils.R
+++ b/R/utils.R
@@ -53,3 +53,7 @@ construct_url_from_key <- function(key) {
   tmp <- "https://docs.google.com/spreadsheets/d/%s/"
   sprintf(tmp, key)
 }
+
+isTOGGLE <- function(x) {
+  is.null(x) || isTRUE(x) || identical(x, FALSE)
+}
diff --git a/README.Rmd b/README.Rmd
index 22dbe7b..b15c0f8 100644
--- a/README.Rmd
+++ b/README.Rmd
@@ -67,7 +67,7 @@ What other ideas do you have?
 devtools::install_github("jennybc/googlesheets")
 ```
 
-*We plan to submit to CRAN in late May or early June 2015, so feedback on functionality and usability is especially valuable to us now!*
+*We plan to submit to CRAN in June 2015, so feedback on functionality and usability is especially valuable to us now!*
 
 ### Take a look at the vignette
 
@@ -109,7 +109,7 @@ gs_gap() %>%
   gs_copy(to = "Gapminder")
 ```
 
-If that seems to have worked, go check that you see a sheet named "Gapminder" listed in your Google Sheets home screen: . You could also run `gs_ls()` again and make sure the Gapminder sheet is listed.
+If that seems to have worked, go check for a sheet named "Gapminder" in your Google Sheets home screen: . You could also run `gs_ls()` again and make sure the Gapminder sheet is listed.
 
 ### Register a spreadsheet
 
@@ -135,9 +135,14 @@ third_party_gap <- GAP_KEY %>%
 third_party_gap <- GAP_URL %>%
   gs_url()
 # note: registration via URL may not work for "old" sheets
+
+# Worried that a spreadsheet's registration is out-of-date?
+# Re-register it!
+gap <- gap %>% gs_gs()
+gap
 ```
 
-The registration functions `gs_title()`, `gs_key()`, and `gs_url()` return a registered sheet as a `googlesheet` object, which is the first argument to practically every function in this package. Likewise, almost every function returns a freshly registered `googlesheet` object, ready to be stored or piped into the next command.
+The registration functions `gs_title()`, `gs_key()`, `gs_url()`, and `gs_gs()` return a registered sheet as a `googlesheet` object, which is the first argument to practically every function in this package. Likewise, almost every function returns a freshly registered `googlesheet` object, ready to be stored or piped into the next command.
 
 ### Consume data
 
diff --git a/README.md b/README.md
index 9cb275b..093be1f 100644
--- a/README.md
+++ b/README.md
@@ -45,7 +45,7 @@ What other ideas do you have?
 devtools::install_github("jennybc/googlesheets")
 ```
 
-*We plan to submit to CRAN in late May or early June 2015, so feedback on functionality and usability is especially valuable to us now!*
+*We plan to submit to CRAN in June 2015, so feedback on functionality and usability is especially valuable to us now!*
 
 ### Take a look at the vignette
 
@@ -77,16 +77,16 @@ The `gs_ls()` function returns the sheets you would see in your Google Sheets ho
 #> Source: local data frame [40 x 10]
 #> 
 #>                 sheet_title        author perm version             updated
-#> 1  Copy of Twitter Archive…   joannazhaoo    r     new 2015-05-30 17:31:25
-#> 2               TAGS v6.0ns     m.hawksey    r     new 2015-05-30 10:46:47
-#> 3   EasyTweetSheet - Shared     m.hawksey    r     new 2015-05-30 16:44:08
-#> 4              #rhizo15 #tw     m.hawksey    r     new 2015-05-30 07:53:02
-#> 5  Ari's Anchor Text Scrap…      anahmani    r     new 2015-05-29 07:18:48
-#> 6  Tweet Collector (TAGS v…      gspreadr   rw     new 2015-05-28 17:43:29
-#> 7      test-gs-cars-private      gspreadr   rw     new 2015-05-27 17:48:34
-#> 8     All R Phylo Functions  omeara.brian    r     new 2015-05-20 18:34:43
-#> 9  test-gs-public-testing-…  rpackagetest    r     new 2015-05-20 01:32:27
-#> 10                 ari copy      gspreadr   rw     new 2015-05-19 23:00:13
+#> 1  Copy of Twitter Archive…   joannazhaoo    r     new 2015-05-31 22:00:25
+#> 2               TAGS v6.0ns     m.hawksey    r     new 2015-05-31 21:12:24
+#> 3  Copy of test-gs-gapmind…      gspreadr   rw     new 2015-05-31 21:11:01
+#> 4              #rhizo15 #tw     m.hawksey    r     new 2015-05-31 13:12:01
+#> 5   EasyTweetSheet - Shared     m.hawksey    r     new 2015-05-31 22:02:01
+#> 6  Ari's Anchor Text Scrap…      anahmani    r     new 2015-05-29 07:18:48
+#> 7  Tweet Collector (TAGS v…      gspreadr   rw     new 2015-05-28 17:43:29
+#> 8      test-gs-cars-private      gspreadr   rw     new 2015-05-27 17:48:34
+#> 9     All R Phylo Functions  omeara.brian    r     new 2015-05-20 18:34:43
+#> 10 test-gs-public-testing-…  rpackagetest    r     new 2015-05-20 01:32:27
 #> ..                      ...           ...  ...     ...                 ...
 #> Variables not shown: sheet_key (chr), ws_feed (chr), alternate (chr), self
 #>   (chr), alt_key (chr)
@@ -94,11 +94,11 @@ The `gs_ls()` function returns the sheets you would see in your Google Sheets ho
 my_sheets %>% glimpse()
 #> Observations: 40
 #> Variables:
-#> $ sheet_title (chr) "Copy of Twitter Archiver v2.1", "TAGS v6.0ns", "E...
-#> $ author      (chr) "joannazhaoo", "m.hawksey", "m.hawksey", "m.hawkse...
-#> $ perm        (chr) "r", "r", "r", "r", "r", "rw", "rw", "r", "r", "rw...
+#> $ sheet_title (chr) "Copy of Twitter Archiver v2.1", "TAGS v6.0ns", "C...
+#> $ author      (chr) "joannazhaoo", "m.hawksey", "gspreadr", "m.hawksey...
+#> $ perm        (chr) "r", "r", "rw", "r", "r", "r", "rw", "rw", "r", "r...
 #> $ version     (chr) "new", "new", "new", "new", "new", "new", "new", "...
-#> $ updated     (time) 2015-05-30 17:31:25, 2015-05-30 10:46:47, 2015-05...
+#> $ updated     (time) 2015-05-31 22:00:25, 2015-05-31 21:12:24, 2015-05...
 #> $ sheet_key   (chr) "1DoMXh2m3FGPoZAle9vnzg763D9FESTU506iqWkUTwtE", "1...
 #> $ ws_feed     (chr) "https://spreadsheets.google.com/feeds/worksheets/...
 #> $ alternate   (chr) "https://docs.google.com/spreadsheets/d/1DoMXh2m3F...
@@ -115,7 +115,7 @@ gs_gap() %>%
   gs_copy(to = "Gapminder")
 ```
 
-If that seems to have worked, go check that you see a sheet named "Gapminder" listed in your Google Sheets home screen: . You could also run `gs_ls()` again and make sure the Gapminder sheet is listed.
+If that seems to have worked, go check for a sheet named "Gapminder" in your Google Sheets home screen: . You could also run `gs_ls()` again and make sure the Gapminder sheet is listed.
 
 ### Register a spreadsheet
 
@@ -130,7 +130,7 @@ gap <- gs_title("Gapminder")
 #> Sheet successfully identifed: "Gapminder"
 gap
 #>                   Spreadsheet title: Gapminder
-#>   Date of googlesheets registration: 2015-05-30 18:00:05 GMT
+#>   Date of googlesheets registration: 2015-05-31 22:07:47 GMT
 #>     Date of last spreadsheet update: 2015-03-23 20:34:08 GMT
 #>                          visibility: private
 #>                         permissions: rw
@@ -161,16 +161,38 @@ third_party_gap <- GAP_KEY %>%
 #> [1] "https://docs.google.com/spreadsheets/d/1BzfL0kZUz1TsI5zxJF1WNF01IxvC67FbOJUiiGMZ_mQ/"
 third_party_gap <- GAP_URL %>%
   gs_url()
-#> Authentication will be used.
 #> Sheet-identifying info appears to be a browser URL.
 #> googlesheets will attempt to extract sheet key from the URL.
 #> Putative key: 1BzfL0kZUz1TsI5zxJF1WNF01IxvC67FbOJUiiGMZ_mQ
 #> Authentication will be used.
 #> Sheet successfully identifed: "test-gs-gapminder"
 # note: registration via URL may not work for "old" sheets
+
+# Worried that a spreadsheet's registration is out-of-date?
+# Re-register it!
+gap <- gap %>% gs_gs()
+#> Authentication will be used.
+#> Sheet successfully identifed: "Gapminder"
+gap
+#>                   Spreadsheet title: Gapminder
+#>   Date of googlesheets registration: 2015-05-31 22:07:51 GMT
+#>     Date of last spreadsheet update: 2015-03-23 20:34:08 GMT
+#>                          visibility: private
+#>                         permissions: rw
+#>                             version: new
+#> 
+#> Contains 5 worksheets:
+#> (Title): (Nominal worksheet extent as rows x columns)
+#> Africa: 625 x 6
+#> Americas: 301 x 6
+#> Asia: 397 x 6
+#> Europe: 361 x 6
+#> Oceania: 25 x 6
+#> 
+#> Key: 1HT5B8SgkKqHdqHJmn5xiuaC04Ngb7dG9Tv94004vezA
 ```
 
-The registration functions `gs_title()`, `gs_key()`, and `gs_url()` return a registered sheet as a `googlesheet` object, which is the first argument to practically every function in this package. Likewise, almost every function returns a freshly registered `googlesheet` object, ready to be stored or piped into the next command.
+The registration functions `gs_title()`, `gs_key()`, `gs_url()`, and `gs_gs()` return a registered sheet as a `googlesheet` object, which is the first argument to practically every function in this package. Likewise, almost every function returns a freshly registered `googlesheet` object, ready to be stored or piped into the next command.
 
 ### Consume data
 
@@ -400,8 +422,8 @@ foo <- gs_new("foo")
 #> Worksheet dimensions: 1000 x 26.
 foo
 #>                   Spreadsheet title: foo
-#>   Date of googlesheets registration: 2015-05-30 18:00:16 GMT
-#>     Date of last spreadsheet update: 2015-05-30 18:00:14 GMT
+#>   Date of googlesheets registration: 2015-05-31 22:07:56 GMT
+#>     Date of last spreadsheet update: 2015-05-31 22:07:55 GMT
 #>                          visibility: private
 #>                         permissions: rw
 #>                             version: new
@@ -410,7 +432,7 @@ foo
 #> (Title): (Nominal worksheet extent as rows x columns)
 #> Sheet1: 1000 x 26
 #> 
-#> Key: 1QRqPObeTYiaddr2bRBo2M16a6hLrCG6e2H2_NqovNlQ
+#> Key: 19J9GvlYsWABg3nEGAOpmemkpRFCoBQAggi5Ww2czLP4
 ```
 
 By default, there will be an empty worksheet called "Sheet1", but you can control it's title, extent, and initial data with additional arguments to `gs_new()`. You can also add, rename, and delete worksheets within an existing sheet via `gs_ws_new()`, `gs_ws_rename()`, and `gs_ws_delete()`. Copy an entire spreadsheet with `gs_copy()`.
@@ -423,6 +445,11 @@ You can modify the data in sheet cells via `gs_edit_cells()`. We'll work on the
 foo <- foo %>% gs_edit_cells(input = head(iris), trim = TRUE)
 #> Range affected by the update: "A1:E7"
 #> Worksheet "Sheet1" successfully updated with 35 new value(s).
+#> Accessing worksheet titled "Sheet1"
+#> Authentication will be used.
+#> Sheet successfully identifed: "foo"
+#> Accessing worksheet titled "Sheet1"
+#> Worksheet "Sheet1" dimensions changed to 7 x 5.
 ```
 
 Go to [your Google Sheets home screen](https://docs.google.com/spreadsheets/u/0/), find the new sheet `foo` and look at it. You should see some iris data in the first (and only) worksheet. We'll also take a look at it here, by consuming `foo` via the list feed.
@@ -466,8 +493,8 @@ iris_ss <- gs_upload("iris.csv")
 #> "iris.csv" uploaded to Google Drive and converted to a Google Sheet named "iris"
 iris_ss
 #>                   Spreadsheet title: iris
-#>   Date of googlesheets registration: 2015-05-30 18:00:31 GMT
-#>     Date of last spreadsheet update: 2015-05-30 18:00:29 GMT
+#>   Date of googlesheets registration: 2015-05-31 22:08:08 GMT
+#>     Date of last spreadsheet update: 2015-05-31 22:08:06 GMT
 #>                          visibility: private
 #>                         permissions: rw
 #>                             version: new
@@ -476,7 +503,7 @@ iris_ss
 #> (Title): (Nominal worksheet extent as rows x columns)
 #> iris: 6 x 5
 #> 
-#> Key: 1JnFpHmc2Wj-Ai8IgbJD_QOrEcDXMrMW6gQa9K6YZsCk
+#> Key: 1JBhoVYK4CjvHvwrWl4N0nr03i6CQ7BBiTUHS6A31u50
 iris_ss %>% gs_read_listfeed()
 #> Accessing worksheet titled "iris"
 #> Source: local data frame [5 x 5]
@@ -498,8 +525,8 @@ gap_xlsx <- gs_upload(system.file("mini-gap.xlsx", package = "googlesheets"))
 #> "mini-gap.xlsx" uploaded to Google Drive and converted to a Google Sheet named "mini-gap"
 gap_xlsx
 #>                   Spreadsheet title: mini-gap
-#>   Date of googlesheets registration: 2015-05-30 18:00:37 GMT
-#>     Date of last spreadsheet update: 2015-05-30 18:00:34 GMT
+#>   Date of googlesheets registration: 2015-05-31 22:08:13 GMT
+#>     Date of last spreadsheet update: 2015-05-31 22:08:11 GMT
 #>                          visibility: private
 #>                         permissions: rw
 #>                             version: new
@@ -512,7 +539,7 @@ gap_xlsx
 #> Europe: 1000 x 26
 #> Oceania: 1000 x 26
 #> 
-#> Key: 1088N7_IdkHaqe-m1g2-U9Vg6ZQiH1z--aH7LQFxYbU4
+#> Key: 1AJakJVpaoEkuuwaO4Tm3IJbGAMxaHOr2U5r3jH2uyrY
 gap_xlsx %>% gs_read_listfeed(ws = "Oceania")
 #> Accessing worksheet titled "Oceania"
 #> Source: local data frame [5 x 6]
@@ -590,8 +617,8 @@ The function `gs_user()` will print and return some information about the curren
 user_session_info <- gs_user()
 #>                        displayName: google sheets
 #>                       emailAddress: gspreadr@gmail.com
-#> Date-time of session authorization: 2015-05-30 18:00:03
-#>   Date-time of access token expiry: 2015-05-30 11:58:52
+#> Date-time of session authorization: 2015-05-31 22:07:45
+#>   Date-time of access token expiry: 2015-05-31 15:11:01
 #> Access token is valid.
 user_session_info
 #> $displayName
@@ -601,10 +628,10 @@ user_session_info
 #> [1] "gspreadr@gmail.com"
 #> 
 #> $auth_date
-#> [1] "2015-05-30 18:00:03 GMT"
+#> [1] "2015-05-31 22:07:45 GMT"
 #> 
 #> $exp_date
-#> [1] "2015-05-30 11:58:52 PDT"
+#> [1] "2015-05-31 15:11:01 PDT"
 ```
 
 ### "Old" Google Sheets
diff --git a/man-roxygen/visibility.R b/man-roxygen/visibility.R
index 9fd33a8..f9dca5f 100644
--- a/man-roxygen/visibility.R
+++ b/man-roxygen/visibility.R
@@ -3,4 +3,5 @@
 #'   when \code{lookup = FALSE} and \code{googlesheets} is prevented from
 #'   looking up information in the spreadsheets feed. If unspecified, will be
 #'   set to "public" if \code{lookup = FALSE} and "private" if \code{lookup =
-#'   TRUE}.
+#'   TRUE}. Consult the API docs for more info about
+#'   \href{https://developers.google.com/google-apps/spreadsheets/worksheets#sheets_api_urls_visibilities_and_projections}{visibility}
diff --git a/man/googlesheet.Rd b/man/googlesheet.Rd
index 2787aad..75d266c 100644
--- a/man/googlesheet.Rd
+++ b/man/googlesheet.Rd
@@ -2,6 +2,7 @@
 % Please edit documentation in R/gs_register.R
 \name{googlesheet}
 \alias{googlesheet}
+\alias{gs_gs}
 \alias{gs_key}
 \alias{gs_title}
 \alias{gs_url}
@@ -15,24 +16,30 @@ gs_key(x, lookup = NULL, visibility = NULL, verbose = TRUE)
 gs_url(x, lookup = NULL, visibility = NULL, verbose = TRUE)
 
 gs_ws_feed(x, lookup = NULL, verbose = TRUE)
+
+gs_gs(x, visibility = NULL, verbose = TRUE)
 }
 \arguments{
 \item{x}{sheet-identifying information; a character vector of length one
-holding sheet title, key, browser URL or worksheets feed}
+holding sheet title, key, browser URL or worksheets feed OR, in the case of
+\code{gs_gs} only, a \code{googlesheet} object}
 
 \item{verbose}{logical; do you want informative messages?}
 
 \item{lookup}{logical, optional. Controls whether \code{googlesheets} will
 place authenticated API requests during registration. If unspecified, will
 be set to \code{TRUE} if authentication has previously been used in this R
-session or if working directory contains a file named \code{.httr-oauth}.}
+session, if working directory contains a file named \code{.httr-oauth}, or
+if \code{x} is a worksheets feed or \code{googlesheet} object that
+specifies "public" visibility.}
 
 \item{visibility}{character, either "public" or "private". Consulted during
 explicit construction of a worksheets feed from a key, which happens only
 when \code{lookup = FALSE} and \code{googlesheets} is prevented from
 looking up information in the spreadsheets feed. If unspecified, will be
 set to "public" if \code{lookup = FALSE} and "private" if \code{lookup =
-TRUE}.}
+TRUE}. Consult the API docs for more info about
+\href{https://developers.google.com/google-apps/spreadsheets/worksheets#sheets_api_urls_visibilities_and_projections}{visibility}}
 }
 \value{
 a \code{googlesheet} object
@@ -45,9 +52,9 @@ prior to any requests to read or write data. We call this
 object. Note this object does not contain any sheet data, but rather contains
 metadata about the sheet. We populate a \code{googlesheet}
 object with information from the
-\href{https://developers.google.com/google-apps/spreadsheets/#working_with_worksheets}{worksheets
+\href{https://developers.google.com/google-apps/spreadsheets/worksheets}{worksheets
 feed} and, if available, also from the
-\href{https://developers.google.com/google-apps/spreadsheets/#retrieving_a_list_of_spreadsheets}{spreadsheets
+\href{https://developers.google.com/google-apps/spreadsheets/worksheets#retrieve_a_list_of_spreadsheets}{spreadsheets
 feed}. Choose from the functions below depending on the type of
 sheet-identifying input you will provide. Is it a sheet title, key,
 browser URL, or worksheets feed (another URL, mostly used internally)?

From c3a3bdeaa6e3991bfaca41f7b6671c540752f25d Mon Sep 17 00:00:00 2001
From: jennybc 
Date: Sun, 31 May 2015 17:03:34 -0700
Subject: [PATCH 4/7] update tests of registration functions

---
 .../for_reference/gap_googlesheet.rds         | Bin 0 -> 1013 bytes
 .../for_reference/iris_pvt_googlesheet.rds    | Bin 0 -> 773 bytes
 tests/testthat/helper01_setup-sheets.R        |   2 +-
 tests/testthat/test-gs-register.R             | 152 ++++++++++--------
 4 files changed, 89 insertions(+), 65 deletions(-)
 create mode 100644 tests/testthat/for_reference/gap_googlesheet.rds
 create mode 100644 tests/testthat/for_reference/iris_pvt_googlesheet.rds

diff --git a/tests/testthat/for_reference/gap_googlesheet.rds b/tests/testthat/for_reference/gap_googlesheet.rds
new file mode 100644
index 0000000000000000000000000000000000000000..8d3a84a3b22d2a78d3c28697795e3b53f18ec0b5
GIT binary patch
literal 1013
zcmVMc&y$PKX*hs5N%IUmAd%<*8G0q{$z|UUN6j7?8I8kUmlyk|9otiC-dp^
zR{MBx_VP7CFHSG|{;#=rzvPhdLysMLAoNk-Kq>}5UoB9STe-z}$a%;f>pBZ51P&WQ
z$k~xch=<{kO?=&j&|&(F&`~_6ZzGu=QZxk|>QjIxkO^hKh_HL_Pyb%N6Hkg-Ny&1{
z#(=Rzz)CWx{c`r($+^vwZf(`&`8WB31Td#s^~LEc@%Leadhyqf&m`*Jatol!@}qla
zhP6ar@z_WVuH=?{MnhnafCnQl<>v0E=bm{!KsfS2Zq**m3lqRu!lP<3r6yB#fH|Z=
zBvL%^F!FcgZhHuQknyyqHx0vj5IOwDi$lVs!BhsZn}fdx!Vn`Ha6|$faN>V6_i-U}
zS=@8UYKPbvvC}Nqo$a^`9mIDMHUAgPTucYRhw?P2N~%*Rd7$#Qp=7l>q|nX1U#Dbt
zB=?ApzsetRG^)7+K<-`R1Kf99i6nZ-W5WR)9Jy5Nm7FJclK?+hov86DUZ$;d^2QkR
zw!0hbF`i+xBkbW~FrQkx!FHPCaQofrDf&UuHLfNbE!C8
zliq&Mhf!A^2x^ZZIam8(LdiM=wKE%nKupHLChi%Pu?Vt5(f$+-iLtLXb4u-smKGzp
znRPK@d^@`)7Q6bqD-JbwOxdWh|I0>=X;!+0S8TrYP06r|+zj1@7;hP_*V3v$*z0&z
zAnbL-DiHQMW)*4V8&K;_5m!!ciWWK_KlPCF#566#G&pPoDUqdL21_&cAx344C4<`eRY-x5d;X^{DM_^QNl(-?Jz66>=%7oG6I*fvm
jl&P|j4&YdqtIMmmW16irlo*lEB76M{;K!9d6Bhsg*TMC&

literal 0
HcmV?d00001

diff --git a/tests/testthat/for_reference/iris_pvt_googlesheet.rds b/tests/testthat/for_reference/iris_pvt_googlesheet.rds
new file mode 100644
index 0000000000000000000000000000000000000000..4dd93e0bf3aa0f017c0c404d6c3059e1f3c6ac48
GIT binary patch
literal 773
zcmV+g1N!_QiwFP!000001LaoTZqqOn&XTNMTLBa74G`Qwn{BY6Ax&(P5E_Uv7)TX2
zm6te)7bnhar`>wROY#m}@C+2^=d7t=V!hZvB2p6j_}Jh1Kkl~;!!QlYR9{OCt$XTo
zN4?AHT`+8;rT&lCKK1$2Zix2Zi}=gh5BpxW7wv65!)qHSc)Z1e4@}#;
zBC(L&Ky(QwqMLFuL=r3dt%9d~>-;C7l&N^`I3nd3c_PGEieA82KylAyvEyUx31`H3
zRIWMm255ep)cSh`ba68_7A&fmD|3b3-4~SYCCn&~H
z04xY{jzKb8o~IDccF3`>?Qd~5>cJU#y)(a*3219M4`(;zF&%h5AgNh3Y$a&wk)n765C|F5LRQ4X_KtM<*>cv0gHuO>bvLe3|+5=?XR3@*7Eu8S$1
z(_()BM%Pgtf*~0@^7KVOyzNyDFuIm8ux!spPA)l3pep{%r32^rusi4!RKZI{tm%2J*YwQkueBDF?X
z9ukouO2$R6+D!>Cmafa4Rd1cuxM(wl0`{a{zb5L@>es?$WvW_LP}5x(=C}i56eh13
zp;Hp-cCeGdNWiT|*{G&nGfNfh#dL||VBk$J8f~RKIVO}yIKD;1AkB_d?*% extract_key_from_url()
 iris_pvt_ws_feed <- "https://spreadsheets.google.com/feeds/worksheets/1UXr4-haIQsmJfyjkEhlkNt2PXduBkB97e15jez9ogRo/private/full"
 
 ## Private cars sheet (owned by rpackagetest)
diff --git a/tests/testthat/test-gs-register.R b/tests/testthat/test-gs-register.R
index 6620c5b..dca251b 100644
--- a/tests/testthat/test-gs-register.R
+++ b/tests/testthat/test-gs-register.R
@@ -1,87 +1,111 @@
 context("register sheets")
 
-test_that("Spreadsheet can be ID'd via URL, key, title, ws_feed or ss", {
-
-  ## NOTE: we've got to look for stuff we (gspreadr) own here, because this is
-  ## all about the spreadsheets feed
-
-  expect_equal_to_iris_identify <- function(x, method = NULL) {
-    expect_equal_to_reference(identify_ss(x), "iris_identify_ss.rds")
-  }
-
-  ## let identify_ss() determine the method
-  #expect_equal_to_iris_identify(iris_pvt_url)
-  #expect_equal_to_iris_identify(iris_pvt_key)
-  #expect_equal_to_iris_identify(iris_pvt_title)
-  #expect_equal_to_iris_identify(iris_pvt_ws_feed)
-  #iris_gs <- identify_ss(iris_pvt_key)
-  #expect_equal_to_iris_identify(iris_gs)
-
-  ## explicitly provide the correct method
-  #expect_equal_to_iris_identify(iris_pvt_url, method = "url")
-
-  #expect_equal_to_iris_identify(iris_pvt_key, method = "key")
-  #expect_equal_to_iris_identify(iris_pvt_title, method = "title")
-  #expect_equal_to_iris_identify(iris_pvt_ws_feed, method = "ws_feed")
-  #expect_equal_to_iris_identify(iris_gs, method = "ss")
-
-  ## request NO verification
-  expect_iris_key <- function(x) {
-    expect_equal(identify_ss(x, verify = FALSE)$sheet_key, iris_pvt_key)
-  }
-
-  #expect_iris_key(iris_pvt_url)
-  #expect_iris_key(iris_pvt_ws_feed)
-  #expect_iris_key(iris_pvt_key)
-  #expect_iris_key(iris_gs)
-  ## note this "works" but proclaims the title as the key
-  #expect_equal(identify_ss(
-    #iris_pvt_title, verify = FALSE)$sheet_key, iris_pvt_title)
+iris_pvt_url %>%
+  gs_url(verbose = FALSE) %>%
+  saveRDS("for_reference/iris_pvt_googlesheet.rds")
+
+GAP_KEY %>%
+  gs_key(verbose = FALSE) %>%
+  saveRDS("for_reference/gap_googlesheet.rds")
+
+pseudo_expect_equal_to_reference <- function(x, ref) {
+  ref_rds <- file.path("for_reference", paste0(ref, "_googlesheet.rds"))
+  ref <- readRDS(ref_rds)
+  stable_bits <- c("sheet_key", "sheet_title", "n_ws",
+                   "author", "email", "version")
+  inherits(x, "googlesheet") &&
+    identical(x[stable_bits], ref[stable_bits])
+}
+
+test_that("Spreadsheet can be registered via title", {
+
+  pseudo_expect_equal_to_reference(iris_pvt_title %>% gs_title(), "iris_pvt")
 
 })
 
-test_that("Bad spreadsheet ID throws informative error", {
+test_that("Spreadsheet can be registered via key", {
+
+  ## sheet owned by gspreadr = authenticated user but not "published to web"
+  pseudo_expect_equal_to_reference(iris_pvt_key %>% gs_key(), "iris_pvt")
+  pseudo_expect_equal_to_reference(
+    iris_pvt_key %>% gs_key(lookup = FALSE, visibility = "private"), "iris_pvt")
+
+  ## sheet owned by rpackagetest and "published to web"
+  pseudo_expect_equal_to_reference(GAP_KEY %>% gs_key(), "gap")
+  pseudo_expect_equal_to_reference(GAP_KEY %>% gs_key(lookup = FALSE), "gap")
+  pseudo_expect_equal_to_reference(GAP_KEY %>% gs_key(visibility = "public"),
+                                   "gap")
+  pseudo_expect_equal_to_reference(GAP_KEY %>% gs_key(visibility = "private"),
+                                   "gap")
+  pseudo_expect_equal_to_reference(
+    GAP_KEY %>% gs_key(lookup = FALSE, visibility = "private"), "gap")
+  pseudo_expect_equal_to_reference(
+    GAP_KEY %>% gs_key(lookup = FALSE, visibility = "public"), "gap")
 
-  ## errors that prevent attempt to identify spreadsheet
-  #expect_error(identify_ss(4L), "must be character")
-  #expect_error(identify_ss(c("Gapminder", "Gapminder")), "must be of length 1")
+})
 
-  ## explicit declaration of an invalid method
-  #expect_error(identify_ss(pts_key, method = "eggplant"), "Error in match.arg")
+test_that("Spreadsheet can be registered via URL", {
 
-  ## incompatible choices for method and verify
-  #expect_error(identify_ss("eggplant", method = "title", verify = FALSE),
-               #"must look up the title")
+  ## sheet owned by gspreadr = authenticated user but not "published to web"
+  pseudo_expect_equal_to_reference(iris_pvt_url %>% gs_url(), "iris_pvt")
+  pseudo_expect_equal_to_reference(iris_pvt_url %>% gs_url(lookup = TRUE),
+                                   "iris_pvt")
+  pseudo_expect_equal_to_reference(
+    iris_pvt_url %>% gs_url(lookup = FALSE, visibility = "private"), "iris_pvt")
 
-  ## errors caused by well-formed input that refers to a nonexistent spreadsheet
-  #expect_error(identify_ss("spatula"), "doesn't match")
+  ## sheet owned by rpackagetest and "published to web"
+  pseudo_expect_equal_to_reference(GAP_URL %>% gs_url(), "gap")
+  pseudo_expect_equal_to_reference(GAP_URL %>% gs_url(lookup = FALSE), "gap")
+  pseudo_expect_equal_to_reference(
+    GAP_URL %>% gs_url(lookup = FALSE, visibility = "private"), "gap")
 
-  nonexistent_ws_feed <- sub(iris_pvt_key, "flyingpig", iris_pvt_ws_feed)
-  expect_error(gs_ws_feed(nonexistent_ws_feed), "doesn't match")
-  expect_error(gs_ws_feed(nonexistent_ws_feed), "doesn't match")
+})
 
-  nonexistent_url <- sub(iris_pvt_key, "flyingpig", iris_pvt_url)
-  expect_error(gs_url(nonexistent_url), "doesn't match")
+test_that("Spreadsheet can be registered via ws_feed", {
 
-  nonexistent_key <- "flyingpig"
-  expect_error(gs_key(nonexistent_key), "doesn't match")
+  ## sheet owned by gspreadr = authenticated user but not "published to web"
+  pseudo_expect_equal_to_reference(iris_pvt_ws_feed %>% gs_ws_feed(),
+                                   "iris_pvt")
+  pseudo_expect_equal_to_reference(
+    iris_pvt_ws_feed %>% gs_ws_feed(lookup = FALSE), "iris_pvt")
+
+  ## sheet owned by rpackagetest and "published to web"
+  pseudo_expect_equal_to_reference(GAP_WS_FEED %>% gs_ws_feed(), "gap")
+  pseudo_expect_equal_to_reference(
+    GAP_WS_FEED %>% gs_ws_feed(lookup = FALSE), "gap")
 
 })
 
-test_that("Spreadsheet can be registered via URL, key, title, ws_feed or ss", {
+test_that("Spreadsheet can be registered via googlesheet", {
+
+  iris_ss <- iris_pvt_key %>% gs_key()
 
-  expect_googlesheet <- function(x) expect_is(x, "googlesheet")
+  ## sheet owned by gspreadr = authenticated user but not "published to web"
+  pseudo_expect_equal_to_reference(iris_ss %>% gs_gs(), "iris_pvt")
 
-  expect_googlesheet(gs_ws_feed(iris_pvt_ws_feed))
-  expect_googlesheet(gs_title(iris_pvt_title))
-  expect_googlesheet(gs_key(iris_pvt_key))
-  expect_googlesheet(gs_url(iris_pvt_url))
-  # am I going to create gs_gs()?
-  #iris_gs <- identify_ss(iris_pvt_key)
-  #expect_googlesheet(register_ss(iris_gs))
+  ## sheet owned by rpackagetest and "published to web"
+  pseudo_expect_equal_to_reference(gs_gap() %>% gs_gs(), "gap")
+  pseudo_expect_equal_to_reference(gs_gap() %>% gs_gs(visibility = "private"),
+                                   "gap")
 
 })
 
+test_that("Bad spreadsheet ID throws error", {
+
+  expect_error(gs_key(4L), "is.character")
+  expect_error(gs_title(rep("Gapminder", 2)), "length")
+  expect_error(gs_gs("Gapminder"), "googlesheet")
+
+  ## errors caused by well-formed input that refers to a nonexistent spreadsheet
+  expect_error(gs_title("spatuala"), "doesn't match")
+  expect_error(gs_key("flyingpig"), "doesn't match")
+  nonexistent_url <- sub(iris_pvt_key, "flyingpig", iris_pvt_url)
+  expect_error(gs_url(nonexistent_url), "doesn't match")
+  nonexistent_ws_feed <- sub(iris_pvt_key, "flyingpig", iris_pvt_ws_feed)
+  expect_error(gs_ws_feed(nonexistent_ws_feed), "doesn't match")
+
+  })
+
 test_that("We get correct number and titles of worksheets", {
 
   ss <- gs_ws_feed(GAP_WS_FEED, lookup = FALSE)

From 0f14528de004ba5b1e41b90056523f222620f0e9 Mon Sep 17 00:00:00 2001
From: jennybc 
Date: Sun, 31 May 2015 17:20:53 -0700
Subject: [PATCH 5/7] move test references files into subdir

---
 .../{ => for_reference}/gap_africa_simplify_A1.rds  | Bin
 .../gap_africa_simplify_R1C1.rds                    | Bin
 .../{ => for_reference}/gap_sheet5_get_via_cf.rds   | Bin
 .../{ => for_reference}/gap_sheet5_get_via_lf.rds   | Bin
 .../gap_sheet5_gs_read_cellfeed.rds                 | Bin
 .../gap_sheet5_gs_read_listfeed.rds                 | Bin
 .../{ => for_reference}/iris_pvt_get_via_lf.rds     | Bin
 .../iris_pvt_gs_read_cellfeed.rds                   | Bin
 .../iris_pvt_gs_read_listfeed.rds                   | Bin
 .../{ => for_reference}/pts_special_chars.rds       | Bin
 tests/testthat/test-consume-data-private.R          |   6 +++---
 tests/testthat/test-inspect.R                       |  11 +++++++----
 .../test-yy-consume-data-public-selective.R         |   7 ++++---
 tests/testthat/test-yy-consume-data-public-tricky.R |   2 +-
 .../test-yy-consume-data-public-whole-sheets.R      |   7 ++++---
 15 files changed, 19 insertions(+), 14 deletions(-)
 rename tests/testthat/{ => for_reference}/gap_africa_simplify_A1.rds (100%)
 rename tests/testthat/{ => for_reference}/gap_africa_simplify_R1C1.rds (100%)
 rename tests/testthat/{ => for_reference}/gap_sheet5_get_via_cf.rds (100%)
 rename tests/testthat/{ => for_reference}/gap_sheet5_get_via_lf.rds (100%)
 rename tests/testthat/{ => for_reference}/gap_sheet5_gs_read_cellfeed.rds (100%)
 rename tests/testthat/{ => for_reference}/gap_sheet5_gs_read_listfeed.rds (100%)
 rename tests/testthat/{ => for_reference}/iris_pvt_get_via_lf.rds (100%)
 rename tests/testthat/{ => for_reference}/iris_pvt_gs_read_cellfeed.rds (100%)
 rename tests/testthat/{ => for_reference}/iris_pvt_gs_read_listfeed.rds (100%)
 rename tests/testthat/{ => for_reference}/pts_special_chars.rds (100%)

diff --git a/tests/testthat/gap_africa_simplify_A1.rds b/tests/testthat/for_reference/gap_africa_simplify_A1.rds
similarity index 100%
rename from tests/testthat/gap_africa_simplify_A1.rds
rename to tests/testthat/for_reference/gap_africa_simplify_A1.rds
diff --git a/tests/testthat/gap_africa_simplify_R1C1.rds b/tests/testthat/for_reference/gap_africa_simplify_R1C1.rds
similarity index 100%
rename from tests/testthat/gap_africa_simplify_R1C1.rds
rename to tests/testthat/for_reference/gap_africa_simplify_R1C1.rds
diff --git a/tests/testthat/gap_sheet5_get_via_cf.rds b/tests/testthat/for_reference/gap_sheet5_get_via_cf.rds
similarity index 100%
rename from tests/testthat/gap_sheet5_get_via_cf.rds
rename to tests/testthat/for_reference/gap_sheet5_get_via_cf.rds
diff --git a/tests/testthat/gap_sheet5_get_via_lf.rds b/tests/testthat/for_reference/gap_sheet5_get_via_lf.rds
similarity index 100%
rename from tests/testthat/gap_sheet5_get_via_lf.rds
rename to tests/testthat/for_reference/gap_sheet5_get_via_lf.rds
diff --git a/tests/testthat/gap_sheet5_gs_read_cellfeed.rds b/tests/testthat/for_reference/gap_sheet5_gs_read_cellfeed.rds
similarity index 100%
rename from tests/testthat/gap_sheet5_gs_read_cellfeed.rds
rename to tests/testthat/for_reference/gap_sheet5_gs_read_cellfeed.rds
diff --git a/tests/testthat/gap_sheet5_gs_read_listfeed.rds b/tests/testthat/for_reference/gap_sheet5_gs_read_listfeed.rds
similarity index 100%
rename from tests/testthat/gap_sheet5_gs_read_listfeed.rds
rename to tests/testthat/for_reference/gap_sheet5_gs_read_listfeed.rds
diff --git a/tests/testthat/iris_pvt_get_via_lf.rds b/tests/testthat/for_reference/iris_pvt_get_via_lf.rds
similarity index 100%
rename from tests/testthat/iris_pvt_get_via_lf.rds
rename to tests/testthat/for_reference/iris_pvt_get_via_lf.rds
diff --git a/tests/testthat/iris_pvt_gs_read_cellfeed.rds b/tests/testthat/for_reference/iris_pvt_gs_read_cellfeed.rds
similarity index 100%
rename from tests/testthat/iris_pvt_gs_read_cellfeed.rds
rename to tests/testthat/for_reference/iris_pvt_gs_read_cellfeed.rds
diff --git a/tests/testthat/iris_pvt_gs_read_listfeed.rds b/tests/testthat/for_reference/iris_pvt_gs_read_listfeed.rds
similarity index 100%
rename from tests/testthat/iris_pvt_gs_read_listfeed.rds
rename to tests/testthat/for_reference/iris_pvt_gs_read_listfeed.rds
diff --git a/tests/testthat/pts_special_chars.rds b/tests/testthat/for_reference/pts_special_chars.rds
similarity index 100%
rename from tests/testthat/pts_special_chars.rds
rename to tests/testthat/for_reference/pts_special_chars.rds
diff --git a/tests/testthat/test-consume-data-private.R b/tests/testthat/test-consume-data-private.R
index c4689c6..f6d623a 100644
--- a/tests/testthat/test-consume-data-private.R
+++ b/tests/testthat/test-consume-data-private.R
@@ -12,14 +12,14 @@ ss <- gs_ws_feed(iris_pvt_ws_feed, verbose = FALSE)
 test_that("We can get all data from the list feed (pvt)", {
 
   expect_equal_to_reference(gs_read_listfeed(ss),
-                            "iris_pvt_gs_read_listfeed.rds")
+                            "for_reference/iris_pvt_gs_read_listfeed.rds")
 
 })
 
 test_that("We can get all data from the cell feed (pvt)", {
 
   expect_equal_to_reference(gs_read_cellfeed(ss),
-                            "iris_pvt_gs_read_cellfeed.rds")
+                            "for_reference/iris_pvt_gs_read_cellfeed.rds")
 
 })
 
@@ -27,6 +27,6 @@ test_that("We can get all data from the exportcsv link (pvt)", {
 
   dat1 <- gs_read_csv(ss)
   names(dat1) <-  dat1 %>% names() %>% tolower()
-  expect_equal_to_reference(dat1, "iris_pvt_gs_read_listfeed.rds")
+  expect_equal_to_reference(dat1, "for_reference/iris_pvt_gs_read_listfeed.rds")
 
 })
diff --git a/tests/testthat/test-inspect.R b/tests/testthat/test-inspect.R
index 3025d43..dcbe137 100644
--- a/tests/testthat/test-inspect.R
+++ b/tests/testthat/test-inspect.R
@@ -6,11 +6,14 @@ test_that("Data frames are plotted as ggplot objects", {
 
   expect_is(gs_inspect(df_with_empty), "ggplot")
 
-  expect_is(readRDS("gap_sheet5_gs_read_cellfeed.rds") %>% gs_inspect(),
+  expect_is(
+    readRDS("for_reference/gap_sheet5_gs_read_cellfeed.rds") %>% gs_inspect(),
+    "ggplot")
+  expect_is(readRDS(
+    "for_reference/gap_sheet5_gs_read_listfeed.rds") %>% gs_inspect(),
+    "ggplot")
+  expect_is(readRDS("for_reference/pts_special_chars.rds") %>% gs_inspect(),
             "ggplot")
-  expect_is(readRDS("gap_sheet5_gs_read_listfeed.rds") %>% gs_inspect(),
-            "ggplot")
-  expect_is(readRDS("pts_special_chars.rds") %>% gs_inspect(), "ggplot")
 
   expect_error(gs_inspect(c(1:10)), "is not TRUE")
 })
diff --git a/tests/testthat/test-yy-consume-data-public-selective.R b/tests/testthat/test-yy-consume-data-public-selective.R
index ed71d2e..d52ae69 100644
--- a/tests/testthat/test-yy-consume-data-public-selective.R
+++ b/tests/testthat/test-yy-consume-data-public-selective.R
@@ -74,9 +74,10 @@ test_that("We can simplify data from the cell feed", {
 
   foo <- ss %>% gs_read_cellfeed(ws = "Africa", range = cell_rows(2:3))
   expect_equal_to_reference(foo %>% gs_simplify_cellfeed(),
-                            "gap_africa_simplify_A1.rds")
-  expect_equal_to_reference(foo %>% gs_simplify_cellfeed(notation = "R1C1"),
-                                                "gap_africa_simplify_R1C1.rds")
+                            "for_reference/gap_africa_simplify_A1.rds")
+  expect_equal_to_reference(
+    foo %>% gs_simplify_cellfeed(notation = "R1C1"),
+    "for_reference/gap_africa_simplify_R1C1.rds")
 
   foo <- ss %>% gs_read_cellfeed(ws = "Oceania", range = cell_cols(3))
   foo_simple <- foo %>% gs_simplify_cellfeed()
diff --git a/tests/testthat/test-yy-consume-data-public-tricky.R b/tests/testthat/test-yy-consume-data-public-tricky.R
index fe10a0d..50b8723 100644
--- a/tests/testthat/test-yy-consume-data-public-tricky.R
+++ b/tests/testthat/test-yy-consume-data-public-tricky.R
@@ -78,7 +78,7 @@ test_that("We can handle embedded empty cells via cell feed", {
 test_that("Special Characters can be imported correctly", {
 
   expect_equal_to_reference(gs_read_listfeed(ss, ws = "special_chars"),
-                            "pts_special_chars.rds")
+                            "for_reference/pts_special_chars.rds")
 
 })
 
diff --git a/tests/testthat/test-yy-consume-data-public-whole-sheets.R b/tests/testthat/test-yy-consume-data-public-whole-sheets.R
index 630b51f..ba733dc 100644
--- a/tests/testthat/test-yy-consume-data-public-whole-sheets.R
+++ b/tests/testthat/test-yy-consume-data-public-whole-sheets.R
@@ -6,14 +6,14 @@ ss <- gs_ws_feed(GAP_WS_FEED, lookup = FALSE, verbose = FALSE)
 test_that("We can get all data from the list feed (pub)", {
 
   expect_equal_to_reference(gs_read_listfeed(ss, ws = 5),
-                            "gap_sheet5_gs_read_listfeed.rds")
+                            "for_reference/gap_sheet5_gs_read_listfeed.rds")
 
 })
 
 test_that("We can get all data from the cell feed (pub)", {
 
   expect_equal_to_reference(gs_read_cellfeed(ss, ws = 5),
-                            "gap_sheet5_gs_read_cellfeed.rds")
+                            "for_reference/gap_sheet5_gs_read_cellfeed.rds")
 
 })
 
@@ -21,7 +21,8 @@ test_that("We can get all data from the exportcsv link (pub)", {
 
   dat1 <- gs_read_csv(ss, ws = 5)
   names(dat1) <-  dat1 %>% names() %>% tolower()
-  expect_equal_to_reference(dat1, "gap_sheet5_gs_read_listfeed.rds")
+  expect_equal_to_reference(dat1,
+                            "for_reference/gap_sheet5_gs_read_listfeed.rds")
 
 })
 

From 8795e78d61bbb777e3a568a7daf577612e664eac Mon Sep 17 00:00:00 2001
From: jennybc 
Date: Sun, 31 May 2015 23:23:15 -0700
Subject: [PATCH 6/7] update README

---
 README.Rmd | 125 ++++++++++-----
 README.md  | 453 +++++++++++++++++++++++++++++++++++++----------------
 2 files changed, 403 insertions(+), 175 deletions(-)

diff --git a/README.Rmd b/README.Rmd
index b15c0f8..6adf088 100644
--- a/README.Rmd
+++ b/README.Rmd
@@ -86,9 +86,7 @@ suppressPackageStartupMessages(library("dplyr"))
 
 ### Function naming convention
 
-*implementation not yet 100% complete ... but we'll get there soon*
-
-All functions start with `gs_`, which plays nicely with tab completion in RStudio, for example. If the function has something to do with worksheets or tabs within a spreadsheet, it will start with `gs_ws_`.
+All functions start with `gs_`, which plays nicely with tab completion in RStudio, for example. If the function has something to do with worksheets or tabs within a spreadsheet, then it will start with `gs_ws_`.
 
 ### See some spreadsheets you can access
 
@@ -139,24 +137,46 @@ third_party_gap <- GAP_URL %>%
 # Worried that a spreadsheet's registration is out-of-date?
 # Re-register it!
 gap <- gap %>% gs_gs()
-gap
 ```
 
 The registration functions `gs_title()`, `gs_key()`, `gs_url()`, and `gs_gs()` return a registered sheet as a `googlesheet` object, which is the first argument to practically every function in this package. Likewise, almost every function returns a freshly registered `googlesheet` object, ready to be stored or piped into the next command.
 
+*We export a utility function, `extract_key_from_url()`, to help you get and store the key from a browser URL. Registering via browser URL is fine, but registering by key is probably a better idea in the long-run.*
+
 ### Consume data
 
 #### Ignorance is bliss
 
-*coming soon: a wrapper for the functions described below that just gets the data you want, while you remain blissfully ignorant of how we're doing it*
+If you want to consume the data in a worksheet and get something rectangular back, use the all-purpose function `gs_read()`. By default, it reads all the data in a worksheet.
+
+```{r}
+oceania <- gap %>% gs_read(ws = "Oceania")
+oceania
+str(oceania)
+glimpse(oceania)
+```
+
+You can target specific cells via the `range =` argument. The simplest usage is to specify an Excel-like cell range, such as range = "D12:F15" or range = "R1C12:R6C15". The cell rectangle can be specified in various other ways, using helper functions.
+
+```{r}
+gap %>% gs_read(ws = 2, range = "A1:D8")
+gap %>% gs_read(ws = "Europe", range = cell_rows(1:4))
+gap %>% gs_read(ws = "Europe", range = cell_rows(100:103), col_names = FALSE)
+gap %>% gs_read(ws = "Africa", range = cell_cols(1:4))
+gap %>% gs_read(ws = "Asia", range = cell_limits(c(1, 5), c(4, NA)))
+```
+
+`gs_read()` is a wrapper that bundles together the most common methods to read data from the API and transform it for downstream use. You can refine it's behavior further, by passing more arguments via `...`. Read the help file for more details.
+
+If `gs_read()` doesn't do what you need, then keep reading for the underlying functions to read and post-process data.
 
 #### Specify the consumption method
 
 There are three ways to consume data from a worksheet within a Google spreadsheet. The order goes from fastest-but-more-limited to slowest-but-most-flexible:
 
-  * `gs_read_csv()`: Don't let the name scare you! Nothing is written to file during this process. The name just reflects that, under the hood, we request the data via the "exportcsv" link. For cases where `gs_read_csv()` and `gs_read_listfeed()` both work, we see that `gs_read_csv()` is around __50 times faster__. Use this when your data occupies a nice rectangle in the sheet and you're willing to consume all of it. You will get a `tbl_df` back, which is basically just a `data.frame`.
+  * `gs_read_csv()`: Don't let the name scare you! Nothing is written to file during this process. The name just reflects that, under the hood, we request the data via the "exportcsv" link. For cases where `gs_read_csv()` and `gs_read_listfeed()` both work, we see that `gs_read_csv()` is around __50 times faster__. Use this when your data occupies a nice rectangle in the sheet and you're willing to consume all of it. You will get a `tbl_df` back, which is basically just a `data.frame`. In fact, you might want to use `gs_read_csv()`, it in other, less tidy scenarios and do further munging in R.
   * `gs_read_listfeed()`: Gets data via the ["list feed"](https://developers.google.com/google-apps/spreadsheets/#working_with_list-based_feeds), which consumes data row-by-row. Like `gs_read_csv()`, this is appropriate when your data occupies a nice rectangle. You will again get a `tbl_df` back, but your variable names may have been mangled (by Google, not us!). Specifically, variable names will be forcefully lowercased and all non-alpha-numeric characters will be removed. Why do we even have this function? The list feed supports some query parameters for sorting and filtering the data, which we plan to support (#17).
-  * `gs_read_cellfeed()`: Get data via the ["cell feed"](https://developers.google.com/google-apps/spreadsheets/#working_with_cell-based_feeds), which consumes data cell-by-cell. This is appropriate when you want to consume arbitrary cells, rows, columns, and regions of the sheet. It works great for small amounts of data but can be rather slow otherwise. `gs_read_cellfeed()` returns a `tbl_df` with __one row per cell__. You can specify cell limits in `gs_read_cellfeed()` via the `range` argument. See below for demos of `gs_reshape_cellfeed()` and `gs_simplify_cellfeed()` which help with post-processing.
+  * `gs_read_cellfeed()`: Get data via the ["cell feed"](https://developers.google.com/google-apps/spreadsheets/#working_with_cell-based_feeds), which consumes data cell-by-cell. This is appropriate when you want to consume arbitrary cells, rows, columns, and regions of the sheet. It is invoked by `gs_read()` whenever the `range =` argument is used. It works great for modest amounts of data but can be rather slow otherwise. `gs_read_cellfeed()` returns a `tbl_df` with __one row per cell__. You can target specific cells via the `range` argument. See below for demos of `gs_reshape_cellfeed()` and `gs_simplify_cellfeed()` which help with post-processing.
 
 ```{r csv-list-and-cell-feed}
 # Get the data for worksheet "Oceania": the super-fast csv way
@@ -164,36 +184,67 @@ oceania_csv <- gap %>% gs_read_csv(ws = "Oceania")
 str(oceania_csv)
 oceania_csv
 
-# Get the data for worksheet "Oceania": the fast tabular way ("list feed")
+# Get the data for worksheet "Oceania": the less-fast tabular way ("list feed")
 oceania_list_feed <- gap %>% gs_read_listfeed(ws = "Oceania") 
 str(oceania_list_feed)
 oceania_list_feed
 
-# Get the data for worksheet "Oceania": the slower cell-by-cell way ("cell feed")
+# Get the data for worksheet "Oceania": the slow cell-by-cell way ("cell feed")
 oceania_cell_feed <- gap %>% gs_read_cellfeed(ws = "Oceania") 
 str(oceania_cell_feed)
 oceania_cell_feed
 ```
 
-#### Convenience wrappers and post-processing the data
+#### Quick speed comparison
 
-There are a few ways to limit the data you're consuming. You can put direct limits into `gs_read_cellfeed()`, ~~but there are also convenience functions to get a row (`get_row()`), a column (`get_col()`), or a range (`get_cells()`)~~. Also, when you consume data via the cell feed (which these wrappers are doing under the hood), you will often want to reshape it or simplify it (`gs_reshape_cellfeed()` and `gs_simplify_cellfeed()`).
+Let's consume all the data for Africa by all 3 methods and see how long it takes.
 
-```{r wrappers-and-post-processing}
-# Reshape: instead of one row per cell, make a nice rectangular data.frame
-oceania_reshaped <- oceania_cell_feed %>% gs_reshape_cellfeed()
-str(oceania_reshaped)
-oceania_reshaped
+```{r}
+jfun <- function(readfun)
+  system.time(do.call(readfun, list(gs_gap(), ws = "Africa", verbose = FALSE)))
+readfuns <- c("gs_read_csv", "gs_read_listfeed", "gs_read_cellfeed")
+readfuns <- sapply(readfuns, get, USE.NAMES = TRUE)
+sapply(readfuns, jfun)
+```
 
-# Limit data retrieval to certain cells
+#### Post-processing data from the cell feed
+
+If you consume data from the cell feed with `gs_read_cellfeed(..., range = ...)`, you get a data.frame back with **one row per cell**. The package offers two functions to post-process this into something more useful, `gs_reshape_cellfeed()` and `gs_simplify_cellfeed()`.
+
+To reshape into a table, use `gs_reshape_cellfeed()`. You can signal that the first row contains column names (or not) with `col_names = TRUE` (or `FALSE`). Or you can provide a character vector of names. This is inspired by the `col_names` argument of `readxl::read_excel()` and `readr::read_delim()`, which generalizes the `header` argument of `read.table()`.
+
+```{r post-processing}
+# Reshape: instead of one row per cell, make a nice rectangular data.frame
+australia_cell_feed <- gap %>%
+  gs_read_cellfeed(ws = "Oceania", range = "A1:F13") 
+str(australia_cell_feed)
+oceania_cell_feed
+australia_reshaped <- australia_cell_feed %>% gs_reshape_cellfeed()
+str(australia_reshaped)
+australia_reshaped
 
 # Example: first 3 rows
 gap_3rows <- gap %>% gs_read_cellfeed("Europe", range = cell_rows(1:3))
 gap_3rows %>% head()
 
-# convert to a data.frame (first row treated as header by default)
+# convert to a data.frame (by default, column names found in first row)
 gap_3rows %>% gs_reshape_cellfeed()
 
+# arbitrary cell range, column names no longer available in first row
+gap %>%
+  gs_read_cellfeed("Oceania", range = "D12:F15") %>%
+  gs_reshape_cellfeed(col_names = FALSE)
+
+# arbitrary cell range, direct specification of column names
+gap %>%
+  gs_read_cellfeed("Oceania", range = cell_limits(c(2, 5), c(1, 3))) %>%
+  gs_reshape_cellfeed(col_names = paste("thing", c("one", "two", "three"),
+                                        sep = "_"))
+```
+
+To extract the cell data into an atomic vector, possibly named, use `gs_simplify_cellfeed()`. You can signal that the first row contains column names (or not) with `col_names = TRUE` (or `FALSE`). There are several arguments to control conversion.
+
+```{r}
 # Example: first row only
 gap_1row <- gap %>% gs_read_cellfeed("Europe", range = cell_rows(1))
 gap_1row
@@ -201,20 +252,12 @@ gap_1row
 # convert to a named character vector
 gap_1row %>% gs_simplify_cellfeed()
 
-# just 2 columns, converted to data.frame
-gap %>%
-  gs_read_cellfeed("Oceania", range = cell_cols(3:4)) %>%
-  gs_reshape_cellfeed()
+# Example: single column
+gap_1col <- gap %>% gs_read_cellfeed("Europe", range = cell_cols(3))
+gap_1col
 
-# arbitrary cell range
-gap %>%
-  gs_read_cellfeed("Oceania", range = "D12:F15") %>%
-  gs_reshape_cellfeed(col_names = FALSE)
-
-# arbitrary cell range, alternative specification
-gap %>%
-  gs_read_cellfeed("Oceania", range = cell_limits(c(NA, 5), c(1, 3))) %>%
-  gs_reshape_cellfeed()
+# convert to a un-named character vector and drop the variable name
+gap_1col %>% gs_simplify_cellfeed(notation = "none", col_names = TRUE)
 ```
 
 ### Create sheets
@@ -226,7 +269,7 @@ foo <- gs_new("foo")
 foo
 ```
 
-By default, there will be an empty worksheet called "Sheet1", but you can control it's title, extent, and initial data with additional arguments to `gs_new()`. You can also add, rename, and delete worksheets within an existing sheet via `gs_ws_new()`, `gs_ws_rename()`, and `gs_ws_delete()`. Copy an entire spreadsheet with `gs_copy()`.
+By default, there will be an empty worksheet called "Sheet1", but you can control it's title, extent, and initial data with additional arguments to `gs_new()` (see `gs_edit_cells()` in the next section). You can also add, rename, and delete worksheets within an existing sheet via `gs_ws_new()`, `gs_ws_rename()`, and `gs_ws_delete()`. Copy an entire spreadsheet with `gs_copy()`.
 
 ### Edit cells
 
@@ -236,7 +279,7 @@ You can modify the data in sheet cells via `gs_edit_cells()`. We'll work on the
 foo <- foo %>% gs_edit_cells(input = head(iris), trim = TRUE)
 ```
 
-Go to [your Google Sheets home screen](https://docs.google.com/spreadsheets/u/0/), find the new sheet `foo` and look at it. You should see some iris data in the first (and only) worksheet. We'll also take a look at it here, by consuming `foo` via the list feed.
+Go to [your Google Sheets home screen](https://docs.google.com/spreadsheets/u/0/), find the new sheet `foo` and look at it. You should see some iris data in the first (and only) worksheet. We'll also take a look at it here, by reading the data from `foo`.
 
 Note how we always store the returned value from `gs_edit_cells()` (and all other sheet editing functions). That's because the registration info changes whenever we edit the sheet and we re-register it inside these functions, so this idiom will help you make sequential edits and queries to the same sheet.
 
@@ -261,10 +304,12 @@ If you'd rather specify sheets for deletion by title, look at `gs_grepdel()` and
 Here's how we can create a new spreadsheet from a suitable local file. First, we'll write then upload a comma-delimited excerpt from the iris data.
 
 ```{r new-sheet-from-file}
-iris %>% head(5) %>% write.csv("iris.csv", row.names = FALSE)
+iris %>%
+  head(5) %>%
+  write.csv("iris.csv", row.names = FALSE)
 iris_ss <- gs_upload("iris.csv")
 iris_ss
-iris_ss %>% gs_read_listfeed()
+iris_ss %>% gs_read()
 file.remove("iris.csv")
 ```
 
@@ -273,14 +318,16 @@ Now we'll upload a multi-sheet Excel workbook. Slowly.
 ```{r new-sheet-from-xlsx}
 gap_xlsx <- gs_upload(system.file("mini-gap.xlsx", package = "googlesheets"))
 gap_xlsx
-gap_xlsx %>% gs_read_listfeed(ws = "Oceania")
+gap_xlsx %>% gs_read(ws = "Asia")
 ```
 
 And we clean up after ourselves on Google Drive.
 
 ```{r delete-moar-sheets}
-gs_delete(iris_ss)
-gs_delete(gap_xlsx)
+gs_vecdel(c("iris", "mini-gap"))
+## achieves same as:
+## gs_delete(iris_ss)
+## gs_delete(gap_xlsx)
 ```
 
 ### Download sheets as csv, pdf, or xlsx file
@@ -331,4 +378,4 @@ user_session_info
 
 In March 2014 [Google introduced "new" Sheets](https://support.google.com/docs/answer/3541068?hl=en). "New" Sheets and "old" sheets behave quite differently with respect to access via API and present a big headache for us. Recently, we've noted that Google is forcibly converting sheets: [all "old" Sheets will be switched over the "new" sheets during 2015](https://support.google.com/docs/answer/6082736?p=new_sheets_migrate&rd=1). However there are still "old" sheets lying around, so we've made some effort to support them, when it's easy to do so. But keep your expectations low.
 
-In particular, `gs_read_csv()` does not and indeed __cannot__ work for "old"   sheets.
+In particular, `gs_read_csv()` does not currently work for "old" sheets.
diff --git a/README.md b/README.md
index 093be1f..e693142 100644
--- a/README.md
+++ b/README.md
@@ -64,9 +64,7 @@ suppressPackageStartupMessages(library("dplyr"))
 
 ### Function naming convention
 
-*implementation not yet 100% complete ... but we'll get there soon*
-
-All functions start with `gs_`, which plays nicely with tab completion in RStudio, for example. If the function has something to do with worksheets or tabs within a spreadsheet, it will start with `gs_ws_`.
+All functions start with `gs_`, which plays nicely with tab completion in RStudio, for example. If the function has something to do with worksheets or tabs within a spreadsheet, then it will start with `gs_ws_`.
 
 ### See some spreadsheets you can access
 
@@ -74,31 +72,31 @@ The `gs_ls()` function returns the sheets you would see in your Google Sheets ho
 
 ``` r
 (my_sheets <- gs_ls())
-#> Source: local data frame [40 x 10]
+#> Source: local data frame [39 x 10]
 #> 
 #>                 sheet_title        author perm version             updated
-#> 1  Copy of Twitter Archive…   joannazhaoo    r     new 2015-05-31 22:00:25
-#> 2               TAGS v6.0ns     m.hawksey    r     new 2015-05-31 21:12:24
-#> 3  Copy of test-gs-gapmind…      gspreadr   rw     new 2015-05-31 21:11:01
-#> 4              #rhizo15 #tw     m.hawksey    r     new 2015-05-31 13:12:01
-#> 5   EasyTweetSheet - Shared     m.hawksey    r     new 2015-05-31 22:02:01
-#> 6  Ari's Anchor Text Scrap…      anahmani    r     new 2015-05-29 07:18:48
-#> 7  Tweet Collector (TAGS v…      gspreadr   rw     new 2015-05-28 17:43:29
-#> 8      test-gs-cars-private      gspreadr   rw     new 2015-05-27 17:48:34
-#> 9     All R Phylo Functions  omeara.brian    r     new 2015-05-20 18:34:43
-#> 10 test-gs-public-testing-…  rpackagetest    r     new 2015-05-20 01:32:27
+#> 1  Copy of Twitter Archive…   joannazhaoo    r     new 2015-06-01 04:31:25
+#> 2   EasyTweetSheet - Shared     m.hawksey    r     new 2015-06-01 02:49:46
+#> 3               TAGS v6.0ns     m.hawksey    r     new 2015-05-31 21:12:24
+#> 4              #rhizo15 #tw     m.hawksey    r     new 2015-06-01 01:37:53
+#> 5  Ari's Anchor Text Scrap…      anahmani    r     new 2015-05-29 07:18:48
+#> 6  Tweet Collector (TAGS v…      gspreadr   rw     new 2015-05-28 17:43:29
+#> 7      test-gs-cars-private      gspreadr   rw     new 2015-05-27 17:48:34
+#> 8     All R Phylo Functions  omeara.brian    r     new 2015-05-20 18:34:43
+#> 9  test-gs-public-testing-…  rpackagetest    r     new 2015-05-20 01:32:27
+#> 10                 ari copy      gspreadr   rw     new 2015-05-19 23:00:13
 #> ..                      ...           ...  ...     ...                 ...
 #> Variables not shown: sheet_key (chr), ws_feed (chr), alternate (chr), self
 #>   (chr), alt_key (chr)
 # (expect a prompt to authenticate with Google interactively HERE)
 my_sheets %>% glimpse()
-#> Observations: 40
+#> Observations: 39
 #> Variables:
-#> $ sheet_title (chr) "Copy of Twitter Archiver v2.1", "TAGS v6.0ns", "C...
-#> $ author      (chr) "joannazhaoo", "m.hawksey", "gspreadr", "m.hawksey...
-#> $ perm        (chr) "r", "r", "rw", "r", "r", "r", "rw", "rw", "r", "r...
+#> $ sheet_title (chr) "Copy of Twitter Archiver v2.1", "EasyTweetSheet -...
+#> $ author      (chr) "joannazhaoo", "m.hawksey", "m.hawksey", "m.hawkse...
+#> $ perm        (chr) "r", "r", "r", "r", "r", "rw", "rw", "r", "r", "rw...
 #> $ version     (chr) "new", "new", "new", "new", "new", "new", "new", "...
-#> $ updated     (time) 2015-05-31 22:00:25, 2015-05-31 21:12:24, 2015-05...
+#> $ updated     (time) 2015-06-01 04:31:25, 2015-06-01 02:49:46, 2015-05...
 #> $ sheet_key   (chr) "1DoMXh2m3FGPoZAle9vnzg763D9FESTU506iqWkUTwtE", "1...
 #> $ ws_feed     (chr) "https://spreadsheets.google.com/feeds/worksheets/...
 #> $ alternate   (chr) "https://docs.google.com/spreadsheets/d/1DoMXh2m3F...
@@ -130,7 +128,7 @@ gap <- gs_title("Gapminder")
 #> Sheet successfully identifed: "Gapminder"
 gap
 #>                   Spreadsheet title: Gapminder
-#>   Date of googlesheets registration: 2015-05-31 22:07:47 GMT
+#>   Date of googlesheets registration: 2015-06-01 04:35:10 GMT
 #>     Date of last spreadsheet update: 2015-03-23 20:34:08 GMT
 #>                          visibility: private
 #>                         permissions: rw
@@ -173,40 +171,125 @@ third_party_gap <- GAP_URL %>%
 gap <- gap %>% gs_gs()
 #> Authentication will be used.
 #> Sheet successfully identifed: "Gapminder"
-gap
-#>                   Spreadsheet title: Gapminder
-#>   Date of googlesheets registration: 2015-05-31 22:07:51 GMT
-#>     Date of last spreadsheet update: 2015-03-23 20:34:08 GMT
-#>                          visibility: private
-#>                         permissions: rw
-#>                             version: new
-#> 
-#> Contains 5 worksheets:
-#> (Title): (Nominal worksheet extent as rows x columns)
-#> Africa: 625 x 6
-#> Americas: 301 x 6
-#> Asia: 397 x 6
-#> Europe: 361 x 6
-#> Oceania: 25 x 6
-#> 
-#> Key: 1HT5B8SgkKqHdqHJmn5xiuaC04Ngb7dG9Tv94004vezA
 ```
 
 The registration functions `gs_title()`, `gs_key()`, `gs_url()`, and `gs_gs()` return a registered sheet as a `googlesheet` object, which is the first argument to practically every function in this package. Likewise, almost every function returns a freshly registered `googlesheet` object, ready to be stored or piped into the next command.
 
+*We export a utility function, `extract_key_from_url()`, to help you get and store the key from a browser URL. Registering via browser URL is fine, but registering by key is probably a better idea in the long-run.*
+
 ### Consume data
 
 #### Ignorance is bliss
 
-*coming soon: a wrapper for the functions described below that just gets the data you want, while you remain blissfully ignorant of how we're doing it*
+If you want to consume the data in a worksheet and get something rectangular back, use the all-purpose function `gs_read()`. By default, it reads all the data in a worksheet.
+
+``` r
+oceania <- gap %>% gs_read(ws = "Oceania")
+#> Accessing worksheet titled "Oceania"
+oceania
+#> Source: local data frame [24 x 6]
+#> 
+#>      country continent year lifeExp      pop gdpPercap
+#> 1  Australia   Oceania 1952   69.12  8691212  10039.60
+#> 2  Australia   Oceania 1957   70.33  9712569  10949.65
+#> 3  Australia   Oceania 1962   70.93 10794968  12217.23
+#> 4  Australia   Oceania 1967   71.10 11872264  14526.12
+#> 5  Australia   Oceania 1972   71.93 13177000  16788.63
+#> 6  Australia   Oceania 1977   73.49 14074100  18334.20
+#> 7  Australia   Oceania 1982   74.74 15184200  19477.01
+#> 8  Australia   Oceania 1987   76.32 16257249  21888.89
+#> 9  Australia   Oceania 1992   77.56 17481977  23424.77
+#> 10 Australia   Oceania 1997   78.83 18565243  26997.94
+#> ..       ...       ...  ...     ...      ...       ...
+str(oceania)
+#> Classes 'tbl_df', 'tbl' and 'data.frame':    24 obs. of  6 variables:
+#>  $ country  : chr  "Australia" "Australia" "Australia" "Australia" ...
+#>  $ continent: chr  "Oceania" "Oceania" "Oceania" "Oceania" ...
+#>  $ year     : int  1952 1957 1962 1967 1972 1977 1982 1987 1992 1997 ...
+#>  $ lifeExp  : num  69.1 70.3 70.9 71.1 71.9 ...
+#>  $ pop      : int  8691212 9712569 10794968 11872264 13177000 14074100 15184200 16257249 17481977 18565243 ...
+#>  $ gdpPercap: num  10040 10950 12217 14526 16789 ...
+glimpse(oceania)
+#> Observations: 24
+#> Variables:
+#> $ country   (chr) "Australia", "Australia", "Australia", "Australia", ...
+#> $ continent (chr) "Oceania", "Oceania", "Oceania", "Oceania", "Oceania...
+#> $ year      (int) 1952, 1957, 1962, 1967, 1972, 1977, 1982, 1987, 1992...
+#> $ lifeExp   (dbl) 69.120, 70.330, 70.930, 71.100, 71.930, 73.490, 74.7...
+#> $ pop       (int) 8691212, 9712569, 10794968, 11872264, 13177000, 1407...
+#> $ gdpPercap (dbl) 10039.60, 10949.65, 12217.23, 14526.12, 16788.63, 18...
+```
+
+You can target specific cells via the `range =` argument. The simplest usage is to specify an Excel-like cell range, such as range = "D12:F15" or range = "R1C12:R6C15". The cell rectangle can be specified in various other ways, using helper functions.
+
+``` r
+gap %>% gs_read(ws = 2, range = "A1:D8")
+#> Accessing worksheet titled "Americas"
+#> Source: local data frame [7 x 4]
+#> 
+#>     country continent year lifeExp
+#> 1 Argentina  Americas 1952  62.485
+#> 2 Argentina  Americas 1957  64.399
+#> 3 Argentina  Americas 1962  65.142
+#> 4 Argentina  Americas 1967  65.634
+#> 5 Argentina  Americas 1972  67.065
+#> 6 Argentina  Americas 1977  68.481
+#> 7 Argentina  Americas 1982  69.942
+gap %>% gs_read(ws = "Europe", range = cell_rows(1:4))
+#> Accessing worksheet titled "Europe"
+#> Source: local data frame [3 x 6]
+#> 
+#>   country continent year lifeExp     pop gdpPercap
+#> 1 Albania    Europe 1952   55.23 1282697  1601.056
+#> 2 Albania    Europe 1957   59.28 1476505  1942.284
+#> 3 Albania    Europe 1962   64.82 1728137  2312.889
+gap %>% gs_read(ws = "Europe", range = cell_rows(100:103), col_names = FALSE)
+#> Accessing worksheet titled "Europe"
+#> Source: local data frame [4 x 6]
+#> 
+#>        X1     X2   X3    X4      X5        X6
+#> 1 Finland Europe 1962 68.75 4491443  9371.843
+#> 2 Finland Europe 1967 69.83 4605744 10921.636
+#> 3 Finland Europe 1972 70.87 4639657 14358.876
+#> 4 Finland Europe 1977 72.52 4738902 15605.423
+gap %>% gs_read(ws = "Africa", range = cell_cols(1:4))
+#> Accessing worksheet titled "Africa"
+#> Source: local data frame [624 x 4]
+#> 
+#>    country continent year lifeExp
+#> 1  Algeria    Africa 1952  43.077
+#> 2  Algeria    Africa 1957  45.685
+#> 3  Algeria    Africa 1962  48.303
+#> 4  Algeria    Africa 1967  51.407
+#> 5  Algeria    Africa 1972  54.518
+#> 6  Algeria    Africa 1977  58.014
+#> 7  Algeria    Africa 1982  61.368
+#> 8  Algeria    Africa 1987  65.799
+#> 9  Algeria    Africa 1992  67.744
+#> 10 Algeria    Africa 1997  69.152
+#> ..     ...       ...  ...     ...
+gap %>% gs_read(ws = "Asia", range = cell_limits(c(1, 5), c(4, NA)))
+#> Accessing worksheet titled "Asia"
+#> Source: local data frame [4 x 3]
+#> 
+#>   lifeExp      pop gdpPercap
+#> 1  28.801  8425333  779.4453
+#> 2  30.332  9240934  820.8530
+#> 3  31.997 10267083  853.1007
+#> 4  34.020 11537966  836.1971
+```
+
+`gs_read()` is a wrapper that bundles together the most common methods to read data from the API and transform it for downstream use. You can refine it's behavior further, by passing more arguments via `...`. Read the help file for more details.
+
+If `gs_read()` doesn't do what you need, then keep reading for the underlying functions to read and post-process data.
 
 #### Specify the consumption method
 
 There are three ways to consume data from a worksheet within a Google spreadsheet. The order goes from fastest-but-more-limited to slowest-but-most-flexible:
 
--   `gs_read_csv()`: Don't let the name scare you! Nothing is written to file during this process. The name just reflects that, under the hood, we request the data via the "exportcsv" link. For cases where `gs_read_csv()` and `gs_read_listfeed()` both work, we see that `gs_read_csv()` is around **50 times faster**. Use this when your data occupies a nice rectangle in the sheet and you're willing to consume all of it. You will get a `tbl_df` back, which is basically just a `data.frame`.
+-   `gs_read_csv()`: Don't let the name scare you! Nothing is written to file during this process. The name just reflects that, under the hood, we request the data via the "exportcsv" link. For cases where `gs_read_csv()` and `gs_read_listfeed()` both work, we see that `gs_read_csv()` is around **50 times faster**. Use this when your data occupies a nice rectangle in the sheet and you're willing to consume all of it. You will get a `tbl_df` back, which is basically just a `data.frame`. In fact, you might want to use `gs_read_csv()`, it in other, less tidy scenarios and do further munging in R.
 -   `gs_read_listfeed()`: Gets data via the ["list feed"](https://developers.google.com/google-apps/spreadsheets/#working_with_list-based_feeds), which consumes data row-by-row. Like `gs_read_csv()`, this is appropriate when your data occupies a nice rectangle. You will again get a `tbl_df` back, but your variable names may have been mangled (by Google, not us!). Specifically, variable names will be forcefully lowercased and all non-alpha-numeric characters will be removed. Why do we even have this function? The list feed supports some query parameters for sorting and filtering the data, which we plan to support (\#17).
--   `gs_read_cellfeed()`: Get data via the ["cell feed"](https://developers.google.com/google-apps/spreadsheets/#working_with_cell-based_feeds), which consumes data cell-by-cell. This is appropriate when you want to consume arbitrary cells, rows, columns, and regions of the sheet. It works great for small amounts of data but can be rather slow otherwise. `gs_read_cellfeed()` returns a `tbl_df` with **one row per cell**. You can specify cell limits in `gs_read_cellfeed()` via the `range` argument. See below for demos of `gs_reshape_cellfeed()` and `gs_simplify_cellfeed()` which help with post-processing.
+-   `gs_read_cellfeed()`: Get data via the ["cell feed"](https://developers.google.com/google-apps/spreadsheets/#working_with_cell-based_feeds), which consumes data cell-by-cell. This is appropriate when you want to consume arbitrary cells, rows, columns, and regions of the sheet. It is invoked by `gs_read()` whenever the `range =` argument is used. It works great for modest amounts of data but can be rather slow otherwise. `gs_read_cellfeed()` returns a `tbl_df` with **one row per cell**. You can target specific cells via the `range` argument. See below for demos of `gs_reshape_cellfeed()` and `gs_simplify_cellfeed()` which help with post-processing.
 
 ``` r
 # Get the data for worksheet "Oceania": the super-fast csv way
@@ -236,7 +319,7 @@ oceania_csv
 #> 10 Australia   Oceania 1997   78.83 18565243  26997.94
 #> ..       ...       ...  ...     ...      ...       ...
 
-# Get the data for worksheet "Oceania": the fast tabular way ("list feed")
+# Get the data for worksheet "Oceania": the less-fast tabular way ("list feed")
 oceania_list_feed <- gap %>% gs_read_listfeed(ws = "Oceania") 
 #> Accessing worksheet titled "Oceania"
 str(oceania_list_feed)
@@ -263,7 +346,7 @@ oceania_list_feed
 #> 10 Australia   Oceania 1997   78.83 18565243  26997.94
 #> ..       ...       ...  ...     ...      ...       ...
 
-# Get the data for worksheet "Oceania": the slower cell-by-cell way ("cell feed")
+# Get the data for worksheet "Oceania": the slow cell-by-cell way ("cell feed")
 oceania_cell_feed <- gap %>% gs_read_cellfeed(ws = "Oceania") 
 #> Accessing worksheet titled "Oceania"
 str(oceania_cell_feed)
@@ -291,38 +374,83 @@ oceania_cell_feed
 #> ..  ...      ... ... ...       ...
 ```
 
-#### Convenience wrappers and post-processing the data
+#### Quick speed comparison
+
+Let's consume all the data for Africa by all 3 methods and see how long it takes.
+
+``` r
+jfun <- function(readfun)
+  system.time(do.call(readfun, list(gs_gap(), ws = "Africa", verbose = FALSE)))
+readfuns <- c("gs_read_csv", "gs_read_listfeed", "gs_read_cellfeed")
+readfuns <- sapply(readfuns, get, USE.NAMES = TRUE)
+sapply(readfuns, jfun)
+#>            gs_read_csv gs_read_listfeed gs_read_cellfeed
+#> user.self        0.057            0.392            1.425
+#> sys.self         0.002            0.017            0.042
+#> elapsed          0.364            0.868            2.802
+#> user.child       0.000            0.000            0.000
+#> sys.child        0.000            0.000            0.000
+```
+
+#### Post-processing data from the cell feed
+
+If you consume data from the cell feed with `gs_read_cellfeed(..., range = ...)`, you get a data.frame back with **one row per cell**. The package offers two functions to post-process this into something more useful, `gs_reshape_cellfeed()` and `gs_simplify_cellfeed()`.
 
-There are a few ways to limit the data you're consuming. You can put direct limits into `gs_read_cellfeed()`, ~~but there are also convenience functions to get a row (`get_row()`), a column (`get_col()`), or a range (`get_cells()`)~~. Also, when you consume data via the cell feed (which these wrappers are doing under the hood), you will often want to reshape it or simplify it (`gs_reshape_cellfeed()` and `gs_simplify_cellfeed()`).
+To reshape into a table, use `gs_reshape_cellfeed()`. You can signal that the first row contains column names (or not) with `col_names = TRUE` (or `FALSE`). Or you can provide a character vector of names. This is inspired by the `col_names` argument of `readxl::read_excel()` and `readr::read_delim()`, which generalizes the `header` argument of `read.table()`.
 
 ``` r
 # Reshape: instead of one row per cell, make a nice rectangular data.frame
-oceania_reshaped <- oceania_cell_feed %>% gs_reshape_cellfeed()
-str(oceania_reshaped)
-#> Classes 'tbl_df', 'tbl' and 'data.frame':    24 obs. of  6 variables:
+australia_cell_feed <- gap %>%
+  gs_read_cellfeed(ws = "Oceania", range = "A1:F13") 
+#> Accessing worksheet titled "Oceania"
+str(australia_cell_feed)
+#> Classes 'tbl_df', 'tbl' and 'data.frame':    78 obs. of  5 variables:
+#>  $ cell     : chr  "A1" "B1" "C1" "D1" ...
+#>  $ cell_alt : chr  "R1C1" "R1C2" "R1C3" "R1C4" ...
+#>  $ row      : int  1 1 1 1 1 1 2 2 2 2 ...
+#>  $ col      : int  1 2 3 4 5 6 1 2 3 4 ...
+#>  $ cell_text: chr  "country" "continent" "year" "lifeExp" ...
+#>  - attr(*, "ws_title")= chr "Oceania"
+oceania_cell_feed
+#> Source: local data frame [150 x 5]
+#> 
+#>    cell cell_alt row col cell_text
+#> 1    A1     R1C1   1   1   country
+#> 2    B1     R1C2   1   2 continent
+#> 3    C1     R1C3   1   3      year
+#> 4    D1     R1C4   1   4   lifeExp
+#> 5    E1     R1C5   1   5       pop
+#> 6    F1     R1C6   1   6 gdpPercap
+#> 7    A2     R2C1   2   1 Australia
+#> 8    B2     R2C2   2   2   Oceania
+#> 9    C2     R2C3   2   3      1952
+#> 10   D2     R2C4   2   4     69.12
+#> ..  ...      ... ... ...       ...
+australia_reshaped <- australia_cell_feed %>% gs_reshape_cellfeed()
+str(australia_reshaped)
+#> Classes 'tbl_df', 'tbl' and 'data.frame':    12 obs. of  6 variables:
 #>  $ country  : chr  "Australia" "Australia" "Australia" "Australia" ...
 #>  $ continent: chr  "Oceania" "Oceania" "Oceania" "Oceania" ...
 #>  $ year     : int  1952 1957 1962 1967 1972 1977 1982 1987 1992 1997 ...
 #>  $ lifeExp  : num  69.1 70.3 70.9 71.1 71.9 ...
 #>  $ pop      : int  8691212 9712569 10794968 11872264 13177000 14074100 15184200 16257249 17481977 18565243 ...
 #>  $ gdpPercap: num  10040 10950 12217 14526 16789 ...
-oceania_reshaped
-#> Source: local data frame [24 x 6]
+australia_reshaped
+#> Source: local data frame [12 x 6]
 #> 
 #>      country continent year lifeExp      pop gdpPercap
-#> 1  Australia   Oceania 1952   69.12  8691212  10039.60
-#> 2  Australia   Oceania 1957   70.33  9712569  10949.65
-#> 3  Australia   Oceania 1962   70.93 10794968  12217.23
-#> 4  Australia   Oceania 1967   71.10 11872264  14526.12
-#> 5  Australia   Oceania 1972   71.93 13177000  16788.63
-#> 6  Australia   Oceania 1977   73.49 14074100  18334.20
-#> 7  Australia   Oceania 1982   74.74 15184200  19477.01
-#> 8  Australia   Oceania 1987   76.32 16257249  21888.89
-#> 9  Australia   Oceania 1992   77.56 17481977  23424.77
-#> 10 Australia   Oceania 1997   78.83 18565243  26997.94
-#> ..       ...       ...  ...     ...      ...       ...
-
-# Limit data retrieval to certain cells
+#> 1  Australia   Oceania 1952  69.120  8691212  10039.60
+#> 2  Australia   Oceania 1957  70.330  9712569  10949.65
+#> 3  Australia   Oceania 1962  70.930 10794968  12217.23
+#> 4  Australia   Oceania 1967  71.100 11872264  14526.12
+#> 5  Australia   Oceania 1972  71.930 13177000  16788.63
+#> 6  Australia   Oceania 1977  73.490 14074100  18334.20
+#> 7  Australia   Oceania 1982  74.740 15184200  19477.01
+#> 8  Australia   Oceania 1987  76.320 16257249  21888.89
+#> 9  Australia   Oceania 1992  77.560 17481977  23424.77
+#> 10 Australia   Oceania 1997  78.830 18565243  26997.94
+#> 11 Australia   Oceania 2002  80.370 19546792  30687.75
+#> 12 Australia   Oceania 2007  81.235 20434176  34435.37
 
 # Example: first 3 rows
 gap_3rows <- gap %>% gs_read_cellfeed("Europe", range = cell_rows(1:3))
@@ -338,7 +466,7 @@ gap_3rows %>% head()
 #> 5   E1     R1C5   1   5       pop
 #> 6   F1     R1C6   1   6 gdpPercap
 
-# convert to a data.frame (first row treated as header by default)
+# convert to a data.frame (by default, column names found in first row)
 gap_3rows %>% gs_reshape_cellfeed()
 #> Source: local data frame [2 x 6]
 #> 
@@ -346,6 +474,37 @@ gap_3rows %>% gs_reshape_cellfeed()
 #> 1 Albania    Europe 1952   55.23 1282697  1601.056
 #> 2 Albania    Europe 1957   59.28 1476505  1942.284
 
+# arbitrary cell range, column names no longer available in first row
+gap %>%
+  gs_read_cellfeed("Oceania", range = "D12:F15") %>%
+  gs_reshape_cellfeed(col_names = FALSE)
+#> Accessing worksheet titled "Oceania"
+#> Source: local data frame [4 x 3]
+#> 
+#>       X4       X5       X6
+#> 1 80.370 19546792 30687.75
+#> 2 81.235 20434176 34435.37
+#> 3 69.390  1994794 10556.58
+#> 4 70.260  2229407 12247.40
+
+# arbitrary cell range, direct specification of column names
+gap %>%
+  gs_read_cellfeed("Oceania", range = cell_limits(c(2, 5), c(1, 3))) %>%
+  gs_reshape_cellfeed(col_names = paste("thing", c("one", "two", "three"),
+                                        sep = "_"))
+#> Accessing worksheet titled "Oceania"
+#> Source: local data frame [4 x 3]
+#> 
+#>   thing_one thing_two thing_three
+#> 1 Australia   Oceania        1952
+#> 2 Australia   Oceania        1957
+#> 3 Australia   Oceania        1962
+#> 4 Australia   Oceania        1967
+```
+
+To extract the cell data into an atomic vector, possibly named, use `gs_simplify_cellfeed()`. You can signal that the first row contains column names (or not) with `col_names = TRUE` (or `FALSE`). There are several arguments to control conversion.
+
+``` r
 # Example: first row only
 gap_1row <- gap %>% gs_read_cellfeed("Europe", range = cell_rows(1))
 #> Accessing worksheet titled "Europe"
@@ -365,51 +524,64 @@ gap_1row %>% gs_simplify_cellfeed()
 #>          A1          B1          C1          D1          E1          F1 
 #>   "country" "continent"      "year"   "lifeExp"       "pop" "gdpPercap"
 
-# just 2 columns, converted to data.frame
-gap %>%
-  gs_read_cellfeed("Oceania", range = cell_cols(3:4)) %>%
-  gs_reshape_cellfeed()
-#> Accessing worksheet titled "Oceania"
-#> Source: local data frame [24 x 2]
-#> 
-#>    year lifeExp
-#> 1  1952   69.12
-#> 2  1957   70.33
-#> 3  1962   70.93
-#> 4  1967   71.10
-#> 5  1972   71.93
-#> 6  1977   73.49
-#> 7  1982   74.74
-#> 8  1987   76.32
-#> 9  1992   77.56
-#> 10 1997   78.83
-#> ..  ...     ...
-
-# arbitrary cell range
-gap %>%
-  gs_read_cellfeed("Oceania", range = "D12:F15") %>%
-  gs_reshape_cellfeed(col_names = FALSE)
-#> Accessing worksheet titled "Oceania"
-#> Source: local data frame [4 x 3]
+# Example: single column
+gap_1col <- gap %>% gs_read_cellfeed("Europe", range = cell_cols(3))
+#> Accessing worksheet titled "Europe"
+gap_1col
+#> Source: local data frame [361 x 5]
 #> 
-#>       X4       X5       X6
-#> 1 80.370 19546792 30687.75
-#> 2 81.235 20434176 34435.37
-#> 3 69.390  1994794 10556.58
-#> 4 70.260  2229407 12247.40
+#>    cell cell_alt row col cell_text
+#> 1    C1     R1C3   1   3      year
+#> 2    C2     R2C3   2   3      1952
+#> 3    C3     R3C3   3   3      1957
+#> 4    C4     R4C3   4   3      1962
+#> 5    C5     R5C3   5   3      1967
+#> 6    C6     R6C3   6   3      1972
+#> 7    C7     R7C3   7   3      1977
+#> 8    C8     R8C3   8   3      1982
+#> 9    C9     R9C3   9   3      1987
+#> 10  C10    R10C3  10   3      1992
+#> ..  ...      ... ... ...       ...
 
-# arbitrary cell range, alternative specification
-gap %>%
-  gs_read_cellfeed("Oceania", range = cell_limits(c(NA, 5), c(1, 3))) %>%
-  gs_reshape_cellfeed()
-#> Accessing worksheet titled "Oceania"
-#> Source: local data frame [4 x 3]
-#> 
-#>     country continent year
-#> 1 Australia   Oceania 1952
-#> 2 Australia   Oceania 1957
-#> 3 Australia   Oceania 1962
-#> 4 Australia   Oceania 1967
+# convert to a un-named character vector and drop the variable name
+gap_1col %>% gs_simplify_cellfeed(notation = "none", col_names = TRUE)
+#>   [1] "year" "1952" "1957" "1962" "1967" "1972" "1977" "1982" "1987" "1992"
+#>  [11] "1997" "2002" "2007" "1952" "1957" "1962" "1967" "1972" "1977" "1982"
+#>  [21] "1987" "1992" "1997" "2002" "2007" "1952" "1957" "1962" "1967" "1972"
+#>  [31] "1977" "1982" "1987" "1992" "1997" "2002" "2007" "1952" "1957" "1962"
+#>  [41] "1967" "1972" "1977" "1982" "1987" "1992" "1997" "2002" "2007" "1952"
+#>  [51] "1957" "1962" "1967" "1972" "1977" "1982" "1987" "1992" "1997" "2002"
+#>  [61] "2007" "1952" "1957" "1962" "1967" "1972" "1977" "1982" "1987" "1992"
+#>  [71] "1997" "2002" "2007" "1952" "1957" "1962" "1967" "1972" "1977" "1982"
+#>  [81] "1987" "1992" "1997" "2002" "2007" "1952" "1957" "1962" "1967" "1972"
+#>  [91] "1977" "1982" "1987" "1992" "1997" "2002" "2007" "1952" "1957" "1962"
+#> [101] "1967" "1972" "1977" "1982" "1987" "1992" "1997" "2002" "2007" "1952"
+#> [111] "1957" "1962" "1967" "1972" "1977" "1982" "1987" "1992" "1997" "2002"
+#> [121] "2007" "1952" "1957" "1962" "1967" "1972" "1977" "1982" "1987" "1992"
+#> [131] "1997" "2002" "2007" "1952" "1957" "1962" "1967" "1972" "1977" "1982"
+#> [141] "1987" "1992" "1997" "2002" "2007" "1952" "1957" "1962" "1967" "1972"
+#> [151] "1977" "1982" "1987" "1992" "1997" "2002" "2007" "1952" "1957" "1962"
+#> [161] "1967" "1972" "1977" "1982" "1987" "1992" "1997" "2002" "2007" "1952"
+#> [171] "1957" "1962" "1967" "1972" "1977" "1982" "1987" "1992" "1997" "2002"
+#> [181] "2007" "1952" "1957" "1962" "1967" "1972" "1977" "1982" "1987" "1992"
+#> [191] "1997" "2002" "2007" "1952" "1957" "1962" "1967" "1972" "1977" "1982"
+#> [201] "1987" "1992" "1997" "2002" "2007" "1952" "1957" "1962" "1967" "1972"
+#> [211] "1977" "1982" "1987" "1992" "1997" "2002" "2007" "1952" "1957" "1962"
+#> [221] "1967" "1972" "1977" "1982" "1987" "1992" "1997" "2002" "2007" "1952"
+#> [231] "1957" "1962" "1967" "1972" "1977" "1982" "1987" "1992" "1997" "2002"
+#> [241] "2007" "1952" "1957" "1962" "1967" "1972" "1977" "1982" "1987" "1992"
+#> [251] "1997" "2002" "2007" "1952" "1957" "1962" "1967" "1972" "1977" "1982"
+#> [261] "1987" "1992" "1997" "2002" "2007" "1952" "1957" "1962" "1967" "1972"
+#> [271] "1977" "1982" "1987" "1992" "1997" "2002" "2007" "1952" "1957" "1962"
+#> [281] "1967" "1972" "1977" "1982" "1987" "1992" "1997" "2002" "2007" "1952"
+#> [291] "1957" "1962" "1967" "1972" "1977" "1982" "1987" "1992" "1997" "2002"
+#> [301] "2007" "1952" "1957" "1962" "1967" "1972" "1977" "1982" "1987" "1992"
+#> [311] "1997" "2002" "2007" "1952" "1957" "1962" "1967" "1972" "1977" "1982"
+#> [321] "1987" "1992" "1997" "2002" "2007" "1952" "1957" "1962" "1967" "1972"
+#> [331] "1977" "1982" "1987" "1992" "1997" "2002" "2007" "1952" "1957" "1962"
+#> [341] "1967" "1972" "1977" "1982" "1987" "1992" "1997" "2002" "2007" "1952"
+#> [351] "1957" "1962" "1967" "1972" "1977" "1982" "1987" "1992" "1997" "2002"
+#> [361] "2007"
 ```
 
 ### Create sheets
@@ -422,8 +594,8 @@ foo <- gs_new("foo")
 #> Worksheet dimensions: 1000 x 26.
 foo
 #>                   Spreadsheet title: foo
-#>   Date of googlesheets registration: 2015-05-31 22:07:56 GMT
-#>     Date of last spreadsheet update: 2015-05-31 22:07:55 GMT
+#>   Date of googlesheets registration: 2015-06-01 04:35:30 GMT
+#>     Date of last spreadsheet update: 2015-06-01 04:35:27 GMT
 #>                          visibility: private
 #>                         permissions: rw
 #>                             version: new
@@ -432,10 +604,10 @@ foo
 #> (Title): (Nominal worksheet extent as rows x columns)
 #> Sheet1: 1000 x 26
 #> 
-#> Key: 19J9GvlYsWABg3nEGAOpmemkpRFCoBQAggi5Ww2czLP4
+#> Key: 162Rin8cVkyb-vY-CAegDXUGZm1MRe0DX98LI4HuTPXA
 ```
 
-By default, there will be an empty worksheet called "Sheet1", but you can control it's title, extent, and initial data with additional arguments to `gs_new()`. You can also add, rename, and delete worksheets within an existing sheet via `gs_ws_new()`, `gs_ws_rename()`, and `gs_ws_delete()`. Copy an entire spreadsheet with `gs_copy()`.
+By default, there will be an empty worksheet called "Sheet1", but you can control it's title, extent, and initial data with additional arguments to `gs_new()` (see `gs_edit_cells()` in the next section). You can also add, rename, and delete worksheets within an existing sheet via `gs_ws_new()`, `gs_ws_rename()`, and `gs_ws_delete()`. Copy an entire spreadsheet with `gs_copy()`.
 
 ### Edit cells
 
@@ -452,7 +624,7 @@ foo <- foo %>% gs_edit_cells(input = head(iris), trim = TRUE)
 #> Worksheet "Sheet1" dimensions changed to 7 x 5.
 ```
 
-Go to [your Google Sheets home screen](https://docs.google.com/spreadsheets/u/0/), find the new sheet `foo` and look at it. You should see some iris data in the first (and only) worksheet. We'll also take a look at it here, by consuming `foo` via the list feed.
+Go to [your Google Sheets home screen](https://docs.google.com/spreadsheets/u/0/), find the new sheet `foo` and look at it. You should see some iris data in the first (and only) worksheet. We'll also take a look at it here, by reading the data from `foo`.
 
 Note how we always store the returned value from `gs_edit_cells()` (and all other sheet editing functions). That's because the registration info changes whenever we edit the sheet and we re-register it inside these functions, so this idiom will help you make sequential edits and queries to the same sheet.
 
@@ -488,13 +660,15 @@ If you'd rather specify sheets for deletion by title, look at `gs_grepdel()` and
 Here's how we can create a new spreadsheet from a suitable local file. First, we'll write then upload a comma-delimited excerpt from the iris data.
 
 ``` r
-iris %>% head(5) %>% write.csv("iris.csv", row.names = FALSE)
+iris %>%
+  head(5) %>%
+  write.csv("iris.csv", row.names = FALSE)
 iris_ss <- gs_upload("iris.csv")
 #> "iris.csv" uploaded to Google Drive and converted to a Google Sheet named "iris"
 iris_ss
 #>                   Spreadsheet title: iris
-#>   Date of googlesheets registration: 2015-05-31 22:08:08 GMT
-#>     Date of last spreadsheet update: 2015-05-31 22:08:06 GMT
+#>   Date of googlesheets registration: 2015-06-01 04:35:45 GMT
+#>     Date of last spreadsheet update: 2015-06-01 04:35:43 GMT
 #>                          visibility: private
 #>                         permissions: rw
 #>                             version: new
@@ -503,12 +677,12 @@ iris_ss
 #> (Title): (Nominal worksheet extent as rows x columns)
 #> iris: 6 x 5
 #> 
-#> Key: 1JBhoVYK4CjvHvwrWl4N0nr03i6CQ7BBiTUHS6A31u50
-iris_ss %>% gs_read_listfeed()
+#> Key: 198OhurSmABzQnAR2zWjZupYdqAIqr7CvDpiw9HttjSw
+iris_ss %>% gs_read()
 #> Accessing worksheet titled "iris"
 #> Source: local data frame [5 x 5]
 #> 
-#>   sepal.length sepal.width petal.length petal.width species
+#>   Sepal.Length Sepal.Width Petal.Length Petal.Width Species
 #> 1          5.1         3.5          1.4         0.2  setosa
 #> 2          4.9         3.0          1.4         0.2  setosa
 #> 3          4.7         3.2          1.3         0.2  setosa
@@ -525,8 +699,8 @@ gap_xlsx <- gs_upload(system.file("mini-gap.xlsx", package = "googlesheets"))
 #> "mini-gap.xlsx" uploaded to Google Drive and converted to a Google Sheet named "mini-gap"
 gap_xlsx
 #>                   Spreadsheet title: mini-gap
-#>   Date of googlesheets registration: 2015-05-31 22:08:13 GMT
-#>     Date of last spreadsheet update: 2015-05-31 22:08:11 GMT
+#>   Date of googlesheets registration: 2015-06-01 04:35:50 GMT
+#>     Date of last spreadsheet update: 2015-06-01 04:35:48 GMT
 #>                          visibility: private
 #>                         permissions: rw
 #>                             version: new
@@ -539,26 +713,33 @@ gap_xlsx
 #> Europe: 1000 x 26
 #> Oceania: 1000 x 26
 #> 
-#> Key: 1AJakJVpaoEkuuwaO4Tm3IJbGAMxaHOr2U5r3jH2uyrY
-gap_xlsx %>% gs_read_listfeed(ws = "Oceania")
-#> Accessing worksheet titled "Oceania"
+#> Key: 1UlSWXv3sK5XtE5W_4KYOx-3Se7vZU3aWyKRZXdUCsY8
+gap_xlsx %>% gs_read(ws = "Asia")
+#> Accessing worksheet titled "Asia"
 #> Source: local data frame [5 x 6]
 #> 
-#>       country continent year lifeexp      pop gdppercap
-#> 1   Australia   Oceania 1952   69.12  8691212  10039.60
-#> 2 New Zealand   Oceania 1952   69.39  1994794  10556.58
-#> 3   Australia   Oceania 1957   70.33  9712569  10949.65
-#> 4 New Zealand   Oceania 1957   70.26  2229407  12247.40
-#> 5   Australia   Oceania 1962   70.93 10794968  12217.23
+#>       country continent year lifeExp       pop gdpPercap
+#> 1 Afghanistan      Asia 1952  28.801   8425333  779.4453
+#> 2     Bahrain      Asia 1952  50.939    120447 9867.0848
+#> 3  Bangladesh      Asia 1952  37.484  46886859  684.2442
+#> 4    Cambodia      Asia 1952  39.417   4693836  368.4693
+#> 5       China      Asia 1952  44.000 556263527  400.4486
 ```
 
 And we clean up after ourselves on Google Drive.
 
 ``` r
-gs_delete(iris_ss)
-#> Success. "iris" moved to trash in Google Drive.
-gs_delete(gap_xlsx)
+gs_vecdel(c("iris", "mini-gap"))
+#> Authentication will be used.
+#> Sheet successfully identifed: "mini-gap"
 #> Success. "mini-gap" moved to trash in Google Drive.
+#> Authentication will be used.
+#> Sheet successfully identifed: "iris"
+#> Success. "iris" moved to trash in Google Drive.
+#> [1] TRUE TRUE
+## achieves same as:
+## gs_delete(iris_ss)
+## gs_delete(gap_xlsx)
 ```
 
 ### Download sheets as csv, pdf, or xlsx file
@@ -617,8 +798,8 @@ The function `gs_user()` will print and return some information about the curren
 user_session_info <- gs_user()
 #>                        displayName: google sheets
 #>                       emailAddress: gspreadr@gmail.com
-#> Date-time of session authorization: 2015-05-31 22:07:45
-#>   Date-time of access token expiry: 2015-05-31 15:11:01
+#> Date-time of session authorization: 2015-06-01 04:35:07
+#>   Date-time of access token expiry: 2015-05-31 22:35:09
 #> Access token is valid.
 user_session_info
 #> $displayName
@@ -628,14 +809,14 @@ user_session_info
 #> [1] "gspreadr@gmail.com"
 #> 
 #> $auth_date
-#> [1] "2015-05-31 22:07:45 GMT"
+#> [1] "2015-06-01 04:35:07 GMT"
 #> 
 #> $exp_date
-#> [1] "2015-05-31 15:11:01 PDT"
+#> [1] "2015-05-31 22:35:09 PDT"
 ```
 
 ### "Old" Google Sheets
 
 In March 2014 [Google introduced "new" Sheets](https://support.google.com/docs/answer/3541068?hl=en). "New" Sheets and "old" sheets behave quite differently with respect to access via API and present a big headache for us. Recently, we've noted that Google is forcibly converting sheets: [all "old" Sheets will be switched over the "new" sheets during 2015](https://support.google.com/docs/answer/6082736?p=new_sheets_migrate&rd=1). However there are still "old" sheets lying around, so we've made some effort to support them, when it's easy to do so. But keep your expectations low.
 
-In particular, `gs_read_csv()` does not and indeed **cannot** work for "old" sheets.
+In particular, `gs_read_csv()` does not currently work for "old" sheets.

From 5c6b9ebfe64422da2485e044d193b2b4a0a83af8 Mon Sep 17 00:00:00 2001
From: jennybc 
Date: Sun, 31 May 2015 23:52:07 -0700
Subject: [PATCH 7/7] docs [covr]

---
 R/gs_cell-specification.R         | 10 ++++++++++
 R/gs_read.R                       |  2 +-
 R/gs_read_cellfeed.R              |  2 +-
 R/gs_simplify_cellfeed.R          |  6 ++++--
 man-roxygen/cellranger.R          | 11 +++++++++--
 man-roxygen/range.R               |  1 +
 man/cell-specification.Rd         | 21 +++++++++++++++++++--
 man/gs_read.Rd                    |  2 +-
 man/gs_read_cellfeed.Rd           |  2 +-
 man/gs_simplify_cellfeed.Rd       |  6 ++++--
 tests/testthat/test-gs-register.R | 16 ++++++++++------
 11 files changed, 61 insertions(+), 18 deletions(-)
 create mode 100644 man-roxygen/range.R

diff --git a/R/gs_cell-specification.R b/R/gs_cell-specification.R
index b53fa99..465b714 100644
--- a/R/gs_cell-specification.R
+++ b/R/gs_cell-specification.R
@@ -20,6 +20,16 @@
 #' package, where you can find full documentation for the functions used in the
 #' examples below.
 #'
+#' @examples
+#' \dontrun{
+#' gs_gap() %>% gs_read(ws = 2, range = "A1:D8")
+#' gs_gap() %>% gs_read(ws = "Europe", range = cell_rows(1:4))
+#' gs_gap() %>% gs_read(ws = "Europe", range = cell_rows(100:103),
+#'                      col_names = FALSE)
+#' gs_gap() %>% gs_read(ws = "Africa", range = cell_cols(1:4))
+#' gs_gap() %>% gs_read(ws = "Asia", range = cell_limits(c(1, 5), c(4, NA)))
+#' }
+#'
 #' @template cellranger
 #' @name cell-specification
 NULL
diff --git a/R/gs_read.R b/R/gs_read.R
index 5190568..1ed41a6 100644
--- a/R/gs_read.R
+++ b/R/gs_read.R
@@ -19,7 +19,7 @@
 #'
 #' @template ss
 #' @template ws
-#' @param range blah blah
+#' @template range
 #' @param ... optional arguments passed on to functions that control reading and
 #'   transforming the data
 #' @template verbose
diff --git a/R/gs_read_cellfeed.R b/R/gs_read_cellfeed.R
index 387e4ef..1c05a26 100644
--- a/R/gs_read_cellfeed.R
+++ b/R/gs_read_cellfeed.R
@@ -23,7 +23,7 @@
 #'
 #' @template ss
 #' @template ws
-#' @param range blah blah
+#' @template range
 #' @param return_empty logical; indicates whether to return empty cells
 #' @param return_links logical; indicates whether to return the edit and self
 #'   links (used internally in cell editing workflow)
diff --git a/R/gs_simplify_cellfeed.R b/R/gs_simplify_cellfeed.R
index c8385b8..edd6753 100644
--- a/R/gs_simplify_cellfeed.R
+++ b/R/gs_simplify_cellfeed.R
@@ -24,9 +24,11 @@
 #' @param notation character; the result vector can have names that reflect
 #'   which cell the data came from; this argument selects between the "A1" and
 #'   "R1C1" positioning notations; specify "none" to suppress names
-#' @param col_names blah blah
+#' @param col_names if \code{TRUE}, the first row of the input will be
+#'   interpreted as a column name and NOT included in the result; useful when
+#'   reading a single column or variable
 #'
-#' @return a named vector
+#' @return a vector
 #'
 #' @examples
 #' \dontrun{
diff --git a/man-roxygen/cellranger.R b/man-roxygen/cellranger.R
index 7643931..90cc6ff 100644
--- a/man-roxygen/cellranger.R
+++ b/man-roxygen/cellranger.R
@@ -1,4 +1,11 @@
 #' @seealso The \code{\link[=cellranger]{cellranger}} package has full
 #'   documentation on cell specification and offers additional functions for
-#'   manipulating "A1:D10" style spreadsheet ranges. See a full list of
-#'   functions in the \link[cellranger:00Index]{cellranger index}.
+#'   manipulating "A1:D10" style spreadsheet ranges. Here are the most relevant:
+#'   \itemize{
+#'     \item \code{\link[cellranger]{cell_limits}}
+#'     \item \code{\link[cellranger]{cell_rows}}
+#'     \item \code{\link[cellranger]{cell_cols}}
+#'     \item \code{\link[cellranger]{anchored}}
+#'   }
+#'   See a full list of functions in the \link[cellranger:00Index]{cellranger
+#'   index}.
diff --git a/man-roxygen/range.R b/man-roxygen/range.R
new file mode 100644
index 0000000..e9d0922
--- /dev/null
+++ b/man-roxygen/range.R
@@ -0,0 +1 @@
+#' @param range a cell range, as described in \code{\link{cell-specification}}
diff --git a/man/cell-specification.Rd b/man/cell-specification.Rd
index 1392fb3..1a88b11 100644
--- a/man/cell-specification.Rd
+++ b/man/cell-specification.Rd
@@ -18,10 +18,27 @@ cell range processing is handled by the \code{\link[=cellranger]{cellranger}}
 package, where you can find full documentation for the functions used in the
 examples below.
 }
+\examples{
+\dontrun{
+gs_gap() \%>\% gs_read(ws = 2, range = "A1:D8")
+gs_gap() \%>\% gs_read(ws = "Europe", range = cell_rows(1:4))
+gs_gap() \%>\% gs_read(ws = "Europe", range = cell_rows(100:103),
+                     col_names = FALSE)
+gs_gap() \%>\% gs_read(ws = "Africa", range = cell_cols(1:4))
+gs_gap() \%>\% gs_read(ws = "Asia", range = cell_limits(c(1, 5), c(4, NA)))
+}
+}
 \seealso{
 The \code{\link[=cellranger]{cellranger}} package has full
   documentation on cell specification and offers additional functions for
-  manipulating "A1:D10" style spreadsheet ranges. See a full list of
-  functions in the \link[cellranger:00Index]{cellranger index}.
+  manipulating "A1:D10" style spreadsheet ranges. Here are the most relevant:
+  \itemize{
+    \item \code{\link[cellranger]{cell_limits}}
+    \item \code{\link[cellranger]{cell_rows}}
+    \item \code{\link[cellranger]{cell_cols}}
+    \item \code{\link[cellranger]{anchored}}
+  }
+  See a full list of functions in the \link[cellranger:00Index]{cellranger
+  index}.
 }
 
diff --git a/man/gs_read.Rd b/man/gs_read.Rd
index 6db2646..8abe545 100644
--- a/man/gs_read.Rd
+++ b/man/gs_read.Rd
@@ -13,7 +13,7 @@ object}
 \item{ws}{positive integer or character string specifying index or title,
 respectively, of the worksheet}
 
-\item{range}{blah blah}
+\item{range}{a cell range, as described in \code{\link{cell-specification}}}
 
 \item{...}{optional arguments passed on to functions that control reading and
 transforming the data}
diff --git a/man/gs_read_cellfeed.Rd b/man/gs_read_cellfeed.Rd
index 829158a..8ae3684 100644
--- a/man/gs_read_cellfeed.Rd
+++ b/man/gs_read_cellfeed.Rd
@@ -14,7 +14,7 @@ object}
 \item{ws}{positive integer or character string specifying index or title,
 respectively, of the worksheet}
 
-\item{range}{blah blah}
+\item{range}{a cell range, as described in \code{\link{cell-specification}}}
 
 \item{return_empty}{logical; indicates whether to return empty cells}
 
diff --git a/man/gs_simplify_cellfeed.Rd b/man/gs_simplify_cellfeed.Rd
index d23b6a0..d4fb832 100644
--- a/man/gs_simplify_cellfeed.Rd
+++ b/man/gs_simplify_cellfeed.Rd
@@ -25,10 +25,12 @@ as \code{NA} values}
 which cell the data came from; this argument selects between the "A1" and
 "R1C1" positioning notations; specify "none" to suppress names}
 
-\item{col_names}{blah blah}
+\item{col_names}{if \code{TRUE}, the first row of the input will be
+  interpreted as a column name and NOT included in the result; useful when
+  reading a single column or variable}
 }
 \value{
-a named vector
+a vector
 }
 \description{
 In some cases, you do not want to convert the data retrieved from the cell
diff --git a/tests/testthat/test-gs-register.R b/tests/testthat/test-gs-register.R
index dca251b..7dc0b60 100644
--- a/tests/testthat/test-gs-register.R
+++ b/tests/testthat/test-gs-register.R
@@ -1,12 +1,16 @@
 context("register sheets")
 
-iris_pvt_url %>%
-  gs_url(verbose = FALSE) %>%
-  saveRDS("for_reference/iris_pvt_googlesheet.rds")
+if(!file.exists("for_reference/iris_pvt_googlesheet.rds")) {
+  iris_pvt_url %>%
+    gs_url(verbose = FALSE) %>%
+    saveRDS("for_reference/iris_pvt_googlesheet.rds")
+}
 
-GAP_KEY %>%
-  gs_key(verbose = FALSE) %>%
-  saveRDS("for_reference/gap_googlesheet.rds")
+if(!file.exists("for_reference/gap_googlesheet.rds")) {
+  GAP_KEY %>%
+    gs_key(verbose = FALSE) %>%
+    saveRDS("for_reference/gap_googlesheet.rds")
+}
 
 pseudo_expect_equal_to_reference <- function(x, ref) {
   ref_rds <- file.path("for_reference", paste0(ref, "_googlesheet.rds"))