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: os:
- linux - linux
- osx - osx
- windows
julia: julia:
- 1.0 - 1.0
- 1.1 - 1.2
- nightly - nightly
matrix: matrix:
allow_failures: allow_failures:
@ -12,14 +13,19 @@ matrix:
fast_finish: true fast_finish: true
notifications: notifications:
email: false 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: jobs:
include: include:
- stage: Documentation - stage: Documentation
julia: 1.1 julia: 1.2
script: julia --project=docs -e ' script: julia --project=docs -e '
using Pkg; using Pkg
Pkg.develop(PackageSpec(path=pwd())); Pkg.develop(PackageSpec(; path=pwd()))
Pkg.instantiate(); Pkg.instantiate()
include("docs/make.jl");' include("docs/make.jl")'
after_success: skip after_success: skip

View File

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

View File

@ -16,7 +16,8 @@ URIParser = "30578b45-9adc-5946-b283-645ec420af67"
julia = "1" julia = "1"
[extras] [extras]
Suppressor = "fd094767-a336-5f1f-9728-57cf17d0bbfb"
Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40" Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40"
[targets] [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 # Documentation: https://github.com/JuliaCI/Appveyor.jl
environment: environment:
matrix: matrix:
- julia_version: {{VERSION}} {{#VERSIONS}}
- julia_version: nightly - julia_version: {{.}}
{{/VERSIONS}}
platform: platform:
- x86 {{#PLATFORMS}}
- x64 - {{.}}
{{/PLATFORMS}}
{{#HAS_NIGHTLY}}
matrix: matrix:
allow_failures: allow_failures:
- julia_version: nightly - julia_version: nightly
{{/HAS_NIGHTLY}}
branches: branches:
only: only:
- master - master
@ -26,8 +30,8 @@ build_script:
test_script: test_script:
- echo "%JL_TEST_SCRIPT%" - echo "%JL_TEST_SCRIPT%"
- C:\julia\bin\julia -e "%JL_TEST_SCRIPT%" - C:\julia\bin\julia -e "%JL_TEST_SCRIPT%"
{{#CODECOV}} {{#HAS_CODECOV}}
on_success: on_success:
- echo "%JL_CODECOV_SCRIPT%" - echo "%JL_CODECOV_SCRIPT%"
- C:\julia\bin\julia -e "%JL_CODECOV_SCRIPT%" - C:\julia\bin\julia -e "%JL_CODECOV_SCRIPT%"
{{/CODECOV}} {{/HAS_CODECOV}}

View File

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

View File

@ -1,19 +1,30 @@
Julia {{VERSION}}: {{#VERSIONS}}
image: julia:{{VERSION}} Julia {{.}}:
script: julia --project='@.' -e 'using Pkg; Pkg.build(); Pkg.test({{#GITLABCOVERAGE}}; coverage=true{{/GITLABCOVERAGE}})' image: julia:{{.}}
{{#GITLABCOVERAGE}} script: julia --project=@. -e '
using Pkg
Pkg.build()
Pkg.test({{#HAS_COVERAGE}}coverage=true{{/HAS_COVERAGE}})'
{{/VERSIONS}}
{{#HAS_COVERAGE}}
coverage: /Test Coverage (\d+\.\d+%)/ coverage: /Test Coverage (\d+\.\d+%)/
after_script: 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' - julia -e '
{{/GITLABCOVERAGE}} using Pkg
{{#DOCUMENTER}} 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: pages:
image: julia:{{VERSION}} image: julia:{{VERSION}}
stage: deploy stage: deploy
script: script:
- julia --project=docs -e ' - julia --project=docs -e '
using Pkg; using Pkg;
Pkg.develop(PackageSpec(path=pwd())); Pkg.develop(PackageSpec(; path=pwd()));
Pkg.instantiate(); Pkg.instantiate();
include("docs/make.jl");' include("docs/make.jl");'
- mkdir -p public - mkdir -p public
@ -23,4 +34,4 @@ pages:
- public - public
only: only:
- master - 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 language: julia
os:
- linux
- osx
julia:
- {{VERSION}}
- nightly
matrix:
allow_failures:
- julia: nightly
fast_finish: true
notifications: notifications:
email: false email: false
{{#COVERAGE}} {{#HAS_DOCUMENTER}}
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}}
jobs: jobs:
fast_finish: true
{{#HAS_NIGHTLY}}
allow_failures:
- julia: nightly
{{/HAS_NIGHTLY}}
include: include:
{{#JOBS}}
- julia: {{JULIA}}
os: {{OS}}
{{#ARCH}}
arch: {{ARCH}}
{{/ARCH}}
{{/JOBS}}
{{#HAS_DOCUMENTER}}
- stage: Documentation - stage: Documentation
julia: {{VERSION}} julia: {{VERSION}}
script: julia --project=docs -e ' script: julia --project=docs -e '
using Pkg; using Pkg;
Pkg.develop(PackageSpec(path=pwd())); Pkg.develop(PackageSpec(; path=pwd()));
Pkg.instantiate(); Pkg.instantiate();
include("docs/make.jl");' include("docs/make.jl");'
after_success: skip 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]] [[Base64]]
uuid = "2a0f44e3-6c83-55bd-87e4-b1978d98bd5f" uuid = "2a0f44e3-6c83-55bd-87e4-b1978d98bd5f"
@ -6,35 +8,34 @@ deps = ["Printf"]
uuid = "ade2ca70-3891-5945-98fb-dc099432e06a" uuid = "ade2ca70-3891-5945-98fb-dc099432e06a"
[[Distributed]] [[Distributed]]
deps = ["LinearAlgebra", "Random", "Serialization", "Sockets"] deps = ["Random", "Serialization", "Sockets"]
uuid = "8ba89e20-285c-5b6f-9357-94700520ee1b" uuid = "8ba89e20-285c-5b6f-9357-94700520ee1b"
[[DocStringExtensions]] [[DocStringExtensions]]
deps = ["LibGit2", "Markdown", "Pkg", "Test"] deps = ["LibGit2", "Markdown", "Pkg", "Test"]
git-tree-sha1 = "1df01539a1c952cef21f2d2d1c092c2bcf0177d7" git-tree-sha1 = "0513f1a8991e9d83255e0140aace0d0fc4486600"
uuid = "ffbed154-4ef7-542d-bbb7-c09d3a79fcae" uuid = "ffbed154-4ef7-542d-bbb7-c09d3a79fcae"
version = "0.6.0" version = "0.8.0"
[[Documenter]] [[Documenter]]
deps = ["Base64", "DocStringExtensions", "InteractiveUtils", "LibGit2", "Logging", "Markdown", "Pkg", "REPL", "Random", "Test", "Unicode"] deps = ["Base64", "DocStringExtensions", "InteractiveUtils", "JSON", "LibGit2", "Logging", "Markdown", "REPL", "Test", "Unicode"]
git-tree-sha1 = "a6db1c69925cdc53aafb38caec4446be26e0c617" git-tree-sha1 = "c61d6eedbc3c4323c08b64af12d29c8ee0fcbb5f"
uuid = "e30172f5-a6a5-5a46-863b-614d45cd2de4" uuid = "e30172f5-a6a5-5a46-863b-614d45cd2de4"
version = "0.21.0" version = "0.23.2"
[[InteractiveUtils]] [[InteractiveUtils]]
deps = ["LinearAlgebra", "Markdown"] deps = ["Markdown"]
uuid = "b77e0a4c-d291-57a0-90e8-8db25a27a240" 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]] [[LibGit2]]
uuid = "76f85450-5226-5b5a-8eaa-529ad045b433" uuid = "76f85450-5226-5b5a-8eaa-529ad045b433"
[[Libdl]]
uuid = "8f399da3-3557-5675-b5ff-fb832c97cbdb"
[[LinearAlgebra]]
deps = ["Libdl"]
uuid = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e"
[[Logging]] [[Logging]]
uuid = "56ddb016-857b-54e1-b83d-db4d58db5568" uuid = "56ddb016-857b-54e1-b83d-db4d58db5568"
@ -42,6 +43,15 @@ uuid = "56ddb016-857b-54e1-b83d-db4d58db5568"
deps = ["Base64"] deps = ["Base64"]
uuid = "d6f4376e-aef5-505a-96c1-9c027394607a" 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]] [[Pkg]]
deps = ["Dates", "LibGit2", "Markdown", "Printf", "REPL", "Random", "SHA", "UUIDs"] deps = ["Dates", "LibGit2", "Markdown", "Printf", "REPL", "Random", "SHA", "UUIDs"]
uuid = "44cfe95a-1eb2-52ea-b672-e2afdf69b78f" uuid = "44cfe95a-1eb2-52ea-b672-e2afdf69b78f"
@ -72,7 +82,7 @@ deps = ["Distributed", "InteractiveUtils", "Logging", "Random"]
uuid = "8dfed614-e22c-5e08-85e1-65c5234f0b40" uuid = "8dfed614-e22c-5e08-85e1-65c5234f0b40"
[[UUIDs]] [[UUIDs]]
deps = ["Random"] deps = ["Random", "SHA"]
uuid = "cf7118a7-6976-5b1a-9a39-7adc72f591a4" uuid = "cf7118a7-6976-5b1a-9a39-7adc72f591a4"
[[Unicode]] [[Unicode]]

View File

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

View File

@ -4,17 +4,11 @@ CurrentModule = PkgTemplates
# Licenses # Licenses
[Many open-source licenses](https://github.com/christopher-dG/PkgTemplates.jl/tree/master/licenses) [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, are available for use with `PkgTemplates`, but if you see that one is missing, don't
don't hesitate to open an issue or PR. hesitate to open an issue or PR.
```@docs ```@docs
available_licenses available_licenses
show_license 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 Creating new packages with `PkgTemplates` revolves around creating a new
[`Template`](@ref), then calling [`generate`](@ref) on it. [`Template`](@ref), then calling [`generate`](@ref) on it.
## `Template`
```@docs ```@docs
Template Template
interactive_template interactive_template
```
## `generate`
```@docs
generate generate
generate_interactive generate_interactive
``` ```
### Helper Functions
```@docs
gen_tests
gen_readme
gen_gitignore
gen_license
```

View File

@ -4,72 +4,69 @@ CurrentModule = PkgTemplates
# Plugin Development # Plugin Development
The best and easiest way to contribute to `PkgTemplates` is to write new The best and easiest way to contribute to `PkgTemplates` is to write new plugins.
plugins.
```@docs ```@docs
Plugin 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 ```@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 ## Custom Plugins
```@docs When a plugin is too complicated to be expressed with [`GeneratedPlugin`](@ref), we only
CustomPlugin need to implement a few methods to create something fully custom.
```
### `CustomPlugin` Required Methods ### Required Methods
#### `gen_plugin`
```@docs ```@docs
gen_plugin gen_plugin
```
### Optional Methods
```@docs
interactive interactive
``` ```
**Note**: [`interactive`](@ref) is not strictly required, however without it, Additionally, [`gitignore`](@ref), [`badges`](@ref), and [`view`](@ref) can also be
your custom plugin will not be available when creating templates with implemented in the same way as for [`GeneratedPlugin`](@ref)s (they have empty default
[`interactive_template`](@ref). implementations). [`source`](@ref) and [`destination`](@ref) have no meaning for custom
plugins.
#### `badges` ### Helpers
```@docs These types and functions will make implementing the above methods much easier.
badges
```
## Helper Types/Functions
#### `gen_file`
```@docs
gen_file
```
#### `substitute`
```@docs
substitute
```
#### `Badge`
```@docs ```@docs
Badge Badge
``` gen_file
substitute
#### `format`
```@docs
format
```
#### `version_floor`
```@docs
version_floor version_floor
``` ```

View File

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

View File

@ -1,36 +1,34 @@
module PkgTemplates module PkgTemplates
using Dates using Base: @kwdef, current_project
using InteractiveUtils using Base.Filesystem: contractuser
using LibGit2 using Dates: month, today, year
using Mustache using InteractiveUtils: subtypes
using Pkg using LibGit2: LibGit2
using REPL.TerminalMenus using Mustache: render
using URIParser using Pkg: PackageSpec, Pkg
using REPL.TerminalMenus: MultiSelectMenu, RadioMenu, request
using URIParser: URI
export export
# Template/package generation.
Template, Template,
generate,
interactive_template,
generate_interactive,
# Licenses.
show_license,
available_licenses,
# Plugins.
GitHubPages,
GitLabPages,
AppVeyor, AppVeyor,
TravisCI,
GitLabCI,
CirrusCI, CirrusCI,
Citation,
Codecov, Codecov,
Coveralls, 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. 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 abstract type Plugin end
@ -38,18 +36,6 @@ include("licenses.jl")
include("template.jl") include("template.jl")
include("generate.jl") include("generate.jl")
include("plugin.jl") include("plugin.jl")
include(joinpath("plugins", "documenter.jl")) include("interactive.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]
end end

View File

@ -1,16 +1,12 @@
""" const TEST_UUID = "8dfed614-e22c-5e08-85e1-65c5234f0b40"
generate(pkg::AbstractString, t::Template) -> Nothing
generate(t::Template, pkg::AbstractString) -> Nothing
Generate a package named `pkg` from `t`. If `git` is `false`, no Git repository is created.
""" """
function generate( (::Template)(pkg::AbstractString)
pkg::AbstractString,
t::Template; Generate a package named `pkg` from a [`Template`](@ref).
git::Bool=true, """
gitconfig::Union{GitConfig, Nothing}=nothing, function (t::Template)(pkg::AbstractString)
) endswith(pkg, ".jl") && (pkg = pkg[1:end-3])
pkg = splitjl(pkg)
pkg_dir = joinpath(t.dir, pkg) pkg_dir = joinpath(t.dir, pkg)
ispath(pkg_dir) && throw(ArgumentError("$pkg_dir already exists")) ispath(pkg_dir) && throw(ArgumentError("$pkg_dir already exists"))
@ -18,9 +14,9 @@ function generate(
# Create the directory with some boilerplate inside. # Create the directory with some boilerplate inside.
Pkg.generate(pkg_dir) 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 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 end
# Replace the authors field with the template's authors. # Replace the authors field with the template's authors.
@ -31,329 +27,35 @@ function generate(
write(path, replace(project, r"authors = .*" => "authors = $authors")) write(path, replace(project, r"authors = .*" => "authors = $authors"))
end end
if git if t.git
# Initialize the repo. # Initialize the repo, make a commit, and set the remote.
repo = LibGit2.init(pkg_dir) 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") LibGit2.commit(repo, "Initial commit")
rmt = if t.ssh rmt = if t.ssh
"git@$(t.host):$(t.user)/$pkg.jl.git" "git@$(t.host):$(t.user)/$pkg.jl.git"
else else
"https://$(t.host)/$(t.user)/$pkg.jl" "https://$(t.host)/$(t.user)/$pkg.jl"
end end
# We need to set the remote in a strange way, see #8.
close(LibGit2.GitRemote(repo, "origin", rmt)) 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 end
# Generate the files. # Generate the files.
files = vcat( foreach(p -> gen_plugin(p, t, pkg_dir), values(t.plugins))
"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))...),
)
if git if t.git
append!(files, gen_gitignore(pkg_dir, t)) # Commit the files.
LibGit2.add!(repo, files...) LibGit2.add!(repo, ".")
LibGit2.commit(repo, "Files generated by PkgTemplates") 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 end
if t.dev if t.dev
# Add the new package to the current environment. # Add the new package to the current environment.
Pkg.develop(PackageSpec(path=pkg_dir)) Pkg.develop(PackageSpec(; path=pkg_dir))
end end
@info "New package is at $pkg_dir" @info "New package is at $pkg_dir"
catch e catch
rm(pkg_dir; recursive=true) rm(pkg_dir; recursive=true, force=true)
rethrow(e) rethrow()
end end
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 @@
""" const DEFAULTS_DIR = normpath(joinpath(@__DIR__, "..", "defaults"))
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.
# Attributes abstract type BasicPlugin <: Plugin end
* `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.
# Example default_file(paths::AbstractString...) = joinpath(DEFAULTS_DIR, paths...)
```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
""" """
Custom plugins are plugins whose behaviour does not follow the [`GenericPlugin`](@ref) view(::Plugin, ::Template, pkg::AbstractString) -> Dict{String}
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.
# Attributes Return extra string substitutions to be made for this plugin.
* `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.
""" """
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 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. * `hover::AbstractString`: Text to appear when the mouse is hovered over the badge.
* `image::AbstractString`: URL to the image to display. * `image::AbstractString`: URL to the image to display.
* `link::AbstractString`: URL to go to upon clicking the badge. * `link::AbstractString`: URL to go to upon clicking the badge.
@ -158,89 +56,70 @@ struct Badge
link::String link::String
end end
""" Base.string(b::Badge) = "[![$(b.hover)]($(b.image))]($(b.link))"
format(b::Badge) -> String
Return `badge`'s data formatted as a Markdown string. # Format a plugin's badges as a list of strings, with all substitutions applied.
""" function badges(p::Plugin, t::Template, pkg_name::AbstractString)
format(b::Badge) = "[![$(b.hover)]($(b.image))]($(b.link))" 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. Generate any files associated with a plugin.
# Arguments ## Arguments
* `p::Plugin`: Plugin whose files are being generated. * `p::Plugin`: Plugin whose files are being generated.
* `t::Template`: Template configuration. * `t::Template`: Template configuration.
* `pkg_name::AbstractString`: Name of the package. * `pkg::AbstractString`: Name of the package.
Returns an array of generated file/directory names.
""" """
gen_plugin(::Plugin, ::Template, ::AbstractString) = String[] gen_plugin(::Plugin, ::Template, ::AbstractString) = nothing
function gen_plugin(p::GenericPlugin, t::Template, pkg_name::AbstractString) function gen_plugin(p::BasicPlugin, t::Template, pkg::AbstractString)
if p.src === nothing source(p) === nothing && return
return String[] text = render(source(p), view(p, t, pkg); tags=tags(p))
end gen_file(joinpath(t.dir, pkg_name, destination(p)), text)
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]
end end
""" """
badges(p::Plugin, user::AbstractString, pkg_name::AbstractString) -> Vector{String} gen_file(file::AbstractString, text::AbstractString) -> Int
Generate Markdown badges for the plugin. Create a new file containing some given text.
Trailing whitespace is removed, and the file will end with a newline.
# 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.
""" """
badges(::Plugin, ::AbstractString, ::AbstractString) = String[] function gen_file(file::AbstractString, text::AbstractString)
mkpath(dirname(file))
function badges(p::GenericPlugin, user::AbstractString, pkg_name::AbstractString) text = join(map(rstrip, split(text, "\n")), "\n")
# Give higher priority to replacements defined in the plugin's view. endswith(text , "\n") || (text *= "\n")
view = merge(Dict("USER" => user, "PKGNAME" => pkg_name), p.view) write(file, text)
return map(b -> substitute(format(b), view), p.badges)
end end
""" render_file(file::AbstractString, view, tags) = render_text(read(file, String), view, tags)
interactive(T::Type{<:Plugin}; file::Union{AbstractString, Nothing}="") -> Plugin
Interactively create a plugin of type `T`, where `file` is the plugin type's default render_text(text::AbstractString, view, tags) = render(text, view; tags=tags)
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)
print("$name: Enter the config template filename (\"None\" for no file) ") function render_badges(p::BasicPlugin, t::Template, pkg::AbstractString)
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)
end 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 Add `Citation` to a [`Template`](@ref)'s plugin list to generate a `CITATION.bib` file.
generated repositories, and an appropriate section in the README. If `readme` is set, then `README.md` will contain a section about citing.
# Keyword Arguments:
* `readme_section::Bool=false`: whether to add a section in the readme pointing to `CITATION.bib`.
""" """
struct Citation <: GenericPlugin @kwdef struct Citation <: BasicPlugin
gitignore::Vector{AbstractString} file::String = default_file("CITATION.bib")
src::Union{String, Nothing} readme::Bool = false
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
end 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}) function interactive(::Type{Citation})
print("Citation: Add a section to README.md mentioning CITATION.bib? [no]: ") readme = prompt_bool("Citation: Add a section to the README", false)
readme = uppercase(readline()) in ["Y", "YES", "TRUE"]
return Citation(; readme_section=readme) return Citation(; readme_section=readme)
end 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 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 Documenter{T<:Union{TravisCI, GitLabCI, Nothing}}(;
generation via [Documenter.jl](https://github.com/JuliaDocs/Documenter.jl). 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 The `Documenter` plugin adds support for documentation generation via [Documenter.jl](https://github.com/JuliaDocs/Documenter.jl).
file contains the Documenter.makedocs command with predefined values for `modules`, Documentation deployment depends on `T`, where `T` is some supported CI plugin, or `Nothing` to only support local documentation builds.
`format`, `pages`, `repo`, `sitename`, and `authors`.
The subtype is expected to include the following fields: ## Keyword Arguments
* `assets::Vector{AbstractString}`, a list of filenames to be included as the `assets` todo
kwarg to `makedocs` - `assets::Vector{<:AbstractString}=String[]`:
* `gitignore::Vector{AbstractString}`, a list of files to be added to the `.gitignore` - `makedocs_kwargs::Dict{Symbol}=Dict{Symbol, Any}()`:
- `canonical_url::Union{Function, Nothing}=nothing`:`
It may optionally include the field `additional_kwargs::Union{AbstractDict, NamedTuple}` !!! note
to allow additional kwargs to be added to `makedocs`. 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) # Can't use @kwdef due to some weird precompilation issues.
path = joinpath(t.dir, pkg_name) function Documenter{T}(
docs_dir = joinpath(path, "docs") assets::Vector{<:AbstractString}=String[],
mkpath(docs_dir) 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. # Create the documentation project.
proj = Base.current_project() docs_dir = joinpath(pkg_dir, "docs")
proj = current_project()
try try
Pkg.activate(docs_dir) Pkg.activate(docs_dir)
Pkg.add(PackageSpec(; name="Documenter", uuid=DOCUMENTER_UUID)) 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) proj === nothing ? Pkg.activate() : Pkg.activate(proj)
end end
tab = repeat(" ", 4) # Copy any assets.
assets_string = if !isempty(p.assets) assets_dir = joinpath(docs_dir, "src", "assets")
mkpath(joinpath(docs_dir, "src", "assets")) isempty(p.assets) || mkpath(assets_dir)
for file in p.assets foreach(a -> cp(a, joinpath(assets_dir, basename(asset))), 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)
end end
function Base.show(io::IO, p::Documenter) function interactive(::Type{Documenter{T}}) where T
spc = " " name = "Documenter{$T}"
println(io, nameof(typeof(p)), ":")
n = length(p.assets) print("$name: Enter any Documenter asset files (separated by spaces) [none]: ")
s = n == 1 ? "" : "s" assets = split(readline())
print(io, spc, "$n asset file$s")
if n == 0 print("$name: Enter any extra makedocs key-value pairs (joined by '=') [none]\n> ")
println(io) kwargs = Dict{Symbol, Any}()
else line = map(split(readline())) do kv
println(io, ": ", join(map(a -> replace(a, homedir() => "~"), p.assets), ", ")) k, v = split(kv, "="; limit=2)
kwargs[Symbol(k)] = eval(Meta.parse(v))
end end
n = length(p.gitignore) return Documenter{T}(; assets=assets, kwargs=kwargs)
s = n == 1 ? "" : "s"
print(io, "$spc→ $n gitignore entrie$s")
n > 0 && print(io, ": ", join(map(repr, p.gitignore), ", "))
end 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) 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 Records common information used to generate a package.
create a template, you can use [`interactive_template`](@ref) instead.
# Keyword Arguments ## Keyword Arguments
* `user::AbstractString=""`: GitHub (or other code hosting service) username. If left - `user::AbstractString=""`: GitHub (or other code hosting service) username.
unset, it will take the the global git config's value (`github.user`). If that is not If left unset, it will take the the global Git config's value (`github.user`).
set, an `ArgumentError` is thrown. **This is case-sensitive for some plugins, so take If that is not set, an `ArgumentError` is thrown.
care to enter it correctly.** 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 - `host::AbstractString="github.com"`: URL to the code hosting service where your package will reside.
will reside. Note that while hosts other than GitHub won't cause errors, they are not 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.
officially supported and they will cause certain plugins will produce incorrect output. - `authors::Union{AbstractString, Vector{<:AbstractString}}=""`: Names that appear on the license.
* `license::AbstractString="MIT"`: Name of the package license. If an empty string is Supply a string for one author or an array for multiple.
given, no license is created. [`available_licenses`](@ref) can be used to list all Similarly to `user`, it will take the value of of the global Git config's value if it is left unset.
available licenses, and [`show_license`](@ref) can be used to print out a particular - `dir::AbstractString=$(contractuser(Pkg.devdir()))`: Directory in which the package will go.
license's text. Relative paths are converted to absolute ones at template creation time.
* `authors::Union{AbstractString, Vector{<:AbstractString}}=""`: Names that appear on the - `julia_version::VersionNumber=$DEFAULT_VERSION`: Minimum allowed Julia version.
license. Supply a string for one author or an array for multiple. Similarly to `user`, - `ssh::Bool=false`: Whether or not to use SSH for the git remote. If `false` HTTPS will be used.
it will take the value of of the global git config's value if it is left unset. - `manifest::Bool=false`: Whether or not to commit the `Manifest.toml`.
* `dir::AbstractString=$(replace(Pkg.devdir(), homedir() => "~"))`: Directory in which the - `git::Bool=true`: Whether or not to create a Git repository for generated packages.
package will go. Relative paths are converted to absolute ones at template creation time. - `develop::Bool=true`: Whether or not to `develop` generated packages in the active environment.
* `julia_version::VersionNumber=$(default_version())`: Minimum allowed Julia version. - `plugins::Vector{<:Plugin}=Plugin[]`: A list of plugins that the package will include.
* `ssh::Bool=false`: Whether or not to use SSH for the git remote. If `false` HTTPS will be used. - `disable_default_plugins::Vector{DataType}=DataType[]`: Default plugins to disable.
* `dev::Bool=true`: Whether or not to `Pkg.develop` generated packages. The default plugins are [`Readme`](@ref), [`License`](@ref), [`Tests`](@ref), and [`Gitignore`](@ref).
* `manifest::Bool=false`: Whether or not to commit the `Manifest.toml`. To override a default plugin instead of disabling it altogether, supply it via `plugins`.
* `plugins::Vector{<:Plugin}=Plugin[]`: A list of `Plugin`s that the package will include. - `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 struct Template
user::String user::String
host::String host::String
license::String
authors::String authors::String
dir::String dir::String
julia_version::VersionNumber julia_version::VersionNumber
ssh::Bool ssh::Bool
dev::Bool
manifest::Bool manifest::Bool
git::Bool
develop::Bool
plugins::Dict{DataType, <:Plugin} plugins::Dict{DataType, <:Plugin}
end
function Template(; Template(; interactive::Bool=false, kwargs...) = make_template(Val(interactive); kwargs...)
user::AbstractString="",
host::AbstractString="https://github.com", function make_template(::Val{false}; kwargs...)
license::AbstractString="MIT", user = getkw(kwargs, :user)
authors::Union{AbstractString, Vector{<:AbstractString}}="", if isempty(user)
dir::AbstractString=Pkg.devdir(), throw(ArgumentError("No username found, set one with user=username"))
julia_version::VersionNumber=default_version(), end
ssh::Bool=false,
dev::Bool=true, host = getkw(kwargs, :host)
manifest::Bool=false, host = URI(occursin("://", host) ? host : "https://$host").host
plugins::Vector{<:Plugin}=Plugin[],
git::Bool=true, 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 end
function Base.show(io::IO, t::Template) hasplugin(t::Template, ::Type{T}) where T <: Plugin = any(U -> U <: T, keys(t.plugins))
maybe(s::String) = isempty(s) ? "None" : s
spc = " "
println(io, "Template:") getkw(kwargs, k) = get(() -> defaultkw(k), kwargs, k)
println(io, spc, "→ User: ", maybe(t.user))
println(io, spc, "→ Host: ", maybe(t.host))
print(io, spc, "→ License: ") defaultkw(s::Symbol) = defaultkw(Val(s))
if isempty(t.license) defaultkw(::Val{:user}) = LibGit2.getconfig("github.user", "")
println(io, "None") defaultkw(::Val{:host}) = "https://github.com"
else defaultkw(::Val{:dir}) = Pkg.devdir()
println(io, t.license, " ($(t.authors) ", year(today()), ")") defaultkw(::Val{:julia_version}) = DEFAULT_VERSION
end defaultkw(::Val{:ssh}) = false
defaultkw(::Val{:manifest}) = false
println(io, spc, "→ Package directory: ", replace(maybe(t.dir), homedir() => "~")) defaultkw(::Val{:git}) = true
println(io, spc, "→ Minimum Julia version: v", version_floor(t.julia_version)) defaultkw(::Val{:develop}) = true
println(io, spc, "→ SSH remote: ", t.ssh ? "Yes" : "No") defaultkw(::Val{:plugins}) = Plugin[]
println(io, spc, "→ Add packages to main environment: ", t.dev ? "Yes" : "No") defaultkw(::Val{:disabled_defaults}) = DataType[]
println(io, spc, "→ Commit Manifest.toml: ", t.manifest ? "Yes" : "No") function defaultkw(::Val{:authors})
name = LibGit2.getconfig("user.name", "")
print(io, spc, "→ Plugins:") email = LibGit2.getconfig("user.email", "")
if isempty(t.plugins) isempty(name) && return ""
print(io, " None") author = name * " "
else isempty(email) || (author *= "<$email>")
for plugin in sort(collect(values(t.plugins)); by=string) return strip(author)
println(io)
buf = IOBuffer()
show(buf, plugin)
print(io, spc^2, "")
print(io, join(split(String(take!(buf)), "\n"), "\n$(spc^2)"))
end
end
end 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.license == "MIT"
@test t.authors == "foo" @test t.authors == "foo"
@test t.dir == default_dir @test t.dir == default_dir
@test t.julia_version == PkgTemplates.default_version() @test t.julia_version == PkgTemplates.default_version
@test !t.ssh @test !t.ssh
@test !t.manifest @test !t.manifest
@test isempty(t.plugins) @test isempty(t.plugins)
@ -107,7 +107,7 @@ end
@testset "Show methods" begin @testset "Show methods" begin
pkg_dir = replace(default_dir, homedir() => "~") pkg_dir = replace(default_dir, homedir() => "~")
ver = PkgTemplates.version_floor(PkgTemplates.default_version()) ver = PkgTemplates.version_floor(PkgTemplates.default_version)
buf = IOBuffer() buf = IOBuffer()
t = Template(; user=me, authors="foo") t = Template(; user=me, authors="foo")
show(buf, t) show(buf, t)
@ -327,12 +327,6 @@ end
rm(temp_dir; recursive=true) rm(temp_dir; recursive=true)
end 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 @testset "Git-less package generation" begin
t = Template(; user=me) t = Template(; user=me)
generate(test_pkg, t; git=false) generate(test_pkg, t; git=false)
@ -340,84 +334,6 @@ end
@test !isfile(joinpath(t.dir, ".gitignore")) @test !isfile(joinpath(t.dir, ".gitignore"))
end 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 @testset "Plugins" begin
t = Template(; user=me) t = Template(; user=me)
pkg_dir = joinpath(t.dir, test_pkg) pkg_dir = joinpath(t.dir, test_pkg)
@ -474,7 +390,8 @@ end
warn_str = "Ignoring predefined Documenter kwargs \"format\" from additional kwargs" warn_str = "Ignoring predefined Documenter kwargs \"format\" from additional kwargs"
check_kwargs(kwargs, warn_str) check_kwargs(kwargs, warn_str)
kwargs = Dict(:checkdocs => :none, kwargs = Dict(
:checkdocs => :none,
:strict => true, :strict => true,
:format => :markdown, :format => :markdown,
:stringarg => "string", :stringarg => "string",
@ -488,5 +405,3 @@ end
end end
include(joinpath("interactive", "interactive.jl")) include(joinpath("interactive", "interactive.jl"))
rm(test_file)