diff --git a/src/PkgTemplates.jl b/src/PkgTemplates.jl index 364c96da..50ae5437 100644 --- a/src/PkgTemplates.jl +++ b/src/PkgTemplates.jl @@ -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 @@ -15,8 +14,7 @@ using Parameters: @with_kw_noshow using Mocking -export - Template, +export Template, AppVeyor, BlueStyleBadge, CirrusCI, @@ -40,6 +38,7 @@ export PkgBenchmark, PkgEvalBadge, ProjectFile, + Quarto, Readme, RegisterAction, Secret, diff --git a/src/deprecated.jl b/src/deprecated.jl index 9c640256..217c0a02 100644 --- a/src/deprecated.jl +++ b/src/deprecated.jl @@ -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...) diff --git a/src/interactive.jl b/src/interactive.jl index 7f003a8f..60d07d54 100644 --- a/src/interactive.jl +++ b/src/interactive.jl @@ -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 @@ -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. @@ -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 @@ -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 """ @@ -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 @@ -91,7 +91,7 @@ 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. @@ -99,11 +99,15 @@ function convert_input(P::Type, ::Type{Union{T, Nothing}}, s::AbstractString) wh 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 @@ -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)")) @@ -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) @@ -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 diff --git a/src/plugin.jl b/src/plugin.jl index 6f7064be..9e1c2688 100644 --- a/src/plugin.jl +++ b/src/plugin.jl @@ -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 @@ -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" @@ -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 @@ -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} @@ -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} @@ -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")) @@ -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 @@ -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 """ @@ -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")) diff --git a/src/plugins/badges.jl b/src/plugins/badges.jl index 075a24fe..562e1118 100644 --- a/src/plugins/badges.jl +++ b/src/plugins/badges.jl @@ -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 diff --git a/src/plugins/ci.jl b/src/plugins/ci.jl index 129d98b9..ac85bb87 100644 --- a/src/plugins/ci.jl +++ b/src/plugins/ci.jl @@ -73,7 +73,7 @@ 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( @@ -81,7 +81,9 @@ function view(p::GitHubActions, t::Template, pkg::AbstractString) "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, @@ -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")) @@ -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 diff --git a/src/plugins/codeowners.jl b/src/plugins/codeowners.jl index 44244f8d..4eb8b002 100644 --- a/src/plugins/codeowners.jl +++ b/src/plugins/codeowners.jl @@ -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@domain.com`")) + contains(subowner, r"\s") && + throw(ArgumentError("Owner name ($subowner) must not contain whitespace")) + '@' ∈ subowner || throw( + ArgumentError( + "Owner name ($subowner) must be `@user` or `email@domain.com`", + ), + ) end end end diff --git a/src/plugins/coverage.jl b/src/plugins/coverage.jl index ae9fb5ec..c0460796 100644 --- a/src/plugins/coverage.jl +++ b/src/plugins/coverage.jl @@ -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 @@ -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 @@ -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, @@ -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 diff --git a/src/plugins/develop.jl b/src/plugins/develop.jl index 835f2717..eb1628cb 100644 --- a/src/plugins/develop.jl +++ b/src/plugins/develop.jl @@ -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 diff --git a/src/plugins/documenter.jl b/src/plugins/documenter.jl index b9106354..2b3b32b5 100644 --- a/src/plugins/documenter.jl +++ b/src/plugins/documenter.jl @@ -1,11 +1,9 @@ -const DOCUMENTER_DEP = PackageSpec(; - name="Documenter", - uuid="e30172f5-a6a5-5a46-863b-614d45cd2de4", -) +const DOCUMENTER_DEP = + PackageSpec(; name = "Documenter", uuid = "e30172f5-a6a5-5a46-863b-614d45cd2de4") struct NoDeploy end -const YesDeploy = Union{TravisCI, GitHubActions, GitLabCI} -const GitHubPagesStyle = Union{TravisCI, GitHubActions} +const YesDeploy = Union{TravisCI,GitHubActions,GitLabCI} +const GitHubPagesStyle = Union{TravisCI,GitHubActions} """ Logo(; light=nothing, dark=nothing) @@ -17,8 +15,8 @@ Logo information for documentation. - `dark::AbstractString`: Path to a logo file for the dark theme. """ @with_kw_noshow struct Logo - light::Union{String, Nothing} = nothing - dark::Union{String, Nothing} = nothing + light::Union{String,Nothing} = nothing + dark::Union{String,Nothing} = nothing end """ @@ -76,23 +74,23 @@ struct Documenter{T} <: Plugin assets::Vector{String} logo::Logo makedocs_kwargs::Dict{Symbol} - canonical_url::Union{Function, Nothing} + canonical_url::Union{Function,Nothing} make_jl::String index_md::String - devbranch::Union{String, Nothing} - edit_link::Union{String, Symbol, Nothing} + devbranch::Union{String,Nothing} + edit_link::Union{String,Symbol,Nothing} end # Can't use @plugin because we're implementing our own no-arguments constructor. function Documenter{T}(; - assets::Vector{<:AbstractString}=String[], - logo::Logo=Logo(), - makedocs_kwargs::Dict{Symbol}=Dict{Symbol, Any}(), - canonical_url::Union{Function, Nothing}=make_canonical(T), - make_jl::AbstractString=default_file("docs", "make.jl"), - index_md::AbstractString=default_file("docs", "src", "index.md"), - devbranch::Union{AbstractString, Nothing}=nothing, - edit_link::Union{AbstractString, Symbol, Nothing}=:devbranch, + assets::Vector{<:AbstractString} = String[], + logo::Logo = Logo(), + makedocs_kwargs::Dict{Symbol} = Dict{Symbol,Any}(), + canonical_url::Union{Function,Nothing} = make_canonical(T), + make_jl::AbstractString = default_file("docs", "make.jl"), + index_md::AbstractString = default_file("docs", "src", "index.md"), + devbranch::Union{AbstractString,Nothing} = nothing, + edit_link::Union{AbstractString,Symbol,Nothing} = :devbranch, ) where {T} return Documenter{T}( assets, @@ -146,12 +144,14 @@ function view(p::Documenter, t::Template, pkg::AbstractString) "AUTHORS" => join(t.authors, ", "), "CANONICAL" => p.canonical_url === nothing ? nothing : p.canonical_url(t, pkg), "HAS_ASSETS" => !isempty(p.assets), - "MAKEDOCS_KWARGS" => map(((k, v),) -> k => repr(v), sort(collect(p.makedocs_kwargs), by=first)), + "MAKEDOCS_KWARGS" => + map(((k, v),) -> k => repr(v), sort(collect(p.makedocs_kwargs), by = first)), "PKG" => pkg, "REPO" => "$(t.host)/$(t.user)/$pkg.jl", "USER" => t.user, "BRANCH" => devbranch, - "EDIT_LINK" => p.edit_link == :devbranch ? _quoted(devbranch) : _quoted(p.edit_link), + "EDIT_LINK" => + p.edit_link == :devbranch ? _quoted(devbranch) : _quoted(p.edit_link), ) end @@ -160,7 +160,7 @@ _quoted(s::AbstractString) = string('"', s, '"') _quoted(s::Symbol) = repr(s) function view(p::Documenter{<:GitHubPagesStyle}, t::Template, pkg::AbstractString) - base = invoke(view, Tuple{Documenter, Template, AbstractString}, p, t, pkg) + base = invoke(view, Tuple{Documenter,Template,AbstractString}, p, t, pkg) return merge(base, Dict("HAS_DEPLOY" => true)) end @@ -176,8 +176,8 @@ function validate(p::Documenter, ::Template) end end -function validate(p::Documenter{T}, t::Template) where T <: YesDeploy - invoke(validate, Tuple{Documenter, Template}, p, t) +function validate(p::Documenter{T}, t::Template) where {T<:YesDeploy} + invoke(validate, Tuple{Documenter,Template}, p, t) if !hasplugin(t, T) name = nameof(T) s = "Documenter: The $name plugin must be included for docs deployment to be set up" @@ -211,7 +211,7 @@ function hook(p::Documenter, t::Template, pkg_dir::AbstractString) # Create the documentation project. with_project(docs_dir) do Pkg.add(DOCUMENTER_DEP) - cd(() -> Pkg.develop(PackageSpec(; path="..")), docs_dir) + cd(() -> Pkg.develop(PackageSpec(; path = "..")), docs_dir) end end @@ -230,7 +230,7 @@ end function interactive(::Type{Documenter}) styles = [NoDeploy, TravisCI, GitLabCI, GitHubActions] - menu = RadioMenu(map(string, styles); pagesize=length(styles)) + menu = RadioMenu(map(string, styles); pagesize = length(styles)) println("Documenter deploy style:") idx = request(menu) return interactive(Documenter{styles[idx]}) @@ -239,5 +239,5 @@ end function prompt(::Type{<:Documenter}, ::Type{Logo}, ::Val{:logo}) light = Base.prompt("Enter value for 'logo.light' (default: nothing)") dark = Base.prompt("Enter value for 'logo.dark' (default: nothing)") - return Logo(; light=light, dark=dark) + return Logo(; light = light, dark = dark) end diff --git a/src/plugins/formatter.jl b/src/plugins/formatter.jl index afaace9f..ce5fcaa5 100644 --- a/src/plugins/formatter.jl +++ b/src/plugins/formatter.jl @@ -19,7 +19,11 @@ end function validate(p::Formatter, t::Template) if p.style ∉ ("nostyle", "blue", "sciml", "yas") - throw(ArgumentError("""JuliaFormatter style must be either "nostyle", "blue", "sciml" or "yas".""")) + throw( + ArgumentError( + """JuliaFormatter style must be either "nostyle", "blue", "sciml" or "yas".""", + ), + ) end end @@ -38,7 +42,7 @@ end function prompt(::Type{Formatter}, ::Type{String}, ::Val{:style}) options = ["nostyle", "blue", "sciml", "yas"] - menu = RadioMenu(options; pagesize=length(options)) + menu = RadioMenu(options; pagesize = length(options)) println("Select a JuliaFormatter style:") idx = request(menu) return options[idx] diff --git a/src/plugins/git.jl b/src/plugins/git.jl index 69d92a65..7007f183 100644 --- a/src/plugins/git.jl +++ b/src/plugins/git.jl @@ -30,8 +30,8 @@ Creates a Git repository and a `.gitignore` file. """ @plugin struct Git <: Plugin ignore::Vector{String} = String[] - name::Union{String, Nothing} = nothing - email::Union{String, Nothing} = nothing + name::Union{String,Nothing} = nothing + email::Union{String,Nothing} = nothing branch::String = @mock(LibGit2.getconfig("init.defaultBranch", DEFAULT_DEFAULT_BRANCH)) ssh::Bool = false jl::Bool = true @@ -51,7 +51,9 @@ function validate(p::Git, t::Template) foreach((:name, :email)) do k user_k = "user.$k" if getproperty(p, k) === nothing && isempty(@mock LibGit2.getconfig(user_k, "")) - throw(ArgumentError("Git: Global Git config is missing required value '$user_k'")) + throw( + ArgumentError("Git: Global Git config is missing required value '$user_k'"), + ) end end end @@ -118,7 +120,7 @@ end function commit(p::Git, repo::GitRepo, pkg_dir::AbstractString, msg::AbstractString) if p.gpgsign - run(pipeline(`git -C $pkg_dir commit -S --allow-empty -m $msg`; stdout=devnull)) + run(pipeline(`git -C $pkg_dir commit -S --allow-empty -m $msg`; stdout = devnull)) else LibGit2.commit(repo, msg) end @@ -128,7 +130,7 @@ needs_username(::Git) = true function git_is_installed() return try - run(pipeline(`git --version`; stdout=devnull)) + run(pipeline(`git --version`; stdout = devnull)) true catch false diff --git a/src/plugins/license.jl b/src/plugins/license.jl index fd83e166..74cf423f 100644 --- a/src/plugins/license.jl +++ b/src/plugins/license.jl @@ -18,9 +18,9 @@ struct License <: FilePlugin end function License(; - name::AbstractString="MIT", - path::Union{AbstractString, Nothing}=nothing, - destination::AbstractString="LICENSE", + name::AbstractString = "MIT", + path::Union{AbstractString,Nothing} = nothing, + destination::AbstractString = "LICENSE", ) if path === nothing path = default_file("licenses", name) @@ -35,17 +35,15 @@ defaultkw(::Type{License}, ::Val{:destination}) = "LICENSE" source(p::License) = p.path destination(p::License) = p.destination -view(::License, t::Template, ::AbstractString) = Dict( - "AUTHORS" => join(t.authors, ", "), - "YEAR" => year(today()), -) +view(::License, t::Template, ::AbstractString) = + Dict("AUTHORS" => join(t.authors, ", "), "YEAR" => year(today())) function prompt(::Type{License}, ::Type, ::Val{:name}) options = readdir(default_file("licenses")) # Move MIT to the top. deleteat!(options, findfirst(==("MIT"), options)) pushfirst!(options, "MIT") - menu = RadioMenu(options; pagesize=length(options)) + menu = RadioMenu(options; pagesize = length(options)) println("Select a license:") idx = request(menu) return options[idx] diff --git a/src/plugins/project_file.jl b/src/plugins/project_file.jl index 63041414..fe3cf9ee 100644 --- a/src/plugins/project_file.jl +++ b/src/plugins/project_file.jl @@ -29,7 +29,10 @@ end function project_key_order(key::String) _project_key_order = ["name", "uuid", "keywords", "license", "desc", "deps", "compat"] - return something(findfirst(x -> x == key, _project_key_order), length(_project_key_order) + 1) + return something( + findfirst(x -> x == key, _project_key_order), + length(_project_key_order) + 1, + ) end write_project(path::AbstractString, dict) = diff --git a/src/plugins/quarto.jl b/src/plugins/quarto.jl new file mode 100644 index 00000000..3f783928 --- /dev/null +++ b/src/plugins/quarto.jl @@ -0,0 +1,102 @@ +""" + Quarto(; + index_qmd::String = default_file("quarto", "index.qmd") + readme_qmd::String = default_file("quarto", "README.qmd") + config::String = default_file("quarto", "_quarto.yml") + ) +""" +@plugin struct Quarto <: Plugin + index_qmd::String = default_file("quarto", "index.qmd") + readme_qmd::String = default_file("quarto", "README.qmd") + make_jl::String = default_file("quarto", "make.jl") + config::String = default_file("quarto", "_quarto.yml") +end + +isfixable(::Quarto, pkg_dir) = true + +""" + PkgTemplates.view(p::Quarto, t::Template, pkg::AbstractString) + +Overloads the `view` function for the Quarto plugin. The Quarto plugin inherits its view from the `Readme` and `Documenter` plugins. +""" +function PkgTemplates.view(p::Quarto, t::Template, pkg::AbstractString) + + v = Dict{AbstractString,Any}() + + # Inherit view from Readme plugin: + if PkgTemplates.hasplugin(t, Readme) + p_readme = t.plugins[findall(typeof.(t.plugins) .<: Readme)][1] + v = merge(v, combined_view(p_readme, t, pkg)) + end + + # Inherit view from Documenter plugin: + if PkgTemplates.hasplugin(t, Documenter) + p_doc = t.plugins[findall(typeof.(t.plugins) .<: Documenter)][1] + v = merge(v, combined_view(p_doc, t, pkg)) + end + + return v +end + +""" + PkgTemplates.validate(p::Quarto, t::Template) + +Overloads the `validate` function for the Quarto plugin. The method asserts that the `Documenter` plugin (if used) is pointing to the same `make.jl` template file as the `Quarto` plugin. +""" +function PkgTemplates.validate(p::Quarto, t::Template) + if PkgTemplates.hasplugin(t, Documenter) + # Overwrite make.jl file path (dirty solution) + doc_plugin = t.plugins[findall(typeof.(t.plugins) .<: Documenter)][1] + @assert doc_plugin.make_jl == p.make_jl "make.jl file path mismatch between Quarto and Documenter plugin. When using the Quarto plugin, make sure that the Documenter plugin points to $(p.make_jl), i.e. use `Documenter(make_jl=Quarto().make_jl)`" + end +end + +""" + PkgTemplates.hook(p::Quarto, t::Template, pkg_dir::AbstractString) + +Overloads the `hook` function for the Quarto plugin. The Quarto plugin does the following: + +1. It adds a `README.qmd` file and renders a `README.md` file from it (locally). +2. It adds an `index.qmd` file to the `docs/src/` folder (to be rendered remotely). +3. It generates a custom `make.jl` for using Documenter.jl with Quarto. +4. It adds a `_quarto.yml` file that configures Quarto for use with Documenter.jl. +""" +function PkgTemplates.hook(p::Quarto, t::Template, pkg_dir::AbstractString) + + pkg = pkg_name(pkg_dir) + docs_dir = joinpath(pkg_dir, "docs") + assets_dir = joinpath(docs_dir, "src", "assets") + ispath(assets_dir) || mkpath(assets_dir) + + # Readme file: + readme = render_file(p.readme_qmd, combined_view(p, t, pkg), tags(p)) + path = joinpath(pkg_dir, "README.qmd") + mkd_path = replace(path, ".qmd" => ".md") + if isfile(path) + path_fixed = replace(path, ".qmd" => "_fixed.qmd") + @warn "README file already exists at $path. Generating a fixed but empty version from template at $path_fixed. You will most likely just have to copy and paste the content from the existing README into the fixed version and then overwrite $path with $path_fixed." + gen_file(path_fixed, readme) + elseif isfile(mkd_path) + backup_path = replace(mkd_path, ".md" => "_backup.md") + run(`cp $mkd_path $backup_path`) + @warn "Existing `README.md` (markdown) file found and backed up as $backup_path. You may have to copy existing contents into the newly generated Quarto file at $path." + gen_file(path, readme) + else + gen_file(path, readme) + end + @info "Rendering README" + run(`quarto render $path`) + + # Index file: + index = render_file(p.index_qmd, combined_view(p, t, pkg), tags(p)) + gen_file(joinpath(docs_dir, "src", "index.qmd"), index) + + # Make.jl: + makejl = render_file(p.make_jl, combined_view(p, t, pkg), tags(p)) + gen_file(joinpath(docs_dir, "make.jl"), makejl) + run(`chmod u+x $(joinpath(docs_dir, "make.jl"))`) # turn into executable + + # Config file: + config = render_file(p.config, combined_view(p, t, pkg), tags(p)) + gen_file(joinpath(pkg_dir, "_quarto.yml"), config) +end diff --git a/src/plugins/readme.jl b/src/plugins/readme.jl index 35fa2f15..bfa00190 100644 --- a/src/plugins/readme.jl +++ b/src/plugins/readme.jl @@ -63,5 +63,5 @@ default_badge_order() = [ CirrusCI, Codecov, Coveralls, - subtypes(BadgePlugin)... + subtypes(BadgePlugin)..., ] diff --git a/src/plugins/tagbot.jl b/src/plugins/tagbot.jl index 190c3399..11734e1b 100644 --- a/src/plugins/tagbot.jl +++ b/src/plugins/tagbot.jl @@ -39,16 +39,16 @@ Adds GitHub release support via [TagBot](https://github.com/JuliaRegistries/TagB destination::String = "TagBot.yml" trigger::String = "JuliaTagBot" token::Secret = Secret("GITHUB_TOKEN") - ssh::Union{Secret, Nothing} = Secret("DOCUMENTER_KEY") - ssh_password::Union{Secret, Nothing} = nothing - changelog::Union{String, Nothing} = nothing - changelog_ignore::Union{Vector{String}, Nothing} = nothing - gpg::Union{Secret, Nothing} = nothing - gpg_password::Union{Secret, Nothing} = nothing - registry::Union{String, Nothing} = nothing - branches::Union{Bool, Nothing} = nothing - dispatch::Union{Bool, Nothing} = nothing - dispatch_delay::Union{Int, Nothing} = nothing + ssh::Union{Secret,Nothing} = Secret("DOCUMENTER_KEY") + ssh_password::Union{Secret,Nothing} = nothing + changelog::Union{String,Nothing} = nothing + changelog_ignore::Union{Vector{String},Nothing} = nothing + gpg::Union{Secret,Nothing} = nothing + gpg_password::Union{Secret,Nothing} = nothing + registry::Union{String,Nothing} = nothing + branches::Union{Bool,Nothing} = nothing + dispatch::Union{Bool,Nothing} = nothing + dispatch_delay::Union{Int,Nothing} = nothing end source(p::TagBot) = p.file diff --git a/src/plugins/tests.jl b/src/plugins/tests.jl index 0aa06eff..5f4bccaf 100644 --- a/src/plugins/tests.jl +++ b/src/plugins/tests.jl @@ -1,11 +1,11 @@ const TEST_UUID = "8dfed614-e22c-5e08-85e1-65c5234f0b40" -const TEST_DEP = PackageSpec(; name="Test", uuid=TEST_UUID) +const TEST_DEP = PackageSpec(; name = "Test", uuid = TEST_UUID) const AQUA_UUID = "4c88cf16-eb10-579e-8560-4a9242c79595" -const AQUA_DEP = PackageSpec(; name="Aqua", uuid=AQUA_UUID) +const AQUA_DEP = PackageSpec(; name = "Aqua", uuid = AQUA_UUID) const JET_UUID = "c3a54625-cd67-489e-a8e7-0a5a0ff4e31b" -const JET_DEP = PackageSpec(; name="JET", uuid=JET_UUID) +const JET_DEP = PackageSpec(; name = "JET", uuid = JET_UUID) """ Tests(; @@ -76,7 +76,9 @@ end function validate(p::Tests, t::Template) invoke(validate, Tuple{FilePlugin,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) ", "but a Julia version older than 1.2 ($(t.julia)) is supported by the template", ) @@ -127,7 +129,7 @@ function add_test_dependency(p::Tests, pkg_dir::AbstractString) # Add the dependency manually since there's no programmatic way to add to [extras]. path = joinpath(pkg_dir, "Project.toml") toml = TOML.parsefile(path) - + get!(toml, "extras", Dict())["Test"] = TEST_UUID if p.aqua get!(toml, "extras", Dict())["Aqua"] = AQUA_UUID @@ -135,7 +137,7 @@ function add_test_dependency(p::Tests, pkg_dir::AbstractString) if p.jet get!(toml, "extras", Dict())["JET"] = JET_UUID end - + targets = String[] if p.aqua push!(targets, "Aqua") @@ -145,7 +147,7 @@ function add_test_dependency(p::Tests, pkg_dir::AbstractString) end push!(targets, "Test") get!(toml, "targets", Dict())["test"] = targets - + write_project(path, toml) # Generate the manifest by updating the project. diff --git a/src/show.jl b/src/show.jl index 85f2b83e..95e3e591 100644 --- a/src/show.jl +++ b/src/show.jl @@ -7,14 +7,14 @@ function Base.show(io::IO, m::MIME"text/plain", t::Template) print(io, " plugins: None") else print(io, repeat(' ', 2), "plugins:") - foreach(sort(t.plugins; by=string)) do p + foreach(sort(t.plugins; by = string)) do p println(io) show(IOContext(io, :indent => 4), m, p) end end end -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) print(io, repeat(' ', indent), nameof(T)) ns = fieldnames(T) diff --git a/src/template.jl b/src/template.jl index f60f6dba..fed09936 100644 --- a/src/template.jl +++ b/src/template.jl @@ -22,7 +22,7 @@ function default_authors() end struct MissingUserException{T} <: Exception end -function Base.showerror(io::IO, ::MissingUserException{T}) where T +function Base.showerror(io::IO, ::MissingUserException{T}) where {T} s = """$(nameof(T)): Git hosting service username is required, set one with keyword `user=""`""" print(io, s) end @@ -80,7 +80,7 @@ struct Template user::String end -Template(; interactive::Bool=false, kwargs...) = Template(Val(interactive); kwargs...) +Template(; interactive::Bool = false, kwargs...) = Template(Val(interactive); kwargs...) Template(::Val{true}; kwargs...) = interactive(Template; kwargs...) function Template(::Val{false}; kwargs...) @@ -102,7 +102,7 @@ function Template(::Val{false}; kwargs...) !(typeof(p) in vcat(typeof.(plugins), disabled)) end append!(plugins, defaults) - plugins = Vector{Plugin}(sort(plugins; by=string)) + plugins = Vector{Plugin}(sort(plugins; by = string)) if isempty(user) foreach(plugins) do p @@ -135,12 +135,12 @@ function (t::Template)(pkg::AbstractString) try foreach((prehook, hook, posthook)) do h @info "Running $(nameof(h))s" - foreach(sort(t.plugins; by=p -> priority(p, h), rev=true)) do p + foreach(sort(t.plugins; by = p -> priority(p, h), rev = true)) do p h(p, t, pkg_dir) end end catch - rm(pkg_dir; recursive=true, force=true) + rm(pkg_dir; recursive = true, force = true) rethrow() end @@ -159,23 +159,23 @@ end function Base.:(==)(a::Template, b::Template) return a.authors == b.authors && - a.dir == b.dir && - a.host == b.host && - a.julia == b.julia && - a.user == b.user && - all(map(==, a.plugins, b.plugins)) + a.dir == b.dir && + a.host == b.host && + a.julia == b.julia && + a.user == b.user && + all(map(==, a.plugins, b.plugins)) 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) +hasplugin(t::Template, ::Type{T}) where {T<:Plugin} = hasplugin(t, p -> p isa T) """ getplugin(t::Template, ::Type{T<:Plugin}) -> Union{T, Nothing} Get the plugin of type `T` from the template `t`, if it's present. """ -function getplugin(t::Template, ::Type{T}) where T <: Plugin +function getplugin(t::Template, ::Type{T}) where {T<:Plugin} i = findfirst(p -> p isa T, t.plugins) return i === nothing ? nothing : t.plugins[i] end @@ -184,7 +184,7 @@ end getkw!(kwargs, k) = pop!(kwargs, k, defaultkw(Template, k)) # Default Template keyword values. -defaultkw(::Type{T}, s::Symbol) where T = defaultkw(T, Val(s)) +defaultkw(::Type{T}, s::Symbol) where {T} = defaultkw(T, Val(s)) defaultkw(::Type{Template}, ::Val{:authors}) = default_authors() defaultkw(::Type{Template}, ::Val{:dir}) = contractuser(Pkg.devdir()) defaultkw(::Type{Template}, ::Val{:host}) = "github.com" @@ -194,7 +194,7 @@ defaultkw(::Type{Template}, ::Val{:user}) = default_user() function interactive(::Type{Template}; kwargs...) # If the user supplied any keywords themselves, don't prompt for them. - kwargs = Dict{Symbol, Any}(kwargs) + kwargs = Dict{Symbol,Any}(kwargs) options = [:user, :authors, :dir, :host, :julia, :plugins] customizable = setdiff(options, keys(kwargs)) @@ -205,8 +205,8 @@ function interactive(::Type{Template}; kwargs...) try println("Template keywords to customize:") - opts = map(k -> "$k ($(repr(defaultkw(Template, k))))" , customizable) - menu = MultiSelectMenu(opts; pagesize=length(customizable)) + opts = map(k -> "$k ($(repr(defaultkw(Template, k))))", customizable) + menu = MultiSelectMenu(opts; pagesize = length(customizable)) customize = customizable[sort!(collect(request(menu)))] just_one && last(customizable) in customize && return Template(; kwargs...) @@ -245,7 +245,7 @@ end function prompt(::Type{Template}, ::Type, ::Val{:host}) hosts = ["github.com", "gitlab.com", "bitbucket.org", "Other"] - menu = RadioMenu(hosts; pagesize=length(hosts)) + menu = RadioMenu(hosts; pagesize = length(hosts)) println("Select Git repository hosting service:") idx = request(menu) return if idx == lastindex(hosts) @@ -258,7 +258,7 @@ end function prompt(::Type{Template}, ::Type, ::Val{:julia}) versions = map(format_version, VersionNumber.(1, 0:VERSION.minor)) push!(versions, "Other") - menu = RadioMenu(map(string, versions); pagesize=length(versions)) + menu = RadioMenu(map(string, versions); pagesize = length(versions)) println("Select minimum Julia version:") idx = request(menu) return if idx == lastindex(versions) @@ -276,7 +276,7 @@ function prompt(::Type{Template}, ::Type, ::Val{:plugins}) ndefaults = length(defaults) # Put the defaults first. options = unique!([defaults; concretes(Plugin)]) - menu = MultiSelectMenu(map(T -> string(nameof(T)), options); pagesize=length(options)) + menu = MultiSelectMenu(map(T -> string(nameof(T)), options); pagesize = length(options)) println("Select plugins:") # Pre-select the default plugins and move the cursor to the first non-default. # To make this better, we need julia#30043. diff --git a/templates/github/workflows/CI.yml b/templates/github/workflows/CI.yml index 351ae847..d448d9ec 100644 --- a/templates/github/workflows/CI.yml +++ b/templates/github/workflows/CI.yml @@ -101,3 +101,50 @@ jobs: DocMeta.setdocmeta!(<<&PKG>>, :DocTestSetup, :(using <<&PKG>>); recursive=true) doctest(<<&PKG>>) <> + <<#HAS_QUARTO>> + docs: + name: Documentation + runs-on: ubuntu-latest + permissions: + actions: write # needed to allow julia-actions/cache to proactively delete old caches that it has created + contents: write + statuses: write + steps: + - uses: actions/checkout@v4 + - uses: quarto-dev/quarto-actions/setup@v2 + with: + version: 1.5.54 + - uses: julia-actions/setup-julia@latest + with: + version: '1' + - uses: julia-actions/cache@v2 + - name: Cache Quarto + id: cache-quarto + uses: actions/cache@v3 + env: + cache-name: cache-quarto + with: + path: _freeze + key: ${{ runner.os }}-${{ env.cache-name }}-${{ hashFiles('*.qmd') }} + restore-keys: | + ${{ runner.os }}-${{ env.cache-name }}- + - name: Configure doc environment + shell: julia --project=docs --color=yes {0} + run: | + using Pkg + Pkg.develop(PackageSpec(path=pwd())) + Pkg.instantiate() + - uses: julia-actions/julia-buildpkg@v1 + - name: "Documenter rendering (including Quarto)" + run: "docs/make.jl --quarto" + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + DOCUMENTER_KEY: ${{ secrets.DOCUMENTER_KEY }} + - name: Run doctests + shell: julia --project=docs --color=yes {0} + run: | + using Documenter: DocMeta, doctest + using <<&PKG>> + DocMeta.setdocmeta!(<<&PKG>>, :DocTestSetup, :(using <<&PKG>>); recursive=true) + doctest(<<&PKG>>) + <> diff --git a/templates/quarto/README.qmd b/templates/quarto/README.qmd new file mode 100644 index 00000000..63b0118f --- /dev/null +++ b/templates/quarto/README.qmd @@ -0,0 +1,25 @@ +--- +format: + commonmark: + variant: -raw_html+tex_math_dollars + wrap: none + mermaid-format: png +crossref: + fig-prefix: Figure + tbl-prefix: Table +output: asis +--- + +# {{{PKG}}}{{#HAS_INLINE_BADGES}} {{#BADGES}}{{{.}}} {{/BADGES}}{{/HAS_INLINE_BADGES}} +{{^HAS_INLINE_BADGES}} + +{{#BADGES}} +{{{.}}} +{{/BADGES}} +{{/HAS_INLINE_BADGES}} +{{#HAS_CITATION}} + +## Citing + +See [`CITATION.bib`](CITATION.bib) for the relevant reference(s). +{{/HAS_CITATION}} \ No newline at end of file diff --git a/templates/quarto/_quarto.yml b/templates/quarto/_quarto.yml new file mode 100644 index 00000000..8985ce9d --- /dev/null +++ b/templates/quarto/_quarto.yml @@ -0,0 +1,16 @@ +project: + title: "{{{PKG}}}.jl" + execute-dir: project + render: + - "*.qmd" + - "!docs/src/*.md" +crossref: + fig-prefix: Figure + tbl-prefix: Table +resource-path: + - docs/src/assets +format: + commonmark: + variant: -raw_html+tex_math_dollars + wrap: preserve + mermaid-format: png \ No newline at end of file diff --git a/templates/quarto/index.qmd b/templates/quarto/index.qmd new file mode 100644 index 00000000..15f3a87f --- /dev/null +++ b/templates/quarto/index.qmd @@ -0,0 +1,14 @@ +```@meta +CurrentModule = {{{PKG}}} +``` + +# {{{PKG}}} + +Documentation for [{{{PKG}}}](https://{{{REPO}}}). + +```@index +``` + +```@autodocs +Modules = [{{{PKG}}}] +``` \ No newline at end of file diff --git a/templates/quarto/make.jl b/templates/quarto/make.jl new file mode 100755 index 00000000..ab913d24 --- /dev/null +++ b/templates/quarto/make.jl @@ -0,0 +1,85 @@ +#!/usr/bin/env julia +# +# + +if "--help" ∈ ARGS + println( + """ +docs/make.jl + +Render the documentation using Quarto with optional arguments + +Arguments +* `--help` - print this help and exit without rendering the documentation +* `--prettyurls` – toggle the prettyurls part to true (which is otherwise only true on CI) +* `--project` - specify the project name (default: `$(@__DIR__)`) +* `--quarto` – run the Quarto notebooks from the `tutorials/` folder before generating the documentation + this has to be run locally at least once for the `tutorials/*.md` files to exist that are included in + the documentation (see `--exclude-tutorials`) for the alternative. + If they are generated once they are cached accordingly. + Then you can spare time in the rendering by not passing this argument. + If quarto is not run, some tutorials are generated as empty files, since they + are referenced from within the documentation. +""", + ) + exit(0) +end + +# (a) Specify project +using Pkg +if any(contains.(ARGS, "--project")) + @assert sum(contains.(ARGS, "--project")) == 1 "Only one environment can be specified using the `--project` argument." + _path = + ARGS[findall(contains.(ARGS, "--project"))][1] |> + x -> replace(x, "--project=" => "") + Pkg.activate(_path) +else + Pkg.activate(@__DIR__) +end + +# (b) Did someone say render? +if "--quarto" ∈ ARGS + @info "Rendering docs" + run(`quarto render $(joinpath(@__DIR__, "src"))`) +end + +using {{{PKG}}} +using Documenter + +DocMeta.setdocmeta!({{{PKG}}}, :DocTestSetup, :(using {{{PKG}}}); recursive=true) + +makedocs(; + modules=[{{{PKG}}}], + authors="{{{AUTHORS}}}", + sitename="{{{PKG}}}.jl", + format=Documenter.HTML(; +{{#CANONICAL}} + canonical="{{{CANONICAL}}}", +{{/CANONICAL}} +{{#EDIT_LINK}} + edit_link={{{EDIT_LINK}}}, +{{/EDIT_LINK}} + assets={{^HAS_ASSETS}}String{{/HAS_ASSETS}}[{{^HAS_ASSETS}}],{{/HAS_ASSETS}} +{{#ASSETS}} + "assets/{{{.}}}", +{{/ASSETS}} +{{#HAS_ASSETS}} + ], +{{/HAS_ASSETS}} + ), + pages=[ + "Home" => "index.md", + ], +{{#MAKEDOCS_KWARGS}} + {{{first}}}={{{second}}}, +{{/MAKEDOCS_KWARGS}} +) +{{#HAS_DEPLOY}} + +deploydocs(; + repo="{{{REPO}}}", +{{#BRANCH}} + devbranch="{{{BRANCH}}}", +{{/BRANCH}} +) +{{/HAS_DEPLOY}}