The Big Squash: A bunch of irrelevant garbage

This commit is contained in:
Chris de Graaf 2019-02-05 11:31:51 -06:00
parent 895f2be769
commit c0fe7bb2c2
No known key found for this signature in database
GPG Key ID: 150FFDD9B0073C7B
53 changed files with 1009 additions and 2353 deletions

View File

@ -1,27 +0,0 @@
environment:
matrix:
- julia_version: 0.7
- julia_version: 1.0
- julia_version: nightly
platform:
- x86
- x64
matrix:
allow_failures:
- julia_version: nightly
notifications:
- provider: Email
on_build_success: false
on_build_failure: false
on_build_status_changed: false
install:
- ps: iex ((new-object net.webclient).DownloadString("https://raw.githubusercontent.com/JuliaCI/Appveyor.jl/version-1/bin/install.ps1"))
build_script:
- echo "%JL_BUILD_SCRIPT%"
- C:\julia\bin\julia -e "%JL_BUILD_SCRIPT%"
test_script:
- echo "%JL_TEST_SCRIPT%"
- C:\julia\bin\julia -e "%JL_TEST_SCRIPT%"
on_success:
- echo "%JL_CODECOV_SCRIPT%"
- C:\julia\bin\julia -e "%JL_CODECOV_SCRIPT%"

View File

@ -1 +0,0 @@
comment: false

View File

@ -2,9 +2,10 @@ language: julia
os:
- linux
- osx
- windows
julia:
- 1.0
- 1.1
- 1.2
- nightly
matrix:
allow_failures:
@ -12,14 +13,19 @@ matrix:
fast_finish: true
notifications:
email: false
after_success: julia -e 'using Pkg; Pkg.add("Coverage"); using Coverage; Codecov.submit(process_folder())'
after_success:
- julia -e '
using Pkg
Pkg.add("Coverage")
using Coverage
Codecov.submit(process_folder())'
jobs:
include:
- stage: Documentation
julia: 1.1
julia: 1.2
script: julia --project=docs -e '
using Pkg;
Pkg.develop(PackageSpec(path=pwd()));
Pkg.instantiate();
include("docs/make.jl");'
using Pkg
Pkg.develop(PackageSpec(; path=pwd()))
Pkg.instantiate()
include("docs/make.jl")'
after_success: skip

View File

@ -3,6 +3,16 @@
[[Base64]]
uuid = "2a0f44e3-6c83-55bd-87e4-b1978d98bd5f"
[[DataAPI]]
git-tree-sha1 = "8903f0219d3472543fc4b2f5ebaf675a07f817c0"
uuid = "9a962f9c-6df0-11e9-0e5d-c546b8b5ee8a"
version = "1.0.1"
[[DataValueInterfaces]]
git-tree-sha1 = "bfc1187b79289637fa0ef6d4436ebdfe6905cbd6"
uuid = "e2d170a0-9d28-54be-80f0-106bbe20a464"
version = "1.0.0"
[[Dates]]
deps = ["Printf"]
uuid = "ade2ca70-3891-5945-98fb-dc099432e06a"
@ -39,9 +49,9 @@ uuid = "d6f4376e-aef5-505a-96c1-9c027394607a"
[[Mustache]]
deps = ["Printf", "Tables"]
git-tree-sha1 = "d27b8b8b99c052ea1fdd40c678bfb2dfaec4e96e"
git-tree-sha1 = "f39de3a12232eb47bd0629b3a661054287780276"
uuid = "ffc61752-8dc7-55ee-8c37-f3e9cdd09e70"
version = "0.5.12"
version = "0.5.13"
[[Pkg]]
deps = ["Dates", "LibGit2", "Markdown", "Printf", "REPL", "Random", "SHA", "UUIDs"]
@ -59,12 +69,6 @@ uuid = "3fa0cd96-eef1-5676-8a61-b3b8758bbffb"
deps = ["Serialization"]
uuid = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c"
[[Requires]]
deps = ["Test"]
git-tree-sha1 = "f6fbf4ba64d295e146e49e021207993b6b48c7d1"
uuid = "ae029012-a4dd-5104-9daa-d747884805df"
version = "0.5.2"
[[SHA]]
uuid = "ea8e919c-243c-51af-8825-aaa63cd721ce"
@ -81,10 +85,10 @@ uuid = "3783bdb8-4a98-5b6b-af9a-565f29a5fe9c"
version = "1.0.0"
[[Tables]]
deps = ["IteratorInterfaceExtensions", "LinearAlgebra", "Requires", "TableTraits", "Test"]
git-tree-sha1 = "351a4b894122e1553c6ed05fda54086ab036adef"
deps = ["DataAPI", "DataValueInterfaces", "IteratorInterfaceExtensions", "LinearAlgebra", "TableTraits", "Test"]
git-tree-sha1 = "aaed7b3b00248ff6a794375ad6adf30f30ca5591"
uuid = "bd369af6-aec1-5ad0-b16a-f7cc5008161c"
version = "0.2.5"
version = "0.2.11"
[[Test]]
deps = ["Distributed", "InteractiveUtils", "Logging", "Random"]

View File

@ -16,7 +16,8 @@ URIParser = "30578b45-9adc-5946-b283-645ec420af67"
julia = "1"
[extras]
Suppressor = "fd094767-a336-5f1f-9728-57cf17d0bbfb"
Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40"
[targets]
test = ["Test"]
test = ["Suppressor", "Test"]

8
defaults/CITATION.bib Normal file
View File

@ -0,0 +1,8 @@
@misc{<<PKG>>.jl,
author = {<<AUTHORS>>},
title = {<<PKG>>.jl},
url = {<<URL>>},
version = {v0.1.0},
year = {<<YEAR>>},
month = {<<MONTH>>}
}

13
defaults/README.md Normal file
View File

@ -0,0 +1,13 @@
# {{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}}

View File

@ -1,14 +1,18 @@
# Documentation: https://github.com/JuliaCI/Appveyor.jl
environment:
matrix:
- julia_version: {{VERSION}}
- julia_version: nightly
{{#VERSIONS}}
- julia_version: {{.}}
{{/VERSIONS}}
platform:
- x86
- x64
{{#PLATFORMS}}
- {{.}}
{{/PLATFORMS}}
{{#HAS_NIGHTLY}}
matrix:
allow_failures:
- julia_version: nightly
{{/HAS_NIGHTLY}}
branches:
only:
- master
@ -26,8 +30,8 @@ build_script:
test_script:
- echo "%JL_TEST_SCRIPT%"
- C:\julia\bin\julia -e "%JL_TEST_SCRIPT%"
{{#CODECOV}}
{{#HAS_CODECOV}}
on_success:
- echo "%JL_CODECOV_SCRIPT%"
- C:\julia\bin\julia -e "%JL_CODECOV_SCRIPT%"
{{/CODECOV}}
{{/HAS_CODECOV}}

View File

@ -1,16 +1,18 @@
freebsd_instance:
image: freebsd-12-0-release-amd64
image: {{IMAGE}}
task:
name: FreeBSD
env:
JULIA_VERSION: {{VERSION}}
{{#VERSIONS}}
JULIA_VERSION: {{.}}
{{/VERSIONS}}
install_script:
- sh -c "$(fetch https://raw.githubusercontent.com/ararslan/CirrusCI.jl/master/bin/install.sh -o -)"
build_script:
- cirrusjl build
test_script:
- cirrusjl test
{{#COVERAGE}}
{{#HAS_COVERAGE}}
coverage_script:
- cirrusjl coverage{{#CODECOV}} codecov{{/CODECOV}}{{#COVERALLS}} coveralls{{/COVERALLS}}
{{/COVERAGE}}
- cirrusjl coverage{{#HAS_CODECOV}} codecov{{/HAS_CODECOV}}{{#HAS_COVERALLS}} coveralls{{/HAS_COVERALLS}}
{{/HAS_COVERAGE}}

View File

@ -1,19 +1,30 @@
Julia {{VERSION}}:
image: julia:{{VERSION}}
script: julia --project='@.' -e 'using Pkg; Pkg.build(); Pkg.test({{#GITLABCOVERAGE}}; coverage=true{{/GITLABCOVERAGE}})'
{{#GITLABCOVERAGE}}
{{#VERSIONS}}
Julia {{.}}:
image: julia:{{.}}
script: julia --project=@. -e '
using Pkg
Pkg.build()
Pkg.test({{#HAS_COVERAGE}}coverage=true{{/HAS_COVERAGE}})'
{{/VERSIONS}}
{{#HAS_COVERAGE}}
coverage: /Test Coverage (\d+\.\d+%)/
after_script:
- julia -e 'using Printf; using Pkg; Pkg.add("Coverage"); using Coverage; c, t = get_summary(process_folder()); @printf "Test Coverage %.2f%%\n" 100c/t'
{{/GITLABCOVERAGE}}
{{#DOCUMENTER}}
- julia -e '
using Pkg
Pkg.add("Coverage")
using Coverage
c, t = get_summary(process_folder())
using Printf
@printf "Test Coverage %.2f%%\n" 100c/t'
{{/HAS_COVERAGE}}
{{#HAS_DOCUMENTER}}
pages:
image: julia:{{VERSION}}
stage: deploy
script:
- julia --project=docs -e '
using Pkg;
Pkg.develop(PackageSpec(path=pwd()));
Pkg.develop(PackageSpec(; path=pwd()));
Pkg.instantiate();
include("docs/make.jl");'
- mkdir -p public
@ -23,4 +34,4 @@ pages:
- public
only:
- master
{{/DOCUMENTER}}
{{/HAS_DOCUMENTER}}

8
defaults/index.md Normal file
View File

@ -0,0 +1,8 @@
# {{PKG}}
```@index
```
```@autodocs
Modules = [{{PKG}}]
```

31
defaults/make.jl Normal file
View File

@ -0,0 +1,31 @@
using {{PKG}}
using Documenter
makedocs(
modules=[{{PKG}}],
authors="{{AUTHORS}}",
repo="{{REPO}}",
sitename="{{PKG}}.jl",
format=Documenter.HTML(;
canonical="{{CANONICAL}}",
assets={{^HAS_ASSETS}}String{{/HAS_ASSETS}}[{{^HAS_ASSETS}}],{{/HAS_ASSETS}}
{{#ASSETS}}
"{{.}}",
{{/ASSETS}}
{{#HAS_ASSETS}}
],
{{/HAS_ASSETS}}
),
pages=[
"Home" => "index.md",
],
{{#MAKEDOCS_KWARGS}}
{{first}}={{second}},
{{/MAKEDOCS_KWARGS}}
)
{{#HAS_DEPLOY}}
deploydocs(;
repo="{{REPO}}",
)
{{/HAS_DEPLOY}}

6
defaults/runtests.jl Normal file
View File

@ -0,0 +1,6 @@
using {{PKG}}
using Test
@testset "{{PKG}}.jl" begin
# Write your tests here.
end

View File

@ -1,35 +1,46 @@
# Documentation: http://docs.travis-ci.com/user/languages/julia/
# Documentation: http://docs.travis-ci.com/user/languages/julia
language: julia
os:
- linux
- osx
julia:
- {{VERSION}}
- nightly
matrix:
allow_failures:
- julia: nightly
fast_finish: true
notifications:
email: false
{{#COVERAGE}}
after_success:
{{#CODECOV}}
- julia -e 'using Pkg; Pkg.add("Coverage"); using Coverage; Codecov.submit(process_folder())'
{{/CODECOV}}
{{#COVERALLS}}
- julia -e 'using Pkg; Pkg.add("Coverage"); using Coverage; Coveralls.submit(process_folder())'
{{/COVERALLS}}
{{/COVERAGE}}
{{#DOCUMENTER}}
{{#HAS_DOCUMENTER}}
jobs:
fast_finish: true
{{#HAS_NIGHTLY}}
allow_failures:
- julia: nightly
{{/HAS_NIGHTLY}}
include:
{{#JOBS}}
- julia: {{JULIA}}
os: {{OS}}
{{#ARCH}}
arch: {{ARCH}}
{{/ARCH}}
{{/JOBS}}
{{#HAS_DOCUMENTER}}
- stage: Documentation
julia: {{VERSION}}
script: julia --project=docs -e '
using Pkg;
Pkg.develop(PackageSpec(path=pwd()));
Pkg.develop(PackageSpec(; path=pwd()));
Pkg.instantiate();
include("docs/make.jl");'
after_success: skip
{{/DOCUMENTER}}
{{/HAS_DOCUMENTER}}
{{#HAS_COVERAGE}}
after_success:
{{#HAS_CODECOV}}
- julia -e '
using Pkg;
Pkg.add("Coverage");
using Coverage;
Codecov.submit(process_folder());'
{{/HAS_CODECOV}}
{{#HAS_COVERALLS}}
- julia -e '
using Pkg;
Pkg.add("Coverage");
using Coverage;
Coveralls.submit(process_folder());'
{{/HAS_COVERALLS}}
{{/HAS_COVERAGE}}

View File

@ -1,3 +1,5 @@
# This file is machine-generated - editing it directly is not advised
[[Base64]]
uuid = "2a0f44e3-6c83-55bd-87e4-b1978d98bd5f"
@ -6,35 +8,34 @@ deps = ["Printf"]
uuid = "ade2ca70-3891-5945-98fb-dc099432e06a"
[[Distributed]]
deps = ["LinearAlgebra", "Random", "Serialization", "Sockets"]
deps = ["Random", "Serialization", "Sockets"]
uuid = "8ba89e20-285c-5b6f-9357-94700520ee1b"
[[DocStringExtensions]]
deps = ["LibGit2", "Markdown", "Pkg", "Test"]
git-tree-sha1 = "1df01539a1c952cef21f2d2d1c092c2bcf0177d7"
git-tree-sha1 = "0513f1a8991e9d83255e0140aace0d0fc4486600"
uuid = "ffbed154-4ef7-542d-bbb7-c09d3a79fcae"
version = "0.6.0"
version = "0.8.0"
[[Documenter]]
deps = ["Base64", "DocStringExtensions", "InteractiveUtils", "LibGit2", "Logging", "Markdown", "Pkg", "REPL", "Random", "Test", "Unicode"]
git-tree-sha1 = "a6db1c69925cdc53aafb38caec4446be26e0c617"
deps = ["Base64", "DocStringExtensions", "InteractiveUtils", "JSON", "LibGit2", "Logging", "Markdown", "REPL", "Test", "Unicode"]
git-tree-sha1 = "c61d6eedbc3c4323c08b64af12d29c8ee0fcbb5f"
uuid = "e30172f5-a6a5-5a46-863b-614d45cd2de4"
version = "0.21.0"
version = "0.23.2"
[[InteractiveUtils]]
deps = ["LinearAlgebra", "Markdown"]
deps = ["Markdown"]
uuid = "b77e0a4c-d291-57a0-90e8-8db25a27a240"
[[JSON]]
deps = ["Dates", "Mmap", "Parsers", "Unicode"]
git-tree-sha1 = "b34d7cef7b337321e97d22242c3c2b91f476748e"
uuid = "682c06a0-de6a-54ab-a142-c8b1cf79cde6"
version = "0.21.0"
[[LibGit2]]
uuid = "76f85450-5226-5b5a-8eaa-529ad045b433"
[[Libdl]]
uuid = "8f399da3-3557-5675-b5ff-fb832c97cbdb"
[[LinearAlgebra]]
deps = ["Libdl"]
uuid = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e"
[[Logging]]
uuid = "56ddb016-857b-54e1-b83d-db4d58db5568"
@ -42,6 +43,15 @@ uuid = "56ddb016-857b-54e1-b83d-db4d58db5568"
deps = ["Base64"]
uuid = "d6f4376e-aef5-505a-96c1-9c027394607a"
[[Mmap]]
uuid = "a63ad114-7e13-5084-954f-fe012c677804"
[[Parsers]]
deps = ["Dates", "Test"]
git-tree-sha1 = "db2b35dedab3c0e46dc15996d170af07a5ab91c9"
uuid = "69de0a69-1ddd-5017-9359-2bf0b02dc9f0"
version = "0.3.6"
[[Pkg]]
deps = ["Dates", "LibGit2", "Markdown", "Printf", "REPL", "Random", "SHA", "UUIDs"]
uuid = "44cfe95a-1eb2-52ea-b672-e2afdf69b78f"
@ -72,7 +82,7 @@ deps = ["Distributed", "InteractiveUtils", "Logging", "Random"]
uuid = "8dfed614-e22c-5e08-85e1-65c5234f0b40"
[[UUIDs]]
deps = ["Random"]
deps = ["Random", "SHA"]
uuid = "cf7118a7-6976-5b1a-9a39-7adc72f591a4"
[[Unicode]]

View File

@ -72,8 +72,7 @@ t = Template(;
Codecov(),
Coveralls(),
AppVeyor(),
GitHubPages(),
CirrusCI(),
Documenter{TravisCI}(),
],
)
generate("MyPkg2", t)
@ -103,5 +102,4 @@ include, you are encouraged to use [AttoBot](https://github.com/apps/attobot) in
## Contributing
It's extremely easy to extend `PkgTemplates` with new plugins. To get started,
check out the
[plugin development guide](https://invenia.github.io/PkgTemplates.jl/stable/pages/plugin_development.html).
check out [Plugin Development](@ref).

View File

@ -4,17 +4,11 @@ CurrentModule = PkgTemplates
# Licenses
[Many open-source licenses](https://github.com/christopher-dG/PkgTemplates.jl/tree/master/licenses)
are available for use with `PkgTemplates`, but if you see that one is missing,
don't hesitate to open an issue or PR.
[Many open-source licenses](https://github.com/invenia/PkgTemplates.jl/tree/master/licenses)
are available for use with `PkgTemplates`, but if you see that one is missing, don't
hesitate to open an issue or PR.
```@docs
available_licenses
show_license
```
### Helper Functions
```@docs
read_license
```

View File

@ -7,25 +7,9 @@ CurrentModule = PkgTemplates
Creating new packages with `PkgTemplates` revolves around creating a new
[`Template`](@ref), then calling [`generate`](@ref) on it.
## `Template`
```@docs
Template
interactive_template
```
## `generate`
```@docs
generate
generate_interactive
```
### Helper Functions
```@docs
gen_tests
gen_readme
gen_gitignore
gen_license
```

View File

@ -4,72 +4,69 @@ CurrentModule = PkgTemplates
# Plugin Development
The best and easiest way to contribute to `PkgTemplates` is to write new
plugins.
The best and easiest way to contribute to `PkgTemplates` is to write new plugins.
```@docs
Plugin
```
## Generic Plugins
## "Generic" Plugins
Many plugins fall into the category of managing some configuration file.
Think Travis CI's `.travis.yml`, and so on for every CI service ever.
For these one-file plugins, a shortcut macro is available to define a plugin in one line.
```@docs
GenericPlugin
GeneratedPlugin
@plugin
```
### `GeneratedPlugin` Customization
When you generate a plugin type with [`@plugin`](@ref), all required methods are
implemented for you. However, you're still allowed to override behaviour if you so desire.
These are the relevant methods:
```@docs
source
destination
gitignore
badges
view
```
For some examples, see
[`generated.jl`](https://github.com/invenia/PkgTemplates.jl/tree/master/src/plugins/generated.jl).
## Custom Plugins
```@docs
CustomPlugin
```
When a plugin is too complicated to be expressed with [`GeneratedPlugin`](@ref), we only
need to implement a few methods to create something fully custom.
### `CustomPlugin` Required Methods
#### `gen_plugin`
### Required Methods
```@docs
gen_plugin
```
### Optional Methods
```@docs
interactive
```
**Note**: [`interactive`](@ref) is not strictly required, however without it,
your custom plugin will not be available when creating templates with
[`interactive_template`](@ref).
Additionally, [`gitignore`](@ref), [`badges`](@ref), and [`view`](@ref) can also be
implemented in the same way as for [`GeneratedPlugin`](@ref)s (they have empty default
implementations). [`source`](@ref) and [`destination`](@ref) have no meaning for custom
plugins.
#### `badges`
### Helpers
```@docs
badges
```
## Helper Types/Functions
#### `gen_file`
```@docs
gen_file
```
#### `substitute`
```@docs
substitute
```
#### `Badge`
These types and functions will make implementing the above methods much easier.
```@docs
Badge
```
#### `format`
```@docs
format
```
#### `version_floor`
```@docs
gen_file
substitute
version_floor
```

View File

@ -28,6 +28,4 @@ Coveralls
```@docs
Documenter
GitHubPages
GitLabPages
```

View File

@ -1,36 +1,34 @@
module PkgTemplates
using Dates
using InteractiveUtils
using LibGit2
using Mustache
using Pkg
using REPL.TerminalMenus
using URIParser
using Base: @kwdef, current_project
using Base.Filesystem: contractuser
using Dates: month, today, year
using InteractiveUtils: subtypes
using LibGit2: LibGit2
using Mustache: render
using Pkg: PackageSpec, Pkg
using REPL.TerminalMenus: MultiSelectMenu, RadioMenu, request
using URIParser: URI
export
# Template/package generation.
Template,
generate,
interactive_template,
generate_interactive,
# Licenses.
show_license,
available_licenses,
# Plugins.
GitHubPages,
GitLabPages,
AppVeyor,
TravisCI,
GitLabCI,
CirrusCI,
Citation,
Codecov,
Coveralls,
Citation
Documenter,
Gitignore,
GitLabCI,
License,
Readme,
Tests,
TravisCI
const DEFAULT_VERSION = VersionNumber(VERSION.major)
"""
A plugin to be added to a [`Template`](@ref), which adds some functionality or integration.
New plugins should almost always extend [`GenericPlugin`](@ref) or [`CustomPlugin`](@ref).
"""
abstract type Plugin end
@ -38,18 +36,6 @@ include("licenses.jl")
include("template.jl")
include("generate.jl")
include("plugin.jl")
include(joinpath("plugins", "documenter.jl"))
include(joinpath("plugins", "coveralls.jl"))
include(joinpath("plugins", "appveyor.jl"))
include(joinpath("plugins", "codecov.jl"))
include(joinpath("plugins", "travisci.jl"))
include(joinpath("plugins", "gitlabci.jl"))
include(joinpath("plugins", "cirrusci.jl"))
include(joinpath("plugins", "githubpages.jl"))
include(joinpath("plugins", "gitlabpages.jl"))
include(joinpath("plugins", "citation.jl"))
const DEFAULTS_DIR = normpath(joinpath(@__DIR__, "..", "defaults"))
const BADGE_ORDER = [GitHubPages, GitLabPages, TravisCI, AppVeyor, GitLabCI, Codecov, Coveralls]
include("interactive.jl")
end

View File

@ -1,16 +1,12 @@
"""
generate(pkg::AbstractString, t::Template) -> Nothing
generate(t::Template, pkg::AbstractString) -> Nothing
const TEST_UUID = "8dfed614-e22c-5e08-85e1-65c5234f0b40"
Generate a package named `pkg` from `t`. If `git` is `false`, no Git repository is created.
"""
function generate(
pkg::AbstractString,
t::Template;
git::Bool=true,
gitconfig::Union{GitConfig, Nothing}=nothing,
)
pkg = splitjl(pkg)
(::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"))
@ -18,9 +14,9 @@ function generate(
# Create the directory with some boilerplate inside.
Pkg.generate(pkg_dir)
# Add a [compat] section for Julia.
# Add a [compat] section for Julia. By default, Julia 1.x is supported.
open(joinpath(pkg_dir, "Project.toml"), "a") do io
println(io, "\n[compat]\njulia = $(repr_version(t.julia_version))")
println(io, "\n[compat]\njulia = \"1\"")
end
# Replace the authors field with the template's authors.
@ -31,329 +27,35 @@ function generate(
write(path, replace(project, r"authors = .*" => "authors = $authors"))
end
if git
# Initialize the repo.
if t.git
# Initialize the repo, make a commit, and set the remote.
repo = LibGit2.init(pkg_dir)
@info "Initialized Git repo at $pkg_dir"
if gitconfig !== nothing
# Configure the repo.
repoconfig = GitConfig(repo)
for c in LibGit2.GitConfigIter(gitconfig)
LibGit2.set!(repoconfig, unsafe_string(c.name), unsafe_string(c.value))
end
end
# Commit and set the remote.
LibGit2.commit(repo, "Initial commit")
rmt = if t.ssh
"git@$(t.host):$(t.user)/$pkg.jl.git"
else
"https://$(t.host)/$(t.user)/$pkg.jl"
end
# We need to set the remote in a strange way, see #8.
close(LibGit2.GitRemote(repo, "origin", rmt))
@info "Set remote origin to $rmt"
# Create the gh-pages branch if necessary.
if haskey(t.plugins, GitHubPages)
LibGit2.branch!(repo, "gh-pages")
LibGit2.commit(repo, "Initial commit")
@info "Created empty gh-pages branch"
LibGit2.branch!(repo, "master")
end
end
# Generate the files.
files = vcat(
"src/", "Project.toml", # Created by Pkg.generate.
gen_tests(pkg_dir, t),
gen_readme(pkg_dir, t),
gen_license(pkg_dir, t),
vcat(map(p -> gen_plugin(p, t, pkg), values(t.plugins))...),
)
foreach(p -> gen_plugin(p, t, pkg_dir), values(t.plugins))
if git
append!(files, gen_gitignore(pkg_dir, t))
LibGit2.add!(repo, files...)
if t.git
# Commit the files.
LibGit2.add!(repo, ".")
LibGit2.commit(repo, "Files generated by PkgTemplates")
@info "Committed $(length(files)) files/directories: $(join(files, ", "))"
if length(collect(LibGit2.GitBranchIter(repo))) > 1
@info "Remember to push all created branches to your remote: git push --all"
end
end
if t.dev
# Add the new package to the current environment.
Pkg.develop(PackageSpec(path=pkg_dir))
Pkg.develop(PackageSpec(; path=pkg_dir))
end
@info "New package is at $pkg_dir"
catch e
rm(pkg_dir; recursive=true)
rethrow(e)
catch
rm(pkg_dir; recursive=true, force=true)
rethrow()
end
end
function generate(
t::Template,
pkg::AbstractString;
git::Bool=true,
gitconfig::Union{GitConfig, Nothing}=nothing,
)
generate(pkg, t; git=git, gitconfig=gitconfig)
end
"""
generate_interactive(pkg::AbstractString; fast::Bool=false, git::Bool=true) -> Template
Interactively create a template, and then generate a package with it. Arguments and
keywords are used in the same way as in [`generate`](@ref) and
[`interactive_template`](@ref).
"""
function generate_interactive(
pkg::AbstractString;
fast::Bool=false,
git::Bool=true,
gitconfig::Union{GitConfig, Nothing}=nothing,
)
t = interactive_template(; git=git, fast=fast)
generate(pkg, t; git=git, gitconfig=gitconfig)
return t
end
"""
gen_tests(pkg_dir::AbstractString, t::Template) -> Vector{String}
Create the test entrypoint in `pkg_dir`.
# Arguments
* `pkg_dir::AbstractString`: The package directory in which the files will be generated
* `t::Template`: The template whose tests we are generating.
Returns an array of generated file/directory names.
"""
function gen_tests(pkg_dir::AbstractString, t::Template)
# TODO: Silence Pkg for this section? Adding and removing Test creates a lot of noise.
proj = Base.current_project()
try
Pkg.activate(pkg_dir)
Pkg.add("Test")
# Move the Test dependency into the [extras] section.
toml = read(joinpath(pkg_dir, "Project.toml"), String)
lines = split(toml, "\n")
idx = findfirst(l -> startswith(l, "Test = "), lines)
testdep = lines[idx]
deleteat!(lines, idx)
toml = join(lines, "\n") * """
[extras]
$testdep
[targets]
test = ["Test"]
"""
gen_file(joinpath(pkg_dir, "Project.toml"), toml)
Pkg.update() # Regenerate Manifest.toml (this cleans up Project.toml too).
finally
proj === nothing ? Pkg.activate() : Pkg.activate(proj)
end
pkg = basename(pkg_dir)
text = """
using $pkg
using Test
@testset "$pkg.jl" begin
# Write your own tests here.
end
"""
gen_file(joinpath(pkg_dir, "test", "runtests.jl"), text)
return ["test/"]
end
"""
gen_readme(pkg_dir::AbstractString, t::Template) -> Vector{String}
Create a README in `pkg_dir` with badges for each enabled plugin.
# Arguments
* `pkg_dir::AbstractString`: The directory in which the files will be generated.
* `t::Template`: The template whose README we are generating.
Returns an array of generated file/directory names.
"""
function gen_readme(pkg_dir::AbstractString, t::Template)
pkg = basename(pkg_dir)
text = "# $pkg\n"
done = []
# Generate the ordered badges first, then add any remaining ones to the right.
for plugin_type in BADGE_ORDER
if haskey(t.plugins, plugin_type)
text *= "\n"
text *= join(
badges(t.plugins[plugin_type], t.user, pkg),
"\n",
)
push!(done, plugin_type)
end
end
for plugin_type in setdiff(keys(t.plugins), done)
text *= "\n"
text *= join(
badges(t.plugins[plugin_type], t.user, pkg),
"\n",
)
end
if haskey(t.plugins, Citation) && t.plugins[Citation].readme_section
text *= "\n## Citing\n\nSee `CITATION.bib` for the relevant reference(s).\n"
end
gen_file(joinpath(pkg_dir, "README.md"), text)
return ["README.md"]
end
"""
gen_gitignore(pkg_dir::AbstractString, t::Template) -> Vector{String}
Create a `.gitignore` in `pkg_dir`.
# Arguments
* `pkg_dir::AbstractString`: The directory in which the files will be generated.
* `t::Template`: The template whose .gitignore we are generating.
Returns an array of generated file/directory names.
"""
function gen_gitignore(pkg_dir::AbstractString, t::Template)
pkg = basename(pkg_dir)
init = [".DS_Store", "/dev/"]
entries = mapfoldl(p -> p.gitignore, append!, values(t.plugins); init=init)
if !t.manifest && !in("Manifest.toml", entries)
push!(entries, "/Manifest.toml") # Only ignore manifests at the repo root.
end
unique!(sort!(entries))
text = join(entries, "\n")
gen_file(joinpath(pkg_dir, ".gitignore"), text)
files = [".gitignore"]
t.manifest && push!(files, "Manifest.toml")
return files
end
"""
gen_license(pkg_dir::AbstractString, t::Template) -> Vector{String}
Create a license in `pkg_dir`.
# Arguments
* `pkg_dir::AbstractString`: The directory in which the files will be generated.
* `t::Template`: The template whose LICENSE we are generating.
Returns an array of generated file/directory names.
"""
function gen_license(pkg_dir::AbstractString, t::Template)
if isempty(t.license)
return String[]
end
text = "Copyright (c) $(year(today())) $(t.authors)\n"
text *= read_license(t.license)
gen_file(joinpath(pkg_dir, "LICENSE"), text)
return ["LICENSE"]
end
"""
gen_file(file::AbstractString, text::AbstractString) -> Int
Create a new file containing some given text. Always ends the file with a newline.
# Arguments
* `file::AbstractString`: Path to the file to be created.
* `text::AbstractString`: Text to write to the file.
Returns the number of bytes written to the file.
"""
function gen_file(file::AbstractString, text::AbstractString)
mkpath(dirname(file))
if !endswith(text , "\n")
text *= "\n"
end
return write(file, text)
end
"""
version_floor(v::VersionNumber=VERSION) -> String
Format the given Julia version.
# Keyword arguments
* `v::VersionNumber=VERSION`: Version to floor.
Returns "major.minor" for the most recent release version relative to v. For prereleases
with v.minor == v.patch == 0, returns "major.minor-".
"""
function version_floor(v::VersionNumber=VERSION)
return if isempty(v.prerelease) || v.patch > 0
"$(v.major).$(v.minor)"
else
"$(v.major).$(v.minor)-"
end
end
"""
substitute(template::AbstractString, view::Dict{String, Any}) -> String
substitute(
template::AbstractString,
pkg_template::Template;
view::Dict{String, Any}=Dict{String, Any}(),
) -> String
Replace placeholders in `template` with values in `view` via
[`Mustache`](https://github.com/jverzani/Mustache.jl). `template` is not modified.
If `pkg_template` is supplied, some default replacements are also performed.
For information on how to structure `template`, see "Defining Template Files" section in
[Custom Plugins](@ref).
**Note**: Conditionals in `template` without a corresponding key in `view` won't error,
but will simply be evaluated as false.
"""
substitute(template::AbstractString, view::Dict{String, Any}) = render(template, view)
function substitute(
template::AbstractString,
pkg_template::Template;
view::Dict{String, Any}=Dict{String, Any}(),
)
# Don't use version_floor here because we don't want the trailing '-' on prereleases.
v = pkg_template.julia_version
d = Dict{String, Any}(
"USER" => pkg_template.user,
"VERSION" => "$(v.major).$(v.minor)",
"DOCUMENTER" => any(map(p -> isa(p, Documenter), values(pkg_template.plugins))),
"CODECOV" => haskey(pkg_template.plugins, Codecov),
"COVERALLS" => haskey(pkg_template.plugins, Coveralls),
)
# d["AFTER"] is true whenever something needs to occur in a CI "after_script".
d["AFTER"] = d["DOCUMENTER"] || d["CODECOV"] || d["COVERALLS"]
# d["COVERAGE"] is true whenever a coverage plugin is enabled.
# TODO: This doesn't handle user-defined coverage plugins.
# Maybe we need an abstract CoveragePlugin <: GenericPlugin?
d["COVERAGE"] = d["CODECOV"] || d["COVERALLS"]
return substitute(template, merge(d, view))
end
splitjl(pkg::AbstractString) = endswith(pkg, ".jl") ? pkg[1:end-3] : pkg
# Format a version in a way suitable for a Project.toml file.
function repr_version(v::VersionNumber)
s = string(v.major)
v.minor == 0 || (s *= ".$(v.minor)")
v.patch == 0 || (s *= ".$(v.patch)")
return repr(s)
end

152
src/interactive.jl Normal file
View File

@ -0,0 +1,152 @@
# Printing utils.
const TAB = repeat(' ', 4)
const HALFTAB = repeat(' ', 2)
const DOT = ""
const ARROW = ""
const PLUGIN_TYPES = let
leaves(T::Type) = isabstracttype(T) ? vcat(map(leaves, subtypes(T))...) : [T]
leaves(Plugin)
end
yesno(x::Bool) = x ? "Yes" : "No"
maybe_string(s::AbstractString) = isempty(s) ? "None" : string(s)
"""
interactive(T::Type{<:Plugin}) -> T
Interactively create a plugin of type `T`.
When this method is implemented for a type, it becomes available to [`Template`](@ref)s created with `interactive=true`.
"""
function interactive end
function make_template(::Val{true}; kwargs...)
@info "Default values are shown in [brackets]"
opts = Dict{Symbol, Any}()
fast = get(kwargs, :fast, false)
opts[:user] = get(kwargs, :user) do
default = defaultkw(:user)
default = isempty(default) ? nothing : default
prompt_string("Username", default)
end
git = opts[:git] = get(kwargs, :git) do
default = defaultkw(:git)
fast ? default : prompt_bool("Create Git repositories for packages", default)
end
opts[:host] = get(kwargs, :host) do
default = defaultkw(:host)
if fast || !git
default
else
prompt_string("Code hosting service", default)
end
end
opts[:license] = get(kwargs, :license) do
default = defaultkw(:license)
if fast
default
else
# TODO: Break this out into something reusable?
choices = String["None"; split(sprint(available_licenses), "\n")]
licenses = ["" => "", pairs(LICENSES)...]
menu = RadioMenu(choices)
first(licenses[request("License:", menu)])
end
end
opts[:authors] = get(kwargs, :authors) do
default = defaultkw(:authors)
if fast || !git
default
else
prompt_string("Package author(s)", isempty(default) ? "None" : default)
end
end
opts[:dir] = get(kwargs, :dir) do
default = defaultkw(:dir)
fast ? default : prompt_string("Path to package directory", default)
end
opts[:julia_version] = get(kwargs, :julia_version) do
default = defaultkw(:julia_version)
if fast
default
else
VersionNumber(prompt_string("Minimum Julia version", string(default)))
end
end
opts[:ssh] = get(kwargs, :ssh) do
default = defaultkw(:ssh)
fast || !git ? default : prompt_bool("Set remote to SSH", default)
end
opts[:manifest] = get(kwargs, :manifest) do
default = defaultkw(:manifest)
fast || !git ? default : prompt_bool("Commit Manifest.toml", default)
end
opts[:develop] = get(kwargs, :develop) do
default = defaultkw(:develop)
fast || !git ? default : prompt_bool("Develop generated packages", default)
end
opts[:plugins] = get(kwargs, :plugins) do
# TODO: Break this out into something reusable?
types = filter(T -> applicable(interactive, T), PLUGIN_TYPES)
menu = MultiSelectMenu(map(string nameof, types))
selected = types[collect(request("Plugins:", menu))]
map(interactive, selected)
end
return make_template(Val(false); opts...)
end
prompt_string(s::AbstractString, default=nothing) = prompt(string, s, default)
function prompt_bool(s::AbstractString, default=nothing)
return prompt(s, default) do answer
answer = lowercase(answer)
if answer in ["yes", "true", "y", "t"]
true
elseif answer in ["no", "false", "n", "f"]
false
else
throw(ArgumentError("Invalid yes/no response"))
end
end
end
function prompt(f::Function, s::AbstractString, default)
required = default === nothing
default_display = default isa Bool ? yesno(default) : default
print(s, " [", required ? "REQUIRED" : default_display, "]: ")
answer = readline()
return if isempty(answer)
required && throw(ArgumentError("This argument is required"))
default
else
f(answer)
end
end
function prompt_config(T::Type{<:BasicPlugin})
s = "$(nameof(T)): Source file template path"
default = source(T)
default === nothing && (s *= " (\"None\" for no file)")
answer = prompt_string(s, default === nothing ? "None" : contractuser(default))
return if lowercase(answer) == "none"
nothing
elseif isempty(answer)
default
else
answer
end
end

View File

@ -1,45 +0,0 @@
const LICENSE_DIR = normpath(joinpath(@__DIR__, "..", "licenses"))
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+",
)
"""
available_licenses([io::IO]) -> Nothing
Print the names of all available licenses.
"""
available_licenses(io::IO) = print(io, join(("$k: $v" for (k, v) in LICENSES), "\n"))
available_licenses() = available_licenses(stdout)
"""
show_license([io::IO], license::AbstractString) -> Nothing
Print the text of `license`. Errors if the license is not found.
"""
show_license(io::IO, license::AbstractString) = print(io, read_license(license))
show_license(license::AbstractString) = show_license(stdout, license)
"""
read_license(license::AbstractString) -> String
Returns the contents of `license`. Errors if the license is not found. Use
[`available_licenses`](@ref) to view available licenses.
"""
function read_license(license::AbstractString)
path = joinpath(LICENSE_DIR, license)
if isfile(path)
return string(readchomp(path))
else
throw(ArgumentError("License '$license' is not available"))
end
end

View File

@ -1,153 +1,51 @@
"""
Generic plugins are plugins that add any number of patterns to the generated package's
`.gitignore`, and have at most one associated file to generate.
const DEFAULTS_DIR = normpath(joinpath(@__DIR__, "..", "defaults"))
# Attributes
* `gitignore::Vector{AbstractString}`: Array of patterns to be added to the `.gitignore` of
generated packages that use this plugin.
* `src::Union{AbstractString, Nothing}`: Path to the file that will be copied into the generated
package repository. If set to `nothing`, no file will be generated. When this defaults
to an empty string, there should be a default file in `defaults` that will be copied.
That file's name is usually the same as the plugin's name, except in all lowercase and
with the `.yml` extension. If this is not the case, an `interactive` method needs to be
implemented to call `interactive(; file="file.ext")`.
* `dest::AbstractString`: Path to the generated file, relative to the root of the generated
package repository.
* `badges::Vector{Badge}`: Array of [`Badge`](@ref)s containing information used to
create Markdown-formatted badges from the plugin. Entries will be run through
[`substitute`](@ref), so they may contain placeholder values.
* `view::Dict{String, Any}`: Additional substitutions to make in both the plugin's badges
and its associated file. See [`substitute`](@ref) for details.
abstract type BasicPlugin <: Plugin end
# Example
```julia
struct MyPlugin <: GenericPlugin
gitignore::Vector{AbstractString}
src::Union{AbstractString, Nothing}
dest::AbstractString
badges::Vector{Badge}
view::Dict{String, Any}
function MyPlugin(; config_file::Union{AbstractString, Nothing}="")
if config_file != nothing
config_file = if isempty(config_file)
joinpath(DEFAULTS_DIR, "my-plugin.toml")
elseif isfile(config_file)
abspath(config_file)
else
throw(ArgumentError(
"File \$(abspath(config_file)) does not exist"
))
end
end
new(
["*.mgp"],
config_file,
".my-plugin.toml",
[
Badge(
"My Plugin",
"https://myplugin.com/badge-{{YEAR}}.png",
"https://myplugin.com/{{USER}}/{{PKGNAME}}.jl",
),
],
Dict{String, Any}("YEAR" => year(today())),
)
end
end
interactive(::Type{MyPlugin}) = interactive(MyPlugin; file="my-plugin.toml")
```
The above plugin ignores files ending with `.mgp`, copies `defaults/my-plugin.toml` by
default, and creates a badge that links to the project on its own site, using the default
substitutions with one addition: `{{YEAR}} => year(today())`. Since the default config
template file doesn't follow the generic naming convention, we added another `interactive`
method to correct the assumed filename.
"""
abstract type GenericPlugin <: Plugin end
function Base.show(io::IO, p::GenericPlugin)
spc = " "
println(io, nameof(typeof(p)), ":")
cfg = if p.src === nothing
"None"
else
dirname(p.src) == DEFAULTS_DIR ? "Default" : p.src
end
println(io, spc, "→ Config file: ", cfg)
n = length(p.gitignore)
s = n == 1 ? "" : "s"
print(io, spc, "$n gitignore entrie$s")
n > 0 && print(io, ": ", join(map(repr, p.gitignore), ", "))
end
default_file(paths::AbstractString...) = joinpath(DEFAULTS_DIR, paths...)
"""
Custom plugins are plugins whose behaviour does not follow the [`GenericPlugin`](@ref)
pattern. They can implement [`gen_plugin`](@ref), [`badges`](@ref), and
[`interactive`](@ref) in any way they choose, as long as they conform to the usual type
signature.
view(::Plugin, ::Template, pkg::AbstractString) -> Dict{String}
# Attributes
* `gitignore::Vector{AbstractString}`: Array of patterns to be added to the `.gitignore` of
generated packages that use this plugin.
# Example
```julia
struct MyPlugin <: CustomPlugin
gitignore::Vector{AbstractString}
lucky::Bool
MyPlugin() = new([], rand() > 0.8)
function gen_plugin(p::MyPlugin, t::Template, pkg_name::AbstractString)
return if p.lucky
text = substitute("You got lucky with {{PKGNAME}}, {{USER}}!", t)
gen_file(joinpath(t.dir, pkg_name, ".myplugin.yml"), text)
[".myplugin.yml"]
else
println("Maybe next time.")
String[]
end
end
function badges(p::MyPlugin, user::AbstractString, pkg_name::AbstractString)
return if p.lucky
[
format(Badge(
"You got lucky!",
"https://myplugin.com/badge.png",
"https://myplugin.com/\$user/\$pkg_name.jl",
)),
]
else
String[]
end
end
end
interactive(:Type{MyPlugin}) = MyPlugin()
```
This plugin doesn't do much, but it demonstrates how [`gen_plugin`](@ref), [`badges`](@ref)
and [`interactive`](@ref) can be implemented using [`substitute`](@ref),
[`gen_file`](@ref), [`Badge`](@ref), and [`format`](@ref).
# Defining Template Files
Often, the contents of the config file that your plugin generates depends on variables like
the package name, the user's username, etc. Template files (which are stored in `defaults`)
can use [here](https://github.com/jverzani/Mustache.jl)'s syntax to define replacements.
Return extra string substitutions to be made for this plugin.
"""
abstract type CustomPlugin <: Plugin end
view(::Plugin, ::Template, ::AbstractString) = Dict{String, Any}()
"""
gitignore(::Plugin) -> Vector{String}
Return patterns that should be added to `.gitignore`.
"""
gitignore(::Plugin) = String[]
"""
badges(::Plugin) -> Union{Badge, Vector{Badge}}
Return a list of [`Badge`](@ref)s, or just one, to be added to `README.md`.
"""
badges(::Plugin) = Badge[]
"""
source(::BasicPlugin) -> Union{String, Nothing}
Return the path to a plugin's configuration file template, or `nothing` to indicate no file.
"""
source(::BasicPlugin) = nothing
"""
destination(::BasicPlugin) -> String
Return the destination, relative to the package root, of a plugin's configuration file.
"""
function destination end
"""
Badge(hover::AbstractString, image::AbstractString, link::AbstractString) -> Badge
A `Badge` contains the data necessary to generate a Markdown badge.
Container for Markdown badge data.
Each argument can contain placeholders.
# Arguments
## Arguments
* `hover::AbstractString`: Text to appear when the mouse is hovered over the badge.
* `image::AbstractString`: URL to the image to display.
* `link::AbstractString`: URL to go to upon clicking the badge.
@ -158,89 +56,70 @@ struct Badge
link::String
end
"""
format(b::Badge) -> String
Base.string(b::Badge) = "[![$(b.hover)]($(b.image))]($(b.link))"
Return `badge`'s data formatted as a Markdown string.
"""
format(b::Badge) = "[![$(b.hover)]($(b.image))]($(b.link))"
# Format a plugin's badges as a list of strings, with all substitutions applied.
function badges(p::Plugin, t::Template, pkg_name::AbstractString)
bs = badges(p)
bs isa Vector || (bs = [bs])
bs = map(string, bs)
# TODO render
end
"""
gen_plugin(p::Plugin, t::Template, pkg_name::AbstractString) -> Vector{String}
gen_plugin(p::Plugin, t::Template, pkg::AbstractString) -> Nothing
Generate any files associated with a plugin.
# Arguments
## Arguments
* `p::Plugin`: Plugin whose files are being generated.
* `t::Template`: Template configuration.
* `pkg_name::AbstractString`: Name of the package.
Returns an array of generated file/directory names.
* `pkg::AbstractString`: Name of the package.
"""
gen_plugin(::Plugin, ::Template, ::AbstractString) = String[]
gen_plugin(::Plugin, ::Template, ::AbstractString) = nothing
function gen_plugin(p::GenericPlugin, t::Template, pkg_name::AbstractString)
if p.src === nothing
return String[]
end
text = substitute(
read(p.src, String),
t;
view=merge(Dict("PKGNAME" => pkg_name), p.view),
)
gen_file(joinpath(t.dir, pkg_name, p.dest), text)
return [p.dest]
function gen_plugin(p::BasicPlugin, t::Template, pkg::AbstractString)
source(p) === nothing && return
text = render(source(p), view(p, t, pkg); tags=tags(p))
gen_file(joinpath(t.dir, pkg_name, destination(p)), text)
end
"""
badges(p::Plugin, user::AbstractString, pkg_name::AbstractString) -> Vector{String}
gen_file(file::AbstractString, text::AbstractString) -> Int
Generate Markdown badges for the plugin.
# Arguments
* `p::Plugin`: Plugin whose badges we are generating.
* `user::AbstractString`: Username of the package creator.
* `pkg_name::AbstractString`: Name of the package.
Returns an array of Markdown badges.
Create a new file containing some given text.
Trailing whitespace is removed, and the file will end with a newline.
"""
badges(::Plugin, ::AbstractString, ::AbstractString) = String[]
function badges(p::GenericPlugin, user::AbstractString, pkg_name::AbstractString)
# Give higher priority to replacements defined in the plugin's view.
view = merge(Dict("USER" => user, "PKGNAME" => pkg_name), p.view)
return map(b -> substitute(format(b), view), p.badges)
function gen_file(file::AbstractString, text::AbstractString)
mkpath(dirname(file))
text = join(map(rstrip, split(text, "\n")), "\n")
endswith(text , "\n") || (text *= "\n")
write(file, text)
end
"""
interactive(T::Type{<:Plugin}; file::Union{AbstractString, Nothing}="") -> Plugin
render_file(file::AbstractString, view, tags) = render_text(read(file, String), view, tags)
Interactively create a plugin of type `T`, where `file` is the plugin type's default
config template with a non-standard name (for `MyPlugin`, this is anything but
"myplugin.yml").
"""
function interactive(T::Type{<:GenericPlugin}; file::Union{AbstractString, Nothing}="")
name = string(nameof(T))
# By default, we expect the default plugin file template for a plugin called
# "MyPlugin" to be called "myplugin.yml".
fn = file != nothing && isempty(file) ? "$(lowercase(name)).yml" : file
default_config_file = fn == nothing ? fn : joinpath(DEFAULTS_DIR, fn)
render_text(text::AbstractString, view, tags) = render(text, view; tags=tags)
print("$name: Enter the config template filename (\"None\" for no file) ")
if default_config_file == nothing
print("[None]: ")
else
print("[", replace(default_config_file, homedir() => "~"), "]: ")
end
config_file = readline()
config_file = if uppercase(config_file) == "NONE"
nothing
elseif isempty(config_file)
default_config_file
else
config_file
end
return T(; config_file=config_file)
function render_badges(p::BasicPlugin, t::Template, pkg::AbstractString)
end
function render_plugin(p::BasicPlugin, t::Template, pkg::AbstractString)
render_file(source(p), view(p, t, pkg), tags(p))
end
include(joinpath("plugins", "essentials.jl"))
include(joinpath("plugins", "coverage.jl"))
include(joinpath("plugins", "ci.jl"))
include(joinpath("plugins", "citation.jl"))
include(joinpath("plugins", "documenter.jl"))
const BADGE_ORDER = [
Documenter{GitLabCI},
Documenter{TravisCI},
TravisCI,
AppVeyor,
GitLabCI,
Codecov,
Coveralls,
]

View File

@ -1,42 +0,0 @@
"""
AppVeyor(; config_file::Union{AbstractString, Nothing}="") -> AppVeyor
Add `AppVeyor` to a template's plugins to add a `.appveyor.yml` configuration file to
generated repositories, and an appropriate badge to the README.
# Keyword Arguments
* `config_file::Union{AbstractString, Nothing}=""`: Path to a custom `.appveyor.yml`.
If `nothing` is supplied, no file will be generated.
"""
struct AppVeyor <: GenericPlugin
gitignore::Vector{String}
src::Union{String, Nothing}
dest::String
badges::Vector{Badge}
view::Dict{String, Any}
function AppVeyor(; config_file::Union{AbstractString, Nothing}="")
if config_file != nothing
config_file = if isempty(config_file)
config_file = joinpath(DEFAULTS_DIR, "appveyor.yml")
elseif isfile(config_file)
abspath(config_file)
else
throw(ArgumentError("File $(abspath(config_file)) does not exist"))
end
end
new(
[],
config_file,
".appveyor.yml",
[
Badge(
"Build Status",
"https://ci.appveyor.com/api/projects/status/github/{{USER}}/{{PKGNAME}}.jl?svg=true",
"https://ci.appveyor.com/project/{{USER}}/{{PKGNAME}}-jl",
)
],
Dict{String, Any}(),
)
end
end

140
src/plugins/ci.jl Normal file
View File

@ -0,0 +1,140 @@
const DEFAULT_CI_VERSIONS = ["1.0", "nightly"]
const VersionsOrStrings = Vector{Union{VersionNumber, String}}
format_version(v::VersionNumber) = "$(v.major).$(v.minor)"
function collect_versions(versions::Vector, t::Template)
return unique(sort([versions; format_version(t.julia_version)]; by=string))
end
abstract type CI <: Plugin end
@kwdef struct TravisCI <: CI
file::String = default_file("travis.yml")
linux::Bool = true
osx::Bool = true
windows::Bool = true
x86::Bool = false
coverage::Bool = true
extra_versions::VersionsOrStrings = DEFAULT_CI_VERSIONS
end
source(p::TravisCI) = p.file
destination(::TravisCI) = ".travis.yml"
badges(::TravisCI) = Badge(
"Build Status",
"https://travis-ci.com/{{USER}}/{{PKG}}.jl.svg?branch=master",
"https://travis-ci.com/{{USER}}/{{PKG}}.jl",
)
function view(p::TravisCI, t::Template, ::AbstractString)
jobs = Dict{String, String}[]
for v in collect_versions(p.extra_versions, t)
p.linux && push!(jobs, Dict("JULIA" => v, "OS" => "linux", "ARCH" => "x64"))
p.linux && p.x86 && push!(jobs, Dict("JULIA" => v, "OS" => "linux", "ARCH" => "x86"))
p.osx && push!(jobs, Dict("JULIA" => v, "OS" => "osx"))
p.windows && push!(jobs, Dict("JULIA" => v, "OS" => "windows", "ARCH" => "x64"))
p.windows && p.x86 && push!(jobs, Dict("JULIA" => v, "OS" => "windows", "ARCH" => "x86"))
end
return Dict(
"HAS_CODECOV" => hasplugin(t, Codecov),
"HAS_COVERAGE" => p.coverage && hasplugin(t, Coverage),
"HAS_COVERALLS" => hasplugin(t, Coveralls),
"HAS_DOCUMENTER" => hasplugin(t, Documenter{TravisCI}),
"HAS_NIGHTLY" => "nightly" in versions,
"PKG" => pkg,
"VERSION" => format_version(t.julia_version),
)
end
@kwdef struct AppVeyor <: CI
file::String = default_file("appveyor.yml")
x86::Bool = false
coverage::Bool = true
extra_versions::VersionsOrStrings = DEFAULT_CI_VERSIONS
end
source(p::AppVeyor) = p.file
destination(::AppVeyor) = ".appveyor.yml"
badges(::AppVeyor) = Badge(
"Build Status",
"https://ci.appveyor.com/api/projects/status/github/{{USER}}/{{PKG}}.jl?svg=true",
"https://ci.appveyor.com/project/{{USER}}/{{PKG}}-jl",
)
function view(p::AppVeyor, t::Template, ::AbstractString)
platforms = ["x64"]
t.x86 && push!(platforms, "x86")
return Dict(
"HAS_CODECOV" => t.coverage && hasplugin(t, Codecov),
"HAS_NIGHTLY" => "nightly" in versions,
"PKG" => pkg,
"PLATFORMS" => os,
"VERSIONS" => collect_versions(p.extra_versions, t),
)
end
@kwdef struct CirrusCI <: CI
file::String = default_file("cirrus.yml")
image::String = "freebsd-12-0-release-amd64"
coverage::Bool = true
extra_versions::VersionsOrStrings = DEFAULT_CI_VERSIONS
end
source(p::CirrusCI) = p.file
destination(::CirrusCI) = ".cirrus.yml"
badges(::CirrusCI) = Badge(
"Build Status",
"https://api.cirrus-ci.com/github/{{USER}}/{{PACKAGE}}.jl.svg",
"https://cirrus-ci.com/github/{{USER}}/{{PKG}}.jl",
)
function view(p::CirrusCI, t::Template, ::AbstractString)
return Dict(
"HAS_CODECOV" => hasplugin(t, Codecov),
"HAS_COVERALLS" => hasplugin(t, Coveralls),
"HAS_COVERAGE" => p.coverage && hasplugin(t, Coverage),
"IMAGE" => p.image,
"PKG" => pkg,
"VERSIONS" => collect_versions(p.extra_versions, t),
)
end
@kwdef struct GitLabCI <: CI
file::String
documentation::Bool = true
coverage::Bool = true
extra_versions::Vector{VersionNumber} = [v"1.0"]
end
gitignore(p::GitLabCI) = p.coverage ? COVERAGE_GITIGNORE : String[]
source(p::GitLabCI) = p.source
destination(::GitLabCI) = ".gitlab-ci.yml"
function badges(p::GitLabCI)
ci = Badge(
"Build Status",
"https://gitlab.com/{{USER}}/{{PKG}}.jl/badges/master/build.svg",
"https://gitlab.com/{{USER}}/{{PKG}}.jl/pipelines",
)
cov = Badge(
"Coverage",
"https://gitlab.com/{{USER}}/{{PKG}}.jl/badges/master/coverage.svg",
"https://gitlab.com/{{USER}}/{{PKG}}.jl/commits/master",
)
return p.coverage ? [ci, cov] : [ci]
end
function view(p::GitLabCI, t::Template, ::AbstractString)
return Dict(
"HAS_COVERAGE" => p.coverage,
"HAS_DOCUMENTER" => hasplugin(t, Documenter{GitLabCI}),
"PKG" => pkg,
"VERSION" => format_version(t.julia_version),
"VERSIONS" => collect_versions(p.extra_versions, t),
)
end

View File

@ -1,45 +0,0 @@
"""
CirrusCI(; config_file::Union{AbstractString, Nothing}="") -> CirrusCI
Add `CirrusCI` to a template's plugins to add a `.cirrus.yml` configuration file to
generated repositories, and an appropriate badge to the README. The default configuration
file supports only FreeBSD builds via [CirrusCI.jl](https://github.com/ararslan/CirrusCI.jl)
# Keyword Arguments
* `config_file::Union{AbstractString, Nothing}=""`: Path to a custom `.cirrus.yml`.
If `nothing` is supplied, no file will be generated.
"""
struct CirrusCI <: GenericPlugin
gitignore::Vector{String}
src::Union{String, Nothing}
dest::String
badges::Vector{Badge}
view::Dict{String, Any}
function CirrusCI(; config_file::Union{AbstractString, Nothing}="")
if config_file !== nothing
config_file = if isempty(config_file)
joinpath(DEFAULTS_DIR, "cirrus.yml")
elseif isfile(config_file)
abspath(config_file)
else
throw(ArgumentError("File $(abspath(config_file)) does not exist"))
end
end
return new(
[],
config_file,
".cirrus.yml",
[
Badge(
"Build Status",
"https://api.cirrus-ci.com/github/{{USER}}/{{PKGNAME}}.jl.svg",
"https://cirrus-ci.com/github/{{USER}}/{{PKGNAME}}.jl",
),
],
Dict{String, Any}(),
)
end
end
interactive(::Type{CirrusCI}) = interactive(CirrusCI; file="cirrus.yml")

View File

@ -1,50 +1,28 @@
"""
Citation(; readme_section::Bool=false)
Citation(; readme_section::Bool=false) -> Citation
Add `Citation` to a template's plugins to add a `CITATION.bib` file to
generated repositories, and an appropriate section in the README.
# Keyword Arguments:
* `readme_section::Bool=false`: whether to add a section in the readme pointing to `CITATION.bib`.
Add `Citation` to a [`Template`](@ref)'s plugin list to generate a `CITATION.bib` file.
If `readme` is set, then `README.md` will contain a section about citing.
"""
struct Citation <: GenericPlugin
gitignore::Vector{AbstractString}
src::Union{String, Nothing}
dest::AbstractString
badges::Vector{Badge}
view::Dict{String, Any}
readme_section::Bool
function Citation(; readme_section::Bool=false)
new(
[],
nothing,
"CITATION.bib",
[],
Dict{String, Any}(),
readme_section,
)
end
@kwdef struct Citation <: BasicPlugin
file::String = default_file("CITATION.bib")
readme::Bool = false
end
tags(::Citation) = "<<", ">>"
source(p::Citation) = p.file
destination(::Citation) = "CITATION.bib"
view(::Citation, t::Template, pkg::AbstractString) = Dict(
"AUTHORS" => t.authors,
"MONTH" => month(today()),
"PKG" => pkg,
"URL" => "https://$(t.host)/$(t.user)/$pkg.jl",
"YEAR" => year(today()),
)
function interactive(::Type{Citation})
print("Citation: Add a section to README.md mentioning CITATION.bib? [no]: ")
readme = uppercase(readline()) in ["Y", "YES", "TRUE"]
readme = prompt_bool("Citation: Add a section to the README", false)
return Citation(; readme_section=readme)
end
function gen_plugin(p::Citation, t::Template, pkg_name::AbstractString)
pkg_dir = joinpath(t.dir, pkg_name)
text = """
@misc{$pkg_name.jl,
\tauthor = {$(t.authors)},
\ttitle = {{$(pkg_name).jl}},
\turl = {https://$(t.host)/$(t.user)/$(pkg_name).jl},
\tversion = {v0.1.0},
\tyear = {$(year(today()))},
\tmonth = {$(month(today()))}
}
"""
gen_file(joinpath(pkg_dir, "CITATION.bib"), text)
return ["CITATION.bib"]
end

View File

@ -1,44 +0,0 @@
"""
Codecov(; config_file::Union{AbstractString, Nothing}=nothing) -> Codecov
Add `Codecov` to a template's plugins to optionally add a `.codecov.yml` configuration file
to generated repositories, and an appropriate badge to the README. Also updates the
`.gitignore` accordingly.
# Keyword Arguments:
* `config_file::Union{AbstractString, Nothing}=nothing`: Path to a custom `.codecov.yml`.
If left unset, no file will be generated.
"""
struct Codecov <: GenericPlugin
gitignore::Vector{String}
src::Union{String, Nothing}
dest::String
badges::Vector{Badge}
view::Dict{String, Any}
function Codecov(; config_file::Union{AbstractString, Nothing}=nothing)
if config_file != nothing
config_file = if isfile(config_file)
abspath(config_file)
else
throw(ArgumentError("File $(abspath(config_file)) does not exist"))
end
end
new(
["*.jl.cov", "*.jl.*.cov", "*.jl.mem"],
config_file,
".codecov.yml",
[
Badge(
"Codecov",
"https://codecov.io/gh/{{USER}}/{{PKGNAME}}.jl/branch/master/graph/badge.svg",
"https://codecov.io/gh/{{USER}}/{{PKGNAME}}.jl",
),
],
Dict{String, Any}(),
)
end
end
Base.@deprecate_binding CodeCov Codecov
interactive(::Type{Codecov}) = interactive(Codecov; file=nothing)

31
src/plugins/coverage.jl Normal file
View File

@ -0,0 +1,31 @@
abstract type Coverage <: Plugin end
const COVERAGE_GITIGNORE = ["*.jl.cov", "*.jl.*.cov", "*.jl.mem"]
gitignore(::Coverage) = COVERAGE_GITIGNORE
@kwdef struct Codecov <: Coverage
file::Union{String, Nothing} = nothing
end
source(p::Codecov) = p.file
destination(::Codecov) = ".codecov.yml"
badges(::Codecov) = Badge(
"Coverage",
"https://codecov.io/gh/{{USER}}/{{PKG}}.jl/branch/master/graph/badge.svg",
"https://codecov.io/gh/{{USER}}/{{PKG}}.jl",
)
@kwdef struct Coveralls <: Coverage
file::Union{String, Nothing} = nothing
end
source(p::Coveralls) = p.file
destination(::Coveralls) = ".coveralls.yml"
badges(::Coveralls) = Badge(
"Coverage",
"https://coveralls.io/repos/github/{{USER}}/{{PKG}}.jl/badge.svg?branch=master",
"https://coveralls.io/github/{{USER}}/{{PKG}}.jl?branch=master",
)

View File

@ -1,43 +0,0 @@
"""
Coveralls(; config_file::Union{AbstractString, Nothing}=nothing) -> Coveralls
Add `Coveralls` to a template's plugins to optionally add a `.coveralls.yml` configuration
file to generated repositories, and an appropriate badge to the README. Also updates the
`.gitignore` accordingly.
# Keyword Arguments:
* `config_file::Union{AbstractString, Nothing}=nothing`: Path to a custom `.coveralls.yml`.
If left unset, no file will be generated.
"""
struct Coveralls <: GenericPlugin
gitignore::Vector{String}
src::Union{String, Nothing}
dest::String
badges::Vector{Badge}
view::Dict{String, Any}
function Coveralls(; config_file::Union{AbstractString, Nothing}=nothing)
if config_file != nothing
config_file = if isfile(config_file)
abspath(config_file)
else
throw(ArgumentError("File $(abspath(config_file)) does not exist"))
end
end
new(
["*.jl.cov", "*.jl.*.cov", "*.jl.mem"],
config_file,
".coveralls.yml",
[
Badge(
"Coveralls",
"https://coveralls.io/repos/github/{{USER}}/{{PKGNAME}}.jl/badge.svg?branch=master",
"https://coveralls.io/github/{{USER}}/{{PKGNAME}}.jl?branch=master",
),
],
Dict{String, Any}(),
)
end
end
interactive(::Type{Coveralls}) = interactive(Coveralls; file=nothing)

View File

@ -1,31 +1,85 @@
const DOCUMENTER_UUID = "e30172f5-a6a5-5a46-863b-614d45cd2de4"
const STANDARD_KWS = [:modules, :format, :pages, :repo, :sitename, :authors, :assets]
"""
Add a `Documenter` subtype to a template's plugins to add support for documentation
generation via [Documenter.jl](https://github.com/JuliaDocs/Documenter.jl).
Documenter{T<:Union{TravisCI, GitLabCI, Nothing}}(;
assets::Vector{<:AbstractString}=String[],
makedocs_kwargs::Dict{Symbol}=Dict(),
canonical_url::Union{Function, Nothing}=nothing,
) -> Documenter{T}
By default, the plugin generates a minimal index.md and a make.jl file. The make.jl
file contains the Documenter.makedocs command with predefined values for `modules`,
`format`, `pages`, `repo`, `sitename`, and `authors`.
The `Documenter` plugin adds support for documentation generation via [Documenter.jl](https://github.com/JuliaDocs/Documenter.jl).
Documentation deployment depends on `T`, where `T` is some supported CI plugin, or `Nothing` to only support local documentation builds.
The subtype is expected to include the following fields:
* `assets::Vector{AbstractString}`, a list of filenames to be included as the `assets`
kwarg to `makedocs`
* `gitignore::Vector{AbstractString}`, a list of files to be added to the `.gitignore`
## Keyword Arguments
todo
- `assets::Vector{<:AbstractString}=String[]`:
- `makedocs_kwargs::Dict{Symbol}=Dict{Symbol, Any}()`:
- `canonical_url::Union{Function, Nothing}=nothing`:`
It may optionally include the field `additional_kwargs::Union{AbstractDict, NamedTuple}`
to allow additional kwargs to be added to `makedocs`.
!!! note
If deploying documentation with Travis CI, don't forget to complete the required configuration.
See [here](https://juliadocs.github.io/Documenter.jl/stable/man/hosting/#SSH-Deploy-Keys-1).
"""
abstract type Documenter <: CustomPlugin end
struct Documenter{T<:Union{TravisCI, GitLabCI, Nothing}} <: Plugin
assets::Vector{String}
makedocs_kwargs::Dict{Symbol}
canonical_url::Union{Function, Nothing}
function gen_plugin(p::Documenter, t::Template, pkg_name::AbstractString)
path = joinpath(t.dir, pkg_name)
docs_dir = joinpath(path, "docs")
mkpath(docs_dir)
# Can't use @kwdef due to some weird precompilation issues.
function Documenter{T}(
assets::Vector{<:AbstractString}=String[],
makedocs_kwargs::Dict{Symbol}=Dict{Symbol, Any}(),
canonical_url::Union{Function, Nothing}=T === TravisCI ? github_pages_url : nothing,
) where T <: Union{TravisCI, GitLabCI, Nothing}
return new(assets, makedocs_kwargs, canonical_url)
end
end
Documenter(; kwargs...) = Documenter{Nothing}(; kwargs...)
gitignore(::Documenter) = ["/docs/build/", "/docs/site/"]
badges(::Documenter) = Badge[]
badges(::Documenter{TravisCI}) = [
Badge(
"Stable",
"https://img.shields.io/badge/docs-stable-blue.svg",
"https://{{USER}}.github.io/{{PKG}}.jl/stable",
),
Badge(
"Dev",
"https://img.shields.io/badge/docs-dev-blue.svg",
"https://{{USER}}.github.io/{{PKG}}.jl/dev",
),
]
badges(::Documenter{GitLabCI}) = Badge(
"Dev",
"https://img.shields.io/badge/docs-dev-blue.svg",
"https://{{USER}}.gitlab.io/{{PKG}}.jl/dev",
)
view(p::Documenter, t::Template, pkg::AbstractString) = Dict(
"ASSETS" => p.assets,
"AUTHORS" => 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), collect(p.makedocs_kwargs)),
"PKG" => pkg,
"REPO" => "https://$(t.host)/$(t.user)/$pkg.jl",
)
function view(p::Documenter{TravisCI}, t::Template, pkg::AbstractString)
base = invoke(view, Tuple{Documenter, Template, AbstractString}, p, t, pkg)
return merge(base, Dict("HAS_DEPLOY" => true))
end
function gen_plugin(p::Documenter, t::Template, pkg_dir::AbstractString)
# TODO: gen make.jl
# TODO: gen index.md
# Create the documentation project.
proj = Base.current_project()
docs_dir = joinpath(pkg_dir, "docs")
proj = current_project()
try
Pkg.activate(docs_dir)
Pkg.add(PackageSpec(; name="Documenter", uuid=DOCUMENTER_UUID))
@ -33,95 +87,38 @@ function gen_plugin(p::Documenter, t::Template, pkg_name::AbstractString)
proj === nothing ? Pkg.activate() : Pkg.activate(proj)
end
tab = repeat(" ", 4)
assets_string = if !isempty(p.assets)
mkpath(joinpath(docs_dir, "src", "assets"))
for file in p.assets
cp(file, joinpath(docs_dir, "src", "assets", basename(file)))
end
# We want something that looks like the following:
# [
# assets/file1,
# assets/file2,
# ]
s = "[\n"
for asset in p.assets
s *= """$(tab^2)"assets/$(basename(asset))",\n"""
end
s *= "$tab]"
s
else
"String[]"
end
kwargs_string = if :additional_kwargs in fieldnames(typeof(p)) &&
fieldtype(typeof(p), :additional_kwargs) <: Union{AbstractDict, NamedTuple}
# We want something that looks like the following:
# key1="val1",
# key2="val2",
#
kws = [keys(p.additional_kwargs)...]
valid_keys = filter(k -> !in(Symbol(k), STANDARD_KWS), kws)
if length(p.additional_kwargs) > length(valid_keys)
invalid_keys = filter(k -> Symbol(k) in STANDARD_KWS, kws)
@warn string(
"Ignoring predefined Documenter kwargs ",
join(map(repr, invalid_keys), ", "),
" from additional kwargs"
)
end
join(map(k -> string(tab, k, "=", repr(p.additional_kwargs[k]), ",\n"), valid_keys))
else
""
end
make = """
using Documenter, $pkg_name
makedocs(;
modules=[$pkg_name],
format=Documenter.HTML(),
pages=[
"Home" => "index.md",
],
repo="https://$(t.host)/$(t.user)/$pkg_name.jl/blob/{commit}{path}#L{line}",
sitename="$pkg_name.jl",
authors="$(t.authors)",
assets=$assets_string,
$kwargs_string)
"""
docs = """
# $pkg_name.jl
```@index
```
```@autodocs
Modules = [$pkg_name]
```
"""
gen_file(joinpath(docs_dir, "make.jl"), make)
gen_file(joinpath(docs_dir, "src", "index.md"), docs)
# Copy any assets.
assets_dir = joinpath(docs_dir, "src", "assets")
isempty(p.assets) || mkpath(assets_dir)
foreach(a -> cp(a, joinpath(assets_dir, basename(asset))), p.assets)
end
function Base.show(io::IO, p::Documenter)
spc = " "
println(io, nameof(typeof(p)), ":")
function interactive(::Type{Documenter{T}}) where T
name = "Documenter{$T}"
n = length(p.assets)
s = n == 1 ? "" : "s"
print(io, spc, "$n asset file$s")
if n == 0
println(io)
else
println(io, ": ", join(map(a -> replace(a, homedir() => "~"), p.assets), ", "))
print("$name: Enter any Documenter asset files (separated by spaces) [none]: ")
assets = split(readline())
print("$name: Enter any extra makedocs key-value pairs (joined by '=') [none]\n> ")
kwargs = Dict{Symbol, Any}()
line = map(split(readline())) do kv
k, v = split(kv, "="; limit=2)
kwargs[Symbol(k)] = eval(Meta.parse(v))
end
n = length(p.gitignore)
s = n == 1 ? "" : "s"
print(io, "$spc→ $n gitignore entrie$s")
n > 0 && print(io, ": ", join(map(repr, p.gitignore), ", "))
return Documenter{T}(; assets=assets, kwargs=kwargs)
end
function interactive(::Type{Documenter})
types = Dict(
"None (local documentation only)" => Nothing,
"TravisCI (GitHub Pages)" => TravisCI,
"GitLabCI (GitLab Pages)" => GitLabCI,
)
options = collect(keys(types))
menu = RadioMenu(options)
T = types[options[request("Documenter: Select integration:", menu)]]
return interactive(Documenter{T})
end
github_pages_url(t::Template, pkg::AbstractString) = "https://$(t.user).github.io/$pkg.jl"

115
src/plugins/essentials.jl Normal file
View File

@ -0,0 +1,115 @@
const LICENSE_DIR = normpath(joinpath(@__DIR__, "..", "..", "licenses"))
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+",
)
@kwdef struct Readme <: BasicPlugin
file::String = default_file("README.md")
destination::String = "README.md"
inline_badges::Bool = false
end
source(p::Readme) = p.file
destination(p::Readme) = p.destination
function view(::Readme, t::Template, pkg::AbstractString)
# Explicitly ordered badges go first.
strings = String[]
done = DataType[]
foreach(BADGE_ORDER) do T
if hasplugin(t, T)
bs = badges(t.plugins[T], t, pkg)
append!(strings, badges(t.plugins[T], t, pkg))
push!(done, T)
end
end
foreach(setdiff(keys(t.plugins), done)) do T
bs = badges(t.plugins[T], t, pkg)
text *= "\n" * join(badges(t.plugins[T], t.user, pkg), "\n")
end
return Dict(
"HAS_CITATION" => hasplugin(t, Citation),
"HAS_INLINE_BADGES" => p.inline_badges,
)
end
struct License <: Plugin
path::String
destination::String
function License(name::AbstractString="MIT", destination::AbstractString="LICENSE")
return new(license_path(name), destination)
end
end
function license_path(license::AbstractString)
path = joinpath(LICENSE_DIR, license)
isfile(path) || throw(ArgumentError("License '$license' is not available"))
return path
end
read_license(license::AbstractString) = string(readchomp(license_path(license)))
function render_plugin(p::License, t::Template)
text = "Copyright (c) $(year(today())) $(t.authors)\n"
license = read(p.path, String)
startswith(license, "\n") || (text *= "\n")
return text * license
end
function gen_plugin(p::License, t::Template, pkg_dir::AbstractString)
gen_file(joinpath(pkg_dir, p.destination), render_plugin(p, t))
end
struct Gitignore <: Plugin end
function render_plugin(p::Gitignore, t::Template)
entries = mapreduce(gitignore, append!, values(t.plugins); init=[".DS_Store", "/dev/"])
# Only ignore manifests at the repo root.
t.manifest || "Manifest.toml" in entries || push!(entries, "/Manifest.toml")
unique!(sort!(entries))
return join(entries, "\n")
end
function gen_plugin(p::Gitignore, t::Template, pkg_dir::AbstractString)
gen_file(joinpath(pkg_dir, ".gitignore"), render_plugin(p, t))
end
@kwdef struct Tests <: BasicPlugin
file::String = default_file("runtests.jl")
end
source(p::Tests) = p.file
destination(::Tests) = joinpath("test", "runtests.jl")
view(::Tests, ::Template, pkg::AbstractString) = Dict("PKG" => pkg)
function gen_plugin(p::Tests, t::Template, pkg_dir::AbstractString)
# Do the normal BasicPlugin behaviour to create the test script.
invoke(gen_plugin, Tuple{BasicPlugin, Template, AbstractString}, p, t, pkg_dir)
# Add the Test dependency as a test-only dependency.
# To avoid visual noise from adding/removing the dependency, insert it manually.
proj = current_project()
try
Pkg.activate(pkg_dir)
lines = readlines(joinpath(pkg_dir, "Project.toml"))
dep = "Test = $(repr(TEST_UUID))"
push!(lines, "[extras]", dep, "", "[targets]", "test = [\"Test\"]")
gen_file(joinpath(pkg_dir, "Project.toml"), join(lines, "\n"))
touch(joinpath(pkg_dir, "Manifest.toml")) # File must exist to be modified by Pkg.
Pkg.update() # Clean up both Manifest.toml and Project.toml.
finally
proj === nothing ? Pkg.activate() : Pkg.activate(proj)
end
end

View File

@ -1,69 +0,0 @@
"""
GitHubPages(; assets::Vector{<:AbstractString}=String[]) -> GitHubPages
Add `GitHubPages` to a template's plugins to add [`Documenter`](@ref) support via GitHub
Pages, including automatic uploading of documentation from [`TravisCI`](@ref). Also
adds appropriate badges to the README, and updates the `.gitignore` accordingly.
# Keyword Arguments
* `assets::Vector{<:AbstractString}=String[]`: Array of paths to Documenter asset files.
!!! note
If deploying documentation with Travis CI, don't forget to complete the required
configuration (see
[here](https://juliadocs.github.io/Documenter.jl/stable/man/hosting/#SSH-Deploy-Keys-1)).
"""
struct GitHubPages <: Documenter
gitignore::Vector{String}
assets::Vector{String}
function GitHubPages(; assets::Vector{<:AbstractString}=String[])
for file in assets
if !isfile(file)
throw(ArgumentError("Asset file $(abspath(file)) does not exist"))
end
end
# Windows Git recognizes these paths as well.
new(["/docs/build/", "/docs/site/"], abspath.(assets))
end
end
function badges(::GitHubPages, user::AbstractString, pkg_name::AbstractString)
return [
format(Badge(
"Stable",
"https://img.shields.io/badge/docs-stable-blue.svg",
"https://$user.github.io/$pkg_name.jl/stable"
)),
format(Badge(
"Dev",
"https://img.shields.io/badge/docs-dev-blue.svg",
"https://$user.github.io/$pkg_name.jl/dev"
)),
]
end
function gen_plugin(p::GitHubPages, t::Template, pkg_name::AbstractString)
invoke(gen_plugin, Tuple{Documenter, Template, AbstractString}, p, t, pkg_name)
if haskey(t.plugins, TravisCI)
docs_src = joinpath(t.dir, pkg_name, "docs", "src")
open(joinpath(dirname(docs_src), "make.jl"), "a") do file
write(
file,
"""
deploydocs(;
repo="$(t.host)/$(t.user)/$pkg_name.jl",
)
"""
)
end
end
return ["docs/"]
end
function interactive(::Type{GitHubPages})
print("GitHubPages: Enter any Documenter asset files (separated by spaces) []: ")
return GitHubPages(; assets=string.(split(readline())))
end

View File

@ -1,83 +0,0 @@
"""
GitLabCI(; config_file::Union{AbstractString, Nothing}="", coverage::Bool=true) -> GitLabCI
Add `GitLabCI` to a template's plugins to add a `.gitlab-ci.yml` configuration file to
generated repositories, and appropriate badge(s) to the README.
# Keyword Arguments:
* `config_file::Union{AbstractString, Nothing}=""`: Path to a custom `.gitlab-ci.yml`.
If `nothing` is supplied, no file will be generated.
* `coverage::Bool=true`: Whether or not GitLab CI's built-in code coverage analysis should
be enabled.
"""
struct GitLabCI <: GenericPlugin
gitignore::Vector{String}
src::Union{String, Nothing}
dest::String
badges::Vector{Badge}
view::Dict{String, Any}
function GitLabCI(; config_file::Union{AbstractString, Nothing}="", coverage::Bool=true)
if config_file != nothing
config_file = if isempty(config_file)
config_file = joinpath(DEFAULTS_DIR, "gitlab-ci.yml")
elseif isfile(config_file)
abspath(config_file)
else
throw(ArgumentError("File $(abspath(config_file)) does not exist"))
end
end
badges = [
Badge(
"Build Status",
"https://gitlab.com/{{USER}}/{{PKGNAME}}.jl/badges/master/build.svg",
"https://gitlab.com/{{USER}}/{{PKGNAME}}.jl/pipelines",
),
]
if coverage
push!(
badges,
Badge(
"Coverage",
"https://gitlab.com/{{USER}}/{{PKGNAME}}.jl/badges/master/coverage.svg",
"https://gitlab.com/{{USER}}/{{PKGNAME}}.jl/commits/master",
),
)
end
new(
coverage ? ["*.jl.cov", "*.jl.*.cov", "*.jl.mem"] : [],
config_file,
".gitlab-ci.yml",
badges,
Dict("GITLABCOVERAGE" => coverage),
)
end
end
function interactive(::Type{GitLabCI})
name = "GitLabCI"
kwargs = Dict{Symbol, Any}()
default_config_file = joinpath(DEFAULTS_DIR, "gitlab-ci.yml")
print("$name: Enter the config template filename (\"None\" for no file) ")
print("[", replace(default_config_file, homedir() => "~"), "]: ")
config_file = readline()
kwargs[:config_file] = if uppercase(config_file) == "NONE"
nothing
elseif isempty(config_file)
default_config_file
else
config_file
end
print("$name: Enable test coverage analysis? [yes]: ")
coverage = readline()
kwargs[:coverage] = if isempty(coverage)
true
else
!in(uppercase(coverage), ["N", "NO", "FALSE", "NONE"])
end
return GitLabCI(; kwargs...)
end

View File

@ -1,47 +0,0 @@
"""
GitLabPages(; assets::Vector{<:AbstractString}=String[]) -> GitLabPages
Add `GitLabPages` to a template's plugins to add [`Documenter`](@ref) support via GitLab
Pages, including automatic uploading of documentation from [`GitLabCI`](@ref). Also
adds appropriate badges to the README, and updates the `.gitignore` accordingly.
# Keyword Arguments
* `assets::Vector{<:AbstractString}=String[]`: Array of paths to Documenter asset files.
"""
struct GitLabPages <: Documenter
gitignore::Vector{String}
assets::Vector{String}
function GitLabPages(; assets::Vector{<:AbstractString}=String[])
for file in assets
if !isfile(file)
throw(ArgumentError("Asset file $(abspath(file)) does not exist"))
end
end
# Windows Git recognizes these paths as well.
new(["/docs/build/", "/docs/site/"], abspath.(assets))
end
end
function badges(::GitLabPages, user::AbstractString, pkg_name::AbstractString)
# We are only including a badge for `dev` documentation since versioned documentation
# is not supported in GitLab pages yet. See:
# https://github.com/invenia/PkgTemplates.jl/pull/54
return [
format(Badge(
"Dev",
"https://img.shields.io/badge/docs-dev-blue.svg",
"https://$user.gitlab.io/$pkg_name.jl/dev"
)),
]
end
function gen_plugin(p::GitLabPages, t::Template, pkg_name::AbstractString)
invoke(gen_plugin, Tuple{Documenter, Template, AbstractString}, p, t, pkg_name)
return ["docs/"]
end
function interactive(::Type{GitLabPages})
print("GitLabPages: Enter any Documenter asset files (separated by spaces) []: ")
return GitLabPages(; assets=string.(split(readline())))
end

View File

@ -1,44 +0,0 @@
"""
TravisCI(; config_file::Union{AbstractString, Nothing}="") -> TravisCI
Add `TravisCI` to a template's plugins to add a `.travis.yml` configuration file to
generated repositories, and an appropriate badge to the README.
# Keyword Arguments:
* `config_file::Union{AbstractString, Nothing}=""`: Path to a custom `.travis.yml`.
If `nothing` is supplied, no file will be generated.
"""
struct TravisCI <: GenericPlugin
gitignore::Vector{String}
src::Union{String, Nothing}
dest::String
badges::Vector{Badge}
view::Dict{String, Any}
function TravisCI(; config_file::Union{AbstractString, Nothing}="")
if config_file != nothing
config_file = if isempty(config_file)
config_file = joinpath(DEFAULTS_DIR, "travis.yml")
elseif isfile(config_file)
abspath(config_file)
else
throw(ArgumentError("File $(abspath(config_file)) does not exist"))
end
end
new(
[],
config_file,
".travis.yml",
[
Badge(
"Build Status",
"https://travis-ci.com/{{USER}}/{{PKGNAME}}.jl.svg?branch=master",
"https://travis-ci.com/{{USER}}/{{PKGNAME}}.jl",
),
],
Dict{String, Any}(),
)
end
end
interactive(::Type{TravisCI}) = interactive(TravisCI; file="travis.yml")

View File

@ -1,242 +1,107 @@
default_version() = VersionNumber(VERSION.major)
"""
Template(; kwargs...) -> Template
Template(interactive::Bool=false; kwargs...) -> Template
Records common information used to generate a package. If you don't wish to manually
create a template, you can use [`interactive_template`](@ref) instead.
Records common information used to generate a package.
# Keyword Arguments
* `user::AbstractString=""`: GitHub (or other code hosting service) username. If left
unset, it will take the the global git config's value (`github.user`). If that is not
set, an `ArgumentError` is thrown. **This is case-sensitive for some plugins, so take
care to enter it correctly.**
* `host::AbstractString="github.com"`: URL to the code hosting service where your package
will reside. Note that while hosts other than GitHub won't cause errors, they are not
officially supported and they will cause certain plugins will produce incorrect output.
* `license::AbstractString="MIT"`: Name of the package license. If an empty string is
given, no license is created. [`available_licenses`](@ref) can be used to list all
available licenses, and [`show_license`](@ref) can be used to print out a particular
license's text.
* `authors::Union{AbstractString, Vector{<:AbstractString}}=""`: Names that appear on the
license. Supply a string for one author or an array for multiple. Similarly to `user`,
it will take the value of of the global git config's value if it is left unset.
* `dir::AbstractString=$(replace(Pkg.devdir(), homedir() => "~"))`: Directory in which the
package will go. Relative paths are converted to absolute ones at template creation time.
* `julia_version::VersionNumber=$(default_version())`: Minimum allowed Julia version.
* `ssh::Bool=false`: Whether or not to use SSH for the git remote. If `false` HTTPS will be used.
* `dev::Bool=true`: Whether or not to `Pkg.develop` generated packages.
* `manifest::Bool=false`: Whether or not to commit the `Manifest.toml`.
* `plugins::Vector{<:Plugin}=Plugin[]`: A list of `Plugin`s that the package will include.
## Keyword Arguments
- `user::AbstractString=""`: GitHub (or other code hosting service) username.
If left unset, it will take the the global Git config's value (`github.user`).
If that is not set, an `ArgumentError` is thrown.
This is case-sensitive for some plugins, so take care to enter it correctly!
- `host::AbstractString="github.com"`: URL to the code hosting service where your package will reside.
Note that while hosts other than GitHub won't cause errors, they are not officially supported and they will cause certain plugins will produce incorrect output.
- `authors::Union{AbstractString, Vector{<:AbstractString}}=""`: Names that appear on the license.
Supply a string for one author or an array for multiple.
Similarly to `user`, it will take the value of of the global Git config's value if it is left unset.
- `dir::AbstractString=$(contractuser(Pkg.devdir()))`: Directory in which the package will go.
Relative paths are converted to absolute ones at template creation time.
- `julia_version::VersionNumber=$DEFAULT_VERSION`: Minimum allowed Julia version.
- `ssh::Bool=false`: Whether or not to use SSH for the git remote. If `false` HTTPS will be used.
- `manifest::Bool=false`: Whether or not to commit the `Manifest.toml`.
- `git::Bool=true`: Whether or not to create a Git repository for generated packages.
- `develop::Bool=true`: Whether or not to `develop` generated packages in the active environment.
- `plugins::Vector{<:Plugin}=Plugin[]`: A list of plugins that the package will include.
- `disable_default_plugins::Vector{DataType}=DataType[]`: Default plugins to disable.
The default plugins are [`Readme`](@ref), [`License`](@ref), [`Tests`](@ref), and [`Gitignore`](@ref).
To override a default plugin instead of disabling it altogether, supply it via `plugins`.
- `interactive::Bool=false`: When set, creates the template interactively from user input,
using the previous keywords as a starting point.
- `fast::Bool=false`: Only applicable when `interactive` is set.
Skips prompts for any unsupplied keywords except `user` and `plugins`.
"""
struct Template
user::String
host::String
license::String
authors::String
dir::String
julia_version::VersionNumber
ssh::Bool
dev::Bool
manifest::Bool
git::Bool
develop::Bool
plugins::Dict{DataType, <:Plugin}
end
function Template(;
user::AbstractString="",
host::AbstractString="https://github.com",
license::AbstractString="MIT",
authors::Union{AbstractString, Vector{<:AbstractString}}="",
dir::AbstractString=Pkg.devdir(),
julia_version::VersionNumber=default_version(),
ssh::Bool=false,
dev::Bool=true,
manifest::Bool=false,
plugins::Vector{<:Plugin}=Plugin[],
git::Bool=true,
Template(; interactive::Bool=false, kwargs...) = make_template(Val(interactive); kwargs...)
function make_template(::Val{false}; kwargs...)
user = getkw(kwargs, :user)
if isempty(user)
throw(ArgumentError("No username found, set one with user=username"))
end
host = getkw(kwargs, :host)
host = URI(occursin("://", host) ? host : "https://$host").host
authors = getkw(kwargs, :authors)
authors isa Vector && (authors = join(authors, ", "))
dir = abspath(expanduser(getkw(kwargs, :dir)))
disabled = getkw(kwargs, :disabled_defaults)
defaults = [Readme, License, Tests, Gitignore]
plugins = map(T -> T(), filter(T -> !in(T, disabled), defaults))
append!(plugins, getkw(kwargs, :plugins))
# This comprehensions resolves duplicate plugin types by overwriting,
# which means that default plugins get replaced by user values.
plugin_dict = Dict(typeof(p) => p for p in plugins)
return Template(
user,
host,
authors,
dir,
getkw(kwargs, :julia_version),
getkw(kwargs, :ssh),
getkw(kwargs, :manifest),
getkw(kwargs, :git),
getkw(kwargs, :develop),
plugin_dict,
)
# Check for required Git options for package generation
# (you can't commit to a repository without them).
git && isempty(LibGit2.getconfig("user.name", "")) && missingopt("user.name")
git && isempty(LibGit2.getconfig("user.email", "")) && missingopt("user.email")
# If no username was set, look for one in the global git config.
# Note: This is one of a few GitHub specifics (maybe we could use the host value).
if isempty(user)
user = LibGit2.getconfig("github.user", "")
end
if isempty(user)
throw(ArgumentError("No GitHub username found, set one with user=username"))
end
host = URI(startswith(host, "https://") ? host : "https://$host").host
if !isempty(license) && !isfile(joinpath(LICENSE_DIR, license))
throw(ArgumentError("License '$license' is not available"))
end
# If no author was set, look for one in the global git config.
if isempty(authors)
authors = LibGit2.getconfig("user.name", "")
email = LibGit2.getconfig("user.email", "")
isempty(email) || (authors *= " <$email>")
elseif authors isa Vector
authors = join(authors, ", ")
end
dir = abspath(expanduser(dir))
plugin_dict = Dict{DataType, Plugin}(typeof(p) => p for p in plugins)
if (length(plugins) != length(plugin_dict))
@warn "Plugin list contained duplicates, only the last of each type was kept"
end
new(user, host, license, authors, dir, julia_version, ssh, dev, manifest, plugin_dict)
end
end
function Base.show(io::IO, t::Template)
maybe(s::String) = isempty(s) ? "None" : s
spc = " "
hasplugin(t::Template, ::Type{T}) where T <: Plugin = any(U -> U <: T, keys(t.plugins))
println(io, "Template:")
println(io, spc, "→ User: ", maybe(t.user))
println(io, spc, "→ Host: ", maybe(t.host))
getkw(kwargs, k) = get(() -> defaultkw(k), kwargs, k)
print(io, spc, "→ License: ")
if isempty(t.license)
println(io, "None")
else
println(io, t.license, " ($(t.authors) ", year(today()), ")")
end
println(io, spc, "→ Package directory: ", replace(maybe(t.dir), homedir() => "~"))
println(io, spc, "→ Minimum Julia version: v", version_floor(t.julia_version))
println(io, spc, "→ SSH remote: ", t.ssh ? "Yes" : "No")
println(io, spc, "→ Add packages to main environment: ", t.dev ? "Yes" : "No")
println(io, spc, "→ Commit Manifest.toml: ", t.manifest ? "Yes" : "No")
print(io, spc, "→ Plugins:")
if isempty(t.plugins)
print(io, " None")
else
for plugin in sort(collect(values(t.plugins)); by=string)
println(io)
buf = IOBuffer()
show(buf, plugin)
print(io, spc^2, "")
print(io, join(split(String(take!(buf)), "\n"), "\n$(spc^2)"))
end
end
defaultkw(s::Symbol) = defaultkw(Val(s))
defaultkw(::Val{:user}) = LibGit2.getconfig("github.user", "")
defaultkw(::Val{:host}) = "https://github.com"
defaultkw(::Val{:dir}) = Pkg.devdir()
defaultkw(::Val{:julia_version}) = DEFAULT_VERSION
defaultkw(::Val{:ssh}) = false
defaultkw(::Val{:manifest}) = false
defaultkw(::Val{:git}) = true
defaultkw(::Val{:develop}) = true
defaultkw(::Val{:plugins}) = Plugin[]
defaultkw(::Val{:disabled_defaults}) = DataType[]
function defaultkw(::Val{:authors})
name = LibGit2.getconfig("user.name", "")
email = LibGit2.getconfig("user.email", "")
isempty(name) && return ""
author = name * " "
isempty(email) || (author *= "<$email>")
return strip(author)
end
"""
interactive_template(; fast::Bool=false) -> Template
Interactively create a [`Template`](@ref). If `fast` is set, defaults will be assumed for
all values except username and plugins.
"""
function interactive_template(; git::Bool=true, fast::Bool=false)
@info "Default values are shown in [brackets]"
# Getting the leaf types in a separate thread eliminates an awkward wait after
# "Select plugins" is printed.
plugin_types = @async leaves(Plugin)
kwargs = Dict{Symbol, Any}()
default_user = LibGit2.getconfig("github.user", "")
print("Username [", isempty(default_user) ? "REQUIRED" : default_user, "]: ")
user = readline()
kwargs[:user] = if !isempty(user)
user
elseif !isempty(default_user)
default_user
else
throw(ArgumentError("Username is required"))
end
kwargs[:host] = if fast || !git
"https://github.com" # If Git isn't enabled, this value never gets used.
else
default_host = "github.com"
print("Code hosting service [$default_host]: ")
host = readline()
isempty(host) ? default_host : host
end
kwargs[:license] = if fast
"MIT"
else
println("License:")
io = IOBuffer()
available_licenses(io)
licenses = ["" => "", collect(LICENSES)...]
menu = RadioMenu(String["None", split(String(take!(io)), "\n")...])
# If the user breaks out of the menu with Ctrl-c, the result is -1, the absolute
# value of which correponds to no license.
first(licenses[abs(request(menu))])
end
# We don't need to ask for authors if there is no license,
# because the license is the only place that they matter.
kwargs[:authors] = if fast || isempty(kwargs[:license])
LibGit2.getconfig("user.name", "")
else
default_authors = LibGit2.getconfig("user.name", "")
default_str = isempty(default_authors) ? "None" : default_authors
print("Package author(s) [$default_str]: ")
authors = readline()
isempty(authors) ? default_authors : authors
end
kwargs[:dir] = if fast
Pkg.devdir()
else
default_dir = Pkg.devdir()
print("Path to package directory [$default_dir]: ")
dir = readline()
isempty(dir) ? default_dir : dir
end
kwargs[:julia_version] = if fast
VERSION
else
default_julia_version = VERSION
print("Minimum Julia version [", version_floor(default_julia_version), "]: ")
julia_version = readline()
isempty(julia_version) ? default_julia_version : VersionNumber(julia_version)
end
kwargs[:ssh] = if fast || !git
false
else
print("Set remote to SSH? [no]: ")
uppercase(readline()) in ["Y", "YES", "T", "TRUE"]
end
kwargs[:dev] = if fast
true
else
print("Add packages to main environment? [yes]: ")
uppercase(readline()) in ["", "Y", "YES", "T", "TRUE"]
end
kwargs[:manifest] = if fast
false
else
print("Commit Manifest.toml? [no]: ")
uppercase(readline()) in ["Y", "YES", "T", "TRUE"]
end
println("Plugins:")
# Only include plugin types which have an `interactive` method.
plugin_types = filter(t -> hasmethod(interactive, (Type{t},)), fetch(plugin_types))
type_names = map(t -> split(string(t), ".")[end], plugin_types)
menu = MultiSelectMenu(String.(type_names); pagesize=length(type_names))
selected = collect(request(menu))
kwargs[:plugins] = Vector{Plugin}(map(interactive, getindex(plugin_types, selected)))
return Template(; git=git, kwargs...)
end
leaves(T::Type)::Vector{DataType} = isconcretetype(T) ? [T] : vcat(leaves.(subtypes(T))...)
missingopt(name) = @warn "Git config option '$name' missing, package generation will fail unless you supply a GitConfig"

View File

@ -1,3 +0,0 @@
[user]
name = Travis
email = travis@c.i

View File

@ -1,68 +0,0 @@
@testset "Interactive mode" begin
@testset "Template creation" begin
write(stdin.buffer, "$me\n\n\r\n\n\n\n\nd")
t = interactive_template()
@test t.user == me
@test t.host == "github.com"
@test isempty(t.license)
@test t.authors == LibGit2.getconfig("user.name", "")
@test t.dir == default_dir
@test t.julia_version == VERSION
@test !t.ssh
@test t.dev
@test !t.manifest
@test isempty(t.plugins)
if isempty(LibGit2.getconfig("github.user", ""))
write(stdin.buffer, "\n")
@test_throws ArgumentError t = interactive_template()
end
down = '\x1b' * "[B" # Down array key.
write(stdin.buffer, "$me\ngitlab.com\n$down\r$me\n$test_file\n0.5\nyes\nno\nyes\n$down\r$down\rd\n\n")
t = interactive_template()
@test t.user == me
@test t.host == "gitlab.com"
# Not sure if the order the licenses are displayed in is consistent.
@test !isempty(t.license)
@test t.authors == me
@test t.dir == abspath(test_file)
@test t.julia_version == v"0.5.0"
@test t.ssh
@test !t.dev
@test t.manifest
# Like above, not sure which plugins this will generate.
@test length(t.plugins) == 2
write(stdin.buffer, "$me\nd")
t = interactive_template(; fast=true)
@test t.user == me
@test t.host == "github.com"
@test t.license == "MIT"
@test t.authors == LibGit2.getconfig("user.name", "")
@test t.dir == default_dir
@test t.julia_version == VERSION
@test !t.ssh
@test !t.manifest
@test isempty(t.plugins)
println()
# Host and SSH aren't prompted for when git is disabled.
write(stdin.buffer, "$me\n\n\r\n\n\n\nd")
t = interactive_template(; git=false)
@test t.host == "github.com"
@test !t.ssh
println()
end
@testset "Package generation" begin
write(stdin.buffer, "$me\n\n\r\n\n\n\n\n\n\n\nd")
generate_interactive(test_pkg; gitconfig=gitconfig)
@test isdir(joinpath(default_dir, test_pkg))
rm(joinpath(default_dir, test_pkg); force=true, recursive=true)
end
@testset "Plugins" begin
include("plugins.jl")
end
end

View File

@ -1,90 +0,0 @@
# These tests are to be skipped in OSX builds, see ./interactive.jl for more info.
@testset "TravisCI" begin
write(stdin.buffer, "\n")
p = interactive(TravisCI)
@test p.src == joinpath(DEFAULTS_DIR, "travis.yml")
write(stdin.buffer, "$test_file\n")
p = interactive(TravisCI)
@test p.src == test_file
write(stdin.buffer, "none\n")
p = interactive(TravisCI)
@test p.src === nothing
write(stdin.buffer, "$fake_path\n")
@test_throws ArgumentError interactive(TravisCI)
println()
end
@testset "AppVeyor" begin
write(stdin.buffer, "\n")
p = interactive(AppVeyor)
@test p.src == joinpath(DEFAULTS_DIR, "appveyor.yml")
write(stdin.buffer, "$test_file\n")
p = interactive(AppVeyor)
@test p.src == test_file
write(stdin.buffer, "none\n")
p = interactive(AppVeyor)
@test p.src === nothing
write(stdin.buffer, "$fake_path\n")
@test_throws ArgumentError interactive(AppVeyor)
println()
end
@testset "GitLabCI" begin
write(stdin.buffer, "\n\n")
p = interactive(GitLabCI)
@test p.src == joinpath(DEFAULTS_DIR, "gitlab-ci.yml")
@test p.view == Dict("GITLABCOVERAGE" => true)
write(stdin.buffer, "$test_file\nno\n")
p = interactive(GitLabCI)
@test p.src == test_file
@test p.view == Dict("GITLABCOVERAGE" => false)
write(stdin.buffer, "none\n\n")
p = interactive(GitLabCI)
@test p.src === nothing
write(stdin.buffer, "$fake_path\n\n")
@test_throws ArgumentError interactive(GitLabCI)
println()
end
@testset "Codecov" begin
write(stdin.buffer, "\n")
p = interactive(Codecov)
@test p.src === nothing
write(stdin.buffer, "$test_file\n")
p = interactive(Codecov)
@test p.src == test_file
write(stdin.buffer, "none\n")
p = interactive(Codecov)
@test p.src === nothing
write(stdin.buffer, "$fake_path\n")
@test_throws ArgumentError interactive(Codecov)
println()
end
@testset "Coveralls" begin
write(stdin.buffer, "\n")
p = interactive(Coveralls)
@test p.src === nothing
write(stdin.buffer, "$test_file\n")
p = interactive(Coveralls)
@test p.src == test_file
write(stdin.buffer, "none\n")
p = interactive(Coveralls)
@test p.src === nothing
write(stdin.buffer, "$fake_path\n")
@test_throws ArgumentError interactive(Coveralls)
println()
end
@testset "GitHubPages" begin
write(stdin.buffer, "\n")
p = interactive(GitHubPages)
@test isempty(p.assets)
write(stdin.buffer, "$test_file\n")
p = interactive(GitHubPages)
@test p.assets == [test_file]
write(stdin.buffer, "$fake_path\n")
@test_throws ArgumentError interactive(GitHubPages)
println()
end

View File

@ -1,57 +0,0 @@
t = Template(; user=me)
pkg_dir = joinpath(t.dir, test_pkg)
@testset "AppVeyor" begin
@testset "Plugin creation" begin
p = AppVeyor()
@test isempty(p.gitignore)
@test p.src == joinpath(PkgTemplates.DEFAULTS_DIR, "appveyor.yml")
@test p.dest == ".appveyor.yml"
@test p.badges == [
Badge(
"Build Status",
"https://ci.appveyor.com/api/projects/status/github/{{USER}}/{{PKGNAME}}.jl?svg=true",
"https://ci.appveyor.com/project/{{USER}}/{{PKGNAME}}-jl",
),
]
@test isempty(p.view)
p = AppVeyor(; config_file=nothing)
@test p.src === nothing
p = AppVeyor(; config_file=test_file)
@test p.src == test_file
@test_throws ArgumentError AppVeyor(; config_file=fake_path)
end
@testset "Badge generation" begin
p = AppVeyor()
@test badges(p, me, test_pkg) == ["[![Build Status](https://ci.appveyor.com/api/projects/status/github/$me/$test_pkg.jl?svg=true)](https://ci.appveyor.com/project/$me/$test_pkg-jl)"]
end
@testset "File generation" begin
# Without a coverage plugin in the template, there should be no post-test step.
p = AppVeyor()
@test gen_plugin(p, t, test_pkg) == [".appveyor.yml"]
@test isfile(joinpath(pkg_dir, ".appveyor.yml"))
appveyor = read(joinpath(pkg_dir, ".appveyor.yml"), String)
@test !occursin("on_success", appveyor)
@test !occursin("%JL_CODECOV_SCRIPT%", appveyor)
rm(joinpath(pkg_dir, ".appveyor.yml"))
# Generating the plugin with Codecov in the template should create a post-test step.
t.plugins[Codecov] = Codecov()
gen_plugin(p, t, test_pkg)
delete!(t.plugins, Codecov)
appveyor = read(joinpath(pkg_dir, ".appveyor.yml"), String)
@test occursin("on_success", appveyor)
@test occursin("%JL_CODECOV_SCRIPT%", appveyor)
rm(joinpath(pkg_dir, ".appveyor.yml"))
# TODO: Add Coveralls tests when AppVeyor.jl supports it.
p = AppVeyor(; config_file=nothing)
@test isempty(gen_plugin(p, t, test_pkg))
@test !isfile(joinpath(pkg_dir, ".appveyor.yml"))
end
end
rm(pkg_dir; recursive=true)

View File

@ -1,54 +0,0 @@
t = Template(; user=me)
pkg_dir = joinpath(t.dir, test_pkg)
@testset "CirrusCI" begin
@testset "Plugin creation" begin
p = CirrusCI()
@test isempty(p.gitignore)
@test p.src == joinpath(DEFAULTS_DIR, "cirrus.yml")
@test p.dest == ".cirrus.yml"
@test p.badges == [
Badge(
"Build Status",
"https://api.cirrus-ci.com/github/{{USER}}/{{PKGNAME}}.jl.svg",
"https://cirrus-ci.com/github/{{USER}}/{{PKGNAME}}.jl",
),
]
@test isempty(p.view)
p = CirrusCI(; config_file=nothing)
@test p.src === nothing
p = CirrusCI(; config_file=test_file)
@test p.src == test_file
@test_throws ArgumentError CirrusCI(; config_file=fake_path)
end
@testset "Badge generation" begin
p = CirrusCI()
@test badges(p, me, test_pkg) == ["[![Build Status](https://api.cirrus-ci.com/github/$me/$test_pkg.jl.svg)](https://cirrus-ci.com/github/$me/$test_pkg.jl)"]
end
@testset "File generation" begin
# Without a coverage plugin in the template, there should be no coverage step.
p = CirrusCI()
@test gen_plugin(p, t, test_pkg) == [".cirrus.yml"]
@test isfile(joinpath(pkg_dir, ".cirrus.yml"))
cirrus = read(joinpath(pkg_dir, ".cirrus.yml"), String)
@test !occursin("coverage_script", cirrus)
rm(joinpath(pkg_dir, ".cirrus.yml"))
# Generating the plugin with Codecov in the template should create a post-test step.
t.plugins[Codecov] = Codecov()
gen_plugin(p, t, test_pkg)
delete!(t.plugins, Codecov)
cirrus = read(joinpath(pkg_dir, ".cirrus.yml"), String)
@test occursin("coverage_script", cirrus)
@test occursin("cirrusjl coverage", cirrus)
rm(joinpath(pkg_dir, ".cirrus.yml"))
p = CirrusCI(; config_file=nothing)
@test isempty(gen_plugin(p, t, test_pkg))
@test !isfile(joinpath(pkg_dir, ".cirrus.yml"))
end
end
rm(pkg_dir; recursive=true)

View File

@ -1,48 +0,0 @@
t = Template(; user=me)
pkg_dir = joinpath(t.dir, test_pkg)
@testset "CITATION" begin
@testset "Plugin creation" begin
p = Citation()
@test isempty(p.gitignore)
@test p.dest == "CITATION.bib"
@test isempty(p.badges)
@test isempty(p.view)
@test !p.readme_section
p = Citation(; readme_section=true)
@test p.readme_section
end
@testset "File generation" begin
p = Citation()
@test gen_plugin(p, t, test_pkg) == ["CITATION.bib"]
@test isfile(joinpath(pkg_dir, "CITATION.bib"))
citation = read(joinpath(pkg_dir, "CITATION.bib"), String)
@test occursin("@misc", citation)
@test occursin("$(t.authors)", citation)
@test occursin("v0.1.0", citation)
end
@testset "Readme untouched" begin
p = Citation(; readme_section=false)
t.plugins[Citation] = p
isdir(pkg_dir) && rm(pkg_dir; recursive=true)
generate(test_pkg, t, git=false)
readme = read(joinpath(pkg_dir, "README.md"), String)
@test !occursin("## Citing", readme)
@test !occursin("CITATION.bib", readme)
end
@testset "Readme modification" begin
p = Citation(; readme_section=true)
t.plugins[Citation] = p
isdir(pkg_dir) && rm(pkg_dir; recursive=true)
generate(test_pkg, t, git=false)
readme = read(joinpath(pkg_dir, "README.md"), String)
@test occursin("## Citing", readme)
@test occursin("CITATION.bib", readme)
end
end
rm(pkg_dir; recursive=true)

View File

@ -1,41 +0,0 @@
t = Template(; user=me)
pkg_dir = joinpath(t.dir, test_pkg)
@testset "Codecov" begin
@testset "Plugin creation" begin
p = Codecov()
@test p.gitignore == ["*.jl.cov", "*.jl.*.cov", "*.jl.mem"]
@test p.src === nothing
@test p.dest == ".codecov.yml"
@test p.badges == [
Badge(
"Codecov",
"https://codecov.io/gh/{{USER}}/{{PKGNAME}}.jl/branch/master/graph/badge.svg",
"https://codecov.io/gh/{{USER}}/{{PKGNAME}}.jl",
)
]
@test isempty(p.view)
p = Codecov(; config_file=nothing)
@test p.src === nothing
p = Codecov(; config_file=test_file)
@test p.src == test_file
@test_throws ArgumentError Codecov(; config_file=fake_path)
end
@testset "Badge generation" begin
p = Codecov()
@test badges(p, me, test_pkg) == ["[![Codecov](https://codecov.io/gh/$me/$test_pkg.jl/branch/master/graph/badge.svg)](https://codecov.io/gh/$me/$test_pkg.jl)"]
end
@testset "File generation" begin
p = Codecov()
@test isempty(gen_plugin(p, t, test_pkg))
@test !isfile(joinpath(pkg_dir, ".codecov.yml"))
p = Codecov(; config_file=test_file)
@test gen_plugin(p, t, test_pkg) == [".codecov.yml"]
@test isfile(joinpath(pkg_dir, ".codecov.yml"))
end
end
rm(pkg_dir; recursive=true)

View File

@ -1,40 +0,0 @@
t = Template(; user=me)
pkg_dir = joinpath(t.dir, test_pkg)
@testset "Coveralls" begin
@testset "Plugin creation" begin
p = Coveralls()
@test p.gitignore == ["*.jl.cov", "*.jl.*.cov", "*.jl.mem"]
@test p.src === nothing
@test p.dest == ".coveralls.yml"
@test p.badges == [
Badge(
"Coveralls",
"https://coveralls.io/repos/github/{{USER}}/{{PKGNAME}}.jl/badge.svg?branch=master",
"https://coveralls.io/github/{{USER}}/{{PKGNAME}}.jl?branch=master",
)
]
@test isempty(p.view)
p = Coveralls(; config_file=nothing)
@test p.src === nothing
p = Coveralls(; config_file=test_file)
@test p.src == test_file
@test_throws ArgumentError Coveralls(; config_file=fake_path)
end
@testset "Badge generation" begin
p = Coveralls()
@test badges(p, me, test_pkg) == ["[![Coveralls](https://coveralls.io/repos/github/$me/$test_pkg.jl/badge.svg?branch=master)](https://coveralls.io/github/$me/$test_pkg.jl?branch=master)"]
end
@testset "File generation" begin
p = Coveralls()
@test isempty(gen_plugin(p, t, test_pkg))
@test !isfile(joinpath(pkg_dir, ".coveralls.yml"))
p = Coveralls(; config_file=test_file)
@test gen_plugin(p, t, test_pkg) == [".coveralls.yml"]
@test isfile(joinpath(pkg_dir, ".coveralls.yml"))
end
end
rm(pkg_dir; recursive=true)

View File

@ -1,68 +0,0 @@
t = Template(; user=me)
pkg_dir = joinpath(t.dir, test_pkg)
@testset "GitHubPages" begin
@testset "Plugin creation" begin
p = GitHubPages()
@test p.gitignore == ["/docs/build/", "/docs/site/"]
@test isempty(p.assets)
p = GitHubPages(; assets=[test_file])
@test p.assets == [test_file]
@test_throws ArgumentError GitHubPages(; assets=[fake_path])
end
@testset "Badge generation" begin
p = GitHubPages()
@test badges(p, me, test_pkg) == [
"[![Stable](https://img.shields.io/badge/docs-stable-blue.svg)](https://$me.github.io/$test_pkg.jl/stable)"
"[![Dev](https://img.shields.io/badge/docs-dev-blue.svg)](https://$me.github.io/$test_pkg.jl/dev)"
]
end
@testset "File generation" begin
p = GitHubPages()
@test gen_plugin(p, t, test_pkg) == ["docs/"]
@test isdir(joinpath(pkg_dir, "docs"))
@test isfile(joinpath(pkg_dir, "docs", "make.jl"))
make = readchomp(joinpath(pkg_dir, "docs", "make.jl"))
@test occursin("assets=String[]", make)
@test !occursin("deploydocs", make)
@test isdir(joinpath(pkg_dir, "docs", "src"))
@test isfile(joinpath(pkg_dir, "docs", "src", "index.md"))
index = readchomp(joinpath(pkg_dir, "docs", "src", "index.md"))
@test occursin("autodocs", index)
rm(joinpath(pkg_dir, "docs"); recursive=true)
p = GitHubPages(; assets=[test_file])
@test gen_plugin(p, t, test_pkg) == ["docs/"]
make = readchomp(joinpath(pkg_dir, "docs", "make.jl"))
# Check the formatting of the assets list.
@test occursin(
strip("""
assets=[
"assets/$(basename(test_file))",
]
"""),
make,
)
@test isfile(joinpath(pkg_dir, "docs", "src", "assets", basename(test_file)))
rm(joinpath(pkg_dir, "docs"); recursive=true)
t.plugins[TravisCI] = TravisCI()
@test gen_plugin(p, t, test_pkg) == ["docs/"]
make = readchomp(joinpath(pkg_dir, "docs", "make.jl"))
@test occursin("deploydocs", make)
rm(joinpath(pkg_dir, "docs"); recursive=true)
end
@testset "Package generation with GitHubPages plugin" begin
temp_dir = mktempdir()
t = Template(; user=me, dir=temp_dir, plugins=[GitHubPages()])
generate(test_pkg, t; gitconfig=gitconfig)
# Check that the gh-pages branch exists.
repo = LibGit2.GitRepo(joinpath(t.dir, test_pkg))
branches = map(b -> LibGit2.shortname(first(b)), LibGit2.GitBranchIter(repo))
@test in("gh-pages", branches)
end
end
rm(pkg_dir; recursive=true)

View File

@ -1,69 +0,0 @@
t = Template(; user=me)
pkg_dir = joinpath(t.dir, test_pkg)
@testset "GitLabCI" begin
@testset "Plugin creation" begin
p = GitLabCI()
@test p.gitignore == ["*.jl.cov", "*.jl.*.cov", "*.jl.mem"]
@test p.src == joinpath(PkgTemplates.DEFAULTS_DIR, "gitlab-ci.yml")
@test p.dest == ".gitlab-ci.yml"
@test p.badges == [
Badge(
"Build Status",
"https://gitlab.com/{{USER}}/{{PKGNAME}}.jl/badges/master/build.svg",
"https://gitlab.com/{{USER}}/{{PKGNAME}}.jl/pipelines",
),
Badge(
"Coverage",
"https://gitlab.com/{{USER}}/{{PKGNAME}}.jl/badges/master/coverage.svg",
"https://gitlab.com/{{USER}}/{{PKGNAME}}.jl/commits/master",
),
]
@test p.view == Dict("GITLABCOVERAGE" => true)
p = GitLabCI(; config_file=nothing)
@test p.src === nothing
p = GitLabCI(; config_file=test_file)
@test p.src == test_file
@test_throws ArgumentError GitLabCI(; config_file=fake_path)
p = GitLabCI(; coverage=false)
@test p.badges == [
Badge(
"Build Status",
"https://gitlab.com/{{USER}}/{{PKGNAME}}.jl/badges/master/build.svg",
"https://gitlab.com/{{USER}}/{{PKGNAME}}.jl/pipelines",
),
]
@test p.view == Dict("GITLABCOVERAGE" => false)
end
@testset "Badge generation" begin
p = GitLabCI()
@test badges(p, me, test_pkg) == [
"[![Build Status](https://gitlab.com/$me/$test_pkg.jl/badges/master/build.svg)](https://gitlab.com/$me/$test_pkg.jl/pipelines)",
"[![Coverage](https://gitlab.com/$me/$test_pkg.jl/badges/master/coverage.svg)](https://gitlab.com/$me/$test_pkg.jl/commits/master)",
]
end
@testset "File generation" begin
p = GitLabCI()
@test gen_plugin(p, t, test_pkg) == [".gitlab-ci.yml"]
@test isfile(joinpath(pkg_dir, ".gitlab-ci.yml"))
gitlab = read(joinpath(pkg_dir, ".gitlab-ci.yml"), String)
# The default plugin should enable the coverage step.
@test occursin("using Coverage", gitlab)
rm(joinpath(pkg_dir, ".gitlab-ci.yml"))
p = GitLabCI(; coverage=false)
gen_plugin(p, t, test_pkg)
gitlab = read(joinpath(pkg_dir, ".gitlab-ci.yml"), String)
# If coverage is false, there should be no coverage step.
@test !occursin("using Coverage", gitlab)
rm(joinpath(pkg_dir, ".gitlab-ci.yml"))
p = GitLabCI(; config_file=nothing)
@test isempty(gen_plugin(p, t, test_pkg))
@test !isfile(joinpath(pkg_dir, ".gitlab-ci.yml"))
end
end
rm(pkg_dir; recursive=true)

View File

@ -1,60 +0,0 @@
t = Template(; user=me)
pkg_dir = joinpath(t.dir, test_pkg)
@testset "GitLabPages" begin
@testset "Plugin creation" begin
p = GitLabPages()
@test p.gitignore == ["/docs/build/", "/docs/site/"]
@test isempty(p.assets)
p = GitLabPages(; assets=[test_file])
@test p.assets == [test_file]
@test_throws ArgumentError GitLabPages(; assets=[fake_path])
end
@testset "Badge generation" begin
p = GitLabPages()
@test badges(p, me, test_pkg) == [
"[![Dev](https://img.shields.io/badge/docs-dev-blue.svg)](https://$me.gitlab.io/$test_pkg.jl/dev)"
]
end
@testset "File generation" begin
p = GitLabPages()
@test gen_plugin(p, t, test_pkg) == ["docs/"]
@test isdir(joinpath(pkg_dir, "docs"))
@test isfile(joinpath(pkg_dir, "docs", "make.jl"))
make = readchomp(joinpath(pkg_dir, "docs", "make.jl"))
@test occursin("assets=String[]", make)
@test !occursin("deploydocs", make)
@test isdir(joinpath(pkg_dir, "docs", "src"))
@test isfile(joinpath(pkg_dir, "docs", "src", "index.md"))
index = readchomp(joinpath(pkg_dir, "docs", "src", "index.md"))
@test occursin("autodocs", index)
rm(joinpath(pkg_dir, "docs"); recursive=true)
p = GitLabPages(; assets=[test_file])
@test gen_plugin(p, t, test_pkg) == ["docs/"]
make = readchomp(joinpath(pkg_dir, "docs", "make.jl"))
# Check the formatting of the assets list.
@test occursin(
strip("""
assets=[
"assets/$(basename(test_file))",
]
"""),
make,
)
@test isfile(joinpath(pkg_dir, "docs", "src", "assets", basename(test_file)))
rm(joinpath(pkg_dir, "docs"); recursive=true)
end
@testset "Package generation with GitLabPages plugin" begin
temp_dir = mktempdir()
t = Template(; user=me, dir=temp_dir, plugins=[GitLabCI(), GitLabPages()])
generate(test_pkg, t; gitconfig=gitconfig)
gitlab = read(joinpath(t.dir, test_pkg, ".gitlab-ci.yml"), String)
@test occursin("pages:", gitlab)
end
end
rm(pkg_dir; recursive=true)

View File

@ -1,82 +0,0 @@
t = Template(; user=me)
pkg_dir = joinpath(t.dir, test_pkg)
@testset "TravisCI" begin
@testset "Plugin creation" begin
p = TravisCI()
@test isempty(p.gitignore)
@test p.src == joinpath(PkgTemplates.DEFAULTS_DIR, "travis.yml")
@test p.dest == ".travis.yml"
@test p.badges == [
Badge(
"Build Status",
"https://travis-ci.com/{{USER}}/{{PKGNAME}}.jl.svg?branch=master",
"https://travis-ci.com/{{USER}}/{{PKGNAME}}.jl",
),
]
@test isempty(p.view)
p = TravisCI(; config_file=nothing)
@test p.src === nothing
p = TravisCI(; config_file=test_file)
@test p.src == test_file
@test_throws ArgumentError TravisCI(; config_file=fake_path)
end
@testset "Badge generation" begin
p = TravisCI()
@test badges(p, me, test_pkg) == ["[![Build Status](https://travis-ci.com/$me/$test_pkg.jl.svg?branch=master)](https://travis-ci.com/$me/$test_pkg.jl)"]
end
@testset "File generation" begin
# Without a coverage plugin in the template, there should be no post-test step.
p = TravisCI()
@test gen_plugin(p, t, test_pkg) == [".travis.yml"]
@test isfile(joinpath(pkg_dir, ".travis.yml"))
travis = read(joinpath(pkg_dir, ".travis.yml"), String)
@test !occursin("after_success", travis)
@test !occursin("Codecov.submit", travis)
@test !occursin("Coveralls.submit", travis)
@test !occursin("stage: Documentation", travis)
rm(joinpath(pkg_dir, ".travis.yml"))
# Generating the plugin with Codecov in the template should create a post-test step.
t.plugins[Codecov] = Codecov()
gen_plugin(p, t, test_pkg)
delete!(t.plugins, Codecov)
travis = read(joinpath(pkg_dir, ".travis.yml"), String)
@test occursin("after_success", travis)
@test occursin("Codecov.submit", travis)
@test !occursin("Coveralls.submit", travis)
@test !occursin("stage: Documentation", travis)
rm(joinpath(pkg_dir, ".travis.yml"))
# Coveralls should do the same.
t.plugins[Coveralls] = Coveralls()
gen_plugin(p, t, test_pkg)
delete!(t.plugins, Coveralls)
travis = read(joinpath(pkg_dir, ".travis.yml"), String)
@test occursin("after_success", travis)
@test occursin("Coveralls.submit", travis)
@test !occursin("Codecov.submit", travis)
@test !occursin("stage: Documentation", travis)
rm(joinpath(pkg_dir, ".travis.yml"))
# With a Documenter plugin, there should be a docs deployment step.
t.plugins[GitHubPages] = GitHubPages()
gen_plugin(p, t, test_pkg)
delete!(t.plugins, GitHubPages)
travis = read(joinpath(pkg_dir, ".travis.yml"), String)
@test occursin("after_success", travis)
@test occursin("stage: Documentation", travis)
@test !occursin("Codecov.submit", travis)
@test !occursin("Coveralls.submit", travis)
rm(joinpath(pkg_dir, ".travis.yml"))
p = TravisCI(; config_file=nothing)
@test isempty(gen_plugin(p, t, test_pkg))
@test !isfile(joinpath(pkg_dir, ".travis.yml"))
end
end
rm(pkg_dir; recursive=true)

View File

@ -1,20 +0,0 @@
using PkgTemplates
using Test
using Dates
using LibGit2
using Pkg
import PkgTemplates: badges, version_floor, substitute, read_license, gen_file, gen_readme,
gen_tests, gen_license, gen_gitignore, gen_plugin, show_license, LICENSES,
LICENSE_DIR, Plugin, GenericPlugin, CustomPlugin, Badge, format, interactive,
DEFAULTS_DIR, Documenter
mktempdir() do temp_dir
mkdir(joinpath(temp_dir, "dev"))
pushfirst!(DEPOT_PATH, temp_dir)
cd(temp_dir) do
@testset "PkgTemplates.jl" begin
include("tests.jl")
end
end
end

View File

@ -44,7 +44,7 @@ write(test_file, template_text)
@test t.license == "MIT"
@test t.authors == "foo"
@test t.dir == default_dir
@test t.julia_version == PkgTemplates.default_version()
@test t.julia_version == PkgTemplates.default_version
@test !t.ssh
@test !t.manifest
@test isempty(t.plugins)
@ -107,7 +107,7 @@ end
@testset "Show methods" begin
pkg_dir = replace(default_dir, homedir() => "~")
ver = PkgTemplates.version_floor(PkgTemplates.default_version())
ver = PkgTemplates.version_floor(PkgTemplates.default_version)
buf = IOBuffer()
t = Template(; user=me, authors="foo")
show(buf, t)
@ -327,12 +327,6 @@ end
rm(temp_dir; recursive=true)
end
@testset "Git-less template creation" begin
if isempty(LibGit2.getconfig("user.name", ""))
@test_logs Template(; user=me, git=false)
end
end
@testset "Git-less package generation" begin
t = Template(; user=me)
generate(test_pkg, t; git=false)
@ -340,84 +334,6 @@ end
@test !isfile(joinpath(t.dir, ".gitignore"))
end
@testset "Version floor" begin
@test version_floor(v"1.0.0") == "1.0"
@test version_floor(v"1.0.1") == "1.0"
@test version_floor(v"1.0.1-pre") == "1.0"
@test version_floor(v"1.0.0-pre") == "1.0-"
end
@testset "Mustache substitution" begin
view = Dict{String, Any}()
text = substitute(template_text, view)
@test !occursin("PKGNAME: $test_pkg", text)
@test !occursin("Documenter", text)
@test !occursin("Codecov", text)
@test !occursin("Coveralls", text)
@test !occursin("After", text)
@test !occursin("Other", text)
view["PKGNAME"] = test_pkg
view["OTHER"] = true
text = substitute(template_text, view)
@test occursin("PKGNAME: $test_pkg", text)
@test occursin("Other", text)
t = Template(; user=me)
view["OTHER"] = false
text = substitute(template_text, t; view=view)
@test occursin("PKGNAME: $test_pkg", text)
@test occursin("VERSION: $(t.julia_version.major).$(t.julia_version.minor)", text)
@test !occursin("Documenter", text)
@test !occursin("After", text)
@test !occursin("Other", text)
t.plugins[GitHubPages] = GitHubPages()
text = substitute(template_text, t; view=view)
@test occursin("Documenter", text)
@test occursin("After", text)
empty!(t.plugins)
t.plugins[Codecov] = Codecov()
text = substitute(template_text, t; view=view)
@test occursin("Codecov", text)
@test occursin("After", text)
empty!(t.plugins)
t.plugins[Coveralls] = Coveralls()
text = substitute(template_text, t; view=view)
@test occursin("Coveralls", text)
@test occursin("After", text)
empty!(t.plugins)
view["OTHER"] = true
text = substitute(template_text, t; view=view)
@test occursin("Other", text)
end
@testset "License display" begin
io = IOBuffer()
available_licenses(io)
licenses = String(take!(io))
show_license(io, "MIT")
mit = String(take!(io))
# Check that all licenses are included in the display.
for (short, long) in LICENSES
@test occursin("$short: $long", licenses)
end
@test strip(mit) == strip(read_license("MIT"))
@test strip(read_license("MIT")) == strip(read(joinpath(LICENSE_DIR, "MIT"), String))
@test_throws ArgumentError read_license(fake_path)
# Check that all licenses included with the package are displayed.
for license in readdir(LICENSE_DIR)
@test haskey(LICENSES, license)
end
# Check that all licenses displayed are included with the package.
@test length(readdir(LICENSE_DIR)) == length(LICENSES)
end
@testset "Plugins" begin
t = Template(; user=me)
pkg_dir = joinpath(t.dir, test_pkg)
@ -474,7 +390,8 @@ end
warn_str = "Ignoring predefined Documenter kwargs \"format\" from additional kwargs"
check_kwargs(kwargs, warn_str)
kwargs = Dict(:checkdocs => :none,
kwargs = Dict(
:checkdocs => :none,
:strict => true,
:format => :markdown,
:stringarg => "string",
@ -488,5 +405,3 @@ end
end
include(joinpath("interactive", "interactive.jl"))
rm(test_file)