Many misc changes and bug fixes

This commit is contained in:
Chris de Graaf 2019-08-29 23:04:11 +07:00
parent 4fa7d9a62b
commit 810ac5ab28
No known key found for this signature in database
GPG Key ID: 150FFDD9B0073C7B
7 changed files with 152 additions and 112 deletions

View File

@ -2,17 +2,19 @@
environment:
matrix:
{{#VERSIONS}}
- julia_version: {{.}}
- julia_version: {{.}}
{{/VERSIONS}}
platform:
{{#PLATFORMS}}
- {{.}}
{{/PLATFORMS}}
{{#HAS_NIGHTLY}}
{{#HAS_ALLOW_FAILURES}}
matrix:
allow_failures:
- julia_version: nightly
{{/HAS_NIGHTLY}}
{{#ALLOW_FAILURES}}
- julia_version: {{.}}
{{/ALLOW_FAILURES}}
{{/HAS_ALLOW_FAILURES}}
branches:
only:
- master

View File

@ -97,6 +97,7 @@ Perform any work associated with a plugin.
gen_plugin(::Plugin, ::Template, ::AbstractString) = nothing
function gen_plugin(p::BasicPlugin, t::Template, pkg_dir::AbstractString)
source(p) === nothing && return
pkg = basename(pkg_dir)
path = joinpath(pkg_dir, destination(p))
text = render_plugin(p, t, pkg)
@ -104,10 +105,8 @@ function gen_plugin(p::BasicPlugin, t::Template, pkg_dir::AbstractString)
end
function render_plugin(p::BasicPlugin, t::Template, pkg::AbstractString)
src = source(p)
src === nothing && return
# TODO template rendering code
return render_file(src, combined_view(p, t, pkg), tags(p))
return render_file(source(p), combined_view(p, t, pkg), tags(p))
end
function combined_view(p::Plugin, t::Template, pkg::AbstractString)
@ -123,15 +122,16 @@ Trailing whitespace is removed, and the file will end with a newline.
"""
function gen_file(file::AbstractString, text::AbstractString)
mkpath(dirname(file))
text = join(map(rstrip, split(text, "\n")), "\n")
endswith(text , "\n") || (text *= "\n")
text = strip(join(map(rstrip, split(text, "\n")), "\n")) * "\n"
write(file, text)
end
# Render text from a file.
render_file(file::AbstractString, view, tags) = render_text(read(file, String), view, tags)
function render_file(file::AbstractString, view::Dict{<:AbstractString}, tags)
render_text(read(file, String), view, tags)
end
# Render text, using Mustache's templating system. HTML escaping is disabled.
# Render text using Mustache's templating system. HTML escaping is disabled.
function render_text(text::AbstractString, view::Dict{<:AbstractString}, tags=nothing)
saved = copy(entityMap)
empty!(entityMap)
@ -146,7 +146,7 @@ function render_text(text::AbstractString, view::Dict{<:AbstractString}, tags=no
end
end
include(joinpath("plugins", "essentials.jl"))
include(joinpath("plugins", "defaults.jl"))
include(joinpath("plugins", "coverage.jl"))
include(joinpath("plugins", "ci.jl"))
include(joinpath("plugins", "citation.jl"))

View File

@ -1,15 +1,17 @@
const DEFAULT_CI_VERSIONS = ["1.0", "nightly"]
# TODO: Update the allowed failures as new versions come out.
const VersionsOrStrings = Vector{Union{VersionNumber, String}}
const ALLOWED_FAILURES = ["1.3", "nightly"]
const DEFAULT_CI_VERSIONS = VersionsOrStrings([VERSION, "1.0", "nightly"])
format_version(v::VersionNumber) = "$(v.major).$(v.minor)"
format_version(v::AbstractString) = string(v)
function collect_versions(versions::Vector, t::Template)
return unique(sort([versions; format_version(t.julia_version)]; by=string))
function collect_versions(t::Template, versions::Vector)
vs = [format_version(t.julia_version); map(format_version, versions)]
return unique!(sort!(vs))
end
abstract type CI <: Plugin end
@with_kw struct TravisCI <: CI
@with_kw struct TravisCI <: BasicPlugin
file::String = default_file("travis.yml")
linux::Bool = true
osx::Bool = true
@ -34,9 +36,9 @@ function view(p::TravisCI, t::Template, pkg::AbstractString)
p.osx && push!(os, "osx")
p.windows && push!(os, "windows")
# TODO: Update the allowed failures as new versions come out.
versions = collect_versions(p.extra_versions, t)
allow_failures = filter(v -> v in versions, ["1.3", "nightly"])
versions = collect_versions(t, p.extra_versions)
allow_failures = filter(in(versions), ALLOWED_FAILURES)
x86 = Dict{String, String}[]
if p.x86
@ -50,18 +52,20 @@ function view(p::TravisCI, t::Template, pkg::AbstractString)
"ALLOW_FAILURES" => allow_failures,
"HAS_ALLOW_FAILURES" => !isempty(allow_failures),
"HAS_CODECOV" => hasplugin(t, Codecov),
"HAS_COVERAGE" => p.coverage && hasplugin(t, Coverage),
"HAS_COVERAGE" => p.coverage && hasplugin(t, is_coverage),
"HAS_COVERALLS" => hasplugin(t, Coveralls),
"HAS_DOCUMENTER" => hasplugin(t, Documenter{TravisCI}),
"HAS_JOBS" => p.x86 || hasplugin(t, Documenter{TravisCI}),
"OS" => os,
"PKG" => pkg,
"USER" => t.user,
"VERSION" => format_version(t.julia_version),
"VERSIONS" => versions,
"X86" => x86,
)
end
@with_kw struct AppVeyor <: CI
@with_kw struct AppVeyor <: BasicPlugin
file::String = default_file("appveyor.yml")
x86::Bool = false
coverage::Bool = true
@ -77,19 +81,25 @@ badges(::AppVeyor) = Badge(
"https://ci.appveyor.com/project/{{USER}}/{{PKG}}-jl",
)
function view(p::AppVeyor, t::Template, ::AbstractString)
function view(p::AppVeyor, t::Template, pkg::AbstractString)
platforms = ["x64"]
t.x86 && push!(platforms, "x86")
p.x86 && push!(platforms, "x86")
versions = collect_versions(t, p.extra_versions)
allow_failures = filter(in(versions), ALLOWED_FAILURES)
return Dict(
"HAS_CODECOV" => t.coverage && hasplugin(t, Codecov),
"HAS_NIGHTLY" => "nightly" in versions,
"ALLOW_FAILURES" => allow_failures,
"HAS_ALLOW_FAILURES" => !isempty(allow_failures),
"HAS_CODECOV" => p.coverage && hasplugin(t, Codecov),
"PKG" => pkg,
"PLATFORMS" => os,
"VERSIONS" => collect_versions(p.extra_versions, t),
"PLATFORMS" => platforms,
"USER" => t.user,
"VERSIONS" => versions,
)
end
@with_kw struct CirrusCI <: CI
@with_kw struct CirrusCI <: BasicPlugin
file::String = default_file("cirrus.yml")
image::String = "freebsd-12-0-release-amd64"
coverage::Bool = true
@ -109,14 +119,15 @@ 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),
"HAS_COVERAGE" => p.coverage && hasplugin(t, is_coverage),
"IMAGE" => p.image,
"PKG" => pkg,
"VERSIONS" => collect_versions(p.extra_versions, t),
"USER" => t.user,
"VERSIONS" => collect_versions(t, p.extra_versions),
)
end
@with_kw struct GitLabCI <: CI
@with_kw struct GitLabCI <: BasicPlugin
file::String
documentation::Bool = true
coverage::Bool = true
@ -147,7 +158,11 @@ function view(p::GitLabCI, t::Template, ::AbstractString)
"HAS_COVERAGE" => p.coverage,
"HAS_DOCUMENTER" => hasplugin(t, Documenter{GitLabCI}),
"PKG" => pkg,
"USER" => t.user,
"VERSION" => format_version(t.julia_version),
"VERSIONS" => collect_versions(p.extra_versions, t),
"VERSIONS" => collect_versions(t, p.extra_versions),
)
end
is_ci(::Type) = false
is_ci(::Type{<:Union{AppVeyor, TravisCI, CirrusCI, GitLabCI}}) = true

View File

@ -1,10 +1,6 @@
abstract type Coverage <: Plugin end
const COVERAGE_GITIGNORE = ["*.jl.cov", "*.jl.*.cov", "*.jl.mem"]
gitignore(::Coverage) = COVERAGE_GITIGNORE
@with_kw struct Codecov <: Coverage
@with_kw struct Codecov <: BasicPlugin
file::Union{String, Nothing} = nothing
end
@ -17,7 +13,7 @@ badges(::Codecov) = Badge(
"https://codecov.io/gh/{{USER}}/{{PKG}}.jl",
)
@with_kw struct Coveralls <: Coverage
@with_kw struct Coveralls <: BasicPlugin
file::Union{String, Nothing} = nothing
end
@ -29,3 +25,8 @@ badges(::Coveralls) = Badge(
"https://coveralls.io/repos/github/{{USER}}/{{PKG}}.jl/badge.svg?branch=master",
"https://coveralls.io/github/{{USER}}/{{PKG}}.jl?branch=master",
)
gitignore(::Union{Codecov, Coveralls}) = COVERAGE_GITIGNORE
is_coverage(::Type) = false
is_coverage(::Type{<:Union{Codecov, Coveralls}}) = true

View File

@ -76,10 +76,16 @@ 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
@with_kw struct Gitignore <: Plugin
ds_store::Bool = true
dev::Bool = true
end
function render_plugin(p::Gitignore, t::Template)
entries = mapreduce(gitignore, append!, values(t.plugins); init=[".DS_Store", "/dev/"])
init = String[]
p.ds_store && push!(init, ".DS_Store")
p.dev && push!(init, "/dev/")
entries = mapreduce(gitignore, append!, values(t.plugins); init=init)
# Only ignore manifests at the repo root.
t.manifest || "Manifest.toml" in entries || push!(entries, "/Manifest.toml")
unique!(sort!(entries))
@ -110,6 +116,7 @@ function gen_plugin(p::Tests, t::Template, pkg_dir::AbstractString)
open(io -> TOML.print(io, toml), path, "w")
# Generate the manifest.
# This also ensures that keys in Project.toml are sorted properly.
touch(joinpath(pkg_dir, "Manifest.toml")) # File must exist to be modified by Pkg.
proj = current_project()
try

View File

@ -5,16 +5,18 @@ const DOCUMENTER_UUID = "e30172f5-a6a5-5a46-863b-614d45cd2de4"
assets::Vector{<:AbstractString}=String[],
makedocs_kwargs::Dict{Symbol}=Dict(),
canonical_url::Union{Function, Nothing}=nothing,
make_jl::AbstractString="$(contractuser(default_file("make.jl")))",
index_md::AbstractString="$(contractuser(default_file("index.md")))",
) -> Documenter{T}
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.
## Keyword Arguments
todo
TODO
- `assets::Vector{<:AbstractString}=String[]`:
- `makedocs_kwargs::Dict{Symbol}=Dict{Symbol, Any}()`:
- `canonical_url::Union{Function, Nothing}=nothing`:`
- `canonical_url::Union{Function, Nothing}=nothing`:
- `index_md::AbstractString`
- `make_jl::AbstractString`
@ -29,7 +31,7 @@ struct Documenter{T<:Union{TravisCI, GitLabCI, Nothing}} <: Plugin
make_jl::String
index_md::String
# Can't use @kwdef due to some weird precompilation issues.
# Can't use @with_kw due to some weird precompilation issues.
function Documenter{T}(
assets::Vector{<:AbstractString}=String[],
makedocs_kwargs::Dict{Symbol}=Dict{Symbol, Any}(),
@ -72,6 +74,7 @@ view(p::Documenter, t::Template, pkg::AbstractString) = Dict(
"MAKEDOCS_KWARGS" => map((k, v) -> k => repr(v), collect(p.makedocs_kwargs)),
"PKG" => pkg,
"REPO" => "https://$(t.host)/$(t.user)/$pkg.jl",
"USER" => t.user,
)
function view(p::Documenter{TravisCI}, t::Template, pkg::AbstractString)
@ -80,11 +83,20 @@ function view(p::Documenter{TravisCI}, t::Template, pkg::AbstractString)
end
function gen_plugin(p::Documenter, t::Template, pkg_dir::AbstractString)
# TODO: gen make.jl
# TODO: gen index.md
docs_dir = joinpath(pkg_dir, "docs")
# Generate files.
make = render_file(p.make_jl, combined_view(p, t, pkg), tags(p))
gen_file(joinpath(docs_dir, "make.jl"), make)
index = render_file(p.index_md, combined_view(p, t, pkg), tags(p))
gen_file(joinpath(docs_dir, "src", "index.md"), index)
# Copy over any assets.
assets_dir = joinpath(docs_dir, "src", "assets")
isempty(p.assets) || mkpath(assets_dir)
foreach(a -> cp(a, joinpath(assets_dir, basename(a))), p.assets)
# Create the documentation project.
docs_dir = joinpath(pkg_dir, "docs")
proj = current_project()
try
Pkg.activate(docs_dir)
@ -92,11 +104,6 @@ function gen_plugin(p::Documenter, t::Template, pkg_dir::AbstractString)
finally
proj === nothing ? Pkg.activate() : Pkg.activate(proj)
end
# 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 interactive(::Type{Documenter{T}}) where T

View File

@ -1,4 +1,14 @@
const DEFAULT_USER = LibGit2.getconfig("github.user", "")
const DEFAULT_VERSION = VersionNumber(VERSION.major)
const DEFAULT_AUTHORS = let
name = LibGit2.getconfig("user.name", "")
email = LibGit2.getconfig("user.email", "")
if isempty(name)
""
else
isempty(email) ? name : "$name <$email>"
end
end
"""
Template(; interactive::Bool=false, kwargs...) -> Template
@ -6,52 +16,56 @@ const DEFAULT_VERSION = VersionNumber(VERSION.major)
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.
- `authors::Union{AbstractString, Vector{<:AbstractString}}=""`: Names that appear on the license.
### User Options
- `user::AbstractString="$DEFAULT_USER"`: GitHub (or other code hosting service) username.
The default value comes from the global Git config (`github.user`).
If no value is obtained, an `ArgumentError` is thrown.
- `authors::Union{AbstractString, Vector{<:AbstractString}}="$DEFAULT_AUTHORS"`: Package authors.
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.
Like `user`, it takes its default value from the global Git config (`user.name` and `user.email`).
### Package Options
- `host::AbstractString="github.com"`: URL to the code hosting service where packages will reside.
- `dir::AbstractString="$(contractuser(Pkg.devdir()))"`: Directory to place packages in.
- `julia_version::VersionNumber=$(repr(DEFAULT_VERSION))`: Minimum allowed Julia version.
- `develop::Bool=true`: Whether or not to `develop` new packages in the active environment.
### Git Options
- `git::Bool=true`: Whether or not to create a Git repository for new packages.
- `ssh::Bool=false`: Whether or not to use SSH for the Git remote.
If left unset, HTTPS will be used.
- `manifest::Bool=false`: Whether or not to commit the `Manifest.toml`.
- `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.
### Template Plugins
- `plugins::Vector{<:Plugin}=Plugin[]`: A list of [`Plugin`](@ref)s used by the template.
- `disabled_defaults::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 Usage
- `interactive::Bool=false`: When set, creates the template interactively, filling unset keywords with user input.
- `fast::Bool=false`: Skips prompts for any unsupplied keywords except `user` and `plugins`.
### Interactive Usage
- `interactive::Bool=false`: When set, the template is created interactively, filling unset keywords with user input.
- `fast::Bool=false`: Skips prompts for any unsupplied keywords except `user` and `plugins`, accepting default values.
"""
struct Template
user::String
host::String
authors::Vector{String}
dir::String
julia_version::VersionNumber
ssh::Bool
manifest::Bool
git::Bool
develop::Bool
dir::String
git::Bool
host::String
julia_version::VersionNumber
manifest::Bool
plugins::Dict{DataType, <:Plugin}
ssh::Bool
user::String
end
Template(; interactive::Bool=false, kwargs...) = make_template(Val(interactive); kwargs...)
Template(; interactive::Bool=false, kwargs...) = Template(Val(interactive); kwargs...)
# Non-interactive Template constructor.
function make_template(::Val{false}; kwargs...)
# Non-interactive constructor.
function Template(::Val{false}; kwargs...)
user = getkw(kwargs, :user)
if isempty(user)
throw(ArgumentError("No username found, set one with user=username"))
end
isempty(user) && throw(ArgumentError("No user set, please pass user=username"))
host = getkw(kwargs, :host)
host = URI(occursin("://", host) ? host : "https://$host").host
@ -63,49 +77,43 @@ function make_template(::Val{false}; kwargs...)
disabled = getkw(kwargs, :disabled_defaults)
defaults = [Readme, License, Tests, Gitignore]
plugins = map(T -> T(), filter(T -> !in(T, disabled), defaults))
plugins = map(T -> T(), filter(T -> !(T in disabled), defaults))
append!(plugins, getkw(kwargs, :plugins))
# This comprehensions resolves duplicate plugin types by overwriting,
# This comprehension 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),
dir,
getkw(kwargs, :git),
host,
getkw(kwargs, :julia_version),
getkw(kwargs, :manifest),
plugin_dict,
getkw(kwargs, :ssh),
user,
)
end
# Does the template have a plugin of this type? Subtypes count too.
hasplugin(t::Template, ::Type{T}) where T <: Plugin = any(U -> U <: T, keys(t.plugins))
# Does the template have a plugin that satisfies some predicate?
hasplugin(t::Template, f::Function) = any(f, keys(t.plugins))
hasplugin(t::Template, ::Type{T}) where T <: Plugin = hasplugin(t, U -> U <: T)
# Get a keyword, or compute some default value.
getkw(kwargs, k) = get(() -> defaultkw(k), kwargs, k)
# Default Template keyword values.
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{:authors}) = DEFAULT_AUTHORS
defaultkw(::Val{:develop}) = true
defaultkw(::Val{:plugins}) = Plugin[]
defaultkw(::Val{:dir}) = Pkg.devdir()
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
defaultkw(::Val{:git}) = true
defaultkw(::Val{:host}) = "github.com"
defaultkw(::Val{:julia_version}) = DEFAULT_VERSION
defaultkw(::Val{:manifest}) = false
defaultkw(::Val{:plugins}) = Plugin[]
defaultkw(::Val{:ssh}) = false
defaultkw(::Val{:user}) = DEFAULT_USER