Rework code, fix/add tests, fix/add docs. All the things.

This commit is contained in:
Chris de Graaf 2017-08-17 17:06:05 -05:00
parent dfa9f123de
commit d009acf24b
20 changed files with 479 additions and 342 deletions

View File

@ -33,5 +33,6 @@ build_script:
test_script:
- C:\projects\julia\bin\julia -e "Pkg.test(\"PkgTemplates\")"
after_script:
- C:\projects\julia\bin\julia -e "cd(Pkg.dir(\"AppVeyorTesting\")); Pkg.add(\"Coverage\"); using Coverage; Codecov.submit(process_folder())"

2
.gitignore vendored
View File

@ -1,8 +1,6 @@
.DS_Store
# CodeCov generated files
*.jl.cov
*.jl.*.cov
*.jl.mem
# Documenter generated files
/docs/build/
/docs/site/

View File

@ -12,8 +12,6 @@ script:
- if [[ -a .git/shallow ]]; then git fetch --unshallow; fi
- julia -e 'Pkg.clone(pwd()); Pkg.build("PkgTemplates"); Pkg.test("PkgTemplates"; coverage=true)'
after_success:
# push coverage results to CodeCov
- julia -e 'cd(Pkg.dir("PkgTemplates")); Pkg.add("Coverage"); using Coverage; Codecov.submit(process_folder())'
# build documentation
- julia -e 'Pkg.add("Documenter")'
- julia -e 'cd(Pkg.dir("PkgTemplates")); include(joinpath("docs", "make.jl"))'

View File

@ -1,193 +0,0 @@
# Contributing to PkgTemplates
The best way to contribute to `PkgTemplates` is by adding new plugins.
There are two main types of plugins:
[`GenericPlugin`](https://invenia.github.io/PkgTemplates.jl/stable/pages/plugins.html#GenericPlugin-1)s
and
[`CustomPlugin`](https://invenia.github.io/PkgTemplates.jl/stable/pages/plugins.html#CustomPlugin-1)s.
## Writing a Generic Plugin
As the name suggests, generic plugins are simpler than custom ones, and as
such are extremely easy to implement. They have the ability to add patterns
the the generated `.gitignore`, as well as create a single configuration file.
We're going to define a new generic plugin `MyPlugin` in
`src/plugins/myplugin.jl`:
```julia
@auto_hash_equals struct MyPlugin <: GenericPlugin
gitignore::Vector{AbstractString}
src::Nullable{AbstractString}
dest::AbstractString
badges::Vector{AbstractString}
view::Dict{String, Any}
function MyPlugin(; config_file::Union{AbstractString, Void}="")
if config_file != nothing
if isempty(config_file)
config_file = joinpath(DEFAULTS_DIR, "myplugin.yml")
elseif !isfile(config_file)
throw(ArgumentError("File $(abspath(config_file)) does not exist"))
end
end
new([], config_file, ".myplugin.yml", [], Dict{String, Any}())
end
end
```
That's all there is to it! Let's take a better look at what we've done:
* The plugin has five attributes, these must be exactly as they are.
* `gitignore` is the array of patterns to add the the generated package's
`.gitignore`, we chose not to add any with this plugin.
* `src` is the location of the config file we're going to copy into the
generated package repository. If this is `nothing`, no config file will be
generated. This came from the `config_file` keyword argument, which
defaulted to an empty string. That's because we've placed a default
config file at `defaults/myplugin.yml`.
* `dest` is the path to our generated config file, relative to the root of
the package repository. In this example, the file will go in
`.myplugin.yml` at the root of the repository.
* `badges` is an array of Markdown-formatted badge strings to be displayed
on the package's README. We chose not to include any here. TODO talk about
`substitute`.
* `view` is a dictionary of additional replacements to `substitute`.
Plenty of services like
[`TravisCI`](https://invenia.github.io/PkgTemplates.jl/stable/pages/plugins.html#TravisCI-1)
and
[`CodeCov`](https://invenia.github.io/PkgTemplates.jl/stable/pages/plugins.html#CodeCov-1)
follow this format, so generic plugins should be able to get you pretty far.
## Writing a Custom Plugin
When a service doesn't follow the pattern demonstrated above, it's time to write a custom
plugin. These are still pretty simple, needing at most two additional methods. Let's create
a custom plugin called `Gamble` in `src/plugins/gamble.jl` that only generates a file if
you get lucky enough:
```julia
@auto_hash_equals struct Gamble <: CustomPlugin
gitignore:Vector{AbstractString}
src::AbstractString
success::Bool
function Gamble(config_file::AbstractString)
if !isfile(config_file)
throw(ArgumentError("File $(abspath(config_file)) does not exist"))
end
success = rand() > 0.8
println(success ? "Congratulations!" : "Maybe next time.")
new([], config_file, success)
end
end
function badges(plugin: Gamble, user::AbstractString, pkg_name::AbstractString)
if plugin.success
return ["[![You won!](https://i.imgur.com/poker-chip)](https://pokerstars.net)"]
else
return String[]
end
end
function gen_plugin(plugin::Gamble, template::Template, pkg_name::AbstractString)
if plugin.success
text = substitute(readstring(plugin.src), template, pkg_name)
gen_file(joinpath(t.temp_dir, ".gambler.yml"), text)
return [".gambler.yml"]
else
return String[]
end
end
```
With that, we've got everything we need. Note that this plugin still has a `gitignore`
attribute; it's required for all plugins. Let's look at the extra methods we implemented:
#### `gen_plugin`
We read the text from the plugin's source file, and then we run it through the `substitute`
function (more on that [later](#template-substitution)).
Next, we use `gen_file` to write the text, with substitutions applied, to the destination
file in `t.temp_dir`. Generating our repository in a temp directory means we're not stuck
with leftovers in the case of an error.
This function returns an array of all the root-level files or directories
that were created. If both `foo/bar` and `foo/baz` were created, we only need
to return `["foo/"]`.
#### `badges`
This function returns an array of Markdown-formatted badges to be displayed on
the package README. You can find badges and Markdown strings for just about
everything on [Shields.io](https://shields.io).
This will do the trick, but if we want our badge to appear at a specific
position in the README, we need to edit `BADGE_ORDER` in
[`src/PkgTemplates.jl`(https://github.com/invenia/PkgTemplates.jl/blob/master/src/PkgTemplates.jl).
Say we want our badge to appear before all others, we'll add `Gamble` to the
beginning of the array.
That's all there is to it! We've just created a nifty custom plugin.
***
### Template Substitution
Since plugin configuration files are often specific to the package they belong
to, we might want to replace some placeholder values in our plugin's config
file. We can do this by following
[Mustache.jl](https://github.com/jverzani/Mustache.jl)'s rules. Some
replacements are defined by `PkgTemplates`:
* `{{PKGNAME}}` is replaced by `pkg_name`.
* `{{VERSION}}` is replaced by `$major.$minor` corresponding to
`template.julia_version`.
Some conditional replacements are also defined:
* `{{DOCUMENTER}}Documenter{{/DOCUMENTER}}`
* "Documenter" only appears in the rendered text if the template contains
a [`Documenter`](src/plugins/documenter.jl) subtype.
* `{{CODECOV}}CodeCov{{/CODECOV}}`
* "CodeCov" only appears in the rendered text if the template contains
the [`CodeCov`](src/plugins/codecov.jl) plugin.
* `{{#AFTER}}After{{/AFTER}}`
* "After" only appears in the rendered text if something needs to happen
**after** CI testing occurs. As of right now, this is true when either of
the above two conditions are true.
We can also specify our own replacements by passing a dictionary to
`substitute`:
```julia
view = Dict("KEY" => "VAL", "HEADS" => 2rand() > 1)
text = """
{{KEY}}
{{PKGNAME}}
{{#HEADS}}Heads{{/HEADS}}
"""
substituted = substitute(text, "MyPkg", template; view=view)
```
This will return `"VAL\nMyPkg\nHeads\n"` if `2rand() > 1` was true,
`"VAL\nMyPkg\n\n"` otherwise.
Note the double newline in the second outcome; `Mustache` has a bug with
conditionals that inserts extra newlines (more detail
[here](https://github.com/jverzani/Mustache.jl/issues/47)). We can get around
this by writing ugly template files, like so:
```
{{KEY}}
{{PKGNAME}}{{#HEADS}}
Heads{{/HEADS}}
```
The resulting string will end with a single newline regardless of the value
of `view["HEADS"]`
Also note that conditionals without a corresponding key in `view` won't error,
but will simply be evaluated as false.

View File

@ -85,5 +85,11 @@ Information on each keyword as well as plugin types can be found in the
`PkgTemplates` is similar in functionality to `PkgDev`'s `generate` function.
However, `PkgTemplates` offers more customizability in templates and more
extensibility via plugins. For the package registration and release management
features that `PkgTemplates` lacks, you are encouraged to use
features that `PkgTemplates` doesn't include, you are encouraged to use
[AttoBot](https://github.com/apps/attobot) instead.
## 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).

View File

@ -32,6 +32,8 @@ build_script:
Pkg.clone(pwd(), \"{{PKGNAME}}\"); Pkg.build(\"{{PKGNAME}}\")"
test_script:
- C:\projects\julia\bin\julia -e "Pkg.test(\"{{PKGNAME}}\")"
{{#AFTER}}after_script:
{{#CODECOV}}- C:\projects\julia\bin\julia -e "cd(Pkg.dir(\"{{PKGNAME}}\")); Pkg.add(\"Coverage\"); using Coverage; Codecov.submit(process_folder())"{{/CODECOV}}{{/AFTER}}
- C:\projects\julia\bin\julia -e "Pkg.test(\"{{PKGNAME}}\")"{{#AFTER}}
after_script:{{#CODECOV}}
- C:\projects\julia\bin\julia -e "cd(Pkg.dir(\"{{PKGNAME}}\")); Pkg.add(\"Coverage\"); using Coverage; Codecov.submit(process_folder())"{{/CODECOV}}{{#COVERALLS}}
- C:\projects\julia\bin\julia -e "cd(Pkg.dir(\"{{PKGNAME}}\")); Pkg.add(\"Coverage\"); using Coverage; Coveralls.submit(process_folder())"{{/COVERALLS}}{{/AFTER}}

View File

@ -10,8 +10,9 @@ notifications:
email: false
script:
- if [[ -a .git/shallow ]]; then git fetch --unshallow; fi
- julia -e 'Pkg.clone(pwd()); Pkg.build("{{PKGNAME}}"); Pkg.test("{{PKGNAME}}"; coverage=true)'
{{#AFTER}}after_success:
{{#CODECOV}}- julia -e 'cd(Pkg.dir("{{PKGNAME}}")); Pkg.add("Coverage"); using Coverage; Codecov.submit(process_folder())'{{/CODECOV}}
{{#DOCUMENTER}}- julia -e 'Pkg.add("Documenter")'
- julia -e 'Pkg.clone(pwd()); Pkg.build("{{PKGNAME}}"); Pkg.test("{{PKGNAME}}"; coverage=true)'{{#AFTER}}
after_success:{{#CODECOV}}
- julia -e 'cd(Pkg.dir("{{PKGNAME}}")); Pkg.add("Coverage"); using Coverage; Codecov.submit(process_folder())'{{/CODECOV}}{{#COVERALLS}}
- julia -e 'cd(Pkg.dir("{{PKGNAME}}")); Pkg.add("Coverage"); using Coverage; Coveralls.submit(process_folder())'{{/COVERALLS}}{{#DOCUMENTER}}
- julia -e 'Pkg.add("Documenter")'
- julia -e 'cd(Pkg.dir("{{PKGNAME}}")); include(joinpath("docs", "make.jl"))'{{/DOCUMENTER}}{{/AFTER}}

View File

@ -7,6 +7,7 @@ makedocs(
"Home" => "index.md",
"Package Generation" => "pages/package_generation.md",
"Plugins" => "pages/plugins.md",
"Plugin Development" => "pages/plugin_development.md",
"Licenses" => "pages/licenses.md",
"Index" => "pages/index.md"
],

View File

@ -0,0 +1,62 @@
```@meta
CurrentModule = PkgTemplates
```
# Plugin Development
The best and easiest way to contribute to `PkgTemplates` is to write new
plugins.
There are two types of plugins: [`GenericPlugin`](@ref)s and [`CustomPlugin`](@ref)s.
## Generic Plugins
```@docs
GenericPlugin
```
## Custom Plugins
```@docs
CustomPlugin
```
### CustomPlugin required methods
#### `gen_plugin`
```@docs
gen_plugin
```
#### `badges`
```@docs
badges
```
## Helper Functions
#### gen_file
```@docs
gen_file
```
#### substitute
```@docs
substitute
```
#### badge
```@docs
badge
```
#### version_floor
```@docs
version_floor
```

View File

@ -4,8 +4,9 @@ CurrentModule = PkgTemplates
# Plugins
For information on writing your own plugins, see
[CONTRIBUTING.md](https://github.com/invenia/PkgTemplates.jl/tree/master/CONTRIBUTING.md).
Plugins are the driver for `PkgTemplates`'s customization and extension. This page
describes plugins that already exist; for information on writing your own plugins, see the
[plugin development guide](https://invenia.github.io/PkgTemplates.jl/stable/pages/plugin_development.html).
## TravisCI
@ -25,6 +26,12 @@ AppVeyor
CodeCov
```
## Coveralls
```@docs
Coveralls
```
## Documenter
```@docs

View File

@ -42,8 +42,11 @@ function generate(
end
LibGit2.commit(repo, "Empty initial commit")
info("Made initial empty commit")
rmt = ssh ? "git@$(t.host):$(t.user)/$pkg_name.jl.git" :
rmt = if ssh
"git@$(t.host):$(t.user)/$pkg_name.jl.git"
else
"https://$(t.host)/$(t.user)/$pkg_name.jl"
end
LibGit2.set_remote_url(repo, rmt)
info("Set remote origin to $rmt")
@ -72,7 +75,7 @@ function generate(
info("Committed files generated by PkgTemplates")
multiple_branches = length(collect(LibGit2.GitBranchIter(repo))) > 1
info("Copying temporary package directory into $(t.dir)/")
cp(temp_pkg_dir, pkg_dir; remove_destination=force)
mv(temp_pkg_dir, pkg_dir; remove_destination=force)
rm(t.temp_dir; recursive=true)
info("Finished")
if multiple_branches
@ -103,7 +106,7 @@ function gen_readme(pkg_name::AbstractString, template::Template)
badges(template.plugins[plugin_type], template.user, pkg_name),
"\n",
)
deleteat!(remaining, findin(remaining, plugin_type))
deleteat!(remaining, find(p -> p == plugin_type, remaining)[1])
end
end
for plugin_type in remaining
@ -273,71 +276,69 @@ function version_floor(v::VersionNumber=VERSION)
end
end
"""
substitute(template::AbstractString, view::Dict{String, Any}) -> String
Replace placeholders in a template string. The input string is not modified.
# Arguments
* `template::AbstracString`: Template string in which to make replacements.
* `view::Dict{String, Any}`: (Placeholder => value) pairs.
Returns the template string with replacements applied.
# Notes
Due to a bug in `Mustache`, conditionals often insert undesired newlines (more detail
[here](https://github.com/jverzani/Mustache.jl/issues/47)).
For example:
```
A
{{#B}}B{{/B}}
C
```
When `view` doesn't have a `"B"` key (or it does, but it's false), this becomes
`"A\\n\\nC"` We can get around this by writing ugly template files, like so:
```
A{{#B}}
B{{/B}}
C
```
In this case, the result is `"A\\nB\\nC"`, like we want it to be.
Also note that conditionals 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)
"""
substitute(
template::AbstractString,
pkg_template::Template;
pkg_name::AbstractString,
view::Dict{String, Any}=Dict{String, Any}(),
) -> String
Replace placeholders in `template`. The input string is not modified.
# Arguments:
* `template::AbstractString`: Template string to make replacements in.
* `pkg_template::Template`: The package template in use.
* `pkg_name::AbstractString`: Name of the package being created.
* `view::Dict{String, Any}=Dict{String, Any}()`: Additional values to be substituted.
Returns the text with substitutions applied.
Replace placeholders in template string, using some default replacements based on the
package template. The input string is not modified.
"""
function substitute(
template::AbstractString,
pkg_template::Template,
pkg_name::AbstractString;
pkg_template::Template;
view::Dict{String, Any}=Dict{String, Any}(),
)
d = merge!(Dict{String, Any}(), view)
d["PKGNAME"] = pkg_name
d["USER"] = pkg_template.user
v = pkg_template.julia_version
# Don't use version_floor here because we don't want the trailing '-' on prereleases.
d["VERSION"] = "$(v.major).$(v.minor)"
v = pkg_template.julia_version
d = Dict{String, Any}(
"USER" => pkg_template.user,
"VERSION" => "$(v.major).$(v.minor)",
"DOCUMENTER" => any(isa(p, Documenter) for p in 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".
if any(isa(p, Documenter) for p in values(pkg_template.plugins))
d["DOCUMENTER"] = true
d["AFTER"] = true
end
if haskey(pkg_template.plugins, CodeCov)
d["CODECOV"] = true
d["AFTER"] = true
end
return render(template, d)
end
"""
substitute(
template::AbstractString,
pkg_plugin::Plugin;
pkg_name::AbstractString,
view::Dict{String, Any}=Dict{String, Any}(),
) -> String
Replace placeholders in `template`. The input string is not modified.
# Arguments:
* `template::AbstractString`: Template string to make replacements in.
* `pkg_plugin::Plugin`: The plugin in use.
* `pkg_name::AbstractString`: Name of the package being created.
* `view::Dict{String, Any}=Dict{String, Any}()`: Additional values to be substituted.
Returns the text with substitutions applied.
"""
function substitute(
template::AbstractString,
pkg_plugin::Plugin,
pkg_name::AbstractString;
view::Dict{String, Any}=Dict{String, Any}(),
)
return render(template, merge(Dict("PKGNAME" => pkg_name), view))
d["AFTER"] = d["DOCUMENTER"] || d["CODECOV"] || d["COVERALLS"]
return substitute(template, merge(d, view))
end

View File

@ -1,28 +1,123 @@
abstract type GenericPlugin <: Plugin end
abstract type CustomPlugin <: Plugin end
"""
badges(\_::Plugin, user::AbstractString, pkg_name::AbstractString)
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.
Generate Markdown badges for the current package.
# Attributes
* `gitignore::Vector{AbstractString}`: Array of patterns to be added to the `.gitignore` of
generated packages that use this plugin.
* `src::Nullable{AbstractString}`: 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.
* `dest::AbstractString`: Path to the generated file, relative to the root of the generated
package repository.
* `badges::Vector{Vector{AbstractString}}`: Array of arrays containing information to
create a Markdown-formatted badge from the plugin. Each entry is of the form
`[hover_text, image_url, link_url]`. 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.
# Arguments
* `plugin::Plugin`: Plugin whose badges we are generating.
* `user::AbstractString`: Username of the package creator.
* `pkg_name::AbstractString`: Name of the package.
# Example
```julia
@auto_hash_equals struct MyPlugin <: GenericPlugin
gitignore::Vector{AbstractString}
src::Nullable{AbstractString}
dest::AbstractString
badges::Vector{Vector{AbstractString}}
view::Dict{String, Any}
Returns an array of Markdown badges.
"""
badges(plugin::Plugin, user::AbstractString, pkg_name::AbstractString) = String[]
function badges(plugin::GenericPlugin, user::AbstractString, pkg_name::AbstractString)
return substitute.(
plugin.badges,
plugin,
pkg_name;
view=Dict{String, Any}("USER" => user)
)
function MyPlugin(; config_file::Union{AbstractString, Void}="")
if config_file != nothing
if isempty(config_file)
config_file = joinpath(DEFAULTS_DIR, "myplugin.yml")
elseif !isfile(config_file)
throw(ArgumentError(
"File \$(abspath(config_file)) does not exist"
))
end
end
new(
["*.mgp"],
config_file,
".mypugin.yml",
[
[
"My Plugin",
"https://myplugin.com/badge-{{YEAR}}.png",
"https://myplugin.com/{{USER}}/{{PKGNAME}}.jl",
],
],
Dict{String, Any}("YEAR" => Dates.year(Dates.now())),
)
end
end
```
The above plugin ignores files ending with `.mgp`, copies `defaults/myplugin.yml` by
default, and creates a badge that links to the project on its own site, using the default
substitutions with one addition: `{{YEAR}} => Dates.year(Dates.now())`.
"""
abstract type GenericPlugin <: Plugin end
"""
Custom plugins are plugins whose behaviour does not follow the [`GenericPlugin`](@ref)
pattern. They can implement [`gen_plugin`](@ref) and [`badges`](@ref) in any way they
choose.
# Attributes
* `gitignore::Vector{AbstractString}`: Array of patterns to be added to the `.gitignore` of
generated packages that use this plugin.
# Example
```julia
@auto_hash_equals struct MyPlugin <: CustomPlugin
gitignore::Vector{AbstractString}
lucky::Bool
MyPlugin() = new([], rand() > 0.8)
function gen_plugin(
plugin::MyPlugin,
template::Template,
pkg_name::AbstractString
)
if plugin.lucky
text = substitute(
"You got lucky with {{PKGNAME}}, {{USER}}!"),
template,
)
gen_file(joinpath(template.temp_dir, ".myplugin.yml"), text)
else
println("Maybe next time.")
end
end
function badges(
plugin::MyPlugin,
user::AbstractString,
pkg_name::AbstractString,
)
if plugin.lucky
return [
badge(
"You got lucky!",
"https://myplugin.com/badge.png",
"https://myplugin.com/\$user/\$pkg_name.jl",
),
]
else
return String[]
end
end
end
```
This plugin doesn't do much, but it demonstrates how [`gen_plugin`](@ref) and
[`badges`](@ref) can be implemented using [`substitute`](@ref), [`gen_file`](@ref),
and [`badge`](@ref).
"""
abstract type CustomPlugin <: Plugin end
"""
gen_plugin(plugin::Plugin, template::Template, pkg_name::AbstractString) -> Vector{String}
@ -39,16 +134,50 @@ Returns an array of generated file/directory names.
gen_plugin(plugin::Plugin, template::Template, pkg_name::AbstractString) = String[]
function gen_plugin(plugin::GenericPlugin, template::Template, pkg_name::AbstractString)
try
text = substitute(
readstring(get(plugin.src)),
template,
pkg_name;
view=plugin.view,
)
gen_file(joinpath(template.temp_dir, pkg_name, plugin.dest), text)
return [plugin.dest]
src = try
get(plugin.src)
catch
return String[]
end
text = substitute(
readstring(src),
template;
view=merge(Dict("PKGNAME" => pkg_name), plugin.view),
)
gen_file(joinpath(template.temp_dir, pkg_name, plugin.dest), text)
return [plugin.dest]
end
"""
badges(plugin::Plugin, user::AbstractString, pkg_name::AbstractString) -> Vector{String}
Generate Markdown badges for the plugin.
# Arguments
* `plugin::Plugin`: Plugin whose badges we are generating.
* `user::AbstractString`: Username of the package creator.
* `pkg_name::AbstractString`: Name of the package.
Returns an array of Markdown badges.
"""
badges(plugin::Plugin, user::AbstractString, pkg_name::AbstractString) = String[]
function badges(plugin::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), plugin.view)
return [badge([substitute(part, view) for part in b]...) for b in plugin.badges]
end
"""
badge(hover::AbstractString, image::AbstractString, image::AbstractString) -> String
Format a single Markdown badge.
# 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.
"""
function badge(hover::AbstractString, image::AbstractString, link::AbstractString)
return "[![$hover]($image)]($link)"
end

View File

@ -1,7 +1,8 @@
"""
AppVeyor(; config_file::Union{AbstractString, Void}="") -> GenericPlugin
Add AppVeyor to a template's plugins to add AppVeyor CI support.
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, Void}=""`: Path to a custom `.appveyor.yml`.
@ -11,7 +12,7 @@ Add AppVeyor to a template's plugins to add AppVeyor CI support.
gitignore::Vector{AbstractString}
src::Nullable{AbstractString}
dest::AbstractString
badges::Vector{AbstractString}
badges::Vector{Vector{AbstractString}}
view::Dict{String, Any}
function AppVeyor(; config_file::Union{AbstractString, Void}="")
@ -26,7 +27,13 @@ Add AppVeyor to a template's plugins to add AppVeyor CI support.
[],
config_file,
".appveyor.yml",
["[![Build Status](https://ci.appveyor.com/api/projects/status/github/{{USER}}/{{PKGNAME}}.jl?svg=true)](https://ci.appveyor.com/project/{{USER}}/{{PKGNAME}}-jl)"],
[
[
"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

View File

@ -1,7 +1,9 @@
"""
CodeCov(; config_file::Union{AbstractString, Void}="") -> GenericPlugin
Add CodeCov to a template's plugins to enable CodeCov coverage reports.
Add `CodeCov` to a template's plugins to 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, Void}=""`: Path to a custom `.codecov.yml`.
@ -11,7 +13,7 @@ Add CodeCov to a template's plugins to enable CodeCov coverage reports.
gitignore::Vector{AbstractString}
src::Nullable{AbstractString}
dest::AbstractString
badges::Vector{AbstractString}
badges::Vector{Vector{AbstractString}}
view::Dict{String, Any}
function CodeCov(; config_file::Union{AbstractString, Void}="")
@ -26,7 +28,13 @@ Add CodeCov to a template's plugins to enable CodeCov coverage reports.
["*.jl.cov", "*.jl.*.cov", "*.jl.mem"],
config_file,
".codecov.yml",
["[![CodeCov](https://codecov.io/gh/{{USER}}/{{PKGNAME}}.jl/branch/master/graph/badge.svg)](https://codecov.io/gh/{{USER}}/{{PKGNAME}}.jl)"],
[
[
"CodeCov",
"https://codecov.io/gh/{{USER}}/{{PKGNAME}}.jl/branch/master/graph/badge.svg",
"https://codecov.io/gh/{{USER}}/{{PKGNAME}}.jl",
],
],
Dict{String, Any}(),
)
end

View File

@ -1,7 +1,9 @@
"""
Coveralls(; config_file::Union{AbstractString, Void}="") -> Coveralls
Add Coveralls to a template's plugins to enable Coveralls coverage reports.
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, Void}=nothing`: Path to a custom `.coveralls.yml`.
@ -11,7 +13,7 @@ Add Coveralls to a template's plugins to enable Coveralls coverage reports.
gitignore::Vector{AbstractString}
src::Nullable{AbstractString}
dest::AbstractString
badges::Vector{AbstractString}
badges::Vector{Vector{AbstractString}}
view::Dict{String, Any}
function Coveralls(; config_file::Union{AbstractString, Void}=nothing)
@ -22,7 +24,13 @@ Add Coveralls to a template's plugins to enable Coveralls coverage reports.
["*.jl.cov", "*.jl.*.cov", "*.jl.mem"],
config_file,
".coveralls.yml",
["[![Coveralls](https://coveralls.io/repos/github/{{USER}}/{{PKGNAME}}.jl/badge.svg?branch=master)](https://coveralls.io/github/{{USER}}/{{PKGNAME}}.jl?branch=master)"],
[
[
"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

View File

@ -1,19 +1,9 @@
"""
Add a Documenter subtype to a template's plugins to add support for
[Documenter.jl](https://github.com/JuliaDocs/Documenter.jl).
"""
Add a `Documenter` subtype to a template's plugins to add support for documentation
generation via [Documenter.jl](https://github.com/JuliaDocs/Documenter.jl).
"""
abstract type Documenter <: CustomPlugin end
"""
gen_plugin(plugin::Documenter, template::Template, pkg_name::AbstractString) -> Void
Generate the "docs" directory with files common to all Documenter subtypes.
# Arguments
* `plugin::Documenter`: Plugin whose files are being generated.
* `template::Template`: Template configuration and plugins.
* `pkg_name::AbstractString`: Name of the package.
"""
function gen_plugin(plugin::Documenter, template::Template, pkg_name::AbstractString)
if Pkg.installed("Documenter") == nothing
info("Adding Documenter.jl")

View File

@ -1,7 +1,9 @@
"""
GitHubPages(; assets::Vector{AbstractString}=String[]) -> GitHubPages
Add GitHubPages to a template's plugins to add Documenter.jl support via GitHub Pages.
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{String}=String[]`: Array of paths to Documenter asset files.
@ -23,8 +25,8 @@ end
function badges(_::GitHubPages, user::AbstractString, pkg_name::AbstractString)
return [
"[![Stable](https://img.shields.io/badge/docs-stable-blue.svg)](https://$user.github.io/$pkg_name.jl/stable)"
"[![Latest](https://img.shields.io/badge/docs-latest-blue.svg)](https://$user.github.io/$pkg_name.jl/latest)"
badge("Stable", "https://img.shields.io/badge/docs-stable-blue.svg", "https://$user.github.io/$pkg_name.jl/stable")
badge("Latest", "https://img.shields.io/badge/docs-latest-blue.svg", "https://$user.github.io/$pkg_name.jl/latest")
]
end

View File

@ -1,7 +1,8 @@
"""
TravisCI(; config_file::Union{AbstractString, Void}="") -> GenericPlugin
Add TravisCI to a template's plugins to add Travis CI support.
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, Void}=""`: Path to a custom `.travis.yml`.
@ -11,7 +12,7 @@ Add TravisCI to a template's plugins to add Travis CI support.
gitignore::Vector{AbstractString}
src::Nullable{AbstractString}
dest::AbstractString
badges::Vector{AbstractString}
badges::Vector{Vector{AbstractString}}
view::Dict{String, Any}
function TravisCI(; config_file::Union{AbstractString, Void}="")
@ -26,7 +27,13 @@ Add TravisCI to a template's plugins to add Travis CI support.
[],
config_file,
".travis.yml",
["[![Build Status](https://travis-ci.org/{{USER}}/{{PKGNAME}}.jl.svg?branch=master)](https://travis-ci.org/{{USER}}/{{PKGNAME}}.jl)"],
[
[
"Build Status",
"https://travis-ci.org/{{USER}}/{{PKGNAME}}.jl.svg?branch=master",
"https://travis-ci.org/{{USER}}/{{PKGNAME}}.jl",
],
],
Dict{String, Any}(),
)
end

View File

@ -3,7 +3,7 @@ using Base.Test
import PkgTemplates: badges, version_floor, substitute, read_license, gen_file, gen_readme,
gen_tests, gen_license, gen_require, gen_entrypoint, gen_gitignore, gen_plugin,
show_license, LICENSES, LICENSE_DIR
show_license, LICENSES, LICENSE_DIR, GenericPlugin
mktempdir() do temp_dir
withenv("JULIA_PKGDIR" => temp_dir) do

View File

@ -1,3 +1,12 @@
struct Foo <: GenericPlugin
gitignore::Vector{AbstractString}
src::Nullable{AbstractString}
dest::AbstractString
badges::Vector{Vector{AbstractString}}
view::Dict{String, Any}
Foo() = new([], @__FILE__, "", [["foo", "bar", "baz"]], Dict{String, Any}())
end
const git_config = Dict(
"user.name" => "Tester McTestFace",
"user.email" => "email@web.site",
@ -11,6 +20,7 @@ template_text = """
VERSION: {{VERSION}}}
{{#DOCUMENTER}}Documenter{{/DOCUMENTER}}
{{#CODECOV}}CodeCov{{/CODECOV}}
{{#CODECOV}}Coveralls{{/CODECOV}}
{{#AFTER}}After{{/AFTER}}
{{#OTHER}}Other{{/OTHER}}
"""
@ -70,11 +80,15 @@ write(test_file, template_text)
t = Template(;
user="invenia",
plugins = [GitHubPages(), TravisCI(), AppVeyor(), CodeCov()],
plugins = [GitHubPages(), TravisCI(), AppVeyor(), CodeCov(), Coveralls()],
)
rm(t.temp_dir; recursive=true)
@test Set(keys(t.plugins)) == Set([GitHubPages, TravisCI, AppVeyor, CodeCov])
@test Set(values(t.plugins)) == Set([GitHubPages(), TravisCI(), AppVeyor(), CodeCov()])
@test Set(keys(t.plugins)) == Set(
[GitHubPages, TravisCI, AppVeyor, CodeCov, Coveralls]
)
@test Set(values(t.plugins)) == Set(
[GitHubPages(), TravisCI(), AppVeyor(), CodeCov(), Coveralls()]
)
@test_warn r".+" t = Template(;
user="invenia",
@ -97,7 +111,11 @@ end
@test isempty(p.gitignore)
@test get(p.src) == joinpath(PkgTemplates.DEFAULTS_DIR, "appveyor.yml")
@test p.dest == ".appveyor.yml"
@test p.badges == ["[![Build Status](https://ci.appveyor.com/api/projects/status/github/{{USER}}/{{PKGNAME}}.jl?svg=true)](https://ci.appveyor.com/project/{{USER}}/{{PKGNAME}}-jl)"]
@test p.badges == [[
"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_throws NullException get(p.src)
@ -109,7 +127,11 @@ end
@test isempty(p.gitignore)
@test get(p.src) == joinpath(PkgTemplates.DEFAULTS_DIR, "travis.yml")
@test p.dest == ".travis.yml"
@test p.badges == ["[![Build Status](https://travis-ci.org/{{USER}}/{{PKGNAME}}.jl.svg?branch=master)](https://travis-ci.org/{{USER}}/{{PKGNAME}}.jl)"]
@test p.badges == [[
"Build Status",
"https://travis-ci.org/{{USER}}/{{PKGNAME}}.jl.svg?branch=master",
"https://travis-ci.org/{{USER}}/{{PKGNAME}}.jl",
]]
@test isempty(p.view)
p = TravisCI(; config_file=nothing)
@test_throws NullException get(p.src)
@ -121,7 +143,11 @@ end
@test p.gitignore == ["*.jl.cov", "*.jl.*.cov", "*.jl.mem"]
@test get(p.src) == joinpath(PkgTemplates.DEFAULTS_DIR, "codecov.yml")
@test p.dest == ".codecov.yml"
@test p.badges == ["[![CodeCov](https://codecov.io/gh/{{USER}}/{{PKGNAME}}.jl/branch/master/graph/badge.svg)](https://codecov.io/gh/{{USER}}/{{PKGNAME}}.jl)"]
@test p.badges == [[
"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_throws NullException get(p.src)
@ -133,7 +159,11 @@ end
@test p.gitignore == ["*.jl.cov", "*.jl.*.cov", "*.jl.mem"]
@test_throws NullException get(p.src)
@test p.dest == ".coveralls.yml"
@test p.badges == ["[![Coveralls](https://coveralls.io/repos/github/{{USER}}/{{PKGNAME}}.jl/badge.svg?branch=master)](https://coveralls.io/github/{{USER}}/{{PKGNAME}}.jl?branch=master)"]
@test p.badges == [[
"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_throws NullException get(p.src)
@ -176,7 +206,7 @@ end
user="invenia",
license="MPL",
git_config=git_config,
plugins=[TravisCI(), CodeCov(), GitHubPages(), AppVeyor()],
plugins=[Coveralls(), TravisCI(), CodeCov(), GitHubPages(), AppVeyor()],
)
pkg_dir = joinpath(t.temp_dir, test_pkg)
@ -198,7 +228,14 @@ end
@test search(readme, "github.io").start <
search(readme, "travis").start <
search(readme, "appveyor").start <
search(readme, "codecov").start
search(readme, "codecov").start <
search(readme, "coveralls").start
# Plugins with badges but not in BADGE_ORDER should appear at the far right side.
t.plugins[Foo] = Foo()
gen_readme(test_pkg, t)
readme = readchomp(joinpath(pkg_dir, "README.md"))
rm(joinpath(pkg_dir, "README.md"))
@test search(readme, "coveralls").start < search(readme, "baz").start
@test gen_gitignore(test_pkg, t) == [".gitignore"]
@test isfile(joinpath(pkg_dir, ".gitignore"))
@ -326,6 +363,38 @@ end
p = TravisCI()
@test gen_plugin(p, t, test_pkg) == [".travis.yml"]
@test isfile(joinpath(pkg_dir, ".travis.yml"))
travis = readstring(joinpath(pkg_dir, ".travis.yml"))
@test !contains(travis, "after_success")
@test !contains(travis, "Codecov.submit")
@test !contains(travis, "Coveralls.submit")
@test !contains(travis, "Pkg.add(\"Documenter\")")
rm(joinpath(pkg_dir, ".travis.yml"))
t.plugins[CodeCov] = CodeCov()
gen_plugin(p, t, test_pkg)
delete!(t.plugins, CodeCov)
travis = readstring(joinpath(pkg_dir, ".travis.yml"))
@test contains(travis, "after_success")
@test contains(travis, "Codecov.submit")
@test !contains(travis, "Coveralls.submit")
@test !contains(travis, "Pkg.add(\"Documenter\")")
rm(joinpath(pkg_dir, ".travis.yml"))
t.plugins[Coveralls] = Coveralls()
gen_plugin(p, t, test_pkg)
delete!(t.plugins, Coveralls)
travis = readstring(joinpath(pkg_dir, ".travis.yml"))
@test contains(travis, "after_success")
@test contains(travis, "Coveralls.submit")
@test !contains(travis, "Codecov.submit")
@test !contains(travis, "Pkg.add(\"Documenter\")")
rm(joinpath(pkg_dir, ".travis.yml"))
t.plugins[GitHubPages] = GitHubPages()
gen_plugin(p, t, test_pkg)
delete!(t.plugins, GitHubPages)
travis = readstring(joinpath(pkg_dir, ".travis.yml"))
@test contains(travis, "after_success")
@test contains(travis, "Pkg.add(\"Documenter\")")
@test !contains(travis, "Codecov.submit")
@test !contains(travis, "Coveralls.submit")
rm(joinpath(pkg_dir, ".travis.yml"))
p = TravisCI(; config_file=nothing)
@test isempty(gen_plugin(p, t, test_pkg))
@ -335,6 +404,26 @@ end
p = AppVeyor()
@test gen_plugin(p, t, test_pkg) == [".appveyor.yml"]
@test isfile(joinpath(pkg_dir, ".appveyor.yml"))
appveyor = readstring(joinpath(pkg_dir, ".appveyor.yml"))
@test !contains(appveyor, "after_script")
@test !contains(appveyor, "Codecov.submit")
@test !contains(appveyor, "Coveralls.submit")
rm(joinpath(pkg_dir, ".appveyor.yml"))
t.plugins[CodeCov] = CodeCov()
gen_plugin(p, t, test_pkg)
delete!(t.plugins, CodeCov)
appveyor = readstring(joinpath(pkg_dir, ".appveyor.yml"))
@test contains(appveyor, "after_script")
@test contains(appveyor, "Codecov.submit")
@test !contains(appveyor, "Coveralls.submit")
rm(joinpath(pkg_dir, ".appveyor.yml"))
t.plugins[Coveralls] = Coveralls()
gen_plugin(p, t, test_pkg)
delete!(t.plugins, Coveralls)
appveyor = readstring(joinpath(pkg_dir, ".appveyor.yml"))
@test contains(appveyor, "after_script")
@test contains(appveyor, "Coveralls.submit")
@test !contains(appveyor, "Codecov.submit")
rm(joinpath(pkg_dir, ".appveyor.yml"))
p = AppVeyor(; config_file=nothing)
@test isempty(gen_plugin(p, t, test_pkg))
@ -393,12 +482,25 @@ end
end
@testset "Mustache substitution" begin
view = Dict{String, Any}()
text = substitute(template_text, view)
@test !contains(text, "PKGNAME: $test_pkg")
@test !contains(text, "Documenter")
@test !contains(text, "CodeCov")
@test !contains(text, "Coveralls")
@test !contains(text, "After")
@test !contains(text, "Other")
view["PKGNAME"] = test_pkg
view["OTHER"] = true
text = substitute(template_text, view)
@test contains(text, "PKGNAME: $test_pkg")
@test contains(text, "Other")
t = Template(; user="invenia")
rm(t.temp_dir; recursive=true)
p = Coveralls()
view = Dict{String, Any}("OTHER" => false)
view["OTHER"] = false
text = substitute(template_text, t, test_pkg; view=view)
text = substitute(template_text, t; view=view)
@test contains(text, "PKGNAME: $test_pkg")
@test contains(text, "VERSION: $(t.julia_version.major).$(t.julia_version.minor)")
@test !contains(text, "Documenter")
@ -406,25 +508,25 @@ end
@test !contains(text, "Other")
t.plugins[GitHubPages] = GitHubPages()
text = substitute(template_text, t, test_pkg; view=view)
text = substitute(template_text, t; view=view)
@test contains(text, "Documenter")
@test contains(text, "After")
empty!(t.plugins)
t.plugins[CodeCov] = CodeCov()
text = substitute(template_text, t, test_pkg; view=view)
text = substitute(template_text, t; view=view)
@test contains(text, "CodeCov")
@test contains(text, "After")
empty!(t.plugins)
t.plugins[CodeCov] = Coveralls()
text = substitute(template_text, t; view=view)
@test contains(text, "Coveralls")
@test contains(text, "After")
empty!(t.plugins)
view["OTHER"] = true
text = substitute(template_text, t, test_pkg; view=view)
@test contains(text, "Other")
text = substitute(template_text, p, test_pkg)
@test contains(text, "PKGNAME: $test_pkg")
text = substitute(template_text, p, test_pkg; view=view)
text = substitute(template_text, t; view=view)
@test contains(text, "Other")
end