Full Code of tidyverse/googledrive for AI

main 16321035e1ed cached
280 files
2.4 MB
635.7k tokens
1 requests
Download .txt
Showing preview only (2,541K chars total). Download the full file or copy to clipboard to get everything.
Repository: tidyverse/googledrive
Branch: main
Commit: 16321035e1ed
Files: 280
Total size: 2.4 MB

Directory structure:
gitextract_wxrorbn_/

├── .Rbuildignore
├── .covrignore
├── .github/
│   ├── .gitignore
│   ├── CODEOWNERS
│   ├── CODE_OF_CONDUCT.md
│   ├── CONTRIBUTING.Rmd
│   ├── CONTRIBUTING.md
│   ├── ISSUE_TEMPLATE/
│   │   └── issue_template.md
│   ├── SUPPORT.md
│   └── workflows/
│       ├── R-CMD-check.yaml
│       ├── format-suggest.yaml
│       ├── pkgdown.yaml
│       ├── pr-commands.yaml
│       ├── test-coverage.yaml
│       └── with-auth.yml
├── .gitignore
├── .vscode/
│   ├── extensions.json
│   └── settings.json
├── DESCRIPTION
├── LICENSE
├── LICENSE.md
├── NAMESPACE
├── NEWS.md
├── R/
│   ├── aaa.R
│   ├── camelCase.R
│   ├── compat-dplyr.R
│   ├── compat-vctrs.R
│   ├── deprecated.R
│   ├── dribble.R
│   ├── drive_about.R
│   ├── drive_auth.R
│   ├── drive_browse.R
│   ├── drive_cp.R
│   ├── drive_create.R
│   ├── drive_download.R
│   ├── drive_endpoints.R
│   ├── drive_examples.R
│   ├── drive_fields.R
│   ├── drive_find.R
│   ├── drive_get.R
│   ├── drive_get_path.R
│   ├── drive_id-class.R
│   ├── drive_ls.R
│   ├── drive_mime_type.R
│   ├── drive_mkdir.R
│   ├── drive_mv.R
│   ├── drive_publish.R
│   ├── drive_put.R
│   ├── drive_read.R
│   ├── drive_rename.R
│   ├── drive_reveal.R
│   ├── drive_rm.R
│   ├── drive_share.R
│   ├── drive_trash.R
│   ├── drive_update.R
│   ├── drive_upload.R
│   ├── drive_user.R
│   ├── googledrive-package.R
│   ├── promote.R
│   ├── request_generate.R
│   ├── request_make.R
│   ├── roxygen-templates.R
│   ├── shared_drive_create.R
│   ├── shared_drive_find.R
│   ├── shared_drive_get.R
│   ├── shared_drive_rm.R
│   ├── shared_drive_update.R
│   ├── shared_drives.R
│   ├── shortcut.R
│   ├── sysdata.rda
│   ├── team_drive.R
│   ├── utils-io.R
│   ├── utils-paths.R
│   ├── utils-pipe.R
│   ├── utils-ui.R
│   ├── utils.R
│   └── zzz.R
├── README.Rmd
├── README.md
├── _pkgdown.yml
├── air.toml
├── codecov.yml
├── cran-comments.md
├── data-raw/
│   ├── discovery-doc-ingest.R
│   ├── drive-examples-create.R
│   ├── drive-examples-inventory.R
│   ├── drive-v3_2025-08-29.json
│   ├── export-mime-type-defaults.csv
│   ├── extension-mime-type-defaults.csv
│   ├── file-fields.R
│   ├── mime-types-and-file-extensions.R
│   ├── mime-types-google.R
│   └── old/
│       ├── 20170519_drive-v3_discovery-document.json
│       ├── 20170519_drive-v3_endpoints-list.json
│       ├── 20170519_drive-v3_endpoints-list.rds
│       ├── 20170519_drive-v3_endpoints-tibble.rds
│       ├── 20170721_drive-v3_discovery-document.json
│       ├── 20170721_drive-v3_endpoints-list.json
│       ├── 20170721_drive-v3_endpoints-list.rds
│       ├── 20170721_drive-v3_endpoints-tibble.rds
│       ├── 20171110_drive-v3_discovery-document.json
│       ├── 20171110_drive-v3_endpoints-list.json
│       ├── 20171110_drive-v3_endpoints-list.rds
│       ├── 20171110_drive-v3_endpoints-tibble.rds
│       ├── discovery-doc-prep.R
│       ├── drive-v3_2019-02-14.json
│       ├── drive-v3_2019-07-08.json
│       ├── drive-v3_2020-04-17.json
│       ├── drive-v3_2021-03-22.json
│       └── drive-v3_2021-06-21.json
├── googledrive.Rproj
├── index.Rmd
├── index.md
├── inst/
│   ├── WORDLIST
│   ├── extdata/
│   │   ├── data/
│   │   │   ├── client_secret_123.googleusercontent.com.json
│   │   │   ├── files_fields.csv
│   │   │   ├── mime_tbl.csv
│   │   │   ├── remote_example_files.csv
│   │   │   └── translate_mime_types.csv
│   │   └── example_files/
│   │       ├── chicken.csv
│   │       ├── chicken.txt
│   │       ├── imdb_latin1.csv
│   │       ├── markdown.md
│   │       └── r_about.html
│   └── secret/
│       ├── googledrive-docs.json
│       └── googledrive-testing.json
├── man/
│   ├── as_dribble.Rd
│   ├── as_shared_drive.Rd
│   ├── deprecated-team-drive-functions.Rd
│   ├── dribble-checks.Rd
│   ├── dribble.Rd
│   ├── drive_about.Rd
│   ├── drive_auth.Rd
│   ├── drive_auth_configure.Rd
│   ├── drive_browse.Rd
│   ├── drive_cp.Rd
│   ├── drive_create.Rd
│   ├── drive_deauth.Rd
│   ├── drive_download.Rd
│   ├── drive_empty_trash.Rd
│   ├── drive_endpoints.Rd
│   ├── drive_examples.Rd
│   ├── drive_extension.Rd
│   ├── drive_fields.Rd
│   ├── drive_find.Rd
│   ├── drive_get.Rd
│   ├── drive_has_token.Rd
│   ├── drive_id.Rd
│   ├── drive_link.Rd
│   ├── drive_ls.Rd
│   ├── drive_mime_type.Rd
│   ├── drive_mkdir.Rd
│   ├── drive_mv.Rd
│   ├── drive_publish.Rd
│   ├── drive_put.Rd
│   ├── drive_read_string.Rd
│   ├── drive_rename.Rd
│   ├── drive_reveal.Rd
│   ├── drive_rm.Rd
│   ├── drive_scopes.Rd
│   ├── drive_share.Rd
│   ├── drive_token.Rd
│   ├── drive_trash.Rd
│   ├── drive_update.Rd
│   ├── drive_upload.Rd
│   ├── drive_user.Rd
│   ├── expose.Rd
│   ├── googledrive-configuration.Rd
│   ├── googledrive-deprecated.Rd
│   ├── googledrive-package.Rd
│   ├── pipe.Rd
│   ├── request_generate.Rd
│   ├── request_make.Rd
│   ├── shared_drive_create.Rd
│   ├── shared_drive_find.Rd
│   ├── shared_drive_get.Rd
│   ├── shared_drive_rm.Rd
│   ├── shared_drive_update.Rd
│   ├── shared_drives.Rd
│   ├── shortcut_create.Rd
│   └── shortcut_resolve.Rd
├── man-roxygen/
│   ├── corpus.R
│   ├── dots-metadata.R
│   ├── file-plural.R
│   ├── file-singular.R
│   ├── media.R
│   ├── n_max.R
│   ├── overwrite.R
│   ├── pattern.R
│   ├── shared-drive-description.R
│   ├── shared_drive-plural.R
│   ├── shared_drive-singular.R
│   ├── team-drives-description.R
│   ├── team_drive-plural.R
│   ├── team_drive-singular.R
│   └── verbose.R
├── revdep/
│   ├── .gitignore
│   ├── README.md
│   ├── cran.md
│   ├── failures.md
│   └── problems.md
├── tests/
│   ├── spelling.R
│   ├── testthat/
│   │   ├── .gitignore
│   │   ├── _snaps/
│   │   │   ├── deprecated.md
│   │   │   ├── dribble.md
│   │   │   ├── drive_auth.md
│   │   │   ├── drive_cp.md
│   │   │   ├── drive_create.md
│   │   │   ├── drive_download.md
│   │   │   ├── drive_examples.md
│   │   │   ├── drive_fields.md
│   │   │   ├── drive_find.md
│   │   │   ├── drive_get.md
│   │   │   ├── drive_id-class.md
│   │   │   ├── drive_ls.md
│   │   │   ├── drive_mime_type.md
│   │   │   ├── drive_mv.md
│   │   │   ├── drive_publish.md
│   │   │   ├── drive_put.md
│   │   │   ├── drive_reveal.md
│   │   │   ├── drive_share.md
│   │   │   ├── drive_update.md
│   │   │   ├── drive_upload.md
│   │   │   ├── request_generate.md
│   │   │   ├── shared_drives.md
│   │   │   ├── shortcut.md
│   │   │   ├── utils-paths.md
│   │   │   └── utils-ui.md
│   │   ├── driver.R
│   │   ├── helper.R
│   │   ├── setup-testing.R
│   │   ├── test-camelCase.R
│   │   ├── test-compat-dplyr.R
│   │   ├── test-compat-vctrs.R
│   │   ├── test-deprecated.R
│   │   ├── test-dribble.R
│   │   ├── test-drive_auth.R
│   │   ├── test-drive_browse.R
│   │   ├── test-drive_cp.R
│   │   ├── test-drive_create.R
│   │   ├── test-drive_download.R
│   │   ├── test-drive_endpoints.R
│   │   ├── test-drive_examples.R
│   │   ├── test-drive_fields.R
│   │   ├── test-drive_find.R
│   │   ├── test-drive_get.R
│   │   ├── test-drive_get_path.R
│   │   ├── test-drive_id-class.R
│   │   ├── test-drive_ls.R
│   │   ├── test-drive_mime_type.R
│   │   ├── test-drive_mv.R
│   │   ├── test-drive_publish.R
│   │   ├── test-drive_put.R
│   │   ├── test-drive_read.R
│   │   ├── test-drive_reveal.R
│   │   ├── test-drive_rm.R
│   │   ├── test-drive_share.R
│   │   ├── test-drive_trash.R
│   │   ├── test-drive_update.R
│   │   ├── test-drive_upload.R
│   │   ├── test-drive_user.R
│   │   ├── test-fixtures/
│   │   │   ├── just_a_dribble.rds
│   │   │   └── mix_of_files_and_teamdrives.rds
│   │   ├── test-promote.R
│   │   ├── test-request_generate.R
│   │   ├── test-shared_drives.R
│   │   ├── test-shortcut.R
│   │   ├── test-utils-paths.R
│   │   ├── test-utils-ui.R
│   │   └── test-utils.R
│   └── testthat.R
└── vignettes/
    ├── .gitignore
    ├── articles/
    │   ├── .gitignore
    │   ├── bring-your-own-client.Rmd
    │   ├── example-files.Rmd
    │   ├── file-identification.Rmd
    │   ├── messages-and-errors.Rmd
    │   ├── multiple-files.Rmd
    │   └── permissions.Rmd
    └── googledrive.Rmd

================================================
FILE CONTENTS
================================================

================================================
FILE: .Rbuildignore
================================================
^.*\.Rproj$
^\.Rproj\.user$
^scratch.R$
^internal$
^data-raw$
^\.httr-oauth$
^\.httr-oauth-SUSPENDED$
^\.travis\.yml$
^codecov\.yml$
^appveyor\.yml$
^README\.Rmd$
^README-.*\.png$
^man-roxygen$
^docs$
^_pkgdown\.yml$
^index\.Rmd$
^cran-comments\.md$
^\.github$
^pkgdown$
^vignettes/articles$
^revdep$
^index\.md$
^LICENSE\.md$
^CRAN-RELEASE$
^\.covrignore$
^tests/testthat/all-test.+
^CRAN-SUBMISSION$
^[.]?air[.]toml$
^\.vscode$


================================================
FILE: .covrignore
================================================
R/aaa.R
R/deprecated.R
R/dplyr-compat.R


================================================
FILE: .github/.gitignore
================================================
*.html


================================================
FILE: .github/CODEOWNERS
================================================
# CODEOWNERS for googledrive
# https://www.tidyverse.org/development/understudies
.github/CODEOWNERS @jennybc @lucymcgowan


================================================
FILE: .github/CODE_OF_CONDUCT.md
================================================
# Contributor Covenant Code of Conduct

## Our Pledge

We as members, contributors, and leaders pledge to make participation in our
community a harassment-free experience for everyone, regardless of age, body
size, visible or invisible disability, ethnicity, sex characteristics, gender
identity and expression, level of experience, education, socio-economic status,
nationality, personal appearance, race, caste, color, religion, or sexual
identity and orientation.

We pledge to act and interact in ways that contribute to an open, welcoming,
diverse, inclusive, and healthy community.

## Our Standards

Examples of behavior that contributes to a positive environment for our
community include:

* Demonstrating empathy and kindness toward other people
* Being respectful of differing opinions, viewpoints, and experiences
* Giving and gracefully accepting constructive feedback
* Accepting responsibility and apologizing to those affected by our mistakes,
  and learning from the experience
* Focusing on what is best not just for us as individuals, but for the overall
  community

Examples of unacceptable behavior include:

* The use of sexualized language or imagery, and sexual attention or advances of
  any kind
* Trolling, insulting or derogatory comments, and personal or political attacks
* Public or private harassment
* Publishing others' private information, such as a physical or email address,
  without their explicit permission
* Other conduct which could reasonably be considered inappropriate in a
  professional setting

## Enforcement Responsibilities

Community leaders are responsible for clarifying and enforcing our standards of
acceptable behavior and will take appropriate and fair corrective action in
response to any behavior that they deem inappropriate, threatening, offensive,
or harmful.

Community leaders have the right and responsibility to remove, edit, or reject
comments, commits, code, wiki edits, issues, and other contributions that are
not aligned to this Code of Conduct, and will communicate reasons for moderation
decisions when appropriate.

## Scope

This Code of Conduct applies within all community spaces, and also applies when
an individual is officially representing the community in public spaces.
Examples of representing our community include using an official e-mail address,
posting via an official social media account, or acting as an appointed
representative at an online or offline event.

## Enforcement

Instances of abusive, harassing, or otherwise unacceptable behavior may be
reported to the community leaders responsible for enforcement at codeofconduct@posit.co. 
All complaints will be reviewed and investigated promptly and fairly.

All community leaders are obligated to respect the privacy and security of the
reporter of any incident.

## Enforcement Guidelines

Community leaders will follow these Community Impact Guidelines in determining
the consequences for any action they deem in violation of this Code of Conduct:

### 1. Correction

**Community Impact**: Use of inappropriate language or other behavior deemed
unprofessional or unwelcome in the community.

**Consequence**: A private, written warning from community leaders, providing
clarity around the nature of the violation and an explanation of why the
behavior was inappropriate. A public apology may be requested.

### 2. Warning

**Community Impact**: A violation through a single incident or series of
actions.

**Consequence**: A warning with consequences for continued behavior. No
interaction with the people involved, including unsolicited interaction with
those enforcing the Code of Conduct, for a specified period of time. This
includes avoiding interactions in community spaces as well as external channels
like social media. Violating these terms may lead to a temporary or permanent
ban.

### 3. Temporary Ban

**Community Impact**: A serious violation of community standards, including
sustained inappropriate behavior.

**Consequence**: A temporary ban from any sort of interaction or public
communication with the community for a specified period of time. No public or
private interaction with the people involved, including unsolicited interaction
with those enforcing the Code of Conduct, is allowed during this period.
Violating these terms may lead to a permanent ban.

### 4. Permanent Ban

**Community Impact**: Demonstrating a pattern of violation of community
standards, including sustained inappropriate behavior, harassment of an
individual, or aggression toward or disparagement of classes of individuals.

**Consequence**: A permanent ban from any sort of public interaction within the
community.

## Attribution

This Code of Conduct is adapted from the [Contributor Covenant][homepage],
version 2.1, available at
<https://www.contributor-covenant.org/version/2/1/code_of_conduct.html>.

Community Impact Guidelines were inspired by
[Mozilla's code of conduct enforcement ladder][https://github.com/mozilla/inclusion].

For answers to common questions about this code of conduct, see the FAQ at
<https://www.contributor-covenant.org/faq>. Translations are available at <https://www.contributor-covenant.org/translations>.

[homepage]: https://www.contributor-covenant.org


================================================
FILE: .github/CONTRIBUTING.Rmd
================================================
---
title: "Contributing to googledrive"
output:
  github_document:
    toc: true
    toc_depth: 3
---

## Making a pull request

  * Uphold the design principles and package mechanics outlined below.
  * When in doubt, discuss in an issue before doing lots of work.
  * Make sure the package still passes `R CMD check` locally for you. It's a good idea to do that before you touch anything, so you have a baseline.
  * Match the existing code style. Our intent is to follow <https://style.tidyverse.org>. Please use the [styler package](https://styler.r-lib.org) to re-style any code that you touch.
  * Tests: please *try* to run our tests or at least those that exercise your PR. Add tests, if relevant. If things go sideways, just say so. We are painfully aware that it's not easy to test API-wrapping, auth-requiring packages like googledrive and are open to constructive feedback. More below.
  * Documentation: Update the documentation source, if your PR changes any behavior. We use [roxygen2](https://cran.r-project.org/package=roxygen2), so you must edit the roxygen comments above the function; never edit `NAMESPACE` or `.Rd` files by hand. More below.
  * Website: The pkgdown-created website is built and deployed automatically via Travis-CI. Some changes require an edit to the `reference` section of `_pkgdown.yml`, i.e. to make sure that a function appears there.
  * If the PR is related to an issue, link to it in the description, with [the `#15` syntax](https://help.github.com/articles/autolinked-references-and-urls/) and the issue slug for context. If the PR is meant to close an issue, make sure one of the commit messages includes [text like `closes #44` or `fixes #101`](https://help.github.com/articles/closing-issues-using-keywords/). Provide the issue number and slug in the description, even if the issue is mentioned in the title, because auto-linking does not work in the PR title.
    - GOOD PR title: "Obtain user's intent via mind-reading; fixes #86".
    - BAD PR title: "Fixes #1043". Please remind us all what issue #1043 is about!
    - BAD PR title: "Something about #345". This will not actually close issue #345 upon merging. [Use the magic words](https://help.github.com/articles/closing-issues-using-keywords/).
  * Add a bullet to `NEWS.md` with a concise description of the change, if it's something a user would want to know when updating the package. [dplyr's `NEWS.md`](https://github.com/tidyverse/dplyr/blob/main/NEWS.md) is a good source of examples. Note the sentence format, the inclusion of GitHub username, and links to relevant issue(s)/PR(s). We will handle any organization into sub-sections just prior to a release. What merits a bullet?
    - Fixing a typo in the docs does not, but it is still awesome and deeply appreciated.
    - Fixing a bug or adding a new feature is bullet-worthy.

## Package philosophy

  * When in doubt, take a cue from the Unix file system commands or the Google Drive browser UI.
  * Have a reasonable default whenever humanly possible. This applies to auth, file name, file location, etc.
  * Be pipe-friendly.
  * If it's not well-documented (e.g. working example!), it doesn't really exist.
  * Accommodate initial file specification via path or name, but constantly push downstream work to be based on file id.
  * Return a tidy tibble, almost always a [`dribble`](https://tidyverse.github.io/googledrive/reference/dribble.html), whenever it makes sense.

There is a high-level interface for the typical user. These functions help you accomplish the most common tasks, hopefully in a natural way. Examples: `drive_find()`, `drive_upload()`, `drive_download()`. A few hand-picked functions support passing extra parameters through to the API request via `...`, but we don't do this across the board.

There is also a low-level interface that is used internally. An example is the function `request_generate()`. These functions are exported for use by programming-oriented users who are willing to read [Drive API docs](https://developers.google.com/drive/v3/web/about-sdk) and want to do things we haven't made available in the high-level interface.

## Package mechanics

### Documentation

We use [roxygen2](https://cran.r-project.org/package=roxygen2), specifically with the [Markdown syntax](https://cran.r-project.org/web/packages/roxygen2/vignettes/markdown.html), to create `NAMESPACE` and all `.Rd` files. All edits to documentation should be done in roxygen comments above the associated function 
or object.

Use templates or inheritance to repeat documentation whenever it is helpful, but without actually repeating its source.

Use internal and external links liberally, i.e. to other docs in googledrive or to Drive API resources.

We encourage working examples that include any necessary setup and teardown. In most cases, you'll have to put them inside a `\dontrun{}`.

It's nice if a pull request includes the result of running `devtools::document()`, to update `NAMESPACE` and the `.Rd` files, but that's optional. A good reason to NOT `document()` is if you have a different version of roxygen2 installed and that sprays minor formatting changes across `.Rd` files that have nothing to do with the PR.

### Testing

We use [testthat](https://cran.r-project.org/package=testthat).

We have many tests that (1) require authorization and that (2) rely on the existence of specific files and folders. Therefore, to fully test googledrive, you have to do some setup.

For small changes, it's fine to test your specific change locally and make a PR. Keep reading for an explanation of how to run full tests for googledrive.

#### Auth

A token is put into force at the beginning of a test run by the first few lines of [tests/testthat/helper.R](https://github.com/tidyverse/googledrive/blob/main/tests/testthat/helper.R).

  * This reflects the approach documented in the gargle vignette [Managing tokens securely](https://gargle.r-lib.org/articles/articles/managing-tokens-securely.html). We use embedded, encrypted service account token.
  * If you want to use a token you already have, edit those lines to use any of the techniques described in the gargle vignette [Non-interactive auth](https://gargle.r-lib.org/articles/non-interactive-auth.html). Don't commit and submit this change as part of a pull request; just use it as a pragmatic way to run tests with a token you have on hand.

#### R scripts for setup and clean

For speed reasons, the googledrive tests expect to find certain pre-existing files and folders, i.e. we don't do full setup and tear down on each run. You do setup at the beginning of your googledrive development and leave these files in place while you work. When you're done, e.g., when your PR is complete, you can clean up these files. Each test run also creates and destroys files, both locally and on Drive, but that is different and not what we're talking about here.

1. Source `tests/testthat/driver.R` to extract and aggregate the current setup and clean code across all test files.
    ```{r eval = FALSE}
    ## gather all the test setup and clean code from individual test files
    source(testthat::test_path("driver.R"))
    ## leaves behind:
    ##   * all-test-setup.R
    ##   * all-test-clean.R
    ```
    - This creates two R scripts: `tests/testthat/all-test-setup.R` and `tests/testthat/all-test-clean.R`. Inspect them.
1. When you are truly ready to perform setup or clean, edit the code to set the `SETUP` or `CLEAN` variable to `TRUE` instead of `FALSE`. This friction is intentional, so you don't accidentally create or delete lots of Drive files without meaning to.
1. Render `all-test-setup.R` with the Knit button in RStudio or like so:
```{r eval = FALSE}
rmarkdown::render(testthat::test_path("all-test-setup.R"))
```
You could also just source it, but it's nice to have a report that records what actually happened.

You should now be able to run the tests via *Build > Test Package* or *Build > Check Package* in RStudio or via `devtools::test()`.

You can leave the setup in place for as long as you're working on googledrive, i.e. you don't need to do this for every test run. In fact, that is the whole point!

When your googledrive development is over, render the clean script:
```{r eval = FALSE}
rmarkdown::render(testthat::test_path("all-test-clean.R"))
```

Again, read the report to look over what happened, in case anything was trashed that should not have been (btw, let us know about that so we can fix!). Once you're satisfied that your own files were not touched, you can `drive_empty_trash()` to truly delete the test files.

#### Adding tests

If you're going to add or modify tests, follow these conventions:

  * Test files are marked up with knitr chunk headers in comments, e.g. `# ---- clean ----` or `# ---- tests ----`. This is what enables the `driver.R` script to isolate the setup or cleaning code. Don't break that.
  * Any file that is truly necessary and can be setup in advance and persist? Do it, in order to make future test runs faster. Put the associated setup and clean code at the top of the test file.
  * All test files should have a name that documents why they exist and who made them. Use the `# ---- nm_fun ----` chunk to define naming functions used in that test file (see existing files for examples). Always use one of these functions to generate file names. Use `nm_()` for test files that persist. Use `me_()` for ephemeral test files that are created and destroyed in one test run.
  
Example and structure of a self-documenting name for a persistent test file:

```
move-files-into-me-TEST-drive-mv
<informative-slug>-TEST-<test-context>
```

Example and structure of a self-documenting name for an ephemeral test file:

```
DESCRIPTION-TEST-drive-upload-travis
<informative-slug>-TEST-<test-context>-<user>
```

Note that the current user is appended! This is so that concurrent test runs do not attempt to edit the same files.

### Continuous integration

googledrive is checked on a large matrix of R versions and operating systems via GitHub Actions.
We use [codecov](https://codecov.io/github/tidyverse/googledrive?branch=main) to track the test coverage.
In general, the package is subjected to `R CMD check`, unit tests, and test coverage analysis after every push to GitHub.
For internal branches, an encrypted service account token is available on GHA, so tests against the Drive API can be run.

Things are a bit different for pull requests from outside contributors, however. These PRs do not have access to the encrypted tokens, therefore many tests must be skipped. The PR will still be vetted via `R CMD check` and tests that do not call the Drive API can still be run. After you make a PR, it's a good idea to check back after a few minutes to see all of these results. If there are problems, read the log and try to correct the problem proactively. We "squash and merge" most pull requests, internal or external, so don't agonize over the commit history.

## Code of Conduct

Please note that the googledrive project is released with a
[Contributor Code of Conduct](CODE_OF_CONDUCT.md). By contributing to this
project you agree to abide by its terms.


================================================
FILE: .github/CONTRIBUTING.md
================================================
Contributing to googledrive
================

-   [Making a pull request](#making-a-pull-request)
-   [Package philosophy](#package-philosophy)
-   [Package mechanics](#package-mechanics)
    -   [Documentation](#documentation)
    -   [Testing](#testing)
    -   [Continuous integration](#continuous-integration)
-   [Code of Conduct](#code-of-conduct)

## Making a pull request

-   Uphold the design principles and package mechanics outlined below.
-   When in doubt, discuss in an issue before doing lots of work.
-   Make sure the package still passes `R CMD check` locally for you.
    It’s a good idea to do that before you touch anything, so you have a
    baseline.
-   Match the existing code style. Our intent is to follow
    <https://style.tidyverse.org>. Please use the [styler
    package](https://styler.r-lib.org) to re-style any code that you
    touch.
-   Tests: please *try* to run our tests or at least those that exercise
    your PR. Add tests, if relevant. If things go sideways, just say so.
    We are painfully aware that it’s not easy to test API-wrapping,
    auth-requiring packages like googledrive and are open to
    constructive feedback. More below.
-   Documentation: Update the documentation source, if your PR changes
    any behavior. We use
    [roxygen2](https://cran.r-project.org/package=roxygen2), so you must
    edit the roxygen comments above the function; never edit `NAMESPACE`
    or `.Rd` files by hand. More below.
-   Website: The pkgdown-created website is built and deployed
    automatically via Travis-CI. Some changes require an edit to the
    `reference` section of `_pkgdown.yml`, i.e. to make sure that a
    function appears there.
-   If the PR is related to an issue, link to it in the description,
    with [the `#15`
    syntax](https://help.github.com/articles/autolinked-references-and-urls/)
    and the issue slug for context. If the PR is meant to close an
    issue, make sure one of the commit messages includes [text like
    `closes #44` or
    `fixes #101`](https://help.github.com/articles/closing-issues-using-keywords/).
    Provide the issue number and slug in the description, even if the
    issue is mentioned in the title, because auto-linking does not work
    in the PR title.
    -   GOOD PR title: “Obtain user’s intent via mind-reading; fixes
        #86”.
    -   BAD PR title: “Fixes #1043”. Please remind us all what issue
        #1043 is about!
    -   BAD PR title: “Something about #345”. This will not actually
        close issue #345 upon merging. [Use the magic
        words](https://help.github.com/articles/closing-issues-using-keywords/).
-   Add a bullet to `NEWS.md` with a concise description of the change,
    if it’s something a user would want to know when updating the
    package. [dplyr’s
    `NEWS.md`](https://github.com/tidyverse/dplyr/blob/main/NEWS.md) is
    a good source of examples. Note the sentence format, the inclusion
    of GitHub username, and links to relevant issue(s)/PR(s). We will
    handle any organization into sub-sections just prior to a release.
    What merits a bullet?
    -   Fixing a typo in the docs does not, but it is still awesome and
        deeply appreciated.
    -   Fixing a bug or adding a new feature is bullet-worthy.

## Package philosophy

-   When in doubt, take a cue from the Unix file system commands or the
    Google Drive browser UI.
-   Have a reasonable default whenever humanly possible. This applies to
    auth, file name, file location, etc.
-   Be pipe-friendly.
-   If it’s not well-documented (e.g. working example!), it doesn’t
    really exist.
-   Accommodate initial file specification via path or name, but
    constantly push downstream work to be based on file id.
-   Return a tidy tibble, almost always a
    [`dribble`](https://tidyverse.github.io/googledrive/reference/dribble.html),
    whenever it makes sense.

There is a high-level interface for the typical user. These functions
help you accomplish the most common tasks, hopefully in a natural way.
Examples: `drive_find()`, `drive_upload()`, `drive_download()`. A few
hand-picked functions support passing extra parameters through to the
API request via `...`, but we don’t do this across the board.

There is also a low-level interface that is used internally. An example
is the function `request_generate()`. These functions are exported for
use by programming-oriented users who are willing to read [Drive API
docs](https://developers.google.com/drive/v3/web/about-sdk) and want to
do things we haven’t made available in the high-level interface.

## Package mechanics

### Documentation

We use [roxygen2](https://cran.r-project.org/package=roxygen2),
specifically with the [Markdown
syntax](https://cran.r-project.org/web/packages/roxygen2/vignettes/markdown.html),
to create `NAMESPACE` and all `.Rd` files. All edits to documentation
should be done in roxygen comments above the associated function or
object.

Use templates or inheritance to repeat documentation whenever it is
helpful, but without actually repeating its source.

Use internal and external links liberally, i.e. to other docs in
googledrive or to Drive API resources.

We encourage working examples that include any necessary setup and
teardown. In most cases, you’ll have to put them inside a `\dontrun{}`.

It’s nice if a pull request includes the result of running
`devtools::document()`, to update `NAMESPACE` and the `.Rd` files, but
that’s optional. A good reason to NOT `document()` is if you have a
different version of roxygen2 installed and that sprays minor formatting
changes across `.Rd` files that have nothing to do with the PR.

### Testing

We use [testthat](https://cran.r-project.org/package=testthat).

We have many tests that (1) require authorization and that (2) rely on
the existence of specific files and folders. Therefore, to fully test
googledrive, you have to do some setup.

For small changes, it’s fine to test your specific change locally and
make a PR. Keep reading for an explanation of how to run full tests for
googledrive.

#### Auth

A token is put into force at the beginning of a test run by the first
few lines of
[tests/testthat/helper.R](https://github.com/tidyverse/googledrive/blob/main/tests/testthat/helper.R).

-   This reflects the approach documented in the gargle vignette
    [Managing tokens
    securely](https://gargle.r-lib.org/articles/articles/managing-tokens-securely.html).
    We use embedded, encrypted service account token.
-   If you want to use a token you already have, edit those lines to use
    any of the techniques described in the gargle vignette
    [Non-interactive
    auth](https://gargle.r-lib.org/articles/non-interactive-auth.html).
    Don’t commit and submit this change as part of a pull request; just
    use it as a pragmatic way to run tests with a token you have on
    hand.

#### R scripts for setup and clean

For speed reasons, the googledrive tests expect to find certain
pre-existing files and folders, i.e. we don’t do full setup and tear
down on each run. You do setup at the beginning of your googledrive
development and leave these files in place while you work. When you’re
done, e.g., when your PR is complete, you can clean up these files. Each
test run also creates and destroys files, both locally and on Drive, but
that is different and not what we’re talking about here.

1.  Source `tests/testthat/driver.R` to extract and aggregate the
    current setup and clean code across all test files.

    ``` r
    ## gather all the test setup and clean code from individual test files
    source(testthat::test_path("driver.R"))
    ## leaves behind:
    ##   * all-test-setup.R
    ##   * all-test-clean.R
    ```

    -   This creates two R scripts: `tests/testthat/all-test-setup.R`
        and `tests/testthat/all-test-clean.R`. Inspect them.

2.  When you are truly ready to perform setup or clean, edit the code to
    set the `SETUP` or `CLEAN` variable to `TRUE` instead of `FALSE`.
    This friction is intentional, so you don’t accidentally create or
    delete lots of Drive files without meaning to.

3.  Render `all-test-setup.R` with the Knit button in RStudio or like
    so:

``` r
rmarkdown::render(testthat::test_path("all-test-setup.R"))
```

You could also just source it, but it’s nice to have a report that
records what actually happened.

You should now be able to run the tests via *Build \> Test Package* or
*Build \> Check Package* in RStudio or via `devtools::test()`.

You can leave the setup in place for as long as you’re working on
googledrive, i.e. you don’t need to do this for every test run. In fact,
that is the whole point!

When your googledrive development is over, render the clean script:

``` r
rmarkdown::render(testthat::test_path("all-test-clean.R"))
```

Again, read the report to look over what happened, in case anything was
trashed that should not have been (btw, let us know about that so we can
fix!). Once you’re satisfied that your own files were not touched, you
can `drive_empty_trash()` to truly delete the test files.

#### Adding tests

If you’re going to add or modify tests, follow these conventions:

-   Test files are marked up with knitr chunk headers in comments,
    e.g. `# ---- clean ----` or `# ---- tests ----`. This is what
    enables the `driver.R` script to isolate the setup or cleaning code.
    Don’t break that.
-   Any file that is truly necessary and can be setup in advance and
    persist? Do it, in order to make future test runs faster. Put the
    associated setup and clean code at the top of the test file.
-   All test files should have a name that documents why they exist and
    who made them. Use the `# ---- nm_fun ----` chunk to define naming
    functions used in that test file (see existing files for examples).
    Always use one of these functions to generate file names. Use
    `nm_()` for test files that persist. Use `me_()` for ephemeral test
    files that are created and destroyed in one test run.

Example and structure of a self-documenting name for a persistent test
file:

    move-files-into-me-TEST-drive-mv
    <informative-slug>-TEST-<test-context>

Example and structure of a self-documenting name for an ephemeral test
file:

    DESCRIPTION-TEST-drive-upload-travis
    <informative-slug>-TEST-<test-context>-<user>

Note that the current user is appended! This is so that concurrent test
runs do not attempt to edit the same files.

### Continuous integration

googledrive is checked on a large matrix of R versions and operating
systems via GitHub Actions. We use
[codecov](https://codecov.io/github/tidyverse/googledrive?branch=main)
to track the test coverage. In general, the package is subjected to
`R CMD check`, unit tests, and test coverage analysis after every push
to GitHub. For internal branches, an encrypted service account token is
available on GHA, so tests against the Drive API can be run.

Things are a bit different for pull requests from outside contributors,
however. These PRs do not have access to the encrypted tokens, therefore
many tests must be skipped. The PR will still be vetted via
`R CMD check` and tests that do not call the Drive API can still be run.
After you make a PR, it’s a good idea to check back after a few minutes
to see all of these results. If there are problems, read the log and try
to correct the problem proactively. We “squash and merge” most pull
requests, internal or external, so don’t agonize over the commit
history.

## Code of Conduct

Please note that the googledrive project is released with a [Contributor
Code of Conduct](CODE_OF_CONDUCT.md). By contributing to this project
you agree to abide by its terms.


================================================
FILE: .github/ISSUE_TEMPLATE/issue_template.md
================================================
Please briefly describe your problem and what output you expect. If you have a question, please don't use this form. Instead, ask on <https://stackoverflow.com/> or <https://community.rstudio.com/>.

Please include a minimal reproducible example (AKA a reprex). If you've never heard of a [reprex](https://reprex.tidyverse.org/) before, start by reading <https://www.tidyverse.org/help/#reprex>.

---

Brief description of the problem

```r
# insert reprex here
```


================================================
FILE: .github/SUPPORT.md
================================================
# Getting help with googledrive

Thanks for using googledrive. Before filing an issue, there are a few places
to explore and pieces to put together to make the process as smooth as possible.

Start by making a minimal **repr**oducible **ex**ample using the 
[reprex](https://reprex.tidyverse.org/) package. If you haven't heard of or used 
reprex before, you're in for a treat! Seriously, reprex will make all of your 
R-question-asking endeavors easier (which is a pretty insane ROI for the five to 
ten minutes it'll take you to learn what it's all about). For additional reprex
pointers, check out the [Get help!](https://www.tidyverse.org/help/) section of
the tidyverse site.

Armed with your reprex, the next step is to figure out [where to ask](https://www.tidyverse.org/help/#where-to-ask). 

  * If it's a question: start with [community.rstudio.com](https://community.rstudio.com/), 
    and/or StackOverflow. There are more people there to answer questions.  
  * If it's a bug: you're in the right place, file an issue.  
  * If you're not sure: let the community help you figure it out! If your 
    problem _is_ a bug or a feature request, you can easily return here and 
    report it. 

Before opening a new issue, be sure to [search issues and pull requests](https://github.com/tidyverse/googledrive/issues) to make sure the 
bug hasn't been reported and/or already fixed in the development version. By 
default, the search will be pre-populated with `is:issue is:open`. You can 
[edit the qualifiers](https://help.github.com/articles/searching-issues-and-pull-requests/) 
(e.g. `is:pr`, `is:closed`) as needed. For example, you'd simply
remove `is:open` to search _all_ issues in the repo, open or closed.


If you _are_ in the right place, and need to file an issue, please review the 
["File issues"](https://www.tidyverse.org/contribute/#issues) paragraph from 
the tidyverse contributing guidelines.

Thanks for your help!


================================================
FILE: .github/workflows/R-CMD-check.yaml
================================================
# Workflow derived from https://github.com/r-lib/actions/tree/v2/examples
# Need help debugging build failures? Start at https://github.com/r-lib/actions#where-to-find-help
#
# NOTE: This workflow is overkill for most R packages and
# check-standard.yaml is likely a better choice.
# usethis::use_github_action("check-standard") will install it.
on:
  push:
    branches: [main, master]
  pull_request:
    branches: [main, master]

name: R-CMD-check.yaml

permissions: read-all

jobs:
  R-CMD-check:
    runs-on: ${{ matrix.config.os }}

    name: ${{ matrix.config.os }} (${{ matrix.config.r }})

    strategy:
      fail-fast: false
      matrix:
        config:
          - {os: macos-latest,   r: 'release'}

          - {os: windows-latest, r: 'release'}
          # use 4.0 or 4.1 to check with rtools40's older compiler
          - {os: windows-latest, r: 'oldrel-4'}

          - {os: ubuntu-latest,  r: 'devel', http-user-agent: 'release'}
          - {os: ubuntu-latest,  r: 'release'}
          - {os: ubuntu-latest,  r: 'oldrel-1'}
          - {os: ubuntu-latest,  r: 'oldrel-2'}
          - {os: ubuntu-latest,  r: 'oldrel-3'}
          - {os: ubuntu-latest,  r: 'oldrel-4'}

    env:
      GITHUB_PAT: ${{ secrets.GITHUB_TOKEN }}
      R_KEEP_PKG_SOURCE: yes

    steps:
      - uses: actions/checkout@v4

      - uses: r-lib/actions/setup-pandoc@v2

      - uses: r-lib/actions/setup-r@v2
        with:
          r-version: ${{ matrix.config.r }}
          http-user-agent: ${{ matrix.config.http-user-agent }}
          use-public-rspm: true

      - uses: r-lib/actions/setup-r-dependencies@v2
        with:
          extra-packages: any::rcmdcheck
          needs: check

      - uses: r-lib/actions/check-r-package@v2
        with:
          upload-snapshots: true
          build_args: 'c("--no-manual","--compact-vignettes=gs+qpdf")'


================================================
FILE: .github/workflows/format-suggest.yaml
================================================
# Workflow derived from https://github.com/posit-dev/setup-air/tree/main/examples

on:
  # Using `pull_request_target` over `pull_request` for elevated `GITHUB_TOKEN`
  # privileges, otherwise we can't set `pull-requests: write` when the pull
  # request comes from a fork, which is our main use case (external contributors).
  #
  # `pull_request_target` runs in the context of the target branch (`main`, usually),
  # rather than in the context of the pull request like `pull_request` does. Due
  # to this, we must explicitly checkout `ref: ${{ github.event.pull_request.head.sha }}`.
  # This is typically frowned upon by GitHub, as it exposes you to potentially running
  # untrusted code in a context where you have elevated privileges, but they explicitly
  # call out the use case of reformatting and committing back / commenting on the PR
  # as a situation that should be safe (because we aren't actually running the untrusted
  # code, we are just treating it as passive data).
  # https://securitylab.github.com/resources/github-actions-preventing-pwn-requests/
  pull_request_target:

name: format-suggest.yaml

jobs:
  format-suggest:
    name: format-suggest
    runs-on: ubuntu-latest

    permissions:
      # Required to push suggestion comments to the PR
      pull-requests: write

    steps:
      - uses: actions/checkout@v4
        with:
          ref: ${{ github.event.pull_request.head.sha }}

      - name: Install
        uses: posit-dev/setup-air@v1

      - name: Format
        run: air format .

      - name: Suggest
        uses: reviewdog/action-suggester@v1
        with:
          level: error
          fail_level: error
          tool_name: air


================================================
FILE: .github/workflows/pkgdown.yaml
================================================
# Workflow derived from https://github.com/r-lib/actions/tree/v2/examples
# Need help debugging build failures? Start at https://github.com/r-lib/actions#where-to-find-help
on:
  push:
    branches: [main, master]
  schedule:
    # * is a special character in YAML so we have to quote this string
    # 13:30 (UTC) - 7 or 8 (offset for Pacific) = 5:30 or 6:30 am
    # try to avoid overlap other jobs in googledrive and googlesheets4 because quota
    # https://crontab.guru is your friend
    - cron:  '30 13 * * *'
  release:
    types: [published]
  workflow_dispatch:

name: pkgdown.yaml

permissions: read-all

jobs:
  pkgdown:
    runs-on: ubuntu-latest
    if: |
      github.event_name == 'schedule' ||
      github.event_name == 'workflow_dispatch' ||
      github.event_name == 'release' ||
      contains(github.event.head_commit.message, '[pkgdown]')

    env:
      GOOGLEDRIVE_KEY: ${{ secrets.GOOGLEDRIVE_KEY }}
      GITHUB_PAT: ${{ secrets.GITHUB_TOKEN }}
    permissions:
      contents: write
    steps:
      - uses: actions/checkout@v4

      - uses: r-lib/actions/setup-pandoc@v2

      - uses: r-lib/actions/setup-r@v2
        with:
          use-public-rspm: true

      - uses: r-lib/actions/setup-r-dependencies@v2
        with:
          extra-packages: any::pkgdown, local::.
          needs: website

      - name: Build site
        run: pkgdown::build_site_github_pages(new_process = FALSE, install = FALSE)
        shell: Rscript {0}

      - name: Deploy to GitHub pages 🚀
        if: github.event_name != 'pull_request'
        uses: JamesIves/github-pages-deploy-action@v4.5.0
        with:
          clean: false
          branch: gh-pages
          folder: docs


================================================
FILE: .github/workflows/pr-commands.yaml
================================================
# Workflow derived from https://github.com/r-lib/actions/tree/v2/examples
# Need help debugging build failures? Start at https://github.com/r-lib/actions#where-to-find-help
on:
  issue_comment:
    types: [created]

name: pr-commands.yaml

permissions: read-all

jobs:
  document:
    if: ${{ github.event.issue.pull_request && (github.event.comment.author_association == 'MEMBER' || github.event.comment.author_association == 'OWNER') && startsWith(github.event.comment.body, '/document') }}
    name: document
    runs-on: ubuntu-latest
    env:
      GITHUB_PAT: ${{ secrets.GITHUB_TOKEN }}
    permissions:
      contents: write
    steps:
      - uses: actions/checkout@v4

      - uses: r-lib/actions/pr-fetch@v2
        with:
          repo-token: ${{ secrets.GITHUB_TOKEN }}

      - uses: r-lib/actions/setup-r@v2
        with:
          use-public-rspm: true

      - uses: r-lib/actions/setup-r-dependencies@v2
        with:
          extra-packages: any::roxygen2
          needs: pr-document

      - name: Document
        run: roxygen2::roxygenise()
        shell: Rscript {0}

      - name: commit
        run: |
          git config --local user.name "$GITHUB_ACTOR"
          git config --local user.email "$GITHUB_ACTOR@users.noreply.github.com"
          git add man/\* NAMESPACE
          git commit -m 'Document'

      - uses: r-lib/actions/pr-push@v2
        with:
          repo-token: ${{ secrets.GITHUB_TOKEN }}

  style:
    if: ${{ github.event.issue.pull_request && (github.event.comment.author_association == 'MEMBER' || github.event.comment.author_association == 'OWNER') && startsWith(github.event.comment.body, '/style') }}
    name: style
    runs-on: ubuntu-latest
    env:
      GITHUB_PAT: ${{ secrets.GITHUB_TOKEN }}
    permissions:
      contents: write
    steps:
      - uses: actions/checkout@v4

      - uses: r-lib/actions/pr-fetch@v2
        with:
          repo-token: ${{ secrets.GITHUB_TOKEN }}

      - uses: r-lib/actions/setup-r@v2

      - name: Install dependencies
        run: install.packages("styler")
        shell: Rscript {0}

      - name: Style
        run: styler::style_pkg()
        shell: Rscript {0}

      - name: commit
        run: |
          git config --local user.name "$GITHUB_ACTOR"
          git config --local user.email "$GITHUB_ACTOR@users.noreply.github.com"
          git add \*.R
          git commit -m 'Style'

      - uses: r-lib/actions/pr-push@v2
        with:
          repo-token: ${{ secrets.GITHUB_TOKEN }}


================================================
FILE: .github/workflows/test-coverage.yaml
================================================
# Workflow derived from https://github.com/r-lib/actions/tree/v2/examples
# Need help debugging build failures? Start at https://github.com/r-lib/actions#where-to-find-help
on:
  push:
    branches: [main, master]
  pull_request:
    branches: [main, master]
  schedule:
    # * is a special character in YAML so we have to quote this string
    # 2am Pacific = 10am UTC
    # https://crontab.guru is your friend
    - cron:  '0 10 * * *'

name: test-coverage.yaml

permissions: read-all

jobs:
  test-coverage:
    runs-on: ubuntu-latest
    if: "github.event_name == 'schedule' || contains(github.event.head_commit.message, '[covr]')"

    env:
      GITHUB_PAT: ${{ secrets.GITHUB_TOKEN }}
      GOOGLEDRIVE_KEY: ${{ secrets.GOOGLEDRIVE_KEY }}

    steps:
      - uses: actions/checkout@v4

      - uses: r-lib/actions/setup-r@v2
        with:
          use-public-rspm: true

      - uses: r-lib/actions/setup-r-dependencies@v2
        with:
          extra-packages: any::covr, any::xml2
          needs: coverage

      - name: Test coverage
        run: |
          cov <- covr::package_coverage(
            quiet = FALSE,
            clean = FALSE,
            install_path = file.path(normalizePath(Sys.getenv("RUNNER_TEMP"), winslash = "/"), "package")
          )
          print(cov)
          covr::to_cobertura(cov)
        shell: Rscript {0}

      - uses: codecov/codecov-action@v5
        with:
          # Fail if error if not on PR, or if on PR and token is given
          fail_ci_if_error: ${{ github.event_name != 'pull_request' || secrets.CODECOV_TOKEN }}
          files: ./cobertura.xml
          plugins: noop
          disable_search: true
          token: ${{ secrets.CODECOV_TOKEN }}

      - name: Show testthat output
        if: always()
        run: |
          ## --------------------------------------------------------------------
          find '${{ runner.temp }}/package' -name 'testthat.Rout*' -exec cat '{}' \; || true
        shell: bash

      - name: Upload test results
        if: failure()
        uses: actions/upload-artifact@v4
        with:
          name: coverage-test-failures
          path: ${{ runner.temp }}/package


================================================
FILE: .github/workflows/with-auth.yml
================================================
# Workflow derived from https://github.com/r-lib/actions/tree/v2/examples
# Need help debugging build failures? Start at https://github.com/r-lib/actions#where-to-find-help
on:
  push:
    branches: [main, master]
  pull_request:
    branches: [main, master]

name: with-auth

permissions: read-all

jobs:
  with-auth:
    runs-on: ubuntu-latest

    env:
      GITHUB_PAT: ${{ secrets.GITHUB_TOKEN }}

    steps:
      - uses: actions/checkout@v4

      - uses: r-lib/actions/setup-pandoc@v2

      - uses: r-lib/actions/setup-r@v2
        with:
          r-version: release
          http-user-agent: 'release'
          use-public-rspm: true

      - uses: r-lib/actions/setup-r-dependencies@v2
        with:
          extra-packages: any::rcmdcheck
          needs: check

      - uses: r-lib/actions/check-r-package@v2
        env:
          GOOGLEDRIVE_KEY: ${{ secrets.GOOGLEDRIVE_KEY }}
        with:
          upload-snapshots: true
          build_args: 'c("--no-manual","--compact-vignettes=gs+qpdf")'


================================================
FILE: .gitignore
================================================
sandbox
scratch.R
*.Rproj.user
.Rhistory
.Rproj.user
.RData
.httr-oauth*
.Rapp.history
*token.rds
inst/doc
docs/
internal


================================================
FILE: .vscode/extensions.json
================================================
{
    "recommendations": [
        "Posit.air-vscode"
    ]
}


================================================
FILE: .vscode/settings.json
================================================
{
    "[r]": {
        "editor.formatOnSave": true,
        "editor.defaultFormatter": "Posit.air-vscode"
    },
    "[quarto]": {
        "editor.formatOnSave": true,
        "editor.defaultFormatter": "quarto.quarto"
    }
}


================================================
FILE: DESCRIPTION
================================================
Package: googledrive
Title: An Interface to Google Drive
Version: 2.1.2.9000
Authors@R: c(
    person("Lucy", "D'Agostino McGowan", , role = "aut"),
    person("Jennifer", "Bryan", , "jenny@posit.co", role = c("aut", "cre"),
           comment = c(ORCID = "0000-0002-6983-2759")),
    person("Posit Software, PBC", role = c("cph", "fnd"))
  )
Description: Manage Google Drive files from R.
License: MIT + file LICENSE
URL: https://googledrive.tidyverse.org,
    https://github.com/tidyverse/googledrive
BugReports: https://github.com/tidyverse/googledrive/issues
Depends:
    R (>= 4.1)
Imports:
    cli (>= 3.0.0),
    gargle (>= 1.6.0),
    glue (>= 1.4.2),
    httr,
    jsonlite,
    lifecycle,
    magrittr,
    pillar (>= 1.9.0),
    purrr (>= 1.0.1),
    rlang (>= 1.0.2),
    tibble (>= 2.0.0),
    utils,
    uuid,
    vctrs (>= 0.3.0),
    withr
Suggests:
    curl,
    dplyr (>= 1.0.0),
    knitr,
    rmarkdown,
    spelling,
    testthat (>= 3.1.5)
VignetteBuilder:
    knitr
Config/Needs/website:
    tidyverse,
    tidyverse/tidytemplate
Config/testthat/edition: 3
Encoding: UTF-8
Language: en-US
Roxygen: list(markdown = TRUE)
RoxygenNote: 7.3.3


================================================
FILE: LICENSE
================================================
YEAR: 2023
COPYRIGHT HOLDER: googledrive authors


================================================
FILE: LICENSE.md
================================================
# MIT License

Copyright (c) 2023 googledrive authors

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.


================================================
FILE: NAMESPACE
================================================
# Generated by roxygen2: do not edit by hand

S3method("[",dribble)
S3method("names<-",dribble)
S3method(as_dribble,"NULL")
S3method(as_dribble,character)
S3method(as_dribble,data.frame)
S3method(as_dribble,default)
S3method(as_dribble,drive_id)
S3method(as_dribble,list)
S3method(as_id,"NULL")
S3method(as_id,character)
S3method(as_id,data.frame)
S3method(as_id,default)
S3method(as_id,dribble)
S3method(as_id,drive_id)
S3method(as_shared_drive,"NULL")
S3method(as_shared_drive,character)
S3method(as_shared_drive,data.frame)
S3method(as_shared_drive,default)
S3method(as_shared_drive,dribble)
S3method(as_shared_drive,drive_id)
S3method(as_shared_drive,list)
S3method(as_tibble,dribble)
S3method(format,drive_user)
S3method(gargle_map_cli,dribble)
S3method(gargle_map_cli,drive_id)
S3method(pillar_shaft,drive_id)
S3method(print,drive_user)
S3method(tbl_sum,dribble)
S3method(vec_cast,character.drive_id)
S3method(vec_cast,data.frame.dribble)
S3method(vec_cast,dribble.data.frame)
S3method(vec_cast,dribble.dribble)
S3method(vec_cast,dribble.tbl_df)
S3method(vec_cast,drive_id.character)
S3method(vec_cast,drive_id.drive_id)
S3method(vec_cast,tbl_df.dribble)
S3method(vec_ptype2,character.drive_id)
S3method(vec_ptype2,data.frame.dribble)
S3method(vec_ptype2,dribble.data.frame)
S3method(vec_ptype2,dribble.dribble)
S3method(vec_ptype2,dribble.tbl_df)
S3method(vec_ptype2,drive_id.character)
S3method(vec_ptype2,drive_id.drive_id)
S3method(vec_ptype2,tbl_df.dribble)
S3method(vec_ptype_abbr,drive_id)
S3method(vec_restore,dribble)
export("%>%")
export(as_dribble)
export(as_id)
export(as_shared_drive)
export(as_team_drive)
export(confirm_dribble)
export(confirm_single_file)
export(confirm_some_files)
export(do_paginated_request)
export(do_request)
export(drive_about)
export(drive_api_key)
export(drive_auth)
export(drive_auth_config)
export(drive_auth_configure)
export(drive_browse)
export(drive_cp)
export(drive_create)
export(drive_deauth)
export(drive_download)
export(drive_empty_trash)
export(drive_endpoint)
export(drive_endpoints)
export(drive_example)
export(drive_example_local)
export(drive_example_remote)
export(drive_examples_local)
export(drive_examples_remote)
export(drive_extension)
export(drive_fields)
export(drive_find)
export(drive_get)
export(drive_has_token)
export(drive_link)
export(drive_ls)
export(drive_mime_type)
export(drive_mkdir)
export(drive_mv)
export(drive_oauth_app)
export(drive_oauth_client)
export(drive_publish)
export(drive_put)
export(drive_read_raw)
export(drive_read_string)
export(drive_rename)
export(drive_reveal)
export(drive_rm)
export(drive_scopes)
export(drive_share)
export(drive_share_anyone)
export(drive_token)
export(drive_trash)
export(drive_unpublish)
export(drive_untrash)
export(drive_update)
export(drive_upload)
export(drive_user)
export(expose)
export(is_dribble)
export(is_folder)
export(is_folder_shortcut)
export(is_mine)
export(is_native)
export(is_parental)
export(is_shared_drive)
export(is_shortcut)
export(is_team_drive)
export(local_drive_quiet)
export(no_file)
export(prep_fields)
export(request_generate)
export(request_make)
export(shared_drive_create)
export(shared_drive_find)
export(shared_drive_get)
export(shared_drive_rm)
export(shared_drive_update)
export(shortcut_create)
export(shortcut_resolve)
export(single_file)
export(some_files)
export(team_drive_create)
export(team_drive_find)
export(team_drive_get)
export(team_drive_rm)
export(team_drive_update)
export(with_drive_quiet)
import(rlang)
import(vctrs)
importFrom(gargle,bulletize)
importFrom(gargle,gargle_map_cli)
importFrom(glue,glue)
importFrom(glue,glue_collapse)
importFrom(glue,glue_data)
importFrom(lifecycle,deprecated)
importFrom(magrittr,"%>%")
importFrom(pillar,pillar_shaft)
importFrom(purrr,map)
importFrom(purrr,map2)
importFrom(purrr,map_chr)
importFrom(purrr,map_if)
importFrom(purrr,map_int)
importFrom(purrr,map_lgl)
importFrom(purrr,pluck)
importFrom(tibble,as_tibble)
importFrom(tibble,tbl_sum)
importFrom(tibble,tibble)


================================================
FILE: NEWS.md
================================================
# googledrive (development version)

# googledrive 2.1.2

* `drive_upload()` and `drive_download()` support the conversion of a local
  markdown file to a Google Doc and vice versa (#465, @ateucher).

# googledrive 2.1.1

* `drive_auth(subject =)` is a new argument that can be used with
  `drive_auth(path =)`, i.e. when using a service account. The `path` and
  `subject` arguments are ultimately processed by
  `gargle::credentials_service_account()` and support the use of a service
  account to impersonate a normal user (#413).

* All requests now route through `gargle::request_retry()` (#380).

* `drive_scopes()` is a new function to access scopes relevant to the Drive API.
  When called without arguments, `drive_scopes()` returns a named vector of
  scopes, where the names are the associated short aliases. `drive_scopes()` can
  also be called with a character vector; any element that's recognized as a
  short alias is replaced with the associated full scope (#430).
  
* Various internal changes to sync up with gargle v1.5.0.

# googledrive 2.1.0

## Syncing up with gargle

Version 1.3.0 of gargle introduced some changes around OAuth and googledrive is syncing up that:

* `drive_oauth_client()` is a new function to replace the now-deprecated `drive_oauth_app()`.
* The new `client` argument of `drive_auth_configure()` replaces the now-deprecated `app` argument.
* The documentation of `drive_auth_configure()` emphasizes that the preferred way to "bring your own OAuth client" is by providing the JSON downloaded from Google Developers Console.

## Shared drives

`drive_ls(recursive = TRUE)` now works when the target folder is on a shared drive (#265, @Falnesio).

`drive_mv()` no longer errors with "A shared drive item must have exactly one parent." when moving a file on a shared drive (#377).

## Other

`drive_auth()` now warns if the user specifies both `email` and `path`, because this is almost always an error (#420).

`drive_auth_config()` was deprecated in googledrive 1.0.0 (released 2019-08-19) and is now defunct.

`drive_example()` was deprecated in googledrive 2.0.0 (released 2021-07-08) and is now defunct.

# googledrive 2.0.0

## Team Drives are dead! Long live shared drives!

Google Drive has rebranded Team Drives as **shared drives**.
While anyone can have a **My Drive**, shared drives are only available for Google Workspace (previously known as G Suite).
Shared drives and the files within are owned by a team/organization, as opposed to an individual.

In googledrive, all `team_drive_*()` functions have been deprecated, in favor of
their `shared_drive_*()` successors.
Likewise, any `team_drive` argument has been deprecated, in favor of a new `shared_drive` argument.
The terms used to describe which collections to search have also changed slightly, with `"allDrives"` replacing `"all"`.
This applies to the `corpus` argument of `drive_find()` and `drive_get()`.

Where to learn more:

* [Team Drives is being renamed to shared drives](https://workspaceupdates.googleblog.com/2019/04/shared-drives.html) from Google Workspace blog
* [Upcoming changes to the Google Drive API and Google Picker API](https://cloud.google.com/blog/products/application-development/upcoming-changes-to-the-google-drive-api-and-google-picker-api) from the Google Cloud blog

## Single parenting and shortcuts

As of 2020-09-30, Drive no longer allows a file to be placed in multiple folders; going forward, every file will have exactly 1 parent folder.
In many cases that parent is just the top-level or root folder of your "My Drive" or of a shared drive.

This change has been accompanied by the introduction of file **shortcuts**, which function much like symbolic or "soft" links.
Shortcuts are the new way to make a file appear to be in more than one place or, said another way, the new way for one Drive file to be associated with more than one Drive filepath.
A shortcut is a special type of Drive file, characterized by the `application/vnd.google-apps.shortcut` MIME type.
You can make a shortcut to any Drive file, including to a Drive folder.

Drive has been migrating existing files to the one-parent state, i.e., "single parenting" them.
Drive selects the most suitable parent folder to keep, "based on the hierarchy's properties", and replaces any other parent-child relationships with a shortcut.

New functions related to shortcuts:

* `shortcut_create()`: creates a shortcut to a specific Drive file (or folder).
* `shortcut_resolve()`: resolves a shortcut to its target, i.e. the file it
  refers to. Works for multiple files at once, i.e. the input can be a mix of
  shortcuts and non-shortcuts. The non-shortcuts are passed through and the
  shortcuts are replaced by their targets.
  
How interacts with googledrive's support for specifying file by filepath:

* Main principle: shortcuts are first-class Drive files that we assume users
  will need to manipulate with googledrive. In general, there is no automatic
  resolution to the target file.
* `drive_reveal(what = "path")` returns the canonical path, i.e. there will be
  no shortcuts among the non-terminal "folder" parts of the returned path.
* `drive_get(path = "foo/")` can retrieve a folder named "foo" or a shortcut
  named "foo", whose target is a folder.
* When a shortcut-to-a-folder is specified as the `path`, in a context where it
  unambiguously specifies a parent folder, the `path` **is** auto-resolved to
  its target folder. This is the exception to the "no automatic resolution"
  rule. Functions affected:
  - `drive_ls(path, ...)`
  - `drive_create(name, path, ...)` and its convenience wrappers `drive_mkdir()`
    and `shortcut_create()`
  - `drive_cp(file, path, ...)`
  - `drive_mv(file, path, ...)`
  - `drive_upload(media, path, ...)` and its close friend `drive_put()`

Further reading about changes to the Drive folder model:

* [Simplifying Google Drive’s folder structure and sharing models](https://workspace.google.com/blog/product-announcements/simplifying-google-drives-folder-structure-and-sharing-models)
* [Single-parenting behavior changes](https://developers.google.com/drive/api/v3/ref-single-parent)
* [Create a shortcut to a Drive file](https://developers.google.com/drive/api/v3/shortcuts)
* Find files & folders with Google Drive shortcuts: `https://support.google.com/drive/answer/9700156`

## User interface

The user interface has gotten more stylish, thanks to the cli package (<https://cli.r-lib.org>).
All informational messages, warnings, and errors are now emitted via cli, which uses rlang's condition functions under-the-hood.

`googledrive_quiet` is a new option to suppress informational messages from googledrive.
Unless it's explicitly set to `TRUE`, the default is to message.

The `verbose` argument of all `drive_*()` functions is deprecated and will be removed in a future release.
In the current release, `verbose = FALSE` is still honored, but generates a warning.

`local_drive_quiet()` and `with_drive_quiet()` are [withr-style](https://withr.r-lib.org) convenience helpers for setting `googledrive_quiet = TRUE` for some limited scope.

## Other changes

* We now share a variety of world-readable, persistent example files on Drive,
  for use in examples and documentation. These remote example files complement
  the local example files that were already included in googledrive.
  
  `drive_example()` is deprecated in favor of these accessors for example files:
  - Plural forms:`drive_examples_remote()`, `drive_examples_local()`
  - Singular forms: `drive_example_remote()`, `drive_example_local()`

* `drive_read_string()` and `drive_read_raw()` are new functions that read the
  content of a Drive file directly into R, skipping the step of downloading to a
  local file (#81).

* `drive_reveal(what = "property_name")` now works for any property found in
  the file metadata stored in the `drive_resource` column. The new column is
  also simplified in more cases now, e.g. to `character` or `logical`. If the
  `property_name` suggests it's a date-time, we return `POSIXct`.

* We've modernized the mechanisms by which the `dribble` class is (or is not)
  retained by various data frame operations.
  This boils down to updating or adding methods used by the base, dplyr,
  pillar/tibble, and vctrs packages.
  
  We focus on compatibility with dplyr >= 1.0.0, which was released a year ago.
  googledrive only Suggests dplyr, so all this really means is that `dribble`
  manipulation via dplyr now works best with dplyr >= 1.0.0.

* The `drive_id` S3 class is now implemented more fully, using the vctrs
  package (#93, #364):

  - The `drive_id` class will persist after mundane operations, like subsetting.
  - You can no longer put strings that are obviously invalid into a `drive_id`
    object.
  - The `id` column of a `dribble` is now an instance of `drive_id`.

## Dependency changes

cli, lifecycle, and withr are new in Imports.

pillar and vctrs are new in Imports, but were already indirect hard dependencies via tibble.

mockr is new in Suggests.

curl moves from Imports to Suggests, but remains an indirect hard dependency.

# googledrive 1.0.1

Patch release to modify a test for compatibility with an upcoming release of gargle.

`drive_share()` gains awareness of the `"fileOrganizer"` role (#302).

Better handling of filenames that include characters that have special meaning in a regular expression (#292).

`drive_find()` explicitly checks for and eliminates duplicate records for a file ID, guarding against repetition in the paginated results returned by the API. It would seem that this should never happen, but there is some indication that it does. (#272, #277, #279, #281)

`drive_share_anyone()` is a new convenience wrapper that makes a file readable by "anyone with a link".

`as_tibble()` method for `dribble` objects now passes `...` through, which could apply, for example, to tibble's `.name_repair` argument.

# googledrive 1.0.0

The release of version 1.0.0 marks two events:

  * The overall design of googledrive has survived ~2 years on CRAN, with very little need for change. The API and feature set is fairly stable.
  * There are changes in the auth interface that are not backwards compatible.
  
There is also new functionality that makes it less likely you'll create multiple files with the same name, without actually meaning to.

## Auth from gargle

googledrive's auth functionality now comes from the [gargle package](https://gargle.r-lib.org), which provides R infrastructure to work with Google APIs, in general. The same transition is happening in several other packages, such as [bigrquery](https://bigrquery.r-dbi.org) and [gmailr](https://gmailr.r-lib.org). This makes user interfaces more consistent and makes two new token flows available in googledrive:

  * Application Default Credentials
  * Service account tokens from the metadata server available to VMs running on GCE
  
Where to learn more:
  
  * Help for [`drive_auth()`](https://googledrive.tidyverse.org/reference/drive_auth.html) *all that most users need*
  * *details for more advanced users*
    - Bring your own OAuth app or API key *article no longer available*
    - [How to get your own API credentials](https://gargle.r-lib.org/articles/get-api-credentials.html) 
    - [Non-interactive auth](https://gargle.r-lib.org/articles/non-interactive-auth.html)
    - [Auth when using R in the browser](https://gargle.r-lib.org/articles/auth-from-web.html)
    - [How gargle gets tokens](https://gargle.r-lib.org/articles/how-gargle-gets-tokens.html)
    - [Managing tokens securely](https://gargle.r-lib.org/articles/articles/managing-tokens-securely.html)

### Changes that a user will notice

OAuth2 tokens are now cached at the user level, by default, instead of in `.httr-oauth` in the current project. We recommend that you delete any vestigial `.httr-oauth` files lying around your googledrive projects and re-authorize googledrive, i.e. get a new token, stored in the new way.

googledrive uses a new OAuth "app", owned by a verified Google Cloud Project entitled "Tidyverse API Packages", which is the project name you will see on the OAuth consent screen. See our new [Privacy Policy](https://www.tidyverse.org/google_privacy_policy/) for details.

The local OAuth2 token key-value store now incorporates the associated Google user when indexing, which makes it easier to switch between Google identities.

The arguments and usage of `drive_auth()` have changed.

  * Previous signature (v0.1.3 and earlier)
  
    ``` r  
    drive_auth(
      oauth_token = NULL,                       # use `token` now
      service_token = NULL,                     # use `path` now
      reset = FALSE,                            
      cache = getOption("httr_oauth_cache"),
      use_oob = getOption("httr_oob_default"),
      verbose = TRUE
    )
    ```
  
  * Current signature (>= v1.0.0)
  
    ``` r
    drive_auth(
      email = gargle::gargle_oauth_email(),             # NEW!
      path = NULL,                                      # was `service_token`
      scopes = "https://www.googleapis.com/auth/drive", # NEW!
      cache = gargle::gargle_oauth_cache(),
      use_oob = gargle::gargle_oob_default(),
      token = NULL                                      # was `oauth_token`
    )
    ```

For full details see the resources listed in *Where to learn more* above. The change that probably affects the most code is the way to provide a service account token:
  - Previously: `drive_auth(service_token = "/path/to/your/service-account-token.json")` (v0.1.3 and earlier)
  - Now: `drive_auth(path = "/path/to/your/service-account-token.json")` (>= v1.0.0)

Auth configuration has also changed:

  * `drive_auth_configure()` is a variant of the now-deprecated `drive_auth_config()` whose explicit and only job is to *set* aspects of the configuration, i.e. the OAuth app or API key.
    - Use `drive_oauth_app()` (new) and `drive_api_key()` to *retrieve* a user-configured app or API key, if such exist.
    - These functions no longer return built-in auth assets, although built-in assets still exist and are used in the absence of user configuration.
  * `drive_deauth()` is how you go into a de-authorized state, i.e. send an API key in lieu of a token.
  
`drive_has_token()` is a new helper that simply reports whether a token is in place, without triggering the auth flow.

There are other small changes to the low-level developer-facing API:

  - `generate_request()` has been renamed to `request_generate()`.
  - `make_request()` had been renamed to `request_make()` and is a very thin wrapper around `gargle::request_make()` that only adds googledrive's user agent.
  - `build_request()` has been removed. If you can't do what you need with `request_generate()`, use `gargle::request_develop()` or `gargle::request_build()` directly.
  - `process_response()` has been removed. Instead, use `gargle::response_process(response)`, as we do inside googledrive.
  
## `overwrite = NA / TRUE / FALSE` and `drive_put()`

Google Drive doesn't impose a 1-to-1 relationship between files and filepaths, the way your local file system does. Therefore, when working via the Drive API (instead of in the browser), it's fairly easy to create multiple Drive files with the same name or filepath, without actually meaning to. This is perfectly valid on Drive, which identifies file by ID, but can be confusing and undesirable for humans.

googledrive v1.0.0 offers some new ways to fight this:

  * All functions that create a new item or rename/move an existing item have
    gained an `overwrite` argument.
  * `drive_put()` is a new convenience wrapper that figures out whether to call
    `drive_upload()` or `drive_update()`.
    
Changes inspired by #230.

### `overwrite = NA / TRUE / FALSE`

These functions gain an `overwrite` argument:

  * `drive_create()` *this whole function is new*
  * `drive_cp()`
  * `drive_mkdir()`
  * `drive_mv()`
  * `drive_rename()`
  * `drive_upload()`
  
The default of `overwrite = NA` corresponds to the current behaviour, which is to "Just. Do. It.", i.e. to not consider pre-existing files at all.

`overwrite = TRUE` requests to move a pre-existing file at the target filepath to the trash, prior to creating the new item. If 2 or more files are found, an error is thrown, because it's not clear which one(s) to trash.

`overwrite = FALSE` means the new item will only be created if there is no pre-existing file at that filepath.

Existence checks based on filepath (or name) can be expensive. This is why the default is `overwrite = NA`, in addition to backwards compatibility.

### `drive_put()`

Sometimes you have a file you will repeatedly send to Drive, i.e. the first time you run an analysis, you create the file and, when you re-run it, you update the file. Previously this was hard to express with googledrive.

`drive_put()` is useful here and refers to the HTTP verb `PUT`: create the thing if it doesn't exist or, if it does, replace its contents. A good explanation of `PUT` is [RESTful API Design -- PUT vs PATCH](https://medium.com/backticks-tildes/restful-api-design-put-vs-patch-4a061aa3ed0b).

In pseudo-code, here's the basic idea of `drive_put()`:

``` r
target_filepath <- <determined from arguments `path`, `name`, and `media`>
hits <- <get all Drive files at target_filepath>
if (no hits) {
 drive_upload(media, path, name, type, ..., verbose)
} else if (exactly 1 hit) {
 drive_update(hit, media, ..., verbose)
} else {
 ERROR
}
```

## Other changes

All functions that support `...` as a way to pass more parameters to the Drive API now have "tidy dots semantics": `!!!` is supported for splicing and `!!` can be used on the LHS of `:=`. Full docs are in [dynamic dots](https://rlang.r-lib.org/reference/dyn-dots.html).

`drive_find()` now sorts by "recency", by default.

`drive_create()` is a new function that creates a new empty file, with an optional file type specification (#258, @ianmcook). `drive_mkdir()` becomes a thin wrapper around `drive_create()`, with the file type hard-wired to "folder".

In `drive_mkdir()`, the optional parent directory is now known as `path` instead of `parent`. This is more consistent with everything else in googledrive, which became very obvious when adding `drive_create()` and the general `overwrite` functionality.

`drive_empty_trash()` now exploits the correct endpoint (as opposed to deleting individual files) and is therefore much faster (#203).

Colaboratory notebooks now have some MIME type support, in terms of the `type` argument in various functions (<https://colab.research.google.com/>). The internal table of known MIME types includes `"application/vnd.google.colab"`, which is associated with the file extension `.ipynb` and the human-oriented nickname `"colab"` (#207).

`drive_endpoints()` gains a singular friend, `drive_endpoint()` which returns exactly one endpoint. These helpers index into the internal list of Drive API endpoints with `[` and `[[`, respectively.

## Dependency changes

R 3.1 is no longer explicitly supported or tested. Our general practice is to support the current release (3.6), devel, and the 4 previous versions of R (3.5, 3.4, 3.3, 3.2). See [Which versions of R do tidyverse packages support?](https://www.tidyverse.org/blog/2019/04/r-version-support/).

gargle and magrittr are newly Imported.

rprojroot has been removed from Suggests, because we can now use a version of testthat recent enough to offer `testthat::test_path()`.

# googledrive 0.1.3

Minor patch release for compatibility with the imminent release of purrr 0.3.0.

# googledrive 0.1.2

* Internal usage of `glue::collapse()` modified to call `glue::glue_collapse()` if glue v1.3.0 or later is installed and `glue::collapse()` otherwise. Eliminates a deprecation warning emanating from glue. (#222 @jimhester)

# googledrive 0.1.1

* initial CRAN release


================================================
FILE: R/aaa.R
================================================
# environment to hold data about the Drive API
.drive <- new.env(parent = emptyenv())

.drive$translate_mime_types <-
  system.file(
    "extdata",
    "data",
    "translate_mime_types.csv",
    package = "googledrive",
    mustWork = TRUE
  ) |>
  utils::read.csv(stringsAsFactors = FALSE) |>
  as_tibble()

.drive$mime_tbl <-
  system.file(
    "extdata",
    "data",
    "mime_tbl.csv",
    package = "googledrive",
    mustWork = TRUE
  ) |>
  utils::read.csv(stringsAsFactors = FALSE) |>
  as_tibble()

.drive$files_fields <-
  system.file(
    "extdata",
    "data",
    "files_fields.csv",
    package = "googledrive",
    mustWork = TRUE
  ) |>
  utils::read.csv(stringsAsFactors = FALSE) |>
  as_tibble()

# environment to hold other data that is convenient to cache
.googledrive <- new.env(parent = emptyenv())


================================================
FILE: R/camelCase.R
================================================
# camelCase() and toCamel() taken from
# https://github.com/r-dbi/bigrquery/blob/main/R/camelCase.R

# in theory, belongs in gargle
# but then we'd need to export it and I'm not sure it's worth it
# https://github.com/r-lib/gargle

# camelCase vs snake_case policy
# ** all arguments in functions exported by googledrive shall be snake_case**
#
# HOWEVER, the Drive API is camelCase
# both wrt parameter names and many of their string values
# examples: `pageSize`, `mimeType`, `corpora = "allDrives"`
# therefore, whenever we pass `...` through, we process with toCamel()
# this means user can say `page_size = 20` and we send `pageSize = 20`
#
# we do not trumpet this snake_case to camelCase conversion in the docs,
# because many of the strings/values we handle are camelCase and we ARE not
# going to alter them
# there's too much potential for confusion
#
# at this point, snake_case to camelCase is a very quiet feature
camelCase <- function(x) {
  gsub("_(.)", "\\U\\1", x, perl = TRUE)
}

toCamel <- function(x) {
  if (is.list(x)) {
    x[] <- lapply(x, toCamel)
  }

  if (!is.null(names(x))) {
    names(x) <- camelCase(names(x))
  }

  x
}

# added later
snake_case <- function(x) {
  gsub("([a-z0-9])([A-Z])", "\\1_\\L\\2", x, perl = TRUE)
}


================================================
FILE: R/compat-dplyr.R
================================================
dribble_maybe_reconstruct <- function(data, template) {
  if (dribble_is_reconstructable(data)) {
    # in workflowsets, davis uses new_workflow_set0(x) here
    new_dribble(data)
  } else {
    # @davis tells me there can be internal-to-vctrs-or-dplyr situations where
    # reconstruction starts with a conformable list, instead of data.frame
    new_tibble0(data)
  }
}

dribble_is_reconstructable <- function(data) {
  # see above for why this is is_list() instead of is.data.frame()
  is_list(data) &&
    has_dribble_cols(data) &&
    has_dribble_coltypes(data) &&
    id_can_be_drive_id(data$id) &&
    has_drive_resource(data)
}

new_tibble0 <- function(x, ..., class = NULL) {
  # Handle the 0-column case correctly by using `new_data_frame()`.
  # This also correctly strips any attributes except `names` off `x`.
  x <- new_data_frame(x)
  tibble::new_tibble(x, nrow = nrow(x), class = class)
}


================================================
FILE: R/compat-vctrs.R
================================================
# based on https://github.com/tidymodels/workflowsets/blob/main/R/compat-vctrs.R

# ------------------------------------------------------------------------------

# `vec_restore()`
#
# Called at the end of `vec_slice()` and `vec_ptype()` after all slicing has
# been done on the proxy object.

#' @export
vec_restore.dribble <- function(x, to, ...) {
  dribble_maybe_reconstruct(x)
}

# ------------------------------------------------------------------------------

# `vec_ptype2()`
#
# When combining two dribbles together, `x` and `y` will be zero-row slices
# which should always result in a new dribble, as long as
# `df_ptype2()` can compute a common type.
#
# Combining a dribble with a tibble/data.frame will only ever happen if
# the user calls `vec_c()` or `vec_rbind()` with one of each of those inputs.
# Although I could probably make this work, it feels pretty weird and exotic
# and I think that user, if they even exist, should just turn that "other"
# tibble/data.frame into a dribble first.
# So I'll follow workflowsets and not attempt to return dribble for these
# combinations.

#' @export
vec_ptype2.dribble.dribble <- function(x, y, ..., x_arg = "", y_arg = "") {
  out <- df_ptype2(x, y, ..., x_arg = x_arg, y_arg = y_arg)
  dribble_maybe_reconstruct(out)
}
#' @export
vec_ptype2.dribble.tbl_df <- function(x, y, ..., x_arg = "", y_arg = "") {
  tib_ptype2(x, y, ..., x_arg = x_arg, y_arg = y_arg)
}
#' @export
vec_ptype2.tbl_df.dribble <- function(x, y, ..., x_arg = "", y_arg = "") {
  tib_ptype2(x, y, ..., x_arg = x_arg, y_arg = y_arg)
}
#' @export
vec_ptype2.dribble.data.frame <- function(x, y, ..., x_arg = "", y_arg = "") {
  tib_ptype2(x, y, ..., x_arg = x_arg, y_arg = y_arg)
}
#' @export
vec_ptype2.data.frame.dribble <- function(x, y, ..., x_arg = "", y_arg = "") {
  tib_ptype2(x, y, ..., x_arg = x_arg, y_arg = y_arg)
}

# ------------------------------------------------------------------------------

# `vec_cast()`
#
# These methods are designed with `vec_ptype2()` in mind.
#
# Casting from one dribble to another will happen "automatically" when
# two dribbles are combined with `vec_c()`. The common type will be
# computed with `vec_ptype2()`, then each input will be `vec_cast()` to that
# common type. It should always be possible to reconstruct the dribble
# if `df_cast()` is able to cast the underlying data frames successfully.
#
# Casting a tibble or data.frame to a dribble should never happen
# automatically, because the ptype2 methods always push towards
# tibble / data.frame. Since it is so unlikely that this will be done
# correctly, we don't ever allow it.
#
# Casting a dribble to a tibble or data.frame is easy, the underlying
# vctrs function does the work for us. This is used when doing
# `vec_c(<dribble>, <tbl>)`, as the `vec_ptype2()` method will compute
# a common type of tibble, and then each input will be cast to tibble.

#' @export
vec_cast.dribble.dribble <- function(x, to, ..., x_arg = "", to_arg = "") {
  out <- df_cast(x, to, ..., x_arg = x_arg, to_arg = to_arg)
  dribble_maybe_reconstruct(out)
}
#' @export
vec_cast.dribble.tbl_df <- function(x, to, ..., x_arg = "", to_arg = "") {
  stop_incompatible_cast_dribble(x, to, x_arg = x_arg, to_arg = to_arg)
}
#' @export
vec_cast.tbl_df.dribble <- function(x, to, ..., x_arg = "", to_arg = "") {
  tib_cast(x, to, ..., x_arg = x_arg, to_arg = to_arg)
}
#' @export
vec_cast.dribble.data.frame <- function(x, to, ..., x_arg = "", to_arg = "") {
  stop_incompatible_cast_dribble(x, to, x_arg = x_arg, to_arg = to_arg)
}
#' @export
vec_cast.data.frame.dribble <- function(x, to, ..., x_arg = "", to_arg = "") {
  df_cast(x, to, ..., x_arg = x_arg, to_arg = to_arg)
}

# ------------------------------------------------------------------------------

stop_incompatible_cast_dribble <- function(x, to, ..., x_arg, to_arg) {
  details <- "Can't cast to a <dribble> because the resulting structure is likely invalid."
  stop_incompatible_cast(
    x,
    to,
    x_arg = x_arg,
    to_arg = to_arg,
    details = details
  )
}


================================================
FILE: R/deprecated.R
================================================
#' Deprecated googledrive functions
#'
#' @description
#' `r lifecycle::badge("deprecated")`
#'
#' @section `drive_auth_config()`:
#'
#' This function is defunct.
#' * Use [drive_auth_configure()] to configure your own OAuth client or API key.
#' * Use [drive_deauth()] to go into a de-authorized state.
#' * Use [drive_oauth_client()] to retrieve a user-configured client, if it
#'   exists.
#' * Use [drive_api_key()] to retrieve a user-configured API key, if it exists.
#'
#' @section `drive_oauth_app()`:
#'
#' In light of the new [gargle::gargle_oauth_client()] constructor and class of
#' the same name, `drive_oauth_app()` is being replaced by
#' [drive_oauth_client()].
#'
#' @section `drive_example()`:
#'
#' This function is defunct. Access example files with [drive_examples_local()],
#' [drive_example_local()], [drive_examples_remote()], and
#' [drive_example_remote()].
#'
#' @keywords internal
#' @name googledrive-deprecated
NULL

#' @rdname googledrive-deprecated
#' @inheritParams drive_auth_configure
#' @export
drive_auth_config <- function(active, app, path, api_key) {
  lifecycle::deprecate_stop(
    "1.0.0",
    "drive_auth_config()",
    details = c(
      "Use `drive_auth_configure()` to configure your own OAuth client or API key.",
      "Use `drive_deauth()` to go into a de-authorized state.",
      "Use `drive_oauth_client()` to retrieve a user-configured client, if it exists.",
      "Use `drive_api_key()` to retrieve a user-configured API key, if it exists."
    )
  )
}

#' @rdname googledrive-deprecated
#' @export
drive_oauth_app <- function() {
  lifecycle::deprecate_warn(
    "2.1.0",
    "drive_oauth_app()",
    "drive_oauth_client()"
  )
  drive_oauth_client()
}

#' @rdname googledrive-deprecated
#' @export
drive_example <- function(path = NULL) {
  lifecycle::deprecate_stop(
    "2.0.0",
    what = "drive_example()",
    with = I("`drive_examples_local()` or `drive_example_local()`")
  )
}


================================================
FILE: R/dribble.R
================================================
#' dribble object
#'
#' @description googledrive stores the metadata for one or more Drive files or
#'   shared drives as a `dribble`. It is a "Drive
#'   [tibble][tibble::tibble-package]" with one row per file or shared drive
#'   and, at a minimum, these columns:
#'   * `name`: a character column containing file or shared drive names
#'   * `id`: a character column of file or shared drive ids
#'   * `drive_resource`: a list-column, each element of which is either a
#'   [Files resource](https://developers.google.com/drive/api/v3/reference/files#resource-representations)
#'   or a [Drives resource](https://developers.google.com/drive/api/v3/reference/drives#resource-representations)
#'   object. Note there is no guarantee that all documented fields are always
#'   present. We do check if the `kind` field is present and equal to one of
#'   `drive#file` or `drive#drive`.
#'
#' @description The `dribble` format is handy because it exposes the file name,
#'   which is good for humans, but keeps it bundled with the file's unique id
#'   and other metadata, which are needed for API calls.
#'
#' @description In general, the `dribble` class will be retained even after
#'   manipulation, as long as the required variables are present and of the
#'   correct type. This works best for manipulations via the dplyr and vctrs
#'   packages.
#'
#' @name dribble
#' @seealso [as_dribble()]
NULL

# implementing dribble as advised here:
# https://github.com/hadley/adv-r/blob/master/S3.Rmd

new_dribble <- function(x) {
  # new_tibble0() strips attributes
  out <- structure(
    new_tibble0(x),
    class = c("dribble", "tbl_df", "tbl", "data.frame")
  )
  out$id <- new_drive_id(unclass(out$id))
  out
}

validate_dribble <- function(x) {
  stopifnot(inherits(x, "dribble"))

  if (!has_dribble_cols(x)) {
    missing_cols <- setdiff(dribble_cols, colnames(x))
    drive_abort(c(
      "Invalid {.cls dribble}. \\
       {cli::qty(length(missing_cols))}{?This/These} required column{?s} \\
       {?is/are} missing:",
      bulletize(gargle_map_cli(missing_cols, template = "{.code <<x>>}"))
    ))
  }

  if (!has_dribble_coltypes(x)) {
    mistyped_cols <- dribble_cols[!dribble_coltypes_ok(x)]
    drive_abort(c(
      "Invalid {.cls dribble}. \\
       {cli::qty(length(mistyped_cols))}{?This/These} column{?s} {?has/have} \\
       the wrong type:",
      bulletize(gargle_map_cli(mistyped_cols, template = "{.code <<x>>}"))
    ))
  }

  # TODO: should I make sure there are no NAs in the id column?
  # let's wait and see if we ever experience any harm from NOT checking this
  # also, that feels more like something to enforce by creating a proper
  # S3 vctr for Drive file ids and it might be odd to make NAs unacceptable

  if (!has_drive_resource(x)) {
    # \u00a0 is a nonbreaking space
    drive_abort(c(
      'Invalid {.cls dribble}. Can\'t confirm \\
       {.code kind\u00a0=\u00a0"drive#file"} or \\
       {.code kind\u00a0=\u00a0"drive#drive"} \\
       for all elements of the {.code drive_resource} column.'
    ))
  }
  x
}

dribble <- function(x = NULL) {
  x <- x %||%
    list(
      name = character(),
      id = character(),
      drive_resource = list()
    )
  validate_dribble(new_dribble(x))
}

#' @export
`[.dribble` <- function(x, i, j, drop = FALSE) {
  dribble_maybe_reconstruct(NextMethod())
}

#' @export
`names<-.dribble` <- function(x, value) {
  dribble_maybe_reconstruct(NextMethod())
}

#' @export
tbl_sum.dribble <- function(x) {
  orig <- NextMethod()
  c("A dribble" = unname(orig))
}

#' @export
as_tibble.dribble <- function(x, ...) {
  as_tibble(new_tibble0(x), ...)
}

dribble_cols <- c("name", "id", "drive_resource")

has_dribble_cols <- function(x) {
  all(dribble_cols %in% colnames(x))
}

dribble_coltypes_ok <- function(x) {
  c(
    name = is.character(x$name),
    id = is.character(x$id),
    drive_resource = inherits(x$drive_resource, "list")
  )
}

has_dribble_coltypes <- function(x) {
  all(dribble_coltypes_ok(x))
}

id_can_be_drive_id <- function(x) {
  all(is_valid_drive_id(x))
}

has_drive_resource <- function(x) {
  kind <- map_chr(x$drive_resource, "kind", .default = NA_character_)
  # TODO: remove `drive#teamDrive` here, when possible
  all(
    !is.na(kind) & kind %in% c("drive#file", "drive#drive", "drive#teamDrive")
  )
}

#' Coerce to a `dribble`
#'
#' @description
#' Converts various representations of Google Drive files into a [`dribble`],
#' the object used by googledrive to hold Drive file metadata. Files can be
#' specified via:
#'   * File path. File name is an important special case.
#'   * File id. Mark with [as_id()] to distinguish from file path.
#'   * Data frame or [`dribble`]. Once you've successfully used googledrive to
#'     identify the files of interest, you'll have a [`dribble`]. Pass it into
#'     downstream functions.
#'   * List representing [Files resource](https://developers.google.com/drive/api/v3/reference/files)
#'     objects. Mostly for internal use.
#'
#' This is a generic function.
#'
#' For maximum clarity, get your files into a [`dribble`] (or capture file id)
#' as early as possible. When specifying via path, it's best to include the
#' trailing slash when you're targeting a folder. If you want the folder `foo`,
#' say `foo/`, not `foo`.
#'
#' Some functions, such as [drive_cp()], [drive_mkdir()], [drive_mv()], and
#' [drive_upload()], can accept the new file or folder name as the last part of
#' `path`, when `name` is not given. But if you say `a/b/c` (no trailing slash)
#' and a folder `a/b/c/` already exists, it's unclear what you want. A file
#' named `c` in `a/b/` or a file with default name in `a/b/c/`? You get an
#' error and must make your intent clear.
#'
#' @param x A vector of Drive file paths, a vector of file ids marked
#'   with [as_id()], a list of Files Resource objects, or a suitable data
#'   frame.
#' @param ... Other arguments passed down to methods. (Not used.)
#' @export
#' @examplesIf drive_has_token()
#' # create some files for us to re-discover by name or filepath
#' alfa <- drive_create("alfa", type = "folder")
#' bravo <- drive_create("bravo", path = alfa)
#'
#' # as_dribble() can work with file names or paths
#' as_dribble("alfa")
#' as_dribble("bravo")
#' as_dribble("alfa/bravo")
#' as_dribble(c("alfa", "alfa/bravo"))
#'
#' # specify the file id (substitute a real file id of your own!)
#' # as_dribble(as_id("0B0Gh-SuuA2nTOGZVTXZTREgwZ2M"))
#'
#' # Clean up
#' drive_find("alfa") |> drive_rm()
as_dribble <- function(x, ...) UseMethod("as_dribble")

#' @export
as_dribble.default <- function(x, ...) {
  drive_abort(
    "
    Don't know how to coerce an object of class {.cls {class(x)}} into \\
    a {.cls dribble}."
  )
}

#' @export
as_dribble.NULL <- function(x, ...) dribble()

#' @export
as_dribble.character <- function(x, ...) {
  with_drive_quiet(drive_get(path = x))
}

#' @export
as_dribble.drive_id <- function(x, ...) drive_get(id = x)

#' @export
as_dribble.data.frame <- function(x, ...) validate_dribble(new_dribble(x))

#' @export
as_dribble.list <- function(x, ...) {
  if (length(x) == 0) {
    return(dribble())
  }

  required_nms <- c("name", "id", "kind")
  stopifnot(map_lgl(x, ~ all(required_nms %in% names(.x))))

  as_dribble(
    tibble(
      name = map_chr(x, "name"),
      id = map_chr(x, "id"),
      drive_resource = x
    )
  )
}

# used across several functions that create a file or modify "parentage"
# processes a putative parent folder or shared drive
as_parent <- function(d) {
  in_var <- deparse(substitute(d))
  if (is_path(d)) {
    d <- append_slash(d)
  }
  d <- as_dribble(d)
  # wording chosen to work for folder and shared drive
  invalid_parent <- "Parent specified via {.arg {in_var}} is invalid:"
  if (no_file(d)) {
    drive_abort(c(invalid_parent, x = "Does not exist."))
  }
  if (!single_file(d)) {
    drive_abort(c(
      invalid_parent,
      x = "Doesn't uniquely identify exactly one folder or shared drive."
    ))
  }
  if (is_folder_shortcut(d)) {
    drive_bullets(c(
      i = "Parent specified via {.arg {in_var}} is a shortcut; resolving to \\
           its target folder"
    ))
    d <- shortcut_resolve(d)
  }
  if (!is_parental(d)) {
    drive_abort(c(
      invalid_parent,
      x = "Is neither a folder nor a shared drive."
    ))
  }
  d
}

#' Check facts about a dribble
#'
#' Sometimes you need to check things about a [`dribble`]` or about the files it
#' represents, such as:
#'   * Is it even a dribble?
#'   * Size: Does the dribble hold exactly one file? At least one file? No file?
#'   * File type: Is this file a folder?
#'   * File ownership and access: Is it mine? Published? Shared?
#'
#' @name dribble-checks
#' @param d A [`dribble`].
#' @examplesIf drive_has_token()
#' ## most of us have multiple files or folders on Google Drive
#' d <- drive_find()
#' is_dribble(d)
#' no_file(d)
#' single_file(d)
#' some_files(d)
#'
#' # this will error
#' # confirm_single_file(d)
#'
#' confirm_some_files(d)
#' is_folder(d)
#' is_mine(d)
NULL

#' @export
#' @rdname dribble-checks
is_dribble <- function(d) {
  inherits(d, "dribble")
}

#' @export
#' @rdname dribble-checks
no_file <- function(d) {
  stopifnot(inherits(d, "dribble"))
  nrow(d) == 0
}

#' @export
#' @rdname dribble-checks
single_file <- function(d) {
  stopifnot(inherits(d, "dribble"))
  nrow(d) == 1
}

#' @export
#' @rdname dribble-checks
some_files <- function(d) {
  stopifnot(inherits(d, "dribble"))
  nrow(d) > 0
}

#' @export
#' @rdname dribble-checks
confirm_dribble <- function(d) {
  if (!is_dribble(d)) {
    drive_abort("Input is not a {.cls dribble}.")
  }
  d
}

#' @export
#' @rdname dribble-checks
confirm_single_file <- function(d) {
  in_var <- deparse(substitute(d))
  if (no_file(d)) {
    drive_abort("{.arg {in_var}} does not identify at least one Drive file.")
  }
  if (!single_file(d)) {
    drive_abort("{.arg {in_var}} identifies more than one Drive file.")
  }
  d
}

#' @export
#' @rdname dribble-checks
confirm_some_files <- function(d) {
  in_var <- deparse(substitute(d))
  if (no_file(d)) {
    drive_abort("{.arg {in_var}} does not identify at least one Drive file.")
  }
  d
}

#' @export
#' @rdname dribble-checks
is_folder <- function(d) {
  stopifnot(inherits(d, "dribble"))
  map_chr(d$drive_resource, "mimeType", .default = NA) ==
    "application/vnd.google-apps.folder"
}

#' @export
#' @rdname dribble-checks
is_shortcut <- function(d) {
  stopifnot(inherits(d, "dribble"))
  map_chr(d$drive_resource, "mimeType", .default = NA) ==
    "application/vnd.google-apps.shortcut"
}

#' @export
#' @rdname dribble-checks
is_folder_shortcut <- function(d) {
  stopifnot(inherits(d, "dribble"))
  is_shortcut(d) &
    (map_chr(
      d$drive_resource,
      c("shortcutDetails", "targetMimeType"),
      .default = ""
    ) ==
      "application/vnd.google-apps.folder")
}

#' @export
#' @rdname dribble-checks
is_native <- function(d) {
  stopifnot(inherits(d, "dribble"))
  d <- promote(d, "mimeType")
  grepl("application/vnd.google-apps.", d$mimeType) & !is_folder(d)
}

#' @export
#' @rdname dribble-checks
is_parental <- function(d) {
  stopifnot(inherits(d, "dribble"))
  kind <- map_chr(d$drive_resource, "kind")
  mime_type <- map_chr(d$drive_resource, "mimeType", .default = "")
  # TODO: remove `drive#teamDrive` here, when possible
  kind == "drive#teamDrive" |
    kind == "drive#drive" |
    mime_type == "application/vnd.google-apps.folder"
}

#' @export
#' @rdname dribble-checks
## TO DO: do I need to do anything about shared drives here?
is_mine <- function(d) {
  stopifnot(inherits(d, "dribble"))
  map_lgl(d$drive_resource, list("owners", 1, "me"))
}

#' @export
#' @rdname dribble-checks
is_shared_drive <- function(d) {
  stopifnot(inherits(d, "dribble"))
  map_chr(d$drive_resource, "kind") == "drive#drive"
}


================================================
FILE: R/drive_about.R
================================================
#' Get info on Drive capabilities
#'
#' Gets information about the user, the user's Drive, and system capabilities.
#' This function mostly exists to power [drive_user()], which extracts the most
#' useful information (the information on current user) and prints it nicely.
#'
#' @seealso Wraps the `about.get` endpoint:
#'   * <https://developers.google.com/drive/api/v3/reference/about/get>
#'
#' @return A list representation of a Drive
#'   [about resource](https://developers.google.com/drive/api/v3/reference/about)
#' @export
#'
#' @examplesIf drive_has_token()
#' drive_about()
#'
#' # explore the export formats available for Drive files, by MIME type
#' about <- drive_about()
#' about[["exportFormats"]] |>
#'   purrr::map(unlist)
drive_about <- function() {
  request <- request_generate(
    endpoint = "drive.about.get",
    params = list(fields = "*")
  )
  response <- request_make(request)
  gargle::response_process(response)
}


================================================
FILE: R/drive_auth.R
================================================
# This file is the interface between googledrive and the
# auth functionality in gargle.

# Initialization happens in .onLoad()
.auth <- NULL

## The roxygen comments for these functions are mostly generated from data
## in this list and template text maintained in gargle.
gargle_lookup_table <- list(
  PACKAGE = "googledrive",
  YOUR_STUFF = "your Drive files",
  PRODUCT = "Google Drive",
  API = "Drive API",
  PREFIX = "drive"
)

#' Authorize googledrive
#'
#' @eval gargle:::PREFIX_auth_description(gargle_lookup_table)
#' @eval gargle:::PREFIX_auth_details(gargle_lookup_table)
#' @eval gargle:::PREFIX_auth_params()
#'
#' @param scopes One or more API scopes. Each scope can be specified in full or,
#'   for Drive API-specific scopes, in an abbreviated form that is recognized by
#'   [drive_scopes()]:
#'   * "drive" = "https://www.googleapis.com/auth/drive" (the default)
#'   * "full" = "https://www.googleapis.com/auth/drive" (same as "drive")
#'   * "drive.readonly" = "https://www.googleapis.com/auth/drive.readonly"
#'   * "drive.file" = "https://www.googleapis.com/auth/drive.file"
#'   * "drive.appdata" = "https://www.googleapis.com/auth/drive.appdata"
#'   * "drive.metadata" = "https://www.googleapis.com/auth/drive.metadata"
#'   * "drive.metadata.readonly" = "https://www.googleapis.com/auth/drive.metadata.readonly"
#'   * "drive.photos.readonly" = "https://www.googleapis.com/auth/drive.photos.readonly"
#'   * "drive.scripts" = "https://www.googleapis.com/auth/drive.scripts
#'
#'   See <https://developers.google.com/drive/api/guides/api-specific-auth> for
#'   details on the permissions for each scope.
#'
#' @family auth functions
#' @export
#'
#' @examplesIf rlang::is_interactive()
#' # load/refresh existing credentials, if available
#' # otherwise, go to browser for authentication and authorization
#' drive_auth()
#'
#' # see user associated with current token
#' drive_user()
#'
#' # force use of a token associated with a specific email
#' drive_auth(email = "jenny@example.com")
#' drive_user()
#'
#' # force the OAuth web dance
#' drive_auth(email = NA)
#'
#' # use a 'read only' scope, so it's impossible to edit or delete files
#' drive_auth(scopes = "drive.readonly")
#'
#' # use a service account token
#' drive_auth(path = "foofy-83ee9e7c9c48.json")
drive_auth <- function(
  email = gargle::gargle_oauth_email(),
  path = NULL,
  subject = NULL,
  scopes = "drive",
  cache = gargle::gargle_oauth_cache(),
  use_oob = gargle::gargle_oob_default(),
  token = NULL
) {
  gargle::check_is_service_account(path, hint = "drive_auth_configure")
  scopes <- drive_scopes(scopes)
  env_unbind(.googledrive, "root_folder")

  # If `token` is not `NULL`, it's much better to error noisily now, before
  # getting silently swallowed by `token_fetch()`.
  force(token)

  cred <- gargle::token_fetch(
    scopes = scopes,
    client = drive_oauth_client() %||% gargle::tidyverse_client(),
    email = email,
    path = path,
    subject = subject,
    package = "googledrive",
    cache = cache,
    use_oob = use_oob,
    token = token
  )
  if (!inherits(cred, "Token2.0")) {
    drive_abort(c(
      "Can't get Google credentials.",
      "i" = "Are you running {.pkg googledrive} in a non-interactive session? \\
             Consider:",
      "*" = "Call {.fun drive_deauth} to prevent the attempt to get credentials.",
      "*" = "Call {.fun drive_auth} directly with all necessary specifics.",
      "i" = "See gargle's \"Non-interactive auth\" vignette for more details:",
      "i" = "{.url https://gargle.r-lib.org/articles/non-interactive-auth.html}"
    ))
  }
  .auth$set_cred(cred)
  .auth$set_auth_active(TRUE)

  invisible()
}

#' Suspend authorization
#'
#' @eval gargle:::PREFIX_deauth_description_with_api_key(gargle_lookup_table)
#'
#' @family auth functions
#' @export
#' @examplesIf rlang::is_interactive()
#' drive_deauth()
#' drive_user()
#'
#' # in a deauth'ed state, we can still get metadata on a world-readable file
#' public_file <- drive_example_remote("chicken.csv")
#' public_file
#' # we can still download it too
#' drive_download(public_file)
drive_deauth <- function() {
  .auth$set_auth_active(FALSE)
  .auth$clear_cred()
  env_unbind(.googledrive, "root_folder")
  invisible()
}

#' Produce configured token
#'
#' @eval gargle:::PREFIX_token_description(gargle_lookup_table)
#' @eval gargle:::PREFIX_token_return()
#'
#' @family low-level API functions
#' @export
#' @examplesIf drive_has_token()
#' req <- request_generate(
#'   "drive.files.get",
#'   list(fileId = "abc"),
#'   token = drive_token()
#' )
#' req
drive_token <- function() {
  if (isFALSE(.auth$auth_active)) {
    return(NULL)
  }
  if (!drive_has_token()) {
    drive_auth()
  }
  httr::config(token = .auth$cred)
}

#' Is there a token on hand?
#'
#' @eval gargle:::PREFIX_has_token_description(gargle_lookup_table)
#' @eval gargle:::PREFIX_has_token_return()
#'
#' @family low-level API functions
#' @export
#'
#' @examples
#' drive_has_token()
drive_has_token <- function() {
  inherits(.auth$cred, "Token2.0")
}

#' Edit and view auth configuration
#'
#' @eval gargle:::PREFIX_auth_configure_description(gargle_lookup_table)
#' @eval gargle:::PREFIX_auth_configure_params()
#' @eval gargle:::PREFIX_auth_configure_return(gargle_lookup_table)
#'
#' @family auth functions
#' @export
#' @examples
#' # see and store the current user-configured OAuth client (probaby `NULL`)
#' (original_client <- drive_oauth_client())
#'
#' # see and store the current user-configured API key (probaby `NULL`)
#' (original_api_key <- drive_api_key())
#'
#' # the preferred way to configure your own client is via a JSON file
#' # downloaded from Google Developers Console
#' # this example JSON is indicative, but fake
#' path_to_json <- system.file(
#'   "extdata", "client_secret_installed.googleusercontent.com.json",
#'   package = "gargle"
#' )
#' drive_auth_configure(path = path_to_json)
#'
#' # this is also obviously a fake API key
#' drive_auth_configure(api_key = "the_key_I_got_for_a_google_API")
#'
#' # confirm the changes
#' drive_oauth_client()
#' drive_api_key()
#'
#' # restore original auth config
#' drive_auth_configure(client = original_client, api_key = original_api_key)
drive_auth_configure <- function(client, path, api_key, app = deprecated()) {
  if (lifecycle::is_present(app)) {
    lifecycle::deprecate_warn(
      "2.1.0",
      "drive_auth_configure(app)",
      "drive_auth_configure(client)"
    )
    drive_auth_configure(client = app, path = path, api_key = api_key)
  }

  if (!missing(client) && !missing(path)) {
    drive_abort(
      "Must supply exactly one of {.arg client} or {.arg path}, not both"
    )
  }
  stopifnot(missing(api_key) || is.null(api_key) || is_string(api_key))

  if (!missing(path)) {
    stopifnot(is_string(path))
    client <- gargle::gargle_oauth_client_from_json(path)
  }
  stopifnot(
    missing(client) ||
      is.null(client) ||
      inherits(client, "gargle_oauth_client")
  )

  if (!missing(client) || !missing(path)) {
    .auth$set_client(client)
  }

  if (!missing(api_key)) {
    .auth$set_api_key(api_key)
  }

  invisible(.auth)
}

#' @export
#' @rdname drive_auth_configure
drive_api_key <- function() {
  .auth$api_key
}

#' @export
#' @rdname drive_auth_configure
drive_oauth_client <- function() {
  .auth$client
}

#' Produce scopes specific to the Drive API
#'
#' When called with no arguments, `drive_scopes()` returns a named character vector
#' of scopes associated with the Drive API. If `drive_scopes(scopes =)` is given,
#' an abbreviated entry such as `"drive.readonly"` is expanded to a full scope
#' (`"https://www.googleapis.com/auth/drive.readonly"` in this case).
#' Unrecognized scopes are passed through unchanged.
#'
#' @inheritParams drive_auth
#'
#' @seealso <https://developers.google.com/drive/api/guides/api-specific-auth> for details on
#'   the permissions for each scope.
#' @returns A character vector of scopes.
#' @family auth functions
#' @export
#' @examples
#' drive_scopes("full")
#' drive_scopes("drive.readonly")
#' drive_scopes()
drive_scopes <- function(scopes = NULL) {
  if (is.null(scopes)) {
    drive_api_scopes
  } else {
    resolve_scopes(user_scopes = scopes, package_scopes = drive_api_scopes)
  }
}

drive_api_scopes <- c(
  drive = "https://www.googleapis.com/auth/drive",
  full = "https://www.googleapis.com/auth/drive",
  drive.readonly = "https://www.googleapis.com/auth/drive.readonly",
  drive.file = "https://www.googleapis.com/auth/drive.file",
  drive.appdata = "https://www.googleapis.com/auth/drive.appdata",
  drive.metadata = "https://www.googleapis.com/auth/drive.metadata",
  drive.metadata.readonly = "https://www.googleapis.com/auth/drive.metadata.readonly",
  drive.photos.readonly = "https://www.googleapis.com/auth/drive.photos.readonly",
  drive.scripts = "https://www.googleapis.com/auth/drive.scripts"
)

resolve_scopes <- function(user_scopes, package_scopes) {
  m <- match(user_scopes, names(package_scopes))
  ifelse(is.na(m), user_scopes, package_scopes[m])
}

# unexported helpers that are nice for internal use ----
drive_auth_internal <- function(account = c("docs", "testing"), scopes = NULL) {
  account <- match.arg(account)
  can_decrypt <- gargle::secret_has_key("GOOGLEDRIVE_KEY")
  online <- !is.null(curl::nslookup("drive.googleapis.com", error = FALSE))
  if (!can_decrypt || !online) {
    drive_abort(
      message = c(
        "Auth unsuccessful:",
        if (!can_decrypt) {
          c("x" = "Can't decrypt the {.field {account}} service account token.")
        },
        if (!online) {
          c(
            "x" = "We don't appear to be online. Or maybe the Drive API is down?"
          )
        }
      ),
      class = "googledrive_auth_internal_error",
      can_decrypt = can_decrypt,
      online = online
    )
  }

  if (!is_interactive()) {
    local_drive_quiet()
  }
  filename <- glue("googledrive-{account}.json")
  # TODO: revisit when I do PKG_scopes()
  # https://github.com/r-lib/gargle/issues/103
  scopes <- scopes %||% "https://www.googleapis.com/auth/drive"
  drive_auth(
    scopes = scopes,
    path = gargle::secret_decrypt_json(
      system.file("secret", filename, package = "googledrive"),
      "GOOGLEDRIVE_KEY"
    )
  )
  print(drive_user())
  invisible(TRUE)
}

drive_auth_docs <- function(scopes = NULL) {
  drive_auth_internal("docs", scopes = scopes)
}

drive_auth_testing <- function(scopes = NULL) {
  drive_auth_internal("testing", scopes = scopes)
}

local_deauth <- function(env = parent.frame()) {
  original_cred <- .auth$get_cred()
  original_auth_active <- .auth$auth_active
  drive_bullets(c("i" = "Going into deauthorized state."))
  withr::defer(
    drive_bullets(c("i" = "Restoring previous auth state.")),
    envir = env
  )
  withr::defer(
    {
      .auth$set_cred(original_cred)
      .auth$set_auth_active(original_auth_active)
    },
    envir = env
  )
  drive_deauth()
}


================================================
FILE: R/drive_browse.R
================================================
#' Retrieve Drive file links
#'
#' Returns the `"webViewLink"` for one or more files, which is the "link for
#' opening the file in a relevant Google editor or viewer in a browser".
#'
#' @template file-plural
#'
#' @return Character vector of file hyperlinks.
#' @export
#' @examplesIf drive_has_token()
#' # get a few files into a dribble
#' three_files <- drive_find(n_max = 3)
#'
#' # get their browser links
#' drive_link(three_files)
drive_link <- function(file) {
  file <- as_dribble(file)
  links <- map_chr(
    file$drive_resource,
    "webViewLink",
    .default = NA_character_
  )
  # no documented, programmatic way to get browser links for shared drives
  # but this seems to work ... I won't document it either, though
  sd <- is_shared_drive(file)
  links[sd] <- glue(
    "https://drive.google.com/drive/folders/{id}",
    id = as_id(file)[sd]
  )
  links
}

#' Visit Drive file in browser
#'
#' Visits a file on Google Drive in your default browser.
#'
#' @template file-singular
#'
#' @return Character vector of file hyperlinks, from [drive_link()], invisibly.
#' @export
#' @examplesIf drive_has_token() && rlang::is_interactive()
#' drive_find(n_max = 1) |> drive_browse()
drive_browse <- function(file = .Last.value) {
  file <- as_dribble(file)
  links <- drive_link(file)
  if (!interactive() || no_file(file)) {
    return(invisible(links))
  }
  if (!single_file(file)) {
    drive_bullets(c("v" = "Browsing the first file of {nrow(file)}."))
  }
  utils::browseURL(links[1])
  invisible(links)
}


================================================
FILE: R/drive_cp.R
================================================
#' Copy a Drive file
#'
#' Copies an existing Drive file into a new file id.
#'
#' @seealso Wraps the `files.copy` endpoint:
#'   * <https://developers.google.com/drive/api/v3/reference/files/copy>
#'
#' @template file-singular
#' @eval param_path(
#'   thing = "new file",
#'   default_notes = "By default, the new file has the same parent folder as the
#'      source file."
#' )
#' @eval param_name(
#'   thing = "file",
#'   default_notes = "Defaults to \"Copy of `FILE-NAME`\"."
#' )
#' @template dots-metadata
#' @template overwrite
#' @template verbose
#' @eval return_dribble()
#' @export
#'
#' @examplesIf drive_has_token()
#' # Target one of the official example files
#' (src_file <- drive_example_remote("chicken.txt"))
#'
#' # Make a "Copy of" copy in your My Drive
#' cp1 <- drive_cp(src_file)
#'
#' # Make an explicitly named copy, in a different folder, and star it.
#' # The starring is an example of providing metadata via `...`.
#' # `starred` is not an actual argument to `drive_cp()`,
#' # it just gets passed through to the API.
#' folder <- drive_mkdir("drive-cp-folder")
#' cp2 <- drive_cp(
#'   src_file,
#'   path = folder,
#'   name = "chicken-cp.txt",
#'   starred = TRUE
#' )
#' drive_reveal(cp2, "starred")
#'
#' # `overwrite = FALSE` errors if file already exists at target filepath
#' # THIS WILL ERROR!
#' # drive_cp(src_file, name = "Copy of chicken.txt", overwrite = FALSE)
#'
#' # `overwrite = TRUE` moves an existing file to trash, then proceeds
#' cp3 <- drive_cp(src_file, name = "Copy of chicken.txt", overwrite = TRUE)
#'
#' # Delete all of our copies and the new folder!
#' drive_rm(cp1, cp2, cp3, folder)
#'
#' # Target an official example file that's a csv file
#' (csv_file <- drive_example_remote("chicken.csv"))
#'
#' # copy AND AT THE SAME TIME convert it to a Google Sheet
#' chicken_sheet <- drive_cp(
#'   csv_file,
#'   name = "chicken-sheet-copy",
#'   mime_type = drive_mime_type("spreadsheet")
#' )
#' # is it really a Google Sheet?
#' drive_reveal(chicken_sheet, "mime_type")$mime_type
#'
#' # go see the new Sheet in the browser
#' # drive_browse(chicken_sheet)
#'
#' # Clean up
#' drive_rm(chicken_sheet)
drive_cp <- function(
  file,
  path = NULL,
  name = NULL,
  ...,
  overwrite = NA,
  verbose = deprecated()
) {
  warn_for_verbose(verbose)

  file <- as_dribble(file)
  file <- confirm_single_file(file)
  if (is_parental(file)) {
    drive_abort("The Drive API does not copy folders or shared drives.")
  }

  tmp <- rationalize_path_name(path, name)
  path <- tmp$path
  name <- tmp$name

  params <- toCamel(list2(...))

  # load (path, name) into params
  if (!is.null(path)) {
    path <- as_parent(path)
    params[["parents"]] <- list(path$id)
  }
  params[["name"]] <- name %||% glue("Copy of {file$name}")
  check_for_overwrite(params[["parents"]], params[["name"]], overwrite)

  params[["fields"]] <- params[["fields"]] %||% "*"
  params[["fileId"]] <- file$id

  request <- request_generate(
    endpoint = "drive.files.copy",
    params = params
  )
  res <- request_make(request)
  proc_res <- gargle::response_process(res)
  out <- as_dribble(list(proc_res))

  drive_bullets(c(
    "Original file:",
    bulletize(gargle_map_cli(file)),
    "Copied to file:",
    # drive_reveal_path() puts immediate parent, if specified, in the `path`
    # then we reveal `path`, instead of `name`
    bulletize(gargle_map_cli(
      drive_reveal_path(out, ancestors = path),
      template = c(
        id_string = "<id:\u00a0<<id>>>", # \u00a0 is a nonbreaking space
        out = "{.drivepath <<path>>} {cli::col_grey('<<id_string>>')}"
      )
    ))
  ))

  invisible(out)
}


================================================
FILE: R/drive_create.R
================================================
#' Create a new blank Drive file
#'
#' Creates a new blank Drive file. Note there are better options for these
#' special cases:
#'   * Creating a folder? Use [drive_mkdir()].
#'   * Want to upload existing local content into a new Drive file? Use
#'     [drive_upload()].
#'
#' @seealso Wraps the `files.create` endpoint:
#'   * <https://developers.google.com/drive/api/v3/reference/files/create>
#'
#' @param name Name for the new file or, optionally, a path that specifies
#'   an existing parent folder, as well as the new file name.
#' @eval param_path_known_parent()
#' @param type Character. Create a blank Google Doc, Sheet or Slides by
#'   setting `type` to `document`, `spreadsheet`, or `presentation`,
#'   respectively. All non-`NULL` values for `type` are pre-processed with
#'   [drive_mime_type()].
#' @template dots-metadata
#' @template overwrite
#' @template verbose
#'
#' @eval return_dribble()
#' @export
#' @examplesIf drive_has_token()
#' # Create a blank Google Doc named 'WordStar' in
#' # your 'My Drive' root folder and star it
#' wordstar <- drive_create("WordStar", type = "document", starred = TRUE)
#'
#' # is 'WordStar' really starred? YES
#' purrr::pluck(wordstar, "drive_resource", 1, "starred")
#'
#' # Create a blank Google Slides presentation in
#' # the root folder, and set its description
#' execuvision <- drive_create(
#'   "ExecuVision",
#'   type = "presentation",
#'   description = "deeply nested bullet lists FTW"
#' )
#'
#' # Did we really set the description? YES
#' purrr::pluck(execuvision, "drive_resource", 1, "description")
#'
#' # check out the new presentation
#' drive_browse(execuvision)
#'
#' # Create folder 'b4xl' in the root folder,
#' # then create an empty new Google Sheet in it
#' b4xl <- drive_mkdir("b4xl")
#' drive_create("VisiCalc", path = b4xl, type = "spreadsheet")
#'
#' # Another way to create a Google Sheet in the folder 'b4xl'
#' drive_create("b4xl/SuperCalc", type = "spreadsheet")
#'
#' # Yet another way to create a new file in a folder,
#' # this time specifying parent `path` as a character
#' drive_create("Lotus 1-2-3", path = "b4xl", type = "spreadsheet")
#'
#' # Did we really create those Sheets in the intended folder? YES
#' drive_ls("b4xl")
#'
#' # `overwrite = FALSE` errors if file already exists at target filepath
#' # THIS WILL ERROR!
#' drive_create("VisiCalc", path = b4xl, overwrite = FALSE)
#'
#' # `overwrite = TRUE` moves an existing file to trash, then proceeds
#' drive_create("VisiCalc", path = b4xl, overwrite = TRUE)
#'
#' # Clean up
#' drive_rm(wordstar, b4xl, execuvision)
drive_create <- function(
  name,
  path = NULL,
  type = NULL,
  ...,
  overwrite = NA,
  verbose = deprecated()
) {
  warn_for_verbose(verbose)

  # in the special case of creating a shortcut, `name` is not required
  if (!identical(drive_mime_type(type), drive_mime_type("shortcut"))) {
    check_required(name)
    stopifnot(is_string(name))
  }
  # the order and role of `path` and `name` is naturally inverted here,
  # relative to all other related functions, hence we pre-process

  if (is.null(path)) {
    path <- name
    name <- NULL
  }
  tmp <- rationalize_path_name(path, name)
  path <- tmp$path
  name <- tmp$name

  params <- toCamel(list2(...))

  # load (path, name) into params
  if (!is.null(path)) {
    path <- as_parent(path)
    params[["parents"]] <- list(path[["id"]])
  }
  params[["name"]] <- name
  check_for_overwrite(params[["parents"]], params[["name"]], overwrite)

  params[["fields"]] <- params[["fields"]] %||% "*"
  params[["mimeType"]] <- drive_mime_type(type)

  request <- request_generate(
    endpoint = "drive.files.create",
    params = params
  )
  response <- request_make(request)
  proc_res <- gargle::response_process(response)

  out <- as_dribble(list(proc_res))

  drive_bullets(c(
    "Created Drive file:",
    bulletize(gargle_map_cli(out)),
    "With MIME type:",
    bulletize(gargle_map_cli(purrr::pluck(
      out,
      "drive_resource",
      1,
      "mimeType"
    )))
  ))
  invisible(out)
}


================================================
FILE: R/drive_download.R
================================================
#' Download a Drive file
#'
#' @description This function downloads a file from Google Drive. Native Google
#'   file types, such as Google Docs, Google Sheets, and Google Slides, must be
#'   exported to a conventional local file type. This can be specified:

#'   * explicitly via `type`
#'   * implicitly via the file extension of `path`
#'   * not at all, i.e. rely on the built-in default
#'
#' @description To see what export file types are even possible, see the [Drive
#'   API
#'   documentation](https://developers.google.com/drive/api/v3/ref-export-formats)
#'    or the result of `drive_about()$exportFormats`. The returned dribble
#'   includes a `local_path` column.
#'
#' @seealso [Download
#'   files](https://developers.google.com/drive/api/v3/manage-downloads), in the
#'   Drive API documentation.
#'
#' @template file-singular
#' @param path Character. Path for output file. If absent, the default file name
#'   is the file's name on Google Drive and the default location is working
#'   directory, possibly with an added file extension.
#' @param type Character. Only consulted if `file` is a native Google file.
#'   Specifies the desired type of the exported file. Will be processed via
#'   [drive_mime_type()], so either a file extension like `"pdf"` or a full MIME
#'   type like `"application/pdf"` is acceptable.
#' @param overwrite A logical scalar. If local `path` already exists, do you
#'   want to overwrite it?
#' @template verbose
#' @eval return_dribble()
#' @export
#' @examplesIf drive_has_token()
#' # Target one of the official example files
#' (src_file <- drive_example_remote("chicken_sheet"))
#'
#' # Download Sheet as csv, explicit type
#' downloaded_file <- drive_download(src_file, type = "csv")
#'
#' # See local path to new file
#' downloaded_file$local_path
#'
#' # Download as csv, type implicit in file extension
#' drive_download(src_file, path = "my_csv_file.csv")
#'
#' # Download with default name and type (xlsx)
#' drive_download(src_file)
#'
#' # Clean up
#' unlink(c("chicken_sheet.csv", "chicken_sheet.xlsx", "my_csv_file.csv"))
drive_download <- function(
  file,
  path = NULL,
  type = NULL,
  overwrite = FALSE,
  verbose = deprecated()
) {
  warn_for_verbose(verbose)
  if (!is.null(path) && file.exists(path) && !overwrite) {
    drive_abort(c(
      "Local {.arg path} already exists and overwrite is {.code FALSE}:",
      bulletize(gargle_map_cli(path, "{.path <<x>>}"))
    ))
  }
  file <- as_dribble(file)
  file <- confirm_single_file(file)

  ## preserve extension from path, before possible override by file$name
  ext <- file_ext_safe(path)
  path <- path %||% file$name

  mime_type <- file$drive_resource[[1]]$mimeType

  if (!grepl("google", mime_type) && !is.null(type)) {
    drive_bullets(c(
      "!" = "Ignoring {.arg type}. Only consulted for native Google file types.",
      " " = "MIME type of {.arg file}: {.field mime_type}."
    ))
  }

  if (grepl("google", mime_type)) {
    export_type <- type %||% ext %||% get_export_mime_type(mime_type)
    export_type <- drive_mime_type(export_type)
    verify_export_mime_type(mime_type, export_type)
    path <- apply_extension(path, drive_extension(export_type))

    request <- request_generate(
      endpoint = "drive.files.export",
      params = list(
        fileId = file$id,
        mimeType = export_type
      )
    )
  } else {
    request <- request_generate(
      endpoint = "drive.files.get",
      params = list(
        fileId = file$id,
        alt = "media"
      )
    )
  }

  response <- request_make(
    request,
    httr::write_disk(path, overwrite = overwrite)
  )
  success <- httr::status_code(response) == 200 && file.exists(path)

  if (success) {
    drive_bullets(c(
      "File downloaded:",
      bulletize(gargle_map_cli(file)),
      "Saved locally as:",
      "*" = "{.path {path}}"
    ))
  } else {
    drive_abort("Download failed.")
  }
  invisible(put_column(file, nm = "local_path", val = path, .after = "name"))
}

## get the default export MIME type for a native Google MIME type
## examples:
##    Google Doc --> MS Word
##  Google Sheet --> MS Excel
## Google Slides --> MS PowerPoint
get_export_mime_type <- function(mime_type) {
  m <- .drive$translate_mime_types$mime_type_google == mime_type &
    is_true(.drive$translate_mime_types$default)
  if (!any(m)) {
    drive_abort(c(
      "Not a recognized Google MIME type:",
      bulletize(gargle_map_cli(mime_type), bullet = "x")
    ))
  }
  .drive$translate_mime_types$mime_type_local[m]
}

## affirm that export_type is a valid export MIME type for a native Google file
## of type mime_type
verify_export_mime_type <- function(mime_type, export_type) {
  m <- .drive$translate_mime_types$mime_type_google == mime_type
  ok <- export_type %in% .drive$translate_mime_types$mime_type_local[m]
  if (!ok) {
    ## to be really nice, we would look these up in drive_mime_type() tibble
    ## and use the human_type, if found
    drive_abort(c(
      "Cannot export Google file of type:",
      bulletize(gargle_map_cli(mime_type)),
      "as a file of type:",
      bulletize(gargle_map_cli(export_type))
    ))
  }
  export_type
}


================================================
FILE: R/drive_endpoints.R
================================================
#' List Drive endpoints
#'
#' @description
#' The googledrive package stores a named list of Drive API v3 endpoints (or
#' "methods", using Google's vocabulary) internally and these functions expose
#' this data.
#'   * `drive_endpoint()` returns one endpoint, i.e. it uses `[[`.
#'   * `drive_endpoints()` returns a list of endpoints, i.e. it uses `[`.
#'
#' The names of this list (or the `id` sub-elements) are the nicknames that can
#' be used to specify an endpoint in [request_generate()]. For each endpoint, we
#' store its nickname or `id`, the associated HTTP verb, the `path`, and details
#' about the parameters. This list is derived programmatically from the Drive
#' API v3 Discovery Document
#' (`https://www.googleapis.com/discovery/v1/apis/drive/v3/rest`) using the
#' approach described in the [Discovery Documents
#' section](https://gargle.r-lib.org/articles/request-helper-functions.html#discovery-documents)
#' of the gargle vignette [Request helper
#' functions](https://gargle.r-lib.org/articles/request-helper-functions.html).
#'
#' @param i The name(s) or integer index(ices) of the endpoints to return. `i`
#'   is optional for `drive_endpoints()` and, if not given, the entire list is
#'   returned.
#'
#' @return One or more of the Drive API v3 endpoints that are used internally by
#'   googledrive.
#' @export
#'
#' @examples
#' str(head(drive_endpoints(), 3), max.level = 2)
#' drive_endpoint("drive.files.delete")
#' drive_endpoint(4)
drive_endpoints <- function(i = NULL) {
  if (is.null(i) || is_expose(i)) {
    i <- seq_along(.endpoints)
  }
  stopifnot(is.character(i) || (is.numeric(i)))
  .endpoints[i]
}

#' @rdname drive_endpoints
#' @export
drive_endpoint <- function(i) {
  stopifnot(is_string(i) || (is.numeric(i) && length(i) == 1))
  .endpoints[[i]]
}


================================================
FILE: R/drive_examples.R
================================================
#' Example files
#'
#' googledrive makes a variety of example files -- both local and remote --
#' available for use in examples and reprexes. These functions help you access
#' the example files. See `vignette("example-files", package = "googledrive")`
#' for more.
#'
#' @param matches A regular expression that matches the name of the desired
#'   example file(s). This argument is optional for the plural forms
#'   (`drive_examples_local()` and `drive_examples_remote()`) and, if provided,
#'   multiple matches are allowed. The single forms (`drive_example_local()` and
#'   `drive_example_remote()`) require this argument and require that there is
#'   exactly one match.

#'
#' @returns

#' * For `drive_example_local()` and `drive_examples_local()`, one or more local
#' filepaths.

#' * For `drive_example_remote()` and `drive_examples_remote()`, a `dribble`.

#' @name drive_examples
#' @examples
#' drive_examples_local() |> basename()
#' drive_examples_local("chicken") |> basename()
#' drive_example_local("imdb")
#'
#' @examplesIf drive_has_token()
#' drive_examples_remote()
#' drive_examples_remote("chicken")
#' drive_example_remote("chicken_doc")
NULL

#' @rdname drive_examples
#' @export
drive_examples_local <- function(matches) {
  out <- many_files(
    needle = matches,
    haystack = local_example_files(),
    where = "local"
  )
  out$path
}

#' @rdname drive_examples
#' @export
drive_examples_remote <- function(matches) {
  many_files(
    needle = matches,
    haystack = remote_example_files(),
    where = "remote"
  )
}

#' @rdname drive_examples
#' @export
drive_example_local <- function(matches) {
  out <- one_file(
    needle = matches,
    haystack = local_example_files(),
    where = "local"
  )
  out$path
}

#' @rdname drive_examples
#' @export
drive_example_remote <- function(matches) {
  one_file(
    needle = matches,
    haystack = remote_example_files(),
    where = "remote"
  )
}

many_files <- function(needle, haystack, where = c("local", "remote")) {
  where <- match.arg(where)
  out <- haystack

  if (!missing(needle)) {
    check_needle(needle)
    sel <- grepl(needle, haystack$name, ignore.case = TRUE)
    if (!any(sel)) {
      drive_abort(
        "Can't find a {where} example file with a name that matches \\
        \"{needle}\"."
      )
    }
    out <- haystack[sel, ]
  }

  out
}

one_file <- function(needle, haystack, where) {
  out <- many_files(needle = needle, haystack = haystack, where = where)
  if (nrow(out) > 1) {
    drive_abort(c(
      "Found multiple matching {where} files:",
      bulletize(gargle_map_cli(out$name)),
      i = "Make the {.arg matches} regular expression more specific."
    ))
  }
  out
}

local_example_files <- function() {
  # inlining env_cache() logic, so I don't need bleeding edge rlang
  if (!env_has(.googledrive, "local_example_files")) {
    pths <- list.files(
      system.file(
        "extdata",
        "example_files",
        package = "googledrive",
        mustWork = TRUE
      ),
      full.names = TRUE
    )
    env_poke(
      .googledrive,
      "local_example_files",
      tibble(name = basename(pths), path = pths)
    )
  }
  env_get(.googledrive, "local_example_files")
}

remote_example_files <- function() {
  # inlining env_cache() logic, so I don't need bleeding edge rlang
  if (!env_has(.googledrive, "remote_example_files")) {
    inventory_id <- "1XiwJJdoqoZ876OoSTjsnBZ5SxxUg6gUC"
    if (!drive_has_token()) {
      # don't trigger auth just for this
      local_drive_quiet()
      local_deauth()
    }
    dat_string <- drive_read_string(as_id(inventory_id), encoding = "UTF-8")
    dat <- utils::read.csv(text = dat_string, stringsAsFactors = FALSE)
    env_poke(.googledrive, "remote_example_files", as_dribble(as_id(dat$id)))
  }
  env_get(.googledrive, "remote_example_files")
}

check_needle <- function(needle) {
  if (is_string(needle)) {
    return()
  }
  drive_abort(c(
    "{.arg matches} must be a string, not {.cls class(needle)}"
  ))
}


================================================
FILE: R/drive_fields.R
================================================
#' Request partial resources
#'
#' @description You may be able to improve the performance of your API calls by
#'   requesting only the metadata that you actually need. This function is
#'   primarily for internal use and is currently focused on the [Files
#'   resource](https://developers.google.com/drive/api/v3/reference/files). Note
#'   that high-level googledrive functions assume that the `name`, `id`, and
#'   `kind` fields are included, at a bare minimum. Assuming that `resource =
#'   "files"` (the default), input provided via `fields` is checked for validity
#'   against the known field names and the validated fields are returned. To see
#'   a tibble containing all possible fields and a short description of each,
#'   call `drive_fields(expose())`.
#'
#' @description `prep_fields()` prepares fields for inclusion as query
#'   parameters.
#'
#' @seealso [Improve
#'   performance](https://developers.google.com/drive/api/v3/performance), in
#'   the Drive API documentation.
#'
#' @param fields Character vector of field names. If `resource = "files"`, they
#'   are checked for validity. Otherwise, they are passed through.
#' @param resource Character, naming the API resource of interest. Currently,
#'   only the Files resource is anticipated.
#'
#' @return `drive_fields()`: Character vector of field names. `prep_fields()`: a
#'   string.
#' @export
#'
#' @examples
#' # get a tibble of all fields for the Files resource + indicator of defaults
#' drive_fields(expose())
#'
#' # invalid fields are removed and throw warning
#' drive_fields(c("name", "parents", "ownedByMe", "pancakes!"))
#'
#' # prepare fields for query
#' prep_fields(c("name", "parents", "kind"))
drive_fields <- function(fields = NULL, resource = "files") {
  if (!identical(resource, "files")) {
    drive_bullets(c(
      "!" = "Currently only fields for the {.field files} resource can be \\
             checked for validity.",
      " " = "Nothing done."
    ))
  }
  if (is.null(fields)) {
    return(invisible(character()))
  }
  if (is_expose(fields)) {
    return(.drive$files_fields)
  }

  stopifnot(is.character(fields))
  if (!identical(resource, "files")) {
    return(fields)
  }

  out <- intersect(fields, .drive$files_fields$name)
  if (!setequal(fields, out)) {
    bad_fields <- setdiff(fields, out)
    drive_warn(c(
      "Omitting fields that are not recognized as part of the Files resource:",
      bulletize(gargle_map_cli(bad_fields))
    ))
  }
  out
}

#' @rdname drive_fields
#' @export
prep_fields <- function(fields, resource = "files") {
  resource <- glue("{resource}/")
  paste0(resource, fields, collapse = ",")
}
## usage:
## resource = NULL because we prepend "files/" when n > 1 items can come back
# request <- request_generate(
#   endpoint = "drive.files.get",
#   params = list(
#     fileId = two_files_search$id[1],
#     fields = prep_fields(c("name", "owners"), resource = NULL)
#   )
# )
# response <- request_make(request)
# gargle::response_process(response)


================================================
FILE: R/drive_find.R
================================================
#' Find files on Google Drive
#'
#' This is the closest googledrive function to what you can do at
#' <https://drive.google.com>: by default, you just get a listing of your files.
#' You can also search in various ways, e.g., filter by file type or ownership
#' or work with [shared drives][shared_drives]. This is a very powerful
#' function. Together with the more specific [drive_get()], this is the main way
#' to identify files to target for downstream work. If you know you want to
#' search within a specific folder or shared drive, use [drive_ls()].

#' @section File type:
#'
#'   The `type` argument is pre-processed with [drive_mime_type()], so you can
#'   use a few shortcuts and file extensions, in addition to full-blown MIME
#'   types. googledrive forms a search clause to pass to `q`.

#' @section Search parameters:
#'
#' Do advanced search on file properties by providing search clauses to the
#' `q` parameter that is passed to the API via `...`. Multiple `q` clauses or
#' vector-valued `q` are combined via 'and'.

#' @section Trash:
#'
#'   By default, `drive_find()` sets `trashed = FALSE` and does not include
#'   files in the trash. Literally, it adds `q = "trashed = false"` to the
#'   query. To search *only* the trash, set `trashed = TRUE`. To see files
#'   regardless of trash status, set `trashed = NA`, which adds
#'   `q = "(trashed = true or trashed = false)"` to the query.

#' @section Sort order:
#'
#'   By default, `drive_find()` sends `orderBy = "recency desc"`, so the top
#'   files in your result have high "recency" (whatever that means). To suppress
#'   sending `orderBy` at all, do `drive_find(orderBy = NULL)`. The `orderBy`
#'   parameter accepts sort keys in addition to `recency`, which are documented
#'   in the [`files.list` endpoint](https://developers.google.com/drive/api/v3/reference/files/list).
#'   googledrive translates a snake_case specification of `order_by` into the
#'   lowerCamel form, `orderBy`.

#' @section Shared drives and domains:
#'
#'   If you work with shared drives and/or Google Workspace, you can apply your
#'   search query to collections of items beyond those associated with "My
#'   Drive". Use the `shared_drive` or `corpus` arguments to control this.
#'   Read more about [shared drives][shared_drives].

#' @seealso Wraps the `files.list` endpoint:
#'   * <https://developers.google.com/drive/api/v3/reference/files/list>
#'
#' Helpful resource for forming your own queries:
#'   * <https://developers.google.com/drive/api/v3/search-files>
#'
#' @template pattern
#' @param trashed Logical. Whether to search files that are not in the trash
#'   (`trashed = FALSE`, the default), only files that are in the trash
#'   (`trashed = TRUE`), or to search regardless of trashed status (`trashed =
#'   NA`).
#' @param type Character. If provided, only files of this type will be returned.
#'   Can be anything that [drive_mime_type()] knows how to handle. This is
#'   processed by googledrive and sent as a query parameter.
#' @template n_max
#' @template shared_drive-singular
#' @template corpus
#' @param ... Other parameters to pass along in the request. The most likely
#'   candidate is `q`. See below and the API's
#'   [Search for files and folders guide](https://developers.google.com/drive/api/v3/search-files).
#' @template verbose
#' @template team_drive-singular
#'
#' @eval return_dribble()
#' @examples
#' \dontrun{
#' # list "My Drive" w/o regard for folder hierarchy
#' drive_find()
#'
#' # filter for folders, the easy way and the hard way
#' drive_find(type = "folder")
#' drive_find(q = "mimeType = 'application/vnd.google-apps.folder'")
#'
#' # filter for Google Sheets, the easy way and the hard way
#' drive_find(type = "spreadsheet")
#' drive_find(q = "mimeType='application/vnd.google-apps.spreadsheet'")
#'
#' # files whose names match a regex
#' # the local, general, sometimes-slow-to-execute version
#' drive_find(pattern = "ick")
#' # the server-side, executes-faster version
#' # NOTE: works only for a pattern at the beginning of file name
#' drive_find(q = "name contains 'chick'")
#'
#' # search for files located directly in your root folder
#' drive_find(q = "'root' in parents")
#' # FYI: this is equivalent to
#' drive_ls("~/")
#'
#' # control page size or cap the number of files returned
#' drive_find(pageSize = 50)
#' # all params passed through `...` can be camelCase or snake_case
#' drive_find(page_size = 50)
#' drive_find(n_max = 58)
#' drive_find(page_size = 5, n_max = 15)
#'
#' # various ways to specify q search clauses
#' # multiple q's
#' drive_find(
#'   q = "name contains 'TEST'",
#'   q = "modifiedTime > '2020-07-21T12:00:00'"
#' )
#' # vector q
#' drive_find(q = c("starred = true", "visibility = 'anyoneWithLink'"))
#'
#' # default `trashed = FALSE` excludes files in the trash
#' # `trashed = TRUE` consults ONLY file in the trash
#' drive_find(trashed = TRUE)
#' # `trashed = NA` disregards trash status completely
#' drive_find(trashed = NA)
#'
#' # suppress the default sorting on recency
#' drive_find(order_by = NULL, n_max = 5)
#'
#' # sort on various keys
#' drive_find(order_by = "modifiedByMeTime", n_max = 5)
#' # request descending order
#' drive_find(order_by = "quotaBytesUsed desc", n_max = 5)
#' }
#'
#' @export
drive_find <- function(
  pattern = NULL,
  trashed = FALSE,
  type = NULL,
  n_max = Inf,
  shared_drive = NULL,
  corpus = NULL,
  ...,
  verbose = deprecated(),
  team_drive = deprecated()
) {
  warn_for_verbose(verbose)
  if (!is.null(pattern) && !(is_string(pattern))) {
    drive_abort("{.arg pattern} must be a character string.")
  }
  stopifnot(is_toggle(trashed))
  stopifnot(is.numeric(n_max), n_max >= 0, length(n_max) == 1)

  if (lifecycle::is_present(team_drive)) {
    lifecycle::deprecate_warn(
      "2.0.0",
      "drive_find(team_drive)",
      "drive_find(shared_drive)"
    )
    shared_drive <- shared_drive %||% team_drive
  }

  if (n_max < 1) {
    return(dribble())
  }

  params <- toCamel(list2(...))
  params[["fields"]] <- params[["fields"]] %||% "*"
  if (!has_name(params, "orderBy")) {
    params[["orderBy"]] <- "recency desc"
  }
  params <- marshal_q_clauses(params)

  trash_clause <- switch(
    as.character(trashed),
    `TRUE` = "trashed = true",
    `FALSE` = "trashed = false",
    "(trashed = true or trashed = false)"
  )
  params$q <- append(params$q, trash_clause)

  if (!is.null(type)) {
    ## if they are all NA, this will error, because drive_mime_type()
    ## doesn't allow it, otherwise we proceed with the non-NA mime types
    mime_type <- drive_mime_type(type)
    mime_type <- purrr::discard(mime_type, is.na)
    params$q <- append(params$q, or(glue("mimeType = {sq(mime_type)}")))
  }

  params$q <- and(params$q)

  params <- append(params, handle_shared_drives(shared_drive, corpus))

  request <- request_generate(endpoint = "drive.files.list", params = params)
  proc_res_list <- do_paginated_request(
    request,
    n_max = n_max,
    n = function(x) length(x$files)
  )

  res_tbl <- proc_res_list |>
    map("files") |>
    purrr::flatten() |>
    as_dribble()

  # there is some evidence of overlap in the results returned in different
  # pages; this is attempt to eliminate a 2nd (or 3rd ...) record for an ID
  # #272 #273 #277 #279 #281
  res_tbl <- res_tbl[!duplicated(res_tbl$id), ]

  if (!is.null(pattern)) {
    res_tbl <- res_tbl[grep(pattern, res_tbl$name), ]
  }
  if (n_max < nrow(res_tbl)) {
    res_tbl <- res_tbl[seq_len(n_max), ]
  }
  res_tbl
}

## finds all the q clauses and collapses into one character vector of clauses
## these are destined to be and'ed to form q in the query
marshal_q_clauses <- function(params) {
  params <- partition_params(params, "q")
  if (length(params[["matched"]]) == 0) {
    return(params[["unmatched"]])
  }

  q_bits <- params[["matched"]]
  stopifnot(all(vapply(q_bits, is.character, logical(1))))
  q_bits <- unique(unlist(q_bits, use.names = FALSE))
  q_bits <- q_bits[lengths(q_bits) > 0]
  c(params[["unmatched"]], q = list(q_bits))
}

# https://developers.google.com/drive/api/v3/search-shareddrives#query_multiple_terms_with_parentheses
parenthesize <- function(x) glue("({x})")
and <- function(x) glue_collapse(parenthesize(x), sep = " and ")
or <- function(x) glue_collapse(x, sep = " or ")

handle_shared_drives <- function(shared_drive, corpus) {
  if (!is.null(shared_drive)) {
    shared_drive <- as_shared_drive(shared_drive)
    if (no_file(shared_drive)) {
      drive_abort("Can't find the requested {.arg shared_drive}.")
    }
    shared_drive <- as_id(shared_drive)
  }
  if (identical(corpus, "all")) {
    lifecycle::deprecate_warn(
      "2.0.0",
      "drive_find(corpus = 'now expects \"allDrives\" instead of \"all\"')"
    )
    corpus <- "allDrives"
  }
  if (is.null(shared_drive) && is.null(corpus)) {
    return()
  }
  shared_drive_params(shared_drive, corpus)
}


================================================
FILE: R/drive_get.R
================================================
#' Get Drive files by path or id
#'
#' Retrieves metadata for files specified via `path` or via file `id`. This
#' function is quite straightforward if you specify files by `id`. But there are
#' some important considerations when you specify your target files by `path`.
#' See below for more. If the target files are specified via `path`, the
#' returned [`dribble`] will include a `path` column.

#' @section Getting by `path`:
#'
#'   Google Drive does NOT behave like your local file system! File and folder
#'   names need not be unique, even at a given level of the hierarchy. This
#'   means that a single path can describe multiple files (or 0 or exactly 1).
#'
#'   A single file can also be compatible with multiple paths, i.e. one path
#'   could be more specific than the other. A file located at `~/alfa/bravo` can
#'   be found as `bravo`, `alfa/bravo`, and `~/alfa/bravo`. If all 3 of those
#'   were included in the input `path`, they would be represented by a
#'   **single** row in the output.
#'
#'   It's best to think of `drive_get()` as a setwise operation when using file
#'   paths. Do not assume that the `i`-th input path corresponds to row `i` in
#'   the output (although it often does!). If there's not a 1-to-1 relationship
#'   between the input and output, this will be announced in a message.
#'
#'   `drive_get()` performs just enough path resolution to uniquely identify a
#'   file compatible with each input `path`, for all `path`s at once. If you
#'   absolutely want the full canonical path, run the output of `drive_get()`
#'   through [`drive_reveal(d, "path")`][drive_reveal()]`.
#'

#' @section Files that you don't own:
#'
#'   If you want to get a file via `path` and it's not necessarily on your My
#'   Drive, you may need to specify the `shared_drive` or `corpus` arguments to
#'   search other collections of items. Read more about [shared
#'   drives][shared_drives].
#'
#' @seealso To add path information to any [`dribble`] that lacks it, use
#'   [`drive_reveal(d, "path")`][drive_reveal()]. To list the contents of a
#'   folder, use [drive_ls()]. For general searching, use [drive_find()].
#'
#' Wraps the `files.get` endpoint and, if you specify files by name or
#'   path, also calls `files.list`:
#'   * <https://developers.google.com/drive/api/v3/reference/files/get>
#'   * <https://developers.google.com/drive/api/v3/reference/files/list>
#'
#' @param path Character vector of path(s) to get. Use a trailing slash to
#'   indicate explicitly that a path is a folder, which can disambiguate if
#'   there is a file of the same name (yes this is possible on Drive!). If
#'   `path` appears to contain Drive URLs or is explicitly marked with
#'   [as_id()], it is treated as if it was provided via the `id` argument.
#' @param id Character vector of Drive file ids or URLs (it is first processed
#'   with [as_id()]). If both `path` and `id` are non-`NULL`, `id` is silently
#'   ignored.
#' @template shared_drive-singular
#' @template corpus
#' @template verbose
#' @template team_drive-singular
#'
#' @eval return_dribble(extras = "If the target files were specified via `path`,
#'   there will be a `path` column.")
#' @export
#'
#' @examplesIf drive_has_token()
#' # get info about your "My Drive" root folder
#' drive_get("~/")
#' # the API reserves the file id "root" for your root folder
#' drive_get(id = "root")
#' drive_get(id = "root") |> drive_reveal("path")
#'
#' # set up some files to get by path
#' alfalfa <- drive_mkdir("alfalfa")
#' broccoli <- drive_upload(
#'   drive_example_local("chicken.txt"),
#'   name = "broccoli", path = alfalfa
#' )
#' drive_get("broccoli")
#' drive_get("alfalfa/broccoli")
#' drive_get("~/alfalfa/broccoli")
#' drive_get(c("broccoli", "alfalfa/", "~/alfalfa/broccoli"))
#'
#' # Clean up
#' drive_rm(alfalfa)
#'
#' \dontrun{
#' # The examples below are indicative of correct syntax.
#' # But note these will generally result in an error or a
#' # 0-row dribble, unless you replace the inputs with paths
#' # or file ids that exist in your Drive.
#'
#' # multiple names
#' drive_get(c("abc", "def"))
#'
#' # multiple names, one of which must be a folder
#' drive_get(c("abc", "def/"))
#'
#' # query by file id(s)
#' drive_get(id = "abcdefgeh123456789")
#' drive_get(as_id("abcdefgeh123456789"))
#' drive_get(id = c("abcdefgh123456789", "jklmnopq123456789"))
#'
#' # apply to a browser URL for, e.g., a Google Sheet
#' my_url <- "https://docs.google.com/spreadsheets/d/FILE_ID/edit#gid=SHEET_ID"
#' drive_get(my_url)
#' drive_get(as_id(my_url))
#' drive_get(id = my_url)
#'
#' # access the shared drive named "foo"
#' # shared_drive params must be specified if getting by path
#' foo <- shared_drive_get("foo")
#' drive_get(c("this.jpg", "that-file"), shared_drive = foo)
#' # shared_drive params are not necessary if getting by id
#' drive_get(as_id("123456789"))
#'
#' # search all shared drives and other files user has accessed
#' drive_get(c("this.jpg", "that-file"), corpus = "allDrives")
#' }
drive_get <- function(
  path = NULL,
  id = NULL,
  shared_drive = NULL,
  corpus = NULL,
  verbose = deprecated(),
  team_drive = deprecated()
) {
  warn_for_verbose(verbose)
  if (length(path) + length(id) == 0) {
    return(dribble_with_path())
  }
  stopifnot(is.null(path) || is.character(path))
  stopifnot(is.null(id) || is.character(id))

  if (lifecycle::is_present(team_drive)) {
    lifecycle::deprecate_warn(
      "2.0.0",
      "drive_get(team_drive)",
      "drive_get(shared_drive)"
    )
    shared_drive <- shared_drive %||% team_drive
  }

  if (!is.null(path) && any(is_drive_url(path))) {
    path <- as_id(path)
  }

  if (!is.null(path) && is_drive_id(path)) {
    id <- path
    path <- NULL
  }

  if (is.null(path)) {
    as_dribble(map(as_id(id), get_one_file_id))
  } else {
    drive_get_path(path, shared_drive, corpus)
  }
}

get_one_file_id <- function(id) {
  if (is.na(id)) {
    drive_abort("Can't {.fun drive_get} a file when {.arg id} is {.code NA}.")
  }
  # drive_id validity checks catch the id = "" case, but just FYI:
  # when id = "", drive.files.get actually becomes a call to drive.files.list
  # and, therefore, returns 100 files by default; this is a bad thing
  request <- request_generate(
    endpoint = "drive.files.get",
    params = list(
      fileId = id,
      fields = "*"
    )
  )
  response <- request_make(request)
  gargle::response_process(response)
}


================================================
FILE: R/drive_get_path.R
================================================
# all the helpers behind:
# drive_get(path =)
# drive_reveal(what = "path")

drive_reveal_path <- function(x, ancestors = c("none", "parents", "all")) {
  stopifnot(inherits(x, "dribble"))
  if (no_file(x)) {
    return(dribble_with_path())
  }
  ancestors <- ancestors %||% dribble()

  if (!inherits(ancestors, "dribble")) {
    ancestors <- arg_match(ancestors)
    if (ancestors == "all") {
      tmp <- sort_out_shared_drive_and_corpus(x)
      shared_drive <- tmp$shared_drive
      corpus <- tmp$corpus
    }
    ancestors <- switch(
      ancestors,
      none = dribble(),
      parents = get_immediate_parents(x),
      all = get_folders(shared_drive = shared_drive, corpus = corpus)
    )
  }

  resolve_paths(x, ancestors)
}

drive_reveal_canonical_path <- function(x) {
  drive_reveal_path(x, ancestors = "all")
}

# TODO: can this somehow be unified with drive_find()'s fussing about with
# (shared_drive, corpus) and with shared_drive_params()?
sort_out_shared_drive_and_corpus <- function(x) {
  shared_drive <- NULL
  corpus <- NULL
  sid <- map_chr(x$drive_resource, "driveId", .default = NA)
  sid <- unique(sid[!is.na(sid)])
  if (length(sid) == 1) {
    shared_drive <- as_id(sid)
  }
  if (length(sid) > 1) {
    corpus <- "allDrives"
  }
  list(shared_drive = shared_drive, corpus = corpus)
}

drive_get_path <- function(path = NULL, shared_drive = NULL, corpus = NULL) {
  if (length(path) == 0) {
    return(dribble_with_path())
  }
  stopifnot(is_path(path))
  path <- rootize_path(path)

  last_path_part <- get_last_path_part(path)
  candidates <- get_by_name(
    last_path_part,
    shared_drive = shared_drive,
    corpus = corpus
  )
  candidates <- drive_reveal_path(candidates)

  # setup a tibble to structure the work
  dat <- tibble(
    orig_path = path,
    doomed = !map_lgl(
      last_path_part,
      path_has_match,
      haystack = candidates$path
    ),
    done = FALSE
  )

  dat$done <- map_lgl(dat$orig_path, path_has_match, haystack = candidates$path)
  if (all(dat$done | dat$doomed)) {
    return(finalize(dat, candidates))
  }
  # all undone paths assert something about parent folder(s)

  candidates <- drive_reveal_path(candidates, "parents")
  dat$done <- map_lgl(dat$orig_path, path_has_match, haystack = candidates$path)
  if (all(dat$done | dat$doomed)) {
    return(finalize(dat, candidates))
  }

  candidates <- drive_reveal_path(candidates, "all")
  dat$done <- map_lgl(dat$orig_path, path_has_match, haystack = candidates$path)
  if (all(dat$done | dat$doomed)) {
    return(finalize(dat, candidates))
  }

  # TODO: paths that are still undone could possibly be resolved by considering
  # folder shortcuts, i.e. non-canonical paths
  # but for now, just return what we've got
  finalize(dat, candidates)
}

path_match <- function(needle, haystack) {
  if (!has_slash(needle)) {
    haystack <- strip_slash(haystack)
  }
  if (startsWith(needle, "[/~]")) {
    needle <- paste0("^", needle)
  }
  needle <- paste0(escape_regex(needle), "$")
  grep(needle, haystack)
}

path_has_match <- function(needle, haystack) {
  any(path_match(needle, haystack))
}

get_folders <- function(shared_drive = NULL, corpus = NULL) {
  # TODO: could possibly be nice to limit the fields
  folders <-
    drive_find(type = "folder", shared_drive = shared_drive, corpus = corpus)
  folders <- vec_rbind(root_folder(), folders)
}

get_immediate_parents <- function(x) {
  stopifnot(inherits(x, "dribble"))
  x <- drive_reveal(x, "parent")
  parent_ids <- unique(x$id_parent[!is.na(x$id_parent)])
  # TODO: I suspect I must deal with the case where don't have permission to
  # drive_get() one of these ids, but I haven't tripped up on this yet myself.
  # TODO: could possibly be nice to limit the fields
  drive_get(id = as_id(parent_ids))
}

resolve_paths <- function(d, folders = dribble()) {
  probands <- pthize(d)
  ancestors <- pthize(folders)
  raw_paths <- map(probands, ~ pth(list(.x), ancestors))
  pretty_paths <- map_chr(raw_paths, pathify)
  put_column(d, nm = "path", val = pretty_paths, .after = "name")
}

# converts files in a dribble to the form used in pth()
pthize <- function(d) {
  d <- d |>
    drive_reveal("mime_type") |>
    drive_reveal("parent") |>
    drive_reveal("shortcut_details")
  purrr::transpose(
    d[c(
      "id",
      "id_parent", # needed to resolve path relationships
      "name",
      "mime_type",
      "shortcut_details" # needed to create path string
    )]
  )
}

# turns the output of pth() (a list) into a filepath (a string)
pathify <- function(x) {
  x <- map_if(
    x,
    ~ .x$id == root_id(),
    ~ {
      .x$name <- "~"
      .x
    }
  )

  last_mime_type <- pluck(last(x), "mime_type")
  last_is_folder <- identical(last_mime_type, drive_mime_type("folder"))
  last_is_folder_shortcut <-
    identical(last_mime_type, drive_mime_type("shortcut")) &&
    identical(
      pluck(last(x), "shortcut_details", "targetMimeType"),
      drive_mime_type("folder")
    )
  if (last_is_folder || last_is_folder_shortcut) {
    nm <- pluck(last(x), "name")
    purrr::pluck(x, length(x), "name") <- append_slash(nm)
  }

  glue_collapse(map_chr(x, "name"), sep = "/")
}

# the recursive workhorse that walks up a file tree
# x is a list, each element describes 1 file
# a file is described by:
# - id
# - id_parent        (can be NA)
# - name             (just along for the ride; needed to create path strings)
# - mime_type        (ditto)
# - shortcut_details (ditto; is often NULL)
# typical x at start: list(some_file)
# typical x at finish: list(grandparent_folder, parent_folder, some_file)
pth <- function(x, ancestors) {
  this <- x[[1]]
  if (is.na(this$id_parent)) {
    return(x)
  }
  parent <- purrr::detect(ancestors, ~ identical(.x$id, this$id_parent))
  if (is.null(parent)) {
    return(x)
  }
  pth(c(list(parent), x), ancestors)
}

finalize <- function(dat, candidates) {
  scratch <- dat
  scratch$m <- map(dat$orig_path, path_match, haystack = candidates$path)
  scratch$nm <- lengths(scratch$m)
  scratch$status <- NA_character_

  # doomed: never even found a file with correct name, much less path
  # (remember this filter goes a bit beyond the name, e.g. maybe folder-hood)
  scratch$status[scratch$doomed] <- "unmatched"

  # empty_string: special case of doomed
  scratch$status[!nzchar(scratch$orig_path)] <- "empty_string"

  # not doomed, but undone: these could be valid paths, but we won't know until
  # we start resolving non-canonical paths
  scratch$status[!scratch$done & !scratch$doomed] <- "undone"

  # unspecific: path is compatible with more than 1 file
  scratch$status[scratch$done & scratch$nm > 1] <- "unspecific"

  # resolved: path identifies exactly 1 file
  scratch$status[scratch$done & scratch$nm == 1] <- "resolved"

  no_status <- is.na(scratch$status)
  if (any(no_status)) {
    abort("Internal error: paths with missing status")
  }

  report_weird_stuff <- function(x, indicator, problem) {
    weird <- vec_slice(x, x[["status"]] == indicator)
    if (vec_size(weird) == 0) {
      return()
    }
    drive_bullets(c(
      "!" = "Problem with {nrow(weird)} path{?s}: {problem}",
      # these really should be sub-bullets, but not possible at this time
      bulletize(
        gargle_map_cli(weird[["orig_path"]], "{.path <<x>>}"),
        bullet = " "
      )
    ))
  }
  report_weird_stuff(scratch, "unmatched", "no files found by this name")
  report_weird_stuff(scratch, "undone", "no file has such a canonical path")
  report_weird_stuff(
    scratch,
    "unspecific",
    "path is compatible with more than 1 file"
  )

  n_empty_string <- sum(scratch$status == "empty_string")
  if (n_empty_string > 0) {
    drive_bullets(c(
      "!" = "Problem with {n_empty_string} path{?s}: \\
             path is empty string"
    ))
  }

  index <- unlist(scratch$m)
  dupes <- duplicated(index)
  if (any(dupes)) {
    multis <- vec_slice(candidates, unique(index[dupes]))

    drive_bullets(c(
      "!" = "{nrow(multis)} file{?s} in the output {?is/are} associated with \\
             more than 1 input {.arg path}",
      # these really should be sub-bullets, but not possible at this time
      bulletize(gargle_map_cli(multis), bullet = " ")
    ))
  }

  resolved <- scratch$status == "resolved"
  if (all(resolved)) {
    if (nrow(scratch) > 1) {
      b <- c(
        v = "All {nrow(scratch)} input {.arg path}s resolved to exactly \\
                  1 file."
      )
    } else {
      b <- c(v = "The input {.arg path} resolved to exactly 1 file.")
    }
  } else if (any(resolved)) {
    b <- c(
      "!" = "{sum(scratch$status == 'resolved')} out of {nrow(scratch)} \\
                  input paths resolved to exactly 1 file."
    )
  } else {
    # TODO: this wording is not great, yet I don't know what would be better
    b <- c("!" = "No path resolved to exactly 1 file.")
  }
  drive_bullets(b)

  vec_slice(candidates, index[!dupes])
}

get_by_name <- function(names, shared_drive = NULL, corpus = NULL) {
  nms <- strip_slash(unique(names))
  is_root <- nms == "~"
  nms <- nms[!is_root]
  q_clauses <- glue("name = {sq(nms)}")

  # fields <-
  #   c("kind", "id", "name", "mimeType", "parents", "shortcutDetails", "driveId")
  if (length(q_clauses) == 0) {
    found <- dribble()
  } else {
    found <- drive_find(
      q = or(q_clauses),
      # fields = prep_fields(fields),
      shared_drive = shared_drive,
      corpus = corpus
    )
  }

  if (any(is_root)) {
    found <- vec_rbind(root_folder(), found)
  }

  found
}

# you might think this can be merged with partition_path(), but their purposes
# are different enough that it's not worth it
get_last_path_part <- function(path) {
  stopifnot(is_path(path))
  path <- rootize_path(path)

  # NOTE: we ignore (but retain) a trailing slash
  # why? googledrive encourages the user to use a trailing slash to explicitly
  # indicate a path that refers to a folder
  slash_pos <- gregexpr(pattern = "/.", path)
  no_slash <- map_lgl(slash_pos, ~ all(.x == -1))
  last_slash <- map_int(slash_pos, max)
  ifelse(no_slash, path, substr(path, last_slash + 1, nchar(path)))
}


================================================
FILE: R/drive_id-class.R
================================================
#' `drive_id` class
#'
#' @description

#' `drive_id` is an S3 class to mark strings as Drive file ids, in order to
#' distinguish them from Drive file names or paths. `as_id()` converts various
#' inputs into an instance of `drive_id`.
#'
#' `as_id()` is a generic function.

#' @param x A character vector of file or shared drive ids or URLs, a
#'   [`dribble`], or a suitable data frame.
#' @param ... Other arguments passed down to methods. (Not used.)
#' @return A character vector bearing the S3 class `drive_id`.
#' @name drive_id
#' @examplesIf drive_has_token()
#' as_id("123abc")
#' as_id("https://docs.google.com/spreadsheets/d/qawsedrf16273849/edit#gid=12345")
#'
#' x <- drive_find(n_max = 3)
#' as_id(x)
NULL

new_drive_id <- function(x = character()) {
  vec_assert(x, character())
  new_vctr(x, class = "drive_id", inherit_base_type = TRUE)
}

validate_drive_id <- function(x) {
  ok <- is_valid_drive_id(x)
  if (all(ok)) {
    return(x)
  }

  # proceed with plain character vector
  x <- vec_data(x)
  # pragmatism re: how to cli-style a path that is the empty string
  # this is related to the use of gargle_map_cli() for vectorized styling
  # if cli gains native vectorization, this may become unnecessary
  x[!nzchar(x)] <- "\"\""

  drive_abort(c(
    "A {.cls drive_id} must match this regular expression: \\
     {.code {drive_id_regex()}}",
    "Invalid input{?s}:{cli::qty(sum(!ok))}",
    bulletize(gargle_map_cli(x[!ok]), bullet = "x")
  ))
}

#' @export
#' @rdname drive_id
as_id <- function(x, ...) UseMethod("as_id")

#' @export
as_id.default <- function(x, ...) {
  drive_abort(
    "
    Don't know how to coerce an object of class {.cls {class(x)}} into \\
    a {.cls drive_id}."
  )
}

#' @export
as_id.NULL <- function(x, ...) NULL

#' @export
as_id.drive_id <- function(x, ...) x

#' @export
as_id.dribble <- function(x, ...) as_id(x$id)

#' @export
as_id.data.frame <- function(x, ...) as_id(validate_dribble(new_dribble(x)))

#' @export
as_id.character <- function(x, ...) {
  if (length(x) == 0L) {
    return(new_drive_id())
  }
  out <- map_chr(x, get_one_id)
  validate_drive_id(new_drive_id(out))
}

drive_id_regex <- function() "^[a-zA-Z0-9_-]+$"

is_valid_drive_id <- function(x) {
  # among practitioners, It Is Known that file IDs have >= 25 characters
  # but I'm not convinced the pros outweigh the cons re: checking length
  # for example, in tests, it's nice to not worry about this
  grepl(drive_id_regex(), x) | is.na(x)
}

is_drive_id <- function(x) {
  inherits(x, "drive_id")
}

#' @export
gargle_map_cli.drive_id <- function(x, ...) {
  NextMethod()
}

#' @export
vec_ptype2.drive_id.drive_id <- function(x, y, ...) new_drive_id()
#' @export
vec_ptype2.drive_id.character <- function(x, y, ...) character()
#' @export
vec_ptype2.character.drive_id <- function(x, y, ...) character()

#' @export
vec_cast.drive_id.drive_id <- function(x, to, ...) x
#' @export
vec_cast.drive_id.character <- function(x, to, ...) {
  validate_drive_id(new_drive_id(x))
}
#' @export
vec_cast.character.drive_id <- function(x, to, ...) vec_data(x)

#' @export
vec_ptype_abbr.drive_id <- function(x, ...) "drv_id"

#' @export
pillar_shaft.drive_id <- function(x, ...) {
  # The goal is to either see drive_id in full (which would allow, e.g. copy
  # and paste) or to truncate it severely and leave room for more interesting
  # columns, such as the Drive file name.
  # Anything in between these two extremes seems like a waste of horizontal space.

  x_valid <- !is.na(x)

  # It's important to keep NA in the vector!
  out <- rep(NA_character_, vec_size(x))
  out[x_valid] <- format(x[x_valid])
  out_short <- out

  # nchar("<drv_id>") is 8
  n <- 8
  trunkate <- function(x) {
    glue("{substr(x, 1, n - 1)}{cli::symbol$continue}")
  }
  out_width <- nchar(trimws(out))
  too_wide <- which(x_valid & out_width > n)
  if (any(too_wide)) {
    out_short[too_wide] <- trunkate(out_short[too_wide])
  }

  have_color <- cli::num_ansi_colors() > 1
  pillar::new_pillar_shaft_simple(
    out,
    short_formatted = out_short,
    na = if (have_color) pillar::style_na("NA") else "<NA>"
  )
}

## we anticipate file-id-containing URLs in these forms:
##       /d/FILE_ID   Drive file
## /folders/FILE_ID   Drive folder
##       id=FILE_ID   uploaded blob
id_regexp <- "(/d/|/folders/|id=)[^/?]+"

is_drive_url <- function(x) grepl("^http", x) & grepl(id_regexp, x)

get_one_id <- function(x) {
  if (!grepl("^http|/", x)) {
    return(x)
  }

  id_loc <- regexpr(id_regexp, x)
  if (id_loc == -1) {
    NA_character_
  } else {
    gsub("/d/|/folders/|id=", "", regmatches(x, id_loc))
  }
}


================================================
FILE: R/drive_ls.R
================================================
#' List contents of a folder or shared drive
#'
#' List the contents of a folder or shared drive, recursively or not. This is a
#' thin wrapper around [drive_find()], that simply adds one constraint: the
#' search is limited to direct or indirect children of `path`.
#'
#' @param path Specifies a single folder on Google Drive whose contents you want
#'   to list. Can be an actual path (character), a file id or URL marked with
#'   [as_id()], or a [`dribble`]. If it is a shared drive or is a folder on a
#'   shared drive, it must be passed as a [`dribble`]. If `path` is a shortcut
#'   to a folder, it is automatically resolved to its target folder.
#' @param ... Any parameters that are valid for [drive_find()].
#' @param recursive Logical, indicating if you want only direct children of
#'   `path` (`recursive = FALSE`, the default) or all children, including
#'   indirect (`recursive = TRUE`).
#'
#' @eval return_dribble()
#' @export
#' @examples
#' \dontrun{
#' # get contents of the folder 'abc' (non-recursive)
#' drive_ls("abc")
#'
#' # get contents of folder 'abc' whose names contain the letters 'def'
#' drive_ls(path = "abc", pattern = "def")
#'
#' # get all Google spreadsheets in folder 'abc'
#' # whose names contain the letters 'def'
#' drive_ls(path = "abc", pattern = "def", type = "spreadsheet")
#'
#' # get all the files below 'abc', recursively, that are starred
#' drive_ls(path = "abc", q = "starred = true", recursive = TRUE)
#' }
drive_ls <- function(path = NULL, ..., recursive = FALSE) {
  stopifnot(is.logical(recursive), length(recursive) == 1)
  if (is.null(path)) {
    return(drive_find(...))
  }

  path <- as_parent(path)

  params <- list2(...)
  if (is_shared_drive(path)) {
    params[["shared_drive"]] <- as_id(path)
  } else {
    shared_drive <- pluck(path, "drive_resource", 1, "driveId")
    if (!is.null(shared_drive)) {
      params[["shared_drive"]] <- params[["shared_drive"]] %||%
        as_id(shared_drive)
    }
  }

  parent <- path[["id"]]
  if (isTRUE(recursive)) {
    parent <- c(parent, folders_below(parent, params[["shared_drive"]]))
  }
  parent <- glue("{sq(parent)} in parents")
  parent <- glue("({or(parent)})")
  params[["q"]] <- append(params[["q"]], parent)

  exec(drive_find, !!!params)
}

folders_below <- function(id, shared_drive = NULL) {
  folder_kids <- folder_kids_of(id, shared_drive = shared_drive)
  if (length(folder_kids) == 0) {
    character()
  } else {
    c(
      folder_kids,
      unlist(
        lapply(
          folder_kids,
          folders_below,
          shared_drive = shared_drive
        ),
        recursive = FALSE
      )
    )
  }
}

folder_kids_of <- function(id, shared_drive = NULL) {
  drive_find(
    shared_drive = as_id(shared_drive),
    type = "folder",
    q = glue("{sq(id)} in parents"),
    fields = prep_fields(c("kind", "name", "id"))
  )[["id"]]
}


================================================
FILE: R/drive_mime_type.R
================================================
#' Lookup MIME type
#'
#' @description This is a helper to determine which MIME type should be used
#' for a file. Three types of input are acceptable:
#'   * Native Google Drive file types. Important examples:
#'     - "document" for Google Docs
#'     - "folder" for folders
#'     - "presentation" for Google Slides
#'     - "spreadsheet" for Google Sheets
#'   * File extensions, such as "pdf", "csv", etc.
#'   * MIME types accepted by Google Drive (these are simply passed through).
#'
#' @param type Character. Google Drive file type, file extension, or MIME type.
#'   Pass the sentinel [`expose()`] if you want to get the full table used for
#'   validation and lookup, i.e. all MIME types known to be relevant to the
#'   Drive API.
#'
#' @return Character. MIME type.
#'
#' @examples
#' ## get the mime type for Google Spreadsheets
#' drive_mime_type("spreadsheet")
#'
#' ## get the mime type for jpegs
#' drive_mime_type("jpeg")
#'
#' ## it's vectorized
#' drive_mime_type(c("presentation", "pdf", "image/gif"))
#'
#' ## see the internal tibble of MIME types known to the Drive API
#' drive_mime_type(expose())
#' @export
drive_mime_type <- function(type = NULL) {
  if (is.null(type)) {
    return(invisible())
  }
  if (is_expose(type)) {
    return(.drive$mime_tbl)
  }
  if (!(is.character(type))) {
    drive_abort("{.arg type} must be character.")
  }

  human_m <- match(
    type,
    .drive$mime_tbl$human_type,
    nomatch = NA_character_,
    incomparables = NA
  )
  ext_m <- match(
    type,
    .drive$mime_tbl$mime_type,
    nomatch = NA_character_,
    incomparables = NA
  )
  m <- ifelse(is.na(human_m), ext_m, human_m)
  mime_type <- .drive$mime_tbl$mime_type[m]

  if (all(is.na(mime_type))) {
    drive_abort(c(
      "Unrecognized {.arg type}:",
      bulletize(gargle_map_cli(type), bullet = "x")
    ))
  }
  mime_type
}

#' Lookup extension from MIME type
#'
#' @description This is a helper to determinine which extension should be used
#' for a file. Two types of input are acceptable:
#'   * MIME types accepted by Google Drive.
#'   * File extensions, such as "pdf", "csv", etc. (these are simply passed through).
#'
#' @param type Character. MIME type or file extension.
#'
#' @return Character. File extension.
#'
#' @examples
#'
#' ## get the extension for mime type image/jpeg
#' drive_extension("image/jpeg")
#'
#' ## it's vectorized
#' drive_extension(c("text/plain", "pdf", "image/gif"))
#' @export
drive_extension <- function(type = NULL) {
  if (is.null(type)) {
    return(invisible())
  }
  stopifnot(is.character(type))

  type <- drive_mime_type(type)
  m <- map_int(type, one_ext)
  .drive$mime_tbl$ext[m]
}

one_ext <- function(type) {
  m <- which(
    .drive$mime_tbl$mime_type %in% type & is_true(.drive$mime_tbl$default)
  )
  if (length(m) == 0L) {
    m <- NA_integer_
  }
  m
}


================================================
FILE: R/drive_mkdir.R
================================================
#' Create a Drive folder
#'
#' Creates a new Drive folder. To update the metadata of an existing Drive file,
#' including a folder, use [drive_update()].
#'
#' @seealso Wraps the `files.create` endpoint:
#'   * <https://developers.google.com/drive/api/v3/reference/files/create>
#'
#' @param name Name for the new folder or, optionally, a path that specifies
#'   an existing parent folder, as well as the new name.
#' @eval param_path_known_parent("folder")
#' @inheritParams drive_create
#'
#' @eval return_dribble()
#' @export
#' @examplesIf drive_has_token()
#' # Create folder named 'ghi', then another below named it 'jkl' and star it
#' ghi <- drive_mkdir("ghi")
#' jkl <- drive_mkdir("ghi/jkl", starred = TRUE)
#'
#' # is 'jkl' really starred? YES
#' purrr::pluck(jkl, "drive_resource", 1, "starred")
#'
#' # Another way to create folder 'mno' in folder 'ghi'
#' drive_mkdir("mno", path = "ghi")
#'
#' # Yet another way to create a folder named 'pqr' in folder 'ghi',
#' # this time with parent folder stored in a dribble,
#' # and setting the new folder's description
#' pqr <- drive_mkdir("pqr", path = ghi, description = "I am a folder")
#'
#' # Did we really set the description? YES
#' purrr::pluck(pqr, "drive_resource", 1, "description")
#'
#' # `overwrite = FALSE` errors if something already exists at target filepath
#' # THIS WILL ERROR!
#' drive_create("name-squatter-mkdir", path = ghi)
#' drive_mkdir("name-squatter-mkdir", path = ghi, overwrite = FALSE)
#'
#' # `overwrite = TRUE` moves the existing item to trash, then proceeds
#' drive_mkdir("name-squatter-mkdir", path = ghi, overwrite = TRUE)
#'
#' # list everything inside 'ghi'
#' drive_ls("ghi")
#'
#' # Clean up
#' drive_rm(ghi)
drive_mkdir <- function(
  name,
  path = NULL,
  ...,
  overwrite = NA,
  verbose = deprecated()
) {
  warn_for_verbose(verbose)

  drive_create(
    name = name,
    path = path,
    type = "application/vnd.google-apps.folder",
    ...,
    overwrite = overwrite
  )
}


================================================
FILE: R/drive_mv.R
================================================
#' Move a Drive file
#'
#' Move a Drive file to a different folder, give it a different name, or both.
#'

#' @seealso Makes a metadata-only request to the `files.update` endpoint:
#'   * <https://developers.google.com/drive/api/v3/reference/files/update>

#' @template file-singular
#' @eval param_path(
#'   thing = "file",
#'   default_notes = "By default, the file stays in its current folder."
#' )
#' @eval param_name(
#'   thing = "file",
#'   default_notes = "By default, the file keeps its current name."
#' )
#' @template overwrite
#' @template verbose
#'
#' @eval return_dribble()
#' @export
#' @examplesIf drive_has_token()
#' # create a file to move
#' file <- drive_example_remote("chicken.txt") |>
#'   drive_cp("chicken-mv.txt")
#'
#' # rename it, but leave in current folder (root folder, in this case)
#' file <- drive_mv(file, "chicken-mv-renamed.txt")
#'
#' # create a folder to move the file into
#' folder <- drive_mkdir("mv-folder")
#'
#' # move the file and rename it again,
#' # specify destination as a dribble
#' file <- drive_mv(file, path = folder, name = "chicken-mv-re-renamed.txt")
#'
#' # verify renamed file is now in the folder
#' drive_ls(folder)
#'
#' # move the file back to root folder
#' file <- drive_mv(file, "~/")
#'
#' # move it again
#' # specify destination as path with trailing slash
#' # to ensure we get a move vs. renaming it to "mv-folder"
#' file <- drive_mv(file, "mv-folder/")
#'
#' # `overwrite = FALSE` errors if something already exists at target filepath
#' # THIS WILL ERROR!
#' drive_create("name-squatter-mv", path = "~/")
#' drive_mv(file, path = "~/", name = "name-squatter-mv", overwrite = FALSE)
#'
#' # `overwrite = TRUE` moves the existing item to trash, then proceeds
#' drive_mv(file, path = "~/", name = "name-squatter-mv", overwrite = TRUE)
#'
#' # Clean up
#' drive_rm(file, folder)
drive_mv <- function(
  file,
  path = NULL,
  name = NULL,
  overwrite = NA,
  verbose = deprecated()
) {
  warn_for_verbose(verbose)

  file <- as_dribble(file)
  file <- confirm_single_file(file)

  if (is.null(path) && is.null(name)) {
    drive_bullets(c(
      "!" = "Nothing to be done."
    ))
    return(invisible(file))
  }

  tmp <- rationalize_path_name(path, name)
  path <- tmp$path
  name <- tmp$name

  params <- list()

  # load (path, name) into params ... maybe
  parent_before <- pluck(file, "drive_resource", 1, "parents", 1)
  if (!is.null(path)) {
    path <- as_parent(path)
    if (path$id != parent_before) {
      params[["addParents"]] <- path$id
      params[["removeParents"]] <- parent_before
    }
  }
  if (!is.null(name) && name != file$name) {
    params[["name"]] <- name
  }

  if (length(params) == 0) {
    drive_bullets(c(
      "!" = "Nothing to be done."
    ))
    return(invisible(file))
  }

  check_for_overwrite(
    parent = params[["addParents"]] %||% parent_before,
    name = params[["name"]] %||% file$name,
    overwrite = overwrite
  )

  params[["fields"]] <- "*"
  out <- drive_update_metadata(file, params)

  actions <- c(
    renamed = !identical(out$name, file$name),
    moved = !is.null(params[["addParents"]])
  )
  action <- glue_collapse(names(actions)[actions], last = " and ")

  drive_bullets(c(
    "Original file:",
    bulletize(gargle_map_cli(file)),
    "Has been {action}:",
    # drive_reveal_path() puts immediate parent, if specified, in the `path`
    # then we reveal `path`, instead of `name`
    bulletize(gargle_map_cli(
      drive_reveal_path(out, ancestors = path),
      template = c(
        id_string = "<id:\u00a0<<id>>>", # \u00a0 is a nonbreaking space
        out = "{.drivepath <<path>>} {cli::col_grey('<<id_string>>')}"
      )
    ))
  ))

  invisible(out)
}


================================================
FILE: R/drive_publish.R
================================================
#' Publish native Google files
#'
#' Publish (or un-publish) native Google files to the web. Native Google files
#' include Google Docs, Google Sheets, and Google Slides. The returned
#' [`dribble`] will have extra columns, `published` and `revisions_resource`.
#' Read more in [drive_reveal()].
#'
#' @seealso Wraps the `revisions.update` endpoint:
#'   * <https://developers.google.com/drive/api/v3/reference/revisions/update>
#'
#' @template file-plural
#' @param ... Name-value pairs to add to the API request body (see API docs
#' linked below for details). For `drive_publish()`, we include
#' `publishAuto = TRUE` and `publishedOutsideDomain = TRUE`, if user does not
#' specify other values.
#' @template verbose
#'
#' @eval return_dribble(extras = "There will be extra columns, `published` and
#'   `revisions_resource`.")
#' @export
#' @examplesIf drive_has_token()
#' # Create a file to publish
#' file <- drive_example_remote("chicken_sheet") |>
#'   drive_cp()
#'
#' # Publish file
#' file <- drive_publish(file)
#' file$published
#'
#' # Unpublish file
#' file <- drive_unpublish(file)
#' file$published
#'
#' # Clean up
#' drive_rm(file)
drive_publish <- function(file, ..., verbose = deprecated()) {
  warn_for_verbose(verbose)
  drive_change_publish(file = file, publish = TRUE, ...)
}

#' @rdname drive_publish
#' @export
drive_unpublish <- function(file, ..., verbose = deprecated()) {
  warn_for_verbose(verbose)
  drive_change_publish(file = file, publish = FALSE, ...)
}

drive_change_publish <- function(file, publish = TRUE, ...) {
  file <- as_dribble(file)
  file <- confirm_some_files(file)

  type_ok <- is_native(file)
  if (!all(type_ok)) {
    file <- file[!type_ok, ]
    file <- promote(file, "mimeType")
    drive_abort(c(
      "Only native Google files can be published.",
      "{.arg file} includes {?a/} file{?s} \\
       with non-native MIME type{cli::qty(nrow(file))}",
      bulletize(gargle_map_cli(
        file,
        "{.drivepath <<name>>}: {.field <<mimeType>>}"
      )),
      "i" = "You can use {.fun drive_share} to change a file's sharing \\
             permissions."
    ))
  }

  params <- toCamel(list2(...))
  params[["published"]] <- publish
  params[["publishAuto"]] <- params[["publishAuto"]] %||% TRUE
  params[["publishedOutsideDomain"]] <-
    params[["publishedOutsideDomain"]] %||% TRUE
  params[["revisionId"]] <- "head"
  params[["fields"]] <- "*"

  revision_resource <- map(
    file$id,
    change_publish_one,
    params = params
  )
  n <- nrow(file)
  drive_bullets(c(
    cli::pluralize(
      "{cli::qty(n)}File{?s} now {if (publish) '' else 'NOT '}published:"
    ),
    bulletize(gargle_map_cli(file))
  ))
  invisible(drive_reveal(file, "published"))
}

change_publish_one <- function(id, params) {
  params[["fileId"]] <- id
  request <- request_generate(
    endpoint = "drive.revisions.update",
    params = params
  )
  response <- request_make(request)
  gargle::response_process(response)
}

drive_reveal_published <- function(file) {
  confirm_dribble(file)
  revision_resource <- map(file$id, get_publish_one)
  file <- put_column(
    file,
    nm = "published",
    val = map_lgl(revision_resource, "published", .default = FALSE),
    .after = 1
  )
  put_column(
    file,
    nm = "revision_resource",
    val = revision_resource
  )
}

get_publish_one <- function(id) {
  request <- request_generate(
    endpoint = "drive.revisions.get",
    params = list(
      fileId = id,
      revisionId = "head",
      fields = "*"
    )
  )
  response <- request_make(request)
  ## folders generate a 403
  if (httr::status_code(response) == 403) {
    return(NULL)
  }
  gargle::response_process(response)
}


================================================
FILE: R/drive_put.R
================================================
#' PUT new media into a Drive file
#'
#' @description
#' PUTs new media into a Drive file, in the HTTP sense:
#' * If the file already exists, we replace its content.
#' * If the file does not already exist, we create a new file.
#'
#' @description
#' This is a convenience wrapper around [`drive_upload()`] and
#' [`drive_update()`]. In pseudo-code:
#'
#' ```
#' target_filepath <- <determined from `path`, `name`, and `media`>
#' hits <- <get all Drive files at target_filepath>
#' if (no hits) {
#'   drive_upload(media, path, name, type, ...)
#' } else if (exactly 1 hit) {
#'   drive_update(hit, media, ...)
#' } else {
#'   ERROR
#' }
#' ```
#'
#' @inheritParams drive_upload
#'
#' @eval return_dribble()
#' @export
#' @examplesIf drive_has_token()
#' # create a local file to work with
#' local_file <- tempfile("drive_put_", fileext = ".txt")
#' writeLines(c("beginning", "middle"), local_file)
#'
#' # PUT to a novel filepath --> drive_put() delegates to drive_upload()
#' file <- drive_put(local_file)
#'
#' # update the local file
#' cat("end", file = local_file, sep = "\n", append = TRUE)
#'
#' # PUT again --> drive_put() delegates to drive_update()
#' file <- drive_put(local_file)
#'
#' # create a second file at this filepath
#' file2 <- drive_create(basename(local_file))
#'
#' # PUT again --> ERROR
#' drive_put(local_file)
#'
#' # Clean up
#' drive_find("drive_put_.+[.]txt") |> drive_rm()
#' unlink(local_file)
drive_put <- function(
  media,
  path = NULL,
  name = NULL,
  ...,
  type = NULL,
  verbose = deprecated()
) {
  warn_for_verbose(verbose)
  if (file.exists(media)) {
    media <- enc2utf8(media)
  } else {
    drive_abort(c(
      "No file exists at the local {.arg media} path:",
      bulletize(gargle_map_cli(media, "{.path <<x>>}"), bullet = "x")
    ))
  }

  tmp <- rationalize_path_name(path, name)
  path <- tmp$path
  name <- tmp$name

  params <- list()

  # load (path, name) into params
  if (!is.null(path)) {
    path <- as_parent(path)
    params[["parents"]] <- path$id
  }
  params[["name"]] <- name %||% basename(media)

  hits <- overwrite_hits(
    parent = params[["parents"]],
    name = params[["name"]],
    overwrite = FALSE
  )

  # Happy Path 1 of 2: no name collision
  if (is.null(hits) || no_file(hits)) {
    drive_bullets(c(
      "i" = "No pre-existing Drive file at this path. Calling \\
             {.fun drive_upload}."
    ))

    return(drive_upload(
      media = media,
      path = as_id(params[["parents"]]),
      name = params[["name"]],
      type = type,
      ...
    ))
  }

  # Happy Path 2 of 2: single name collision
  if (single_file(hits)) {
    drive_bullets(c(
      "i" = "A Drive file already exists at this path. Calling \\
             {.fun drive_update}."
    ))
    return(drive_update(
      hits,
      media = media,
      ...
    ))
  }

  # Unhappy Path: multiple collisions
  drive_abort(c(
    "Multiple items already exist on Drive at the target filepath.",
    "Unclear what {.fun drive_put} should do. Exiting.",
    # drive_reveal_path() puts immediate parent, if specified, in the `path`
    # then we reveal `path`, instead of `name`
    bulletize(gargle_map_cli(
      drive_reveal_path(hits, ancestors = path),
      template = c(
        id_string = "<id:\u00a0<<id>>>", # \u00a0 is a nonbreaking space
        out = "{.drivepath <<path>>} {cli::col_grey('<<id_string>>')}"
      )
    ))
  ))
}


================================================
FILE: R/drive_read.R
================================================
#' Read the content of a Drive file
#'
#' @description These functions return the content of a Drive file as either a
#'   string or raw bytes. You will likely need to do additional work to parse
#'   the content into a useful R object.
#'
#'   [drive_download()] is the more generally useful function, but for certain
#'   file types, such as comma-separated values (MIME type `text/csv`), it can
#'   be handy to read data directly from Google Drive and avoid writing to disk.
#'
#'   Just as for [drive_download()], native Google file types, such as Google
#'   Sheets or Docs, must be exported as a conventional MIME type. See the help
#'   for [drive_download()] for more.

#' @template file-singular
#' @inheritParams drive_download
#' @param encoding Passed along to [httr::content()]. Describes the encoding of
#'   the *input* `file`.

#' @return
#' * `read_drive_string()`: a UTF-8 encoded string
#' * `read_drive_raw()`: a [raw()] vector

#' @export
#' @examplesIf drive_has_token()
#' # comma-separated values --> data.frame or tibble
#' (chicken_csv <- drive_example_remote("chicken.csv"))
#' read.csv(text = chicken_csv |> drive_read_string())
#'
#' # Google Doc --> character vector
#' (chicken_doc <- drive_example_remote("chicken_doc"))
#' chicken_doc |>
#'   # NOTE: we must specify an export MIME type
#'   drive_read_string(type = "text/plain") |>
#'   strsplit(split = "(\r\n|\r|\n)")
#'   (\(x) x[[1]])()
drive_read_string <- function(file, type = NULL, encoding = NULL) {
  drive_read_impl(file = file, type = type, as = "string", encoding = encoding)
}

#' @export
#' @rdname drive_read_string
drive_read_raw <- function(file, type = NULL) {
  drive_read_impl(file = file, type = type, as = "raw")
}

drive_read_impl <- function(
  file,
  type = NULL,
  as = c("string", "raw"),
  encoding = NULL
) {
  as <- match.arg(as)
  file <- as_dribble(file)
  file <- confirm_single_file(file)

  mime_type <- pluck(file, "drive_resource", 1, "mimeType")

  if (!grepl("google", mime_type) && !is.null(type)) {
    drive_bullets(c(
      "!" = "Ignoring {.arg type}. Only consulted for native Google file types.",
      " " = "MIME type of {.arg file}: {.field {mime_type}}."
    ))
  }

  if (grepl("google", mime_type)) {
    export_type <- type %||% get_export_mime_type(mime_type)
    export_type <- drive_mime_type(export_type)
    verify_export_mime_type(mime_type, export_type)

    request <- request_generate(
      endpoint = "drive.files.export",
      params = list(
        fileId = file$id,
        mimeType = export_type
      )
    )
  } else {
    request <- request_generate(
      endpoint = "drive.files.get",
      params = list(
        fileId = file$id,
        alt = "media"
      )
    )
  }

  response <- request_make(
    request,
    httr::write_memory()
  )

  # only call gargle::response_process() for a failed request
  # it's hard-wired to parse a JSON body
  code <- httr::status_code(response)
  if (code < 200 || code >= 300) {
    return(gargle::response_process(response))
  }

  if (as == "string") {
    resp_body_string(response, encoding = encoding)
  } else if (as == "raw") {
    resp_body_raw(response)
  } else {
    drive_abort(c(
      "Internal error: unexpected value for the {.arg as} argument.",
      x = "{.field {as}}"
    ))
  }
}

# stubs for eventual calls to httr2 functions by these same names
resp_body_string <- function(resp, encoding = NULL) {
  out <- httr::content(resp, as = "text", encoding = encoding)
  # Learned this fact the hard way (quoting from Wikipedia):
  # Google Docs also adds a BOM when converting a Doc to a plain text file
  # for download.
  # https://en.wikipedia.org/wiki/Byte_order_mark#UTF-8
  # Therefore we remove such a BOM, if present
  # UTF-8 representation of BOM: ef bb bf
  sub("^\uFEFF", "", out)
}

resp_body_raw <- function(resp) {
  httr::content(resp, as = "raw")
}


================================================
FILE: R/drive_rename.R
================================================
#' Rename a Drive file
#'
#' This is a wrapper for [`drive_mv()`] that only renames a file.
#' If you would like to rename AND move the file, see [`drive_mv()`].
#'
#' @template file-singular
#' @param name Character. Name you would like the file to have.
#' @template overwrite
#' @template verbose
#'
#' @eval return_dribble()
#'
#' @examplesIf drive_has_token()
#' # Create a file to rename
#' file <- drive_create("file-to-rename")
#'
#' # Rename it
#' file <- drive_rename(file, name = "renamed-file")
#'
#' # `overwrite = FALSE` errors if something already exists at target filepath
#' # THIS WILL ERROR!
#' drive_create("name-squatter-rename")
#' drive_rename(file, name = "name-squatter-rename", overwrite = FALSE)
#'
#' # `overwrite = TRUE` moves the existing item to trash, then proceeds
#' file <- drive_rename(file, name = "name-squatter-rename", overwrite = TRUE)
#'
#' # Clean up
#' drive_rm(file)
#' @export
drive_rename <- function(
  file,
  name = NULL,
  overwrite = NA,
  verbose = deprecated()
) {
  warn_for_verbose(verbose)
  drive_mv(file = file, name = name, overwrite = overwrite)
}


================================================
FILE: R/drive_reveal.R
================================================
#' Add a new column of Drive file information
#'
#' @description
#' `drive_reveal()` adds extra information about your Drive files that is not
#' readily available in the default [`dribble`] produced by googledrive. Why is
#' this info not always included in the default `dribble`?
#' * You don't always care about it. There is a lot of esoteric information in
#' the `drive_resource` that has little value for most users.
#' * It might be "expensive" to get this information and put it into a usable
#' form. For example, revealing a file's `"path"`, `"permissions"`, or
#' `"published"` status all require additional API calls.
#'

#' `drive_reveal()` can also **hoist** any property out of the `drive_resource`
#' list-column, when the property's name is passed as the `what` argument. The
#' resulting new column is simplified if it is easy to do so, e.g., if the
#' individual elements are all string or logical. If `what` extracts a
#' date-time, we return [`POSIXct`][DateTimeClasses]. Otherwise, you'll get a
#' list-column. If this makes you sad, consider using `tidyr::hoist()` instead.
#' It is more powerful due to a richer "plucking specification" and its `ptype`
#' and `transform` arguments. Another useful function is
#' `tidyr::unnest_wider()`.
#'
#'

#' @section File path:
#' When `what = "path"` the [`dribble`] gains a character column holding each
#' file's path. This can be *very slow*, so use with caution.
#'
#' The example path `~/a/b/` illustrates two conventions used in googledrive:

#'   * The leading `~/` means that the folder `a` is located in the current
#'   user's "My Drive" root folder.

#'   * The trailing `/` means that `b`, located in `a`, is *a folder or a folder
#'   shortcut*.

#' @section Permissions:
#' When `what = "permissions"` the [`dribble`] gains a logical column `shared`
#' that indicates whether a file is shared and a new list-column
#' `permissions_resource` containing lists of
#' [Permissions resources](https://developers.google.com/drive/api/v3/reference/permissions).
#'
#' @section Publishing:
#' When `what = "published"` the [`dribble`] gains a logical column
#' `published` that indicates whether a file is published and a new list-column
#' `revision_resource` containing lists of
#' [Revisions resources](https://developers.google.com/drive/api/v3/reference/revisions).
#'

#' @section Parent:
#' When `what = "parent"` the [`dribble`] gains a character column `id_parent`
#' that is the file id of this item's parent folder. This information is
#' available in the `drive_resource`, but can't just be hoisted out:
#' * Google Drive used to allow files to have multiple parents, but this is no
#'   longer supported and googledrive now assumes this is impossible. However,
#'   we have seen (very old) files that still have >1 parent folder. If we see
#'   this we message about it and drop all but the first parent.
#' * The `parents` property in `drive_resource` has an "extra" layer of nesting
#'   and needs to be flattened.
#'

#' If you really want the raw `parents` property, call `drive_reveal(what =
#' "parents")`.

#' @template file-plural

#' @param what Character, describing the type of info you want to add. These
#'   values get special handling (more details below):
#'   * `path`
#'   * `permissions`
#'   * `published`
#'   * `parent`
#'

#'   You can also request any property in the `drive_resource` column by name.
#'   The request can be in `camelCase` or `snake_case`, but the new column name
#'   will always be `snake_case`. Some examples of `what`:

#'   * `mime_type` (or `mimeType`)
#'   * `trashed`
#'   * `starred`
#'   * `description`
#'   * `version`
#'   * `web_view_link` (or `webViewLink`)
#'   * `modified_time` (or `modifiedTime`)
#'   * `created_time` (or `createdTime`)
#'   * `owned_by_me` (or `ownedByMe`)
#'   * `size`
#'   * `quota_bytes_used` (or `quotaBytesUsed`)
#'
#' @eval return_dribble(extras = "The additional info requested via `what`
#'   appears in one (or more) extra columns.")
#'

#' @seealso To learn more about the properties present in the metadata of a
#'   Drive file (which is what's in the `drive_resource` list-column of a
#'   [`dribble`]), see the API docs:

#'   * <https://developers.google.com/drive/api/v3/reference/files#resource-representations>
#'

#' @export
#' @examplesIf drive_has_token()
#' # Get a few of your files
#' files <- drive_find(n_max = 10, trashed = NA)
#'
#' # the "special" cases that require additional API calls and can be slow
#' drive_reveal(files, "path")
#' drive_reveal(files, "permissions")
#' drive_reveal(files, "published")
#'
#' # a "special" case of digging info out of `drive_resource`, then processing
#' # a bit
#' drive_reveal(files, "parent")
#'
#' # the "simple" cases of digging info out of `drive_resource`
#' drive_reveal(files, "trashed")
#' drive_reveal(files, "mime_type")
#' drive_reveal(files, "starred")
#' drive_reveal(files, "description")
#' drive_reveal(files, "version")
#' drive_reveal(files, "web_view_link")
#' drive_reveal(files, "modified_time")
#' drive_reveal(files, "created_time")
#' drive_reveal(files, "owned_by_me")
#' drive_reveal(files, "size")
#' drive_reveal(files, "quota_bytes_used")
#'
#' # 'root' is a special file id that represents your My Drive root folder
#' drive_get(id = "root") |>
#'   drive_reveal("path")
drive_reveal <- function(
  file,
  what = c("path", "permissions", "published", "parent")
) {
  stopifnot(is_string(what))
  file <- as_dribble(file)

  if (what %in% c("path", "permissions", "published", "parent")) {
    reveal <- switch(
      what,
      "path" = drive_reveal_canonical_path,
      "permissions" = drive_reveal_permissions,
      "published" = drive_reveal_published,
      "parent" = drive_reveal_parent
    )
    return(reveal(file))
  }

  drive_reveal_this(file, what)
}

drive_reveal_this <- function(file, this) {
  elem_snake_case <- snake_case(this)
  is_dttm <- grepl("_time$", elem_snake_case)

  if (no_file(file)) {
    return(
      put_column(
        file,
        nm = elem_snake_case,
        val = list(),
        .after = "name"
      )
    )
  }

  out <- promote(file, elem_snake_case)

  if (is_dttm && is.character(out[[elem_snake_case]])) {
    out[[elem_snake_case]] <- as.POSIXct(
      out[[elem_snake_case]],
      format = "%Y-%m-%dT%H:%M:%OSZ",
      tz = "UTC"
    )
  }

  out
}

drive_reveal_parent <- function(file) {
  confirm_dribble(file)

  file <- drive_reveal(file, "parents")
  # due to the historical use of multiple parents, there is a gratuitous level
  # of nesting here
  file$parents <- map(file$parents, 1)

  n_parents <- lengths(file$parents)
  has_multiple_parents <- n_parents > 1
  if (any(has_multiple_parents)) {
    drive_bullets(c(
      "{sum(has_multiple_parents)} file{?s} {?has/have} >1 parent, which is a \\
       remnant of legacy Drive behaviour:",
      bulletize(gargle_map_cli(file[has_multiple_parents, ])),
      "!" = "Only the first parent will be used"
    ))
  }

  file <- put_column(
    file,
    nm = "id_parent",
    val = as_id(map_chr(file$parents, 1, .default = NA)),
    .after = "name"
  )
  file$parents <- NULL
  file
}


================================================
FILE: R/drive_rm.R
================================================
#' Delete files from Drive
#'
#' Caution: this will permanently delete your files! For a safer, reversible
#' option, see [drive_trash()].
#'
#' @seealso Wraps the `files.delete` endpoint:
#'   * <https://developers.google.com/drive/api/v3/reference/files/delete>
#'
#' @param ... One or more Drive files, specified in any valid way, i.e. as a
#' [`dribble`], by name or path, or by file id or URL marked with [as_id()]. Or
#' any combination thereof. Elements are processed with [as_dribble()] and
#' row-bound prior to deletion.
#' @template verbose
#'
#' @return Logical vector, indicating whether the delete succeeded.
#' @export
#'
#' @examplesIf drive_has_token()
#' # Target one of the official example files to copy (then remove)
#' (src_file <- drive_example_remote("chicken.txt"))
#'
#' # Create a copy, then remove it by name
#' src_file |>
#'   drive_cp(name = "chicken-rm.txt")
#' drive_rm("chicken-rm.txt")
#'
#' # Create several more copies
#' x1 <- src_file |>
#'   drive_cp(name = "chicken-abc.txt")
#' drive_cp(src_file, name = "chicken-def.txt")
#' x2 <- src_file |>
#'   drive_cp(name = "chicken-ghi.txt")
#'
#' # Remove the copies all at once, specified in different ways
#' drive_rm(x1, "chicken-def.txt", as_id(x2))
drive_rm <- function(..., verbose = deprecated()) {
  warn_for_verbose(verbose)
  dots <- list(...)
  if (length(dots) == 0) {
    dots <- list(NULL)
  }

  # explicitly select on var name to exclude 'path', if present
  file <- map(dots, ~ as_dribble(.x)[c("name", "id", "drive_resource")])
  file <- vec_rbind(!!!file)
  # filter to the unique file ids (multiple parents mean drive_get() and
  # therefore as_dribble() can return >1 row representing a single file)
  file <- file[!duplicated(file$id), ]

  if (no_file(file)) {
    drive_bullets(c(
      "!" = "No such file to delete."
    ))
    return(invisible(file))
  }

  out <- map_lgl(file$id, delete_one)

  if (any(out)) {
    successes <- file[out, ]
    drive_bullets(c(
      "File{?s} deleted:{cli::qty(nrow(successes))}",
      bulletize(gargle_map_cli(successes))
    ))
  }
  # I'm not sure this ever comes up IRL?
  # Is it even possible that removal fails but there's no error?
  if (any(!out)) {
    failures <- file[!out, ]
    drive_bullets(c(
      "File{?s} NOT deleted:{cli::qty(nrow(failures))}",
      bulletize(gargle_map_cli(failures))
    ))
  }
  invisible(out)
}

delete_one <- function(id) {
  request <- request_generate(
    endpoint = "drive.files.delete",
    params = list(fileId = id)
  )
  response <- request_make(request)
  gargle::response_process(response)
}


================================================
FILE: R/drive_share.R
================================================
#' Share Drive files
#'
#' @description
#' Grant individuals or other groups access to files, including permission to
#' read, comment, or edit. The returned [`dribble`] will have extra columns,
#' `shared` and `permissions_resource`. Read more in [drive_reveal()].
#'
#' `drive_share_anyone()` is a convenience wrapper for a common special case:
#' "make this `file` readable by 'anyone with a link'".
#'
#' @seealso
#' Wraps the `permissions.create` endpoint:
#' * <https://developers.google.com/drive/api/v3/reference/permissions/create>
#'
#' Drive roles and permissions are described here:
#' * <https://developers.google.com/drive/api/v3/ref-roles>
#'
#' @template file-plural
#' @param role Character. The role to grant. Must be one of:
#'   * owner (not allowed in shared drives)
#'   * organizer (applies to shared drives)
#'   * fileOrganizer (applies to shared drives)
#'   * writer
#'   * commenter
#'   * reader
#' @param type Character. Describes the grantee. Must be one of:
#'   * user
#'   * group
#'   * domain
#'   * anyone
#' @param ... Name-value pairs to add to the API request. This is where you
#'   provide additional information, such as the `emailAddress` (when grantee
#'   `type` is `"group"` or `"user"`) or the `domain` (when grantee type is
#'   `"domain"`). Read the API docs linked below for more details.
#' @template verbose
#'
#' @eval return_dribble(extras = "There will be extra columns, `shared` and
#'   `permissions_resource`.")
#' @export
#' @examplesIf drive_has_token()
#' # Create a file to share
#' file <- drive_example_remote("chicken_doc") |>
#'   drive_cp(name = "chicken-share.txt")
#'
#' # Let a specific person comment
#' file <- file |>
#'   drive_share(
#'     role = "commenter",
#'     type = "user",
#'     emailAddress = "susan@example.com"
#'   )
#'
#' # Let a different specific person edit and customize the email notification
#' file <- file |>
#'   drive_share(
#'     role = "writer",
#'     type = "user",
#'     emailAddress = "carol@example.com",
#'     emailMessage = "Would appreciate your feedback on this!"
#'   )
#'
#' # Let anyone read the file
#' file <- file |>
#'   drive_share(role = "reader", type = "anyone")
#' # Single-purpose wrapper function for this
#' drive_share_anyone(file)
#'
#' # Clean up
#' drive_rm(file)
drive_share <- function(
  file,
  role = c(
    "reader",
    "commenter",
    "writer",
    "fileOrganizer",
    "owner",
    "organizer"
  ),
  type = c("user", "group", "domain", "anyone"),
  ...,
  verbose = deprecated()
) {
  warn_for_verbose(verbose)

  role <- match.arg(role)
  type <- match.arg(type)
  file <- as_dribble(file)
  file <- confirm_some_files(file)

  params <- toCamel(list2(...))
  params[["role"]] <- role
  params[["type"]] <- type
  params[["fields"]] <- "*"
  ## this resource pertains only to the affected permission
  permission_out <- map(
    file$id,
    drive_share_one,
    params = params
  )

  ok <- map_chr(permission_out, "type") == type
  if (any(ok)) {
    successes <- file[ok, ]
    drive_bullets(c(
      "Permissions updated:",
      "*" = "role = {role}",
      "*" = "type = {type}",
      "For file{?s}:{cli::qty(nrow(successes))}",
      bulletize(gargle_map_cli(successes))
    ))
  }
  # I'm not sure this ever comes up IRL?
  # Is it even possible that permission update fails but there's no error?
  if (any(!ok)) {
    failures <- file[!ok, ]
    drive_bullets(c(
      "Permissions were NOT updated for file{?s}:{cli::qty(nrow(failures))}",
      bulletize(gargle_map_cli(failures))
    ))
  }

  ## refresh drive_resource, get full permissions_resource
  out <- drive_get(as_id(file))
  invisible(drive_reveal(out, "permissions"))
}

#' @rdname drive_share
#' @export
drive_share_anyone <- function(file, verbose = deprecated()) {
  warn_for_verbose(verbose)
  drive_share(file = file, role = "reader", type = "anyone")
}

drive_share_one <- function(id, params) {
  params[["fileId"]] <- id
  request <- request_generate(
    endpoint = "drive.permissions.create",
    params = params
  )
  response <- request_make(request)
  gargle::response_process(response)
}

drive_reveal_permissions <- function(file) {
  confirm_dribble(file)
  permissions_resource <- map(file$id, list_permissions_one)
  # TODO: revisit this in light of Team Drives --> shared drives
  ## can't use promote() here (yet) because Team Drive files don't have
  ## `shared` and their NULLs would force `shared` to be a list-column
  file <- put_column(
    file,
    nm = "shared",
    val = map_lgl(file$drive_resource, "shared", .default = NA),
    .after = "name"
  )
  put_column(
    file,
    nm = "permissions_resource",
    val = permissions_resource
  )
}

list_permissions_one <- function(id) {
  request <- request_generate(
    endpoint = "drive.permissions.list",
    params = list(
      fileId = id,
      fields = "*"
    )
  )
  # TODO: is this still a problem for shared drives? probably
  # TO DO: we aren't dealing with the fact that this endpoint is paginated
  # for Team Drives
  response <- request_make(request)
  # if capabilities/canReadRevisions (present in File resource) is not true,
  # user will get a 403 "insufficientFilePermissions" here
  if (httr::status_code(response) == 403) {
    return(NULL)
  }
  gargle::response_process(response)
}


================================================
FILE: R/drive_trash.R
================================================
#' Move Drive files to or from trash
#' @template file-plural
#' @templ
Download .txt
gitextract_wxrorbn_/

├── .Rbuildignore
├── .covrignore
├── .github/
│   ├── .gitignore
│   ├── CODEOWNERS
│   ├── CODE_OF_CONDUCT.md
│   ├── CONTRIBUTING.Rmd
│   ├── CONTRIBUTING.md
│   ├── ISSUE_TEMPLATE/
│   │   └── issue_template.md
│   ├── SUPPORT.md
│   └── workflows/
│       ├── R-CMD-check.yaml
│       ├── format-suggest.yaml
│       ├── pkgdown.yaml
│       ├── pr-commands.yaml
│       ├── test-coverage.yaml
│       └── with-auth.yml
├── .gitignore
├── .vscode/
│   ├── extensions.json
│   └── settings.json
├── DESCRIPTION
├── LICENSE
├── LICENSE.md
├── NAMESPACE
├── NEWS.md
├── R/
│   ├── aaa.R
│   ├── camelCase.R
│   ├── compat-dplyr.R
│   ├── compat-vctrs.R
│   ├── deprecated.R
│   ├── dribble.R
│   ├── drive_about.R
│   ├── drive_auth.R
│   ├── drive_browse.R
│   ├── drive_cp.R
│   ├── drive_create.R
│   ├── drive_download.R
│   ├── drive_endpoints.R
│   ├── drive_examples.R
│   ├── drive_fields.R
│   ├── drive_find.R
│   ├── drive_get.R
│   ├── drive_get_path.R
│   ├── drive_id-class.R
│   ├── drive_ls.R
│   ├── drive_mime_type.R
│   ├── drive_mkdir.R
│   ├── drive_mv.R
│   ├── drive_publish.R
│   ├── drive_put.R
│   ├── drive_read.R
│   ├── drive_rename.R
│   ├── drive_reveal.R
│   ├── drive_rm.R
│   ├── drive_share.R
│   ├── drive_trash.R
│   ├── drive_update.R
│   ├── drive_upload.R
│   ├── drive_user.R
│   ├── googledrive-package.R
│   ├── promote.R
│   ├── request_generate.R
│   ├── request_make.R
│   ├── roxygen-templates.R
│   ├── shared_drive_create.R
│   ├── shared_drive_find.R
│   ├── shared_drive_get.R
│   ├── shared_drive_rm.R
│   ├── shared_drive_update.R
│   ├── shared_drives.R
│   ├── shortcut.R
│   ├── sysdata.rda
│   ├── team_drive.R
│   ├── utils-io.R
│   ├── utils-paths.R
│   ├── utils-pipe.R
│   ├── utils-ui.R
│   ├── utils.R
│   └── zzz.R
├── README.Rmd
├── README.md
├── _pkgdown.yml
├── air.toml
├── codecov.yml
├── cran-comments.md
├── data-raw/
│   ├── discovery-doc-ingest.R
│   ├── drive-examples-create.R
│   ├── drive-examples-inventory.R
│   ├── drive-v3_2025-08-29.json
│   ├── export-mime-type-defaults.csv
│   ├── extension-mime-type-defaults.csv
│   ├── file-fields.R
│   ├── mime-types-and-file-extensions.R
│   ├── mime-types-google.R
│   └── old/
│       ├── 20170519_drive-v3_discovery-document.json
│       ├── 20170519_drive-v3_endpoints-list.json
│       ├── 20170519_drive-v3_endpoints-list.rds
│       ├── 20170519_drive-v3_endpoints-tibble.rds
│       ├── 20170721_drive-v3_discovery-document.json
│       ├── 20170721_drive-v3_endpoints-list.json
│       ├── 20170721_drive-v3_endpoints-list.rds
│       ├── 20170721_drive-v3_endpoints-tibble.rds
│       ├── 20171110_drive-v3_discovery-document.json
│       ├── 20171110_drive-v3_endpoints-list.json
│       ├── 20171110_drive-v3_endpoints-list.rds
│       ├── 20171110_drive-v3_endpoints-tibble.rds
│       ├── discovery-doc-prep.R
│       ├── drive-v3_2019-02-14.json
│       ├── drive-v3_2019-07-08.json
│       ├── drive-v3_2020-04-17.json
│       ├── drive-v3_2021-03-22.json
│       └── drive-v3_2021-06-21.json
├── googledrive.Rproj
├── index.Rmd
├── index.md
├── inst/
│   ├── WORDLIST
│   ├── extdata/
│   │   ├── data/
│   │   │   ├── client_secret_123.googleusercontent.com.json
│   │   │   ├── files_fields.csv
│   │   │   ├── mime_tbl.csv
│   │   │   ├── remote_example_files.csv
│   │   │   └── translate_mime_types.csv
│   │   └── example_files/
│   │       ├── chicken.csv
│   │       ├── chicken.txt
│   │       ├── imdb_latin1.csv
│   │       ├── markdown.md
│   │       └── r_about.html
│   └── secret/
│       ├── googledrive-docs.json
│       └── googledrive-testing.json
├── man/
│   ├── as_dribble.Rd
│   ├── as_shared_drive.Rd
│   ├── deprecated-team-drive-functions.Rd
│   ├── dribble-checks.Rd
│   ├── dribble.Rd
│   ├── drive_about.Rd
│   ├── drive_auth.Rd
│   ├── drive_auth_configure.Rd
│   ├── drive_browse.Rd
│   ├── drive_cp.Rd
│   ├── drive_create.Rd
│   ├── drive_deauth.Rd
│   ├── drive_download.Rd
│   ├── drive_empty_trash.Rd
│   ├── drive_endpoints.Rd
│   ├── drive_examples.Rd
│   ├── drive_extension.Rd
│   ├── drive_fields.Rd
│   ├── drive_find.Rd
│   ├── drive_get.Rd
│   ├── drive_has_token.Rd
│   ├── drive_id.Rd
│   ├── drive_link.Rd
│   ├── drive_ls.Rd
│   ├── drive_mime_type.Rd
│   ├── drive_mkdir.Rd
│   ├── drive_mv.Rd
│   ├── drive_publish.Rd
│   ├── drive_put.Rd
│   ├── drive_read_string.Rd
│   ├── drive_rename.Rd
│   ├── drive_reveal.Rd
│   ├── drive_rm.Rd
│   ├── drive_scopes.Rd
│   ├── drive_share.Rd
│   ├── drive_token.Rd
│   ├── drive_trash.Rd
│   ├── drive_update.Rd
│   ├── drive_upload.Rd
│   ├── drive_user.Rd
│   ├── expose.Rd
│   ├── googledrive-configuration.Rd
│   ├── googledrive-deprecated.Rd
│   ├── googledrive-package.Rd
│   ├── pipe.Rd
│   ├── request_generate.Rd
│   ├── request_make.Rd
│   ├── shared_drive_create.Rd
│   ├── shared_drive_find.Rd
│   ├── shared_drive_get.Rd
│   ├── shared_drive_rm.Rd
│   ├── shared_drive_update.Rd
│   ├── shared_drives.Rd
│   ├── shortcut_create.Rd
│   └── shortcut_resolve.Rd
├── man-roxygen/
│   ├── corpus.R
│   ├── dots-metadata.R
│   ├── file-plural.R
│   ├── file-singular.R
│   ├── media.R
│   ├── n_max.R
│   ├── overwrite.R
│   ├── pattern.R
│   ├── shared-drive-description.R
│   ├── shared_drive-plural.R
│   ├── shared_drive-singular.R
│   ├── team-drives-description.R
│   ├── team_drive-plural.R
│   ├── team_drive-singular.R
│   └── verbose.R
├── revdep/
│   ├── .gitignore
│   ├── README.md
│   ├── cran.md
│   ├── failures.md
│   └── problems.md
├── tests/
│   ├── spelling.R
│   ├── testthat/
│   │   ├── .gitignore
│   │   ├── _snaps/
│   │   │   ├── deprecated.md
│   │   │   ├── dribble.md
│   │   │   ├── drive_auth.md
│   │   │   ├── drive_cp.md
│   │   │   ├── drive_create.md
│   │   │   ├── drive_download.md
│   │   │   ├── drive_examples.md
│   │   │   ├── drive_fields.md
│   │   │   ├── drive_find.md
│   │   │   ├── drive_get.md
│   │   │   ├── drive_id-class.md
│   │   │   ├── drive_ls.md
│   │   │   ├── drive_mime_type.md
│   │   │   ├── drive_mv.md
│   │   │   ├── drive_publish.md
│   │   │   ├── drive_put.md
│   │   │   ├── drive_reveal.md
│   │   │   ├── drive_share.md
│   │   │   ├── drive_update.md
│   │   │   ├── drive_upload.md
│   │   │   ├── request_generate.md
│   │   │   ├── shared_drives.md
│   │   │   ├── shortcut.md
│   │   │   ├── utils-paths.md
│   │   │   └── utils-ui.md
│   │   ├── driver.R
│   │   ├── helper.R
│   │   ├── setup-testing.R
│   │   ├── test-camelCase.R
│   │   ├── test-compat-dplyr.R
│   │   ├── test-compat-vctrs.R
│   │   ├── test-deprecated.R
│   │   ├── test-dribble.R
│   │   ├── test-drive_auth.R
│   │   ├── test-drive_browse.R
│   │   ├── test-drive_cp.R
│   │   ├── test-drive_create.R
│   │   ├── test-drive_download.R
│   │   ├── test-drive_endpoints.R
│   │   ├── test-drive_examples.R
│   │   ├── test-drive_fields.R
│   │   ├── test-drive_find.R
│   │   ├── test-drive_get.R
│   │   ├── test-drive_get_path.R
│   │   ├── test-drive_id-class.R
│   │   ├── test-drive_ls.R
│   │   ├── test-drive_mime_type.R
│   │   ├── test-drive_mv.R
│   │   ├── test-drive_publish.R
│   │   ├── test-drive_put.R
│   │   ├── test-drive_read.R
│   │   ├── test-drive_reveal.R
│   │   ├── test-drive_rm.R
│   │   ├── test-drive_share.R
│   │   ├── test-drive_trash.R
│   │   ├── test-drive_update.R
│   │   ├── test-drive_upload.R
│   │   ├── test-drive_user.R
│   │   ├── test-fixtures/
│   │   │   ├── just_a_dribble.rds
│   │   │   └── mix_of_files_and_teamdrives.rds
│   │   ├── test-promote.R
│   │   ├── test-request_generate.R
│   │   ├── test-shared_drives.R
│   │   ├── test-shortcut.R
│   │   ├── test-utils-paths.R
│   │   ├── test-utils-ui.R
│   │   └── test-utils.R
│   └── testthat.R
└── vignettes/
    ├── .gitignore
    ├── articles/
    │   ├── .gitignore
    │   ├── bring-your-own-client.Rmd
    │   ├── example-files.Rmd
    │   ├── file-identification.Rmd
    │   ├── messages-and-errors.Rmd
    │   ├── multiple-files.Rmd
    │   └── permissions.Rmd
    └── googledrive.Rmd
Condensed preview — 280 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (2,714K chars).
[
  {
    "path": ".Rbuildignore",
    "chars": 430,
    "preview": "^.*\\.Rproj$\n^\\.Rproj\\.user$\n^scratch.R$\n^internal$\n^data-raw$\n^\\.httr-oauth$\n^\\.httr-oauth-SUSPENDED$\n^\\.travis\\.yml$\n^c"
  },
  {
    "path": ".covrignore",
    "chars": 40,
    "preview": "R/aaa.R\nR/deprecated.R\nR/dplyr-compat.R\n"
  },
  {
    "path": ".github/.gitignore",
    "chars": 7,
    "preview": "*.html\n"
  },
  {
    "path": ".github/CODEOWNERS",
    "chars": 123,
    "preview": "# CODEOWNERS for googledrive\n# https://www.tidyverse.org/development/understudies\n.github/CODEOWNERS @jennybc @lucymcgow"
  },
  {
    "path": ".github/CODE_OF_CONDUCT.md",
    "chars": 5244,
    "preview": "# Contributor Covenant Code of Conduct\n\n## Our Pledge\n\nWe as members, contributors, and leaders pledge to make participa"
  },
  {
    "path": ".github/CONTRIBUTING.Rmd",
    "chars": 11184,
    "preview": "---\ntitle: \"Contributing to googledrive\"\noutput:\n  github_document:\n    toc: true\n    toc_depth: 3\n---\n\n## Making a pull"
  },
  {
    "path": ".github/CONTRIBUTING.md",
    "chars": 11710,
    "preview": "Contributing to googledrive\n================\n\n-   [Making a pull request](#making-a-pull-request)\n-   [Package philosoph"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/issue_template.md",
    "chars": 466,
    "preview": "Please briefly describe your problem and what output you expect. If you have a question, please don't use this form. Ins"
  },
  {
    "path": ".github/SUPPORT.md",
    "chars": 1945,
    "preview": "# Getting help with googledrive\n\nThanks for using googledrive. Before filing an issue, there are a few places\nto explore"
  },
  {
    "path": ".github/workflows/R-CMD-check.yaml",
    "chars": 1856,
    "preview": "# Workflow derived from https://github.com/r-lib/actions/tree/v2/examples\n# Need help debugging build failures? Start at"
  },
  {
    "path": ".github/workflows/format-suggest.yaml",
    "chars": 1683,
    "preview": "# Workflow derived from https://github.com/posit-dev/setup-air/tree/main/examples\n\non:\n  # Using `pull_request_target` o"
  },
  {
    "path": ".github/workflows/pkgdown.yaml",
    "chars": 1699,
    "preview": "# Workflow derived from https://github.com/r-lib/actions/tree/v2/examples\n# Need help debugging build failures? Start at"
  },
  {
    "path": ".github/workflows/pr-commands.yaml",
    "chars": 2501,
    "preview": "# Workflow derived from https://github.com/r-lib/actions/tree/v2/examples\n# Need help debugging build failures? Start at"
  },
  {
    "path": ".github/workflows/test-coverage.yaml",
    "chars": 2175,
    "preview": "# Workflow derived from https://github.com/r-lib/actions/tree/v2/examples\n# Need help debugging build failures? Start at"
  },
  {
    "path": ".github/workflows/with-auth.yml",
    "chars": 1013,
    "preview": "# Workflow derived from https://github.com/r-lib/actions/tree/v2/examples\n# Need help debugging build failures? Start at"
  },
  {
    "path": ".gitignore",
    "chars": 122,
    "preview": "sandbox\nscratch.R\n*.Rproj.user\n.Rhistory\n.Rproj.user\n.RData\n.httr-oauth*\n.Rapp.history\n*token.rds\ninst/doc\ndocs/\ninterna"
  },
  {
    "path": ".vscode/extensions.json",
    "chars": 62,
    "preview": "{\n    \"recommendations\": [\n        \"Posit.air-vscode\"\n    ]\n}\n"
  },
  {
    "path": ".vscode/settings.json",
    "chars": 227,
    "preview": "{\n    \"[r]\": {\n        \"editor.formatOnSave\": true,\n        \"editor.defaultFormatter\": \"Posit.air-vscode\"\n    },\n    \"[q"
  },
  {
    "path": "DESCRIPTION",
    "chars": 1162,
    "preview": "Package: googledrive\nTitle: An Interface to Google Drive\nVersion: 2.1.2.9000\nAuthors@R: c(\n    person(\"Lucy\", \"D'Agostin"
  },
  {
    "path": "LICENSE",
    "chars": 49,
    "preview": "YEAR: 2023\nCOPYRIGHT HOLDER: googledrive authors\n"
  },
  {
    "path": "LICENSE.md",
    "chars": 1078,
    "preview": "# MIT License\n\nCopyright (c) 2023 googledrive authors\n\nPermission is hereby granted, free of charge, to any person obtai"
  },
  {
    "path": "NAMESPACE",
    "chars": 3989,
    "preview": "# Generated by roxygen2: do not edit by hand\n\nS3method(\"[\",dribble)\nS3method(\"names<-\",dribble)\nS3method(as_dribble,\"NUL"
  },
  {
    "path": "NEWS.md",
    "chars": 19911,
    "preview": "# googledrive (development version)\n\n# googledrive 2.1.2\n\n* `drive_upload()` and `drive_download()` support the conversi"
  },
  {
    "path": "R/aaa.R",
    "chars": 822,
    "preview": "# environment to hold data about the Drive API\n.drive <- new.env(parent = emptyenv())\n\n.drive$translate_mime_types <-\n  "
  },
  {
    "path": "R/camelCase.R",
    "chars": 1256,
    "preview": "# camelCase() and toCamel() taken from\n# https://github.com/r-dbi/bigrquery/blob/main/R/camelCase.R\n\n# in theory, belong"
  },
  {
    "path": "R/compat-dplyr.R",
    "chars": 906,
    "preview": "dribble_maybe_reconstruct <- function(data, template) {\n  if (dribble_is_reconstructable(data)) {\n    # in workflowsets,"
  },
  {
    "path": "R/compat-vctrs.R",
    "chars": 4049,
    "preview": "# based on https://github.com/tidymodels/workflowsets/blob/main/R/compat-vctrs.R\n\n# ------------------------------------"
  },
  {
    "path": "R/deprecated.R",
    "chars": 1943,
    "preview": "#' Deprecated googledrive functions\n#'\n#' @description\n#' `r lifecycle::badge(\"deprecated\")`\n#'\n#' @section `drive_auth_"
  },
  {
    "path": "R/dribble.R",
    "chars": 11820,
    "preview": "#' dribble object\n#'\n#' @description googledrive stores the metadata for one or more Drive files or\n#'   shared drives a"
  },
  {
    "path": "R/drive_about.R",
    "chars": 946,
    "preview": "#' Get info on Drive capabilities\n#'\n#' Gets information about the user, the user's Drive, and system capabilities.\n#' T"
  },
  {
    "path": "R/drive_auth.R",
    "chars": 10954,
    "preview": "# This file is the interface between googledrive and the\n# auth functionality in gargle.\n\n# Initialization happens in .o"
  },
  {
    "path": "R/drive_browse.R",
    "chars": 1526,
    "preview": "#' Retrieve Drive file links\n#'\n#' Returns the `\"webViewLink\"` for one or more files, which is the \"link for\n#' opening "
  },
  {
    "path": "R/drive_cp.R",
    "chars": 3647,
    "preview": "#' Copy a Drive file\n#'\n#' Copies an existing Drive file into a new file id.\n#'\n#' @seealso Wraps the `files.copy` endpo"
  },
  {
    "path": "R/drive_create.R",
    "chars": 4039,
    "preview": "#' Create a new blank Drive file\n#'\n#' Creates a new blank Drive file. Note there are better options for these\n#' specia"
  },
  {
    "path": "R/drive_download.R",
    "chars": 5168,
    "preview": "#' Download a Drive file\n#'\n#' @description This function downloads a file from Google Drive. Native Google\n#'   file ty"
  },
  {
    "path": "R/drive_endpoints.R",
    "chars": 1798,
    "preview": "#' List Drive endpoints\n#'\n#' @description\n#' The googledrive package stores a named list of Drive API v3 endpoints (or\n"
  },
  {
    "path": "R/drive_examples.R",
    "chars": 4004,
    "preview": "#' Example files\n#'\n#' googledrive makes a variety of example files -- both local and remote --\n#' available for use in "
  },
  {
    "path": "R/drive_fields.R",
    "chars": 3007,
    "preview": "#' Request partial resources\n#'\n#' @description You may be able to improve the performance of your API calls by\n#'   req"
  },
  {
    "path": "R/drive_find.R",
    "chars": 8899,
    "preview": "#' Find files on Google Drive\n#'\n#' This is the closest googledrive function to what you can do at\n#' <https://drive.goo"
  },
  {
    "path": "R/drive_get.R",
    "chars": 6421,
    "preview": "#' Get Drive files by path or id\n#'\n#' Retrieves metadata for files specified via `path` or via file `id`. This\n#' funct"
  },
  {
    "path": "R/drive_get_path.R",
    "chars": 10120,
    "preview": "# all the helpers behind:\n# drive_get(path =)\n# drive_reveal(what = \"path\")\n\ndrive_reveal_path <- function(x, ancestors "
  },
  {
    "path": "R/drive_id-class.R",
    "chars": 4629,
    "preview": "#' `drive_id` class\n#'\n#' @description\n\n#' `drive_id` is an S3 class to mark strings as Drive file ids, in order to\n#' d"
  },
  {
    "path": "R/drive_ls.R",
    "chars": 2874,
    "preview": "#' List contents of a folder or shared drive\n#'\n#' List the contents of a folder or shared drive, recursively or not. Th"
  },
  {
    "path": "R/drive_mime_type.R",
    "chars": 2841,
    "preview": "#' Lookup MIME type\n#'\n#' @description This is a helper to determine which MIME type should be used\n#' for a file. Three"
  },
  {
    "path": "R/drive_mkdir.R",
    "chars": 1981,
    "preview": "#' Create a Drive folder\n#'\n#' Creates a new Drive folder. To update the metadata of an existing Drive file,\n#' includin"
  },
  {
    "path": "R/drive_mv.R",
    "chars": 3711,
    "preview": "#' Move a Drive file\n#'\n#' Move a Drive file to a different folder, give it a different name, or both.\n#'\n\n#' @seealso M"
  },
  {
    "path": "R/drive_publish.R",
    "chars": 3695,
    "preview": "#' Publish native Google files\n#'\n#' Publish (or un-publish) native Google files to the web. Native Google files\n#' incl"
  },
  {
    "path": "R/drive_put.R",
    "chars": 3410,
    "preview": "#' PUT new media into a Drive file\n#'\n#' @description\n#' PUTs new media into a Drive file, in the HTTP sense:\n#' * If th"
  },
  {
    "path": "R/drive_read.R",
    "chars": 3887,
    "preview": "#' Read the content of a Drive file\n#'\n#' @description These functions return the content of a Drive file as either a\n#'"
  },
  {
    "path": "R/drive_rename.R",
    "chars": 1109,
    "preview": "#' Rename a Drive file\n#'\n#' This is a wrapper for [`drive_mv()`] that only renames a file.\n#' If you would like to rena"
  },
  {
    "path": "R/drive_reveal.R",
    "chars": 7161,
    "preview": "#' Add a new column of Drive file information\n#'\n#' @description\n#' `drive_reveal()` adds extra information about your D"
  },
  {
    "path": "R/drive_rm.R",
    "chars": 2595,
    "preview": "#' Delete files from Drive\n#'\n#' Caution: this will permanently delete your files! For a safer, reversible\n#' option, se"
  },
  {
    "path": "R/drive_share.R",
    "chars": 5308,
    "preview": "#' Share Drive files\n#'\n#' @description\n#' Grant individuals or other groups access to files, including permission to\n#'"
  },
  {
    "path": "R/drive_trash.R",
    "chars": 2580,
    "preview": "#' Move Drive files to or from trash\n#' @template file-plural\n#' @template verbose\n#'\n#' @eval return_dribble()\n#' @expo"
  },
  {
    "path": "R/drive_update.R",
    "chars": 3917,
    "preview": "#' Update an existing Drive file\n#'\n#' Update an existing Drive file id with new content (\"media\" in Drive\n#' API-speak)"
  },
  {
    "path": "R/drive_upload.R",
    "chars": 4770,
    "preview": "#' Upload into a new Drive file\n#'\n#' Uploads a local file into a new Drive file. To update the content or metadata\n#' o"
  },
  {
    "path": "R/drive_user.R",
    "chars": 1249,
    "preview": "#' Get info on current user\n#'\n#' Reveals information about the user associated with the current token. This is\n#' a thi"
  },
  {
    "path": "R/googledrive-package.R",
    "chars": 2705,
    "preview": "#' @description googledrive allows you to interact with files on Google Drive\n#'   from R.\n#'\n#'   `googledrive::drive_f"
  },
  {
    "path": "R/promote.R",
    "chars": 1376,
    "preview": "# promote an element in drive_resource into a top-level column\n#\n# if you request `this_var` or `thisVar`, we look for `"
  },
  {
    "path": "R/request_generate.R",
    "chars": 3269,
    "preview": "#' Build a request for the Google Drive API\n#'\n#' @description Build a request, using knowledge of the [Drive v3\n#'   AP"
  },
  {
    "path": "R/request_make.R",
    "chars": 5607,
    "preview": "#' Make a request for the Google Drive v3 API\n#'\n#' Low-level functions to execute one or more Drive API requests and, p"
  },
  {
    "path": "R/roxygen-templates.R",
    "chars": 1531,
    "preview": "# nocov start\n\nparam_path <- function(thing = \"THING\", default_notes = \"\") {\n  glue(\n    \"\n    @param path Specifies tar"
  },
  {
    "path": "R/shared_drive_create.R",
    "chars": 956,
    "preview": "#' Create a new shared drive\n#'\n#' @template shared-drive-description\n#'\n#' @seealso Wraps the `drives.create` endpoint:"
  },
  {
    "path": "R/shared_drive_find.R",
    "chars": 1476,
    "preview": "#' Find shared drives\n#'\n#' @description This is the closest googledrive function to what you get from\n#'   visiting <ht"
  },
  {
    "path": "R/shared_drive_get.R",
    "chars": 2345,
    "preview": "#' Get shared drives by name or id\n#'\n#' @description\n\n#' Retrieve metadata for shared drives specified by name or id. N"
  },
  {
    "path": "R/shared_drive_rm.R",
    "chars": 1887,
    "preview": "#' Delete shared drives\n#'\n#' @template shared-drive-description\n#'\n#' @seealso Wraps the `drives.delete` endpoint:\n#'  "
  },
  {
    "path": "R/shared_drive_update.R",
    "chars": 1871,
    "preview": "#' Update a shared drive\n#'\n#' Update the metadata of an existing shared drive, e.g. its background image or\n#' theme.\n#"
  },
  {
    "path": "R/shared_drives.R",
    "chars": 10645,
    "preview": "#' Access shared drives\n#'\n#' @description\n\n#' A shared drive supports files owned by an organization rather than an\n#' "
  },
  {
    "path": "R/shortcut.R",
    "chars": 6429,
    "preview": "#' Create a shortcut to a Drive file\n#'\n#' Creates a shortcut to the target Drive `file`, which could be a folder. A\n#' "
  },
  {
    "path": "R/team_drive.R",
    "chars": 2258,
    "preview": "#' Deprecated Team Drive functions\n#'\n#' @description\n#' `r lifecycle::badge('deprecated')`\n#' @template team-drives-des"
  },
  {
    "path": "R/utils-io.R",
    "chars": 1796,
    "preview": "readLines <- function(...) {\n  drive_abort(\"In this house, we use {.fun read_utf8} for UTF-8 reasons.\")\n}\n\nwriteLines <-"
  },
  {
    "path": "R/utils-paths.R",
    "chars": 5221,
    "preview": "# path utilities that CAN call the Drive API ----\nroot_folder <- function() {\n  # inlining env_cache() logic, so I don't"
  },
  {
    "path": "R/utils-pipe.R",
    "chars": 202,
    "preview": "#' Pipe operator\n#'\n#' See \\code{magrittr::\\link[magrittr]{\\%>\\%}} for details.\n#'\n#' @name %>%\n#' @rdname pipe\n#' @keyw"
  },
  {
    "path": "R/utils-ui.R",
    "chars": 7189,
    "preview": "drive_theme <- function() {\n  list(\n    span.field = list(transform = single_quote_if_no_color),\n    # I want to style t"
  },
  {
    "path": "R/utils.R",
    "chars": 2526,
    "preview": "isFALSE <- function(x) identical(x, FALSE)\n\nis_toggle <- function(x) length(x) == 1L && is.logical(x)\n\nlast <- function("
  },
  {
    "path": "R/zzz.R",
    "chars": 847,
    "preview": ".onLoad <- function(libname, pkgname) {\n  # .auth is created in R/drive_auth.R\n  # this is to insure we get an instance "
  },
  {
    "path": "README.Rmd",
    "chars": 2314,
    "preview": "---\noutput: github_document\n---\n\n<!-- README.md is generated from README.Rmd. Please edit that file -->\n\n\n```{r setup, i"
  },
  {
    "path": "README.md",
    "chars": 2728,
    "preview": "\n<!-- README.md is generated from README.Rmd. Please edit that file -->\n\n# googledrive <a href=\"https://googledrive.tidy"
  },
  {
    "path": "_pkgdown.yml",
    "chars": 3481,
    "preview": "url: https://googledrive.tidyverse.org\n\ndevelopment:\n  mode: auto\n\ntemplate:\n  package: tidytemplate\n  bootstrap: 5\n\n  i"
  },
  {
    "path": "air.toml",
    "chars": 0,
    "preview": ""
  },
  {
    "path": "codecov.yml",
    "chars": 232,
    "preview": "comment: false\n\ncoverage:\n  status:\n    project:\n      default:\n        target: auto\n        threshold: 1%\n        infor"
  },
  {
    "path": "cran-comments.md",
    "chars": 195,
    "preview": "## revdepcheck results\n\nWe checked 23 reverse dependencies, comparing R CMD check results across CRAN and dev versions o"
  },
  {
    "path": "data-raw/discovery-doc-ingest.R",
    "chars": 1750,
    "preview": "library(tidyverse)\n\nsource(\n  system.file(\"discovery-doc-ingest\", \"ingest-functions.R\", package = \"gargle\")\n)\n\ndownload_"
  },
  {
    "path": "data-raw/drive-examples-create.R",
    "chars": 1986,
    "preview": "library(here)\nlibrary(glue)\nlibrary(googledrive)\nlibrary(tidyverse)\n\n# auth with the special-purpose service account\nfil"
  },
  {
    "path": "data-raw/drive-examples-inventory.R",
    "chars": 1262,
    "preview": "# given the files owned by the googledrive-file-keeper service account,\n# create/update an inventory file consulted by d"
  },
  {
    "path": "data-raw/drive-v3_2025-08-29.json",
    "chars": 234342,
    "preview": "{\n  \"icons\": {\n    \"x16\": \"http://www.google.com/images/icons/product/search-16.gif\",\n    \"x32\": \"http://www.google.com/"
  },
  {
    "path": "data-raw/export-mime-type-defaults.csv",
    "chars": 534,
    "preview": "mime_type_google, mime_type_local\napplication/vnd.google-apps.document,application/vnd.openxmlformats-officedocument.wor"
  },
  {
    "path": "data-raw/extension-mime-type-defaults.csv",
    "chars": 1092,
    "preview": "mime_type,ext\r\ntext/tab-separated-values,tsv\r\nimage/jpeg,jpeg\r\nimage/gif,gif\r\napplication/vnd.openxmlformats-officedocum"
  },
  {
    "path": "data-raw/file-fields.R",
    "chars": 420,
    "preview": "library(here)\nlibrary(jsonlite)\nlibrary(tidyverse)\nlibrary(fs)\n\njson_fname <- dir_ls(here(\"data-raw\"), regexp = \"drive-v"
  },
  {
    "path": "data-raw/mime-types-and-file-extensions.R",
    "chars": 5822,
    "preview": "# Generate a table associating file extensions or \"type\" with MIME types.\n# Used in drive_mime_type() to do these sorts "
  },
  {
    "path": "data-raw/mime-types-google.R",
    "chars": 2367,
    "preview": "# Generate a table of MIME types that maps between types that are specific to\n# Google Workspace and Google Drive and ot"
  },
  {
    "path": "data-raw/old/20170519_drive-v3_discovery-document.json",
    "chars": 120083,
    "preview": "{\n  \"kind\": \"discovery#restDescription\",\n  \"etag\": \"\\\"YWOzh2SDasdU84ArJnpYek-OMdg/wH03RLGjiQZHXW-bECcrydLGp0c\\\"\",\n  \"dis"
  },
  {
    "path": "data-raw/old/20170519_drive-v3_endpoints-list.json",
    "chars": 185681,
    "preview": "{\n  \"drive.about.get\": {\n    \"id\": [\"drive.about.get\"],\n    \"httpMethod\": [\"GET\"],\n    \"path\": [\"drive/v3/about\"],\n    \""
  },
  {
    "path": "data-raw/old/20170721_drive-v3_discovery-document.json",
    "chars": 103695,
    "preview": "{\n \"kind\": \"discovery#restDescription\",\n \"etag\": \"\\\"YWOzh2SDasdU84ArJnpYek-OMdg/28UCLZv0WUjAkY4ueul9YtmjTwQ\\\"\",\n \"discov"
  },
  {
    "path": "data-raw/old/20170721_drive-v3_endpoints-list.json",
    "chars": 207156,
    "preview": "{\n  \"drive.about.get\": {\n    \"id\": [\"drive.about.get\"],\n    \"httpMethod\": [\"GET\"],\n    \"path\": [\"drive/v3/about\"],\n    \""
  },
  {
    "path": "data-raw/old/20171110_drive-v3_discovery-document.json",
    "chars": 106706,
    "preview": "{\n \"kind\": \"discovery#restDescription\",\n \"etag\": \"\\\"YWOzh2SDasdU84ArJnpYek-OMdg/AEVtwe7z5EsohwHDzv8_woT69CA\\\"\",\n \"discov"
  },
  {
    "path": "data-raw/old/20171110_drive-v3_endpoints-list.json",
    "chars": 212550,
    "preview": "{\n  \"drive.about.get\": {\n    \"id\": [\"drive.about.get\"],\n    \"httpMethod\": [\"GET\"],\n    \"path\": [\"drive/v3/about\"],\n    \""
  },
  {
    "path": "data-raw/old/discovery-doc-prep.R",
    "chars": 6758,
    "preview": "library(rprojroot)\nlibrary(jsonlite)\nlibrary(httr)\nlibrary(tidyverse)\ndevtools::load_all(find_package_root_file())\n\n## l"
  },
  {
    "path": "data-raw/old/drive-v3_2019-02-14.json",
    "chars": 111392,
    "preview": "{\n \"kind\": \"discovery#restDescription\",\n \"etag\": \"\\\"J3WqvAcMk4eQjJXvfSI4Yr8VouA/f-jeGQ4IdJCazKkL_1D6ZQ3gAjs\\\"\",\n \"discov"
  },
  {
    "path": "data-raw/old/drive-v3_2019-07-08.json",
    "chars": 135403,
    "preview": "{\n \"kind\": \"discovery#restDescription\",\n \"etag\": \"\\\"9eZ1uxVRThTDhLJCZHhqs3eQWz4/NhaDzk7bBdjuLhkxPwx-7nNLC6I\\\"\",\n \"discov"
  },
  {
    "path": "data-raw/old/drive-v3_2020-04-17.json",
    "chars": 140086,
    "preview": "{\n \"kind\": \"discovery#restDescription\",\n \"etag\": \"\\\"u9GIe6H63LSGq-9_t39K2Zx_EAc/FdQR2PuSNWtGMMUKoNdRc27FezA\\\"\",\n \"discov"
  },
  {
    "path": "data-raw/old/drive-v3_2021-03-22.json",
    "chars": 142805,
    "preview": "{\n \"kind\": \"discovery#restDescription\",\n \"etag\": \"\\\"uWj2hSb4GVjzdDlAnRd2gbM1ZQ8/Nyk9QooT1-WBB03Ilrr_sA6IsJk\\\"\",\n \"discov"
  },
  {
    "path": "data-raw/old/drive-v3_2021-06-21.json",
    "chars": 144113,
    "preview": "{\n \"kind\": \"discovery#restDescription\",\n \"etag\": \"\\\"uWj2hSb4GVjzdDlAnRd2gbM1ZQ8/RDW9kL-wYxpc9TU9SMLoLWEfo9w\\\"\",\n \"discov"
  },
  {
    "path": "googledrive.Rproj",
    "chars": 450,
    "preview": "Version: 1.0\n\nRestoreWorkspace: Default\nSaveWorkspace: Default\nAlwaysSaveHistory: Default\n\nEnableCodeIndexing: Yes\nUseSp"
  },
  {
    "path": "index.Rmd",
    "chars": 8863,
    "preview": "---\noutput: github_document\n---\n\n```{r setup, include = FALSE}\nauth_success <- tryCatch(\n  googledrive:::drive_auth_docs"
  },
  {
    "path": "index.md",
    "chars": 12301,
    "preview": "\n# googledrive\n\n<!-- badges: start -->\n\n[![CRAN\nstatus](https://www.r-pkg.org/badges/version/googledrive)](https://CRAN."
  },
  {
    "path": "inst/WORDLIST",
    "chars": 510,
    "preview": "API's\nAuth\nCLI\nCMD\nCodecov\nColaboratory\nD'Agostino\nGCE\nGCP\nIDEs\nOAuth\nOOB\nORCID\nPBC\nPUTs\nRESTful\nRStudio\nTidyverse\nVMs\na"
  },
  {
    "path": "inst/extdata/data/client_secret_123.googleusercontent.com.json",
    "chars": 348,
    "preview": "{\"installed\":{\"client_id\":\"abc.apps.googleusercontent.com\",\"project_id\":\"a_project\",\"auth_uri\":\"https://accounts.google."
  },
  {
    "path": "inst/extdata/data/files_fields.csv",
    "chars": 9703,
    "preview": "name,desc\nappProperties,\"A collection of arbitrary key-value pairs which are private to the requesting app.\nEntries with"
  },
  {
    "path": "inst/extdata/data/mime_tbl.csv",
    "chars": 4401,
    "preview": "mime_type,ext,description,human_type,default\napplication/epub+zip,epub,NA,epub,FALSE\napplication/msword,doc,NA,doc,TRUE\n"
  },
  {
    "path": "inst/extdata/data/remote_example_files.csv",
    "chars": 614,
    "preview": "name,mime_type,id\nchicken_doc,application/vnd.google-apps.document,1X9pd4nOjl33zDFfTjw-_eFL7Qb9_g6VfVFDp1PPae94\nchicken_"
  },
  {
    "path": "inst/extdata/data/translate_mime_types.csv",
    "chars": 6125,
    "preview": "action,mime_type_google,mime_type_local,default\nexport,application/vnd.google-apps.document,application/epub+zip,FALSE\ne"
  },
  {
    "path": "inst/extdata/example_files/chicken.csv",
    "chars": 372,
    "preview": "chicken,breed,sex,motto\nFoghorn Leghorn,Leghorn,rooster,\"That's a joke, ah say, that's a joke, son.\"\nChicken Little,unkn"
  },
  {
    "path": "inst/extdata/example_files/chicken.txt",
    "chars": 163,
    "preview": "A chicken whose name was Chantecler\nClucked in iambic pentameter\nIt sat on a shelf, reading Song of Myself\nAnd laid eggs"
  },
  {
    "path": "inst/extdata/example_files/imdb_latin1.csv",
    "chars": 1319,
    "preview": "Votes,Rating,Title,Year,Decade\n635139,8.6,Lon,1994,1990\n264285,8.1,The Princess Bride,1987,1980\n43090,N/K,\"Paris, Texas "
  },
  {
    "path": "inst/extdata/example_files/markdown.md",
    "chars": 504,
    "preview": "# Heading 1\n\nThis document is designed as a toy for experimenting with markdown **export and import** functionality.\n\n# "
  },
  {
    "path": "inst/extdata/example_files/r_about.html",
    "chars": 1972,
    "preview": "<!DOCTYPE html PUBLIC \"-//W3C//DTD HTML 4.01 Transitional//EN\">\n<html>\n<head>\n<title>R: About R</title>\n<meta http-equiv"
  },
  {
    "path": "man/as_dribble.Rd",
    "chars": 2526,
    "preview": "% Generated by roxygen2: do not edit by hand\n% Please edit documentation in R/dribble.R\n\\name{as_dribble}\n\\alias{as_drib"
  },
  {
    "path": "man/as_shared_drive.Rd",
    "chars": 1509,
    "preview": "% Generated by roxygen2: do not edit by hand\n% Please edit documentation in R/shared_drives.R\n\\name{as_shared_drive}\n\\al"
  },
  {
    "path": "man/deprecated-team-drive-functions.Rd",
    "chars": 2902,
    "preview": "% Generated by roxygen2: do not edit by hand\n% Please edit documentation in R/team_drive.R\n\\name{deprecated-team-drive-f"
  },
  {
    "path": "man/dribble-checks.Rd",
    "chars": 1441,
    "preview": "% Generated by roxygen2: do not edit by hand\n% Please edit documentation in R/dribble.R\n\\name{dribble-checks}\n\\alias{dri"
  },
  {
    "path": "man/dribble.Rd",
    "chars": 1489,
    "preview": "% Generated by roxygen2: do not edit by hand\n% Please edit documentation in R/dribble.R\n\\name{dribble}\n\\alias{dribble}\n\\"
  },
  {
    "path": "man/drive_about.Rd",
    "chars": 985,
    "preview": "% Generated by roxygen2: do not edit by hand\n% Please edit documentation in R/drive_about.R\n\\name{drive_about}\n\\alias{dr"
  },
  {
    "path": "man/drive_auth.Rd",
    "chars": 8171,
    "preview": "% Generated by roxygen2: do not edit by hand\n% Please edit documentation in R/drive_auth.R\n\\name{drive_auth}\n\\alias{driv"
  },
  {
    "path": "man/drive_auth_configure.Rd",
    "chars": 3146,
    "preview": "% Generated by roxygen2: do not edit by hand\n% Please edit documentation in R/drive_auth.R\n\\name{drive_auth_configure}\n\\"
  },
  {
    "path": "man/drive_browse.Rd",
    "chars": 767,
    "preview": "% Generated by roxygen2: do not edit by hand\n% Please edit documentation in R/drive_browse.R\n\\name{drive_browse}\n\\alias{"
  },
  {
    "path": "man/drive_cp.Rd",
    "chars": 4984,
    "preview": "% Generated by roxygen2: do not edit by hand\n% Please edit documentation in R/drive_cp.R\n\\name{drive_cp}\n\\alias{drive_cp"
  },
  {
    "path": "man/drive_create.Rd",
    "chars": 5106,
    "preview": "% Generated by roxygen2: do not edit by hand\n% Please edit documentation in R/drive_create.R\n\\name{drive_create}\n\\alias{"
  },
  {
    "path": "man/drive_deauth.Rd",
    "chars": 1324,
    "preview": "% Generated by roxygen2: do not edit by hand\n% Please edit documentation in R/drive_auth.R\n\\name{drive_deauth}\n\\alias{dr"
  },
  {
    "path": "man/drive_download.Rd",
    "chars": 3145,
    "preview": "% Generated by roxygen2: do not edit by hand\n% Please edit documentation in R/drive_download.R\n\\name{drive_download}\n\\al"
  },
  {
    "path": "man/drive_empty_trash.Rd",
    "chars": 893,
    "preview": "% Generated by roxygen2: do not edit by hand\n% Please edit documentation in R/drive_trash.R\n\\name{drive_empty_trash}\n\\al"
  },
  {
    "path": "man/drive_endpoints.Rd",
    "chars": 1717,
    "preview": "% Generated by roxygen2: do not edit by hand\n% Please edit documentation in R/drive_endpoints.R\n\\name{drive_endpoints}\n\\"
  },
  {
    "path": "man/drive_examples.Rd",
    "chars": 1612,
    "preview": "% Generated by roxygen2: do not edit by hand\n% Please edit documentation in R/drive_examples.R\n\\name{drive_examples}\n\\al"
  },
  {
    "path": "man/drive_extension.Rd",
    "chars": 755,
    "preview": "% Generated by roxygen2: do not edit by hand\n% Please edit documentation in R/drive_mime_type.R\n\\name{drive_extension}\n\\"
  },
  {
    "path": "man/drive_fields.Rd",
    "chars": 1881,
    "preview": "% Generated by roxygen2: do not edit by hand\n% Please edit documentation in R/drive_fields.R\n\\name{drive_fields}\n\\alias{"
  },
  {
    "path": "man/drive_find.Rd",
    "chars": 7417,
    "preview": "% Generated by roxygen2: do not edit by hand\n% Please edit documentation in R/drive_find.R\n\\name{drive_find}\n\\alias{driv"
  },
  {
    "path": "man/drive_get.Rd",
    "chars": 6821,
    "preview": "% Generated by roxygen2: do not edit by hand\n% Please edit documentation in R/drive_get.R\n\\name{drive_get}\n\\alias{drive_"
  },
  {
    "path": "man/drive_has_token.Rd",
    "chars": 522,
    "preview": "% Generated by roxygen2: do not edit by hand\n% Please edit documentation in R/drive_auth.R\n\\name{drive_has_token}\n\\alias"
  },
  {
    "path": "man/drive_id.Rd",
    "chars": 955,
    "preview": "% Generated by roxygen2: do not edit by hand\n% Please edit documentation in R/drive_id-class.R\n\\name{drive_id}\n\\alias{dr"
  },
  {
    "path": "man/drive_link.Rd",
    "chars": 880,
    "preview": "% Generated by roxygen2: do not edit by hand\n% Please edit documentation in R/drive_browse.R\n\\name{drive_link}\n\\alias{dr"
  },
  {
    "path": "man/drive_ls.Rd",
    "chars": 1746,
    "preview": "% Generated by roxygen2: do not edit by hand\n% Please edit documentation in R/drive_ls.R\n\\name{drive_ls}\n\\alias{drive_ls"
  },
  {
    "path": "man/drive_mime_type.Rd",
    "chars": 1289,
    "preview": "% Generated by roxygen2: do not edit by hand\n% Please edit documentation in R/drive_mime_type.R\n\\name{drive_mime_type}\n\\"
  },
  {
    "path": "man/drive_mkdir.Rd",
    "chars": 4204,
    "preview": "% Generated by roxygen2: do not edit by hand\n% Please edit documentation in R/drive_mkdir.R\n\\name{drive_mkdir}\n\\alias{dr"
  },
  {
    "path": "man/drive_mv.Rd",
    "chars": 4395,
    "preview": "% Generated by roxygen2: do not edit by hand\n% Please edit documentation in R/drive_mv.R\n\\name{drive_mv}\n\\alias{drive_mv"
  },
  {
    "path": "man/drive_publish.Rd",
    "chars": 2352,
    "preview": "% Generated by roxygen2: do not edit by hand\n% Please edit documentation in R/drive_publish.R\n\\name{drive_publish}\n\\alia"
  },
  {
    "path": "man/drive_put.Rd",
    "chars": 4024,
    "preview": "% Generated by roxygen2: do not edit by hand\n% Please edit documentation in R/drive_put.R\n\\name{drive_put}\n\\alias{drive_"
  },
  {
    "path": "man/drive_read_string.Rd",
    "chars": 2297,
    "preview": "% Generated by roxygen2: do not edit by hand\n% Please edit documentation in R/drive_read.R\n\\name{drive_read_string}\n\\ali"
  },
  {
    "path": "man/drive_rename.Rd",
    "chars": 2961,
    "preview": "% Generated by roxygen2: do not edit by hand\n% Please edit documentation in R/drive_rename.R\n\\name{drive_rename}\n\\alias{"
  },
  {
    "path": "man/drive_reveal.Rd",
    "chars": 6084,
    "preview": "% Generated by roxygen2: do not edit by hand\n% Please edit documentation in R/drive_reveal.R\n\\name{drive_reveal}\n\\alias{"
  },
  {
    "path": "man/drive_rm.Rd",
    "chars": 2082,
    "preview": "% Generated by roxygen2: do not edit by hand\n% Please edit documentation in R/drive_rm.R\n\\name{drive_rm}\n\\alias{drive_rm"
  },
  {
    "path": "man/drive_scopes.Rd",
    "chars": 2031,
    "preview": "% Generated by roxygen2: do not edit by hand\n% Please edit documentation in R/drive_auth.R\n\\name{drive_scopes}\n\\alias{dr"
  },
  {
    "path": "man/drive_share.Rd",
    "chars": 3609,
    "preview": "% Generated by roxygen2: do not edit by hand\n% Please edit documentation in R/drive_share.R\n\\name{drive_share}\n\\alias{dr"
  },
  {
    "path": "man/drive_token.Rd",
    "chars": 1214,
    "preview": "% Generated by roxygen2: do not edit by hand\n% Please edit documentation in R/drive_auth.R\n\\name{drive_token}\n\\alias{dri"
  },
  {
    "path": "man/drive_trash.Rd",
    "chars": 1713,
    "preview": "% Generated by roxygen2: do not edit by hand\n% Please edit documentation in R/drive_trash.R\n\\name{drive_trash}\n\\alias{dr"
  },
  {
    "path": "man/drive_update.Rd",
    "chars": 2703,
    "preview": "% Generated by roxygen2: do not edit by hand\n% Please edit documentation in R/drive_update.R\n\\name{drive_update}\n\\alias{"
  },
  {
    "path": "man/drive_upload.Rd",
    "chars": 5783,
    "preview": "% Generated by roxygen2: do not edit by hand\n% Please edit documentation in R/drive_upload.R\n\\name{drive_upload}\n\\alias{"
  },
  {
    "path": "man/drive_user.Rd",
    "chars": 1429,
    "preview": "% Generated by roxygen2: do not edit by hand\n% Please edit documentation in R/drive_user.R\n\\name{drive_user}\n\\alias{driv"
  },
  {
    "path": "man/expose.Rd",
    "chars": 1023,
    "preview": "% Generated by roxygen2: do not edit by hand\n% Please edit documentation in R/utils.R\n\\name{expose}\n\\alias{expose}\n\\titl"
  },
  {
    "path": "man/googledrive-configuration.Rd",
    "chars": 2943,
    "preview": "% Generated by roxygen2: do not edit by hand\n% Please edit documentation in R/googledrive-package.R, R/utils-ui.R\n\\name{"
  },
  {
    "path": "man/googledrive-deprecated.Rd",
    "chars": 2260,
    "preview": "% Generated by roxygen2: do not edit by hand\n% Please edit documentation in R/deprecated.R\n\\name{googledrive-deprecated}"
  },
  {
    "path": "man/googledrive-package.Rd",
    "chars": 2140,
    "preview": "% Generated by roxygen2: do not edit by hand\n% Please edit documentation in R/googledrive-package.R\n\\docType{package}\n\\n"
  },
  {
    "path": "man/pipe.Rd",
    "chars": 256,
    "preview": "% Generated by roxygen2: do not edit by hand\n% Please edit documentation in R/utils-pipe.R\n\\name{\\%>\\%}\n\\alias{\\%>\\%}\n\\t"
  },
  {
    "path": "man/request_generate.Rd",
    "chars": 3178,
    "preview": "% Generated by roxygen2: do not edit by hand\n% Please edit documentation in R/request_generate.R\n\\name{request_generate}"
  },
  {
    "path": "man/request_make.Rd",
    "chars": 4245,
    "preview": "% Generated by roxygen2: do not edit by hand\n% Please edit documentation in R/request_make.R\n\\name{request_make}\n\\alias{"
  },
  {
    "path": "man/shared_drive_create.Rd",
    "chars": 1028,
    "preview": "% Generated by roxygen2: do not edit by hand\n% Please edit documentation in R/shared_drive_create.R\n\\name{shared_drive_c"
  },
  {
    "path": "man/shared_drive_find.Rd",
    "chars": 1475,
    "preview": "% Generated by roxygen2: do not edit by hand\n% Please edit documentation in R/shared_drive_find.R\n\\name{shared_drive_fin"
  },
  {
    "path": "man/shared_drive_get.Rd",
    "chars": 1673,
    "preview": "% Generated by roxygen2: do not edit by hand\n% Please edit documentation in R/shared_drive_get.R\n\\name{shared_drive_get}"
  },
  {
    "path": "man/shared_drive_rm.Rd",
    "chars": 1509,
    "preview": "% Generated by roxygen2: do not edit by hand\n% Please edit documentation in R/shared_drive_rm.R\n\\name{shared_drive_rm}\n\\"
  },
  {
    "path": "man/shared_drive_update.Rd",
    "chars": 1748,
    "preview": "% Generated by roxygen2: do not edit by hand\n% Please edit documentation in R/shared_drive_update.R\n\\name{shared_drive_u"
  },
  {
    "path": "man/shared_drives.Rd",
    "chars": 5553,
    "preview": "% Generated by roxygen2: do not edit by hand\n% Please edit documentation in R/shared_drives.R\n\\name{shared_drives}\n\\alia"
  },
  {
    "path": "man/shortcut_create.Rd",
    "chars": 3804,
    "preview": "% Generated by roxygen2: do not edit by hand\n% Please edit documentation in R/shortcut.R\n\\name{shortcut_create}\n\\alias{s"
  },
  {
    "path": "man/shortcut_resolve.Rd",
    "chars": 2451,
    "preview": "% Generated by roxygen2: do not edit by hand\n% Please edit documentation in R/shortcut.R\n\\name{shortcut_resolve}\n\\alias{"
  },
  {
    "path": "man-roxygen/corpus.R",
    "chars": 353,
    "preview": "#' @param corpus Character, specifying which collections of items to search.\n#'   Relevant to those who work with shared"
  },
  {
    "path": "man-roxygen/dots-metadata.R",
    "chars": 353,
    "preview": "#' @param ... Named parameters to pass along to the Drive API. Has [dynamic\n#'   dots][rlang::dyn-dots] semantics. You c"
  },
  {
    "path": "man-roxygen/file-plural.R",
    "chars": 219,
    "preview": "#' @param file Something that identifies the file(s) of interest on your Google\n#'   Drive. Can be a character vector of"
  },
  {
    "path": "man-roxygen/file-singular.R",
    "chars": 175,
    "preview": "#' @param file Something that identifies the file of interest on your Google\n#'   Drive. Can be a name or path, a file i"
  },
  {
    "path": "man-roxygen/media.R",
    "chars": 61,
    "preview": "#' @param media Character, path to the local file to upload.\n"
  },
  {
    "path": "man-roxygen/n_max.R",
    "chars": 203,
    "preview": "#' @param n_max Integer. An upper bound on the number of items to return. This\n#'   applies to the results requested fro"
  },
  {
    "path": "man-roxygen/overwrite.R",
    "chars": 1004,
    "preview": "#' @param overwrite Logical, indicating whether to check for a pre-existing file\n#'   at the targetted \"filepath\". The q"
  },
  {
    "path": "man-roxygen/pattern.R",
    "chars": 187,
    "preview": "#' @param pattern Character. If provided, only the items whose names match this\n#'   regular expression are returned. Th"
  },
  {
    "path": "man-roxygen/shared-drive-description.R",
    "chars": 335,
    "preview": "#' @description\n\n#' A shared drive supports files owned by an organization rather than an\n#' individual user. Shared dri"
  },
  {
    "path": "man-roxygen/shared_drive-plural.R",
    "chars": 233,
    "preview": "#' @param drive Anything that identifies the shared drive(s) of interest. Can\n#'   be a character vector of names, a cha"
  },
  {
    "path": "man-roxygen/shared_drive-singular.R",
    "chars": 283,
    "preview": "#' @param shared_drive Anything that identifies one specific shared drive: its\n#'   name, its id or URL marked with [as_"
  },
  {
    "path": "man-roxygen/team-drives-description.R",
    "chars": 535,
    "preview": "#' @description\n\n#' Team Drives have been rebranded as *shared drives* and, as of googledrive\n#' v2.0.0, all `team_drive"
  },
  {
    "path": "man-roxygen/team_drive-plural.R",
    "chars": 138,
    "preview": "#' @param team_drive `r lifecycle::badge(\"deprecated\")` Google Drive and the\n#'   Drive API have replaced Team Drives wi"
  },
  {
    "path": "man-roxygen/team_drive-singular.R",
    "chars": 138,
    "preview": "#' @param team_drive `r lifecycle::badge(\"deprecated\")` Google Drive and the\n#'   Drive API have replaced Team Drives wi"
  },
  {
    "path": "man-roxygen/verbose.R",
    "chars": 406,
    "preview": "#' @param verbose `r lifecycle::badge(\"deprecated\")` This logical argument to\n#'   individual googledrive functions is d"
  },
  {
    "path": "revdep/.gitignore",
    "chars": 79,
    "preview": "checks\nlibrary\nchecks.noindex\nlibrary.noindex\ndata.sqlite\n*.html\ncloud.noindex\n"
  },
  {
    "path": "revdep/README.md",
    "chars": 11,
    "preview": "# Revdeps\n\n"
  },
  {
    "path": "revdep/cran.md",
    "chars": 196,
    "preview": "## revdepcheck results\n\nWe checked 23 reverse dependencies, comparing R CMD check results across CRAN and dev versions o"
  },
  {
    "path": "revdep/failures.md",
    "chars": 29,
    "preview": "*Wow, no problems at all. :)*"
  },
  {
    "path": "revdep/problems.md",
    "chars": 29,
    "preview": "*Wow, no problems at all. :)*"
  },
  {
    "path": "tests/spelling.R",
    "chars": 153,
    "preview": "if (requireNamespace(\"spelling\", quietly = TRUE)) {\n  spelling::spell_check_test(\n    vignettes = TRUE,\n    error = FALS"
  },
  {
    "path": "tests/testthat/.gitignore",
    "chars": 78,
    "preview": ".httr-oauth\ntesting-token.rds\n.Rapp.history\nall-test-clean.*\nall-test-setup.*\n"
  },
  {
    "path": "tests/testthat/_snaps/deprecated.md",
    "chars": 1011,
    "preview": "# drive_auth_config() is deprecated\n\n    Code\n      drive_auth_config()\n    Condition\n      Error:\n      ! `drive_auth_c"
  },
  {
    "path": "tests/testthat/_snaps/dribble.md",
    "chars": 3413,
    "preview": "# tbl_sum.dribble method works\n\n    Code\n      print(d)\n    Output\n      # A dribble: 2 x 3\n        name  id       drive"
  },
  {
    "path": "tests/testthat/_snaps/drive_auth.md",
    "chars": 1486,
    "preview": "# drive_auth_configure works\n\n    Code\n      drive_auth_configure(client = gargle::gargle_client(), path = \"PATH\")\n    C"
  },
  {
    "path": "tests/testthat/_snaps/drive_cp.md",
    "chars": 1798,
    "preview": "# drive_cp() can copy file in place\n\n    Code\n      write_utf8(drive_cp_message)\n    Output\n      Original file:\n      *"
  },
  {
    "path": "tests/testthat/_snaps/drive_create.md",
    "chars": 787,
    "preview": "# drive_create() errors for bad input (before hitting Drive API)\n\n    Code\n      drive_create()\n    Condition\n      Erro"
  },
  {
    "path": "tests/testthat/_snaps/drive_download.md",
    "chars": 1751,
    "preview": "# drive_download() won't overwrite existing file\n\n    Code\n      withr::with_dir(tmpdir, drive_download(dribble(), path "
  }
]

// ... and 80 more files (download for full content)

About this extraction

This page contains the full source code of the tidyverse/googledrive GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 280 files (2.4 MB), approximately 635.7k tokens. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!