PkgTemplates.jl/src/template.jl

228 lines
8.6 KiB
Julia
Raw Normal View History

2017-08-11 22:18:09 +00:00
"""
Template(; kwargs...) -> Template
2017-08-21 19:53:00 +00:00
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.
2017-08-11 22:18:09 +00:00
# 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
2017-08-18 07:08:11 +00:00
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.
2017-08-23 17:50:52 +00:00
* `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=$VERSION`: Minimum allowed Julia version.
* `ssh::Bool=false`: Whether or not to use SSH for the git remote. If `false` HTTPS will be used.
2018-11-07 20:34:28 +00:00
* `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.
2017-08-11 22:18:09 +00:00
"""
struct Template
user::String
host::String
license::String
authors::String
dir::String
2017-08-11 22:18:09 +00:00
julia_version::VersionNumber
ssh::Bool
2018-11-07 20:34:28 +00:00
manifest::Bool
plugins::Dict{DataType, <:Plugin}
2017-08-11 22:18:09 +00:00
function Template(;
user::AbstractString="",
host::AbstractString="https://github.com",
license::AbstractString="MIT",
2017-08-23 17:50:52 +00:00
authors::Union{AbstractString, Vector{<:AbstractString}}="",
dir::AbstractString=Pkg.devdir(),
2017-08-11 22:18:09 +00:00
julia_version::VersionNumber=VERSION,
ssh::Bool=false,
2018-11-07 20:34:28 +00:00
manifest::Bool=false,
2017-08-23 17:50:52 +00:00
plugins::Vector{<:Plugin}=Plugin[],
git::Bool=true,
2017-08-23 17:50:52 +00:00
)
# 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
2017-08-23 17:50:52 +00:00
if !isempty(license) && !isfile(joinpath(LICENSE_DIR, license))
throw(ArgumentError("License '$license' is not available"))
2017-08-11 22:18:09 +00:00
end
# If no author was set, look for one in the global git config.
if isempty(authors)
authors = LibGit2.getconfig("user.name", "")
2017-08-23 17:50:52 +00:00
elseif isa(authors, Vector)
2017-08-11 22:18:09 +00:00
authors = join(authors, ", ")
end
2017-08-25 05:09:49 +00:00
dir = abspath(expanduser(dir))
2017-08-14 19:15:53 +00:00
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"
2017-08-14 19:15:53 +00:00
end
2017-08-11 22:18:09 +00:00
2018-11-07 20:34:28 +00:00
new(user, host, license, authors, dir, julia_version, ssh, manifest, plugin_dict)
2017-08-11 22:18:09 +00:00
end
end
2017-08-21 19:53:00 +00:00
2018-09-19 21:23:11 +00:00
function Base.show(io::IO, t::Template)
maybe(s::String) = isempty(s) ? "None" : s
2017-12-01 17:33:57 +00:00
spc = " "
println(io, "Template:")
println(io, spc, "→ User: ", maybe(t.user))
println(io, spc, "→ Host: ", maybe(t.host))
2017-12-07 16:43:40 +00:00
print(io, spc, "→ License: ")
2017-12-07 16:43:40 +00:00
if isempty(t.license)
println(io, "None")
else
println(io, t.license, " ($(t.authors) ", year(today()), ")")
2017-12-01 17:33:57 +00:00
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, "→ Commit Manifest.toml: ", t.manifest ? "Yes" : "No")
2017-12-01 17:33:57 +00:00
print(io, spc, "→ Plugins:")
2017-12-01 17:33:57 +00:00
if isempty(t.plugins)
2017-12-05 20:03:57 +00:00
print(io, " None")
2017-12-01 17:33:57 +00:00
else
for plugin in sort(collect(values(t.plugins)); by=string)
println(io)
buf = IOBuffer()
show(buf, plugin)
print(io, spc^2, "")
2017-12-01 17:33:57 +00:00
print(io, join(split(String(take!(buf)), "\n"), "\n$(spc^2)"))
end
end
end
2017-08-21 19:53:00 +00:00
"""
2017-08-25 06:25:26 +00:00
interactive_template(; fast::Bool=false) -> Template
2017-08-21 19:53:00 +00:00
Interactively create a [`Template`](@ref). If `fast` is set, defaults will be assumed for
all values except username and plugins.
2017-08-21 19:53:00 +00:00
"""
function interactive_template(; git::Bool=true, fast::Bool=false)
@info "Default values are shown in [brackets]"
2017-08-21 19:53:00 +00:00
# Getting the leaf types in a separate thread eliminates an awkward wait after
# "Select plugins" is printed.
plugin_types = @async leaves(Plugin)
2017-08-21 19:53:00 +00:00
kwargs = Dict{Symbol, Any}()
default_user = LibGit2.getconfig("github.user", "")
print("Username [", isempty(default_user) ? "REQUIRED" : default_user, "]: ")
2017-08-21 19:53:00 +00:00
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.
2017-08-21 19:53:00 +00:00
else
default_host = "github.com"
2018-11-07 20:34:28 +00:00
print("Code hosting service [$default_host]: ")
host = readline()
isempty(host) ? default_host : host
2017-08-21 19:53:00 +00:00
end
kwargs[:license] = if fast
"MIT"
else
2018-11-07 20:34:28 +00:00
println("License:")
io = IOBuffer()
2017-08-23 17:50:52 +00:00
available_licenses(io)
licenses = ["" => "", collect(LICENSES)...]
menu = RadioMenu(String["None", split(String(take!(io)), "\n")...])
2017-08-23 17:50:52 +00:00
# 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
2018-11-07 20:34:28 +00:00
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()
2018-11-07 20:34:28 +00:00
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
2017-08-21 19:53:00 +00:00
kwargs[:ssh] = if fast || !git
false
else
print("Set remote to SSH? [no]: ")
2018-11-07 20:34:28 +00:00
uppercase(readline()) in ["Y", "YES", "T", "TRUE"]
end
2018-11-07 20:34:28 +00:00
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.
2018-09-17 21:43:12 +00:00
plugin_types = filter(t -> hasmethod(interactive, (Type{t},)), fetch(plugin_types))
2017-08-21 19:53:00 +00:00
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)))
2017-08-21 19:53:00 +00:00
return Template(; git=git, kwargs...)
2017-08-21 19:53:00 +00:00
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"