R/setupProject.R

Defines functions msgNeedGitUserName .libPathDefault .extractModName extractModName errorMsgCleaning makeUpdateRprofileSticky fastOptions checkGitRemote checkUseGit getStudyArea copyPackages stop_quietly ignoreAFolder isProjectGitRepo mergeOpts parseEnvsWithNamedListsSequentially parseExpressionSequentially stripDuplicateFolder dotsToHereOuter checkNameProjectPathConflict setupSpaDES.ProjectDeps setupRestart setupStudyArea stopMessForRequireFail setupRequire dotsToHere messageWarnStop .getOption .paths setPaths setupGitIgnore evalSUB inTempProject isInRstudioProj isInProject checkProjectPath parseFileLists setupParams setupPackages setupModules evalListElems parseListsSequentially isUnevaluatedList setupOptions setupSideEffects setupFunctions setupPaths setupProject

Documented in .libPathDefault setupFunctions setupGitIgnore setupModules setupOptions setupPackages setupParams setupPaths setupProject setupSideEffects setupStudyArea

utils::globalVariables(c(
  ".", "i.module", "i.objectClass", "objectClass", "objectName",
  "i.newValue", "i.oldValue", "newValue", "oldValue", "modulesNoVersion"
))

#' Sets up a new or existing SpaDES project
#'
#' @description `setupProject` calls a sequence of functions in this order:
#' `setupOptions` (first time), `setupPaths`, `setupRestart`,
#' `setupFunctions`, `setupModules`, `setupPackages`, `setupSideEffects`,
#' `setupOptions` (second time), `setupParams`, and `setupGitIgnore`.
#'
#' This sequence will create folder structures, install missing packages from those
#' listed in either the `packages`, `require` arguments or in the modules `reqdPkgs` fields,
#' load packages (only those in the `require` argument), set options, download or
#' confirm the existence of modules. It will also return elements that can be passed
#' directly to `simInit`  or `simInitAndSpades`, specifically, `modules`, `params`,
#' `paths`, `times`, and any named elements passed to `...`. This function will also
#' , if desired, change the .Rprofile file for this project so that every time
#' the project is opened, it has a specific `.libPaths()`.
#'
#' There are a number of convenience elements described in the section below. See Details.
#' Because of this sequence, users can take advantage of settings (i.e., objects)
#' that happen (are created) before others. For example, users can set `paths`
#' then use the `paths` list to set `options` that will can update/change `paths`,
#' or set `times` and use the `times` list for certain entries in `params`.
#'
#'
#' @param name Optional. If supplied, the name of the project. If not supplied, an
#' attempt will be made to extract the name from the `paths[["projectPath"]]`.
#' If this is a GitHub project, then it should indicate the full Github
#' repository and branch name, e.g., `"PredictiveEcology/WBI_forecasts@ChubatyPubNum12"`
#' @param paths a list with named elements, specifically, `modulePath`, `projectPath`,
#'   `packagePath` and all others that are in `SpaDES.core::setPaths()`
#'   (i.e., `inputPath`, `outputPath`, `scratchPath`, `cachePath`, `rasterTmpDir`).
#'   Each of these has a sensible default, which will be overridden but any user
#'   supplied values.
#'   See [setup].
#' @param modules a character string of modules to pass to `getModule`. These
#'   should be one of: simple name (e.g., `fireSense`) which will be searched for locally
#'   in the `paths[["modulePath"]]`; or a GitHub repo with branch (`GitHubAccount/Repo@branch` e.g.,
#'   `"PredictiveEcology/Biomass_core@development"`); or a character vector that identifies
#'   one or more module folders (local or GitHub) (not the module .R script).
#'   If the entire project is a git repository,
#'   then it will not try to re-get these modules; instead it will rely on the user
#'   managing their git status outside of this function.
#'   See [setup].
#' @param times Optional. This will be returned if supplied; if supplied, the values
#'   can be used in e.g., `params`, e.g., `params = list(mod = list(startTime = times$start))`.
#'   See help for `SpaDES.core::simInit`.
#' @param config Still experimental linkage to the `SpaDES.config` package. Currently
#'   not working.
#' @param packages Optional. A vector of packages that must exist in the `libPaths`.
#'   This will be passed to `Require::Install`, i.e., these will be installed, but
#'   not attached to the search path. See also the `require` argument. To force skip
#'   of package installation (without assessing modules), set `packages = NULL`
#' @param require Optional. A character vector of packages to install *and* attach
#'   (with `Require::Require`). These will be installed and attached at the start
#'   of `setupProject` so that a user can use these during `setupProject`.
#'   See [setup]
#' @param options Optional. Either a named list to be passed to `options`
#'   or a character vector indicating one or more file(s) to source,
#'   in the order provided. These will be parsed locally (not
#'   the `.GlobalEnv`), so they will not create globally accessible objects. NOTE:
#'   `options` is run 2x within `setupProject`, once before `setupPaths` and once
#'   after `setupPackages`. This occurs because many packages use options for their
#'   behaviour (need them set before e.g., `Require::require` is run; but many packages
#'   also change `options` at startup. See details.
#'   See [setup].
#' @param params Optional. Similar to `options`, however, this named list will be
#'   returned, i.e., there are no side effects.
#'   See [setup].
#' @param sideEffects Optional. This can be an expression or one or more file names or
#'   a code chunk surrounded by `{...}`.
#'   If a non-text file name is specified (e.g., *not .txt or .R* currently),
#'   these files will simply be downloaded, using their relative path as specified
#'   in the github notation. They will be downloaded or accessed locally at that
#'   relative path.
#'   If these file names represent scripts (*.txt or .R), this/these will be parsed and evaluated,
#'   but nothing is returned (i.e., any assigned objects are not returned). This is intended
#'   to be used for operations like cloud authentication or configuration functions
#'   that are run for their side effects only.
#' @param functions A set of function definitions to be used within `setupProject`.
#'   These will be returned as a list element. If function definitions require non-base
#'   packages, prefix the function call with the package e.g., `terra::rast`. When
#'   using `setupProject`, the `functions` argument is evaluated after `paths`, so
#'   it cannot be used to define functions that help specify `paths`.
#' @param useGit (if not FALSE, then experimental still). There are two levels at which a project
#'   can use GitHub, either the `projectPath` and/or the `modules`. Any given
#'   project can have one or the other, or both of these under git control. If "both",
#'   then this function will assume that git submodules will be used for the `modules`.
#'   A logical or `"sub"` for *submodule*. If `"sub"`, then this function
#'   will attempt to clone the identified `modules` *as git submodules*. This will only
#'   work if the `projectPath` is a git repository. If the project is already a git
#'   repository because the user has set that up externally to this function call, then
#'   this function will add the `modules` as git submodules. If it is not already,
#'   it will use `git clone` for each module. After git clone or submodule add are run,
#'   it will run `git checkout` for the named branch and then `git pull`
#'   to get and change branch for each module, according to its specification in
#'   `modules`. If `FALSE`, this function  will download modules with `getModules`.
#'   NOTE: *CREATING* A
#'   GIT REPOSITORY AT THE PROJECT LEVEL AND SETTING MODULES AS GIT SUBMODULES IS
#'   EXPERIMENTAL. IT IS FINE IF THE PROJECT HAS BEEN MANUALLY SET UP TO BE
#'   A GIT REPOSITORY WITH SUBMODULES: THIS FUNCTION WILL ONLY EVALUTE PATHS. This can
#'   be set with the `option(SpaDES.project.useGit = xxx)`.
#' @param standAlone A logical. Passed to `Require::standAlone`. This keeps all
#'   packages installed in a project-level library, if `TRUE`. Default is `TRUE`.
#' @param libPaths Deprecated. Use `paths = list(packagePath = ...)`.
#' @param Restart Logical or character. If either `TRUE` or a character,
#'   and if the `projectPath` is not the current path, and the session is in
#'   RStudio and interactive, it will try to restart Rstudio in the projectPath with
#'   a new Rstudio project. If character, it should represent the filename
#'   of the script that contains the `setupProject` call that should be copied to
#'   the new folder and opened. If `TRUE`, it will use the active file as the one
#'   that should be copied to the new projectPath and opened in the Rstudio project.
#'   If successful, this will create an RStudio Project file (and .Rproj.user
#'   folder), restart with a new Rstudio session with that new project and with a root
#'   path (i.e. working directory) set to `projectPath`. Default is `FALSE`, and no
#'   RStudio Project is created.
#' @param updateRprofile Logical. Should the `paths$packagePath` be set in the `.Rprofile`
#'   file for this project. Note: if `paths$packagePath` is within the `tempdir()`,
#'   then there will be a warning, indicating this won't persist. If the user is
#'   using `Rstudio` and the `paths$projectPath` is not the root of the current
#'   Rstudio project, then a warning will be given, indicating the .Rprofile may not
#'   be read upon restart.
#' @param setLinuxBinaryRepo Logical. Should the binary RStudio Package Manager be used
#'   on Linux (ignored if Windows)
#' @param studyArea Optional. If a list, it will be passed to
#'        `geodata::gadm`. To specify a country other than the default `"CAN"`,
#'        the list must have a named element, `"country"`. All other named elements
#'        will be passed to `gadm`. 2 additional named elements can be passed for
#'        convenience, `subregion = "..."`, which will be grepped with the column
#'        `NAME_1`, and `epsg = "..."`, so a user can pass an `epsg.io` code to
#'        reproject the `studyArea`. See examples.
#' @param overwrite Logical vector or character vector, however, only `getModule` will respond
#'   to a vector of values. If length-one `TRUE`, then all files that were previously downloaded
#'   will be overwritten throughout the sequence of `setupProject`. If a vector of
#'   logical or character, these will be passed to `getModule`: only the named
#'   modules will be overwritten or the logical vector of the modules.
#'   NOTE: if a vector, no other file specified anywhere in `setupProject` will be
#'   overwritten except a module that/those names, because
#'   only `setupModules` is currently responsive to a vector. To have fine grained control,
#'   a user can just manually delete a file, then rerun.
#' @param dots Any other named objects passed as a list a user might want for other elements.
#' @param defaultDots A named list of any arbitrary R objects.
#'   These can be supplied to give default values to objects that
#'   are otherwise passed in with the `...`, i.e., not specifically named for these
#'   `setup*` functions. If named objects are supplied as top-level arguments, then
#'   the `defaultDots` will be overridden. This can be particularly useful if the
#'   arguments passed to `...` do not always exist, but rely on external e.g., batch
#'   processing to optionally fill them. See examples.
#' @param envir The environment where `setupProject` is called from. Defaults to
#'   `parent.frame()` which should be fine in most cases and user shouldn't need
#'   to set this
#' @param ... further named arguments that acts like `objects`, but a different
#'   way to specify them. These can be anything. The general use case
#'   is to create the `objects` that are would be passed to
#'   `SpaDES.core::simInit`, or `SpaDES.core::simInitAndSpades`,
#'   (e.g. `studyAreaName` or `objects`) or additional objects to be passed to the simulation
#'   (in older versions of `SpaDES.core`, these were passed as a named list
#'   to the `objects` argument). **Order matters**. These are sequentially evaluated,
#'   and also any arguments that are specified before the named arguments
#'   e.g., `name`, `paths`, will be evaluated prior to any of the named arguments,
#'   i.e., "at the start" of the `setupProject`.
#'   If placed after the first named argument, then they will be evaluated at the
#'   end of the `setupProject`, so can access all the packages, objects, etc.
#'
#' @include imports.R
#' @export
#'
#' @section Faster runtime after project is set up:
#'
#' There are a number of checks that occur during `setupProject`. These take time, particularly
#' after an R restart (there is some caching in RAM that occurs, but this will only speed
#' things up if there is no restart of R). To get the "fastest", these options or settings
#' will speed things up, at the expense of not being completely re-runnable.
#' You can add one or more of these to the arguments. These will only be useful after a project
#' is set up, i.e., `setupProject` and `SpaDES.core::simInit` has/have been run at least once
#' to completion (so packages are installed).
#'
#' ```
#' options = c(
#'   reproducible.useMemoise = TRUE,               # For caching, use memory objects
#'   Require.cloneFrom = Sys.getenv("R_LIBS_USER"),# Use personal library as possible source of packages
#'   spades.useRequire = FALSE,                    # Won't install packages/update versions
#'   spades.moduleCodeChecks = FALSE,              # moduleCodeChecks checks for metadata mismatches
#'   reproducible.inputPaths = "~/allData"),       # For sharing data files across projects
#' packages = NULL,                                # Prevents any packages installs with setupProject
#' useGit = FALSE                                  # Prevents checks using git
#' ```
#' These will be set early in `setupProject`, so will affect the running of `setupProject`.
#' If the user manually sets one of these in addition to setting these, the user options will
#' override these.
#' The remining causes of `setupProject` being "slow" will be loading the required packages.
#'
#' These options/arguments can now be set all at once
#' (with caution as these changes will affect how your
#' script will be run) with `options(SpaDES.project.fast = TRUE)` or in the `options` argument.
#'
#'
#' @section Objective:
#'
#' The overarching objectives for these functions are:
#'
#'   \enumerate{
#'     \item To prepare what is needed for `simInit`.
#'     \item To help a user eliminate virtually all assignments to the `.GlobalEnv`,
#'           as these create and encourage spaghetti code that becomes unreproducible
#'           as the project increases in complexity.
#'     \item Be very simple for beginners, but powerful enough to expand to almost
#'           any needs of arbitrarily complex projects, using the same structure
#'     \item Deal with the complexities of R package installation and loading when
#'           working with modules that may have been created by many users
#'     \item Create a common SpaDES project structure, allowing
#'           easy transition from one project to another, regardless of complexity.
#'   }
#'
#'
#' @section Convenience elements:
#'
#' \subsection{Sequential evaluation}{
#' Throughout these functions, efforts have been made to implement sequential evaluation,
#' within files and within lists. This means that a user can *use* the values from an
#' upstream element in the list. For example, the following where `projectPath` is
#' part of the list that will be assigned to the `paths` argument and it is then
#' used in the subsequent list element is valid:
#'
#' ```
#' setupPaths(paths = list(projectPath = "here",
#'                         modulePath = file.path(paths[["projectPath"]], "modules")))
#' ```
#' Because of such sequential evaluation, `paths`, `options`, and `params` files
#' can be sequential lists that have impose a hierarchy specified
#' by the order. For example, a user can first create a list of *default* options,
#' then several lists of user-desired options behind an `if (user("emcintir"))`
#' block that add new or override existing elements, followed by `machine` specific
#' values, such as paths.
#' }
#'
#' ```
#' setupOptions(
#'   maxMemory <- 5e+9 # if (grepl("LandWeb", runName)) 5e+12 else 5e+9
#'
#'   # Example -- Use any arbitrary object that can be passed in the `...` of `setupOptions`
#'   #  or `setupProject`
#'   if (.mode == "development") {
#'      list(test = 2)
#'   }
#'   if (machine("A127")) {
#'     list(test = 3)
#'   }
#' )
#' ````
#'
#' \subsection{Values and/or files}{
#' The arguments, `paths`, `options`, and `params`, can all
#' understand lists of named values, character vectors, or a mixture by using a list where
#' named elements are values and unnamed elements are character strings/vectors. Any unnamed
#' character string/vector will be treated as a file path. If that file path has an `@` symbol,
#' it will be assumed to be a file that exists on a GitHub repository in `https://github.com`.
#' So a user can pass values, or pointers to remote and/or local paths that themselves have values.
#'
#' The following will set an option as declared, plus read the local file (with relative
#' path), plus download and read the cloud-hosted file.
#'
#' ```
#' setupProject(
#'    options = list(reproducible.useTerra = TRUE,
#'                   "inst/options.R",
#'                   "PredictiveEcology/SpaDES.project@transition/inst/options.R")
#'                  )
#'    )
#' ```
#' This approach allows for an organic growth of complexity, e.g., a user begins with
#' only named lists of values, but then as the number of values increases, it may be
#' helpful to put some in an external file.
#'
#' NOTE: if the GitHub repository is *private* the user *must* configure their GitHub
#' token by setting the GITHUB_PAT environment variable -- unfortunately, the `usethis`
#' approach to setting the token will not work at this moment.
#' }
#'
#' \subsection{Specifying `paths`, `options`, `params`}{
#' If `paths`, `options`, and/or `params` are a character string
#' or character vector (or part of an unnamed list element) the string(s)
#' will be interpreted as files to parse. These files should contain R code that
#' specifies *named lists*, where the names are one or more `paths`, `options`,
#' or are module names, each with a named list of parameters for that named module.
#' This last named list for `params` follows the convention used for the `params` argument in
#' `simInit(..., params = )`.
#'
#' These files can use `paths`, `times`, plus any previous list in the sequence of
#' `params` or `options` specified. Any functions that are used must be available,
#'  e.g., prefixed `Require::normPath` if the package has not been loaded (as recommended).
#'
#' If passing a file to `options`, it should **not set** `options()` explicitly;
#' only create named lists. This enables options checking/validating
#' to occur within `setupOptions` and `setupParams`. A simplest case would be a file with this:
#' `opts <- list(reproducible.destinationPath = "~/destPath")`.
#'
#' All named lists will be parsed into their own environment, and then will be
#' sequentially evaluated (i.e., subsequent lists will have access to previous lists),
#' with each named elements setting or replacing the previously named element of the same name,
#' creating a single list. This final list will be assigned to, e.g., `options()` inside `setupOptions`.
#'
#' Because each list is parsed separately, they to not need to be assigned objects;
#' if they are, the object name can be any name, even if similar to another object's name
#' used to built the same argument's (i.e. `paths`, `params`, `options`) final list.
#' Hence, in an file to passed to `options`, instead of incrementing the list as:
#'
#' ```
#' a <- list(optA = 1)
#' b <- append(a, list(optB = 2))
#' c <- append(b, list(optC = 2.5))
#' d <- append(c, list(optD = 3))
#' ```
#'
#' one can do:
#' ```
#' a <- list(optA = 1)
#' a <- list(optB = 2)
#' c <- list(optC = 2.5)
#' list(optD = 3)
#' ```
#'
#' NOTE: only atomics (i.e., character, numeric, etc.), named lists, or either of these
#'   that are protected by 1 level of "if" are parsed. This will not work, therefore,
#'   for other side-effect elements, like authenticating with a cloud service.
#'
#' Several helper functions exist within `SpaDES.project` that may be useful, such
#' as `user(...)`, `machine(...)`
#' }
#'
#' \subsection{Can hard code arguments that may be missing}{
#' To allow for batch submission, a user can specify code `argument = value` even if `value`
#' is missing. This type of specification will not work in normal parsing of arguments,
#' but it is designed to work here. In the next example, `.mode = .mode` can be specified,
#' but if R cannot find `.mode` for the right hand side, it will just skip with no error.
#' Thus a user can source a script with the following line from batch script where `.mode`
#' is specified. When running this line without that batch script specification, then this
#' will assign no value to `.mode`. We include `.nodes` which shows an example of
#' passing a value that does exist. The non-existent `.mode` will be returned in the `out`,
#' but as an unevaluated, captured list element.
#'
#' ```
#' .nodes <- 2
#' out <- setupProject(.mode = .mode,
#'                     .nodes = .nodes,
#'                     options = "inst/options.R"
#'                     )
#' ```
#' }
#'
#' @return
#' `setupProject` will return a named list with elements `modules`, `paths`, `params`, and `times`.
#' The goal of this list is to contain list elements that can be passed directly
#' to `simInit`.
#'
#' It will also append all elements passed by the user in the `...`.
#' This list  can be passed directly to `SpaDES.core::simInit()` or
#' `SpaDES.core::simInitAndSpades()` using a `do.call()`. See example.
#'
#' NOTE: both `projectPath` and `packagePath` will be omitted in the `paths` list
#' as they are used to set current directory (found with `getwd()`) and `.libPaths()[1]`,
#' but are not accepted by `simInit`. `setupPaths` will still return these two paths as its
#' outputs are not expected to be passed directly to `simInit` (unlike `setupProject` outputs).
#'
#' @importFrom Require extractPkgName
#' @importFrom stats na.omit
#' @inheritParams Require::Require
#' @inheritParams Require::setLibPaths
#' @rdname setupProject
#' @seealso [setupPaths()], [setupOptions()], [setupPackages()],
#' [setupModules()], [setupGitIgnore()]. Also, helpful functions such as
#' [user()], [machine()], [node()]
#' @seealso `vignette("i-getting-started", package = "SpaDES.project")`
#'
#' @examples
#' ## For more examples:
#' vignette("i-getting-started", package = "SpaDES.project")
#'
#' library(SpaDES.project)
#'
#' \dontshow{origDir <- getwd()
#'           tmpdir <- Require::tempdir2() # for testing tempdir2 is better}
#' \dontshow{
#' if (is.null(getOption("repos"))) {
#'   options(repos = c(CRAN = "https://cloud.r-project.org"))
#'   }
#'   setwd(tmpdir)
#' }
#'  ## simplest case; just creates folders
#' out <- setupProject(
#'   paths = list(projectPath = ".") #
#' )
#' \dontshow{setwd(origDir)}
setupProject <- function(name, paths, modules, packages,
                         times, options, params, sideEffects, functions, config,
                         require = NULL, studyArea = NULL,
                         Restart = getOption("SpaDES.project.Restart", FALSE),
                         useGit = getOption("SpaDES.project.useGit", FALSE),
                         setLinuxBinaryRepo = getOption("SpaDES.project.setLinuxBinaryRepo", TRUE),
                         standAlone = getOption("SpaDES.project.standAlone", TRUE), libPaths = NULL,
                         updateRprofile = getOption("SpaDES.project.updateRprofile", TRUE),
                         overwrite = getOption("SpaDES.project.overwrite", FALSE), # envir = environment(),
                         verbose = getOption("Require.verbose", 1L),
                         defaultDots, envir = parent.frame(),
                         dots, ...) {

  makeUpdateRprofileSticky(updateRprofile)

  origGetWd <- getwd()
  if (isTRUE(Restart))
    on.exit(setwd(origGetWd), add = TRUE)

  envirCur = environment()

  origArgOrder <- names(tail(sys.calls(), 1)[[1]])
  if (is.null(origArgOrder)) {
    firstNamedArg <- 0
  } else {
    argsAreInFormals <- origArgOrder %in% formalArgs(setupProject)
    firstNamedArg <- if (isTRUE(any(argsAreInFormals))) min(which(argsAreInFormals)) else Inf
  }

  dotsSUB <- as.list(substitute(list(...)))[-1]
  dotsLater <- dotsSUB
  if (firstNamedArg > 2) { # there is always an empty one at first slot
    firstSet <- if (is.infinite(firstNamedArg)) seq(length(origArgOrder) - 1) else (1:(firstNamedArg - 2))
    dotsLater <- dotsSUB[-firstSet]
    dotsSUB <- dotsSUB[firstSet]
    dotsSUB <- dotsToHereOuter(dots, dotsSUB, defaultDots)
  }

  functionsSUB <- substitute(functions)
  timesSUB <- substitute(times) # must do this in case the user passes e.g., `list(fireStart = times$start)`
  if (!missing(timesSUB))
    times <- evalSUB(val = timesSUB, envir = envirCur, valObjName = "times", envir2 = envir)
  modulesSUB <- substitute(modules) # must do this in case the user passes e.g., `list(fireStart = times$start)`
  paramsSUB <- substitute(params) # must do this in case the user passes e.g., `list(fireStart = times$start)`
  optionsSUB <- substitute(options) # must do this in case the user passes e.g., `list(fireStart = times$start)`
  pathsSUB <- substitute(paths) # must do this in case the user passes e.g., `list(modulePath = paths$projectpath)`
  sideEffectsSUB <- substitute(sideEffects)
  libPaths <- substitute(libPaths)

  if (missing(times))
    times <- list(start = 0, end = 1)

  pathsSUB <- checkProjectPath(pathsSUB, name, envir = envirCur, envir2 = envir)
  if (missing(name)) {
    name <- basename(normPath(pathsSUB[["projectPath"]]))
  } else {
    name <- checkNameProjectPathConflict(name, pathsSUB)
  }

  inProject <- isInProject(name)

  # setupOptions is run twice -- because package startup often changes options
  optsFirst <- setupOptions(name, optionsSUB, pathsSUB, times, overwrite = isTRUE(overwrite),
                            envir = envirCur, useGit = useGit,
                            updateRprofile = updateRprofile,
                            verbose = verbose - 1)

  if (isTRUE(getOption("SpaDES.project.fast"))) {
    base::options(fastOptions())
    packages <- NULL
    useGit <- FALSE
  }

  paths <- setupPaths(name, pathsSUB, inProject, standAlone, libPaths,
                      Restart = Restart, defaultDots = defaultDots,
                      useGit = useGit,
                      updateRprofile = updateRprofile, verbose = verbose) # don't pass envir because paths aren't evaluated yet
  inProject <- isInProject(name)

  setupRestart(updateRprofile = updateRprofile, paths, name, inProject, useGit = useGit,
               Restart = Restart, origGetWd, verbose) # This may restart

  # Need to assess if this is a new project locally, but the remote exists
  usingGit <- checkUseGit(useGit)
  gitUserName <- NULL
  if (isTRUE(usingGit)) {
    isLocalGitRepoAlready <- isProjectGitRepo(pathsSUB$projectPath, inProject)
    if (isFALSE(isLocalGitRepoAlready)) {
      gitUserName <- checkGitRemote(name, pathsSUB)
    }
  }

  # this next puts them in this environment, returns NULL
  functions <- setupFunctions(functionsSUB, paths = paths, envir = envirCur)

  modulePackages <- setupModules(name, paths, modulesSUB, inProject = inProject, useGit = useGit,
                                 gitUserName = gitUserName, updateRprofile = updateRprofile,
                                 overwrite = overwrite, envir = envirCur, verbose = verbose)
  modules <- Require::extractPkgName(names(modulePackages))
  names(modules) <- names(modulePackages)

  if (missing(packages))
    packages <- character()

  setupPackages(packages, modulePackages, require = require, paths = paths,
                setLinuxBinaryRepo = setLinuxBinaryRepo,
                standAlone = standAlone,
                libPaths = paths[["packagePath"]], envir = envirCur, verbose = verbose)

  # This next is to set the terra tempdir; don't do it in the cases where terra is not used
  # The longer unique(...) commented next is much slower; they are identical results
  # allPkgs <- unique(Require::extractPkgName(c(packages, unname(unlist(modulePackages)))))
  allPkgs <- c(packages, unname(unlist(modulePackages)))
  if (any(grepl("\\<terra\\>", allPkgs))) {
    terra::terraOptions(tempdir = paths$terraPath)
  }

  sideEffectsSUB <- setupSideEffects(name, sideEffectsSUB, paths, times, overwrite = isTRUE(overwrite),
                                     envir = envirCur, verbose = verbose)

  # 2nd time
  opts <- setupOptions(name, optionsSUB, paths, times, overwrite = isTRUE(overwrite), envir = envirCur,
                       useGit = useGit, updateRprofile = updateRprofile, verbose = verbose - 1)
  if (!is.null(opts$newOptions))
    opts <- mergeOpts(opts, optsFirst, verbose)

  if (isTRUE(getOption("SpaDES.project.fast"))) {
    message("Using fast options:")
    df <- fastOptions()[!names(fastOptions()) %in% names(opts[["newOptions"]])]
    df <- df[!sapply(df, is.null)]
    reproducible::messageDF(data.frame(option = names(df), val = unlist(df)))
  }

  options <- opts[["newOptions"]] # put into this environment so parsing can access

  # Run 2nd time after sideEffects & setupOptions -- may not be necessary
  # setupPackages(packages, modulePackages, require = require,
  #               setLinuxBinaryRepo = setLinuxBinaryRepo,
  #               standAlone = standAlone,
  #               libPaths = paths[["packagePath"]], envir = envirCur, verbose = verbose)

  if (!missing(config)) {
    # messageVerbose("config is supplied; using `SpaDES.config` package internals", verbose = verbose)
    # if (!requireNamespace("SpaDES.config", quietly = TRUE)) {
    #   Require::Install("PredictiveEcology/SpaDES.config@development")
    # }
    messageWarnStop("config is not yet setup to run with SpaDES.project")
    # if (FALSE)
    #   out <- do.call(SpaDES.config::useConfig, append(
    #     list(projectName = config,
    #          projectPath = paths[["projectPath"]], paths = paths),
    #     localVars))

  }

  # TODO from here to out <-  should be brought into the "else" block when `SpaDES.config is worked on`
  params <- setupParams(name, paramsSUB, paths, modules, times, options = opts[["newOptions"]],
                        overwrite = isTRUE(overwrite), envir = envirCur, verbose = verbose)

  studyAreaSUB <- substitute(studyArea)
  if (!is.null(studyAreaSUB)) {
    dotsSUB$studyArea <- setupStudyArea(studyAreaSUB, paths, envir = parent.frame(), verbose = verbose)
    studyArea <- dotsSUB$studyArea
  }

  if (length(dotsLater)) {
    dotsLater <- dotsToHereOuter(dots, dotsLater, defaultDots)
    dotsSUB <- Require::modifyList2(dotsSUB, dotsLater)
  }

  setupGitIgnore(paths, gitignore = getOption("SpaDES.project.gitignore", TRUE), verbose)

  # Put Dots in order
  if (length(dotsSUB) > 1)
    dotsSUB <- dotsSUB[na.omit(match(origArgOrder, names(dotsSUB)))]

  pathsOrig <- paths
  extras <- setdiff(names(paths), spPaths)
  paths <- paths[spPaths]
  attr(paths, "extraPaths") <- pathsOrig[extras]

  out <- append(list(
    modules = modules,
    paths = paths, # this means we lose the packagePath --> but it is in .libPaths()[1]
    # we also lose projectPath --> but this is getwd()
    params = params,
    times = times), dotsSUB)
  if (!is.null(options)) {
    attr(out, "projectOptions") <- opts$updates
  }

  return(out)
}

#' Individual `setup*` functions that are contained within `setupProject`
#'
#' These functions will allow more user control, though in most circumstances,
#' it should be unnecessary to call them directly.
#'
#' @details
#' `setPaths` will fill in any paths that are not explicitly supplied by the
#' user as a named list. These paths that can be set are:
#' `projectPath`, `packagePath`, `cachePath`, `inputPath`,
#' `modulePath`, `outputPath`, `rasterPath`, `scratchPath`, `terraPath`.
#' These are grouped thematically into three groups of paths:
#' `projectPath` and `packagePath` affect the project, regardless
#' of whether a user uses SpaDES modules. `cachePath`, `inputPath`, `outputPath` and
#' `modulePath` are all used by SpaDES within module contexts. `scratchPath`,
#' `rasterPath` and `terraPath` are all "temporary" or "scratch" directories.
#'
#' @return
#' `setupPaths` returns a list of paths that are created. `projectPath` will be
#' assumed to be the base of other non-temporary and non-R-library paths. This means
#' that all paths that are directly used by `simInit` are assumed to be relative
#' to the `projectPath`. If a user chooses to specify absolute paths, then they will
#' be returned as is. It is also called for its
#' side effect which is to call `setPaths`, with each of these paths as an argument.
#' See table for details. If a user supplies extra paths not useable by `SpaDES.core::simInit`,
#' these will added as an attribute ("extraPaths") to the `paths` element
#' in the returned object. These will still exist directly in the returned list
#' if a user uses `setupPaths` directly, but these will not be returned with
#' `setupProject` because `setupProject` is intended to be used with `SpaDES.core::simInit`.
#' In addition, three paths will be added to this same attribute automatically:
#' `projectPath`, `packagePath`, and `.prevLibPaths` which is the previous value for
#' `.libPaths()` before changing to `packagePath`.
#'
#' @section Paths:
#'   \tabular{lll}{
#' **Path**     \tab **Default if not supplied by user** \tab Effects \cr
#'                               \tab *Project Level Paths*   \tab \cr
#' `projectPath`\tab if `getwd()` is `name`, then just `getwd`; if not
#'                            `file.path(getwd(), name)`  \tab If current project is not this project
#'                                                             and using `Rstudio`, then the current
#'                                                             project will close and a new project will
#'                                                             open in the same Rstudio session, unless
#'                                                             `Restart = FALSE`\cr
#' `packagePath`\tab `file.path(tools::R_user_dir("data"), name, "packages",
#'                               version$platform, substr(getRversion(), 1, 3))`
#'                                                    \tab appends this path to `.libPaths(packagePath)`,
#'                                                         unless `standAlone = TRUE`, in which case,
#'                                                         it will set `.libPaths(packagePath,
#'                                                         include.site = FALSE)` to this path \cr
#' ------       \tab -----------                 \tab  -----         \cr
#'              \tab *Module Level Paths*                 \tab \cr
#' `cachePath`  \tab `file.path(projectPath, "cache")` \tab `options(reproducible.cachePath = cachePath)`\cr
#' `inputPath`  \tab `file.path(projectPath, "inputs")` \tab `options(spades.inputPath = inputPath)`\cr
#' `modulePath` \tab `file.path(projectPath, "modules")` \tab `options(spades.inputPath = outputPath)` \cr
#' `outputPath` \tab `file.path(projectPath, "outputs")` \tab `options(spades.inputPath = modulePath)` \cr
#' ------       \tab -----------                 \tab  -----         \cr
#'              \tab *Temporary Paths*                 \tab  \cr
#' `scratchPath`\tab `file.path(tempdir(), name)` \tab \cr
#' `rasterPath` \tab `file.path(scratchPath, "raster")` \tab sets (`rasterOptions(tmpdir = rasterPath)`) \cr
#' `terraPath`  \tab `file.path(scratchPath, "terra")` \tab   sets (`terraOptions(tempdir = terraPath)`)       \cr
#' ------       \tab -----------                 \tab  -----         \cr
#'              \tab *Other Paths*                 \tab  \cr
#' `logPath`    \tab `file.path(outputPath(sim), "log")` \tab sets `options("spades.logPath")` accessible by `logPath(sim)`\cr
#' `tilePath`   \tab Not implemented yet \tab Not implemented yet \cr
#' }
#'
#' @export
#' @inheritParams setupProject
#' @param inProject A logical. If `TRUE`, then the current directory is
#'  inside the `paths[["projectPath"]]`.
#' @rdname setup
#' @param envir An environment within which to look for objects. If called alone,
#' the function should use its own internal environment. If called from another
#' function, e.g., `setupProject`, then the `envir` should be the internal
#' transient environment of that function.
#' @importFrom Require normPath checkPath
#' @importFrom utils packageVersion
setupPaths <- function(name, paths, inProject, standAlone = TRUE, libPaths = NULL,
                       updateRprofile = getOption("SpaDES.project.updateRprofile", TRUE),
                       Restart = getOption("SpaDES.project.Restart", FALSE),
                       overwrite = FALSE, envir = parent.frame(),
                       useGit = getOption("SpaDES.project.useGit", FALSE),
                       verbose = getOption("Require.verbose", 1L), dots, defaultDots, ...) {

  envirCur <- environment()
  makeUpdateRprofileSticky(updateRprofile)

  dotsSUB <- as.list(substitute(list(...)))[-1]
  dotsSUB <- dotsToHere(dots, dotsSUB, defaultDots)

  messageVerbose(yellow("setting up paths ..."), verbose = verbose, verboseLevel = 0)

  pathsSUB <- substitute(paths) # must do this in case the user passes e.g., `list(modulePath = file.path(paths[["projectPath"]]))`
  pathsSUB <- checkProjectPath(pathsSUB, name, envir, parent.frame())

  paths <- evalSUB(val = pathsSUB, valObjName = "paths", envir = envirCur, envir2 = envir)
  paths <- parseFileLists(paths, paths, overwrite = isTRUE(overwrite),
                          envir = envir, verbose = verbose)

  if (!missing(name))
    name <- checkNameProjectPathConflict(name, paths)

  if (missing(name)) {
    name <- basename(paths[["projectPath"]])
  }
  if (missing(inProject))
    inProject <- isInProject(name)
  if (is.null(paths[["projectPath"]]))
    stop("Please specify paths[[\"projectPath\"]] as an absolute path")

  if (!is.null(libPaths)) {
    warning("libPaths argument is deprecated. Pass to `paths = list(packagePath = ...)`",
            "; it is being ignored", verbose = verbose)
    libPaths <- NULL
  }
  #if (is.null(libPaths) || is.call(libPaths)) {
  if (is.null(paths[["packagePath"]])) {
    paths[["packagePath"]] <- .libPathDefault(name)
  }

  if (is.null(paths[["modulePath"]])) paths[["modulePath"]] <- "modules"
  isAbs <- unlist(lapply(paths, isAbsolutePath))
  toMakeAbsolute <- isAbs %in% FALSE & names(paths) != "projectPath"
  if (isTRUE(any(toMakeAbsolute))) {
    firstPart <- paste0("^", paths[["projectPath"]], "(/|\\\\)")
    alreadyHasProjectPath <- unlist(lapply(paths[toMakeAbsolute], grepl, # value = TRUE,
                                           pattern = firstPart))
    if (isTRUE(any(alreadyHasProjectPath)))
      paths[toMakeAbsolute][alreadyHasProjectPath] <-
      gsub(firstPart, replacement = "", paths[toMakeAbsolute][alreadyHasProjectPath])

  }
  if (inProject) {
    paths[["projectPath"]] <- normPath(".") # checkPaths will make an absolute
  }
  # on linux, `normPath` doesn't expand if path doesn't exist -- so create first
  paths[["projectPath"]] <- checkPath(paths[["projectPath"]], create = TRUE)
  paths[["projectPath"]] <- normPath(paths[["projectPath"]]) # expands

  if (isTRUE(any(toMakeAbsolute))) {
    paths[toMakeAbsolute] <- lapply(paths[toMakeAbsolute], function(x) file.path(paths[["projectPath"]], x))
  }
  paths <- lapply(paths, checkPath, create = TRUE)

  if (!inProject) {
    setwd(checkPath(paths[["projectPath"]], create = TRUE))
  }

  if (is.null(paths$scratchPath)) {
    paths$scratchPath <- file.path(tempdir(), name)
  }
  if (!is.null(paths$scratchPath)) {
    paths <- Require::modifyList2(
      list(scratchPath = file.path(paths$scratchPath),
           rasterPath = file.path(paths$scratchPath, "raster"),
           terraPath = file.path(paths$scratchPath, "terra")
      ),
      paths)

  }

  paths <- Require::modifyList2(
    list(cachePath = file.path(paths[["projectPath"]], "cache"),
         inputPath = file.path(paths[["projectPath"]], "inputs"),
         outputPath = file.path(paths[["projectPath"]], "outputs")
    ),
    paths)

  paths <- lapply(paths, normPath)

  changedLibPaths <- (!identical(normPath(.libPaths()[1]), paths[["packagePath"]]) &&
                        (!identical(dirname(normPath(.libPaths()[1])), paths[["packagePath"]])))
  needSetLibPathsNow <- !Restart %in% TRUE || inProject %in% TRUE
  if (isTRUE(changedLibPaths)) {
    deps <- lapply(c("SpaDES.project", "Require"), function(pkg) {
      PackagePath <- getNamespaceInfo(pkg, "path")
      DESCinLibPaths <- file.path(PackagePath, "DESCRIPTION")
      DESCRIPTIONFileDeps(DESCinLibPaths)
    })

    deps <- unique(c("SpaDES.project", Require::extractPkgName(unlist(deps))))
    setupSpaDES.ProjectDeps(paths, verbose = verbose, deps = deps)
    needSetLibPaths <- TRUE
  } else {
    needSetLibPaths <- needSetLibPathsNow
  }

  prevLibPaths <- .libPaths()
  if (needSetLibPaths) {
    if (!useGit %in% FALSE) {
      # requireNamespace will find usethis in memory when devtools is used, but it fails because other
      #   deps of usethis are not in the deps of devtools --> can't use `require` b/c CRAN rules
      #   so need to load before changing .libPaths
      deps <- Require::pkgDep("usethis")
      depsSimple <- c("usethis", Require::extractPkgName(unname(unlist(deps))))
      loaded <- lapply(depsSimple, function(pkg) {
        requireNamespace(c(pkg))
      })
    }
    setLPCall <- quote(Require::setLibPaths(paths[["packagePath"]], standAlone = standAlone,
                                            updateRprofile = updateRprofile,
                                            exact = FALSE, verbose = verbose))
    prevLibPaths <- if (verbose < 0) {
      out <- capture.output(type = "message", ret <- eval(setLPCall))
      ret
      } else {
        eval(setLPCall)
      }
    if (needSetLibPathsNow %in% FALSE)
      on.exit(Require::setLibPaths(prevLibPaths), add = TRUE)
    paths[["packagePath"]] <- .libPaths()[1]
  }

  do.call(setPaths, append(paths[spPaths], list(verbose = verbose)))

  messageVerbose(yellow("  done setting up paths"), verbose = verbose, verboseLevel = 0)

  paths <- paths[order(names(paths))]
  paths[[".previousLibPaths"]] <- prevLibPaths

  pathsOrig <- paths
  extras <- setdiff(names(paths), spPaths)
  attr(paths, "extraPaths") <- paths[extras]

  paths
}


#' @export
#' @rdname setup
#'
#' @details
#' `setupFunctions` will source the functions supplied, with a parent environment being
#' the internal temporary environment of the `setupProject`, i.e., they will have
#' access to all the objects in the call.
#'
#' @return
#' `setupFunctions` returns NULL. All functions will be placed in `envir`.
#'
#' @importFrom data.table data.table
#' @examples
#'
#' \dontshow{origDir <- getwd()
#'           tmpdir <- Require::tempdir2() # for testing tempdir2 is better}
#' \dontshow{
#' if (is.null(getOption("repos"))) {
#'   options(repos = c(CRAN = "https://cloud.r-project.org"))
#'   }
#'   setwd(tmpdir)
#' }
#'  ## simplest case; just creates folders
#' out <- setupProject(
#'   paths = list(projectPath = ".") #
#' )
#' # specifying functions argument, with a local file and a definition here
#' tf <- tempfile(fileext = ".R")
#' fnDefs <- c("fn <- function(x) x\n",
#'             "fn2 <- function(x) x\n",
#'             "fn3 <- function(x) terra::rast(x)")
#' cat(text = fnDefs, file = tf)
#' funHere <- function(y) y
#' out <- setupProject(functions = list(a = function(x) return(x),
#'                                      tf,
#'                                      funHere = funHere), # have to name it
#'                     # now use the functions when creating objects
#'                     drr = 1,
#'                     b = a(drr),
#'                     q = funHere(22),
#'                     ddd = fn3(terra::ext(0,b,0,b)))
#' \dontshow{setwd(origDir)}
setupFunctions <- function(functions, name, sideEffects, paths, overwrite = FALSE,
                           envir = parent.frame(), verbose = getOption("Require.verbose", 1L),
                           dots, defaultDots, ...) {

  envirCur <- environment()
  dotsSUB <- as.list(substitute(list(...)))[-1]
  dotsSUB <- dotsToHere(dots, dotsSUB, defaultDots)

  if (!missing(functions)) {
    messageVerbose(yellow("setting up functions..."), verbose = verbose, verboseLevel = 0)

    functionsSUB <- substitute(functions) # must do this in case the user passes e.g., `list(fireStart = times$start)`
    functions <- evalSUB(functionsSUB, valObjName = "functions", envir = envir, envir2 = envir)

    functions <- parseFileLists(functions, paths = paths, namedList = TRUE,
                                overwrite = isTRUE(overwrite), envir = envir, verbose = verbose)
    isFuns <- vapply(functions, is.function, FUN.VALUE = logical(1))
    if (any(isFuns))
      list2env(functions[isFuns], envir = envir)
    messageVerbose(yellow("  done setting up functions"), verbose = verbose, verboseLevel = 0)
  }

}








#' @export
#' @rdname setup
#'
#' @details
#' Most arguments in the family of `setup*` functions are run *sequentially*, even within
#' the argument. Since most arguments take lists, the user can set values at a first
#' value of a list, then use it in calculation of the 2nd value and so on. See
#' examples. This "sequential" evaluation occurs in the `...`, `setupSideEffects`, `setupOptions`,
#' `setupParams` (this does not work for `setupPaths`) can handle sequentially
#' specified values, meaning a user can
#' first create a list of default options, then a list of user-desired options that
#' may or may not replace individual values. This can create hierarchies, *based on
#' order*.
#'
#' @return
#' `setupSideEffects` is run for its side effects (e.g., web authentication, custom package
#' options that cannot use `base::options`), with deliberately nothing returned to user.
#' This, like other parts of this function, attempts to prevent unwanted outcomes
#' that occur when a user uses e.g., `source` without being very careful about
#' what and where the objects are sourced to.
#'
#'
#' @importFrom data.table data.table
setupSideEffects <- function(name, sideEffects, paths, times, overwrite = FALSE,
                             envir = parent.frame(), verbose = getOption("Require.verbose", 1L),
                             dots, defaultDots, ...) {

  envirCur <- environment()
  dotsSUB <- as.list(substitute(list(...)))[-1]
  dotsSUB <- dotsToHere(dots, dotsSUB, defaultDots)

  if (!missing(sideEffects)) {
    messageVerbose(yellow("setting up sideEffects..."), verbose = verbose, verboseLevel = 0)

    sideEffectsSUB <- substitute(sideEffects) # must do this in case the user passes e.g., `list(fireStart = times$start)`
    sideEffects <- evalSUB(sideEffectsSUB, valObjName = "sideEffects", envir = envirCur, envir2 = envir)

    if (!is.character(sideEffects)) { # this is because I wrote this second;
      tf <- tempfile()
      writeLines(format(sideEffects[-1]), con = tf)
      sideEffects <- tf
    }

    sideEffects <- parseFileLists(sideEffects, paths, namedList = FALSE,
                                  overwrite = isTRUE(overwrite), envir = envir, verbose = verbose)
    messageVerbose(yellow("  done setting up sideEffects"), verbose = verbose, verboseLevel = 0)
  }

}


#' @export
#' @rdname setup
#'
#' @details
#' `setupOptions` can handle sequentially specified values, meaning a user can
#' first create a list of default options, then a list of user-desired options that
#' may or may not replace individual values. Thus final values will be based on the
#' order that they are provided.
#'
#' @return
#' `setupOptions` is run for its side effects, namely, changes to the `options()`. The
#'   list of modified options will be added as an attribute (`attr(out, "projectOptions")`),
#'   e.g., so they can be "unset" by user later.
#'
#'
#' @importFrom data.table data.table
setupOptions <- function(name, options, paths, times, overwrite = FALSE, envir = parent.frame(),
                         verbose = getOption("Require.verbose", 1L), dots, defaultDots,
                         useGit = getOption("SpaDES.project.useGit", FALSE),
                         updateRprofile = getOption("SpaDES.project.updateRprofile", TRUE),
                         ...) {

  makeUpdateRprofileSticky(updateRprofile)

  dotsSUB <- as.list(substitute(list(...)))[-1]
  dotsSUB <- dotsToHere(dots, dotsSUB, defaultDots)

  newValuesComplete <- oldValuesComplete <- NULL
  updates <- NULL
  if (!missing(options)) {

    messageVerbose(yellow("setting up options..."), verbose = verbose, verboseLevel = 0)

    preOptions <- base::options() # need prefix or else greedy evaluation occurs on the `options()` as if it is the arg

    optionsSUB <- substitute(options) # must do this in case the user passes e.g., `list(fireStart = times$start)`
    envirCur <- environment()
    options <- evalSUB(optionsSUB, valObjName = "options", envir = envirCur, envir2 = envir)
    # post check
    # if (isTRUE(try(any(grepl("^options$", eval(optionsSUB, envir = envir)[[1]])), silent = TRUE))) {
    #   warning("It looks like the options argument is passed options(...); please use list(...)")
    # }

    if (missing(paths)) {
      pathsSUB <- substitute(paths) # must do this in case the user passes e.g., `list(modulePath = paths$projectpath)`
      pathsSUB <- checkProjectPath(pathsSUB, name, envir = envirCur, envir2 = envir)
      paths <- setupPaths(paths = pathsSUB, defaultDots = defaultDots,
                          updateRprofile = updateRprofile,
                          useGit = useGit, verbose = verbose - 2)#, inProject = TRUE, standAlone = TRUE, libPaths,
    }

    options <- parseFileLists(options, paths, overwrite = isTRUE(overwrite),
                              envir = envir, verbose = verbose)

    postOptions <- base::options()
    newValues <- oldValues <- list()
    if (length(options)) {
      newValuesComplete <- options
      oldValuesComplete <- Map(nam = names(options), function(nam) NULL)
      oldValuesComplete[names(options)] <- preOptions[names(options)]
      whNULL <- which(lengths(oldValuesComplete) == 0)
      names(oldValuesComplete[unname(whNULL)]) <- names(options)[whNULL]
      newValues <- Require::setdiffNamed(options, preOptions)
      oldValues <- base::options(newValues)
      if (length(newValues)) {
        messageVerbose("The following options have been changed", verbose = verbose)
        updates <- data.table::data.table(optionName = names(newValues), newValue = newValues,
                                          oldValue = oldValues)
        messageDF(updates, verbose = verbose)
      }
    }
    messageVerbose(yellow("  done setting up options"), verbose = verbose, verboseLevel = 0)
  }
  return(invisible(list(newOptions = newValuesComplete, oldOptions = oldValuesComplete, updates = updates)))
}

isUnevaluatedList <- function(p) any( {
  if (!(length(p) == 1 && is.name(p))) { # this is "just" an object name
    if (grepl("^if$|^<-$", p[[1]])[1]) {
      if (grepl("^\\{$", p[[3]][[1]])[1]) {
        grepl("^list$", p[[3]][[2]][[1]])
      } else {
        grepl("^list$", p[[3]][[1]])[1] || is.atomic(p[[3]])[1]
      }
    } else {
      grepl("^list$", p[[1]])[1]
    }
  } else {
    FALSE
  }
}
)

#' @importFrom tools file_ext
#' @importFrom utils modifyList tail
parseListsSequentially <- function(files, parsed, curly, namedList = TRUE, envir = parent.frame(),
                                   verbose = getOption("Require.verbose")) {
  envs <- list(envir) # means

  if (!missing(curly)) {
    obj2 <- deparse(curly)
    obj <- obj2[-c(1, length(obj2))]
    parsed <- parse(text = obj)
  }
  if (!missing(parsed)) {
    envs2 <- parseExpressionSequentially(parsed, envs, namedList, verbose)

    # check for functions -- functions aren't in lists, but they behave a bit like lists internally
    isFuns <- any(vapply(envs2, FUN.VALUE = logical(1), function(env) {
      any(vapply(mget(ls(env), envir = env), is.function, FUN.VALUE = logical(1)))
    }))

    if (isTRUE(namedList) && !isFuns) {
      os <- parseEnvsWithNamedListsSequentially(envs2)
    } else {
      os <- as.list(tail(envs2, 1)[[1]])
    }
  } else {
    llOuter <- lapply(files, function(optFiles) {
      os <- NULL
      if (isTRUE(tools::file_ext(optFiles) %in% c("txt", "R"))) {
        pp <- parse(optFiles)
        obj <- parseListsSequentially(parsed = pp, namedList = namedList, envir = envir,
                                      verbose = verbose)
      }})

    # basically, the optFiles may not be a namedList, but the objects in the file may be
    #   need reassess
    hasName <- lapply(llOuter, function(x) !is.null(names(x)))
    if (all(unlist(hasName))) namedList <- TRUE
    if (isTRUE(namedList))
      os <- Reduce(modifyList, llOuter)
    else
      os <- tail(llOuter, 1)[[1]][[1]]
  }

  os
}

evalListElems <- function(l, envir, verbose = getOption("Require.verbose", 1L)) {

  # need to deal with `if`. If we break it apart, then we fail to evaluate the if part
  # Also, every "normal" object e.g., mode <- "development"
  warns <- list()
  withCallingHandlers(l2 <- try(eval(l, envir), silent = TRUE),
                      warning = function(w) {
                        warns <<- w
                      })
  if (is(l2, "try-error")) {
    mess <- gsub("Error in eval\\(l, envir\\) : ", "", as.character(l2))
    mess <- gsub("\\n", "", as.character(mess))
    messageVerbose(mess, verbose = verbose)
    if (length(warns))
      messageVerbose("  (warning: ", warns$m, ")", verbose = verbose)

    isList <- FALSE
    isAssignedList <- FALSE
    if (length(l) == 3) {# can be list(...) or obj <- list(...)
      if (length(l[[3]]) > 1) {
        if (identical(l[[3]][[1]], quote(list))) {
          isList <- TRUE
          # if (identical(l[[2]][[1]], quote("<-")))
          if (!is(l, "if"))
            isAssignedList <- TRUE
          origL <- l
        }
      }
    } else {
      if (length(l) > 1)
        if (identical(l[[1]], quote(list))) isList <- TRUE
    }
    if (isList) {
      lList <- as.list(l)
      calls <- mapply(m = lList, function(m) inherits(m, "call")) # actual calls `times$start`
      names <- mapply(m = lList, function(m) inherits(m, "name")) # just objects `mode`
      namesNoList <- names[-1]
      if (any(namesNoList) && !isAssignedList) { # the -1 removes "list"
        whNames <- which(names)[-1]
        whNotNames <- which(!names)
        elems <- lList[whNames] # remove "list"
        out <- Map(l3 = elems, function(l3) {
          out <- evalListElems(l3, envir = envir, verbose = verbose)
        })
        lList[whNames] <- out
      }

      if (any(calls)) {
        whCalls <- which(calls)
        whNotCalls <- which(!calls)
        goInList <- length(whCalls) == 1

        if (isTRUE(goInList))
          elems <- lList[[whCalls]]
        else
          elems <- lList[whCalls]
        out <- mapply(l3 = elems, function(l3) {
          out <- evalListElems(l3, envir = envir, verbose = verbose)
        })

        if (isTRUE(goInList)) {
          lList <- out
        } else {
          lList[whCalls] <- out
        }

        l <- lList[-1] # remove "list"

      }
    }
    if (isAssignedList) {
      assign(as.character(parse(text = origL[[2]])), l, envir = envir)
    }

  } else {
    l <- l2
  }
  l
}

#' @export
#' @rdname setup
#' @details
#' `setupModules` will download all modules do not yet exist locally. The current
#' test for "exists locally" is simply whether the directory exists. If a user
#' wants to update the module, `overwrite = TRUE` must be set, or else the user can
#' remove the folder manually.
#'
#' @return
#' `setupModules` is run for its side effects, i.e., downloads modules and puts them
#' into the `paths[["modulePath"]]`. It will return a named list, where the names are the
#' full module names and the list elemen.ts are the R packages that the module
#' depends on (`reqsPkgs`)
#'
#' @param gitUserName The GitHub account name. Used with git clone git@github.com:*gitHuserName*/name
#' @importFrom tools file_ext
setupModules <- function(name, paths, modules, inProject, useGit = getOption("SpaDES.project.useGit", FALSE),
                         overwrite = FALSE, envir = parent.frame(), gitUserName,
                         verbose = getOption("Require.verbose", 1L), dots, defaultDots,
                         updateRprofile = getOption("SpaDES.project.updateRprofile", TRUE),
                         ...) {

  envirCur <- environment()
  makeUpdateRprofileSticky(updateRprofile)

  dotsSUB <- as.list(substitute(list(...)))[-1]
  dotsSUB <- dotsToHere(dots, dotsSUB, defaultDots)

  if (missing(modules)) {
    modulesOrig <- character()
    packages <- list()
  } else {
    if (missing(paths)) {
      pathsSUB <- substitute(paths) # must do this in case the user passes e.g., `list(modulePath = paths$projectpath)`
      pathsSUB <- checkProjectPath(pathsSUB, name, envir = envirCur, envir2 = envir)
      paths <- setupPaths(paths = pathsSUB, updateRprofile = updateRprofile, useGit = useGit,
                          defaultDots = defaultDots)#, inProject = TRUE, standAlone = TRUE, libPaths,
    }
    if (missing(inProject))
      inProject <- isInProject(name)

    messageVerbose(yellow("setting up modules..."), verbose = verbose, verboseLevel = 0)

    modulesSUB <- substitute(modules) # must do this in case the user passes e.g., `list(fireStart = times$start)`
    modules <- evalSUB(val = modulesSUB, valObjName = "modules", envir = envirCur, envir2 = envir)
    exts <- tools::file_ext(modules)
    isRepo <- nzchar(exts) & exts %in% "R"

    if (any(isRepo)) {
      messageVerbose("modules arg supplied as file(s); parsing ... ", verbose = verbose)
      modules <- parseFileLists(modules, paths, namedList = FALSE, overwrite = isTRUE(overwrite),
                                envir = envir, verbose = verbose)
    }

    anyfailed <- character()
    modulesOrig <- modules
    modulesOrigPkgName <- extractPkgName(modulesOrig)
    modulesOrigNestedName <- extractModName(modulesOrig)

    if (useGit %in% FALSE) {
      offlineMode <- getOption("Require.offlineMode")
      if (isTRUE(offlineMode)) {
        opt <- base::options(Require.offlineMode = FALSE)
        on.exit(try(base::options(opt), silent = TRUE))
      }
      out <- getModule(modules, paths[["modulePath"]], overwrite = overwrite, verbose = verbose)
      if (isTRUE(offlineMode))
        base::options(opt)
      anyfailed <- out$failed
      modules <- anyfailed
    }

    isGH <- isGitHub(modules) & grepl("@", modules) # the default isGitHub allows no branch
    anyFailedGH <- intersect(anyfailed, modules[isGH])

    usingGit <- checkUseGit(useGit)

    if (usingGit || ( length(anyFailedGH) ) ) {

      isLocalGitRepoAlready <- isProjectGitRepo(paths[["projectPath"]], inProject)
      origDir <- getwd()
      on.exit({
        setwd(origDir)
      }
      )

      # This will create a new Git Repo at the top level
      if (isLocalGitRepoAlready %in% FALSE && is.character(useGit)) {
        dir1 <- dir(".", all.files = TRUE)
        onlyFiles <- dir1[!dir.exists(dir1)]
        if (length(onlyFiles) == 0) {
          file.create("README.md")
          dir1 <- dir(".", all.files = TRUE)
          onlyFiles <- dir1[!dir.exists(dir1)]
        }
        theFiles <- paste(onlyFiles, collapse = " ")
        system(paste("git add ", theFiles))

        ignoreAFolder(gitIgnoreFile = ".gitIgnore", paths$cachePath, paths[["projectPath"]])
        ignoreAFolder(gitIgnoreFile = ".gitIgnore", paths$inputPath, paths[["projectPath"]])

        system(paste("git commit -a -m \"first commit\""))
        rl <- readline("Update git config --global --edit ? (Y or N): ")
        if (startsWith(tolower(rl), "y") ) {
          system(paste0("git config --global --edit "))

          rl <- readline("Need to amend this commit to use this new user ? (Y or N): ")
          if (startsWith(tolower(rl), "y") ) {
            system(paste0("git commit --amend --reset-author"))
          }
        }
        system("git branch -M main")
        # rl <- readline("Type remote ssh url after git@github.com:")
        addOrigin <- paste0('git remote add origin git@github.com:', gitUserName,'/', name ,'.git')
        system(addOrigin)
        # system(paste0("git remote add origin git@github.com:", rl))
        system("git push -u origin main")
        isLocalGitRepoAlready <- TRUE

      }

      # lala <- unlist(strsplit(
      #   split = " {4,}",
      #   c(
      #     #     'git init -b main
      #     # git add .
      #     # git commit -m "first commit"
      #     'git branch -M main',
      #     paste0('git remote add origin git@github.com:',gitUserName,'/', name ,'.git'),
      #     'git push --set-upstream origin main
      #   git push -u origin main
      #   ')))
      #
      # lala <- gsub("\n", "", lala)
      # lala2 <- lapply(lala, system, intern = TRUE)


      gitSplit <- splitGitRepo(modules)
      gitSplit <-try(Require::invertList(gitSplit), silent = TRUE)
      if (is(gitSplit, "try-error"))
        stop("Did you specify the modules correctly? Is this correct:\n",
             paste(modules, collapse = "\n"))

      mapply(split = gitSplit, function(split) {
        modPath <- file.path(split$acct, split$repo)
        localPath <- file.path(paths[["modulePath"]], split$repo)
        if (!dir.exists(localPath)) {

          prev <- setwd(file.path(paths[["modulePath"]]))
          cloneOrSubmodule <- if (isTRUE(isLocalGitRepoAlready)) {
            "submodule add"
          } else {
            "clone"
          }

          cmd <- paste0("git ", cloneOrSubmodule," https://github.com/", modPath)

          for (i in 1:2) {
            system(cmd)
            if (dir.exists(file.path(paths[["modulePath"]], split$repo)))
              break
            if (getOption("SpaDES.project.forceGit", TRUE)) {
              messageVerbose("It looks like the submodule was deleted; restoring it.\n",
                      "Set options('SpaDES.project.forceGit' = FALSE) to prevent this", verbose = verbose)
              verbose <<- max(0, verbose - 1)
              cmd2 <- strsplit(cmd, cloneOrSubmodule)[[1]]
              cmd <- paste0(cmd2[1], cloneOrSubmodule, " --force", tail(cmd2, 1))
            }
          }

        } else {
          messageVerbose("module exists at ", localPath, "; not cloning", verbose = verbose)
        }
        reportBranch <- TRUE
        # if (!grepl("master|main|HEAD", split$br)) {
        prev <- setwd(file.path(paths[["modulePath"]], split$repo))
        cmd <- "git rev-parse --abbrev-ref HEAD"
        # next line -- cd doesn't work on my windows; no idea why
        # cmd <- paste0("cd ", file.path(paths[["modulePath"]], split$repo), " && git rev-parse --abbrev-ref HEAD ")
        curBr <- system(cmd, intern = TRUE)
        if (!identical(split$br, curBr)) {
          prev <- setwd(file.path(paths[["modulePath"]], split$repo))
          cmd <- paste0("git checkout ", split$br)
          system(cmd)
          reportBranch <- FALSE
        }
        # }
        cmd <- paste0("git pull")
        system(cmd)

        if (reportBranch)
          messageVerbose("\b ... on ", split$br, " branch")
      })
      messageVerbose("You will likely have to commit changes to git repository now", verbose = verbose)
    }
    if (is.character(useGit) && isLocalGitRepoAlready %in% FALSE) {

      res1 <- system(paste("git remote add", name, useGit), intern = TRUE)
      res2 <- system(paste("git push", name), intern = TRUE)
      if (grepl("error|fatal", res1))
        stop("git push failed; perhaps no rights to push; perhaps open a Git GUI to complete this step")

    }

    # Need full path
    m <- gsub("@[[:alnum:]_-]+$", "", modulesOrig)
    m <- gsub("@[[:alnum:]_]+/", "/", m)
    m <- lapply(strsplit(m, "/"), function(r) r[-c(1, length(r))])
    m <- vapply(m, paste, collapse = "/", FUN.VALUE = character(1))

    modulePackages <- Map(mo = modulesOrigNestedName, di = m, function(di, mo)
      modulePackages <-
        unlist(packagesInModules(modulePath = file.path(paths[["modulePath"]], di),
                                 modules = mo), use.names = FALSE))
    # modulePackages <- packagesInModules(modulePath = file.path(paths[["modulePath"]], dirname(m)),
    #                                     modules = modulesOrigNestedName)
    packages <- modulePackages[modulesOrigNestedName]
    messageVerbose(yellow("  done setting up modules"), verbose = verbose, verboseLevel = 0)

  }
  names(packages) <- modulesOrig
  return(packages)

}


#' @export
#' @rdname setup
#' @details
#' `setupPackages` will read the modules' metadata `reqdPkgs` element. It will combine
#' these with any packages passed manually by the user to `packages`, and pass all
#' these packages to `Require::Install(...)`.
#' @param modulePackages A named list, where names are the module names, and the elements
#'   of the list are packages in a form that `Require::Require` accepts.
#'
#' @return
#' `setupPackages` is run for its side effects, i.e., installing packages to
#' `paths[["packagePath"]]`.
#'
setupPackages <- function(packages, modulePackages = list(), require = list(), paths, libPaths,
                          setLinuxBinaryRepo = TRUE,
                          standAlone, envir = parent.frame(), verbose = getOption("Require.verbose"),
                          dots, defaultDots, ...) {

  envirCur <- environment()
  dotsSUB <- as.list(substitute(list(...)))[-1]
  dotsSUB <- dotsToHere(dots, dotsSUB, defaultDots)

  if (isTRUE(setLinuxBinaryRepo))
    Require::setLinuxBinaryRepo()

  if (missing(packages))
    packages <- character()

  if (
    (length(packages) || length(unlist(modulePackages)) || length(require)) &&
    !is.null(packages)
  ){

    messageVerbose(yellow("setting up packages..."), verbose = verbose, verboseLevel = 0)
    messageVerbose("Installing any missing reqdPkgs", verbose = verbose)
    continue <- 3L
    while (continue) {
      mp <- unlist(unname(modulePackages))
      if (is(mp, "list")) { # means there was a call in modulePackages i.e., an unquoted thing like PredictiveEcology/Require
        mp <- substitutePackages(mp, envir = envirCur)
      }
      if (!any(grepl("SpaDES.core", extractPkgName(mp))))
        mp <- c(mp, "SpaDES.core")
      # requireToTry <- unique(c(mp, require))
      packagesToTry <- c(packages, mp, require)
      packagesToTry <- packagesToTry[!duplicated(packagesToTry)]
      areFilesWithPackages <- endsWith(tolower(packagesToTry), ".r") # & grepl("@", packagesToTry) # the default isGitHub allows no branch
      if (any(areFilesWithPackages)) {
        # fileWithPackages <- packagesToTry[areFilesWithPackages]
        remoteFiles <- isGitHub(areFilesWithPackages) & grepl("@", packagesToTry) # the default isGitHub allows no branch
        # hasAt <- grepl("@", packagesToTry)
        # problems <- !hasAt & remoteFiles
        # packagesToTry[problems] <- paste0(packagesToTry[problems], "@HEAD")
        aa <- parseFileLists(trimVersionNumber(packagesToTry[remoteFiles]), paths = paths,
                             envir = envir, namedList = FALSE)
        packagesToTry <- c(packagesToTry[-remoteFiles], unname(unlist(aa)))
      }

      requirePkgNames <- Require::extractPkgName(require)
      # packagesToTry <- unique(c(packages, mp, requireToTry))
      # NOTHING SHOULD LOAD HERE; ONLY THE BARE MINIMUM REQUESTED BY USER
      # useRenv <- getOption("SpaDES.project.useRenv", FALSE)
      # if (useRenv) {
      #   DESCFile <- makeDESCRIPTIONproject(modulePath = paths$modulePath, projectPath = paths$projectPath,
      #                          modules = extractPkgName(names(modulePackages)),
      #                          package = basename(paths$projectPath),
      #                          write = FALSE,
      #                          verbose = verbose - 2)
      #   New <- readLines(DESCFile)
      #   Prev <- readLines("DESCRIPTION")
      #   diffs <- setdiff(New, Prev)
      #   if (length(diffs)) {
      #     messageVerbose("Updating DESCRIPTION file with these changes: ",
      #                    paste(diffs, collapse = "\n"), verbose = verbose)
      #     fileRenameOrMove(DESCFile, file.path(paths$projectPath, "DESCRIPTION"))
      #   }
      # }

      # if (useRenv) {
      #   if (!dir.exists("renv")) renv:init()
      #   renv::install(prompt = TRUE)
      # } else {
        out <- try({
          Require::Require(packagesToTry, require = requirePkgNames, # require = Require::extractPkgName(requireToTry),
                           standAlone = standAlone,
                           libPaths = libPaths,
                           verbose = verbose)
        })
      # }


      if (is(out, "try-error")) {
        deets <- gsub(".+Can't find ([[:alnum:]]+) on GitHub repo (.+); .+", paste0("\\2@\\1"), as.character(out))
        miss <- unlist(Map(mp = modulePackages, function(mp) grep(value = TRUE, pattern = deets, mp)))
        if (length(miss)) {
          modulePackages[[names(miss)]] <- setdiff(modulePackages[[names(miss)]], miss)
          warning("Module ", names(miss), " has reqdPkgs ", paste0(miss, collapse = ", "),
                  ", but branch don't exist; \nplease update module. ",
                  "Omitting that package from this Require call which may mean ",
                  Require::extractPkgName(unname(miss)), " doesn't get installed")
        }
        continue <- continue - 1L
      } else {
        continue <- 0L
      }
    }
    messageVerbose(yellow("  done setting up packages"), verbose = verbose, verboseLevel = 0)
  } else {
    messageVerbose(yellow("no packages to set up"), verbose = verbose, verboseLevel = 0)
  }

  if (is.null(packages)) {
    if (!is.null(require))
      lapply(Require::extractPkgName(require), base::require, character.only = TRUE)
  }
  messageVerbose(".libPaths() are: ", paste(.libPaths(), collapse = ", "), verbose = verbose)

  invisible(NULL)
}

#' @export
#' @rdname setup
#'
#' @return
#' `setupParams` prepares a named list of named lists, suitable to be passed to
#' the `params` argument of `simInit`.
#'
#'
#' @importFrom data.table data.table
setupParams <- function(name, params, paths, modules, times, options, overwrite = FALSE, envir = parent.frame(),
                        verbose = getOption("Require.verbose", 1L), dots, defaultDots,
                        ...) {

  envirCur <- environment()
  dotsSUB <- as.list(substitute(list(...)))[-1]
  dotsSUB <- dotsToHere(dots, dotsSUB, defaultDots)

  if (missing(params)) {
    params <- list()
  } else {

    messageVerbose(yellow("setting up params..."), verbose = verbose, verboseLevel = 0)

    paramsSUB <- substitute(params) # must do this in case the user passes e.g., `list(fireStart = times$start)`
    params <- evalSUB(val = paramsSUB, valObjName = "params", envir = envirCur, envir2 = envir)
    params <- parseFileLists(params, paths, overwrite = isTRUE(overwrite),
                             envir = envir, verbose = verbose)

    if (length(params)) {

      modulesSimple <- Require::extractPkgName(modules)

      paramsForModules <- intersect(modulesSimple, names(params))
      overSupplied <- setdiff(names(params), c(".globals", paramsForModules))

      # This will shrink down to only the modulesSimple -- all others are gone
      hasDotGlobals <- isTRUE(".globals" %in% names(params))
      globs <- if (hasDotGlobals) params[[".globals"]] else NULL

      if (length(overSupplied)) {
        params <- params[paramsForModules]

        messageVerbose(blue("Only returning params that are relevant for modules supplied.\n",
                            "Omitting parameters supplied for: ",
                            paste(overSupplied, collapse = ", "), "\n --- (set verbose == 2 to see details) --- "),
                       verbose = verbose)
      }


      mods <- setdiff(names(params), ".globals")
      # if (FALSE) {
      #   messageVerbose(blue("Using SpaDES.core to compare metadata with supplied parameters: "),
      #                  verbose = verbose)
      #   params <- Map(mod = mods, function(mod) {
      #     knownPars <- SpaDES.core::moduleMetadata(module = mod, path = paths[["modulePath"]],
      #                                              defineModuleListItems = "parameters")$parameters$paramName
      #     if (!is.null(params[[mod]])) {
      #       supplied <- names(params[[mod]])
      #       anyGlobals <- intersect(supplied, names(params[[".globals"]]))
      #       paramsInclGlobalsSupplied <- unique(c(supplied, anyGlobals))
      #       knownParsInMod <- paramsInclGlobalsSupplied %in% knownPars
      #       overInclusion <- knownParsInMod %in% FALSE
      #       if (any(overInclusion)) {
      #         params[[mod]] <- params[[mod]][paramsInclGlobalsSupplied[!overInclusion]]
      #         messageVerbose(blue("These parameters set (",
      #                             paste(paramsInclGlobalsSupplied[knownParsInMod %in% FALSE], collapse = ", "),
      #                             "), but they are not in ", mod,
      #                             " metadata; this should be either added to the metadata, ",
      #                             "or not set in the params argument"), verbose = verbose)
      #       }
      #
      #     } else {
      #       messageVerbose(blue("No parameters set for ", mod), verbose = verbose, verboseLevel = 2)
      #     }
      #     params[[mod]]
      #   })
      # } else {
      #   messageVerbose("Skipping checking of parameters supplied against module parameters because ",
      #                  "it is a slow step that will be performed during `SpaDES.core::simInit`.",
      #                  verbose = verbose)
      # }
      if (hasDotGlobals)
        params[[".globals"]] <- globs
      messageVerbose(blue("The following params were created: "), verbose = verbose, verboseLevel = 2)
      oo <- capture.output(params)
      messageVerbose(blue(paste(oo, collapse = "\n")), verboseLevel = 2, verbose = verbose)
      messageVerbose(yellow("  done setting up params"), verbose = verbose, verboseLevel = 0)
    }
  }
  params <- Require::modifyList2(list(.globals = list(.studyAreaName = basename(paths[["projectPath"]]))),
                        params)
  return(params)
}


parseFileLists <- function(obj, paths, namedList = TRUE, overwrite = FALSE, envir,
                           verbose = getOption("Require.verbose", 1L), dots, ...) {
  if (is(obj, "list")) {
    nams <- names(obj)
    if (is.null(nams)) {
      named <- rep(FALSE, length(obj))
    } else {
      named <- nzchar(nams)
    }
    notNamed <- which(!named)
    if (length(notNamed)) {
      if (any(named))
        namedElements <- obj[which(named)]
      obj[notNamed] <- Map(objInner = obj[notNamed],
                           function(objInner)
                             parseFileLists(objInner, paths, namedList, overwrite,
                                            envir, verbose, dots, ...))
      if (any(named))
        obj[named] <- Map(x = obj[named], nam = names(namedElements), function(x, nam) {
          y <- list(x)
          names(y) <- nam
          y})
      obj <- Reduce(f = modifyList, obj)
    }
  }

  if (is.character(obj)) {
    obj <- mapply(opt = obj, function(opt) {
      isGH <- isGitHub(opt) && grepl("@", opt) # the default isGitHub allows no branch
      if (isGH) {
        rem <- opt
        gitRepo <- splitGitRepo(opt)
        opt <- stripQuestionMark(opt)
        relativeFilePath <- extractGitHubFileRelativePath(opt)
        # if (startsWith(relativeFilePath, basename(paths[["projectPath"]]))) {
        #   # This is a projectPath that is one level into a GitHub repository
        #   opt <- file.path(dirname(paths[["projectPath"]]), relativeFilePath)
        # } else {
        #   opt <- file.path(paths[["projectPath"]], relativeFilePath)
        # }
        opt <- stripDuplicateFolder(relativeFilePath, paths[["projectPath"]])

        fe <- file.exists(opt)
        if (isTRUE(fe && isFALSE(overwrite))) {
          messageVerbose(opt, " already exists; not downloading", verbose = verbose)
        } else {
          if (isTRUE(fe)) {
            messageVerbose(opt, " already exists; overwrite = TRUE; downloading again", verbose = verbose)
            unlink(opt)
          }
          # opt is the correct destination file because it has removed potential duplicated folder names
          #   but getGitHubfile won't know this ... so give it a temporary destdir
          destdir <- Require::tempdir2()
          temp <- getGithubFile(rem, destDir = destdir, overwrite = isTRUE(overwrite))
          checkPath(dirname(opt), create = TRUE)
          same <- temp == opt
          notSame <- same %in% FALSE
          optOrig <- opt
          if (any(same)) {
            temp <- temp[notSame]
            opt <- opt[notSame]
          }

          if (length(temp)) {
            copied <- linkOrCopy(temp, opt)
          }
          opt <- optOrig

        }

      } else {
        isURL <- grepl("^http", opt)
        if (isURL) {
          destfile <- file.path(paths[["inputPath"]], basename(opt))
          if (requireNamespace("reproducible", quietly = TRUE)) {
            ret <- reproducible::preProcess(url = opt, targetFile = destfile, overwrite = isTRUE(overwrite), fun = NA)
          } else {
            dlfileOut <- try(download.file(url = opt, destfile = destfile))
            if (is(dlfileOut, "try-error"))
              stop("File could not be downloaded; perhaps try again after install.packages('reproducible')")
          }
          opt <- destfile
        }
        if (!file.exists(opt))
          messageVerbose(opt, paste(" has no @ specified, so assuming a local file,",
                                    "but local file does not exist"), verbose = verbose)
      }
      opt
    }, SIMPLIFY = TRUE)
    if (verbose > 0) {
      objLocal <- gsub(paths[["projectPath"]], "", obj)
      mess <- unlist(Map(nam = obj, rem = names(obj), function(rem, nam) {
        objRem <- strsplit(rem, split = "/")[[1]]
        len <- length(objRem)
        if (len > 5) {
          paste0(paste(objRem[1:4], collapse = "/"), "/.../", paste(objRem[c(len - 1):len], collapse = "/"))
        } else {
          rem
        }
      }))
      if (!identical(mess, objLocal))
        messageDF(data.frame(url = mess, "is" = "--->", localFile = objLocal))
    }
    areAbs <- isAbsolutePath(obj)
    if (any(areAbs %in% FALSE)) {
      if (!startsWith(fs::path_norm(obj[areAbs %in% FALSE]), fs::path_norm(paths[["projectPath"]]))) {
        obj[areAbs %in% FALSE] <- file.path(paths[["projectPath"]], obj[areAbs %in% FALSE])
      }
    }

  }

  if (is.character(obj)) {
    obj <- parseListsSequentially(files = obj, namedList = namedList, envir = envir,
                                  verbose = verbose)
  } else if (is(obj, "{")) {
    obj <- parseListsSequentially(curly = obj, namedList = namedList, envir = envir,
                                  verbose = verbose)
  }

  return(obj)
}

checkProjectPath <- function(paths, name, envir, envir2) {

  if (missing(paths)) {
    paths <- list()
  }
  if (is.name(paths) || is.call(paths)) {
    paths <- evalSUB(paths, valObjName = "paths", envir = envir, envir2 = envir2)
  }
  if (is.null(paths[["projectPath"]])) {
    prjPth <- if (missing(name))
      getOption("SpaDES.project.projectPath", normPath("."))
    else {
      if (isInProject(name)) {
        normPath(".")
      } else {
        checkPath(name, create = TRUE)
      }

    }
    prjPth <- list(projectPath = prjPth)

    paths <- append(prjPth, as.list(paths))
    paths <- paths[nzchar(names(paths))]
  }

  if (!is.null(paths[["projectPath"]])) {
    paths[["projectPath"]] <- evalSUB(paths[["projectPath"]], valObjName = "paths", envir, envir2)
    #  name <- basename(normPath(paths[["projectPath"]]))
  } else {
    stop("Must provide either a name or a paths[[\"projectPath\"]]")
  }

  paths
}

isInProject <- function(name) {
  if (!missing(name)) {
    gtwd <- getwd()
    gtwdExp <- basename(fs::path_expand_r(gtwd))
    nameExp <- basename(fs::path_expand_r(extractPkgName(name)))
    out <- identical(gtwdExp, nameExp)
    if (out %in% FALSE) {
      gtwdExp <- basename(fs::path_expand(gtwd))
      nameExp <- basename(fs::path_expand(extractPkgName(name)))
      out <- identical(gtwdExp, nameExp)
    }
  } else {
    out <- TRUE
  }
  out
}

isInRstudioProj <- function(name) {
  tryCatch(identical(name, basename(findProjectPath())),
           silent = TRUE, error = function(x) FALSE)
}

inTempProject <- function(paths) {
  grepl(normPath(tempdir()), normPath(paths[["projectPath"]]))
}


evalSUB <- function(val, valObjName, envir, envir2) {
  valOrig <- val
  val2 <- val
  userQuoted <- tryCatch(grepl("quote", val), silent = TRUE, error = function(e) FALSE)
  warns <- character()
  withCallingHandlers({
  while (inherits(val, "call") || inherits(val, "name") || inherits(val, "{") || inherits(val, "if")) {
    if (identical(valObjName, "options")) {
      optionsGrepStart <- "^options\\>"
      if (any(grepl(optionsGrepStart, val))) {
        val[[1]] <- as.name(gsub(optionsGrepStart, "list", val[[1]]))
      }
    }
    if (inherits(val, "name"))
      val2 <- get0(val, envir = envir)
    else {
      val2 <- try(eval(val, envir = envir), silent = TRUE)
    }

    if ((identical(val2, val) && !missing(envir2)) || is.null(val2) ||
        is(val2, "try-error")) {
      val3 <- try(eval(val, envir = envir2), silent = TRUE)
      if (is(val3, "try-error")) {
        # last ditch effort -- brute force
        sfs <- sys.frames()
        for (frm in rev(sfs)) {
          # env <- new.env(parent = frm)
          val3 <- try(eval(val, envir = frm), silent = TRUE)
          if (!is(val3, "try-error"))
            break
        }
        # if here, it means val3 is already an error, so only show val2 warning; val3 will likely
        #   be misleading
        # if (is(val2, "try-error")) {
        #   browser()
        #   val2 <- errorMsgCleaning(val2, valOrig)
        #   warning(val2, call. = FALSE)
        # } else {
        #   if (is(val3, "try-error")) {
        #     val3 <- errorMsgCleaning(val3, valOrig)
        #     warning(val3)
        #   }
        # }
      }
      val <- val3
      val2 <- val3
    } else {
      val <- val2
    }
    if (missing(envir2))
      break
  }
  },
  warning = function(w) {
    warns <<- c(warns, w$message)
    invokeRestart("muffleWarning")
  })
  if (length(warns)) {
    warns <- rev(unique(warns))
    # try to give use more help in debugging
    spaces <- unlist(Map(space = rep("  ", length(warns)), num = seq(length(warns)),
               function(space, num) paste(collapse = "", rep(space, num))))
    warns <- paste0(spaces, warns)
    warns <- errorMsgCleaning(warns, valOrig)
    if (any(userQuoted))
      message(warns)
    else
      warning(warns, call. = FALSE)
    # val2 <- valOrig
  }
  # if (is(val2, "try-error")) {
  #   if (any(userQuoted))
  #     message(rev(val2))
  #   else
  #     warning(rev(val2))
  # }
  if (is(val2, "list") && !is.null(names(val2))) {
    env <- environment()
    namesToEval <- names(val2)
    if (any(userQuoted[-1])) {
      namesToEval <- namesToEval[userQuoted[-1] %in% FALSE]
    }
    # Sequential evaluation
    if (length(namesToEval)) {
      Map(nam = namesToEval, function(nam) {
        val2[[nam]] <<- evalSUB(val2[[nam]], valObjName = valObjName,
                                envir = env, envir2 = envir)
        assign(valObjName, val2, envir = env)
      }
      )
      assign("val2", get(valObjName), envir = env)
    }
  }

  if (is(val2, "try-error")) {
    val2 <- errorMsgCleaning(val2, valOrig)
    warning(val2, call. = FALSE)
  } else {
    if (exists("val3", inherits = FALSE))
      if (is(val3, "try-error")) {
        val3 <- errorMsgCleaning(val3, valOrig)
        warning(val3)
      }
  }

  val2
}


#' @export
#' @rdname setup
#' @param gitignore Logical. Only has an effect if the `paths$projectPath`
#'   is a git repositories without submodules. This case is ambiguous what a user
#'   wants. If `TRUE`, the default, then `paths$modulePath` will be added to
#'   the `.gitignore` file. Can be controled with `options(SpadES.project.gitignore = ...)`.
#' @details
#' `setupGitIgnore` will add.
#'
#' @return
#' `setupGitIgnore` is run for its side effects, i.e., adding either `paths$packagePath`
#' and/or `paths$modulePath` to the
#' `.gitignore` file. It will check whether `packagePath` is located inside the
#' `paths$projectPath` and will add this folder to the `.gitignore` if `TRUE`.
#' If the project is a git repository with git submodules, then it will add nothing else.
#' If the project is a git repository without git submodules, then the `paths$modulePath`
#' will be added to the `.gitignore` file. It is assumed that these modules are
#' used in a `read only` manner.
setupGitIgnore <- function(paths, gitignore = getOption("SpaDES.project.gitignore", TRUE),
                           verbose) {

  if (isTRUE(gitignore)) {
    gitIgnoreFile <- ".gitignore"
    gitFile <- file.path(paths[["projectPath"]], ".git")
    if (dir.exists(gitFile)) { # this is a git repository
      if (file.exists(gitIgnoreFile))
        gif <- readLines(gitIgnoreFile, warn = FALSE)
      else
        gif <- character()
      gifOrig <- gif

      prjP <- normPath(paths[["projectPath"]])
      pkgP <- normPath(paths[["packagePath"]])
      updatedPP <- updatedMP <- FALSE

      # if the R package folder is inside
      isPackagePathInside <- grepl(prjP, pkgP)
      if (isTRUE(isPackagePathInside)) {
        updatedPP <- TRUE
        pkgP <- gsub(prjP, "", pkgP)
        if (startsWith(pkgP, "/"))
          pkgP <- gsub("^/", "", pkgP)
        lineWithPkgPath <- grep(paste0("^", pkgP,"$"), gif)
        insertLine <- if (length(lineWithPkgPath)) lineWithPkgPath[1] else length(gif) + 1
        gif[insertLine] <- file.path(pkgP, "*")
      }

      if (!file.exists(".gitmodules")) { # This is NOT using submodules; so, "it is a git repo, used git
        updatedMP <- TRUE
        lineWithModPath <- grep(paste0("^", basename(paths[["modulePath"]]),"$"), gif)
        insertLine <- if (length(lineWithModPath)) lineWithModPath[1] else length(gif) + 1
        gif[insertLine] <- file.path(basename(paths[["modulePath"]]), "*")

      }

      # ignore these folders/files
      igs <- c("cachePath", "inputPath", "outputPath", ".Rproj.user", ".Rhistory",
               ".Rdata", ".RData", ".secret", ".secrets")
      # rproj <- paste0(basename(prjP), ".Rproj")
      # igs <- c(igs, rproj)

      for (ig in igs) {
        igRel <- if (!is.null(paths[[ig]])) {
          fs::path_rel(paths[[ig]], prjP)
        } else {
          ig
        }

        if (!isAbsolutePath(igRel)) { # if igRel is inside projectPath, then add to .gitignore
          if (!any(grepl(igRel, gif))) { # only add if not already there
            insertLine <- length(gif)
            gif[insertLine + 1] <- igRel
          }
        }
      }

      # Write the file
      if (length(setdiff(gif, gifOrig))) {
        writeLines(con = gitIgnoreFile, unique(gif))
        mess <- paste(c("packagePath"[updatedPP], "modulePath"[updatedMP]), collapse = " and ")
        messageVerbose(verboseLevel = 1, verbose = verbose,
                       ".gitignore file updated with ", mess,"; ",
                       "this may need to be confirmed manually")
      }
    }
  }
}

setPaths <- function(cachePath, inputPath, modulePath, outputPath, rasterPath, scratchPath,
                     terraPath, silent = FALSE, verbose = getOption("Require.verbose", 1L)) {
  defaults <- list(
    CP = FALSE,
    IP = FALSE,
    MP = FALSE,
    OP = FALSE,
    RP = FALSE,
    SP = FALSE,
    TP = FALSE
  )
  if (missing(cachePath)) {
    cachePath <- .getOption("reproducible.cachePath") # nolint
    defaults$CP <- TRUE
  }
  if (missing(inputPath)) {
    inputPath <- getOption("spades.inputPath") # nolint
    defaults$IP <- TRUE
  }
  if (missing(modulePath)) {
    modulePath <- getOption("spades.modulePath") # nolint
    defaults$MP <- TRUE
  }
  if (missing(outputPath)) {
    outputPath <- getOption("spades.outputPath") # nolint
    defaults$OP <- TRUE
  }
  if (missing(rasterPath)) { ## TODO: deprecate
    rasterPath <- file.path(getOption("spades.scratchPath"), "raster") # nolint
    defaults$RP <- TRUE
  }
  if (missing(scratchPath)) {
    scratchPath <- getOption("spades.scratchPath") # nolint
    defaults$SP <- TRUE
  }
  if (missing(terraPath)) {
    terraPath <- file.path(getOption("spades.scratchPath"), "terra") # nolint
    defaults$TP <- TRUE
  }

  allDefault <- all(unlist(defaults))

  originalPaths <- .paths()
  newPaths <- lapply(list(
    cachePath = cachePath,
    inputPath = inputPath,
    modulePath = modulePath,
    outputPath = outputPath,
    rasterPath = rasterPath,
    scratchPath = scratchPath,
    terraPath = terraPath
  ), checkPath, create = TRUE)

  ## set the new paths via options
  base::options(
    rasterTmpDir = newPaths$rasterPath,
    reproducible.cachePath = cachePath,
    spades.inputPath = inputPath,
    spades.modulePath = unlist(modulePath),
    spades.outputPath = outputPath,
    spades.scratchPath = scratchPath
  )

  # if (requireNamespace("terra", quietly = TRUE)) {
  #   terra::terraOptions(tempdir = terraPath)
  # }

  ## message the user
  modPaths <- if (length(modulePath) > 1) {
    paste0("c('", paste(normPath(modulePath), collapse = "', '"), "')")
  } else {
    normPath(modulePath)
  }

  if (!silent) {
    if (!allDefault) {
      messageVerbose(
        "Setting:\n",
        "  options(\n",
        if (!defaults$CP) paste0("    reproducible.cachePath = '", normPath(cachePath), "'\n"),
        if (!defaults$IP) paste0("    spades.inputPath = '", normPath(inputPath), "'\n"),
        if (!defaults$OP) paste0("    spades.outputPath = '", normPath(outputPath), "'\n"),
        if (!defaults$MP) paste0("    spades.modulePath = '" , modPaths, "'\n"),
        if (!defaults$SP) paste0("    spades.scratchPath = '", normPath(scratchPath), "'\n"),
        "  )",
        verbose = verbose
      )
    }

    if (any(unlist(defaults))) {
      messageVerbose(
        "Paths set to:\n",
        "  options(\n",
        "    rasterTmpDir = '", normPath(rasterPath), "'\n",
        "    reproducible.cachePath = '", normPath(cachePath), "'\n",
        "    spades.inputPath = '", normPath(inputPath), "'\n",
        "    spades.outputPath = '", normPath(outputPath), "'\n",
        "    spades.modulePath = '", modPaths, "'\n", # normPath'ed above
        "    spades.scratchPath = '", normPath(scratchPath), "'\n",
        "  )\n",
        "  terra::terraOptions(tempdir = '", normPath(terraPath), "'",
        verbose = verbose
      )
    }
  }

  return(invisible(originalPaths))
}

.paths <- function(verbose = getOption("Require.verbose", 1L)) {
  if (!is.null(.getOption("spades.cachePath"))) {
    messageVerbose("option('spades.cachePath') is being deprecated. Please use ",
                   "option('reproducible.cachePath').\n",
                   "Setting option('reproducible.cachePath' = getOption('spades.cachePath'))",
                   verbose = verbose)
  }

  list(
    cachePath = .getOption("reproducible.cachePath"), # nolint
    inputPath = getOption("spades.inputPath"), # nolint
    modulePath = getOption("spades.modulePath"), # nolint
    outputPath = getOption("spades.outputPath"), # nolint
    rasterPath = file.path(getOption("spades.scratchPath"), "raster"), # nolint
    scratchPath = getOption("spades.scratchPath"), # nolint
    terraPath = file.path(getOption("spades.scratchPath"), "terra") # nolint
  )
}

.getOption <- function(x, default = NULL) {
  optionDefault <- base::options(x)[[1]]
  if (is.null(optionDefault)) optionDefault <- default
  if (is.function(optionDefault)) {
    optionDefault()
  } else {
    optionDefault
  }
}

# These are the setPaths paths that are available
spPaths <- c("cachePath", "inputPath", "modulePath", "outputPath", "rasterPath",
             "scratchPath", "terraPath")

# options(repos = c(CRAN = "http://cloud.r-project.org"))
# # 3 packages (could be just 2) are needed to manage the setup; these are installed in default .libPaths()
# if (!require("remotes", quietly = TRUE)) {
#   install.packages("remotes");
# }
# remotes::install_github("PredictiveEcology/Require@archivedPkg", upgrade = FALSE)
# Require::Require("PredictiveEcology/SpaDES.project@development", upgrade = FALSE)
#
# # Setup the paths, modules, git links if any
# projPath <- "~/GitHub"
# modulePath <- "m"
# packagePath <- "Rpackages"
# setupProject(name = "PredictiveEcology/WBI_forecasts@ChubatyPubNum12",
#              projectPath = projPath,
#              modulePath = modulePath,
#              packagePath = packagePath,
#              modules = c("CeresBarros/Biomass_speciesData@master",
#                          "CeresBarros/Biomass_borealDataPrep@71-ssl-certificate-of-nfi-ftp-server-use",
#                          "CeresBarros/Biomass_core@master",
#                          "CeresBarros/Biomass_validationKNN@master",
#                          "CeresBarros/Biomass_speciesParameters@temp"),
#              packages = c("PredictiveEcology/SpaDES.experiment@development"))
# )

# PseudoCode for above setupProject
# 1. Check whether project exists locally
#     if FALSE A) then setwd to path, git clone, git submodule init, setwd into project
#     if TRUE A) check that it is an SpaDES project
#             B) setwd into project or do nothing if already there
#             C) confirm Git status
# 2. Check whether the Rstudio project is this project --
#     if FALSE A) open the project programmatically from Rstudio
# 3. setProjPkgDir(packagePath) # not necessary if this in inside `newProject`
# 4. setLinuxBinaryRepo()
# 5. SpaDES.project::packagesInModules
# 6. Require::Require(c(unname(unlist(outs)), packages), require = FALSE, standAlone = TRUE)


messageWarnStop <- function(..., type = getOption("SpaDES.project.messageWarnStop", "message"),
                            verbose = getOption("Require.verbose", 1L)) {
  type %in% c("message", "warning", "stop")
  msg <- paste0(...)

  typeFun <- utils::getFromNamespace(type, "base")
  typeFunArgs <- list(
    msg,
    call. = FALSE
  )
  if (type == "message") {
    typeFun <- messageVerbose
    typeFunArgs[["call."]] <- NULL
    typeFunArgs[["verbose"]] <- verbose

  }
  do.call(typeFun, typeFunArgs)

}


dotsToHere <- function(dots, dotsSUB, defaultDots, envir = parent.frame()) {
  if (missing(dots))
    dots <- dotsSUB
  else
    dots <- append(dots, dotsSUB)
  haveDefaults <- !missing(defaultDots)
  if (haveDefaults) {
    newInEnv <- setdiff(names(defaultDots), ls(envir = envir))
    list2env(defaultDots[newInEnv], envir = envir)
    on.exit(rm(list = newInEnv, envir = envir))
  }
  localEnv <- new.env(parent = envir)

  dots <- Map(d = dots, nam = names(dots), # MoreArgs = list(defaultDots = defaultDots),
              function(d, nam) {
                d1 <- evalSUB(d, valObjName = nam, envir = localEnv, envir2 = localEnv)
                # d1 <- try(eval(d, envir = localEnv), silent = TRUE)
                if (is(d1, "try-error")) {
                  if (isTRUE(haveDefaults))
                    d1 <- defaultDots[[nam]]
                  else
                    d1 <- d
                }
                assign(nam, d1, envir = localEnv) # sequential
                d1
              })
  list2env(dots, envir = envir)
  dots
}


setupRequire <- function(allPkgs, packages, ...) {

  # Without installing
  requireNeeds <- unique(unname(unlist(Require::pkgDep(allPkgs, recursive = TRUE))))
  packagesNeeds <- unique(unname(unlist(Require::pkgDep(packages, recursive = TRUE))))
  requirePkgs <- unique(extractPkgName(requireNeeds))
  packagesPkgs <- extractPkgName(packagesNeeds)
  packagesPkgsMatched <- packagesNeeds[match(requirePkgs, packagesPkgs)]
  require <- sort(unique(c(requireNeeds, packagesPkgsMatched)))

  # alreadyLoadedMess <- c()
  withCallingHandlers(
    Require::Require(allPkgs, ...) # basically don't change anything
    , message = function(m) {
      if (any(grepl("Error: package or namespace", m$message))) {
        pkg <- gsub("^.+namespace \u2018(.+)\u2019 .+ is already loaded.+$", "\\1", m$message)
        message(m)
        stop(stopMessForRequireFail(pkg))
      }
    }
    , warning = function(w) {
      warnMess <- "^.+ersion .+ of \u2018(.+)\u2019 masked by .+$"
      if (any(grepl(warnMess, w$message))) {
        pkg <- gsub(warnMess, "\\1", w$message)
        warning(w)
        stop(stopMessForRequireFail(pkg))
      }
    }
  )
}

stopMessForRequireFail <- function(pkg) {
  paste0("\nThe above error(s) likely mean(s) you must restart R and run again.",
         "\nIf this/these occur(s) again, your session likely ",
         "pre-loads old packages from e.g., your personal library. ",
         "The best thing to do is try to\n",
         yellow("restart R without loading any packages."),
         "\n\nIf that is not easy to do, you can try to update it in that location with (for a CRAN package) e.g., :\n",
         yellow("restart R "),
         blue(paste0("\ninstall.packages(c('", pkg, "'))")),
         yellow("\nrestart R"),
         "\n\nIf that does not work (including non-CRAN packages), perhaps removing the old one...",
         yellow("\nrestart R "),
         blue(paste0("\nremove.packages(c('", pkg, "'))")),
         yellow("\nrestart R"),
         "\nThis should trigger a re-installation, or allow ",
         "for a manual install.packages ...")
}

#' @rdname setup
#' @export
#' @inheritParams setupProject
#' @importFrom rstudioapi getActiveProject getSourceEditorContext
#'
#' @details
#' `setupStudyArea` only uses `inputPath` within its `paths` argument, which will
#' be passed to `path` argument of `gadm`. User can pass any named list element
#' that matches the columns in the `sf` object, including e.g., `NAME_1` and, if `level = 2`,
#' is specified, then `NAME_2`.
#'
#' ```
#' setupStudyArea(list(NAME_1 = "Alberta", "NAME_2" = "Division No. 17", level = 2))
#' ```
#'
#' @return
#' `setupStudyArea` will return an `sf` class object coming from `geodata::gadm`,
#' with subregion specification as described in the `studyArea` argument.fsu
setupStudyArea <- function(studyArea, paths, envir, verbose = getOption("Require.verbose", 1L)) {

  if (missing(paths))
    paths <- list(inputPaths = ".")
  studyArea <- evalSUB(studyArea, valObjName = "studyArea", envir = parent.frame(), envir2 = envir)

  if (is(studyArea, "list")) {
    theCall <- quote(getStudyArea(studyArea, paths, verbose = verbose))
    if (requireNamespace("reproducible", quietly = TRUE))
      # Cache doesn't evaluate the `theCall` inside eval, so need .cacheExtra to identify the actual contents
      studyArea <- reproducible::Cache(eval(theCall), .cacheExtra = list(studyArea, getStudyArea),
                                       omitArgs = c("enclos", "envir"), verbose = verbose,
                                       cachePath = paths$cachePath, .functionName = "getStudyArea")
    else
      studyArea <- eval(theCall)
  }
  studyArea
}


setupRestart <- function(updateRprofile, paths, name, inProject,
                         Restart = getOption("SpaDES.project.Restart", FALSE),
                         useGit = getOption("SpaDES.project.useGit", FALSE),
                         origGetWd, verbose = getOption("Require.verbose")) {

  if (isTRUE(updateRprofile)) {

    inTmpProject <- inTempProject(paths)
    if (isTRUE(inTmpProject)) {
      warning("updateRprofile is TRUE, but the projectPath is the tempdir(), which means ",
              "the .Rprofile won't be read upon restart. ",
              "Change the paths$projectPath to a non-temporary path")
    } else {
      if (isRstudio()) {
        inCorrectRstudioProj <- isInRstudioProj(name)
        if (!inProject || !inCorrectRstudioProj) { # either wrong Rstudio or not in project
          if (isFALSE(Restart)) {
            warning("updateRprofile is TRUE, but the projectPath is not an Rstudio project; ",
                    "thus the .Rprofile won't be read upon restart; ignoring updateRprofile = TRUE. ",
                    "Set `Restart = TRUE` to updateRprofile *and* restart R in that folder so ",
                    ".Rprofile will be read")
          }
        }
      }
    }
  }

  if ( (interactive() && (isTRUE(Restart) || is.character(Restart)) ) && isRstudio()
       || !(useGit %in% FALSE)) {# getOption("SpaDES.project.Restart", TRUE))

    on.exit(setwd(origGetWd), add = TRUE)

    if (!(useGit %in% FALSE) && isRstudio() && !inProject) {
      messageVerbose("Because useGit is TRUE or a character string, changing Restart to TRUE", verbose = verbose)
      Restart = TRUE
    }
    pp <- path.expand(paths[["projectPath"]])
    isRstudioProj <- rprojroot::is_rstudio_project$testfun[[1]](pp)
    curRstudioProj <- rstudioapi::getActiveProject()
    isRstudioProj <- isRstudioProj && isTRUE(basename2(curRstudioProj) %in% basename(pp))
    # inProject <- isInProject(name)

    if (!inProject || !isRstudioProj) {
      if (requireNamespace("rstudioapi", lib.loc = paths[["packagePath"]])) {
        messageVerbose("... restarting Rstudio inside the project",
                       verbose = verbose)
        wasUnsaved <- FALSE
        wasLastActive <- FALSE
        activeFile <- rstudioapi::getSourceEditorContext()$path
        if (!is.character(Restart)) {
          rstudioUnsavedFile <- "~/.active-rstudio-document"

          if (!nzchar(activeFile))
            activeFile <- rstudioUnsavedFile
          fe <- file.exists(activeFile)
          wasUnsaved <- identical(activeFile, rstudioUnsavedFile)
          if (isFALSE(fe) || wasUnsaved) {
            if (isTRUE(fe)) {
              newRestart <- file.path(paths[["projectPath"]], "global.R")
              Restart <- activeFile # is absolute
              basenameRestartFile <- basename(newRestart)

              message("Renaming the active, unsaved file: global.R in the new projectPath root")
              wasLastActive <- TRUE
            } else {
              message("There was no 'active-source' file or named Restart file.")
              message("User has requested to restart in a new Rproject; please ")
              message("specify path to the 'global' script (the one that has this setupProject call).")
              message("Please provide name here of that file (e.g., global.R )")
              message("(If it isn't saved, please save it now):")
              Restart <- readline("")
              if (!file.exists(Restart))
                stop("That file does not exist. Please rerun, specifying the global file. This",
                     " will be copied to the new project folder.")
              basenameRestartFile <- basename(Restart)
            }
          } else {
            Restart <- activeFile
            basenameRestartFile <- basename(Restart)
            wasLastActive <- TRUE
          }
        } else {
          basenameRestartFile <- basename(Restart)
        }

        if (!fs::is_absolute_path(Restart))
          Restart <- file.path(origGetWd, Restart)
        newRestart <-  file.path(paths[["projectPath"]], basenameRestartFile)

        # Switch to file to save it
        id <- rstudioapi::navigateToFile(activeFile)
        rstudioapi::documentSave(id)
        # id2 <- rstudioapi::navigateToFile(activeFile)

        copied <- file.copy(Restart, newRestart, overwrite = FALSE)

        # Copy .Rhistory if it exists
        RHistBase <- ".Rhistory"
        RHist <- file.path(origGetWd, RHistBase)
        if (isTRUE(file.exists(RHist)))
          file.copy(RHist, file.path(pp, RHistBase))
        if (all(copied))
          message(green("copied ", Restart, " to ", newRestart))
        else
          message(blue("Did not copy ", Restart, " to ", newRestart, "; it already exists."))
        RprofileInOther <- file.path(paths[["projectPath"]], ".Rprofile")
        RestartTmpFileStart <- ".Restart_"
        tempfileInOther <- file.path(paths[["projectPath"]], paste0(RestartTmpFileStart, basename(tempfile())))
        addToTempFile <- c("setHook('rstudio.sessionInit', function(newSession) {",
                           "if (newSession) {",
                           "# message('Welcome to RStudio ', rstudioapi::getVersion())",
                           "}",
                           "ap <- rstudioapi::getActiveProject()",
                           "if (is.null(ap)) ap <- 'No active project'",
                           "message('This is now an RStudio project and SpaDES.project projectPath: ', ap)",
                           paste0("message('attempting to re-open ", "last active"[wasLastActive],
                                  " file " , paste0("(named ", basenameRestartFile, ") ")[!wasUnsaved],
                                  "(and saved it as global.R as it was unsaved) "[wasUnsaved], "')"),
                           paste0("try(file.edit('", newRestart, "'), silent = TRUE)"), # next line doesn't always work
                           paste0("rstudioapi::navigateToFile('", newRestart, "')")

        )

        newRprofile <- paste0("source('", tempfileInOther, "')")
        if (file.exists(RprofileInOther)) {
          rl <- readLines(RprofileInOther)
          newRprofile <- c(rl, newRprofile)
          lineNext <- paste0("readLns <- readLines('", RprofileInOther, "')")
          lineToDel <- paste0("lineToDel <- grep('^", RestartTmpFileStart,"', readLns)") #paste(rl, collapse = "", ")")
          nextLine <- paste0("readLns <- readLns[-lineToDel]") # remove source line
          nextLine2 <- paste0("cat(readLns, file = '", RprofileInOther, "', sep = '\n')")
          addToTempFile <- c(addToTempFile, lineNext, lineToDel, nextLine, nextLine2)

        } else {
          addToTempFile <- c(addToTempFile, paste0("unlink('", RprofileInOther, "') # delete this .Rprofile that was created"))

        }
        addToTempFile <- c(addToTempFile, paste0("unlink('", tempfileInOther, "') # delete this file"))

        addToTempFile <- c(addToTempFile,
                           "}, action = 'append'",
                           ")")
        cat(addToTempFile, file = tempfileInOther, sep = "\n")
        cat(newRprofile, file = RprofileInOther, sep = "\n")
        cloned <- FALSE

        if (((!useGit %in% FALSE) && requireNamespace("usethis") && requireNamespace("gh") &&
             requireNamespace("gitcreds")) && cloned %in% FALSE ) {

          basenameName <- basename(name)
          needGitUserName <- TRUE
          if (is.character(useGit)) {
            if (useGit != "sub") {
              needGitUserName <- FALSE
              gitUserName <- useGit
            }
          }

          if (needGitUserName) {
            mess <- capture.output(
              type = "message",
              gitUserNamePoss <- gh::gh_whoami()$login)
            if (is.null(gitUserNamePoss)) {
              stop(paste(c(mess, "or try gitcreds::gitcreds_set()"), collapse = "\n"))
            }
            messageVerbose(msgNeedGitUserName(gitUserNamePoss), verbose = interactive() * 10)
            gitUserName <- if (interactive()) readline() else gitUserNamePoss
            if (!nzchar(gitUserName)) {
              gitUserName <- gitUserNamePoss
              needGitUserName <- FALSE
            }

          }


          if (!rprojroot::is_rstudio_project$testfun[[1]](pp)) {
            host <- "https://github.com"
            tf <- tempfile2();
            out <- .downloadFileMasterMainAuth(file.path("https://api.github.com/repos",gitUserName, basenameName),
                                        destfile = tf, verbose = verbose - 10)
            # The suppressWarnings is for "incomplete final line"
            checkExists <- if (file.exists(tf)) suppressWarnings(readLines(tf)) else "Not Found"

            if (any(grepl("Not Found", checkExists))) {
              usethis::create_project(pp, open = FALSE, rstudio = isRstudio())
            } else {
              repo <- file.path(host, gitUserName, basenameName)
              messageVerbose(paste0("The github repository already exists: ", repo),
                             "\nWould you like to clone it now to ", getwd(), "\nType (y)es or any other key for no: ")
              out <- if (interactive()) readline() else "yes"
              if (grepl("y|yes", tolower(out))) {
                setwd(dirname(getwd()))
                unlink(basenameName, recursive = TRUE)
                gert::git_clone(repo, path = basenameName)
                cloned <- TRUE
                setwd(paths[["projectPath"]])
              } else {
                stop("Can't proceed: either delete existing github repo, change the ",
                     "project name, or change the Github account and try again")
              }
            }

          }

          if (!rprojroot::is_git_root$testfun[[1]](pp)) {
            if (needGitUserName) {
              messageVerbose(msgNeedGitUserName(gitUserNamePoss), verbose = interactive() * 10)
              gitUserName <- readline()
            }
            if (!nzchar(gitUserName))
              gitUserName <- NULL
            bbb <- try(usethis::use_git())
            if (!(exists("gitUserNamePoss", inherits = FALSE)))
              gitUserNamePoss <- gh::gh_whoami()$login
            if (identical(gitUserName, gitUserNamePoss))
              gitUserName <- NULL

            githubRepoExists <- usethis::use_github(gitUserName) # This will fail if not an organization
          }
        }
        on.exit(rstudioapi::openProject(path = paths[["projectPath"]], newSession = TRUE))
        message("Starting a new Rstudio session with projectPath (", green(paths[["projectPath"]]), ") as its root")
        stop_quietly()
      } else {
        stop("Please open this in a new Rstudio project at ", paths[["projectPath"]])
      }
    }
  } else {
    if (!Restart %in% FALSE) {
      if (isRstudio())
        messageVerbose("Restart is not FALSE, but this session is not Rstudio, ignoring...",
                       verbose = verbose)
    }
  }
}

setupSpaDES.ProjectDeps <- function(paths,
                                    deps = c("SpaDES.project", "data.table", "Require", "rprojroot", "rstudioapi", "fs"),
                                    verbose = getOption("Require.verbose")) {

  libs <- c(.libPaths()[1], paths[["packagePath"]])
  nsPaths <- vapply(deps, FUN.VALUE = character(1), function(pkg) dirname(getNamespaceInfo(pkg, "path")))
  isLoadedLocally <- !names(nsPaths) %in% libs
  nsPaths <- nsPaths[!names(nsPaths) %in% .basePkgs]

  depsAlreadyInstalled <- Map(lib = nsPaths, pkg = names(nsPaths), function(lib, pkg) {
    pths <- file.path(lib, pkg)
    exist <- file.exists(pths)
    ver <- character(length(pths))
    if (any(exist))
      ver[exist] <- DESCRIPTIONFileVersionV(file.path(pths[exist], "DESCRIPTION"))
    if (any(!exist))
      ver[!exist] <- NA
    names(ver) <- pkg
    ver
  })

  inPkgPath <- dir(paths[["packagePath"]], full.names = TRUE)
  needCopy <- which(!names(depsAlreadyInstalled) %in% basename(inPkgPath))

  if (length(needCopy)) {
    pkgs <- names(depsAlreadyInstalled)[needCopy]
    messageVerbose("Copying ", paste(pkgs, collapse = ", "), " packages to ",
                   paths[["packagePath"]], "", verbose = verbose)

    Map(pkg = pkgs, from = nsPaths[needCopy], function(pkg, from) {
      isInstalled <- file.exists(file.path(from, pkg, "INDEX"))
      if (isInstalled)
        copyPackages(pkg, from = from, to = paths[["packagePath"]], verbose = 0)
    })
    messageVerbose("If you have difficulties, please restart R", verbose = verbose)
  }

  return(invisible())

  # Need to deal with NA, also version differences
  # diffVersion <- Require::setdiffNamed(depsAlreadyInstalled[[1]], depsAlreadyInstalled[[2]])
  # diffs <- names(diffVersion)
  #
  # theNAs <- is.na(depsAlreadyInstalled[[2]])
  # toInstall <- character()
  # toRevInstall <- character()
  # if (any(theNAs)) {
  #   toInstall <- names(theNAs[theNAs])
  # }
  #
  # notNAs <-
  #   Map(dps = depsAlreadyInstalled, function(dps) {
  #     ret <- as.list(as.numeric_version(dps[!theNAs]))
  #   })
  #
  # notNAs <- Require::invertList(notNAs)
  # needUpdate <- unlist(Map(pkg = notNAs, function(pkg) pkg[[1]] > pkg[[2]]), recursive = FALSE)
  # if (any(needUpdate))
  #   toInstall <- unique(c(toInstall, names(needUpdate[needUpdate])))
  #
  # toInstall <- setdiff(toInstall, .basePkgs)
  #
  # needRevUpdate <- unlist(Map(pkg = notNAs, function(pkg) pkg[[1]] < pkg[[2]]), recursive = FALSE)
  # if (any(needRevUpdate))
  #   toRevInstall <- unique(c(toRevInstall, names(needRevUpdate[needRevUpdate])))
  #
  # toRevInstall <- setdiff(toRevInstall, .basePkgs)
  #
  #
  # if (FALSE) { # This is the older, slower way
  #   depsAlreadyInstalled <- dir(paths[["packagePath"]], pattern = paste0(paste0("^", deps, "$"), collapse = "|"))
  #   diffVersion <- Map(dai = depsAlreadyInstalled, function(dai) {
  #     if (file.exists(file.path(paths[["packagePath"]], dai, "DESCRIPTION"))) {
  #       pvLoaded <- packageVersion(dai)
  #       pvLibLoc <- packageVersion(dai, lib.loc = .libPaths()[1])
  #       pvPathsPackagePath <- packageVersion(dai, lib.loc = paths[["packagePath"]])
  #       loadedFrom <- if (identical(pvPathsPackagePath, pvLoaded)) {
  #         "packagePath"
  #       } else {
  #         "libPaths"
  #       }
  #
  #       if (pvLibLoc < pvPathsPackagePath) {# test whether lib loc is lt; so, need to unload; then move from to packagePath; reload
  #         out <- dai
  #       } else if (pvLibLoc > pvPathsPackagePath) { #test whether lib loc is gt; so, need to move to packagePath
  #         out <- list(Package = dai, pvLibLoc, pvPathsPackagePath)
  #       } else {
  #         out <- NULL
  #       }
  #     } else {
  #       out <- NULL
  #     }
  #     out
  #   })
  #   # First check for loaded old version; needs user intervention
  #   needStop <- vapply(diffVersion, function(x) is.character(x), FUN.VALUE = logical(1))
  #   if (any(needStop)) {
  #     pkgsToUpdate <- names(needStop)[needStop]
  #     pkgs <- paste(pkgsToUpdate, collapse = "', '")
  #
  #     stop("\nThe version of ", pkgs, " need updating in the personal library.\n",
  #          "Please restart R and update ", pkgs, ", e.g., using: ",
  #          "\ninstall.packages(c('", pkgs, "'), lib = '", .libPaths()[1],"')")
  #   }
  #   diffVersionNames <- names(diffVersion[!vapply(diffVersion, is.null, FUN.VALUE = logical(1))])
  #   deps <- setdiff(deps, depsAlreadyInstalled)
  #   if (length(diffVersionNames)) {
  #     messageVerbose("Updating ", paste0(diffVersionNames, collapse = ", "), " in paths$packagePath ",
  #                    "because it has been updated in .libPaths()[1]. To turn this updating off, set\n",
  #                    "options(SpaDES.project.updateSelf = FALSE)")
  #     if (!isFALSE(getOption("SpaDES.project.updateSelf")))
  #       deps <- c(deps, diffVersionNames)
  #   }
  # }
  #
  # if (length(toInstall)) {
  #   copyPackages(toInstall, from = .libPaths()[1], to = paths[["packagePath"]], verbose = verbose)
  # }
  # if (length(toRevInstall)) {
  #   copyPackages(toRevInstall, to = .libPaths()[1], from = paths[["packagePath"]], verbose = verbose)
  #   messageVerbose("If you have difficulties, please restart R")
  # }

}

checkNameProjectPathConflict <- function(name, paths) {
  if (!missing(paths)) {
    if (!is.null(paths[["projectPath"]])) {
      paths[["projectPath"]] <- normPath(paths[["projectPath"]])
      prjNmBase <- basename2(paths[["projectPath"]])
      if (!identical(basename(name), prjNmBase) && !is.null(prjNmBase)) {
        warning("both projectPath and name are supplied, but they are not the same; ",
                "these must be the same basename; using projectPath: ", paths[["projectPath"]])
        name <- prjNmBase
      }
    }
  }
  name
}

basename2 <- function (x) {
  if (is.null(x)) {
    NULL
  }
  else {
    basename(x)
  }
}

dotsToHereOuter <- function(dots, dotsSUB, defaultDots, envir = parent.frame()) {
  dotsSUB <- dotsToHere(dots, dotsSUB, defaultDots, envir = envir)
  dotsSUBreworked <- list()
  for (i in seq(dotsSUB)) {
    val <- try(eval(dotsSUB[[i]], envir = envir))
    if (is(val, "try-error"))
      val <- dotsSUB[[i]]
    newNam <- names(dotsSUB)[i]
    dotsSUBreworked[[newNam]] <- val
    assign(newNam, val, envir = envir)
  }
  if (exists("dotsSUBreworked"))
    dotsSUB <- dotsSUBreworked
  dotsSUB
}

stripDuplicateFolder <- function(relativeFilePath, path) {
  relativeFilePath <- fs::path_norm(relativeFilePath) # don't want absolute, just consisten /
  path <- fs::path_norm(path) # don't want absolute, just consisten /
  if (isTRUE(startsWith(relativeFilePath, path) %in% TRUE)) {
    path <- dirname(path)
    #comm <- fs::path_common(c(relativeFilePath, path))
    #relativeFilePath <- fs::path_rel(relativeFilePath, comm)
  }
  fs::path_norm(file.path(path, relativeFilePath))
}


parseExpressionSequentially <- function(pp, envs, namedList, verbose) {
  isFun <- FALSE
  envs2 <- lapply(pp, function(p) {
    env <- new.env(parent = tail(envs, 1)[[1]])
    if (isUnevaluatedList(p) || isFALSE(namedList) || is(p, "<-")) {
      # robust to code failures
      # Try whole list first; if fails, then do individual list elements
      withCallingHandlers(for (i in 1:2) {
        safe <- TRUE
        out <- evalListElems(p, envir = env, verbose = verbose)
        if (safe)
          break
      }, message = function(m) {

        missingPkgs <- grepl("there is no package called", m)
        if (any(missingPkgs)) {
          pkgs <- gsub("^.+called \u2018(.+)\u2019.*$", "\\1", m$message)
          pkgs <- gsub("^.+called '(.+)'.*$", "\\1", pkgs)
          messageVerbose(pkgs, " is missing; attempting to install it. ",
                         "\nIf this fails, please add it manually to the `packages` argument",
                         verbose = verbose)
          Require::getCRANrepos(ind = 1)
          repos <- getOption("repos")
          repos <- repos[!duplicated(repos)]
          Require::Install(pkgs, repos = repos)
          safe <<- FALSE
          invokeRestart("muffleMessage")
        }
      })
      isFun <<- isFun || is.function(out) # in case there are some functions, some not; keep status as isFun = TRUE
      if (length(ls(env)) == 0) # the previous line will evaluated assignments e.g., mode <- "development",
        # putting the object `mode` into the env; but if there is no assignment
        # then we need to put the object into the environment
        env$opt <- out
      envs <<- append(envs, list(env))

    }
    env
  })
  if (isTRUE(isFun)) {
    lapply(envs2[-1], function(env) {
      list2env(mget(ls(envir = env), envir = env), envir = envs2[[1]]
      )})
    envs2 <- list(envs2[[1]])
  }
  envs2
}


parseEnvsWithNamedListsSequentially <- function(envs2) {
  os <- list()

  isNamedList <- mapply(env = envs2, function(env) {
    isNamedList <- FALSE
    if (length(ls(env))) {
      obj <- get(ls(env), env)
      if (is(obj, "list"))
        if (!is.null(names(obj)))
          isNamedList <- TRUE
    }
    isNamedList
  }, SIMPLIFY = TRUE)

  if (isTRUE(any(isNamedList))) {
    ll <- Map(env = envs2[isNamedList], function(env) {
      as.list(env)[[1]]
    })
    os <- Reduce(modifyList, ll)
  }
}

mergeOpts <- function(opts, optsFirst, verbose = getOption("Require.verbose", 1L)) {
  b <- opts$updates
  a <- optsFirst$updates
  if (!is.null(b))
    a <- b[a, on = "optionName"]
  if (!is.null(a)) {
    if (NROW(a)) {
      messageVerbose(yellow("  options changed:"), verbose = verbose, verboseLevel = 0)
      if (!is.null(b)) {
        a[unlist(lapply(newValue, is.null)), newValue := i.newValue]
        a[, oldValue := i.oldValue]
        a[, `:=`(i.newValue = NULL, i.oldValue = NULL)]
      }
      messageDF(a, verbose = verbose)
    }
  } else {
    messageVerbose(yellow("  no options changed"), verbose = verbose, verboseLevel = 0)
  }

  opts$updates <- a
  opts
}

isProjectGitRepo <- function(projectPath, inProject) {
  if (isTRUE(inProject))
    projectPath <- "."
  length(dir(pattern = "^\\.git$", path = projectPath, all.files = TRUE)) == 1L
}

ignoreAFolder <- function(gitIgnoreFile = ".gitIgnore", folder, projectPath) {
  if (!file.exists(gitIgnoreFile))
    file.create(gitIgnoreFile)
  gi <- readLines(gitIgnoreFile)
  cp <- a <- fs::path_rel(folder, projectPath)
  cachePathGrep <- paste0("^", cp, "(\\/)*", "$")
  if (!any(grepl(cachePathGrep, gi))) {
    cat(cp, file = ".gitIgnore", sep = "\n", append = TRUE)
  }
}

stop_quietly <- function(mess) {
  opt <- base::options(show.error.messages = FALSE)
  on.exit(base::options(opt))
  stop(mess)
}


copyPackages <- function(pkgs, from = .libPaths()[1], to, verbose) {
  messageVerbose("Copying ", paste(pkgs, collapse = ", "), " packages to ",
                 to, "", verbose = verbose)

  if (!identical(normPath(from), to) &&
      (!identical(dirname(normPath(from)), to)))
    for (pkg in pkgs) {
      pkgDir <- file.path(from, pkg)
      de <- dir.exists(pkgDir)
      pkgDir <- pkgDir[de][1]
      files1 <- dir(pkgDir, all.files = TRUE, recursive = TRUE)
      newFiles <- file.path(to, pkg, files1)
      lapply(unique(dirname(newFiles)), dir.create, recursive = TRUE, showWarnings = FALSE)
      oldFiles <- file.path(pkgDir, files1)
      linkOrCopy(oldFiles, newFiles)
    }
}



getStudyArea <- function(studyArea, paths, verbose = verbose) {
  needRep <- !requireNamespace("reproducible", quietly = TRUE)
  needGeo <- !requireNamespace("geodata", quietly = TRUE)
  if (needRep || needGeo) {
    needs <- c("reproducible"[needRep], "geodata"[needGeo])
    stop("Please install ", paste0(needs, collapse = " and "), verbose = verbose)
  }

  studyAreaOrig <- studyArea
  if (is.null(studyArea[["country"]]))
    studyArea[["country"]] <- "CAN"
  unnamed <- !nzchar(names(studyArea))
  if (any(unnamed))
    names(studyArea)[unnamed] <- "subregion"

  subregion <- studyArea[["subregion"]]

  studyArea <- append(studyArea, list(path = paths$inputPath))

  geodatCall <- as.call(append(list(geodata::gadm), studyArea))
  geodatCall <- match.call(geodata::gadm, geodatCall)
  geodatCall <- as.call(append(list(geodata::gadm), as.list(geodatCall)[names(geodatCall) %in% formalArgs(geodata::gadm)]))

  studyAreaNoPath <- studyArea[-which(names(studyArea) %in% "path")]
  epsg <- if (!is.null(studyArea$epsg)) paste0("epsg:", studyArea$epsg) else NULL

  studyArea[["epsg"]] <- NULL

  studyAreaOrig <- studyArea
  studyArea <- withCallingHandlers(
    do.call(geodata::gadm, as.list(geodatCall[-1])),
    message = function(m)
      if (grepl("geodata server seems|The geodata server is down for maintenance", m$message)) {
        message("Trying a Google Drive stashed copy")

      }
  )
  if (is.null(studyArea)) {
    if (isTRUE(studyAreaOrig[["country"]] %in% "CAN")) {
      "https://drive.google.com/file/d/1OhYZymGc9VlLg-X0ZlBalBNjDrzT9XtP/view?usp=sharing"
      message("Because the country is Canada, will try manually hosted file that represents level 2 ")
      studyArea <- reproducible::prepInputs(url = "https://drive.google.com/file/d/1DdtWeFYEhSRxXcAaJ_J6i8hP8YbfoC1q/view?usp=drive_link",
                                            destinationPath = paths$inputPath)

      # otherURL <- "https://www12.statcan.gc.ca/census-recensement/2021/geo/sip-pis/boundary-limites/files-fichiers/lpr_000a21a_e.zip"
      # message(otherURL)
      # a <- reproducible::prepInputs(url = otherURL)
      # colnames(a)[which(colnames(a) == "PRENAME")] <- "NAME_2"
    }
  }

  hasSubregion <- grep("subregion", names(studyAreaOrig))
  names(studyAreaOrig)[hasSubregion] <- "NAME_1"
  poss <- setdiff(names(studyAreaOrig), c("country", "level", "path"))
  tos <- c("projectTo", "maskTo", "cropTo", "to")
  poss <- setdiff(poss, tos)
  for (col in poss) {
    saCol <- as.data.frame(studyArea)[[col]]
    greppedNames <- grep(saCol, pattern = studyAreaOrig[[col]], value = TRUE)
    colInSA <- as.data.frame(studyArea)[[col]]
    if (is.null(colInSA)) {
      warning("There is no column ", col, "; ",
              "\nDid you mean one or more of:\n  ", paste(names(studyArea), collapse = "\n  "),
              "\nSkipping subsetting of studyArea by ", col
      )

    } else {
      keep <- which(tolower(colInSA) %in% tolower(greppedNames))
      if (length(keep) == 0) {
        warning(studyAreaOrig[[col]], " does not match any values in ", col,". ",
                "\nDid you mean one or more of:\n  ", paste(colInSA, collapse = "\n  "),
                "\nReturning empty studyArea")
      }
      studyArea <- studyArea[keep, ]
    }
  }

  if (any(names(studyAreaOrig) %in% tos)) {
    if (requireNamespace("reproducible")) {
      tos <- intersect(tos, names(studyAreaOrig))
      studyArea <- do.call(reproducible::postProcessTo, append(list(studyArea), studyAreaOrig[tos]))
    } else {
      message("Cannot project/mask/crop without `reproducible` package")
    }
  }
  # if (requireNamespace("terra")) {
    # studyArea <- studyArea |> terra::project("epsg:4269") # seemed to fail if not in this longlat
  # }
  if (!is.null(epsg))
    if (requireNamespace("terra")) {
      studyArea <- studyArea |> terra::project(epsg)
    } else {
      warning("Could not reproject; need ")
    }
  studyArea
}

checkUseGit <- function(useGit) {
  isTRUE(!(useGit %in% FALSE))
}


#' @importFrom utils browseURL
checkGitRemote <- function(name, paths) {
  # If this function is run, it means that local is not yet a git repo
  message("Please provide the github account for the repository (without quotes): ")
  gitUserName <- readline()
  if (!nzchar(gitUserName))
    stop("Need to supply the account name for the repository (not the repository name)")

  tf <- tempfile()
  urlCheckGit <- file.path("https://api.github.com/repos", gitUserName, name)#, destfile = tf)
  out <- capture.output(type = "message",
                        outSkip <- try(.downloadFileMasterMainAuth(urlCheckGit, destfile = tf)))
  od <- getwd()

  if (isTRUE(any(grepl("cannot open URL", out)) || identical(out, character(0)))) {
    message(paste0("It looks like the repository does not exist, please  go to github.com, create a new repository for: ",
                   gitUserName, " repo name: ", name, "; return here, press enter to continue"))
    browseURL(file.path("https://github.com", paste0(gitUserName, "?tab=repositories")))
    readline()

    checkPath(paths[["projectPath"]], create = TRUE)
    setwd(paths[["projectPath"]])
    on.exit(setwd(od))
    pp <- path.expand(paths[["projectPath"]])

    if (!requireNamespace("usethis")) stop("Please install usethis")

    bbb <- usethis::use_git()
    if (isTRUE(bbb)) {
      usethis::use_github(gitUserName)
      stop_quietly()
    }
    # usethis::use_github(gitUserName, private = FALSE)
    # system("git init -b main")
  } else {
    message("It looks like the remote Git repo exists (",file.path(gitUserName, name),
            "). Would you like to clone it now to ", paths[["projectPath"]], "?")
    cloneNow <- readline("Y or N (if N, this will stop): ")
    if (startsWith(tolower(cloneNow), "y")) {
      if (normalizePath(getwd()) == normalizePath(paths[["projectPath"]], mustWork = FALSE)) {
        stop("Cannot clone into projectPath because it already exists; please delete it; then rerun this.")
      }
      projectBase <- dirname(paths[["projectPath"]])
      dir.create(projectBase, showWarnings = FALSE, recursive = TRUE)
      setwd(projectBase)
      on.exit(setwd(od))
      cmd <- paste0("git clone git@github.com:", gitUserName, "/", name)
      system(cmd)
      setwd(name)
      if (length(dir(pattern = ".gitmodules", all.files = T))) {
        cmd <- paste0("git submodule init")
        a <- system(cmd, intern = TRUE)
        cmd <- paste0("git submodule update --recursive")
        b <- system(cmd, intern = TRUE)
      }
    } else {
      stop("Please clone the project manually, or choose another account and repository")
    }

  }
  return(gitUserName)
}


fastOptions <- function() {
  list(reproducible.useMemoise = TRUE,                 # For caching
       reproducible.inputPaths = getOption("reproducible.inputPaths"),
       reproducible.objSize = FALSE,
       Require.cloneFrom = Sys.getenv("R_LIBS_USER"),  # For package installs
       spades.recoveryMode = FALSE,
       spades.moduleCodeChecks = FALSE,
       spades.sessionInfo = FALSE,
       spades.testMemoryLeaks = FALSE,
       spades.useRequire = FALSE,
       spades.allowSequentialCaching = FALSE,
       reproducible.memoisePersist = TRUE,
       reproducible.cacheSaveFormat = "qs",
       LandR.assertions = FALSE,
       reproducible.cacheSpeed = "fast",
       reproducible.gdalwarp = TRUE,
       spades.recoveryMode = 1
  )
}

makeUpdateRprofileSticky <- function(updateRprofile) {
  if (isTRUE(updateRprofile) && !getOption("Require.updateRprofile") %in% TRUE)
    options(Require.updateRprofile = updateRprofile)
}


errorMsgCleaning <- function(mess, valOrig) {
  mess <- c(paste0(format(valOrig), "\n"), mess)
  mess <- gsub("Error in h.simpleError.msg, call.. : ", "", mess)
  mess <- gsub("Error in eval.{1,3}FUNcaptured.{1,3}, envir = callingEnv.{1,3} : ", "", mess)
  mess <- gsub("\n", "", mess)
  mess <- paste0(mess, "\n")
  mess
}


#' @author Ceres Barros
extractModName <- function(modules) {
  modNams <- Vectorize(.extractModName, USE.NAMES = FALSE)(modules)
  return(modNams)
}

#' @author Ceres Barros
.extractModName <- function(modules) {
  if (tools::file_ext(modules) != "") {
    stop("Expecting local or GitHub path to the module *folder* not .R file.")
  }

  modNam <- extractPkgName(modules)
  modNam2 <- sub("@[^/]*/?", "", basename(modules))   ## is there something after @Branch/?
  if (modNam != modNam2) {
    modNam <- modNam2
  }
  return(modNam)
}

#' SpaDES.project default .libPaths() directory
#'
#' For a given `name`, this will return the default library for packages.
#'
#' @param name A text string. When used in `setupProject`, this is the `projectName`
#' @export
#' @return
#' A path where the packages will be installed.
.libPathDefault <- function(name) {
  pkgPth <- tools::R_user_dir(package = basename(name), which = "data")
  normalizePath(
    file.path(pkgPth, "packages", version$platform, substr(getRversion(), 1, 3)),
    mustWork = FALSE, winslash = "/")
}



msgNeedGitUserName <- function(gitUserNamePoss) {
  paste0("Please provide the github account if an organization, or press enter ",
  "to use your personal account (",gitUserNamePoss , ") for the repository (without quotes): ")
}
PredictiveEcology/SpaDES.project documentation built on Dec. 23, 2024, 7:33 a.m.