Add prehooks/posthooks for more fine-grained plugin control

This commit is contained in:
Chris de Graaf 2019-09-20 00:21:06 +07:00
parent 12fcc121fa
commit 146c1bdbe5
No known key found for this signature in database
GPG Key ID: 150FFDD9B0073C7B
11 changed files with 253 additions and 98 deletions

View File

@ -61,9 +61,9 @@ version = "1.1.0"
[[Parameters]] [[Parameters]]
deps = ["OrderedCollections"] deps = ["OrderedCollections"]
git-tree-sha1 = "1dfd7cd50a8eb06ef693a4c2bbe945943cd000c5" git-tree-sha1 = "b62b2558efb1eef1fa44e4be5ff58a515c287e38"
uuid = "d96e819e-fc66-5662-9728-84c9c7592b0a" uuid = "d96e819e-fc66-5662-9728-84c9c7592b0a"
version = "0.11.0" version = "0.12.0"
[[Pkg]] [[Pkg]]
deps = ["Dates", "LibGit2", "Markdown", "Printf", "REPL", "Random", "SHA", "UUIDs"] deps = ["Dates", "LibGit2", "Markdown", "Printf", "REPL", "Random", "SHA", "UUIDs"]

View File

@ -11,6 +11,7 @@ Mustache = "ffc61752-8dc7-55ee-8c37-f3e9cdd09e70"
Parameters = "d96e819e-fc66-5662-9728-84c9c7592b0a" Parameters = "d96e819e-fc66-5662-9728-84c9c7592b0a"
Pkg = "44cfe95a-1eb2-52ea-b672-e2afdf69b78f" Pkg = "44cfe95a-1eb2-52ea-b672-e2afdf69b78f"
REPL = "3fa0cd96-eef1-5676-8a61-b3b8758bbffb" REPL = "3fa0cd96-eef1-5676-8a61-b3b8758bbffb"
UUIDs = "cf7118a7-6976-5b1a-9a39-7adc72f591a4"
[compat] [compat]
julia = "1" julia = "1"

View File

@ -50,7 +50,7 @@ view(p::Documenter, t::Template, pkg::AbstractString) = Dict(
"USER" => t.user, "USER" => t.user,
) )
function gen_plugin(p::Documenter, t::Template, pkg_dir::AbstractString) function hook(p::Documenter, t::Template, pkg_dir::AbstractString)
pkg = basename(pkg_dir) pkg = basename(pkg_dir)
docs_dir = joinpath(pkg_dir, "docs") docs_dir = joinpath(pkg_dir, "docs")
@ -91,10 +91,10 @@ Third, we implement [`view`](@ref), which is used to fill placeholders in badges
view view
``` ```
Finally, we implement [`gen_plugin`](@ref), which is the real workhorse for the plugin. Finally, we implement [`hook`](@ref), which is the real workhorse for the plugin.
```@docs ```@docs
gen_plugin hook
``` ```
Inside of this function, we call a few more functions, which help us with text templating. Inside of this function, we call a few more functions, which help us with text templating.
@ -167,7 +167,7 @@ Finally, we implement [`view`](@ref) to fill in the placeholders that we saw in
## Doing Extra Work With `BasicPlugin`s ## Doing Extra Work With `BasicPlugin`s
Notice that we didn't have to implement [`gen_plugin`](@ref) for our plugin. Notice that we didn't have to implement [`hook`](@ref) for our plugin.
It's implemented for all [`BasicPlugin`](@ref)s, like so: It's implemented for all [`BasicPlugin`](@ref)s, like so:
```julia ```julia
@ -175,7 +175,7 @@ function render_plugin(p::BasicPlugin, t::Template, pkg::AbstractString)
return render_file(source(p), combined_view(p, t, pkg), tags(p)) return render_file(source(p), combined_view(p, t, pkg), tags(p))
end end
function gen_plugin(p::BasicPlugin, t::Template, pkg_dir::AbstractString) function hook(p::BasicPlugin, t::Template, pkg_dir::AbstractString)
source(p) === nothing && return source(p) === nothing && return
pkg = basename(pkg_dir) pkg = basename(pkg_dir)
path = joinpath(pkg_dir, destination(p)) path = joinpath(pkg_dir, destination(p))
@ -191,7 +191,7 @@ It creates `runtests.jl`, but it also modifies the `Project.toml` to include the
Of course, we could use a normal [`Plugin`](@ref), but it turns out there's a way to avoid that while still getting the extra capbilities that we want. Of course, we could use a normal [`Plugin`](@ref), but it turns out there's a way to avoid that while still getting the extra capbilities that we want.
The plugin implements its own `gen_plugin`, but uses `invoke` to avoid duplicating the file creation code: The plugin implements its own `hook`, but uses `invoke` to avoid duplicating the file creation code:
```julia ```julia
@with_kw_noshow struct Tests <: BasicPlugin @with_kw_noshow struct Tests <: BasicPlugin
@ -202,9 +202,9 @@ source(p::Tests) = p.file
destination(::Tests) = joinpath("test", "runtests.jl") destination(::Tests) = joinpath("test", "runtests.jl")
view(::Tests, ::Template, pkg::AbstractString) = Dict("PKG" => pkg) view(::Tests, ::Template, pkg::AbstractString) = Dict("PKG" => pkg)
function gen_plugin(p::Tests, t::Template, pkg_dir::AbstractString) function hook(p::Tests, t::Template, pkg_dir::AbstractString)
# Do the normal BasicPlugin behaviour to create the test script. # Do the normal BasicPlugin behaviour to create the test script.
invoke(gen_plugin, Tuple{BasicPlugin, Template, AbstractString}, p, t, pkg_dir) invoke(hook, Tuple{BasicPlugin, Template, AbstractString}, p, t, pkg_dir)
# Do some other work. # Do some other work.
add_test_dependency() add_test_dependency()
end end

View File

@ -28,10 +28,12 @@ These plugins are included by default.
They can be overridden by supplying another value via the `plugins` keyword, or disabled by supplying the type via the `disable_defaults` keyword. They can be overridden by supplying another value via the `plugins` keyword, or disabled by supplying the type via the `disable_defaults` keyword.
```@docs ```@docs
Gitignore ProjectFile
License SrcDir
Readme
Tests Tests
Readme
License
Git
``` ```
### Continuous Integration (CI) ### Continuous Integration (CI)
@ -63,6 +65,7 @@ Documenter
### Miscellaneous ### Miscellaneous
```@docs ```@docs
Develop
Citation Citation
``` ```

View File

@ -5,9 +5,10 @@ using Base.Filesystem: contractuser
using Dates: month, today, year using Dates: month, today, year
using InteractiveUtils: subtypes using InteractiveUtils: subtypes
using LibGit2: LibGit2, GitRemote using LibGit2: LibGit2, GitRemote, GitRepo
using Pkg: Pkg, TOML, PackageSpec using Pkg: Pkg, TOML, PackageSpec
using REPL.TerminalMenus: MultiSelectMenu, RadioMenu, request using REPL.TerminalMenus: MultiSelectMenu, RadioMenu, request
using UUIDs: uuid4
using Mustache: render using Mustache: render
using Parameters: @with_kw_noshow using Parameters: @with_kw_noshow
@ -19,11 +20,14 @@ export
Citation, Citation,
Codecov, Codecov,
Coveralls, Coveralls,
Develop,
Documenter, Documenter,
Gitignore, Git,
GitLabCI, GitLabCI,
License, License,
ProjectFile,
Readme, Readme,
SrcDir,
Tests, Tests,
TravisCI TravisCI
@ -36,7 +40,6 @@ When implementing a new plugin, subtype this type to have full control over its
abstract type Plugin end abstract type Plugin end
include("template.jl") include("template.jl")
include("generate.jl")
include("plugin.jl") include("plugin.jl")
include("interactive.jl") include("interactive.jl")

View File

@ -19,7 +19,7 @@ Return the view to be passed to the text templating engine for this plugin.
`pkg` is the name of the package being generated. `pkg` is the name of the package being generated.
For [`BasicPlugin`](@ref)s, this is used for both the plugin badges (see [`badges`](@ref)) and the template file (see [`source`](@ref)). For [`BasicPlugin`](@ref)s, this is used for both the plugin badges (see [`badges`](@ref)) and the template file (see [`source`](@ref)).
For other [`Plugin`](@ref)s, it is used only for badges, but you can always call it yourself as part of your [`gen_plugin`](@ref) implementation. For other [`Plugin`](@ref)s, it is used only for badges, but you can always call it yourself as part of your [`hook`](@ref) implementation.
By default, an empty `Dict` is returned. By default, an empty `Dict` is returned.
""" """
@ -124,19 +124,44 @@ function badges(p::Plugin, t::Template, pkg::AbstractString)
end end
""" """
gen_plugin(::Plugin, ::Template, pkg::AbstractString) prehook(::Plugin, ::Template, pkg_dir::AbstractString)
Do some work associated with a plugin **before** any files are generated.
At this point, `pkg_dir` is an empty directory that will eventually contain the package.
"""
prehook(::Plugin, ::Template, ::AbstractString) = nothing
function prehook(p::T, ::Template, ::AbstractString) where T <: BasicPlugin
src = source(p)
src === nothing && return
isfile(src) || throw(ArgumentError("$(nameof(T)): The file $src does not exist"))
end
"""
posthook(::Plugin, ::Template, pkg_dir::AbstractString)
Do some work associated with a plugin **after** after files have been generated.
"""
posthook(::Plugin, ::Template, ::AbstractString) = nothing
"""
hook(::Plugin, ::Template, pkg_dir::AbstractString)
Perform any work associated with a plugin. Perform any work associated with a plugin.
`pkg` is the name of the package being generated. `pkg_dir` is the directory in which the package is being generated (so `basename(pkg_dir)` is the package name).
For [`Plugin`](@ref)s that are not [`BasicPlugin`](@ref)s, this is the only function that really needs to be implemented. For [`Plugin`](@ref)s that are not [`BasicPlugin`](@ref)s, this is the only function that really needs to be implemented.
If you want your plugin to do anything at all during package generation, you should implement it here. If you want your plugin to do something during the main phase of package generation, you should implement it here.
You should **not** implement this function for `BasicPlugin`s. See also: [`prehook`](@ref) and [`posthook`](@ref).
!!! note
You usually shouldn't implement this function for [`BasicPlugin`](@ref)s.
If you do, it should probably `invoke` the generic method (otherwise, there's no reason to subtype `BasicPlugin`).
""" """
gen_plugin(::Plugin, ::Template, ::AbstractString) = nothing hook(::Plugin, ::Template, ::AbstractString) = nothing
function gen_plugin(p::BasicPlugin, t::Template, pkg_dir::AbstractString) function hook(p::BasicPlugin, t::Template, pkg_dir::AbstractString)
source(p) === nothing && return source(p) === nothing && return
pkg = basename(pkg_dir) pkg = basename(pkg_dir)
path = joinpath(pkg_dir, destination(p)) path = joinpath(pkg_dir, destination(p))
@ -184,4 +209,5 @@ include(joinpath("plugins", "defaults.jl"))
include(joinpath("plugins", "coverage.jl")) include(joinpath("plugins", "coverage.jl"))
include(joinpath("plugins", "ci.jl")) include(joinpath("plugins", "ci.jl"))
include(joinpath("plugins", "citation.jl")) include(joinpath("plugins", "citation.jl"))
include(joinpath("plugins", "develop.jl"))
include(joinpath("plugins", "documenter.jl")) include(joinpath("plugins", "documenter.jl"))

View File

@ -1,18 +1,5 @@
const TEST_UUID = "8dfed614-e22c-5e08-85e1-65c5234f0b40" const TEST_UUID = "8dfed614-e22c-5e08-85e1-65c5234f0b40"
const TEST_DEP = PackageSpec(; name="Test", uuid=TEST_UUID) const TEST_DEP = PackageSpec(; name="Test", uuid=TEST_UUID)
const LICENSES = Dict(
"MIT" => "MIT \"Expat\" License",
"BSD2" => "Simplified \"2-clause\" BSD License",
"BSD3" => "Modified \"3-clause\" BSD License",
"ISC" => "Internet Systems Consortium License",
"ASL" => "Apache License, Version 2.0",
"MPL" => "Mozilla Public License, Version 2.0",
"GPL-2.0+" => "GNU Public License, Version 2.0+",
"GPL-3.0+" => "GNU Public License, Version 3.0+",
"LGPL-2.1+" => "Lesser GNU Public License, Version 2.1+",
"LGPL-3.0+" => "Lesser GNU Public License, Version 3.0+",
"EUPL-1.2+" => "European Union Public Licence, Version 1.2+",
)
badge_order() = [ badge_order() = [
Documenter{GitLabCI}, Documenter{GitLabCI},
@ -25,6 +12,67 @@ badge_order() = [
Coveralls, Coveralls,
] ]
"""
ProjectFile()
Creates a `Project.toml`.
"""
struct ProjectFile <: Plugin end
# Create Project.toml in the prehook because other hooks might depend on it.
function prehook(::ProjectFile, t::Template, pkg_dir::AbstractString)
toml = Dict(
"name" => basename(pkg_dir),
"uuid" => uuid4(),
"authors" => t.authors,
"compat" => Dict("julia" => compat_version(t.julia_version)),
)
open(io -> TOML.print(io, toml), joinpath(pkg_dir, "Project.toml"), "w")
end
"""
compat_version(v::VersionNumber) -> String
Format a `VersionNumber` to exclude trailing zero components.
"""
function compat_version(v::VersionNumber)
return if v.patch == 0 && v.minor == 0
"$(v.major)"
elseif v.patch == 0
"$(v.major).$(v.minor)"
else
"$(v.major).$(v.minor).$(v.patch)"
end
end
"""
SrcDir(; file="$(contractuser(default_file("src", "module.jl")))")
Creates a module entrypoint.
"""
@with_kw_noshow mutable struct SrcDir <: BasicPlugin
file::String = default_file("src", "module.jl")
destination::String = joinpath("src", "<module>.jl")
end
# Don't display the destination field.
function Base.show(io::IO, ::MIME"text/plain", p::SrcDir)
indent = get(io, :indent, 0)
print(io, repeat(' ', indent), "SrcDir:")
print(io, "\n", repeat(' ', indent + 2), "file: ", show_field(p.file))
end
source(p::SrcDir) = p.file
destination(p::SrcDir) = p.destination
view(::SrcDir, ::Template, pkg::AbstractString) = Dict("PKG" => pkg)
# Update the destination now that we know the package name.
# Kind of hacky, but oh well.
function prehook(p::SrcDir, t::Template, pkg_dir::AbstractString)
invoke(prehook, Tuple{BasicPlugin, Template, AbstractString}, p, t, pkg_dir)
p.destination = joinpath("src", basename(pkg_dir) * ".jl")
end
""" """
Readme(; Readme(;
file="$(contractuser(default_file("README.md")))", file="$(contractuser(default_file("README.md")))",
@ -113,32 +161,88 @@ view(::License, t::Template, ::AbstractString) = Dict(
) )
""" """
Gitignore(; ds_store=true, dev=true) Git(; ignore=String[], ssh=false, manifest=false, gpgsign=false)
Creates a `.gitignore` file. Creates a Git repository and a `.gitignore` file.
## Keyword Arguments ## Keyword Arguments
- `ds_store::Bool`: Whether or not to ignore MacOS's `.DS_Store` files. - `ignore::Vector{<:AbstractString}`: Patterns to add to the `.gitignore`.
- `dev::Bool`: Whether or not to ignore the directory of locally-developed packages. See also: [`gitignore`](@ref).
- `ssh::Bool`: Whether or not to use SSH for the remote.
If left unset, HTTPS is used.
- `manifest::Bool`: Whether or not to commit `Manifest.toml`.
- `gpgsign::Bool`: Whether or not to sign commits with your GPG key.
This option requires that the Git CLI is installed.
""" """
@with_kw_noshow struct Gitignore <: Plugin @with_kw_noshow struct Git <: Plugin
ds_store::Bool = true ignore::Vector{String} = []
dev::Bool = true ssh::Bool = false
manifest::Bool = false
gpgsign::Bool = false
end end
function render_plugin(p::Gitignore, t::Template) gitignore(p::Git) = p.ignore
init = String[]
p.ds_store && push!(init, ".DS_Store") # Set up the Git repository.
p.dev && push!(init, "/dev/") function prehook(p::Git, t::Template, pkg_dir::AbstractString)
entries = mapreduce(gitignore, append!, values(t.plugins); init=init) if p.gpgsign && try run(pipeline(`git --version`; stdout=devnull)); false catch; true end
throw(ArgumentError("Git: gpgsign is set but the Git CLI is not installed"))
end
LibGit2.with(LibGit2.init(pkg_dir)) do repo
commit(p, repo, pkg_dir, "Initial commit")
pkg = basename(pkg_dir)
url = if p.ssh
"git@$(t.host):$(t.user)/$pkg.jl.git"
else
"https://$(t.host)/$(t.user)/$pkg.jl"
end
LibGit2.with(GitRemote(repo, "origin", url)) do remote
# TODO: `git pull` still requires some Git branch config.
LibGit2.add_push!(repo, remote, "refs/heads/master")
end
end
end
# Create the .gitignore.
function hook(p::Git, t::Template, pkg_dir::AbstractString)
gen_file(joinpath(pkg_dir, ".gitignore"), render_plugin(p, t))
end
# Commit the files
function posthook(p::Git, t::Template, pkg_dir::AbstractString)
# Ensure that the manifest exists if it's going to be committed.
manifest = joinpath(pkg_dir, "Manifest.toml")
if p.manifest && !isfile(manifest)
touch(manifest)
with_project(Pkg.update, pkg_dir)
end
LibGit2.with(GitRepo(pkg_dir)) do repo
LibGit2.add!(repo, ".")
msg = "Files generated by PkgTemplates"
installed = Pkg.installed()
if haskey(installed, "PkgTemplates")
ver = string(installed["PkgTemplates"])
msg *= "\n\nPkgTemplates version: $ver"
end
commit(p, repo, pkg_dir, msg)
end
end
function commit(p::Git, repo::GitRepo, pkg_dir::AbstractString, msg::AbstractString)
if p.gpgsign
run(pipeline(`git -C $pkg_dir commit -S --allow-empty -m $msg`; stdout=devnull))
else
LibGit2.commit(repo, msg)
end
end
function render_plugin(p::Git, t::Template)
ignore = mapreduce(gitignore, append!, values(t.plugins))
# Only ignore manifests at the repo root. # Only ignore manifests at the repo root.
t.manifest || "Manifest.toml" in entries || push!(entries, "/Manifest.toml") p.manifest || "Manifest.toml" in ignore || push!(ignore, "/Manifest.toml")
unique!(sort!(entries)) unique!(sort!(ignore))
return join(entries, "\n") return join(ignore, "\n")
end
function gen_plugin(p::Gitignore, t::Template, pkg_dir::AbstractString)
t.git && gen_file(joinpath(pkg_dir, ".gitignore"), render_plugin(p, t))
end end
""" """
@ -163,9 +267,18 @@ source(p::Tests) = p.file
destination(::Tests) = joinpath("test", "runtests.jl") destination(::Tests) = joinpath("test", "runtests.jl")
view(::Tests, ::Template, pkg::AbstractString) = Dict("PKG" => pkg) view(::Tests, ::Template, pkg::AbstractString) = Dict("PKG" => pkg)
function gen_plugin(p::Tests, t::Template, pkg_dir::AbstractString) function prehook(p::Tests, t::Template, pkg_dir::AbstractString)
invoke(prehook, Tuple{BasicPlugin, Template, AbstractString}, p, t, pkg_dir)
p.project && t.julia_version < v"1.2" && @warn string(
"Tests: The project option is set to create a project (supported in Julia 1.2 and later) ",
"but a Julia version older than 1.2 is supported by the Template.",
)
end
function hook(p::Tests, t::Template, pkg_dir::AbstractString)
# Do the normal BasicPlugin behaviour to create the test script. # Do the normal BasicPlugin behaviour to create the test script.
invoke(gen_plugin, Tuple{BasicPlugin, Template, AbstractString}, p, t, pkg_dir) invoke(hook, Tuple{BasicPlugin, Template, AbstractString}, p, t, pkg_dir)
# Then set up the test depdendency in the chosen way. # Then set up the test depdendency in the chosen way.
f = p.project ? make_test_project : add_test_dependency f = p.project ? make_test_project : add_test_dependency

11
src/plugins/develop.jl Normal file
View File

@ -0,0 +1,11 @@
"""
Develop()
Adds generated packages to the current environment by `dev`ing them.
See [here](https://julialang.github.io/Pkg.jl/v1/managing-packages/#Developing-packages-1) for more details.
"""
struct Develop <: Plugin end
function posthook(::Develop, ::Template, pkg_dir::AbstractString)
Pkg.develop(PackageSpec(; path=pkg_dir))
end

View File

@ -85,14 +85,14 @@ function view(p::Documenter{TravisCI}, t::Template, pkg::AbstractString)
return merge(base, Dict("HAS_DEPLOY" => true)) return merge(base, Dict("HAS_DEPLOY" => true))
end end
function gen_plugin(p::Documenter, t::Template, pkg_dir::AbstractString) function hook(p::Documenter, t::Template, pkg_dir::AbstractString)
pkg = basename(pkg_dir) pkg = basename(pkg_dir)
docs_dir = joinpath(pkg_dir, "docs") docs_dir = joinpath(pkg_dir, "docs")
# Generate files. # Generate files.
make = render_file(p.make_jl, combined_view(p, t, pkg), tags(p)) make = render_file(p.make_jl, combined_view(p, t, pkg), tags(p))
gen_file(joinpath(docs_dir, "make.jl"), make)
index = render_file(p.index_md, combined_view(p, t, pkg), tags(p)) index = render_file(p.index_md, combined_view(p, t, pkg), tags(p))
gen_file(joinpath(docs_dir, "make.jl"), make)
gen_file(joinpath(docs_dir, "src", "index.md"), index) gen_file(joinpath(docs_dir, "src", "index.md"), index)
# Copy over any assets. # Copy over any assets.

View File

@ -1,4 +1,4 @@
default_plugins() = [Gitignore(), License(), Readme(), Tests()] default_plugins() = [ProjectFile(), SrcDir(), Git(), License(), Readme(), Tests()]
default_user() = LibGit2.getconfig("github.user", "") default_user() = LibGit2.getconfig("github.user", "")
default_version() = VersionNumber(VERSION.major) default_version() = VersionNumber(VERSION.major)
@ -26,20 +26,14 @@ A configuration used to generate packages.
### Package Options ### Package Options
- `dir::AbstractString="$(contractuser(Pkg.devdir()))"`: Directory to place packages in. - `dir::AbstractString="$(contractuser(Pkg.devdir()))"`: Directory to place packages in.
- `host::AbstractString="github.com"`: URL to the code hosting service where packages will reside.
- `julia_version::VersionNumber=$(repr(default_version()))`: Minimum allowed Julia version. - `julia_version::VersionNumber=$(repr(default_version()))`: Minimum allowed Julia version.
- `develop::Bool=true`: Whether or not to `develop` new packages in the active environment. - `develop::Bool=true`: Whether or not to `develop` new packages in the active environment.
### Git Options
- `git::Bool=true`: Whether or not to create a Git repository for new packages.
- `host::AbstractString="github.com"`: URL to the code hosting service where packages will reside.
- `ssh::Bool=false`: Whether or not to use SSH for the Git remote.
If left unset, HTTPS will be used.
- `manifest::Bool=false`: Whether or not to commit the `Manifest.toml`.
### Template Plugins ### Template Plugins
- `plugins::Vector{<:Plugin}=Plugin[]`: A list of [`Plugin`](@ref)s used by the template. - `plugins::Vector{<:Plugin}=Plugin[]`: A list of [`Plugin`](@ref)s used by the template.
- `disable_defaults::Vector{DataType}=DataType[]`: Default plugins to disable. - `disable_defaults::Vector{DataType}=DataType[]`: Default plugins to disable.
The default plugins are [`Readme`](@ref), [`License`](@ref), [`Tests`](@ref), and [`Gitignore`](@ref). The default plugins are [`ProjectFile`](@ref), [`SrcDir`](@ref), [`Tests`](@ref), [`Readme`](@ref), [`License`](@ref), and [`Git`](@ref).
To override a default plugin instead of disabling it altogether, supply it via `plugins`. To override a default plugin instead of disabling it altogether, supply it via `plugins`.
### Interactive Usage ### Interactive Usage
@ -57,14 +51,10 @@ julia> t("PkgName")
""" """
struct Template struct Template
authors::Vector{String} authors::Vector{String}
develop::Bool
dir::String dir::String
git::Bool
host::String host::String
julia_version::VersionNumber julia_version::VersionNumber
manifest::Bool
plugins::Dict{DataType, <:Plugin} plugins::Dict{DataType, <:Plugin}
ssh::Bool
user::String user::String
end end
@ -78,9 +68,9 @@ function Template(::Val{false}; kwargs...)
authors = getkw(kwargs, :authors) authors = getkw(kwargs, :authors)
authors isa Vector || (authors = map(strip, split(authors, ","))) authors isa Vector || (authors = map(strip, split(authors, ",")))
host = replace(getkw(kwargs, :host), r".*://" => "")
dir = abspath(expanduser(getkw(kwargs, :dir))) dir = abspath(expanduser(getkw(kwargs, :dir)))
host = replace(getkw(kwargs, :host), r".*://" => "")
julia_version = getkw(kwargs, :julia_version)
disabled = getkw(kwargs, :disable_defaults) disabled = getkw(kwargs, :disable_defaults)
enabled = filter(p -> !(typeof(p) in disabled), default_plugins()) enabled = filter(p -> !(typeof(p) in disabled), default_plugins())
@ -89,26 +79,7 @@ function Template(::Val{false}; kwargs...)
# which means that default plugins get replaced by user values. # which means that default plugins get replaced by user values.
plugins = Dict(typeof(p) => p for p in enabled) plugins = Dict(typeof(p) => p for p in enabled)
# TODO: It might be nice to offer some kind of warn_incompatible function return Template(authors, dir, host, julia_version, plugins, user)
# to be optionally implemented by plugins instead of hardcoding this case here.
julia = getkw(kwargs, :julia_version)
julia < v"1.2" && haskey(plugins, Tests) && plugins[Tests].project && @warn string(
"The Tests plugin is set to create a project (supported in Julia 1.2 and later)",
"but a Julia version older than 1.2 is supported.",
)
return Template(
authors,
getkw(kwargs, :develop),
dir,
getkw(kwargs, :git),
host,
julia,
getkw(kwargs, :manifest),
plugins,
getkw(kwargs, :ssh),
user,
)
end end
# Does the template have a plugin that satisfies some predicate? # Does the template have a plugin that satisfies some predicate?
@ -121,13 +92,35 @@ getkw(kwargs, k) = get(() -> defaultkw(k), kwargs, k)
# Default Template keyword values. # Default Template keyword values.
defaultkw(s::Symbol) = defaultkw(Val(s)) defaultkw(s::Symbol) = defaultkw(Val(s))
defaultkw(::Val{:authors}) = default_authors() defaultkw(::Val{:authors}) = default_authors()
defaultkw(::Val{:develop}) = true
defaultkw(::Val{:dir}) = Pkg.devdir() defaultkw(::Val{:dir}) = Pkg.devdir()
defaultkw(::Val{:disable_defaults}) = DataType[] defaultkw(::Val{:disable_defaults}) = DataType[]
defaultkw(::Val{:git}) = true
defaultkw(::Val{:host}) = "github.com" defaultkw(::Val{:host}) = "github.com"
defaultkw(::Val{:julia_version}) = default_version() defaultkw(::Val{:julia_version}) = default_version()
defaultkw(::Val{:manifest}) = false
defaultkw(::Val{:plugins}) = Plugin[] defaultkw(::Val{:plugins}) = Plugin[]
defaultkw(::Val{:ssh}) = false
defaultkw(::Val{:user}) = default_user() defaultkw(::Val{:user}) = default_user()
"""
(::Template)(pkg::AbstractString)
Generate a package named `pkg` from a [`Template`](@ref).
"""
function (t::Template)(pkg::AbstractString)
endswith(pkg, ".jl") && (pkg = pkg[1:end-3])
pkg_dir = joinpath(t.dir, pkg)
ispath(pkg_dir) && throw(ArgumentError("$pkg_dir already exists"))
mkpath(pkg_dir)
try
foreach((prehook, hook, posthook)) do h
@info "Running $(h)s"
foreach(values(t.plugins)) do p
h(p, t, pkg_dir)
end
end
catch
rm(pkg_dir; recursive=true, force=true)
rethrow()
end
@info "New package is at $pkg_dir"
end

5
templates/src/module.jl Normal file
View File

@ -0,0 +1,5 @@
module {{{PKG}}}
# Write your package code here.
end