Add prehooks/posthooks for more fine-grained plugin control
This commit is contained in:
parent
12fcc121fa
commit
146c1bdbe5
@ -61,9 +61,9 @@ version = "1.1.0"
|
|||||||
|
|
||||||
[[Parameters]]
|
[[Parameters]]
|
||||||
deps = ["OrderedCollections"]
|
deps = ["OrderedCollections"]
|
||||||
git-tree-sha1 = "1dfd7cd50a8eb06ef693a4c2bbe945943cd000c5"
|
git-tree-sha1 = "b62b2558efb1eef1fa44e4be5ff58a515c287e38"
|
||||||
uuid = "d96e819e-fc66-5662-9728-84c9c7592b0a"
|
uuid = "d96e819e-fc66-5662-9728-84c9c7592b0a"
|
||||||
version = "0.11.0"
|
version = "0.12.0"
|
||||||
|
|
||||||
[[Pkg]]
|
[[Pkg]]
|
||||||
deps = ["Dates", "LibGit2", "Markdown", "Printf", "REPL", "Random", "SHA", "UUIDs"]
|
deps = ["Dates", "LibGit2", "Markdown", "Printf", "REPL", "Random", "SHA", "UUIDs"]
|
||||||
|
@ -11,6 +11,7 @@ Mustache = "ffc61752-8dc7-55ee-8c37-f3e9cdd09e70"
|
|||||||
Parameters = "d96e819e-fc66-5662-9728-84c9c7592b0a"
|
Parameters = "d96e819e-fc66-5662-9728-84c9c7592b0a"
|
||||||
Pkg = "44cfe95a-1eb2-52ea-b672-e2afdf69b78f"
|
Pkg = "44cfe95a-1eb2-52ea-b672-e2afdf69b78f"
|
||||||
REPL = "3fa0cd96-eef1-5676-8a61-b3b8758bbffb"
|
REPL = "3fa0cd96-eef1-5676-8a61-b3b8758bbffb"
|
||||||
|
UUIDs = "cf7118a7-6976-5b1a-9a39-7adc72f591a4"
|
||||||
|
|
||||||
[compat]
|
[compat]
|
||||||
julia = "1"
|
julia = "1"
|
||||||
|
@ -50,7 +50,7 @@ view(p::Documenter, t::Template, pkg::AbstractString) = Dict(
|
|||||||
"USER" => t.user,
|
"USER" => t.user,
|
||||||
)
|
)
|
||||||
|
|
||||||
function gen_plugin(p::Documenter, t::Template, pkg_dir::AbstractString)
|
function hook(p::Documenter, t::Template, pkg_dir::AbstractString)
|
||||||
pkg = basename(pkg_dir)
|
pkg = basename(pkg_dir)
|
||||||
docs_dir = joinpath(pkg_dir, "docs")
|
docs_dir = joinpath(pkg_dir, "docs")
|
||||||
|
|
||||||
@ -91,10 +91,10 @@ Third, we implement [`view`](@ref), which is used to fill placeholders in badges
|
|||||||
view
|
view
|
||||||
```
|
```
|
||||||
|
|
||||||
Finally, we implement [`gen_plugin`](@ref), which is the real workhorse for the plugin.
|
Finally, we implement [`hook`](@ref), which is the real workhorse for the plugin.
|
||||||
|
|
||||||
```@docs
|
```@docs
|
||||||
gen_plugin
|
hook
|
||||||
```
|
```
|
||||||
|
|
||||||
Inside of this function, we call a few more functions, which help us with text templating.
|
Inside of this function, we call a few more functions, which help us with text templating.
|
||||||
@ -167,7 +167,7 @@ Finally, we implement [`view`](@ref) to fill in the placeholders that we saw in
|
|||||||
|
|
||||||
## Doing Extra Work With `BasicPlugin`s
|
## Doing Extra Work With `BasicPlugin`s
|
||||||
|
|
||||||
Notice that we didn't have to implement [`gen_plugin`](@ref) for our plugin.
|
Notice that we didn't have to implement [`hook`](@ref) for our plugin.
|
||||||
It's implemented for all [`BasicPlugin`](@ref)s, like so:
|
It's implemented for all [`BasicPlugin`](@ref)s, like so:
|
||||||
|
|
||||||
```julia
|
```julia
|
||||||
@ -175,7 +175,7 @@ function render_plugin(p::BasicPlugin, t::Template, pkg::AbstractString)
|
|||||||
return render_file(source(p), combined_view(p, t, pkg), tags(p))
|
return render_file(source(p), combined_view(p, t, pkg), tags(p))
|
||||||
end
|
end
|
||||||
|
|
||||||
function gen_plugin(p::BasicPlugin, t::Template, pkg_dir::AbstractString)
|
function hook(p::BasicPlugin, t::Template, pkg_dir::AbstractString)
|
||||||
source(p) === nothing && return
|
source(p) === nothing && return
|
||||||
pkg = basename(pkg_dir)
|
pkg = basename(pkg_dir)
|
||||||
path = joinpath(pkg_dir, destination(p))
|
path = joinpath(pkg_dir, destination(p))
|
||||||
@ -191,7 +191,7 @@ It creates `runtests.jl`, but it also modifies the `Project.toml` to include the
|
|||||||
|
|
||||||
Of course, we could use a normal [`Plugin`](@ref), but it turns out there's a way to avoid that while still getting the extra capbilities that we want.
|
Of course, we could use a normal [`Plugin`](@ref), but it turns out there's a way to avoid that while still getting the extra capbilities that we want.
|
||||||
|
|
||||||
The plugin implements its own `gen_plugin`, but uses `invoke` to avoid duplicating the file creation code:
|
The plugin implements its own `hook`, but uses `invoke` to avoid duplicating the file creation code:
|
||||||
|
|
||||||
```julia
|
```julia
|
||||||
@with_kw_noshow struct Tests <: BasicPlugin
|
@with_kw_noshow struct Tests <: BasicPlugin
|
||||||
@ -202,9 +202,9 @@ source(p::Tests) = p.file
|
|||||||
destination(::Tests) = joinpath("test", "runtests.jl")
|
destination(::Tests) = joinpath("test", "runtests.jl")
|
||||||
view(::Tests, ::Template, pkg::AbstractString) = Dict("PKG" => pkg)
|
view(::Tests, ::Template, pkg::AbstractString) = Dict("PKG" => pkg)
|
||||||
|
|
||||||
function gen_plugin(p::Tests, t::Template, pkg_dir::AbstractString)
|
function hook(p::Tests, t::Template, pkg_dir::AbstractString)
|
||||||
# Do the normal BasicPlugin behaviour to create the test script.
|
# Do the normal BasicPlugin behaviour to create the test script.
|
||||||
invoke(gen_plugin, Tuple{BasicPlugin, Template, AbstractString}, p, t, pkg_dir)
|
invoke(hook, Tuple{BasicPlugin, Template, AbstractString}, p, t, pkg_dir)
|
||||||
# Do some other work.
|
# Do some other work.
|
||||||
add_test_dependency()
|
add_test_dependency()
|
||||||
end
|
end
|
||||||
|
@ -28,10 +28,12 @@ These plugins are included by default.
|
|||||||
They can be overridden by supplying another value via the `plugins` keyword, or disabled by supplying the type via the `disable_defaults` keyword.
|
They can be overridden by supplying another value via the `plugins` keyword, or disabled by supplying the type via the `disable_defaults` keyword.
|
||||||
|
|
||||||
```@docs
|
```@docs
|
||||||
Gitignore
|
ProjectFile
|
||||||
License
|
SrcDir
|
||||||
Readme
|
|
||||||
Tests
|
Tests
|
||||||
|
Readme
|
||||||
|
License
|
||||||
|
Git
|
||||||
```
|
```
|
||||||
|
|
||||||
### Continuous Integration (CI)
|
### Continuous Integration (CI)
|
||||||
@ -63,6 +65,7 @@ Documenter
|
|||||||
### Miscellaneous
|
### Miscellaneous
|
||||||
|
|
||||||
```@docs
|
```@docs
|
||||||
|
Develop
|
||||||
Citation
|
Citation
|
||||||
```
|
```
|
||||||
|
|
||||||
|
@ -5,9 +5,10 @@ using Base.Filesystem: contractuser
|
|||||||
|
|
||||||
using Dates: month, today, year
|
using Dates: month, today, year
|
||||||
using InteractiveUtils: subtypes
|
using InteractiveUtils: subtypes
|
||||||
using LibGit2: LibGit2, GitRemote
|
using LibGit2: LibGit2, GitRemote, GitRepo
|
||||||
using Pkg: Pkg, TOML, PackageSpec
|
using Pkg: Pkg, TOML, PackageSpec
|
||||||
using REPL.TerminalMenus: MultiSelectMenu, RadioMenu, request
|
using REPL.TerminalMenus: MultiSelectMenu, RadioMenu, request
|
||||||
|
using UUIDs: uuid4
|
||||||
|
|
||||||
using Mustache: render
|
using Mustache: render
|
||||||
using Parameters: @with_kw_noshow
|
using Parameters: @with_kw_noshow
|
||||||
@ -19,11 +20,14 @@ export
|
|||||||
Citation,
|
Citation,
|
||||||
Codecov,
|
Codecov,
|
||||||
Coveralls,
|
Coveralls,
|
||||||
|
Develop,
|
||||||
Documenter,
|
Documenter,
|
||||||
Gitignore,
|
Git,
|
||||||
GitLabCI,
|
GitLabCI,
|
||||||
License,
|
License,
|
||||||
|
ProjectFile,
|
||||||
Readme,
|
Readme,
|
||||||
|
SrcDir,
|
||||||
Tests,
|
Tests,
|
||||||
TravisCI
|
TravisCI
|
||||||
|
|
||||||
@ -36,7 +40,6 @@ When implementing a new plugin, subtype this type to have full control over its
|
|||||||
abstract type Plugin end
|
abstract type Plugin end
|
||||||
|
|
||||||
include("template.jl")
|
include("template.jl")
|
||||||
include("generate.jl")
|
|
||||||
include("plugin.jl")
|
include("plugin.jl")
|
||||||
include("interactive.jl")
|
include("interactive.jl")
|
||||||
|
|
||||||
|
@ -19,7 +19,7 @@ Return the view to be passed to the text templating engine for this plugin.
|
|||||||
`pkg` is the name of the package being generated.
|
`pkg` is the name of the package being generated.
|
||||||
|
|
||||||
For [`BasicPlugin`](@ref)s, this is used for both the plugin badges (see [`badges`](@ref)) and the template file (see [`source`](@ref)).
|
For [`BasicPlugin`](@ref)s, this is used for both the plugin badges (see [`badges`](@ref)) and the template file (see [`source`](@ref)).
|
||||||
For other [`Plugin`](@ref)s, it is used only for badges, but you can always call it yourself as part of your [`gen_plugin`](@ref) implementation.
|
For other [`Plugin`](@ref)s, it is used only for badges, but you can always call it yourself as part of your [`hook`](@ref) implementation.
|
||||||
|
|
||||||
By default, an empty `Dict` is returned.
|
By default, an empty `Dict` is returned.
|
||||||
"""
|
"""
|
||||||
@ -124,19 +124,44 @@ function badges(p::Plugin, t::Template, pkg::AbstractString)
|
|||||||
end
|
end
|
||||||
|
|
||||||
"""
|
"""
|
||||||
gen_plugin(::Plugin, ::Template, pkg::AbstractString)
|
prehook(::Plugin, ::Template, pkg_dir::AbstractString)
|
||||||
|
|
||||||
|
Do some work associated with a plugin **before** any files are generated.
|
||||||
|
At this point, `pkg_dir` is an empty directory that will eventually contain the package.
|
||||||
|
"""
|
||||||
|
prehook(::Plugin, ::Template, ::AbstractString) = nothing
|
||||||
|
|
||||||
|
function prehook(p::T, ::Template, ::AbstractString) where T <: BasicPlugin
|
||||||
|
src = source(p)
|
||||||
|
src === nothing && return
|
||||||
|
isfile(src) || throw(ArgumentError("$(nameof(T)): The file $src does not exist"))
|
||||||
|
end
|
||||||
|
|
||||||
|
"""
|
||||||
|
posthook(::Plugin, ::Template, pkg_dir::AbstractString)
|
||||||
|
|
||||||
|
Do some work associated with a plugin **after** after files have been generated.
|
||||||
|
"""
|
||||||
|
posthook(::Plugin, ::Template, ::AbstractString) = nothing
|
||||||
|
|
||||||
|
"""
|
||||||
|
hook(::Plugin, ::Template, pkg_dir::AbstractString)
|
||||||
|
|
||||||
Perform any work associated with a plugin.
|
Perform any work associated with a plugin.
|
||||||
`pkg` is the name of the package being generated.
|
`pkg_dir` is the directory in which the package is being generated (so `basename(pkg_dir)` is the package name).
|
||||||
|
|
||||||
For [`Plugin`](@ref)s that are not [`BasicPlugin`](@ref)s, this is the only function that really needs to be implemented.
|
For [`Plugin`](@ref)s that are not [`BasicPlugin`](@ref)s, this is the only function that really needs to be implemented.
|
||||||
If you want your plugin to do anything at all during package generation, you should implement it here.
|
If you want your plugin to do something during the main phase of package generation, you should implement it here.
|
||||||
|
|
||||||
You should **not** implement this function for `BasicPlugin`s.
|
See also: [`prehook`](@ref) and [`posthook`](@ref).
|
||||||
|
|
||||||
|
!!! note
|
||||||
|
You usually shouldn't implement this function for [`BasicPlugin`](@ref)s.
|
||||||
|
If you do, it should probably `invoke` the generic method (otherwise, there's no reason to subtype `BasicPlugin`).
|
||||||
"""
|
"""
|
||||||
gen_plugin(::Plugin, ::Template, ::AbstractString) = nothing
|
hook(::Plugin, ::Template, ::AbstractString) = nothing
|
||||||
|
|
||||||
function gen_plugin(p::BasicPlugin, t::Template, pkg_dir::AbstractString)
|
function hook(p::BasicPlugin, t::Template, pkg_dir::AbstractString)
|
||||||
source(p) === nothing && return
|
source(p) === nothing && return
|
||||||
pkg = basename(pkg_dir)
|
pkg = basename(pkg_dir)
|
||||||
path = joinpath(pkg_dir, destination(p))
|
path = joinpath(pkg_dir, destination(p))
|
||||||
@ -184,4 +209,5 @@ include(joinpath("plugins", "defaults.jl"))
|
|||||||
include(joinpath("plugins", "coverage.jl"))
|
include(joinpath("plugins", "coverage.jl"))
|
||||||
include(joinpath("plugins", "ci.jl"))
|
include(joinpath("plugins", "ci.jl"))
|
||||||
include(joinpath("plugins", "citation.jl"))
|
include(joinpath("plugins", "citation.jl"))
|
||||||
|
include(joinpath("plugins", "develop.jl"))
|
||||||
include(joinpath("plugins", "documenter.jl"))
|
include(joinpath("plugins", "documenter.jl"))
|
||||||
|
@ -1,18 +1,5 @@
|
|||||||
const TEST_UUID = "8dfed614-e22c-5e08-85e1-65c5234f0b40"
|
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 LICENSES = Dict(
|
|
||||||
"MIT" => "MIT \"Expat\" License",
|
|
||||||
"BSD2" => "Simplified \"2-clause\" BSD License",
|
|
||||||
"BSD3" => "Modified \"3-clause\" BSD License",
|
|
||||||
"ISC" => "Internet Systems Consortium License",
|
|
||||||
"ASL" => "Apache License, Version 2.0",
|
|
||||||
"MPL" => "Mozilla Public License, Version 2.0",
|
|
||||||
"GPL-2.0+" => "GNU Public License, Version 2.0+",
|
|
||||||
"GPL-3.0+" => "GNU Public License, Version 3.0+",
|
|
||||||
"LGPL-2.1+" => "Lesser GNU Public License, Version 2.1+",
|
|
||||||
"LGPL-3.0+" => "Lesser GNU Public License, Version 3.0+",
|
|
||||||
"EUPL-1.2+" => "European Union Public Licence, Version 1.2+",
|
|
||||||
)
|
|
||||||
|
|
||||||
badge_order() = [
|
badge_order() = [
|
||||||
Documenter{GitLabCI},
|
Documenter{GitLabCI},
|
||||||
@ -25,6 +12,67 @@ badge_order() = [
|
|||||||
Coveralls,
|
Coveralls,
|
||||||
]
|
]
|
||||||
|
|
||||||
|
"""
|
||||||
|
ProjectFile()
|
||||||
|
|
||||||
|
Creates a `Project.toml`.
|
||||||
|
"""
|
||||||
|
struct ProjectFile <: Plugin end
|
||||||
|
|
||||||
|
# Create Project.toml in the prehook because other hooks might depend on it.
|
||||||
|
function prehook(::ProjectFile, t::Template, pkg_dir::AbstractString)
|
||||||
|
toml = Dict(
|
||||||
|
"name" => basename(pkg_dir),
|
||||||
|
"uuid" => uuid4(),
|
||||||
|
"authors" => t.authors,
|
||||||
|
"compat" => Dict("julia" => compat_version(t.julia_version)),
|
||||||
|
)
|
||||||
|
open(io -> TOML.print(io, toml), joinpath(pkg_dir, "Project.toml"), "w")
|
||||||
|
end
|
||||||
|
|
||||||
|
"""
|
||||||
|
compat_version(v::VersionNumber) -> String
|
||||||
|
|
||||||
|
Format a `VersionNumber` to exclude trailing zero components.
|
||||||
|
"""
|
||||||
|
function compat_version(v::VersionNumber)
|
||||||
|
return if v.patch == 0 && v.minor == 0
|
||||||
|
"$(v.major)"
|
||||||
|
elseif v.patch == 0
|
||||||
|
"$(v.major).$(v.minor)"
|
||||||
|
else
|
||||||
|
"$(v.major).$(v.minor).$(v.patch)"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
"""
|
||||||
|
SrcDir(; file="$(contractuser(default_file("src", "module.jl")))")
|
||||||
|
|
||||||
|
Creates a module entrypoint.
|
||||||
|
"""
|
||||||
|
@with_kw_noshow mutable struct SrcDir <: BasicPlugin
|
||||||
|
file::String = default_file("src", "module.jl")
|
||||||
|
destination::String = joinpath("src", "<module>.jl")
|
||||||
|
end
|
||||||
|
|
||||||
|
# Don't display the destination field.
|
||||||
|
function Base.show(io::IO, ::MIME"text/plain", p::SrcDir)
|
||||||
|
indent = get(io, :indent, 0)
|
||||||
|
print(io, repeat(' ', indent), "SrcDir:")
|
||||||
|
print(io, "\n", repeat(' ', indent + 2), "file: ", show_field(p.file))
|
||||||
|
end
|
||||||
|
|
||||||
|
source(p::SrcDir) = p.file
|
||||||
|
destination(p::SrcDir) = p.destination
|
||||||
|
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
|
||||||
|
|
||||||
"""
|
"""
|
||||||
Readme(;
|
Readme(;
|
||||||
file="$(contractuser(default_file("README.md")))",
|
file="$(contractuser(default_file("README.md")))",
|
||||||
@ -113,32 +161,88 @@ view(::License, t::Template, ::AbstractString) = Dict(
|
|||||||
)
|
)
|
||||||
|
|
||||||
"""
|
"""
|
||||||
Gitignore(; ds_store=true, dev=true)
|
Git(; ignore=String[], ssh=false, manifest=false, gpgsign=false)
|
||||||
|
|
||||||
Creates a `.gitignore` file.
|
Creates a Git repository and a `.gitignore` file.
|
||||||
|
|
||||||
## Keyword Arguments
|
## Keyword Arguments
|
||||||
- `ds_store::Bool`: Whether or not to ignore MacOS's `.DS_Store` files.
|
- `ignore::Vector{<:AbstractString}`: Patterns to add to the `.gitignore`.
|
||||||
- `dev::Bool`: Whether or not to ignore the directory of locally-developed packages.
|
See also: [`gitignore`](@ref).
|
||||||
|
- `ssh::Bool`: Whether or not to use SSH for the remote.
|
||||||
|
If left unset, HTTPS is used.
|
||||||
|
- `manifest::Bool`: Whether or not to commit `Manifest.toml`.
|
||||||
|
- `gpgsign::Bool`: Whether or not to sign commits with your GPG key.
|
||||||
|
This option requires that the Git CLI is installed.
|
||||||
"""
|
"""
|
||||||
@with_kw_noshow struct Gitignore <: Plugin
|
@with_kw_noshow struct Git <: Plugin
|
||||||
ds_store::Bool = true
|
ignore::Vector{String} = []
|
||||||
dev::Bool = true
|
ssh::Bool = false
|
||||||
|
manifest::Bool = false
|
||||||
|
gpgsign::Bool = false
|
||||||
end
|
end
|
||||||
|
|
||||||
function render_plugin(p::Gitignore, t::Template)
|
gitignore(p::Git) = p.ignore
|
||||||
init = String[]
|
|
||||||
p.ds_store && push!(init, ".DS_Store")
|
# Set up the Git repository.
|
||||||
p.dev && push!(init, "/dev/")
|
function prehook(p::Git, t::Template, pkg_dir::AbstractString)
|
||||||
entries = mapreduce(gitignore, append!, values(t.plugins); init=init)
|
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
|
||||||
|
LibGit2.with(LibGit2.init(pkg_dir)) do repo
|
||||||
|
commit(p, repo, pkg_dir, "Initial commit")
|
||||||
|
pkg = basename(pkg_dir)
|
||||||
|
url = if p.ssh
|
||||||
|
"git@$(t.host):$(t.user)/$pkg.jl.git"
|
||||||
|
else
|
||||||
|
"https://$(t.host)/$(t.user)/$pkg.jl"
|
||||||
|
end
|
||||||
|
LibGit2.with(GitRemote(repo, "origin", url)) do remote
|
||||||
|
# TODO: `git pull` still requires some Git branch config.
|
||||||
|
LibGit2.add_push!(repo, remote, "refs/heads/master")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# Create the .gitignore.
|
||||||
|
function hook(p::Git, t::Template, pkg_dir::AbstractString)
|
||||||
|
gen_file(joinpath(pkg_dir, ".gitignore"), render_plugin(p, t))
|
||||||
|
end
|
||||||
|
|
||||||
|
# Commit the files
|
||||||
|
function posthook(p::Git, t::Template, pkg_dir::AbstractString)
|
||||||
|
# Ensure that the manifest exists if it's going to be committed.
|
||||||
|
manifest = joinpath(pkg_dir, "Manifest.toml")
|
||||||
|
if p.manifest && !isfile(manifest)
|
||||||
|
touch(manifest)
|
||||||
|
with_project(Pkg.update, pkg_dir)
|
||||||
|
end
|
||||||
|
|
||||||
|
LibGit2.with(GitRepo(pkg_dir)) do repo
|
||||||
|
LibGit2.add!(repo, ".")
|
||||||
|
msg = "Files generated by PkgTemplates"
|
||||||
|
installed = Pkg.installed()
|
||||||
|
if haskey(installed, "PkgTemplates")
|
||||||
|
ver = string(installed["PkgTemplates"])
|
||||||
|
msg *= "\n\nPkgTemplates version: $ver"
|
||||||
|
end
|
||||||
|
commit(p, repo, pkg_dir, msg)
|
||||||
|
end
|
||||||
|
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))
|
||||||
|
else
|
||||||
|
LibGit2.commit(repo, msg)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function render_plugin(p::Git, t::Template)
|
||||||
|
ignore = mapreduce(gitignore, append!, values(t.plugins))
|
||||||
# Only ignore manifests at the repo root.
|
# Only ignore manifests at the repo root.
|
||||||
t.manifest || "Manifest.toml" in entries || push!(entries, "/Manifest.toml")
|
p.manifest || "Manifest.toml" in ignore || push!(ignore, "/Manifest.toml")
|
||||||
unique!(sort!(entries))
|
unique!(sort!(ignore))
|
||||||
return join(entries, "\n")
|
return join(ignore, "\n")
|
||||||
end
|
|
||||||
|
|
||||||
function gen_plugin(p::Gitignore, t::Template, pkg_dir::AbstractString)
|
|
||||||
t.git && gen_file(joinpath(pkg_dir, ".gitignore"), render_plugin(p, t))
|
|
||||||
end
|
end
|
||||||
|
|
||||||
"""
|
"""
|
||||||
@ -163,9 +267,18 @@ source(p::Tests) = p.file
|
|||||||
destination(::Tests) = joinpath("test", "runtests.jl")
|
destination(::Tests) = joinpath("test", "runtests.jl")
|
||||||
view(::Tests, ::Template, pkg::AbstractString) = Dict("PKG" => pkg)
|
view(::Tests, ::Template, pkg::AbstractString) = Dict("PKG" => pkg)
|
||||||
|
|
||||||
function gen_plugin(p::Tests, t::Template, pkg_dir::AbstractString)
|
function prehook(p::Tests, t::Template, pkg_dir::AbstractString)
|
||||||
|
invoke(prehook, Tuple{BasicPlugin, Template, AbstractString}, p, t, pkg_dir)
|
||||||
|
p.project && t.julia_version < 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.",
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
function hook(p::Tests, t::Template, pkg_dir::AbstractString)
|
||||||
# Do the normal BasicPlugin behaviour to create the test script.
|
# Do the normal BasicPlugin behaviour to create the test script.
|
||||||
invoke(gen_plugin, Tuple{BasicPlugin, Template, AbstractString}, p, t, pkg_dir)
|
invoke(hook, Tuple{BasicPlugin, Template, AbstractString}, p, t, pkg_dir)
|
||||||
|
|
||||||
# Then set up the test depdendency in the chosen way.
|
# Then set up the test depdendency in the chosen way.
|
||||||
f = p.project ? make_test_project : add_test_dependency
|
f = p.project ? make_test_project : add_test_dependency
|
||||||
|
11
src/plugins/develop.jl
Normal file
11
src/plugins/develop.jl
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
"""
|
||||||
|
Develop()
|
||||||
|
|
||||||
|
Adds generated packages to the current environment by `dev`ing them.
|
||||||
|
See [here](https://julialang.github.io/Pkg.jl/v1/managing-packages/#Developing-packages-1) for more details.
|
||||||
|
"""
|
||||||
|
struct Develop <: Plugin end
|
||||||
|
|
||||||
|
function posthook(::Develop, ::Template, pkg_dir::AbstractString)
|
||||||
|
Pkg.develop(PackageSpec(; path=pkg_dir))
|
||||||
|
end
|
@ -85,14 +85,14 @@ function view(p::Documenter{TravisCI}, t::Template, pkg::AbstractString)
|
|||||||
return merge(base, Dict("HAS_DEPLOY" => true))
|
return merge(base, Dict("HAS_DEPLOY" => true))
|
||||||
end
|
end
|
||||||
|
|
||||||
function gen_plugin(p::Documenter, t::Template, pkg_dir::AbstractString)
|
function hook(p::Documenter, t::Template, pkg_dir::AbstractString)
|
||||||
pkg = basename(pkg_dir)
|
pkg = basename(pkg_dir)
|
||||||
docs_dir = joinpath(pkg_dir, "docs")
|
docs_dir = joinpath(pkg_dir, "docs")
|
||||||
|
|
||||||
# Generate files.
|
# Generate files.
|
||||||
make = render_file(p.make_jl, combined_view(p, t, pkg), tags(p))
|
make = render_file(p.make_jl, combined_view(p, t, pkg), tags(p))
|
||||||
gen_file(joinpath(docs_dir, "make.jl"), make)
|
|
||||||
index = render_file(p.index_md, combined_view(p, t, pkg), tags(p))
|
index = render_file(p.index_md, combined_view(p, t, pkg), tags(p))
|
||||||
|
gen_file(joinpath(docs_dir, "make.jl"), make)
|
||||||
gen_file(joinpath(docs_dir, "src", "index.md"), index)
|
gen_file(joinpath(docs_dir, "src", "index.md"), index)
|
||||||
|
|
||||||
# Copy over any assets.
|
# Copy over any assets.
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
default_plugins() = [Gitignore(), License(), Readme(), Tests()]
|
default_plugins() = [ProjectFile(), SrcDir(), Git(), License(), Readme(), Tests()]
|
||||||
default_user() = LibGit2.getconfig("github.user", "")
|
default_user() = LibGit2.getconfig("github.user", "")
|
||||||
default_version() = VersionNumber(VERSION.major)
|
default_version() = VersionNumber(VERSION.major)
|
||||||
|
|
||||||
@ -26,20 +26,14 @@ A configuration used to generate packages.
|
|||||||
|
|
||||||
### Package Options
|
### Package Options
|
||||||
- `dir::AbstractString="$(contractuser(Pkg.devdir()))"`: Directory to place packages in.
|
- `dir::AbstractString="$(contractuser(Pkg.devdir()))"`: Directory to place packages in.
|
||||||
|
- `host::AbstractString="github.com"`: URL to the code hosting service where packages will reside.
|
||||||
- `julia_version::VersionNumber=$(repr(default_version()))`: Minimum allowed Julia version.
|
- `julia_version::VersionNumber=$(repr(default_version()))`: Minimum allowed Julia version.
|
||||||
- `develop::Bool=true`: Whether or not to `develop` new packages in the active environment.
|
- `develop::Bool=true`: Whether or not to `develop` new packages in the active environment.
|
||||||
|
|
||||||
### Git Options
|
|
||||||
- `git::Bool=true`: Whether or not to create a Git repository for new packages.
|
|
||||||
- `host::AbstractString="github.com"`: URL to the code hosting service where packages will reside.
|
|
||||||
- `ssh::Bool=false`: Whether or not to use SSH for the Git remote.
|
|
||||||
If left unset, HTTPS will be used.
|
|
||||||
- `manifest::Bool=false`: Whether or not to commit the `Manifest.toml`.
|
|
||||||
|
|
||||||
### Template Plugins
|
### Template Plugins
|
||||||
- `plugins::Vector{<:Plugin}=Plugin[]`: A list of [`Plugin`](@ref)s used by the template.
|
- `plugins::Vector{<:Plugin}=Plugin[]`: A list of [`Plugin`](@ref)s used by the template.
|
||||||
- `disable_defaults::Vector{DataType}=DataType[]`: Default plugins to disable.
|
- `disable_defaults::Vector{DataType}=DataType[]`: Default plugins to disable.
|
||||||
The default plugins are [`Readme`](@ref), [`License`](@ref), [`Tests`](@ref), and [`Gitignore`](@ref).
|
The default plugins are [`ProjectFile`](@ref), [`SrcDir`](@ref), [`Tests`](@ref), [`Readme`](@ref), [`License`](@ref), and [`Git`](@ref).
|
||||||
To override a default plugin instead of disabling it altogether, supply it via `plugins`.
|
To override a default plugin instead of disabling it altogether, supply it via `plugins`.
|
||||||
|
|
||||||
### Interactive Usage
|
### Interactive Usage
|
||||||
@ -57,14 +51,10 @@ julia> t("PkgName")
|
|||||||
"""
|
"""
|
||||||
struct Template
|
struct Template
|
||||||
authors::Vector{String}
|
authors::Vector{String}
|
||||||
develop::Bool
|
|
||||||
dir::String
|
dir::String
|
||||||
git::Bool
|
|
||||||
host::String
|
host::String
|
||||||
julia_version::VersionNumber
|
julia_version::VersionNumber
|
||||||
manifest::Bool
|
|
||||||
plugins::Dict{DataType, <:Plugin}
|
plugins::Dict{DataType, <:Plugin}
|
||||||
ssh::Bool
|
|
||||||
user::String
|
user::String
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -78,9 +68,9 @@ function Template(::Val{false}; kwargs...)
|
|||||||
authors = getkw(kwargs, :authors)
|
authors = getkw(kwargs, :authors)
|
||||||
authors isa Vector || (authors = map(strip, split(authors, ",")))
|
authors isa Vector || (authors = map(strip, split(authors, ",")))
|
||||||
|
|
||||||
host = replace(getkw(kwargs, :host), r".*://" => "")
|
|
||||||
|
|
||||||
dir = abspath(expanduser(getkw(kwargs, :dir)))
|
dir = abspath(expanduser(getkw(kwargs, :dir)))
|
||||||
|
host = replace(getkw(kwargs, :host), r".*://" => "")
|
||||||
|
julia_version = getkw(kwargs, :julia_version)
|
||||||
|
|
||||||
disabled = getkw(kwargs, :disable_defaults)
|
disabled = getkw(kwargs, :disable_defaults)
|
||||||
enabled = filter(p -> !(typeof(p) in disabled), default_plugins())
|
enabled = filter(p -> !(typeof(p) in disabled), default_plugins())
|
||||||
@ -89,26 +79,7 @@ function Template(::Val{false}; kwargs...)
|
|||||||
# which means that default plugins get replaced by user values.
|
# which means that default plugins get replaced by user values.
|
||||||
plugins = Dict(typeof(p) => p for p in enabled)
|
plugins = Dict(typeof(p) => p for p in enabled)
|
||||||
|
|
||||||
# TODO: It might be nice to offer some kind of warn_incompatible function
|
return Template(authors, dir, host, julia_version, plugins, user)
|
||||||
# to be optionally implemented by plugins instead of hardcoding this case here.
|
|
||||||
julia = getkw(kwargs, :julia_version)
|
|
||||||
julia < v"1.2" && haskey(plugins, Tests) && plugins[Tests].project && @warn string(
|
|
||||||
"The Tests plugin is set to create a project (supported in Julia 1.2 and later)",
|
|
||||||
"but a Julia version older than 1.2 is supported.",
|
|
||||||
)
|
|
||||||
|
|
||||||
return Template(
|
|
||||||
authors,
|
|
||||||
getkw(kwargs, :develop),
|
|
||||||
dir,
|
|
||||||
getkw(kwargs, :git),
|
|
||||||
host,
|
|
||||||
julia,
|
|
||||||
getkw(kwargs, :manifest),
|
|
||||||
plugins,
|
|
||||||
getkw(kwargs, :ssh),
|
|
||||||
user,
|
|
||||||
)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
# Does the template have a plugin that satisfies some predicate?
|
# Does the template have a plugin that satisfies some predicate?
|
||||||
@ -121,13 +92,35 @@ getkw(kwargs, k) = get(() -> defaultkw(k), kwargs, k)
|
|||||||
# Default Template keyword values.
|
# Default Template keyword values.
|
||||||
defaultkw(s::Symbol) = defaultkw(Val(s))
|
defaultkw(s::Symbol) = defaultkw(Val(s))
|
||||||
defaultkw(::Val{:authors}) = default_authors()
|
defaultkw(::Val{:authors}) = default_authors()
|
||||||
defaultkw(::Val{:develop}) = true
|
|
||||||
defaultkw(::Val{:dir}) = Pkg.devdir()
|
defaultkw(::Val{:dir}) = Pkg.devdir()
|
||||||
defaultkw(::Val{:disable_defaults}) = DataType[]
|
defaultkw(::Val{:disable_defaults}) = DataType[]
|
||||||
defaultkw(::Val{:git}) = true
|
|
||||||
defaultkw(::Val{:host}) = "github.com"
|
defaultkw(::Val{:host}) = "github.com"
|
||||||
defaultkw(::Val{:julia_version}) = default_version()
|
defaultkw(::Val{:julia_version}) = default_version()
|
||||||
defaultkw(::Val{:manifest}) = false
|
|
||||||
defaultkw(::Val{:plugins}) = Plugin[]
|
defaultkw(::Val{:plugins}) = Plugin[]
|
||||||
defaultkw(::Val{:ssh}) = false
|
|
||||||
defaultkw(::Val{:user}) = default_user()
|
defaultkw(::Val{:user}) = default_user()
|
||||||
|
|
||||||
|
"""
|
||||||
|
(::Template)(pkg::AbstractString)
|
||||||
|
|
||||||
|
Generate a package named `pkg` from a [`Template`](@ref).
|
||||||
|
"""
|
||||||
|
function (t::Template)(pkg::AbstractString)
|
||||||
|
endswith(pkg, ".jl") && (pkg = pkg[1:end-3])
|
||||||
|
pkg_dir = joinpath(t.dir, pkg)
|
||||||
|
ispath(pkg_dir) && throw(ArgumentError("$pkg_dir already exists"))
|
||||||
|
mkpath(pkg_dir)
|
||||||
|
|
||||||
|
try
|
||||||
|
foreach((prehook, hook, posthook)) do h
|
||||||
|
@info "Running $(h)s"
|
||||||
|
foreach(values(t.plugins)) do p
|
||||||
|
h(p, t, pkg_dir)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
catch
|
||||||
|
rm(pkg_dir; recursive=true, force=true)
|
||||||
|
rethrow()
|
||||||
|
end
|
||||||
|
|
||||||
|
@info "New package is at $pkg_dir"
|
||||||
|
end
|
||||||
|
5
templates/src/module.jl
Normal file
5
templates/src/module.jl
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
module {{{PKG}}}
|
||||||
|
|
||||||
|
# Write your package code here.
|
||||||
|
|
||||||
|
end
|
Loading…
Reference in New Issue
Block a user