Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 3 additions & 4 deletions src/PkgTemplates.jl
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
@doc read(joinpath(dirname(@__DIR__), "README.md"), String)
module PkgTemplates
@doc read(joinpath(dirname(@__DIR__), "README.md"), String) module PkgTemplates

using Base: active_project, contractuser

Expand All @@ -15,8 +14,7 @@ using Parameters: @with_kw_noshow

using Mocking

export
Template,
export Template,
AppVeyor,
BlueStyleBadge,
CirrusCI,
Expand All @@ -40,6 +38,7 @@ export
PkgBenchmark,
PkgEvalBadge,
ProjectFile,
Quarto,
Readme,
RegisterAction,
Secret,
Expand Down
4 changes: 2 additions & 2 deletions src/deprecated.jl
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
@deprecate generate(t::Template, pkg::AbstractString) t(pkg)
@deprecate generate(pkg::AbstractString, t::Template) t(pkg)
@deprecate interactive_template() Template(; interactive=true)
@deprecate generate_interactive(pkg::AbstractString) Template(; interactive=true)(pkg)
@deprecate interactive_template() Template(; interactive = true)
@deprecate generate_interactive(pkg::AbstractString) Template(; interactive = true)(pkg)
@deprecate GitHubPages(; kwargs...) Documenter{TravisCI}(; kwargs...)
@deprecate GitLabPages(; kwargs...) Documenter{GitLabCI}(; kwargs...)
37 changes: 21 additions & 16 deletions src/interactive.jl
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@
Shortcut for `Template(; interactive=true)(pkg)`.
If no package name is supplied, you will be prompted for one.
"""
function generate(pkg::AbstractString=prompt(Template, String, :pkg))
t = Template(; interactive=true)
function generate(pkg::AbstractString = prompt(Template, String, :pkg))
t = Template(; interactive = true)
t(pkg)
return t
end
Expand All @@ -17,7 +17,7 @@ Interactively create a plugin of type `T`. Implement this method and ignore othe
related functions only if you want completely custom behaviour.
"""
function interactive(T::Type)
pairs = Vector{Pair{Symbol, Type}}(interactive_pairs(T))
pairs = Vector{Pair{Symbol,Type}}(interactive_pairs(T))

# There must be at least 2 MultiSelectMenu options.
# If there are none, return immediately.
Expand All @@ -34,13 +34,13 @@ function interactive(T::Type)
"$k"
end
end
menu = MultiSelectMenu(opts; pagesize=length(pairs))
menu = MultiSelectMenu(opts; pagesize = length(pairs))
customize = sort!(collect(request(menu)))

# If the "None" option was selected, don't customize anything.
just_one && lastindex(pairs) in customize && return T()

kwargs = Dict{Symbol, Any}()
kwargs = Dict{Symbol,Any}()
foreach(pairs[customize]) do (name, F)
kwargs[name] = prompt(T, F, name)
end
Expand All @@ -64,7 +64,7 @@ function pretty_message(s::AbstractString)
r"Array{(.*?),1}" => s"Vector{\1}",
r"Union{Nothing, (.*?)}" => s"Union{\1, Nothing}",
]
return reduce((s, p) -> replace(s, p), replacements; init=s)
return reduce((s, p) -> replace(s, p), replacements; init = s)
end

"""
Expand All @@ -73,12 +73,12 @@ end
Provide some extra tips to users on how to structure their input for the type `T`,
for example if multiple delimited values are expected.
"""
input_tips(::Type{Vector{T}}) where T = [input_tips(T)..., "comma-delimited"]
input_tips(::Type{Union{T, Nothing}}) where T = [input_tips(T)..., input_tips(Nothing)...]
input_tips(::Type{Vector{T}}) where {T} = [input_tips(T)..., "comma-delimited"]
input_tips(::Type{Union{T,Nothing}}) where {T} = [input_tips(T)..., input_tips(Nothing)...]
input_tips(::Type{Nothing}) = ["'nothing' for nothing"]
input_tips(::Type{Secret}) = ["name only"]
# Show expected input type as a tip if it's anything other than `String`
input_tips(::Type{T}) where T = String[string(T)]
input_tips(::Type{T}) where {T} = String[string(T)]
input_tips(::Type{String}) = String[]
input_tips(::Type{<:Signed}) = ["Int"] # Specific Int type likely not important

Expand All @@ -91,19 +91,23 @@ A default implementation of `T(s)` exists.
convert_input(::Type, T::Type{<:Real}, s::AbstractString) = parse(T, s)
convert_input(::Type, T::Type, s::AbstractString) = T(s)

function convert_input(P::Type, ::Type{Union{T, Nothing}}, s::AbstractString) where T
function convert_input(P::Type, ::Type{Union{T,Nothing}}, s::AbstractString) where {T}
# This is kind of sketchy because technically, there might be some other input
# whose value we want to instantiate with the string "nothing",
# but I think that would be a pretty rare occurrence.
# If that really happens, they can just override this method.
return s == "nothing" ? nothing : convert_input(P, T, s)
end

function convert_input(P::Type, ::Type{Union{T, Symbol, Nothing}}, s::AbstractString) where T
function convert_input(
P::Type,
::Type{Union{T,Symbol,Nothing}},
s::AbstractString,
) where {T}
# Assume inputs starting with ':' char are intended as Symbols, if a plugin accept symbols.
# i.e. assume the set of valid Symbols the plugin expects can be spelt starting with ':'.
return if startswith(s, ":")
Symbol(chop(s, head=1, tail=0)) # remove ':'
Symbol(chop(s, head = 1, tail = 0)) # remove ':'
else
convert_input(P, Union{T,Nothing}, s)
end
Expand Down Expand Up @@ -140,7 +144,7 @@ Implement this method to customize particular fields of particular types.
prompt(P::Type, T::Type, name::Symbol) = prompt(P, T, Val(name))

# The trailing `nothing` is a hack for `fallback_prompt` to use, ignore it.
function prompt(P::Type, ::Type{T}, ::Val{name}, ::Nothing=nothing) where {T, name}
function prompt(P::Type, ::Type{T}, ::Val{name}, ::Nothing = nothing) where {T,name}
default = defaultkw(P, name)
tips = join([input_tips(T); "default: $(input_string(default))"], ", ")
input = Base.prompt(pretty_message("Enter value for '$name' ($tips)"))
Expand Down Expand Up @@ -170,8 +174,9 @@ function prompt(P::Type, ::Type{T}, ::Val{name}, ::Nothing=nothing) where {T, na
end

# Compute all the concrete subtypes of T.
concretes_rec(T::Type) = isabstracttype(T) ? vcat(map(concretes_rec, subtypes(T))...) : Any[T]
concretes(T::Type) = sort!(concretes_rec(T); by=nameof)
concretes_rec(T::Type) =
isabstracttype(T) ? vcat(map(concretes_rec, subtypes(T))...) : Any[T]
concretes(T::Type) = sort!(concretes_rec(T); by = nameof)

# Compute name => type pairs for T's interactive options.
function interactive_pairs(T::Type)
Expand All @@ -181,7 +186,7 @@ function interactive_pairs(T::Type)
prepend!(pairs, reverse(customizable(T)))
uniqueby!(first, pairs)
filter!(p -> last(p) !== NotCustomizable, pairs)
sort!(pairs; by=first)
sort!(pairs; by = first)

return pairs
end
Expand Down
24 changes: 15 additions & 9 deletions src/plugin.jl
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
const DEFAULT_PRIORITY = 1000
const DEFAULT_TEMPLATE_DIR = Ref{String}(joinpath(dirname(dirname(pathof(PkgTemplates))), "templates"))
const DEFAULT_TEMPLATE_DIR =
Ref{String}(joinpath(dirname(dirname(pathof(PkgTemplates))), "templates"))

"""
@plugin struct ... end
Expand Down Expand Up @@ -64,7 +65,11 @@ macro plugin(ex::Expr)

msg = "Run `using PkgTemplates: @with_kw_noshow` before using this macro"
@assert isdefined(__module__, Symbol("@with_kw_noshow")) msg
block = :(begin @with_kw_noshow $ex end)
block = :(
begin
@with_kw_noshow $ex
end
)

foreach(filter(arg -> arg isa Expr, ex.args[3].args)) do field
@assert field.head === :(=) "Field must have a default value"
Expand All @@ -77,7 +82,7 @@ macro plugin(ex::Expr)
return esc(block)
end

function Base.:(==)(a::T, b::T) where T <: Plugin
function Base.:(==)(a::T, b::T) where {T<:Plugin}
return all(n -> getfield(a, n) == getfield(b, n), fieldnames(T))
end

Expand Down Expand Up @@ -122,7 +127,7 @@ but you can always call it yourself as part of your [`hook`](@ref) implementatio

By default, an empty `Dict` is returned.
"""
view(::Plugin, ::Template, ::AbstractString) = Dict{String, Any}()
view(::Plugin, ::Template, ::AbstractString) = Dict{String,Any}()

"""
user_view(::Plugin, ::Template, pkg::AbstractString) -> Dict{String, Any}
Expand All @@ -132,7 +137,7 @@ The same as [`view`](@ref), but for use by package *users* for extension.
Values returned by this function will override those from [`view`](@ref)
when the keys are the same.
"""
user_view(::Plugin, ::Template, ::AbstractString) = Dict{String, Any}()
user_view(::Plugin, ::Template, ::AbstractString) = Dict{String,Any}()

"""
combined_view(::Plugin, ::Template, pkg::AbstractString) -> Dict{String, Any}
Expand Down Expand Up @@ -285,7 +290,7 @@ At this point, both the [`prehook`](@ref)s and [`hook`](@ref)s have run.
"""
posthook(::Plugin, ::Template, ::AbstractString) = nothing

function validate(p::T, ::Template) where T <: FilePlugin
function validate(p::T, ::Template) where {T<:FilePlugin}
src = source(p)
src === nothing && return
isfile(src) || throw(ArgumentError("$(nameof(T)): The file $src does not exist"))
Expand Down Expand Up @@ -322,7 +327,7 @@ Render a template file with the data in `view`.
`tags` should be a tuple of two strings, which are the opening and closing delimiters,
or `nothing` to use the default delimiters.
"""
function render_file(file::AbstractString, view::Dict{<:AbstractString}, tags=nothing)
function render_file(file::AbstractString, view::Dict{<:AbstractString}, tags = nothing)
return render_text(read(file, String), view, tags)
end

Expand All @@ -333,8 +338,8 @@ Render some text with the data in `view`.
`tags` should be a tuple of two strings, which are the opening and closing delimiters,
or `nothing` to use the default delimiters.
"""
function render_text(text::AbstractString, view::Dict{<:AbstractString}, tags=nothing)
return tags === nothing ? render(text, view) : render(text, view; tags=tags)
function render_text(text::AbstractString, view::Dict{<:AbstractString}, tags = nothing)
return tags === nothing ? render(text, view) : render(text, view; tags = tags)
end

"""
Expand Down Expand Up @@ -377,3 +382,4 @@ include(joinpath("plugins", "register.jl"))
include(joinpath("plugins", "dependabot.jl"))
include(joinpath("plugins", "formatter.jl"))
include(joinpath("plugins", "pkgbenchmark.jl"))
include(joinpath("plugins", "quarto.jl"))
2 changes: 1 addition & 1 deletion src/plugins/badges.jl
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ function badges(::PkgEvalBadge)
return Badge(
"PkgEval",
"https://JuliaCI.github.io/NanosoldierReports/pkgeval_badges/{{{PKG1}}}/{{{PKG}}}.svg",
"https://JuliaCI.github.io/NanosoldierReports/pkgeval_badges/{{{PKG1}}}/{{{PKG}}}.html"
"https://JuliaCI.github.io/NanosoldierReports/pkgeval_badges/{{{PKG1}}}/{{{PKG}}}.html",
)
end

Expand Down
10 changes: 6 additions & 4 deletions src/plugins/ci.jl
Original file line number Diff line number Diff line change
Expand Up @@ -73,15 +73,17 @@ function view(p::GitHubActions, t::Template, pkg::AbstractString)
p.osx && push!(os, "macOS-latest")
p.windows && push!(os, "windows-latest")
arch = filter(a -> getfield(p, Symbol(a)), ["x64", "x86"])
excludes = Dict{String, String}[]
excludes = Dict{String,String}[]
p.osx && p.x86 && push!(excludes, Dict("E_OS" => "macOS-latest", "E_ARCH" => "x86"))

v = Dict(
"ARCH" => arch,
"EXCLUDES" => excludes,
"HAS_CODECOV" => p.coverage && hasplugin(t, Codecov),
"HAS_COVERALLS" => p.coverage && hasplugin(t, Coveralls),
"HAS_DOCUMENTER" => hasplugin(t, Documenter{GitHubActions}),
"HAS_DOCUMENTER" =>
hasplugin(t, Documenter{GitHubActions}) && !hasplugin(t, Quarto),
"HAS_QUARTO" => hasplugin(t, Quarto),
"HAS_EXCLUDES" => !isempty(excludes),
"OS" => os,
"PKG" => pkg,
Expand Down Expand Up @@ -149,7 +151,7 @@ function view(p::TravisCI, t::Template, pkg::AbstractString)
versions = collect_versions(t, p.extra_versions)
allow_failures = filter(in(versions), ALLOWED_FAILURES)

excludes = Dict{String, String}[]
excludes = Dict{String,String}[]
p.x86 && p.osx && push!(excludes, Dict("E_OS" => "osx", "E_ARCH" => "x86"))
if p.arm64
p.osx && push!(excludes, Dict("E_OS" => "osx", "E_ARCH" => "arm64"))
Expand Down Expand Up @@ -416,7 +418,7 @@ function collect_versions(t::Template, versions::Vector)
return sort(unique(vs))
end

const AllCI = Union{AppVeyor, GitHubActions, TravisCI, CirrusCI, GitLabCI, DroneCI}
const AllCI = Union{AppVeyor,GitHubActions,TravisCI,CirrusCI,GitLabCI,DroneCI}

"""
is_ci(::Plugin) -> Bool
Expand Down
12 changes: 9 additions & 3 deletions src/plugins/codeowners.jl
Original file line number Diff line number Diff line change
Expand Up @@ -29,10 +29,16 @@ end

function PkgTemplates.validate(p::CodeOwners, ::Template)
for (pattern, subowners) in p.owners
contains(pattern, r"\s") && throw(ArgumentError(("Pattern ($pattern) must not contain whitespace")))
contains(pattern, r"\s") &&
throw(ArgumentError(("Pattern ($pattern) must not contain whitespace")))
for subowner in subowners
contains(subowner, r"\s") && throw(ArgumentError("Owner name ($subowner) must not contain whitespace"))
'@' ∈ subowner || throw(ArgumentError("Owner name ($subowner) must be `@user` or `[email protected]`"))
contains(subowner, r"\s") &&
throw(ArgumentError("Owner name ($subowner) must not contain whitespace"))
'@' ∈ subowner || throw(
ArgumentError(
"Owner name ($subowner) must be `@user` or `[email protected]`",
),
)
end
end
end
12 changes: 6 additions & 6 deletions src/plugins/coverage.jl
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ Sets up code coverage submission from CI to [Codecov](https://codecov.io).
or `nothing` to create no file.
"""
@plugin struct Codecov <: FilePlugin
file::Union{String, Nothing} = nothing
file::Union{String,Nothing} = nothing
end

source(p::Codecov) = p.file
Expand All @@ -32,7 +32,7 @@ Sets up code coverage submission from CI to [Coveralls](https://coveralls.io).
or `nothing` to create no file.
"""
@plugin struct Coveralls <: FilePlugin
file::Union{String, Nothing} = nothing
file::Union{String,Nothing} = nothing
end

source(p::Coveralls) = p.file
Expand All @@ -44,8 +44,8 @@ badges(::Coveralls) = Badge(
"https://coveralls.io/github/{{{USER}}}/{{{PKG}}}.jl?branch={{{BRANCH}}}",
)

gitignore(::Union{Codecov, Coveralls}) = COVERAGE_GITIGNORE
view(::Union{Codecov, Coveralls}, t::Template, pkg::AbstractString) = Dict(
gitignore(::Union{Codecov,Coveralls}) = COVERAGE_GITIGNORE
view(::Union{Codecov,Coveralls}, t::Template, pkg::AbstractString) = Dict(
"BRANCH" => something(default_branch(t), DEFAULT_DEFAULT_BRANCH),
"PKG" => pkg,
"USER" => t.user,
Expand All @@ -58,6 +58,6 @@ 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`.
"""
is_coverage(::Plugin) = false
is_coverage(::Union{Codecov, Coveralls}) = true
is_coverage(::Union{Codecov,Coveralls}) = true

needs_username(::Union{Codecov, Coveralls}) = true
needs_username(::Union{Codecov,Coveralls}) = true
2 changes: 1 addition & 1 deletion src/plugins/develop.jl
Original file line number Diff line number Diff line change
Expand Up @@ -9,5 +9,5 @@ for more details.
struct Develop <: Plugin end

function posthook(::Develop, ::Template, pkg_dir::AbstractString)
Pkg.develop(PackageSpec(; path=pkg_dir))
Pkg.develop(PackageSpec(; path = pkg_dir))
end
Loading