Add per-plugin validation at template construction time

This commit is contained in:
Chris de Graaf 2019-09-25 23:26:03 +07:00
parent 385195c6b5
commit fd995a05bc
No known key found for this signature in database
GPG Key ID: 150FFDD9B0073C7B
8 changed files with 74 additions and 15 deletions

View File

@ -17,9 +17,25 @@ Plugin
BasicPlugin BasicPlugin
``` ```
## Package Generation Pipeline ## Template + Package Creation Pipeline
The package generation process looks basically like this:
The [`Template`](@ref) constructor basically does this:
```
- extract values from keyword arguments
- create a Template from the values
- validate each plugin against the constructed Template
```
The plugin validation step uses the [`validate`](@ref) function.
It lets us catch mistakes before we try to generate packages.
```@docs
validate
```
The package generation process looks like this:
``` ```
- create empty directory for the package - create empty directory for the package
@ -31,7 +47,6 @@ The package generation process looks basically like this:
- run plugin posthook - run plugin posthook
``` ```
That's it!
As you can tell, plugins play a central role in setting up a package. As you can tell, plugins play a central role in setting up a package.
The three main entrypoints for plugins to do work are the [`prehook`](@ref), the [`hook`](@ref), and the [`posthook`](@ref). The three main entrypoints for plugins to do work are the [`prehook`](@ref), the [`hook`](@ref), and the [`posthook`](@ref).
@ -151,6 +166,14 @@ struct Git <: Plugin end
priority(::Git, ::typeof(posthook)) = 5 priority(::Git, ::typeof(posthook)) = 5
function validate(::Git, ::Template)
foreach(("user.name", "user.email")) do k
if isempty(LibGit2.getconfig(k, ""))
throw(ArgumentError("Git: Global Git config is missing required value '$k'"))
end
end
end
function prehook(::Git, t::Template, pkg_dir::AbstractString) function prehook(::Git, t::Template, pkg_dir::AbstractString)
LibGit2.with(LibGit2.init(pkg_dir)) do repo LibGit2.with(LibGit2.init(pkg_dir)) do repo
LibGit2.commit(repo, "Initial commit") LibGit2.commit(repo, "Initial commit")
@ -174,8 +197,9 @@ function posthook(::Git, ::Template, pkg_dir::AbstractString)
end end
``` ```
All three hooks are implemented: Validation and all three hooks are implemented:
- [`validate`](@ref) makes sure that all required Git configuration is present.
- [`prehook`](@ref) creates the Git repository for the package. - [`prehook`](@ref) creates the Git repository for the package.
- [`hook`](@ref) generates the `.gitignore` file, using the special [`gitignore`](@ref) function. - [`hook`](@ref) generates the `.gitignore` file, using the special [`gitignore`](@ref) function.
- [`posthook`](@ref) adds and commits all the generated files. - [`posthook`](@ref) adds and commits all the generated files.
@ -286,8 +310,8 @@ function hook(p::Tests, t::Template, pkg_dir::AbstractString)
end end
``` ```
There is also a default [`prehook`](@ref) implementation for [`BasicPlugin`](@ref)s, which checks that the plugin's [`source`](@ref) file exists, and throws an `ArgumentError` otherwise. There is also a default [`validate`](@ref) implementation for [`BasicPlugin`](@ref)s, which checks that the plugin's [`source`](@ref) file exists, and throws an `ArgumentError` otherwise.
If you want to extend the prehook but keep the file existence check, use the `invoke` method as described above. If you want to extend the validation but keep the file existence check, use the `invoke` method as described above.
For more examples, see the plugins in the [Continuous Integration (CI)](@ref) and [Code Coverage](@ref) sections. For more examples, see the plugins in the [Continuous Integration (CI)](@ref) and [Code Coverage](@ref) sections.

View File

@ -134,6 +134,15 @@ function badges(p::Plugin, t::Template, pkg::AbstractString)
return map(b -> render_text(string(b), combined_view(p, t, pkg)), bs) return map(b -> render_text(string(b), combined_view(p, t, pkg)), bs)
end end
"""
validate(::Plugin, ::Template)
Perform any required validation for a [`Plugin`](@ref).
It is preferred to do validation here instead of in [`prehook`](@ref), because this function is called at [`Template`](@ref) construction time, whereas the prehook is only run at package generation time.
"""
validate(::Plugin, ::Template) = nothing
""" """
prehook(::Plugin, ::Template, pkg_dir::AbstractString) prehook(::Plugin, ::Template, pkg_dir::AbstractString)
@ -168,7 +177,7 @@ At this point, both the [`prehook`](@ref)s and [`hook`](@ref)s have run.
""" """
posthook(::Plugin, ::Template, ::AbstractString) = nothing posthook(::Plugin, ::Template, ::AbstractString) = nothing
function prehook(p::T, ::Template, ::AbstractString) where T <: BasicPlugin function validate(p::T, ::Template) where T <: BasicPlugin
src = source(p) src = source(p)
src === nothing && return src === nothing && return
isfile(src) || throw(ArgumentError("$(nameof(T)): The file $src does not exist")) isfile(src) || throw(ArgumentError("$(nameof(T)): The file $src does not exist"))

View File

@ -89,6 +89,16 @@ 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
foreach((TravisCI, GitLabCI)) do T
@eval function validate(::Documenter{$T}, t::Template)
if !hasplugin(t, $T)
name = nameof($T)
s = "Documenter: The $name plugin must be included for docs deployment to be set up"
throw(ArgumentError(s))
end
end
end
function hook(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")

View File

@ -30,11 +30,20 @@ function gitignore(p::Git)
return ignore return ignore
end end
# Set up the Git repository. function validate(p::Git, t::Template)
function prehook(p::Git, t::Template, pkg_dir::AbstractString)
if p.gpgsign && try run(pipeline(`git --version`; stdout=devnull)); false catch; true end 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")) throw(ArgumentError("Git: gpgsign is set but the Git CLI is not installed"))
end end
foreach(("user.name", "user.email")) do k
if isempty(LibGit2.getconfig(k, ""))
throw(ArgumentError("Git: Global Git config is missing required value '$k'"))
end
end
end
# Set up the Git repository.
function prehook(p::Git, t::Template, pkg_dir::AbstractString)
LibGit2.with(LibGit2.init(pkg_dir)) do repo LibGit2.with(LibGit2.init(pkg_dir)) do repo
commit(p, repo, pkg_dir, "Initial commit") commit(p, repo, pkg_dir, "Initial commit")
pkg = basename(pkg_dir) pkg = basename(pkg_dir)

View File

@ -24,6 +24,5 @@ view(::SrcDir, ::Template, pkg::AbstractString) = Dict("PKG" => pkg)
# Update the destination now that we know the package name. # Update the destination now that we know the package name.
# Kind of hacky, but oh well. # Kind of hacky, but oh well.
function prehook(p::SrcDir, t::Template, pkg_dir::AbstractString) 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") p.destination = joinpath("src", basename(pkg_dir) * ".jl")
end end

View File

@ -23,8 +23,8 @@ 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 prehook(p::Tests, t::Template, pkg_dir::AbstractString) function validate(p::Tests, t::Template)
invoke(prehook, Tuple{BasicPlugin, Template, AbstractString}, p, t, pkg_dir) invoke(validate, Tuple{BasicPlugin, Template}, p, t)
p.project && t.julia < v"1.2" && @warn string( p.project && t.julia < v"1.2" && @warn string(
"Tests: The project option is set to create a project (supported in Julia 1.2 and later) ", "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.", "but a Julia version older than 1.2 is supported by the Template.",

View File

@ -85,7 +85,9 @@ function Template(::Val{false}; kwargs...)
end end
end end
return Template(authors, dir, host, julia, plugins, user) t = Template(authors, dir, host, julia, plugins, user)
foreach(p -> validate(p, t), t.plugins)
return t
end end
""" """

View File

@ -46,13 +46,19 @@
end end
@testset "hasplugin" begin @testset "hasplugin" begin
t = tpl(; plugins=[Documenter{TravisCI}()]) t = tpl(; plugins=[TravisCI(), Documenter{TravisCI}()])
@test PT.hasplugin(t, typeof(first(PT.default_plugins()))) @test PT.hasplugin(t, typeof(first(PT.default_plugins())))
@test PT.hasplugin(t, Documenter) @test PT.hasplugin(t, Documenter)
@test PT.hasplugin(t, PT.is_ci)
@test PT.hasplugin(t, _ -> true) @test PT.hasplugin(t, _ -> true)
@test !PT.hasplugin(t, _ -> false) @test !PT.hasplugin(t, _ -> false)
@test !PT.hasplugin(t, Citation) @test !PT.hasplugin(t, Citation)
@test !PT.hasplugin(t, PT.is_ci) end
@testset "validate" begin
mock(LibGit2.getconfig => (_k, _d) -> "") do _gc
@test_throws ArgumentError tpl(; plugins=[Git()])
end
end end
end end