From c0fe7bb2c2073fbdf745124213d9fca5a0ff6cf5 Mon Sep 17 00:00:00 2001 From: Chris de Graaf Date: Tue, 5 Feb 2019 11:31:51 -0600 Subject: [PATCH] The Big Squash: A bunch of irrelevant garbage --- .appveyor.yml | 27 --- .codecov.yml | 1 - .travis.yml | 20 +- Manifest.toml | 26 ++- Project.toml | 3 +- defaults/CITATION.bib | 8 + defaults/README.md | 13 ++ defaults/appveyor.yml | 16 +- defaults/cirrus.yml | 12 +- defaults/gitlab-ci.yml | 29 ++- defaults/index.md | 8 + defaults/make.jl | 31 +++ defaults/runtests.jl | 6 + defaults/travis.yml | 57 +++-- docs/Manifest.toml | 40 ++-- docs/src/index.md | 6 +- docs/src/pages/licenses.md | 12 +- docs/src/pages/package_generation.md | 16 -- docs/src/pages/plugin_development.md | 85 ++++--- docs/src/pages/plugins.md | 2 - src/PkgTemplates.jl | 54 ++--- src/generate.jl | 336 ++------------------------- src/interactive.jl | 152 ++++++++++++ src/licenses.jl | 45 ---- src/plugin.jl | 289 +++++++---------------- src/plugins/appveyor.jl | 42 ---- src/plugins/ci.jl | 140 +++++++++++ src/plugins/cirrusci.jl | 45 ---- src/plugins/citation.jl | 62 ++--- src/plugins/codecov.jl | 44 ---- src/plugins/coverage.jl | 31 +++ src/plugins/coveralls.jl | 43 ---- src/plugins/documenter.jl | 205 ++++++++-------- src/plugins/essentials.jl | 115 +++++++++ src/plugins/githubpages.jl | 69 ------ src/plugins/gitlabci.jl | 83 ------- src/plugins/gitlabpages.jl | 47 ---- src/plugins/travisci.jl | 44 ---- src/template.jl | 305 +++++++----------------- test/gitconfig | 3 - test/interactive/interactive.jl | 68 ------ test/interactive/plugins.jl | 90 ------- test/plugins/appveyor.jl | 57 ----- test/plugins/cirrusci.jl | 54 ----- test/plugins/citation.jl | 48 ---- test/plugins/codecov.jl | 41 ---- test/plugins/coveralls.jl | 40 ---- test/plugins/githubpages.jl | 68 ------ test/plugins/gitlabci.jl | 69 ------ test/plugins/gitlabpages.jl | 60 ----- test/plugins/travisci.jl | 82 ------- test/runtests.jl | 20 -- test/tests.jl | 93 +------- 53 files changed, 1009 insertions(+), 2353 deletions(-) delete mode 100644 .appveyor.yml delete mode 100644 .codecov.yml create mode 100644 defaults/CITATION.bib create mode 100644 defaults/README.md create mode 100644 defaults/index.md create mode 100644 defaults/make.jl create mode 100644 defaults/runtests.jl create mode 100644 src/interactive.jl delete mode 100644 src/plugins/appveyor.jl create mode 100644 src/plugins/ci.jl delete mode 100644 src/plugins/cirrusci.jl delete mode 100644 src/plugins/codecov.jl create mode 100644 src/plugins/coverage.jl delete mode 100644 src/plugins/coveralls.jl create mode 100644 src/plugins/essentials.jl delete mode 100644 src/plugins/githubpages.jl delete mode 100644 src/plugins/gitlabci.jl delete mode 100644 src/plugins/gitlabpages.jl delete mode 100644 src/plugins/travisci.jl delete mode 100644 test/gitconfig delete mode 100644 test/interactive/interactive.jl delete mode 100644 test/interactive/plugins.jl delete mode 100644 test/plugins/appveyor.jl delete mode 100644 test/plugins/cirrusci.jl delete mode 100644 test/plugins/citation.jl delete mode 100644 test/plugins/codecov.jl delete mode 100644 test/plugins/coveralls.jl delete mode 100644 test/plugins/githubpages.jl delete mode 100644 test/plugins/gitlabci.jl delete mode 100644 test/plugins/gitlabpages.jl delete mode 100644 test/plugins/travisci.jl diff --git a/.appveyor.yml b/.appveyor.yml deleted file mode 100644 index 11ec932..0000000 --- a/.appveyor.yml +++ /dev/null @@ -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%" diff --git a/.codecov.yml b/.codecov.yml deleted file mode 100644 index 69cb760..0000000 --- a/.codecov.yml +++ /dev/null @@ -1 +0,0 @@ -comment: false diff --git a/.travis.yml b/.travis.yml index 24b0574..b3fd906 100644 --- a/.travis.yml +++ b/.travis.yml @@ -2,9 +2,10 @@ language: julia os: - linux - osx + - windows julia: - 1.0 - - 1.1 + - 1.2 - nightly matrix: allow_failures: @@ -12,14 +13,19 @@ matrix: fast_finish: true notifications: email: false -after_success: julia -e 'using Pkg; Pkg.add("Coverage"); using Coverage; Codecov.submit(process_folder())' +after_success: + - julia -e ' + using Pkg + Pkg.add("Coverage") + using Coverage + Codecov.submit(process_folder())' jobs: include: - stage: Documentation - julia: 1.1 + julia: 1.2 script: julia --project=docs -e ' - using Pkg; - Pkg.develop(PackageSpec(path=pwd())); - Pkg.instantiate(); - include("docs/make.jl");' + using Pkg + Pkg.develop(PackageSpec(; path=pwd())) + Pkg.instantiate() + include("docs/make.jl")' after_success: skip diff --git a/Manifest.toml b/Manifest.toml index 95994a2..24d77f8 100644 --- a/Manifest.toml +++ b/Manifest.toml @@ -3,6 +3,16 @@ [[Base64]] uuid = "2a0f44e3-6c83-55bd-87e4-b1978d98bd5f" +[[DataAPI]] +git-tree-sha1 = "8903f0219d3472543fc4b2f5ebaf675a07f817c0" +uuid = "9a962f9c-6df0-11e9-0e5d-c546b8b5ee8a" +version = "1.0.1" + +[[DataValueInterfaces]] +git-tree-sha1 = "bfc1187b79289637fa0ef6d4436ebdfe6905cbd6" +uuid = "e2d170a0-9d28-54be-80f0-106bbe20a464" +version = "1.0.0" + [[Dates]] deps = ["Printf"] uuid = "ade2ca70-3891-5945-98fb-dc099432e06a" @@ -39,9 +49,9 @@ uuid = "d6f4376e-aef5-505a-96c1-9c027394607a" [[Mustache]] deps = ["Printf", "Tables"] -git-tree-sha1 = "d27b8b8b99c052ea1fdd40c678bfb2dfaec4e96e" +git-tree-sha1 = "f39de3a12232eb47bd0629b3a661054287780276" uuid = "ffc61752-8dc7-55ee-8c37-f3e9cdd09e70" -version = "0.5.12" +version = "0.5.13" [[Pkg]] deps = ["Dates", "LibGit2", "Markdown", "Printf", "REPL", "Random", "SHA", "UUIDs"] @@ -59,12 +69,6 @@ uuid = "3fa0cd96-eef1-5676-8a61-b3b8758bbffb" deps = ["Serialization"] uuid = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c" -[[Requires]] -deps = ["Test"] -git-tree-sha1 = "f6fbf4ba64d295e146e49e021207993b6b48c7d1" -uuid = "ae029012-a4dd-5104-9daa-d747884805df" -version = "0.5.2" - [[SHA]] uuid = "ea8e919c-243c-51af-8825-aaa63cd721ce" @@ -81,10 +85,10 @@ uuid = "3783bdb8-4a98-5b6b-af9a-565f29a5fe9c" version = "1.0.0" [[Tables]] -deps = ["IteratorInterfaceExtensions", "LinearAlgebra", "Requires", "TableTraits", "Test"] -git-tree-sha1 = "351a4b894122e1553c6ed05fda54086ab036adef" +deps = ["DataAPI", "DataValueInterfaces", "IteratorInterfaceExtensions", "LinearAlgebra", "TableTraits", "Test"] +git-tree-sha1 = "aaed7b3b00248ff6a794375ad6adf30f30ca5591" uuid = "bd369af6-aec1-5ad0-b16a-f7cc5008161c" -version = "0.2.5" +version = "0.2.11" [[Test]] deps = ["Distributed", "InteractiveUtils", "Logging", "Random"] diff --git a/Project.toml b/Project.toml index 4ddb988..5df5d89 100644 --- a/Project.toml +++ b/Project.toml @@ -16,7 +16,8 @@ URIParser = "30578b45-9adc-5946-b283-645ec420af67" julia = "1" [extras] +Suppressor = "fd094767-a336-5f1f-9728-57cf17d0bbfb" Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40" [targets] -test = ["Test"] +test = ["Suppressor", "Test"] diff --git a/defaults/CITATION.bib b/defaults/CITATION.bib new file mode 100644 index 0000000..b49e5ea --- /dev/null +++ b/defaults/CITATION.bib @@ -0,0 +1,8 @@ +@misc{<>.jl, + author = {<>}, + title = {<>.jl}, + url = {<>}, + version = {v0.1.0}, + year = {<>}, + month = {<>} +} diff --git a/defaults/README.md b/defaults/README.md new file mode 100644 index 0000000..230e5a5 --- /dev/null +++ b/defaults/README.md @@ -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}} diff --git a/defaults/appveyor.yml b/defaults/appveyor.yml index c9f4dec..afa1848 100644 --- a/defaults/appveyor.yml +++ b/defaults/appveyor.yml @@ -1,14 +1,18 @@ # Documentation: https://github.com/JuliaCI/Appveyor.jl environment: matrix: - - julia_version: {{VERSION}} - - julia_version: nightly +{{#VERSIONS}} + - julia_version: {{.}} +{{/VERSIONS}} platform: - - x86 - - x64 +{{#PLATFORMS}} + - {{.}} +{{/PLATFORMS}} +{{#HAS_NIGHTLY}} matrix: allow_failures: - julia_version: nightly +{{/HAS_NIGHTLY}} branches: only: - master @@ -26,8 +30,8 @@ build_script: test_script: - echo "%JL_TEST_SCRIPT%" - C:\julia\bin\julia -e "%JL_TEST_SCRIPT%" -{{#CODECOV}} +{{#HAS_CODECOV}} on_success: - echo "%JL_CODECOV_SCRIPT%" - C:\julia\bin\julia -e "%JL_CODECOV_SCRIPT%" -{{/CODECOV}} +{{/HAS_CODECOV}} diff --git a/defaults/cirrus.yml b/defaults/cirrus.yml index 2b1949e..1e182e2 100644 --- a/defaults/cirrus.yml +++ b/defaults/cirrus.yml @@ -1,16 +1,18 @@ freebsd_instance: - image: freebsd-12-0-release-amd64 + image: {{IMAGE}} task: name: FreeBSD env: - JULIA_VERSION: {{VERSION}} +{{#VERSIONS}} + JULIA_VERSION: {{.}} +{{/VERSIONS}} install_script: - sh -c "$(fetch https://raw.githubusercontent.com/ararslan/CirrusCI.jl/master/bin/install.sh -o -)" build_script: - cirrusjl build test_script: - cirrusjl test -{{#COVERAGE}} +{{#HAS_COVERAGE}} coverage_script: - - cirrusjl coverage{{#CODECOV}} codecov{{/CODECOV}}{{#COVERALLS}} coveralls{{/COVERALLS}} -{{/COVERAGE}} + - cirrusjl coverage{{#HAS_CODECOV}} codecov{{/HAS_CODECOV}}{{#HAS_COVERALLS}} coveralls{{/HAS_COVERALLS}} +{{/HAS_COVERAGE}} diff --git a/defaults/gitlab-ci.yml b/defaults/gitlab-ci.yml index ab0908b..d820997 100644 --- a/defaults/gitlab-ci.yml +++ b/defaults/gitlab-ci.yml @@ -1,19 +1,30 @@ -Julia {{VERSION}}: - image: julia:{{VERSION}} - script: julia --project='@.' -e 'using Pkg; Pkg.build(); Pkg.test({{#GITLABCOVERAGE}}; coverage=true{{/GITLABCOVERAGE}})' -{{#GITLABCOVERAGE}} +{{#VERSIONS}} +Julia {{.}}: + image: julia:{{.}} + script: julia --project=@. -e ' + using Pkg + Pkg.build() + Pkg.test({{#HAS_COVERAGE}}coverage=true{{/HAS_COVERAGE}})' +{{/VERSIONS}} +{{#HAS_COVERAGE}} coverage: /Test Coverage (\d+\.\d+%)/ after_script: - - julia -e 'using Printf; using Pkg; Pkg.add("Coverage"); using Coverage; c, t = get_summary(process_folder()); @printf "Test Coverage %.2f%%\n" 100c/t' -{{/GITLABCOVERAGE}} -{{#DOCUMENTER}} + - julia -e ' + using Pkg + Pkg.add("Coverage") + using Coverage + c, t = get_summary(process_folder()) + using Printf + @printf "Test Coverage %.2f%%\n" 100c/t' +{{/HAS_COVERAGE}} +{{#HAS_DOCUMENTER}} pages: image: julia:{{VERSION}} stage: deploy script: - julia --project=docs -e ' using Pkg; - Pkg.develop(PackageSpec(path=pwd())); + Pkg.develop(PackageSpec(; path=pwd())); Pkg.instantiate(); include("docs/make.jl");' - mkdir -p public @@ -23,4 +34,4 @@ pages: - public only: - master -{{/DOCUMENTER}} +{{/HAS_DOCUMENTER}} diff --git a/defaults/index.md b/defaults/index.md new file mode 100644 index 0000000..e5e882f --- /dev/null +++ b/defaults/index.md @@ -0,0 +1,8 @@ +# {{PKG}} + +```@index +``` + +```@autodocs +Modules = [{{PKG}}] +``` diff --git a/defaults/make.jl b/defaults/make.jl new file mode 100644 index 0000000..93adc9b --- /dev/null +++ b/defaults/make.jl @@ -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}} diff --git a/defaults/runtests.jl b/defaults/runtests.jl new file mode 100644 index 0000000..072ffb3 --- /dev/null +++ b/defaults/runtests.jl @@ -0,0 +1,6 @@ +using {{PKG}} +using Test + +@testset "{{PKG}}.jl" begin + # Write your tests here. +end diff --git a/defaults/travis.yml b/defaults/travis.yml index 91e1500..21bc8eb 100644 --- a/defaults/travis.yml +++ b/defaults/travis.yml @@ -1,35 +1,46 @@ -# Documentation: http://docs.travis-ci.com/user/languages/julia/ +# Documentation: http://docs.travis-ci.com/user/languages/julia language: julia -os: - - linux - - osx -julia: - - {{VERSION}} - - nightly -matrix: - allow_failures: - - julia: nightly - fast_finish: true notifications: email: false -{{#COVERAGE}} -after_success: -{{#CODECOV}} - - julia -e 'using Pkg; Pkg.add("Coverage"); using Coverage; Codecov.submit(process_folder())' -{{/CODECOV}} -{{#COVERALLS}} - - julia -e 'using Pkg; Pkg.add("Coverage"); using Coverage; Coveralls.submit(process_folder())' -{{/COVERALLS}} -{{/COVERAGE}} -{{#DOCUMENTER}} +{{#HAS_DOCUMENTER}} jobs: + fast_finish: true +{{#HAS_NIGHTLY}} + allow_failures: + - julia: nightly +{{/HAS_NIGHTLY}} include: +{{#JOBS}} + - julia: {{JULIA}} + os: {{OS}} +{{#ARCH}} + arch: {{ARCH}} +{{/ARCH}} +{{/JOBS}} +{{#HAS_DOCUMENTER}} - stage: Documentation julia: {{VERSION}} script: julia --project=docs -e ' using Pkg; - Pkg.develop(PackageSpec(path=pwd())); + Pkg.develop(PackageSpec(; path=pwd())); Pkg.instantiate(); include("docs/make.jl");' after_success: skip -{{/DOCUMENTER}} +{{/HAS_DOCUMENTER}} +{{#HAS_COVERAGE}} +after_success: +{{#HAS_CODECOV}} + - julia -e ' + using Pkg; + Pkg.add("Coverage"); + using Coverage; + Codecov.submit(process_folder());' +{{/HAS_CODECOV}} +{{#HAS_COVERALLS}} + - julia -e ' + using Pkg; + Pkg.add("Coverage"); + using Coverage; + Coveralls.submit(process_folder());' +{{/HAS_COVERALLS}} +{{/HAS_COVERAGE}} diff --git a/docs/Manifest.toml b/docs/Manifest.toml index 2af149e..bf9d220 100644 --- a/docs/Manifest.toml +++ b/docs/Manifest.toml @@ -1,3 +1,5 @@ +# This file is machine-generated - editing it directly is not advised + [[Base64]] uuid = "2a0f44e3-6c83-55bd-87e4-b1978d98bd5f" @@ -6,35 +8,34 @@ deps = ["Printf"] uuid = "ade2ca70-3891-5945-98fb-dc099432e06a" [[Distributed]] -deps = ["LinearAlgebra", "Random", "Serialization", "Sockets"] +deps = ["Random", "Serialization", "Sockets"] uuid = "8ba89e20-285c-5b6f-9357-94700520ee1b" [[DocStringExtensions]] deps = ["LibGit2", "Markdown", "Pkg", "Test"] -git-tree-sha1 = "1df01539a1c952cef21f2d2d1c092c2bcf0177d7" +git-tree-sha1 = "0513f1a8991e9d83255e0140aace0d0fc4486600" uuid = "ffbed154-4ef7-542d-bbb7-c09d3a79fcae" -version = "0.6.0" +version = "0.8.0" [[Documenter]] -deps = ["Base64", "DocStringExtensions", "InteractiveUtils", "LibGit2", "Logging", "Markdown", "Pkg", "REPL", "Random", "Test", "Unicode"] -git-tree-sha1 = "a6db1c69925cdc53aafb38caec4446be26e0c617" +deps = ["Base64", "DocStringExtensions", "InteractiveUtils", "JSON", "LibGit2", "Logging", "Markdown", "REPL", "Test", "Unicode"] +git-tree-sha1 = "c61d6eedbc3c4323c08b64af12d29c8ee0fcbb5f" uuid = "e30172f5-a6a5-5a46-863b-614d45cd2de4" -version = "0.21.0" +version = "0.23.2" [[InteractiveUtils]] -deps = ["LinearAlgebra", "Markdown"] +deps = ["Markdown"] uuid = "b77e0a4c-d291-57a0-90e8-8db25a27a240" +[[JSON]] +deps = ["Dates", "Mmap", "Parsers", "Unicode"] +git-tree-sha1 = "b34d7cef7b337321e97d22242c3c2b91f476748e" +uuid = "682c06a0-de6a-54ab-a142-c8b1cf79cde6" +version = "0.21.0" + [[LibGit2]] uuid = "76f85450-5226-5b5a-8eaa-529ad045b433" -[[Libdl]] -uuid = "8f399da3-3557-5675-b5ff-fb832c97cbdb" - -[[LinearAlgebra]] -deps = ["Libdl"] -uuid = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e" - [[Logging]] uuid = "56ddb016-857b-54e1-b83d-db4d58db5568" @@ -42,6 +43,15 @@ uuid = "56ddb016-857b-54e1-b83d-db4d58db5568" deps = ["Base64"] uuid = "d6f4376e-aef5-505a-96c1-9c027394607a" +[[Mmap]] +uuid = "a63ad114-7e13-5084-954f-fe012c677804" + +[[Parsers]] +deps = ["Dates", "Test"] +git-tree-sha1 = "db2b35dedab3c0e46dc15996d170af07a5ab91c9" +uuid = "69de0a69-1ddd-5017-9359-2bf0b02dc9f0" +version = "0.3.6" + [[Pkg]] deps = ["Dates", "LibGit2", "Markdown", "Printf", "REPL", "Random", "SHA", "UUIDs"] uuid = "44cfe95a-1eb2-52ea-b672-e2afdf69b78f" @@ -72,7 +82,7 @@ deps = ["Distributed", "InteractiveUtils", "Logging", "Random"] uuid = "8dfed614-e22c-5e08-85e1-65c5234f0b40" [[UUIDs]] -deps = ["Random"] +deps = ["Random", "SHA"] uuid = "cf7118a7-6976-5b1a-9a39-7adc72f591a4" [[Unicode]] diff --git a/docs/src/index.md b/docs/src/index.md index b41a3f5..f2bb79a 100644 --- a/docs/src/index.md +++ b/docs/src/index.md @@ -72,8 +72,7 @@ t = Template(; Codecov(), Coveralls(), AppVeyor(), - GitHubPages(), - CirrusCI(), + Documenter{TravisCI}(), ], ) generate("MyPkg2", t) @@ -103,5 +102,4 @@ include, you are encouraged to use [AttoBot](https://github.com/apps/attobot) in ## Contributing It's extremely easy to extend `PkgTemplates` with new plugins. To get started, -check out the -[plugin development guide](https://invenia.github.io/PkgTemplates.jl/stable/pages/plugin_development.html). +check out [Plugin Development](@ref). diff --git a/docs/src/pages/licenses.md b/docs/src/pages/licenses.md index bf6b886..f8adc5d 100644 --- a/docs/src/pages/licenses.md +++ b/docs/src/pages/licenses.md @@ -4,17 +4,11 @@ CurrentModule = PkgTemplates # Licenses -[Many open-source licenses](https://github.com/christopher-dG/PkgTemplates.jl/tree/master/licenses) -are available for use with `PkgTemplates`, but if you see that one is missing, -don't hesitate to open an issue or PR. +[Many open-source licenses](https://github.com/invenia/PkgTemplates.jl/tree/master/licenses) +are available for use with `PkgTemplates`, but if you see that one is missing, don't +hesitate to open an issue or PR. ```@docs available_licenses show_license ``` - -### Helper Functions - -```@docs -read_license -``` diff --git a/docs/src/pages/package_generation.md b/docs/src/pages/package_generation.md index 24a1995..e3bd567 100644 --- a/docs/src/pages/package_generation.md +++ b/docs/src/pages/package_generation.md @@ -7,25 +7,9 @@ CurrentModule = PkgTemplates Creating new packages with `PkgTemplates` revolves around creating a new [`Template`](@ref), then calling [`generate`](@ref) on it. -## `Template` - ```@docs Template interactive_template -``` - -## `generate` - -```@docs generate generate_interactive ``` - -### Helper Functions - -```@docs -gen_tests -gen_readme -gen_gitignore -gen_license -``` diff --git a/docs/src/pages/plugin_development.md b/docs/src/pages/plugin_development.md index 31aa992..e77b882 100644 --- a/docs/src/pages/plugin_development.md +++ b/docs/src/pages/plugin_development.md @@ -4,72 +4,69 @@ CurrentModule = PkgTemplates # Plugin Development -The best and easiest way to contribute to `PkgTemplates` is to write new -plugins. +The best and easiest way to contribute to `PkgTemplates` is to write new plugins. ```@docs Plugin ``` -## Generic Plugins +## "Generic" Plugins + +Many plugins fall into the category of managing some configuration file. +Think Travis CI's `.travis.yml`, and so on for every CI service ever. +For these one-file plugins, a shortcut macro is available to define a plugin in one line. ```@docs -GenericPlugin +GeneratedPlugin +@plugin ``` +### `GeneratedPlugin` Customization + +When you generate a plugin type with [`@plugin`](@ref), all required methods are +implemented for you. However, you're still allowed to override behaviour if you so desire. +These are the relevant methods: + +```@docs +source +destination +gitignore +badges +view +``` + +For some examples, see +[`generated.jl`](https://github.com/invenia/PkgTemplates.jl/tree/master/src/plugins/generated.jl). + ## Custom Plugins -```@docs -CustomPlugin -``` +When a plugin is too complicated to be expressed with [`GeneratedPlugin`](@ref), we only +need to implement a few methods to create something fully custom. -### `CustomPlugin` Required Methods - -#### `gen_plugin` +### Required Methods ```@docs gen_plugin +``` + +### Optional Methods + +```@docs interactive ``` -**Note**: [`interactive`](@ref) is not strictly required, however without it, -your custom plugin will not be available when creating templates with -[`interactive_template`](@ref). +Additionally, [`gitignore`](@ref), [`badges`](@ref), and [`view`](@ref) can also be +implemented in the same way as for [`GeneratedPlugin`](@ref)s (they have empty default +implementations). [`source`](@ref) and [`destination`](@ref) have no meaning for custom +plugins. -#### `badges` +### Helpers -```@docs -badges -``` - -## Helper Types/Functions - -#### `gen_file` - -```@docs -gen_file -``` - -#### `substitute` - -```@docs -substitute -``` - -#### `Badge` +These types and functions will make implementing the above methods much easier. ```@docs Badge -``` - -#### `format` - -```@docs -format -``` - -#### `version_floor` - -```@docs +gen_file +substitute version_floor ``` diff --git a/docs/src/pages/plugins.md b/docs/src/pages/plugins.md index dd4f07a..af81a08 100644 --- a/docs/src/pages/plugins.md +++ b/docs/src/pages/plugins.md @@ -28,6 +28,4 @@ Coveralls ```@docs Documenter -GitHubPages -GitLabPages ``` diff --git a/src/PkgTemplates.jl b/src/PkgTemplates.jl index 6f54e10..76c715a 100644 --- a/src/PkgTemplates.jl +++ b/src/PkgTemplates.jl @@ -1,36 +1,34 @@ module PkgTemplates -using Dates -using InteractiveUtils -using LibGit2 -using Mustache -using Pkg -using REPL.TerminalMenus -using URIParser +using Base: @kwdef, current_project +using Base.Filesystem: contractuser +using Dates: month, today, year +using InteractiveUtils: subtypes +using LibGit2: LibGit2 +using Mustache: render +using Pkg: PackageSpec, Pkg +using REPL.TerminalMenus: MultiSelectMenu, RadioMenu, request +using URIParser: URI export - # Template/package generation. Template, - generate, - interactive_template, - generate_interactive, - # Licenses. - show_license, - available_licenses, - # Plugins. - GitHubPages, - GitLabPages, AppVeyor, - TravisCI, - GitLabCI, CirrusCI, + Citation, Codecov, Coveralls, - Citation + Documenter, + Gitignore, + GitLabCI, + License, + Readme, + Tests, + TravisCI + +const DEFAULT_VERSION = VersionNumber(VERSION.major) """ A plugin to be added to a [`Template`](@ref), which adds some functionality or integration. -New plugins should almost always extend [`GenericPlugin`](@ref) or [`CustomPlugin`](@ref). """ abstract type Plugin end @@ -38,18 +36,6 @@ include("licenses.jl") include("template.jl") include("generate.jl") include("plugin.jl") -include(joinpath("plugins", "documenter.jl")) -include(joinpath("plugins", "coveralls.jl")) -include(joinpath("plugins", "appveyor.jl")) -include(joinpath("plugins", "codecov.jl")) -include(joinpath("plugins", "travisci.jl")) -include(joinpath("plugins", "gitlabci.jl")) -include(joinpath("plugins", "cirrusci.jl")) -include(joinpath("plugins", "githubpages.jl")) -include(joinpath("plugins", "gitlabpages.jl")) -include(joinpath("plugins", "citation.jl")) - -const DEFAULTS_DIR = normpath(joinpath(@__DIR__, "..", "defaults")) -const BADGE_ORDER = [GitHubPages, GitLabPages, TravisCI, AppVeyor, GitLabCI, Codecov, Coveralls] +include("interactive.jl") end diff --git a/src/generate.jl b/src/generate.jl index 3ab577d..c0e7b02 100644 --- a/src/generate.jl +++ b/src/generate.jl @@ -1,16 +1,12 @@ -""" - generate(pkg::AbstractString, t::Template) -> Nothing - generate(t::Template, pkg::AbstractString) -> Nothing +const TEST_UUID = "8dfed614-e22c-5e08-85e1-65c5234f0b40" -Generate a package named `pkg` from `t`. If `git` is `false`, no Git repository is created. """ -function generate( - pkg::AbstractString, - t::Template; - git::Bool=true, - gitconfig::Union{GitConfig, Nothing}=nothing, -) - pkg = splitjl(pkg) + (::Template)(pkg::AbstractString) + +Generate a package named `pkg` from a [`Template`](@ref). +""" +function (t::Template)(pkg::AbstractString) + endswith(pkg, ".jl") && (pkg = pkg[1:end-3]) pkg_dir = joinpath(t.dir, pkg) ispath(pkg_dir) && throw(ArgumentError("$pkg_dir already exists")) @@ -18,9 +14,9 @@ function generate( # Create the directory with some boilerplate inside. Pkg.generate(pkg_dir) - # Add a [compat] section for Julia. + # Add a [compat] section for Julia. By default, Julia 1.x is supported. open(joinpath(pkg_dir, "Project.toml"), "a") do io - println(io, "\n[compat]\njulia = $(repr_version(t.julia_version))") + println(io, "\n[compat]\njulia = \"1\"") end # Replace the authors field with the template's authors. @@ -31,329 +27,35 @@ function generate( write(path, replace(project, r"authors = .*" => "authors = $authors")) end - if git - # Initialize the repo. + if t.git + # Initialize the repo, make a commit, and set the remote. repo = LibGit2.init(pkg_dir) - @info "Initialized Git repo at $pkg_dir" - - if gitconfig !== nothing - # Configure the repo. - repoconfig = GitConfig(repo) - for c in LibGit2.GitConfigIter(gitconfig) - LibGit2.set!(repoconfig, unsafe_string(c.name), unsafe_string(c.value)) - end - end - - # Commit and set the remote. LibGit2.commit(repo, "Initial commit") rmt = if t.ssh "git@$(t.host):$(t.user)/$pkg.jl.git" else "https://$(t.host)/$(t.user)/$pkg.jl" end - # We need to set the remote in a strange way, see #8. close(LibGit2.GitRemote(repo, "origin", rmt)) - @info "Set remote origin to $rmt" - - # Create the gh-pages branch if necessary. - if haskey(t.plugins, GitHubPages) - LibGit2.branch!(repo, "gh-pages") - LibGit2.commit(repo, "Initial commit") - @info "Created empty gh-pages branch" - LibGit2.branch!(repo, "master") - end end # Generate the files. - files = vcat( - "src/", "Project.toml", # Created by Pkg.generate. - gen_tests(pkg_dir, t), - gen_readme(pkg_dir, t), - gen_license(pkg_dir, t), - vcat(map(p -> gen_plugin(p, t, pkg), values(t.plugins))...), - ) + foreach(p -> gen_plugin(p, t, pkg_dir), values(t.plugins)) - if git - append!(files, gen_gitignore(pkg_dir, t)) - LibGit2.add!(repo, files...) + if t.git + # Commit the files. + LibGit2.add!(repo, ".") LibGit2.commit(repo, "Files generated by PkgTemplates") - @info "Committed $(length(files)) files/directories: $(join(files, ", "))" - - - if length(collect(LibGit2.GitBranchIter(repo))) > 1 - @info "Remember to push all created branches to your remote: git push --all" - end end if t.dev # Add the new package to the current environment. - Pkg.develop(PackageSpec(path=pkg_dir)) + Pkg.develop(PackageSpec(; path=pkg_dir)) end @info "New package is at $pkg_dir" - catch e - rm(pkg_dir; recursive=true) - rethrow(e) + catch + rm(pkg_dir; recursive=true, force=true) + rethrow() end end - -function generate( - t::Template, - pkg::AbstractString; - git::Bool=true, - gitconfig::Union{GitConfig, Nothing}=nothing, -) - generate(pkg, t; git=git, gitconfig=gitconfig) -end - -""" - generate_interactive(pkg::AbstractString; fast::Bool=false, git::Bool=true) -> Template - -Interactively create a template, and then generate a package with it. Arguments and -keywords are used in the same way as in [`generate`](@ref) and -[`interactive_template`](@ref). -""" -function generate_interactive( - pkg::AbstractString; - fast::Bool=false, - git::Bool=true, - gitconfig::Union{GitConfig, Nothing}=nothing, -) - t = interactive_template(; git=git, fast=fast) - generate(pkg, t; git=git, gitconfig=gitconfig) - return t -end - -""" - gen_tests(pkg_dir::AbstractString, t::Template) -> Vector{String} - -Create the test entrypoint in `pkg_dir`. - -# Arguments -* `pkg_dir::AbstractString`: The package directory in which the files will be generated -* `t::Template`: The template whose tests we are generating. - -Returns an array of generated file/directory names. -""" -function gen_tests(pkg_dir::AbstractString, t::Template) - # TODO: Silence Pkg for this section? Adding and removing Test creates a lot of noise. - proj = Base.current_project() - try - Pkg.activate(pkg_dir) - Pkg.add("Test") - - # Move the Test dependency into the [extras] section. - toml = read(joinpath(pkg_dir, "Project.toml"), String) - lines = split(toml, "\n") - idx = findfirst(l -> startswith(l, "Test = "), lines) - testdep = lines[idx] - deleteat!(lines, idx) - toml = join(lines, "\n") * """ - [extras] - $testdep - - [targets] - test = ["Test"] - """ - gen_file(joinpath(pkg_dir, "Project.toml"), toml) - Pkg.update() # Regenerate Manifest.toml (this cleans up Project.toml too). - finally - proj === nothing ? Pkg.activate() : Pkg.activate(proj) - end - - pkg = basename(pkg_dir) - text = """ - using $pkg - using Test - - @testset "$pkg.jl" begin - # Write your own tests here. - end - """ - - gen_file(joinpath(pkg_dir, "test", "runtests.jl"), text) - return ["test/"] -end - -""" - gen_readme(pkg_dir::AbstractString, t::Template) -> Vector{String} - -Create a README in `pkg_dir` with badges for each enabled plugin. - -# Arguments -* `pkg_dir::AbstractString`: The directory in which the files will be generated. -* `t::Template`: The template whose README we are generating. - -Returns an array of generated file/directory names. -""" -function gen_readme(pkg_dir::AbstractString, t::Template) - pkg = basename(pkg_dir) - text = "# $pkg\n" - done = [] - # Generate the ordered badges first, then add any remaining ones to the right. - for plugin_type in BADGE_ORDER - if haskey(t.plugins, plugin_type) - text *= "\n" - text *= join( - badges(t.plugins[plugin_type], t.user, pkg), - "\n", - ) - push!(done, plugin_type) - end - end - for plugin_type in setdiff(keys(t.plugins), done) - text *= "\n" - text *= join( - badges(t.plugins[plugin_type], t.user, pkg), - "\n", - ) - end - if haskey(t.plugins, Citation) && t.plugins[Citation].readme_section - text *= "\n## Citing\n\nSee `CITATION.bib` for the relevant reference(s).\n" - end - - - gen_file(joinpath(pkg_dir, "README.md"), text) - return ["README.md"] -end - -""" - gen_gitignore(pkg_dir::AbstractString, t::Template) -> Vector{String} - -Create a `.gitignore` in `pkg_dir`. - -# Arguments -* `pkg_dir::AbstractString`: The directory in which the files will be generated. -* `t::Template`: The template whose .gitignore we are generating. - -Returns an array of generated file/directory names. -""" -function gen_gitignore(pkg_dir::AbstractString, t::Template) - pkg = basename(pkg_dir) - init = [".DS_Store", "/dev/"] - entries = mapfoldl(p -> p.gitignore, append!, values(t.plugins); init=init) - if !t.manifest && !in("Manifest.toml", entries) - push!(entries, "/Manifest.toml") # Only ignore manifests at the repo root. - end - unique!(sort!(entries)) - text = join(entries, "\n") - - gen_file(joinpath(pkg_dir, ".gitignore"), text) - files = [".gitignore"] - t.manifest && push!(files, "Manifest.toml") - return files -end - -""" - gen_license(pkg_dir::AbstractString, t::Template) -> Vector{String} - -Create a license in `pkg_dir`. - -# Arguments -* `pkg_dir::AbstractString`: The directory in which the files will be generated. -* `t::Template`: The template whose LICENSE we are generating. - -Returns an array of generated file/directory names. -""" -function gen_license(pkg_dir::AbstractString, t::Template) - if isempty(t.license) - return String[] - end - - text = "Copyright (c) $(year(today())) $(t.authors)\n" - text *= read_license(t.license) - - gen_file(joinpath(pkg_dir, "LICENSE"), text) - return ["LICENSE"] -end - -""" - gen_file(file::AbstractString, text::AbstractString) -> Int - -Create a new file containing some given text. Always ends the file with a newline. - -# Arguments -* `file::AbstractString`: Path to the file to be created. -* `text::AbstractString`: Text to write to the file. - -Returns the number of bytes written to the file. -""" -function gen_file(file::AbstractString, text::AbstractString) - mkpath(dirname(file)) - if !endswith(text , "\n") - text *= "\n" - end - return write(file, text) -end - -""" - version_floor(v::VersionNumber=VERSION) -> String - -Format the given Julia version. - -# Keyword arguments -* `v::VersionNumber=VERSION`: Version to floor. - -Returns "major.minor" for the most recent release version relative to v. For prereleases -with v.minor == v.patch == 0, returns "major.minor-". -""" -function version_floor(v::VersionNumber=VERSION) - return if isempty(v.prerelease) || v.patch > 0 - "$(v.major).$(v.minor)" - else - "$(v.major).$(v.minor)-" - end -end - -""" - substitute(template::AbstractString, view::Dict{String, Any}) -> String - substitute( - template::AbstractString, - pkg_template::Template; - view::Dict{String, Any}=Dict{String, Any}(), - ) -> String - -Replace placeholders in `template` with values in `view` via -[`Mustache`](https://github.com/jverzani/Mustache.jl). `template` is not modified. -If `pkg_template` is supplied, some default replacements are also performed. - -For information on how to structure `template`, see "Defining Template Files" section in -[Custom Plugins](@ref). - -**Note**: Conditionals in `template` without a corresponding key in `view` won't error, -but will simply be evaluated as false. -""" -substitute(template::AbstractString, view::Dict{String, Any}) = render(template, view) - -function substitute( - template::AbstractString, - pkg_template::Template; - view::Dict{String, Any}=Dict{String, Any}(), -) - # Don't use version_floor here because we don't want the trailing '-' on prereleases. - v = pkg_template.julia_version - d = Dict{String, Any}( - "USER" => pkg_template.user, - "VERSION" => "$(v.major).$(v.minor)", - "DOCUMENTER" => any(map(p -> isa(p, Documenter), values(pkg_template.plugins))), - "CODECOV" => haskey(pkg_template.plugins, Codecov), - "COVERALLS" => haskey(pkg_template.plugins, Coveralls), - ) - # d["AFTER"] is true whenever something needs to occur in a CI "after_script". - d["AFTER"] = d["DOCUMENTER"] || d["CODECOV"] || d["COVERALLS"] - # d["COVERAGE"] is true whenever a coverage plugin is enabled. - # TODO: This doesn't handle user-defined coverage plugins. - # Maybe we need an abstract CoveragePlugin <: GenericPlugin? - d["COVERAGE"] = d["CODECOV"] || d["COVERALLS"] - return substitute(template, merge(d, view)) -end - -splitjl(pkg::AbstractString) = endswith(pkg, ".jl") ? pkg[1:end-3] : pkg - -# Format a version in a way suitable for a Project.toml file. -function repr_version(v::VersionNumber) - s = string(v.major) - v.minor == 0 || (s *= ".$(v.minor)") - v.patch == 0 || (s *= ".$(v.patch)") - return repr(s) -end diff --git a/src/interactive.jl b/src/interactive.jl new file mode 100644 index 0000000..230393c --- /dev/null +++ b/src/interactive.jl @@ -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 diff --git a/src/licenses.jl b/src/licenses.jl index 0ef1bd8..e69de29 100644 --- a/src/licenses.jl +++ b/src/licenses.jl @@ -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 diff --git a/src/plugin.jl b/src/plugin.jl index 0fc082d..9abb6b6 100644 --- a/src/plugin.jl +++ b/src/plugin.jl @@ -1,153 +1,51 @@ -""" -Generic plugins are plugins that add any number of patterns to the generated package's -`.gitignore`, and have at most one associated file to generate. +const DEFAULTS_DIR = normpath(joinpath(@__DIR__, "..", "defaults")) -# Attributes -* `gitignore::Vector{AbstractString}`: Array of patterns to be added to the `.gitignore` of - generated packages that use this plugin. -* `src::Union{AbstractString, Nothing}`: Path to the file that will be copied into the generated - package repository. If set to `nothing`, no file will be generated. When this defaults - to an empty string, there should be a default file in `defaults` that will be copied. - That file's name is usually the same as the plugin's name, except in all lowercase and - with the `.yml` extension. If this is not the case, an `interactive` method needs to be - implemented to call `interactive(; file="file.ext")`. -* `dest::AbstractString`: Path to the generated file, relative to the root of the generated - package repository. -* `badges::Vector{Badge}`: Array of [`Badge`](@ref)s containing information used to - create Markdown-formatted badges from the plugin. Entries will be run through - [`substitute`](@ref), so they may contain placeholder values. -* `view::Dict{String, Any}`: Additional substitutions to make in both the plugin's badges - and its associated file. See [`substitute`](@ref) for details. +abstract type BasicPlugin <: Plugin end -# Example -```julia -struct MyPlugin <: GenericPlugin - gitignore::Vector{AbstractString} - src::Union{AbstractString, Nothing} - dest::AbstractString - badges::Vector{Badge} - view::Dict{String, Any} - - function MyPlugin(; config_file::Union{AbstractString, Nothing}="") - if config_file != nothing - config_file = if isempty(config_file) - joinpath(DEFAULTS_DIR, "my-plugin.toml") - elseif isfile(config_file) - abspath(config_file) - else - throw(ArgumentError( - "File \$(abspath(config_file)) does not exist" - )) - end - end - new( - ["*.mgp"], - config_file, - ".my-plugin.toml", - [ - Badge( - "My Plugin", - "https://myplugin.com/badge-{{YEAR}}.png", - "https://myplugin.com/{{USER}}/{{PKGNAME}}.jl", - ), - ], - Dict{String, Any}("YEAR" => year(today())), - ) - end -end - -interactive(::Type{MyPlugin}) = interactive(MyPlugin; file="my-plugin.toml") -``` - -The above plugin ignores files ending with `.mgp`, copies `defaults/my-plugin.toml` by -default, and creates a badge that links to the project on its own site, using the default -substitutions with one addition: `{{YEAR}} => year(today())`. Since the default config -template file doesn't follow the generic naming convention, we added another `interactive` -method to correct the assumed filename. -""" -abstract type GenericPlugin <: Plugin end - -function Base.show(io::IO, p::GenericPlugin) - spc = " " - println(io, nameof(typeof(p)), ":") - - cfg = if p.src === nothing - "None" - else - dirname(p.src) == DEFAULTS_DIR ? "Default" : p.src - end - println(io, spc, "→ Config file: ", cfg) - - n = length(p.gitignore) - s = n == 1 ? "" : "s" - print(io, spc, "→ $n gitignore entrie$s") - n > 0 && print(io, ": ", join(map(repr, p.gitignore), ", ")) -end +default_file(paths::AbstractString...) = joinpath(DEFAULTS_DIR, paths...) """ -Custom plugins are plugins whose behaviour does not follow the [`GenericPlugin`](@ref) -pattern. They can implement [`gen_plugin`](@ref), [`badges`](@ref), and -[`interactive`](@ref) in any way they choose, as long as they conform to the usual type -signature. + view(::Plugin, ::Template, pkg::AbstractString) -> Dict{String} -# Attributes -* `gitignore::Vector{AbstractString}`: Array of patterns to be added to the `.gitignore` of - generated packages that use this plugin. - -# Example -```julia -struct MyPlugin <: CustomPlugin - gitignore::Vector{AbstractString} - lucky::Bool - - MyPlugin() = new([], rand() > 0.8) - - function gen_plugin(p::MyPlugin, t::Template, pkg_name::AbstractString) - return if p.lucky - text = substitute("You got lucky with {{PKGNAME}}, {{USER}}!", t) - gen_file(joinpath(t.dir, pkg_name, ".myplugin.yml"), text) - [".myplugin.yml"] - else - println("Maybe next time.") - String[] - end - end - - function badges(p::MyPlugin, user::AbstractString, pkg_name::AbstractString) - return if p.lucky - [ - format(Badge( - "You got lucky!", - "https://myplugin.com/badge.png", - "https://myplugin.com/\$user/\$pkg_name.jl", - )), - ] - else - String[] - end - end -end - -interactive(:Type{MyPlugin}) = MyPlugin() -``` - -This plugin doesn't do much, but it demonstrates how [`gen_plugin`](@ref), [`badges`](@ref) -and [`interactive`](@ref) can be implemented using [`substitute`](@ref), -[`gen_file`](@ref), [`Badge`](@ref), and [`format`](@ref). - -# Defining Template Files -Often, the contents of the config file that your plugin generates depends on variables like -the package name, the user's username, etc. Template files (which are stored in `defaults`) -can use [here](https://github.com/jverzani/Mustache.jl)'s syntax to define replacements. +Return extra string substitutions to be made for this plugin. """ -abstract type CustomPlugin <: Plugin end +view(::Plugin, ::Template, ::AbstractString) = Dict{String, Any}() + +""" + gitignore(::Plugin) -> Vector{String} + +Return patterns that should be added to `.gitignore`. +""" +gitignore(::Plugin) = String[] + +""" + badges(::Plugin) -> Union{Badge, Vector{Badge}} + +Return a list of [`Badge`](@ref)s, or just one, to be added to `README.md`. +""" +badges(::Plugin) = Badge[] + +""" + source(::BasicPlugin) -> Union{String, Nothing} + +Return the path to a plugin's configuration file template, or `nothing` to indicate no file. +""" +source(::BasicPlugin) = nothing + +""" + destination(::BasicPlugin) -> String + +Return the destination, relative to the package root, of a plugin's configuration file. +""" +function destination end """ Badge(hover::AbstractString, image::AbstractString, link::AbstractString) -> Badge -A `Badge` contains the data necessary to generate a Markdown badge. +Container for Markdown badge data. +Each argument can contain placeholders. -# Arguments +## Arguments * `hover::AbstractString`: Text to appear when the mouse is hovered over the badge. * `image::AbstractString`: URL to the image to display. * `link::AbstractString`: URL to go to upon clicking the badge. @@ -158,89 +56,70 @@ struct Badge link::String end -""" - format(b::Badge) -> String +Base.string(b::Badge) = "[![$(b.hover)]($(b.image))]($(b.link))" -Return `badge`'s data formatted as a Markdown string. -""" -format(b::Badge) = "[![$(b.hover)]($(b.image))]($(b.link))" +# Format a plugin's badges as a list of strings, with all substitutions applied. +function badges(p::Plugin, t::Template, pkg_name::AbstractString) + bs = badges(p) + bs isa Vector || (bs = [bs]) + bs = map(string, bs) + # TODO render +end """ - gen_plugin(p::Plugin, t::Template, pkg_name::AbstractString) -> Vector{String} + gen_plugin(p::Plugin, t::Template, pkg::AbstractString) -> Nothing Generate any files associated with a plugin. -# Arguments +## Arguments * `p::Plugin`: Plugin whose files are being generated. * `t::Template`: Template configuration. -* `pkg_name::AbstractString`: Name of the package. - -Returns an array of generated file/directory names. +* `pkg::AbstractString`: Name of the package. """ -gen_plugin(::Plugin, ::Template, ::AbstractString) = String[] +gen_plugin(::Plugin, ::Template, ::AbstractString) = nothing -function gen_plugin(p::GenericPlugin, t::Template, pkg_name::AbstractString) - if p.src === nothing - return String[] - end - text = substitute( - read(p.src, String), - t; - view=merge(Dict("PKGNAME" => pkg_name), p.view), - ) - gen_file(joinpath(t.dir, pkg_name, p.dest), text) - return [p.dest] +function gen_plugin(p::BasicPlugin, t::Template, pkg::AbstractString) + source(p) === nothing && return + text = render(source(p), view(p, t, pkg); tags=tags(p)) + gen_file(joinpath(t.dir, pkg_name, destination(p)), text) end """ - badges(p::Plugin, user::AbstractString, pkg_name::AbstractString) -> Vector{String} + gen_file(file::AbstractString, text::AbstractString) -> Int -Generate Markdown badges for the plugin. - -# Arguments -* `p::Plugin`: Plugin whose badges we are generating. -* `user::AbstractString`: Username of the package creator. -* `pkg_name::AbstractString`: Name of the package. - -Returns an array of Markdown badges. +Create a new file containing some given text. +Trailing whitespace is removed, and the file will end with a newline. """ -badges(::Plugin, ::AbstractString, ::AbstractString) = String[] - -function badges(p::GenericPlugin, user::AbstractString, pkg_name::AbstractString) - # Give higher priority to replacements defined in the plugin's view. - view = merge(Dict("USER" => user, "PKGNAME" => pkg_name), p.view) - return map(b -> substitute(format(b), view), p.badges) +function gen_file(file::AbstractString, text::AbstractString) + mkpath(dirname(file)) + text = join(map(rstrip, split(text, "\n")), "\n") + endswith(text , "\n") || (text *= "\n") + write(file, text) end -""" - interactive(T::Type{<:Plugin}; file::Union{AbstractString, Nothing}="") -> Plugin +render_file(file::AbstractString, view, tags) = render_text(read(file, String), view, tags) -Interactively create a plugin of type `T`, where `file` is the plugin type's default -config template with a non-standard name (for `MyPlugin`, this is anything but -"myplugin.yml"). -""" -function interactive(T::Type{<:GenericPlugin}; file::Union{AbstractString, Nothing}="") - name = string(nameof(T)) - # By default, we expect the default plugin file template for a plugin called - # "MyPlugin" to be called "myplugin.yml". - fn = file != nothing && isempty(file) ? "$(lowercase(name)).yml" : file - default_config_file = fn == nothing ? fn : joinpath(DEFAULTS_DIR, fn) +render_text(text::AbstractString, view, tags) = render(text, view; tags=tags) - print("$name: Enter the config template filename (\"None\" for no file) ") - if default_config_file == nothing - print("[None]: ") - else - print("[", replace(default_config_file, homedir() => "~"), "]: ") - end - - config_file = readline() - config_file = if uppercase(config_file) == "NONE" - nothing - elseif isempty(config_file) - default_config_file - else - config_file - end - - return T(; config_file=config_file) +function render_badges(p::BasicPlugin, t::Template, pkg::AbstractString) end + +function render_plugin(p::BasicPlugin, t::Template, pkg::AbstractString) + render_file(source(p), view(p, t, pkg), tags(p)) +end + +include(joinpath("plugins", "essentials.jl")) +include(joinpath("plugins", "coverage.jl")) +include(joinpath("plugins", "ci.jl")) +include(joinpath("plugins", "citation.jl")) +include(joinpath("plugins", "documenter.jl")) + +const BADGE_ORDER = [ + Documenter{GitLabCI}, + Documenter{TravisCI}, + TravisCI, + AppVeyor, + GitLabCI, + Codecov, + Coveralls, +] diff --git a/src/plugins/appveyor.jl b/src/plugins/appveyor.jl deleted file mode 100644 index 7b42767..0000000 --- a/src/plugins/appveyor.jl +++ /dev/null @@ -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 diff --git a/src/plugins/ci.jl b/src/plugins/ci.jl new file mode 100644 index 0000000..0fde8a2 --- /dev/null +++ b/src/plugins/ci.jl @@ -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 diff --git a/src/plugins/cirrusci.jl b/src/plugins/cirrusci.jl deleted file mode 100644 index 7daca52..0000000 --- a/src/plugins/cirrusci.jl +++ /dev/null @@ -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") diff --git a/src/plugins/citation.jl b/src/plugins/citation.jl index 761a493..34365d5 100644 --- a/src/plugins/citation.jl +++ b/src/plugins/citation.jl @@ -1,50 +1,28 @@ """ - Citation(; readme_section::Bool=false) + Citation(; readme_section::Bool=false) -> Citation -Add `Citation` to a template's plugins to add a `CITATION.bib` file to -generated repositories, and an appropriate section in the README. - -# Keyword Arguments: -* `readme_section::Bool=false`: whether to add a section in the readme pointing to `CITATION.bib`. +Add `Citation` to a [`Template`](@ref)'s plugin list to generate a `CITATION.bib` file. +If `readme` is set, then `README.md` will contain a section about citing. """ -struct Citation <: GenericPlugin - gitignore::Vector{AbstractString} - src::Union{String, Nothing} - dest::AbstractString - badges::Vector{Badge} - view::Dict{String, Any} - readme_section::Bool - function Citation(; readme_section::Bool=false) - new( - [], - nothing, - "CITATION.bib", - [], - Dict{String, Any}(), - readme_section, - ) - end +@kwdef struct Citation <: BasicPlugin + file::String = default_file("CITATION.bib") + readme::Bool = false end +tags(::Citation) = "<<", ">>" + +source(p::Citation) = p.file +destination(::Citation) = "CITATION.bib" + +view(::Citation, t::Template, pkg::AbstractString) = Dict( + "AUTHORS" => t.authors, + "MONTH" => month(today()), + "PKG" => pkg, + "URL" => "https://$(t.host)/$(t.user)/$pkg.jl", + "YEAR" => year(today()), +) + function interactive(::Type{Citation}) - print("Citation: Add a section to README.md mentioning CITATION.bib? [no]: ") - readme = uppercase(readline()) in ["Y", "YES", "TRUE"] + readme = prompt_bool("Citation: Add a section to the README", false) return Citation(; readme_section=readme) end - - -function gen_plugin(p::Citation, t::Template, pkg_name::AbstractString) - pkg_dir = joinpath(t.dir, pkg_name) - text = """ - @misc{$pkg_name.jl, - \tauthor = {$(t.authors)}, - \ttitle = {{$(pkg_name).jl}}, - \turl = {https://$(t.host)/$(t.user)/$(pkg_name).jl}, - \tversion = {v0.1.0}, - \tyear = {$(year(today()))}, - \tmonth = {$(month(today()))} - } - """ - gen_file(joinpath(pkg_dir, "CITATION.bib"), text) - return ["CITATION.bib"] -end diff --git a/src/plugins/codecov.jl b/src/plugins/codecov.jl deleted file mode 100644 index 7865c3d..0000000 --- a/src/plugins/codecov.jl +++ /dev/null @@ -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) diff --git a/src/plugins/coverage.jl b/src/plugins/coverage.jl new file mode 100644 index 0000000..25cf03f --- /dev/null +++ b/src/plugins/coverage.jl @@ -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", +) diff --git a/src/plugins/coveralls.jl b/src/plugins/coveralls.jl deleted file mode 100644 index 8be6392..0000000 --- a/src/plugins/coveralls.jl +++ /dev/null @@ -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) diff --git a/src/plugins/documenter.jl b/src/plugins/documenter.jl index c8b3195..5b6c019 100644 --- a/src/plugins/documenter.jl +++ b/src/plugins/documenter.jl @@ -1,31 +1,85 @@ const DOCUMENTER_UUID = "e30172f5-a6a5-5a46-863b-614d45cd2de4" -const STANDARD_KWS = [:modules, :format, :pages, :repo, :sitename, :authors, :assets] """ -Add a `Documenter` subtype to a template's plugins to add support for documentation -generation via [Documenter.jl](https://github.com/JuliaDocs/Documenter.jl). + Documenter{T<:Union{TravisCI, GitLabCI, Nothing}}(; + assets::Vector{<:AbstractString}=String[], + makedocs_kwargs::Dict{Symbol}=Dict(), + canonical_url::Union{Function, Nothing}=nothing, + ) -> Documenter{T} -By default, the plugin generates a minimal index.md and a make.jl file. The make.jl -file contains the Documenter.makedocs command with predefined values for `modules`, -`format`, `pages`, `repo`, `sitename`, and `authors`. +The `Documenter` plugin adds support for documentation generation via [Documenter.jl](https://github.com/JuliaDocs/Documenter.jl). +Documentation deployment depends on `T`, where `T` is some supported CI plugin, or `Nothing` to only support local documentation builds. -The subtype is expected to include the following fields: -* `assets::Vector{AbstractString}`, a list of filenames to be included as the `assets` -kwarg to `makedocs` -* `gitignore::Vector{AbstractString}`, a list of files to be added to the `.gitignore` +## Keyword Arguments +todo +- `assets::Vector{<:AbstractString}=String[]`: +- `makedocs_kwargs::Dict{Symbol}=Dict{Symbol, Any}()`: +- `canonical_url::Union{Function, Nothing}=nothing`:` -It may optionally include the field `additional_kwargs::Union{AbstractDict, NamedTuple}` -to allow additional kwargs to be added to `makedocs`. +!!! note + If deploying documentation with Travis CI, don't forget to complete the required configuration. + See [here](https://juliadocs.github.io/Documenter.jl/stable/man/hosting/#SSH-Deploy-Keys-1). """ -abstract type Documenter <: CustomPlugin end +struct Documenter{T<:Union{TravisCI, GitLabCI, Nothing}} <: Plugin + assets::Vector{String} + makedocs_kwargs::Dict{Symbol} + canonical_url::Union{Function, Nothing} -function gen_plugin(p::Documenter, t::Template, pkg_name::AbstractString) - path = joinpath(t.dir, pkg_name) - docs_dir = joinpath(path, "docs") - mkpath(docs_dir) + # Can't use @kwdef due to some weird precompilation issues. + function Documenter{T}( + assets::Vector{<:AbstractString}=String[], + makedocs_kwargs::Dict{Symbol}=Dict{Symbol, Any}(), + canonical_url::Union{Function, Nothing}=T === TravisCI ? github_pages_url : nothing, + ) where T <: Union{TravisCI, GitLabCI, Nothing} + return new(assets, makedocs_kwargs, canonical_url) + end +end + +Documenter(; kwargs...) = Documenter{Nothing}(; kwargs...) + +gitignore(::Documenter) = ["/docs/build/", "/docs/site/"] + +badges(::Documenter) = Badge[] +badges(::Documenter{TravisCI}) = [ + Badge( + "Stable", + "https://img.shields.io/badge/docs-stable-blue.svg", + "https://{{USER}}.github.io/{{PKG}}.jl/stable", + ), + Badge( + "Dev", + "https://img.shields.io/badge/docs-dev-blue.svg", + "https://{{USER}}.github.io/{{PKG}}.jl/dev", + ), +] +badges(::Documenter{GitLabCI}) = Badge( + "Dev", + "https://img.shields.io/badge/docs-dev-blue.svg", + "https://{{USER}}.gitlab.io/{{PKG}}.jl/dev", +) + +view(p::Documenter, t::Template, pkg::AbstractString) = Dict( + "ASSETS" => p.assets, + "AUTHORS" => t.authors, + "CANONICAL" => p.canonical_url === nothing ? nothing : p.canonical_url(t, pkg), + "HAS_ASSETS" => !isempty(p.assets), + "MAKEDOCS_KWARGS" => map((k, v) -> k => repr(v), collect(p.makedocs_kwargs)), + "PKG" => pkg, + "REPO" => "https://$(t.host)/$(t.user)/$pkg.jl", +) + +function view(p::Documenter{TravisCI}, t::Template, pkg::AbstractString) + base = invoke(view, Tuple{Documenter, Template, AbstractString}, p, t, pkg) + return merge(base, Dict("HAS_DEPLOY" => true)) +end + +function gen_plugin(p::Documenter, t::Template, pkg_dir::AbstractString) + # TODO: gen make.jl + # TODO: gen index.md # Create the documentation project. - proj = Base.current_project() + docs_dir = joinpath(pkg_dir, "docs") + proj = current_project() try Pkg.activate(docs_dir) Pkg.add(PackageSpec(; name="Documenter", uuid=DOCUMENTER_UUID)) @@ -33,95 +87,38 @@ function gen_plugin(p::Documenter, t::Template, pkg_name::AbstractString) proj === nothing ? Pkg.activate() : Pkg.activate(proj) end - tab = repeat(" ", 4) - assets_string = if !isempty(p.assets) - mkpath(joinpath(docs_dir, "src", "assets")) - for file in p.assets - cp(file, joinpath(docs_dir, "src", "assets", basename(file))) - end - - # We want something that looks like the following: - # [ - # assets/file1, - # assets/file2, - # ] - s = "[\n" - for asset in p.assets - s *= """$(tab^2)"assets/$(basename(asset))",\n""" - end - s *= "$tab]" - - s - else - "String[]" - end - - kwargs_string = if :additional_kwargs in fieldnames(typeof(p)) && - fieldtype(typeof(p), :additional_kwargs) <: Union{AbstractDict, NamedTuple} - # We want something that looks like the following: - # key1="val1", - # key2="val2", - # - kws = [keys(p.additional_kwargs)...] - valid_keys = filter(k -> !in(Symbol(k), STANDARD_KWS), kws) - if length(p.additional_kwargs) > length(valid_keys) - invalid_keys = filter(k -> Symbol(k) in STANDARD_KWS, kws) - @warn string( - "Ignoring predefined Documenter kwargs ", - join(map(repr, invalid_keys), ", "), - " from additional kwargs" - ) - end - join(map(k -> string(tab, k, "=", repr(p.additional_kwargs[k]), ",\n"), valid_keys)) - else - "" - end - - make = """ - using Documenter, $pkg_name - - makedocs(; - modules=[$pkg_name], - format=Documenter.HTML(), - pages=[ - "Home" => "index.md", - ], - repo="https://$(t.host)/$(t.user)/$pkg_name.jl/blob/{commit}{path}#L{line}", - sitename="$pkg_name.jl", - authors="$(t.authors)", - assets=$assets_string, - $kwargs_string) - """ - docs = """ - # $pkg_name.jl - - ```@index - ``` - - ```@autodocs - Modules = [$pkg_name] - ``` - """ - - gen_file(joinpath(docs_dir, "make.jl"), make) - gen_file(joinpath(docs_dir, "src", "index.md"), docs) + # Copy any assets. + assets_dir = joinpath(docs_dir, "src", "assets") + isempty(p.assets) || mkpath(assets_dir) + foreach(a -> cp(a, joinpath(assets_dir, basename(asset))), p.assets) end -function Base.show(io::IO, p::Documenter) - spc = " " - println(io, nameof(typeof(p)), ":") +function interactive(::Type{Documenter{T}}) where T + name = "Documenter{$T}" - n = length(p.assets) - s = n == 1 ? "" : "s" - print(io, spc, "→ $n asset file$s") - if n == 0 - println(io) - else - println(io, ": ", join(map(a -> replace(a, homedir() => "~"), p.assets), ", ")) + print("$name: Enter any Documenter asset files (separated by spaces) [none]: ") + assets = split(readline()) + + print("$name: Enter any extra makedocs key-value pairs (joined by '=') [none]\n> ") + kwargs = Dict{Symbol, Any}() + line = map(split(readline())) do kv + k, v = split(kv, "="; limit=2) + kwargs[Symbol(k)] = eval(Meta.parse(v)) end - n = length(p.gitignore) - s = n == 1 ? "" : "s" - print(io, "$spc→ $n gitignore entrie$s") - n > 0 && print(io, ": ", join(map(repr, p.gitignore), ", ")) + return Documenter{T}(; assets=assets, kwargs=kwargs) end + +function interactive(::Type{Documenter}) + types = Dict( + "None (local documentation only)" => Nothing, + "TravisCI (GitHub Pages)" => TravisCI, + "GitLabCI (GitLab Pages)" => GitLabCI, + ) + options = collect(keys(types)) + menu = RadioMenu(options) + T = types[options[request("Documenter: Select integration:", menu)]] + return interactive(Documenter{T}) +end + +github_pages_url(t::Template, pkg::AbstractString) = "https://$(t.user).github.io/$pkg.jl" diff --git a/src/plugins/essentials.jl b/src/plugins/essentials.jl new file mode 100644 index 0000000..49741e0 --- /dev/null +++ b/src/plugins/essentials.jl @@ -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 diff --git a/src/plugins/githubpages.jl b/src/plugins/githubpages.jl deleted file mode 100644 index 10a28a9..0000000 --- a/src/plugins/githubpages.jl +++ /dev/null @@ -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 diff --git a/src/plugins/gitlabci.jl b/src/plugins/gitlabci.jl deleted file mode 100644 index 4d2e1a2..0000000 --- a/src/plugins/gitlabci.jl +++ /dev/null @@ -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 diff --git a/src/plugins/gitlabpages.jl b/src/plugins/gitlabpages.jl deleted file mode 100644 index 3f2c032..0000000 --- a/src/plugins/gitlabpages.jl +++ /dev/null @@ -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 diff --git a/src/plugins/travisci.jl b/src/plugins/travisci.jl deleted file mode 100644 index 2d3eb72..0000000 --- a/src/plugins/travisci.jl +++ /dev/null @@ -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") diff --git a/src/template.jl b/src/template.jl index d26b371..8fa59c7 100644 --- a/src/template.jl +++ b/src/template.jl @@ -1,242 +1,107 @@ default_version() = VersionNumber(VERSION.major) """ - Template(; kwargs...) -> Template + Template(interactive::Bool=false; kwargs...) -> Template -Records common information used to generate a package. If you don't wish to manually -create a template, you can use [`interactive_template`](@ref) instead. +Records common information used to generate a package. -# Keyword Arguments -* `user::AbstractString=""`: GitHub (or other code hosting service) username. If left - unset, it will take the the global git config's value (`github.user`). If that is not - set, an `ArgumentError` is thrown. **This is case-sensitive for some plugins, so take - care to enter it correctly.** -* `host::AbstractString="github.com"`: URL to the code hosting service where your package - will reside. Note that while hosts other than GitHub won't cause errors, they are not - officially supported and they will cause certain plugins will produce incorrect output. -* `license::AbstractString="MIT"`: Name of the package license. If an empty string is - given, no license is created. [`available_licenses`](@ref) can be used to list all - available licenses, and [`show_license`](@ref) can be used to print out a particular - license's text. -* `authors::Union{AbstractString, Vector{<:AbstractString}}=""`: Names that appear on the - license. Supply a string for one author or an array for multiple. Similarly to `user`, - it will take the value of of the global git config's value if it is left unset. -* `dir::AbstractString=$(replace(Pkg.devdir(), homedir() => "~"))`: Directory in which the - package will go. Relative paths are converted to absolute ones at template creation time. -* `julia_version::VersionNumber=$(default_version())`: Minimum allowed Julia version. -* `ssh::Bool=false`: Whether or not to use SSH for the git remote. If `false` HTTPS will be used. -* `dev::Bool=true`: Whether or not to `Pkg.develop` generated packages. -* `manifest::Bool=false`: Whether or not to commit the `Manifest.toml`. -* `plugins::Vector{<:Plugin}=Plugin[]`: A list of `Plugin`s that the package will include. +## Keyword Arguments +- `user::AbstractString=""`: GitHub (or other code hosting service) username. + If left unset, it will take the the global Git config's value (`github.user`). + If that is not set, an `ArgumentError` is thrown. + This is case-sensitive for some plugins, so take care to enter it correctly! +- `host::AbstractString="github.com"`: URL to the code hosting service where your package will reside. + Note that while hosts other than GitHub won't cause errors, they are not officially supported and they will cause certain plugins will produce incorrect output. +- `authors::Union{AbstractString, Vector{<:AbstractString}}=""`: Names that appear on the license. + Supply a string for one author or an array for multiple. + Similarly to `user`, it will take the value of of the global Git config's value if it is left unset. +- `dir::AbstractString=$(contractuser(Pkg.devdir()))`: Directory in which the package will go. + Relative paths are converted to absolute ones at template creation time. +- `julia_version::VersionNumber=$DEFAULT_VERSION`: Minimum allowed Julia version. +- `ssh::Bool=false`: Whether or not to use SSH for the git remote. If `false` HTTPS will be used. +- `manifest::Bool=false`: Whether or not to commit the `Manifest.toml`. +- `git::Bool=true`: Whether or not to create a Git repository for generated packages. +- `develop::Bool=true`: Whether or not to `develop` generated packages in the active environment. +- `plugins::Vector{<:Plugin}=Plugin[]`: A list of plugins that the package will include. +- `disable_default_plugins::Vector{DataType}=DataType[]`: Default plugins to disable. + The default plugins are [`Readme`](@ref), [`License`](@ref), [`Tests`](@ref), and [`Gitignore`](@ref). + To override a default plugin instead of disabling it altogether, supply it via `plugins`. +- `interactive::Bool=false`: When set, creates the template interactively from user input, + using the previous keywords as a starting point. +- `fast::Bool=false`: Only applicable when `interactive` is set. + Skips prompts for any unsupplied keywords except `user` and `plugins`. """ struct Template user::String host::String - license::String authors::String dir::String julia_version::VersionNumber ssh::Bool - dev::Bool manifest::Bool + git::Bool + develop::Bool plugins::Dict{DataType, <:Plugin} +end - function Template(; - user::AbstractString="", - host::AbstractString="https://github.com", - license::AbstractString="MIT", - authors::Union{AbstractString, Vector{<:AbstractString}}="", - dir::AbstractString=Pkg.devdir(), - julia_version::VersionNumber=default_version(), - ssh::Bool=false, - dev::Bool=true, - manifest::Bool=false, - plugins::Vector{<:Plugin}=Plugin[], - git::Bool=true, +Template(; interactive::Bool=false, kwargs...) = make_template(Val(interactive); kwargs...) + +function make_template(::Val{false}; kwargs...) + user = getkw(kwargs, :user) + if isempty(user) + throw(ArgumentError("No username found, set one with user=username")) + end + + host = getkw(kwargs, :host) + host = URI(occursin("://", host) ? host : "https://$host").host + + authors = getkw(kwargs, :authors) + authors isa Vector && (authors = join(authors, ", ")) + + dir = abspath(expanduser(getkw(kwargs, :dir))) + + disabled = getkw(kwargs, :disabled_defaults) + defaults = [Readme, License, Tests, Gitignore] + plugins = map(T -> T(), filter(T -> !in(T, disabled), defaults)) + append!(plugins, getkw(kwargs, :plugins)) + # This comprehensions resolves duplicate plugin types by overwriting, + # which means that default plugins get replaced by user values. + plugin_dict = Dict(typeof(p) => p for p in plugins) + + return Template( + user, + host, + authors, + dir, + getkw(kwargs, :julia_version), + getkw(kwargs, :ssh), + getkw(kwargs, :manifest), + getkw(kwargs, :git), + getkw(kwargs, :develop), + plugin_dict, ) - # Check for required Git options for package generation - # (you can't commit to a repository without them). - git && isempty(LibGit2.getconfig("user.name", "")) && missingopt("user.name") - git && isempty(LibGit2.getconfig("user.email", "")) && missingopt("user.email") - - # If no username was set, look for one in the global git config. - # Note: This is one of a few GitHub specifics (maybe we could use the host value). - if isempty(user) - user = LibGit2.getconfig("github.user", "") - end - if isempty(user) - throw(ArgumentError("No GitHub username found, set one with user=username")) - end - - host = URI(startswith(host, "https://") ? host : "https://$host").host - - if !isempty(license) && !isfile(joinpath(LICENSE_DIR, license)) - throw(ArgumentError("License '$license' is not available")) - end - - # If no author was set, look for one in the global git config. - if isempty(authors) - authors = LibGit2.getconfig("user.name", "") - email = LibGit2.getconfig("user.email", "") - isempty(email) || (authors *= " <$email>") - elseif authors isa Vector - authors = join(authors, ", ") - end - - dir = abspath(expanduser(dir)) - - plugin_dict = Dict{DataType, Plugin}(typeof(p) => p for p in plugins) - if (length(plugins) != length(plugin_dict)) - @warn "Plugin list contained duplicates, only the last of each type was kept" - end - - new(user, host, license, authors, dir, julia_version, ssh, dev, manifest, plugin_dict) - end end -function Base.show(io::IO, t::Template) - maybe(s::String) = isempty(s) ? "None" : s - spc = " " +hasplugin(t::Template, ::Type{T}) where T <: Plugin = any(U -> U <: T, keys(t.plugins)) - println(io, "Template:") - println(io, spc, "→ User: ", maybe(t.user)) - println(io, spc, "→ Host: ", maybe(t.host)) +getkw(kwargs, k) = get(() -> defaultkw(k), kwargs, k) - print(io, spc, "→ License: ") - if isempty(t.license) - println(io, "None") - else - println(io, t.license, " ($(t.authors) ", year(today()), ")") - end - - println(io, spc, "→ Package directory: ", replace(maybe(t.dir), homedir() => "~")) - println(io, spc, "→ Minimum Julia version: v", version_floor(t.julia_version)) - println(io, spc, "→ SSH remote: ", t.ssh ? "Yes" : "No") - println(io, spc, "→ Add packages to main environment: ", t.dev ? "Yes" : "No") - println(io, spc, "→ Commit Manifest.toml: ", t.manifest ? "Yes" : "No") - - print(io, spc, "→ Plugins:") - if isempty(t.plugins) - print(io, " None") - else - for plugin in sort(collect(values(t.plugins)); by=string) - println(io) - buf = IOBuffer() - show(buf, plugin) - print(io, spc^2, "• ") - print(io, join(split(String(take!(buf)), "\n"), "\n$(spc^2)")) - end - end +defaultkw(s::Symbol) = defaultkw(Val(s)) +defaultkw(::Val{:user}) = LibGit2.getconfig("github.user", "") +defaultkw(::Val{:host}) = "https://github.com" +defaultkw(::Val{:dir}) = Pkg.devdir() +defaultkw(::Val{:julia_version}) = DEFAULT_VERSION +defaultkw(::Val{:ssh}) = false +defaultkw(::Val{:manifest}) = false +defaultkw(::Val{:git}) = true +defaultkw(::Val{:develop}) = true +defaultkw(::Val{:plugins}) = Plugin[] +defaultkw(::Val{:disabled_defaults}) = DataType[] +function defaultkw(::Val{:authors}) + name = LibGit2.getconfig("user.name", "") + email = LibGit2.getconfig("user.email", "") + isempty(name) && return "" + author = name * " " + isempty(email) || (author *= "<$email>") + return strip(author) end - -""" - interactive_template(; fast::Bool=false) -> Template - -Interactively create a [`Template`](@ref). If `fast` is set, defaults will be assumed for -all values except username and plugins. -""" -function interactive_template(; git::Bool=true, fast::Bool=false) - @info "Default values are shown in [brackets]" - # Getting the leaf types in a separate thread eliminates an awkward wait after - # "Select plugins" is printed. - plugin_types = @async leaves(Plugin) - kwargs = Dict{Symbol, Any}() - - default_user = LibGit2.getconfig("github.user", "") - print("Username [", isempty(default_user) ? "REQUIRED" : default_user, "]: ") - user = readline() - kwargs[:user] = if !isempty(user) - user - elseif !isempty(default_user) - default_user - else - throw(ArgumentError("Username is required")) - end - - kwargs[:host] = if fast || !git - "https://github.com" # If Git isn't enabled, this value never gets used. - else - default_host = "github.com" - print("Code hosting service [$default_host]: ") - host = readline() - isempty(host) ? default_host : host - end - - kwargs[:license] = if fast - "MIT" - else - println("License:") - io = IOBuffer() - available_licenses(io) - licenses = ["" => "", collect(LICENSES)...] - menu = RadioMenu(String["None", split(String(take!(io)), "\n")...]) - # If the user breaks out of the menu with Ctrl-c, the result is -1, the absolute - # value of which correponds to no license. - first(licenses[abs(request(menu))]) - end - - # We don't need to ask for authors if there is no license, - # because the license is the only place that they matter. - kwargs[:authors] = if fast || isempty(kwargs[:license]) - LibGit2.getconfig("user.name", "") - else - default_authors = LibGit2.getconfig("user.name", "") - default_str = isempty(default_authors) ? "None" : default_authors - print("Package author(s) [$default_str]: ") - authors = readline() - isempty(authors) ? default_authors : authors - end - - kwargs[:dir] = if fast - Pkg.devdir() - else - default_dir = Pkg.devdir() - print("Path to package directory [$default_dir]: ") - dir = readline() - isempty(dir) ? default_dir : dir - end - - kwargs[:julia_version] = if fast - VERSION - else - default_julia_version = VERSION - print("Minimum Julia version [", version_floor(default_julia_version), "]: ") - julia_version = readline() - isempty(julia_version) ? default_julia_version : VersionNumber(julia_version) - end - - kwargs[:ssh] = if fast || !git - false - else - print("Set remote to SSH? [no]: ") - uppercase(readline()) in ["Y", "YES", "T", "TRUE"] - end - - kwargs[:dev] = if fast - true - else - print("Add packages to main environment? [yes]: ") - uppercase(readline()) in ["", "Y", "YES", "T", "TRUE"] - end - - kwargs[:manifest] = if fast - false - else - print("Commit Manifest.toml? [no]: ") - uppercase(readline()) in ["Y", "YES", "T", "TRUE"] - end - - println("Plugins:") - # Only include plugin types which have an `interactive` method. - plugin_types = filter(t -> hasmethod(interactive, (Type{t},)), fetch(plugin_types)) - type_names = map(t -> split(string(t), ".")[end], plugin_types) - menu = MultiSelectMenu(String.(type_names); pagesize=length(type_names)) - selected = collect(request(menu)) - kwargs[:plugins] = Vector{Plugin}(map(interactive, getindex(plugin_types, selected))) - - return Template(; git=git, kwargs...) -end - -leaves(T::Type)::Vector{DataType} = isconcretetype(T) ? [T] : vcat(leaves.(subtypes(T))...) - -missingopt(name) = @warn "Git config option '$name' missing, package generation will fail unless you supply a GitConfig" diff --git a/test/gitconfig b/test/gitconfig deleted file mode 100644 index 8675f32..0000000 --- a/test/gitconfig +++ /dev/null @@ -1,3 +0,0 @@ -[user] - name = Travis - email = travis@c.i diff --git a/test/interactive/interactive.jl b/test/interactive/interactive.jl deleted file mode 100644 index f04e8f6..0000000 --- a/test/interactive/interactive.jl +++ /dev/null @@ -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 diff --git a/test/interactive/plugins.jl b/test/interactive/plugins.jl deleted file mode 100644 index 3c8760d..0000000 --- a/test/interactive/plugins.jl +++ /dev/null @@ -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 diff --git a/test/plugins/appveyor.jl b/test/plugins/appveyor.jl deleted file mode 100644 index de81e72..0000000 --- a/test/plugins/appveyor.jl +++ /dev/null @@ -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) diff --git a/test/plugins/cirrusci.jl b/test/plugins/cirrusci.jl deleted file mode 100644 index 9efc1eb..0000000 --- a/test/plugins/cirrusci.jl +++ /dev/null @@ -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) diff --git a/test/plugins/citation.jl b/test/plugins/citation.jl deleted file mode 100644 index 888660f..0000000 --- a/test/plugins/citation.jl +++ /dev/null @@ -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) diff --git a/test/plugins/codecov.jl b/test/plugins/codecov.jl deleted file mode 100644 index b7648d8..0000000 --- a/test/plugins/codecov.jl +++ /dev/null @@ -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) diff --git a/test/plugins/coveralls.jl b/test/plugins/coveralls.jl deleted file mode 100644 index f74e219..0000000 --- a/test/plugins/coveralls.jl +++ /dev/null @@ -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) diff --git a/test/plugins/githubpages.jl b/test/plugins/githubpages.jl deleted file mode 100644 index 7ea563f..0000000 --- a/test/plugins/githubpages.jl +++ /dev/null @@ -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) diff --git a/test/plugins/gitlabci.jl b/test/plugins/gitlabci.jl deleted file mode 100644 index e556156..0000000 --- a/test/plugins/gitlabci.jl +++ /dev/null @@ -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) diff --git a/test/plugins/gitlabpages.jl b/test/plugins/gitlabpages.jl deleted file mode 100644 index 299206d..0000000 --- a/test/plugins/gitlabpages.jl +++ /dev/null @@ -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) diff --git a/test/plugins/travisci.jl b/test/plugins/travisci.jl deleted file mode 100644 index 20d1fbb..0000000 --- a/test/plugins/travisci.jl +++ /dev/null @@ -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) diff --git a/test/runtests.jl b/test/runtests.jl index d323438..e69de29 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -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 diff --git a/test/tests.jl b/test/tests.jl index ea8a0ad..1b3e5ae 100644 --- a/test/tests.jl +++ b/test/tests.jl @@ -44,7 +44,7 @@ write(test_file, template_text) @test t.license == "MIT" @test t.authors == "foo" @test t.dir == default_dir - @test t.julia_version == PkgTemplates.default_version() + @test t.julia_version == PkgTemplates.default_version @test !t.ssh @test !t.manifest @test isempty(t.plugins) @@ -107,7 +107,7 @@ end @testset "Show methods" begin pkg_dir = replace(default_dir, homedir() => "~") - ver = PkgTemplates.version_floor(PkgTemplates.default_version()) + ver = PkgTemplates.version_floor(PkgTemplates.default_version) buf = IOBuffer() t = Template(; user=me, authors="foo") show(buf, t) @@ -327,12 +327,6 @@ end rm(temp_dir; recursive=true) end -@testset "Git-less template creation" begin - if isempty(LibGit2.getconfig("user.name", "")) - @test_logs Template(; user=me, git=false) - end -end - @testset "Git-less package generation" begin t = Template(; user=me) generate(test_pkg, t; git=false) @@ -340,84 +334,6 @@ end @test !isfile(joinpath(t.dir, ".gitignore")) end -@testset "Version floor" begin - @test version_floor(v"1.0.0") == "1.0" - @test version_floor(v"1.0.1") == "1.0" - @test version_floor(v"1.0.1-pre") == "1.0" - @test version_floor(v"1.0.0-pre") == "1.0-" -end - -@testset "Mustache substitution" begin - view = Dict{String, Any}() - text = substitute(template_text, view) - @test !occursin("PKGNAME: $test_pkg", text) - @test !occursin("Documenter", text) - @test !occursin("Codecov", text) - @test !occursin("Coveralls", text) - @test !occursin("After", text) - @test !occursin("Other", text) - view["PKGNAME"] = test_pkg - view["OTHER"] = true - text = substitute(template_text, view) - @test occursin("PKGNAME: $test_pkg", text) - @test occursin("Other", text) - - t = Template(; user=me) - view["OTHER"] = false - - text = substitute(template_text, t; view=view) - @test occursin("PKGNAME: $test_pkg", text) - @test occursin("VERSION: $(t.julia_version.major).$(t.julia_version.minor)", text) - @test !occursin("Documenter", text) - @test !occursin("After", text) - @test !occursin("Other", text) - - t.plugins[GitHubPages] = GitHubPages() - text = substitute(template_text, t; view=view) - @test occursin("Documenter", text) - @test occursin("After", text) - empty!(t.plugins) - - t.plugins[Codecov] = Codecov() - text = substitute(template_text, t; view=view) - @test occursin("Codecov", text) - @test occursin("After", text) - empty!(t.plugins) - - t.plugins[Coveralls] = Coveralls() - text = substitute(template_text, t; view=view) - @test occursin("Coveralls", text) - @test occursin("After", text) - empty!(t.plugins) - - view["OTHER"] = true - text = substitute(template_text, t; view=view) - @test occursin("Other", text) -end - -@testset "License display" begin - io = IOBuffer() - available_licenses(io) - licenses = String(take!(io)) - show_license(io, "MIT") - mit = String(take!(io)) - - # Check that all licenses are included in the display. - for (short, long) in LICENSES - @test occursin("$short: $long", licenses) - end - @test strip(mit) == strip(read_license("MIT")) - @test strip(read_license("MIT")) == strip(read(joinpath(LICENSE_DIR, "MIT"), String)) - @test_throws ArgumentError read_license(fake_path) - - # Check that all licenses included with the package are displayed. - for license in readdir(LICENSE_DIR) - @test haskey(LICENSES, license) - end - # Check that all licenses displayed are included with the package. - @test length(readdir(LICENSE_DIR)) == length(LICENSES) -end - @testset "Plugins" begin t = Template(; user=me) pkg_dir = joinpath(t.dir, test_pkg) @@ -474,7 +390,8 @@ end warn_str = "Ignoring predefined Documenter kwargs \"format\" from additional kwargs" check_kwargs(kwargs, warn_str) - kwargs = Dict(:checkdocs => :none, + kwargs = Dict( + :checkdocs => :none, :strict => true, :format => :markdown, :stringarg => "string", @@ -488,5 +405,3 @@ end end include(joinpath("interactive", "interactive.jl")) - -rm(test_file)