Add execution priority to plugins

This commit is contained in:
Chris de Graaf 2019-09-20 09:31:56 +07:00
parent 980452fde3
commit 0cf6d47ada
No known key found for this signature in database
GPG Key ID: 150FFDD9B0073C7B
12 changed files with 68 additions and 49 deletions

View File

@ -19,9 +19,8 @@ However, it's probably desirable to customize the template to your liking with v
```jl ```jl
t = Template(; t = Template(;
dir="~/code", dir="~/code",
ssh=true,
manifest=true,
plugins=[ plugins=[
Git(; manifest=true, ssh=true),
Codecov(), Codecov(),
TravisCI(; x86=true), TravisCI(; x86=true),
Documenter{TravisCI}(), Documenter{TravisCI}(),

View File

@ -94,6 +94,7 @@ view
Finally, we implement [`hook`](@ref), which is the real workhorse for the plugin. Finally, we implement [`hook`](@ref), which is the real workhorse for the plugin.
TODO prehook and posthook in examples TODO prehook and posthook in examples
TODO priority
```@docs ```@docs
prehook prehook

View File

@ -5,8 +5,10 @@
function Base.show(io::IO, ::MIME"text/plain", p::T) where T <: Plugin function Base.show(io::IO, ::MIME"text/plain", p::T) where T <: Plugin
indent = get(io, :indent, 0) indent = get(io, :indent, 0)
print(io, repeat(' ', indent), T, ":") print(io, repeat(' ', indent), T)
foreach(fieldnames(T)) do n ns = fieldnames(T)
isempty(ns) || print(io, ":")
foreach(ns) do n
println(io) println(io)
print(io, repeat(' ', indent + 2), n, ": ", show_field(getfield(p, n))) print(io, repeat(' ', indent + 2), n, ": ", show_field(getfield(p, n)))
end end
@ -28,7 +30,7 @@ function Base.show(io::IO, m::MIME"text/plain", t::Template)
print(io, " plugins: None") print(io, " plugins: None")
else else
print(io, repeat(' ', 2), "plugins:") print(io, repeat(' ', 2), "plugins:")
foreach(sort(collect(values(t.plugins)); by=string)) do p foreach(sort(t.plugins; by=string)) do p
println(io) println(io)
show(IOContext(io, :indent => 4), m, p) show(IOContext(io, :indent => 4), m, p)
end end

View File

@ -1,4 +1,5 @@
const TEMPLATES_DIR = normpath(joinpath(@__DIR__, "..", "templates")) const TEMPLATES_DIR = normpath(joinpath(@__DIR__, "..", "templates"))
const DEFAULT_PRIORITY = 1000
""" """
A simple plugin that, in general, creates a single file. A simple plugin that, in general, creates a single file.
@ -59,6 +60,14 @@ By default, the tags are `"{{"` and `"}}"`.
""" """
tags(::Plugin) = "{{", "}}" tags(::Plugin) = "{{", "}}"
"""
priority(::Plugin) -> Int
Determines the order in which plugins are processed (higher goes first).
The default priority (`DEFAULT_PRIORITY`), is `$DEFAULT_PRIORITY`.
"""
priority(::Plugin) = DEFAULT_PRIORITY
""" """
gitignore(::Plugin) -> Vector{String} gitignore(::Plugin) -> Vector{String}
@ -131,7 +140,7 @@ At this point, `pkg_dir` is an empty directory that will eventually contain the
!!! note !!! note
`pkg_dir` only stays empty until the first plugin chooses to create a file. `pkg_dir` only stays empty until the first plugin chooses to create a file.
Don't count on the order in which the plugins are sorted! See also: [`priority`](@ref).
""" """
prehook(::Plugin, ::Template, ::AbstractString) = nothing prehook(::Plugin, ::Template, ::AbstractString) = nothing

View File

@ -254,10 +254,10 @@ end
""" """
is_ci(::Type{T}) -> Bool is_ci(::Plugin) -> Bool
Determine whether or not `T` is a CI plugin. Determine whether or not a plugin is a CI plugin.
If you are adding a CI plugin, you should implement this function and return `true`. If you are adding a CI plugin, you should implement this function and return `true`.
""" """
is_ci(::Type) = false is_ci(::Plugin) = false
is_ci(::Type{<:Union{AppVeyor, TravisCI, CirrusCI, GitLabCI}}) = true is_ci(::Union{AppVeyor, TravisCI, CirrusCI, GitLabCI}) = true

View File

@ -45,10 +45,10 @@ badges(::Coveralls) = Badge(
gitignore(::Union{Codecov, Coveralls}) = COVERAGE_GITIGNORE gitignore(::Union{Codecov, Coveralls}) = COVERAGE_GITIGNORE
""" """
is_coverage(::Type{T}) -> Bool is_coverage(::Plugin) -> Bool
Determine whether or not `T` is a coverage plugin. Determine whether or not a plugin is a coverage plugin.
If you are adding a coverage plugin, you should implement this function and return `true`. If you are adding a coverage plugin, you should implement this function and return `true`.
""" """
is_coverage(::Type) = false is_coverage(::Plugin) = false
is_coverage(::Type{<:Union{Codecov, Coveralls}}) = true is_coverage(::Union{Codecov, Coveralls}) = true

View File

@ -50,7 +50,7 @@ end
# Create the .gitignore. # Create the .gitignore.
function hook(p::Git, t::Template, pkg_dir::AbstractString) function hook(p::Git, t::Template, pkg_dir::AbstractString)
ignore = mapreduce(gitignore, append!, values(t.plugins)) ignore = mapreduce(gitignore, append!, t.plugins)
# Only ignore manifests at the repo root. # Only ignore manifests at the repo root.
p.manifest || "Manifest.toml" in ignore || push!(ignore, "/Manifest.toml") p.manifest || "Manifest.toml" in ignore || push!(ignore, "/Manifest.toml")
unique!(sort!(ignore)) unique!(sort!(ignore))

View File

@ -5,8 +5,10 @@ Creates a `Project.toml`.
""" """
struct ProjectFile <: Plugin end struct ProjectFile <: Plugin end
# Create Project.toml in the prehook because other hooks might depend on it. # Other plugins like Tests will modify this file.
function prehook(::ProjectFile, t::Template, pkg_dir::AbstractString) priority(::ProjectFile) = typemax(Int) - DEFAULT_PRIORITY + 1
function hook(::ProjectFile, t::Template, pkg_dir::AbstractString)
toml = Dict( toml = Dict(
"name" => basename(pkg_dir), "name" => basename(pkg_dir),
"uuid" => uuid4(), "uuid" => uuid4(),

View File

@ -29,19 +29,18 @@ function view(p::Readme, t::Template, pkg::AbstractString)
done = DataType[] done = DataType[]
foreach(badge_order()) do T foreach(badge_order()) do T
if hasplugin(t, T) if hasplugin(t, T)
bs = badges(t.plugins[T], t, pkg) append!(strings, badges(getplugin(t, T), t, pkg))
append!(strings, badges(t.plugins[T], t, pkg))
push!(done, T) push!(done, T)
end end
end end
foreach(setdiff(keys(t.plugins), done)) do T # And the rest go after, in no particular order.
bs = badges(t.plugins[T], t, pkg) foreach(setdiff(map(typeof, t.plugins), done)) do T
append!(strings, badges(t.plugins[T], t, pkg)) append!(strings, badges(getplugin(t, T), t, pkg))
end end
return Dict( return Dict(
"BADGES" => strings, "BADGES" => strings,
"HAS_CITATION" => hasplugin(t, Citation) && t.plugins[Citation].readme, "HAS_CITATION" => hasplugin(t, Citation) && getplugin(t, Citation).readme,
"HAS_INLINE_BADGES" => !isempty(strings) && p.inline_badges, "HAS_INLINE_BADGES" => !isempty(strings) && p.inline_badges,
"PKG" => pkg, "PKG" => pkg,
) )

View File

@ -54,7 +54,7 @@ struct Template
dir::String dir::String
host::String host::String
julia_version::VersionNumber julia_version::VersionNumber
plugins::Dict{DataType, <:Plugin} plugins::Vector{<:Plugin}
user::String user::String
end end
@ -72,33 +72,17 @@ function Template(::Val{false}; kwargs...)
host = replace(getkw(kwargs, :host), r".*://" => "") host = replace(getkw(kwargs, :host), r".*://" => "")
julia_version = getkw(kwargs, :julia_version) julia_version = getkw(kwargs, :julia_version)
# User-supplied plugins come first, so that deduping the list will remove the defaults.
plugins = Plugin[]
append!(plugins, getkw(kwargs, :plugins))
disabled = getkw(kwargs, :disable_defaults) disabled = getkw(kwargs, :disable_defaults)
enabled = filter(p -> !(typeof(p) in disabled), default_plugins()) append!(plugins, filter(p -> !(typeof(p) in disabled), default_plugins()))
append!(enabled, getkw(kwargs, :plugins)) plugins = unique(typeof, plugins)
# This comprehension resolves duplicate plugin types by overwriting, sort!(plugins; by=priority, rev=true)
# which means that default plugins get replaced by user values.
plugins = Dict(typeof(p) => p for p in enabled)
return Template(authors, dir, host, julia_version, plugins, user) return Template(authors, dir, host, julia_version, plugins, user)
end end
# Does the template have a plugin that satisfies some predicate?
hasplugin(t::Template, f::Function) = any(f, keys(t.plugins))
hasplugin(t::Template, ::Type{T}) where T <: Plugin = hasplugin(t, U -> U <: T)
# Get a keyword, or compute some default value.
getkw(kwargs, k) = get(() -> defaultkw(k), kwargs, k)
# Default Template keyword values.
defaultkw(s::Symbol) = defaultkw(Val(s))
defaultkw(::Val{:authors}) = default_authors()
defaultkw(::Val{:dir}) = Pkg.devdir()
defaultkw(::Val{:disable_defaults}) = DataType[]
defaultkw(::Val{:host}) = "github.com"
defaultkw(::Val{:julia_version}) = default_version()
defaultkw(::Val{:plugins}) = Plugin[]
defaultkw(::Val{:user}) = default_user()
""" """
(::Template)(pkg::AbstractString) (::Template)(pkg::AbstractString)
@ -113,7 +97,7 @@ function (t::Template)(pkg::AbstractString)
try try
foreach((prehook, hook, posthook)) do h foreach((prehook, hook, posthook)) do h
@info "Running $(h)s" @info "Running $(h)s"
foreach(values(t.plugins)) do p foreach(t.plugins) do p
h(p, t, pkg_dir) h(p, t, pkg_dir)
end end
end end
@ -124,3 +108,26 @@ function (t::Template)(pkg::AbstractString)
@info "New package is at $pkg_dir" @info "New package is at $pkg_dir"
end end
# Does the template have a plugin that satisfies some predicate?
hasplugin(t::Template, f::Function) = any(f, t.plugins)
hasplugin(t::Template, ::Type{T}) where T <: Plugin = hasplugin(t, p -> p isa T)
# Get a plugin by type.
function getplugin(t::Template, ::Type{T}) where T <: Plugin
i = findfirst(p -> p isa T, t.plugins)
i === nothing ? nothing : t.plugins[i]
end
# Get a keyword, or compute some default value.
getkw(kwargs, k) = get(() -> defaultkw(k), kwargs, k)
# Default Template keyword values.
defaultkw(s::Symbol) = defaultkw(Val(s))
defaultkw(::Val{:authors}) = default_authors()
defaultkw(::Val{:dir}) = Pkg.devdir()
defaultkw(::Val{:disable_defaults}) = DataType[]
defaultkw(::Val{:host}) = "github.com"
defaultkw(::Val{:julia_version}) = default_version()
defaultkw(::Val{:plugins}) = Plugin[]
defaultkw(::Val{:user}) = default_user()

View File

@ -39,7 +39,7 @@ const LICENSES_DIR = joinpath(TEMPLATES_DIR, "licenses")
License: License:
path: "$(joinpath(LICENSES_DIR, "MIT"))" path: "$(joinpath(LICENSES_DIR, "MIT"))"
destination: "LICENSE" destination: "LICENSE"
ProjectFile: ProjectFile
Readme: Readme:
file: "$(joinpath(TEMPLATES_DIR, "README.md"))" file: "$(joinpath(TEMPLATES_DIR, "README.md"))"
destination: "README.md" destination: "README.md"

View File

@ -28,7 +28,7 @@
@testset "plugins / disabled_defaults" begin @testset "plugins / disabled_defaults" begin
function test_plugins(plugins, expected, disabled=DataType[]) function test_plugins(plugins, expected, disabled=DataType[])
t = tpl(; plugins=plugins, disable_defaults=disabled) t = tpl(; plugins=plugins, disable_defaults=disabled)
@test issetequal(values(t.plugins), expected) @test issetequal(t.plugins, expected)
end end
defaults = PT.default_plugins() defaults = PT.default_plugins()