From fd995a05bccc757bb9852c218de09c6929b99d94 Mon Sep 17 00:00:00 2001 From: Chris de Graaf Date: Wed, 25 Sep 2019 23:26:03 +0700 Subject: [PATCH] Add per-plugin validation at template construction time --- docs/src/developer.md | 36 ++++++++++++++++++++++++++++++------ src/plugin.jl | 11 ++++++++++- src/plugins/documenter.jl | 10 ++++++++++ src/plugins/git.jl | 13 +++++++++++-- src/plugins/src_dir.jl | 1 - src/plugins/tests.jl | 4 ++-- src/template.jl | 4 +++- test/template.jl | 10 ++++++++-- 8 files changed, 74 insertions(+), 15 deletions(-) diff --git a/docs/src/developer.md b/docs/src/developer.md index 96cdf5f..7a92c15 100644 --- a/docs/src/developer.md +++ b/docs/src/developer.md @@ -17,9 +17,25 @@ Plugin 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 @@ -31,7 +47,6 @@ The package generation process looks basically like this: - run plugin posthook ``` -That's it! 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). @@ -151,6 +166,14 @@ struct Git <: Plugin end 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) LibGit2.with(LibGit2.init(pkg_dir)) do repo LibGit2.commit(repo, "Initial commit") @@ -174,8 +197,9 @@ function posthook(::Git, ::Template, pkg_dir::AbstractString) 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. - [`hook`](@ref) generates the `.gitignore` file, using the special [`gitignore`](@ref) function. - [`posthook`](@ref) adds and commits all the generated files. @@ -286,8 +310,8 @@ function hook(p::Tests, t::Template, pkg_dir::AbstractString) 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. -If you want to extend the prehook but keep the file existence check, use the `invoke` method as described above. +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 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. diff --git a/src/plugin.jl b/src/plugin.jl index 88c76fb..00fddc9 100644 --- a/src/plugin.jl +++ b/src/plugin.jl @@ -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) 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) @@ -168,7 +177,7 @@ At this point, both the [`prehook`](@ref)s and [`hook`](@ref)s have run. """ 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 === nothing && return isfile(src) || throw(ArgumentError("$(nameof(T)): The file $src does not exist")) diff --git a/src/plugins/documenter.jl b/src/plugins/documenter.jl index 6fb9f5c..6931d3a 100644 --- a/src/plugins/documenter.jl +++ b/src/plugins/documenter.jl @@ -89,6 +89,16 @@ function view(p::Documenter{TravisCI}, t::Template, pkg::AbstractString) return merge(base, Dict("HAS_DEPLOY" => true)) 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) pkg = basename(pkg_dir) docs_dir = joinpath(pkg_dir, "docs") diff --git a/src/plugins/git.jl b/src/plugins/git.jl index 5b413ab..352a7b5 100644 --- a/src/plugins/git.jl +++ b/src/plugins/git.jl @@ -30,11 +30,20 @@ function gitignore(p::Git) return ignore end -# Set up the Git repository. -function prehook(p::Git, t::Template, pkg_dir::AbstractString) +function validate(p::Git, t::Template) 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 + + 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 commit(p, repo, pkg_dir, "Initial commit") pkg = basename(pkg_dir) diff --git a/src/plugins/src_dir.jl b/src/plugins/src_dir.jl index fbbdad3..aa1e177 100644 --- a/src/plugins/src_dir.jl +++ b/src/plugins/src_dir.jl @@ -24,6 +24,5 @@ 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 diff --git a/src/plugins/tests.jl b/src/plugins/tests.jl index 796c6b5..a7e3620 100644 --- a/src/plugins/tests.jl +++ b/src/plugins/tests.jl @@ -23,8 +23,8 @@ source(p::Tests) = p.file destination(::Tests) = joinpath("test", "runtests.jl") view(::Tests, ::Template, pkg::AbstractString) = Dict("PKG" => pkg) -function prehook(p::Tests, t::Template, pkg_dir::AbstractString) - invoke(prehook, Tuple{BasicPlugin, Template, AbstractString}, p, t, pkg_dir) +function validate(p::Tests, t::Template) + invoke(validate, Tuple{BasicPlugin, Template}, p, t) 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) ", "but a Julia version older than 1.2 is supported by the Template.", diff --git a/src/template.jl b/src/template.jl index d15873d..9a08ae0 100644 --- a/src/template.jl +++ b/src/template.jl @@ -85,7 +85,9 @@ function Template(::Val{false}; kwargs...) 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 """ diff --git a/test/template.jl b/test/template.jl index b4b583c..260f34e 100644 --- a/test/template.jl +++ b/test/template.jl @@ -46,13 +46,19 @@ end @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, Documenter) + @test PT.hasplugin(t, PT.is_ci) @test PT.hasplugin(t, _ -> true) @test !PT.hasplugin(t, _ -> false) @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