Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add sheets_create() #61

Merged
merged 28 commits into from
Nov 22, 2019
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
f697470
Add sheets_create()
jennybc Nov 15, 2019
8028dfb
GridRange has been implemented
jennybc Nov 15, 2019
4cf25b4
More minimalist S3 treatment of the schemas
jennybc Nov 16, 2019
7ee9e43
Mostly I need to turn some of these into tibbles
jennybc Nov 19, 2019
05b1f2c
Import transpose
jennybc Nov 19, 2019
1104171
It's ugly but it works
jennybc Nov 19, 2019
7aa6856
Re-download discovery document
jennybc Nov 21, 2019
4121f39
Make some "tidy" schemas available as internal data
jennybc Nov 21, 2019
ebf1c86
Import imap
jennybc Nov 21, 2019
3f76282
Reboot the schema <--> S3 strategy
jennybc Nov 21, 2019
8f8d8a4
Revise tibblify
jennybc Nov 21, 2019
0f53f80
Export this method
jennybc Nov 21, 2019
e390229
Comment re: explicit list class
jennybc Nov 21, 2019
e19ae1d
Shorten name
jennybc Nov 21, 2019
da74ca8
Snapshot tidy schemas so future diffs are informative
jennybc Nov 21, 2019
1102f48
jsondiff says: "The two files were semantically identical."
jennybc Nov 21, 2019
e57d854
Re-ingest discovery doc
jennybc Nov 21, 2019
073110e
Forgot to rename this in the tests too
jennybc Nov 21, 2019
e07e569
Dear god is the order random?!?
jennybc Nov 21, 2019
a5da356
Add test
jennybc Nov 21, 2019
d2534b7
wip
jennybc Nov 21, 2019
2a53d02
Store schema id as an attribute ("who am I?")
jennybc Nov 21, 2019
03b628a
Refactor patch(); schemas know their own id now
jennybc Nov 22, 2019
ded50e4
Describe status of sheets_create()
jennybc Nov 22, 2019
7677711
Send column names
jennybc Nov 22, 2019
3439414
Teach as_sheets_id() about sheets_Spreadsheet
jennybc Nov 22, 2019
0c5d929
Another small refactor for patch()
jennybc Nov 22, 2019
fbf2375
Add NEWS bullet
jennybc Nov 22, 2019
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions NAMESPACE
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,8 @@ S3method(ctype,SHEETS_CELL)
S3method(ctype,character)
S3method(ctype,default)
S3method(ctype,list)
S3method(format,sheets_meta)
S3method(print,sheets_meta)
S3method(format,sheets_Spreadsheet)
S3method(print,sheets_Spreadsheet)
jennybc marked this conversation as resolved.
Show resolved Hide resolved
export("%>%")
export(anchored)
export(as_sheets_id)
Expand All @@ -27,6 +27,7 @@ export(sheets_auth)
export(sheets_auth_configure)
export(sheets_browse)
export(sheets_cells)
export(sheets_create)
export(sheets_deauth)
export(sheets_endpoints)
export(sheets_example)
Expand Down
40 changes: 40 additions & 0 deletions R/schema_GridRange.R
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
# https://developers.google.com/sheets/api/reference/rest/v4/spreadsheets/other#GridRange
Copy link
Member Author

@jennybc jennybc Nov 15, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There are ~200 so-called schemas in the Sheets API. GridRange is an example.

Once you start writing and updating Sheets, you need to create a lot of bodies, in the REST sense, which was not true while we were only reading. And these bodies must be built up of (often nested) schemas. I have to create some sort of machinery around this.

What I do in this PR would not scale nicely to the full set of schemas, but is that necessary? I don't know. I definitely feel a bit like a schema-and-S3-creating robot, though.

But if I did something more automated re: class creation and validation, then it scales and, in fact, that would belong in gargle, for reuse across multiple APIs.

I don't know what to do.

new_GridRange <- function(sheetId,
startRowIndex,
endRowIndex,
startColumnIndex,
endColumnIndex) {

x <- list(
sheetId = sheetId,
startRowIndex = startRowIndex,
endRowIndex = endRowIndex,
startColumnIndex = startColumnIndex,
endColumnIndex = endColumnIndex
)
structure(x, class = "GridRange")
}

validate_GridRange <- function(x) {
check_non_negative_integer(x$sheetId)
check_non_negative_integer(x$startRowIndex)
check_non_negative_integer(x$endRowIndex)
check_non_negative_integer(x$startColumnIndex)
check_non_negative_integer(x$endColumnIndex)
x
}

GridRange <- function(sheetId,
startRowIndex,
endRowIndex,
startColumnIndex,
endColumnIndex) {
x <- new_GridRange(
sheetId = sheetId,
startRowIndex = startRowIndex,
endRowIndex = endRowIndex,
startColumnIndex = startColumnIndex,
endColumnIndex = endColumnIndex
)
validate_GridRange(x)
}
30 changes: 30 additions & 0 deletions R/schema_NamedRange.R
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
# https://developers.google.com/sheets/api/reference/rest/v4/spreadsheets#NamedRange
new_NamedRange <- function(namedRangeId,
name,
range) {
x <- list(
namedRangeId = namedRangeId,
name = name,
range = range
)
structure(
validate_NamedRange(x),
class = "NamedRange"
)
}

validate_NamedRange <- function(x) {
# I think read-only vs. required vs. optional status of these elements
# depends on what you're trying to do
maybe_string(x$namedRangeId, "namedRangeId")
maybe_string(x$name, "name")

validate_GridRange(x$range)

x
}

NamedRange <- function(...) {
x <- new_NamedRange(...)
compact(x)
}
jennybc marked this conversation as resolved.
Show resolved Hide resolved
27 changes: 27 additions & 0 deletions R/schema_Sheet.R
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
# https://developers.google.com/sheets/api/reference/rest/v4/spreadsheets/sheets#Sheet
new_Sheet <- function(properties = NULL,
data = NULL) {
# a Sheet object has MANY more elements, so I'm just starting with the ones
# I plan to use soon
x <- list(
properties = properties,
data = data
)
structure(validate_Sheet(x), class = "Sheet")
}

validate_Sheet <- function(x) {
if (!is.null(x$properties)) {
validate_SheetProperties(x)
}

# data is an instance of GridData
jennybc marked this conversation as resolved.
Show resolved Hide resolved

x
}

Sheet <- function(...) {
x <- new_Sheet(...)
compact(x)
}

43 changes: 43 additions & 0 deletions R/schema_SheetProperties.R
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
# https://developers.google.com/sheets/api/reference/rest/v4/spreadsheets/sheets#SheetProperties
new_SheetProperties <- function(sheetId = NULL,
title = NULL,
index = NULL,
sheetType = NULL,
gridProperties = NULL,
hidden = NULL,
tabColor = NULL,
rightToLeft = NULL) {
x <- list(
sheetId = sheetId,
title = title,
index = index,
sheetType = sheetType,
gridProperties = gridProperties,
hidden = hidden,
tabColor = tabColor,
rightToLeft = rightToLeft
)
structure(validate_SheetProperties(x), class = "SheetProperties")
}

validate_SheetProperties <- function(x) {
maybe_non_negative_integer(x$sheetId, "sheetId")
maybe_string(x$title, "title")
maybe_non_negative_integer(x$index, "index")
maybe_string(x$sheetType, "sheetType") # enum
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In addition to ~200 schemas there are lots of enums. They aren't particularly easy to pull out of the discovery document in a coherent way. But you could imagine doing so, in a fit of recursive fury, and create a way to declare something a enum, check supplied values against the valid ones, etc.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Auto-generating an internal data structure and code around the enums sounds much easier to me (maybe even a clear "yes, do this"?) than doing same for the schemas.


# gridProperties is an instance of GridProperties

maybe_bool(x$hidden, "hidden")

# tabColor is an instance of Color

maybe_bool(x$rightToLeft, "rightToLeft")

x
}

SheetProperties <- function(...) {
x <- new_SheetProperties(...)
compact(x)
}
164 changes: 164 additions & 0 deletions R/schema_Spreadsheet.R
Original file line number Diff line number Diff line change
@@ -0,0 +1,164 @@
# https://developers.google.com/sheets/api/reference/rest/v4/spreadsheets#Spreadsheet
new_Spreadsheet <- function(spreadsheetId = NULL,
properties = NULL,
sheets = NULL,
namedRanges = NULL,
spreadsheetUrl = NULL,
developerMetadata = NULL) {
x <- list(
spreadsheetId = spreadsheetId,
properties = properties,
sheets = sheets,
namedRanges = namedRanges,
spreadsheetUrl = spreadsheetUrl,
developerMetadata = developerMetadata
)
structure(
validate_Spreadsheet(x),
class = "Spreadsheet"
)
}

validate_Spreadsheet <- function(x) {
maybe_string(x$spreadsheetId, "spreadsheetId")

if (!is.null(x$properties)) {
validate_SpreadsheetProperties(x$properties)
}

if (!is.null(x$sheets)) {
walk(x$sheets, validate_Sheet)
}

if (!is.null(x$namedRanges)) {
walk(x$namedRanges, validate_NamedRange)
}

maybe_string(x$spreadsheetUrl, "spreadsheetUrl")

# developerMetadata is an instance of DeveloperMetadata

x
}

Spreadsheet <- function(...) {
x <- new_Spreadsheet(...)
compact(x)
}

# input: instance of Spreadsheet, in the Sheets API sense, as a named list
# output: instance of sheets_Spreadsheet, which is how I want to hold this info
sheets_Spreadsheet <- function(x = list()) {
jennybc marked this conversation as resolved.
Show resolved Hide resolved
ours_theirs <- list(
spreadsheet_id = "spreadsheetId",
spreadsheet_url = "spreadsheetUrl",
name = list("properties", "title"),
locale = list("properties", "locale"),
time_zone = list("properties", "timeZone")
)
out <- map(ours_theirs, ~ pluck(x, !!!.x))

if (!is.null(x$sheets)) {
# TODO: refactor in terms of a to-be-created sheets_Sheet()? changes the
# angle of attack to Sheet-wise, whereas here I work property-wise
p <- map(x$sheets, "properties")
out$sheets <- tibble::tibble(
# TODO: open question whether I should explicitly unescape here
name = map_chr(p, "title"),
index = map_int(p, "index"),
id = map_chr(p, "sheetId"),
type = map_chr(p, "sheetType"),
visible = !map_lgl(p, "hidden", .default = FALSE),
# TODO: refactor in terms of methods created around GridData?
grid_rows = map_int(p, c("gridProperties", "rowCount"), .default = NA),
grid_columns = map_int(p, c("gridProperties", "columnCount"), .default = NA)
)
}

if (!is.null(x$namedRanges)) {
# TODO: refactor in terms of a to-be-created sheets_NamedRange()? changes
# the angle of attack to NamedRange-wise, whereas here I work column-wise
nr <- x$namedRanges
out$named_ranges <- tibble::tibble(
name = map_chr(nr, "name"),
range = NA_character_,
id = map_chr(nr, "namedRangeId"),
# if there is only 1 sheet, sheetId might not be sent!
# https://github.com/tidyverse/googlesheets4/issues/29
sheet_id = map_chr(nr, c("range", "sheetId"), .default = NA),
sheet_name = NA_character_,
# TODO: extract into functions re: GridRange?
## API sends zero-based row and column
## => we add one
## API indices are half-open, i.e. [start, end)
## => we substract one from end_[row|column]
## net effect
## => we add one to start_[row|column] but not to end_[row|column]
start_row = map_int(nr, c("range", "startRowIndex"), .default = NA) + 1L,
end_row = map_int(nr, c("range", "endRowIndex"), .default = NA),
start_column = map_int(nr, c("range", "startColumnIndex"), .default = NA) + 1L,
end_column = map_int(nr, c("range", "endColumnIndex"), .default = NA)
)
no_sheet <- is.na(out$named_ranges$sheet_id)
if (any(no_sheet)) {
# if no associated sheetId, assume it's the first (only?) sheet
# https://github.com/tidyverse/googlesheets4/issues/29
out$named_ranges$sheet_id[no_sheet] <- out$sheets$id[[1]]
}
out$named_ranges$sheet_name <- vlookup(
out$named_ranges$sheet_id,
data = out$sheets,
key = "id",
value = "name"
)
out$named_ranges$range <- pmap_chr(out$named_ranges, make_range)
}

structure(out, class = c("sheets_Spreadsheet", "list"))
}

#' @export
format.sheets_Spreadsheet <- function(x, ...) {

meta <- glue_data(
x,
"
Spreadsheet name: {name}
ID: {spreadsheet_id}
Locale: {locale}
Time zone: {time_zone}
# of sheets: {nrow(x$sheets)}
",
.sep = "\n"
)
meta <- strsplit(meta, split = "\n")[[1]]

col1 <- fr(c("(Sheet name)", x$sheets$name))
col2 <- c(
"(Nominal extent in rows x columns)",
glue_data(x$sheets, "{grid_rows} x {grid_columns}")
)
meta <- c(
meta,
"",
glue_data(list(col1 = col1, col2 = col2), "{col1}: {col2}")
)

if (!is.null(x$named_ranges)) {
col1 <- fr(c("(Named range)", x$named_ranges$name))
col2 <- fl(c("(A1 range)", x$named_ranges$range))
meta <- c(
meta,
"",
glue_data(list(col1 = col1, col2 = col2), "{col1}: {col2}")
)
}

meta
}

#' @export
print.sheets_Spreadsheet <- function(x, ...) {
cat(format(x), sep = "\n")
invisible(x)
}
38 changes: 38 additions & 0 deletions R/schema_SpreadsheetProperties.R
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
# https://developers.google.com/sheets/api/reference/rest/v4/spreadsheets#SpreadsheetProperties
new_SpreadsheetProperties <- function(title,
locale = NULL,
autoRecalc = NULL,
timeZone = NULL,
defaultFormat = NULL,
iterativeCalculationSettings = NULL) {
x <- list(
title = title,
locale = locale,
autoRecalc = autoRecalc,
timeZone = timeZone,
defaultFormat = defaultFormat,
iterativeCalculationSettings = iterativeCalculationSettings
)
structure(
validate_SpreadsheetProperties(x),
class = "SpreadsheetProperties"
)
}

validate_SpreadsheetProperties <- function(x) {
check_string(x$title, "title")

maybe_string(x$locale, "locale")
maybe_string(x$locale, "autoRecalc") # enum
maybe_string(x$timeZone, "timeZone")

# defaultFormat is an instance of CellFormat
# iterativeCalculationSettings is an instance of IterativeCalculationSettings

x
}

SpreadsheetProperties <- function(title, ...) {
x <- new_SpreadsheetProperties(title = title, ...)
compact(x)
}
Loading