Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Quarto plugin #482

Open
wants to merge 17 commits into
base: master
Choose a base branch
from
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)

Check warning on line 8 in src/interactive.jl

View check run for this annotation

Codecov / codecov/patch

src/interactive.jl#L7-L8

Added lines #L7 - L8 were not covered by tests
t(pkg)
return t
end
Expand All @@ -17,7 +17,7 @@
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 @@
"$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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
"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 @@
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

Check warning on line 63 in src/plugins/coverage.jl

View check run for this annotation

Codecov / codecov/patch

src/plugins/coverage.jl#L63

Added line #L63 was not covered by tests
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
Loading