"""
    deploy(
        source, [dir="."], [targets...=html];
        versioned=true,
        named=false,
        force=false,
        label=nothing,
    )

Build the `source` using the given `targets` list in the `dir` directory.
`source` can be either a `Module` or a `String` representing a `Project.toml`
path.

Keyword arguments can be used to control resulting directory structure.

{#keywords}
  - `versioned` and `named`.

    These keywords will place the built files in either a versioned subdirectory,
    or a named subdirectory of `dir`, or both (with name superceding version).

    The values for `name` and `version` are taken from those provided in the
    project's `Project.toml` file. If these values are not specified then the
    "deployment" will fail.

  - `force` will remove the calculated build path prior to building
    if it already exists.

  - `label` specifies a temporal folder name to copy the finished build to.
    This can be used to build a "tracking" version of documentation such as a
    "dev" or "stable" that changes over time while still retaining the exact
    versioned builds.

# Examples

In the following examples our project will be the `Publish` package. This can
be switched out for any other project source, such as a Julia package or a
simple `Project.toml` file.

```julia
deploy(Publish, "build")
```

writes the output to the `"build"` subdirectory of the current directory.
There will be a `build/<version>` folder containing HTML content.

```julia
deploy(Publish, "build", pdf)
```

does the same as above, but build the [`pdf`](#) output instead.

```julia
deploy(Publish, "all-docs", pdf, html)
```

or build everything at once.

The keyword arguments control other aspects of the build, as shown
[above](#keywords). For example,

```julia
deploy(Publish, "ecosystem"; named=true)
```

would build `Publish` documentation to an `ecosystem/Publish/<version>`
subdirectory.
"""
function deploy(
    source,
    dir::AbstractString=".",
    targets...=html;
    versioned::Bool=true,
    named::Bool=false,
    force::Bool=false,
    label::AbstractString="",
    root::AbstractString="/",
    kws...
)
    startswith(root, '/') || error("'root' keyword must be an absolute path.")
    p = Project(source; kws...)
    name = named ? p.env["name"] : ""
    version = versioned ? p.env["version"] : ""
    parts = filter(!isempty, [dir, name, version])
    path = joinpath(parts...)
    force && rm(path; recursive=true, force=true)
    if isdir(path)
        @warn "'$path' already exists. Use force to overwrite it."
    else
        for target in targets
            target(p, path)
        end
    end
    if !isempty(label)
        # Build the project for the given `label` as well.
        to = joinpath(filter(!isempty, [dir, name, label])...)
        rm(to; recursive=true, force=true)
        for target in targets
            target(p, to)
        end
    end
    if versioned
        # Find the current versions.
        dir = joinpath(filter(!isempty, [dir, name])...)
        versions = sort!(filter(s->!startswith(s, '.') && isdir(dir, s), readdir(dir)); rev=true)
        # Write a version.js file containing the list of all versions.
        io = IOBuffer()
        println(io, "var PUBLISH_VERSIONS = [")
        for v in versions
            println(io, " "^4, "[", repr(v), ",", repr(abspath(joinpath(root, dir, v, "index.html"))), "],")
        end
        println(io, "];")
        # Create/update versions.js file for every built version.
        for v in versions
            file = joinpath(dir, v, "versions.js")
            open(file, "w") do f
                println(f, "var PUBLISH_ROOT = '$(abspath(joinpath(root, dirname(file))))';")
                println(f, "var PUBLISH_VERSION = $(repr(v));")
                write(f, seekstart(io))
            end
        end
    end
    return source
end