PkgTemplates.jl/src/plugin.jl

247 lines
8.4 KiB
Julia
Raw Normal View History

"""
Generic plugins are plugins that add any number of patterns to the generated package's
`.gitignore`, and have at most one associated file to generate.
# Attributes
* `gitignore::Vector{AbstractString}`: Array of patterns to be added to the `.gitignore` of
generated packages that use this plugin.
2018-09-17 19:40:09 +00:00
* `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
2017-08-22 16:25:00 +00:00
implemented to call `interactive(; file="file.ext")`.
* `dest::AbstractString`: Path to the generated file, relative to the root of the generated
package repository.
2017-08-18 04:06:15 +00:00
* `badges::Vector{Badge}`: Array of [`Badge`](@ref)s containing information used to
create Markdown-formatted badges from the plugin. Entries will be run through
[`substitute`](@ref), so they may contain placeholder values.
* `view::Dict{String, Any}`: Additional substitutions to make in both the plugin's badges
and its associated file. See [`substitute`](@ref) for details.
# Example
```julia
@auto_hash_equals struct MyPlugin <: GenericPlugin
gitignore::Vector{AbstractString}
2018-09-17 19:40:09 +00:00
src::Union{AbstractString, Nothing}
dest::AbstractString
2017-08-18 04:06:15 +00:00
badges::Vector{Badge}
view::Dict{String, Any}
2018-09-17 19:34:57 +00:00
function MyPlugin(; config_file::Union{AbstractString, Nothing}="")
if config_file != nothing
2017-10-02 04:26:02 +00:00
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,
2017-09-22 11:07:27 +00:00
".my-plugin.toml",
[
2017-08-18 04:06:15 +00:00
Badge(
"My Plugin",
"https://myplugin.com/badge-{{YEAR}}.png",
"https://myplugin.com/{{USER}}/{{PKGNAME}}.jl",
2017-08-18 04:06:15 +00:00
),
],
2018-10-15 16:55:09 +00:00
Dict{String, Any}("YEAR" => year(today())),
)
end
end
2018-09-28 20:30:10 +00:00
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
2018-10-15 16:55:09 +00:00
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
2018-09-19 21:23:11 +00:00
function Base.show(io::IO, p::GenericPlugin)
2017-12-07 16:43:40 +00:00
spc = " "
println(io, "$(nameof(typeof(p))):")
2017-12-01 17:33:57 +00:00
cfg = if p.src === nothing
2017-12-01 17:33:57 +00:00
"None"
else
dirname(p.src) == DEFAULTS_DIR ? "Default" : p.src
2017-12-01 17:33:57 +00:00
end
2017-12-07 16:43:40 +00:00
println(io, "$spc→ Config file: $cfg")
2017-12-01 17:33:57 +00:00
n = length(p.gitignore)
s = n == 1 ? "" : "s"
2017-12-07 16:43:40 +00:00
print(io, "$spc→ $n gitignore entrie$s")
2017-12-01 17:33:57 +00:00
n > 0 && print(io, ": $(join(map(g -> "\"$g\"", p.gitignore), ", "))")
end
"""
Custom plugins are plugins whose behaviour does not follow the [`GenericPlugin`](@ref)
2017-08-22 16:25:00 +00:00
pattern. They can implement [`gen_plugin`](@ref), [`badges`](@ref), and
2018-09-28 21:20:23 +00:00
[`interactive`](@ref) in any way they choose, as long as they conform to the usual type
signature.
# 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)
2018-09-28 20:30:10 +00:00
function gen_plugin(p::MyPlugin, t::Template, pkg_name::AbstractString)
2018-09-28 21:20:23 +00:00
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.")
2018-09-28 21:20:23 +00:00
String[]
end
end
2018-09-28 21:20:23 +00:00
function badges(p::MyPlugin, user::AbstractString, pkg_name::AbstractString)
return if p.lucky
[
2017-08-18 04:06:15 +00:00
format(Badge(
"You got lucky!",
"https://myplugin.com/badge.png",
"https://myplugin.com/\$user/\$pkg_name.jl",
2017-08-18 04:06:15 +00:00
)),
]
else
2018-09-28 21:20:23 +00:00
String[]
end
end
end
2017-08-22 16:25:00 +00:00
2018-09-28 21:20:23 +00:00
interactive(:Type{MyPlugin}) = MyPlugin()
```
2017-08-22 16:25:00 +00:00
This plugin doesn't do much, but it demonstrates how [`gen_plugin`](@ref), [`badges`](@ref)
and [`interactive`](@ref) can be implemented using [`substitute`](@ref),
[`gen_file`](@ref), [`Badge`](@ref), and [`format`](@ref).
# Defining Template Files
Often, the contents of the config file that your plugin generates depends on variables like
the package name, the user's username, etc. Template files (which are stored in `defaults`)
can use [here](https://github.com/jverzani/Mustache.jl)'s syntax to define replacements.
"""
abstract type CustomPlugin <: Plugin end
2017-08-18 04:06:15 +00:00
"""
Badge(hover::AbstractString, image::AbstractString, link::AbstractString) -> Badge
A `Badge` contains the data necessary to generate a 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.
"""
@auto_hash_equals struct Badge
hover::AbstractString
image::AbstractString
link::AbstractString
end
"""
2018-09-26 20:55:24 +00:00
format(b::Badge) -> String
2017-08-18 04:06:15 +00:00
Return `badge`'s data formatted as a Markdown string.
"""
format(b::Badge) = "[![$(b.hover)]($(b.image))]($(b.link))"
"""
2018-09-28 20:30:10 +00:00
gen_plugin(p::Plugin, t::Template, pkg_name::AbstractString) -> Vector{String}
Generate any files associated with a plugin.
# Arguments
2018-09-28 20:30:10 +00:00
* `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.
"""
2018-09-28 20:30:10 +00:00
gen_plugin(::Plugin, ::Template, ::AbstractString) = String[]
2018-09-28 20:30:10 +00:00
function gen_plugin(p::GenericPlugin, t::Template, pkg_name::AbstractString)
if p.src === nothing
return String[]
end
text = substitute(
2018-09-28 20:30:10 +00:00
read(p.src, String),
t;
view=merge(Dict("PKGNAME" => pkg_name), p.view),
)
2018-09-28 20:30:10 +00:00
gen_file(joinpath(t.dir, pkg_name, p.dest), text)
return [p.dest]
end
"""
2018-09-28 20:30:10 +00:00
badges(p::Plugin, user::AbstractString, pkg_name::AbstractString) -> Vector{String}
Generate Markdown badges for the plugin.
# Arguments
2018-09-28 20:30:10 +00:00
* `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.
"""
2018-09-28 20:30:10 +00:00
badges(::Plugin, ::AbstractString, ::AbstractString) = String[]
2018-09-28 20:30:10 +00:00
function badges(p::GenericPlugin, user::AbstractString, pkg_name::AbstractString)
# Give higher priority to replacements defined in the plugin's view.
2018-09-28 20:30:10 +00:00
view = merge(Dict("USER" => user, "PKGNAME" => pkg_name), p.view)
return map(b -> substitute(format(b), view), p.badges)
end
2017-08-21 19:53:00 +00:00
"""
interactive(T::Type{<:Plugin}; file::Union{AbstractString, Nothing}="") -> Plugin
Interactively create a plugin of type `T`, where `file` is the plugin type's default
2018-09-28 20:30:10 +00:00
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))
2017-08-21 19:53:00 +00:00
# By default, we expect the default plugin file template for a plugin called
# "MyPlugin" to be called "myplugin.yml".
2018-09-28 20:30:10 +00:00
fn = file != nothing && isempty(file) ? "$(lowercase(name)).yml" : file
2017-08-21 19:53:00 +00:00
default_config_file = fn == nothing ? fn : joinpath(DEFAULTS_DIR, fn)
2018-09-28 20:30:10 +00:00
print("$name: Enter the config template filename (\"None\" for no file) ")
2017-08-21 19:53:00 +00:00
if default_config_file == nothing
print("[None]: ")
else
2018-09-19 19:57:21 +00:00
print("[$(replace(default_config_file, homedir() => "~"))]: ")
2017-08-21 19:53:00 +00:00
end
2018-09-28 20:30:10 +00:00
2017-08-21 19:53:00 +00:00
config_file = readline()
config_file = if uppercase(config_file) == "NONE"
2017-08-21 19:53:00 +00:00
nothing
elseif isempty(config_file)
default_config_file
else
config_file
end
2018-09-28 20:30:10 +00:00
return T(; config_file=config_file)
2017-08-21 19:53:00 +00:00
end