2017-12-01 17:33:57 +00:00
|
|
|
import Base.show
|
|
|
|
|
2017-08-17 22:06:05 +00:00
|
|
|
"""
|
|
|
|
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.
|
|
|
|
* `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.
|
2017-08-21 21:19:40 +00:00
|
|
|
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")`.
|
2017-08-17 22:06:05 +00:00
|
|
|
* `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.
|
2017-08-17 22:06:05 +00:00
|
|
|
* `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}
|
|
|
|
src::Nullable{AbstractString}
|
|
|
|
dest::AbstractString
|
2017-08-18 04:06:15 +00:00
|
|
|
badges::Vector{Badge}
|
2017-08-17 22:06:05 +00:00
|
|
|
view::Dict{String, Any}
|
|
|
|
|
2018-09-17 19:34:57 +00:00
|
|
|
function MyPlugin(; config_file::Union{AbstractString, Nothing}="")
|
2017-08-17 22:06:05 +00:00
|
|
|
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
|
2017-08-17 22:06:05 +00:00
|
|
|
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-17 22:06:05 +00:00
|
|
|
[
|
2017-08-18 04:06:15 +00:00
|
|
|
Badge(
|
2017-08-17 22:06:05 +00:00
|
|
|
"My Plugin",
|
|
|
|
"https://myplugin.com/badge-{{YEAR}}.png",
|
|
|
|
"https://myplugin.com/{{USER}}/{{PKGNAME}}.jl",
|
2017-08-18 04:06:15 +00:00
|
|
|
),
|
2017-08-17 22:06:05 +00:00
|
|
|
],
|
2017-08-23 17:50:52 +00:00
|
|
|
Dict{String, Any}("YEAR" => Dates.year(Dates.today())),
|
2017-08-17 22:06:05 +00:00
|
|
|
)
|
|
|
|
end
|
|
|
|
end
|
2017-08-21 21:19:40 +00:00
|
|
|
|
|
|
|
interactive(plugin_type::Type{MyPlugin}) = interactive(plugin_type; file="my-plugin.toml")
|
2017-08-17 22:06:05 +00:00
|
|
|
```
|
|
|
|
|
2017-08-21 21:19:40 +00:00
|
|
|
The above plugin ignores files ending with `.mgp`, copies `defaults/my-plugin.toml` by
|
2017-08-17 22:06:05 +00:00
|
|
|
default, and creates a badge that links to the project on its own site, using the default
|
2017-08-23 17:50:52 +00:00
|
|
|
substitutions with one addition: `{{YEAR}} => Dates.year(Dates.today())`. Since the default
|
|
|
|
config template file doesn't follow the generic naming convention, we added another
|
|
|
|
`interactive` method to correct the assumed filename.
|
2017-08-17 22:06:05 +00:00
|
|
|
"""
|
2017-08-17 06:57:58 +00:00
|
|
|
abstract type GenericPlugin <: Plugin end
|
|
|
|
|
2017-12-01 17:33:57 +00:00
|
|
|
function show(io::IO, p::GenericPlugin)
|
2017-12-07 16:43:40 +00:00
|
|
|
spc = " "
|
2017-12-12 15:15:55 +00:00
|
|
|
println(io, "$(Base.datatype_name(typeof(p))):")
|
2017-12-01 17:33:57 +00:00
|
|
|
|
|
|
|
cfg = if isnull(p.src)
|
|
|
|
"None"
|
|
|
|
else
|
|
|
|
dirname(get(p.src)) == DEFAULTS_DIR ? "Default" : get(p.src)
|
|
|
|
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
|
|
|
|
|
2017-08-17 06:57:58 +00:00
|
|
|
"""
|
2017-08-17 22:06:05 +00:00
|
|
|
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
|
|
|
|
[`interactive`](@ref) in any way they choose.
|
2017-08-17 06:57:58 +00:00
|
|
|
|
2017-08-17 22:06:05 +00:00
|
|
|
# Attributes
|
|
|
|
* `gitignore::Vector{AbstractString}`: Array of patterns to be added to the `.gitignore` of
|
|
|
|
generated packages that use this plugin.
|
2017-08-17 06:57:58 +00:00
|
|
|
|
2017-08-17 22:06:05 +00:00
|
|
|
# Example
|
|
|
|
```julia
|
|
|
|
@auto_hash_equals struct MyPlugin <: CustomPlugin
|
|
|
|
gitignore::Vector{AbstractString}
|
|
|
|
lucky::Bool
|
2017-08-17 06:57:58 +00:00
|
|
|
|
2017-08-17 22:06:05 +00:00
|
|
|
MyPlugin() = new([], rand() > 0.8)
|
|
|
|
|
|
|
|
function gen_plugin(
|
|
|
|
plugin::MyPlugin,
|
|
|
|
template::Template,
|
2017-08-23 17:50:52 +00:00
|
|
|
dir::AbstractString,
|
2017-12-01 12:12:37 +00:00
|
|
|
pkg_name::AbstractString,
|
2017-08-17 06:57:58 +00:00
|
|
|
)
|
2017-08-17 22:06:05 +00:00
|
|
|
if plugin.lucky
|
|
|
|
text = substitute(
|
2017-08-23 17:50:52 +00:00
|
|
|
"You got lucky with {{PKGNAME}}, {{USER}}!",
|
2017-08-17 22:06:05 +00:00
|
|
|
template,
|
|
|
|
)
|
2017-09-04 18:00:18 +00:00
|
|
|
gen_file(joinpath(dir, pkg_name, ".myplugin.yml"), text)
|
2017-08-17 22:06:05 +00:00
|
|
|
else
|
|
|
|
println("Maybe next time.")
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
function badges(
|
|
|
|
plugin::MyPlugin,
|
|
|
|
user::AbstractString,
|
|
|
|
pkg_name::AbstractString,
|
|
|
|
)
|
|
|
|
if plugin.lucky
|
|
|
|
return [
|
2017-08-18 04:06:15 +00:00
|
|
|
format(Badge(
|
2017-08-17 22:06:05 +00:00
|
|
|
"You got lucky!",
|
|
|
|
"https://myplugin.com/badge.png",
|
|
|
|
"https://myplugin.com/\$user/\$pkg_name.jl",
|
2017-08-18 04:06:15 +00:00
|
|
|
)),
|
2017-08-17 22:06:05 +00:00
|
|
|
]
|
|
|
|
else
|
|
|
|
return String[]
|
|
|
|
end
|
|
|
|
end
|
2017-08-17 06:57:58 +00:00
|
|
|
end
|
2017-08-22 16:25:00 +00:00
|
|
|
|
|
|
|
interactive(plugin_type::Type{MyPlugin}) = MyPlugin()
|
2017-08-17 22:06:05 +00:00
|
|
|
```
|
|
|
|
|
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.
|
|
|
|
|
|
|
|
**Note**: Due to a bug in `Mustache`, conditionals can insert undesired newlines
|
|
|
|
(more detail [here](https://github.com/jverzani/Mustache.jl/issues/47)).
|
2017-08-17 22:06:05 +00:00
|
|
|
"""
|
|
|
|
abstract type CustomPlugin <: Plugin end
|
2017-08-17 06:57:58 +00:00
|
|
|
|
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
|
|
|
|
|
|
|
|
"""
|
|
|
|
format(b::Badge)
|
|
|
|
|
|
|
|
Return `badge`'s data formatted as a Markdown string.
|
|
|
|
"""
|
|
|
|
format(b::Badge) = "[)]($(b.link))"
|
|
|
|
|
2017-08-17 06:57:58 +00:00
|
|
|
"""
|
2017-08-23 17:50:52 +00:00
|
|
|
gen_plugin(
|
|
|
|
plugin::Plugin,
|
|
|
|
template::Template,
|
|
|
|
dir::AbstractString,
|
|
|
|
pkg_name::AbstractString
|
|
|
|
) -> Vector{String}
|
2017-08-17 06:57:58 +00:00
|
|
|
|
|
|
|
Generate any files associated with a plugin.
|
|
|
|
|
|
|
|
# Arguments
|
|
|
|
* `plugin::Plugin`: Plugin whose files are being generated.
|
|
|
|
* `template::Template`: Template configuration.
|
2017-08-23 17:50:52 +00:00
|
|
|
* `dir::AbstractString`: The directory in which the files will be generated. Note that
|
|
|
|
this will be joined to `pkg_name`.
|
2017-08-17 06:57:58 +00:00
|
|
|
* `pkg_name::AbstractString`: Name of the package.
|
|
|
|
|
|
|
|
Returns an array of generated file/directory names.
|
|
|
|
"""
|
2017-08-23 17:50:52 +00:00
|
|
|
function gen_plugin(
|
|
|
|
plugin::Plugin,
|
|
|
|
template::Template,
|
|
|
|
dir::AbstractString,
|
|
|
|
pkg_name::AbstractString,
|
|
|
|
)
|
|
|
|
return String[]
|
|
|
|
end
|
2017-08-17 06:57:58 +00:00
|
|
|
|
2017-08-23 17:50:52 +00:00
|
|
|
function gen_plugin(
|
|
|
|
plugin::GenericPlugin,
|
|
|
|
template::Template,
|
|
|
|
dir::AbstractString,
|
|
|
|
pkg_name::AbstractString,
|
|
|
|
)
|
2017-08-17 22:06:05 +00:00
|
|
|
src = try
|
|
|
|
get(plugin.src)
|
2017-08-17 06:57:58 +00:00
|
|
|
catch
|
|
|
|
return String[]
|
|
|
|
end
|
2017-08-17 22:06:05 +00:00
|
|
|
text = substitute(
|
|
|
|
readstring(src),
|
|
|
|
template;
|
|
|
|
view=merge(Dict("PKGNAME" => pkg_name), plugin.view),
|
|
|
|
)
|
2017-08-23 17:50:52 +00:00
|
|
|
gen_file(joinpath(dir, pkg_name, plugin.dest), text)
|
2017-08-17 22:06:05 +00:00
|
|
|
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)
|
2017-12-12 15:15:55 +00:00
|
|
|
return map(b -> substitute(format(b), view), plugin.badges)
|
2017-08-17 06:57:58 +00:00
|
|
|
end
|
2017-08-21 19:53:00 +00:00
|
|
|
|
2017-08-21 21:19:40 +00:00
|
|
|
"""
|
|
|
|
interactive(
|
2017-11-14 18:20:56 +00:00
|
|
|
plugin_type::Type{<:Plugin};
|
2018-09-17 19:34:57 +00:00
|
|
|
file::Union{AbstractString, Nothing}="",
|
2017-08-23 17:50:52 +00:00
|
|
|
) -> Plugin
|
2017-08-21 21:19:40 +00:00
|
|
|
|
|
|
|
Interactively create a plugin of type `plugin_type`, where `file` is the plugin type's
|
|
|
|
default config template with a non-standard name (for `MyPlugin`, this is anything but
|
|
|
|
"myplugin.yml").
|
|
|
|
"""
|
2017-08-21 19:53:00 +00:00
|
|
|
function interactive(
|
2017-08-23 17:50:52 +00:00
|
|
|
plugin_type::Type{<:GenericPlugin};
|
2018-09-17 19:34:57 +00:00
|
|
|
file::Union{AbstractString, Nothing}="",
|
2017-08-23 17:50:52 +00:00
|
|
|
)
|
2017-08-21 19:53:00 +00:00
|
|
|
plugin_name = String(split(string(plugin_type), ".")[end])
|
|
|
|
# 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(plugin_name)).yml" : file
|
|
|
|
default_config_file = fn == nothing ? fn : joinpath(DEFAULTS_DIR, fn)
|
2017-09-04 18:00:18 +00:00
|
|
|
print("$plugin_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
|
|
|
|
print("[$(replace(default_config_file, homedir(), "~"))]: ")
|
|
|
|
end
|
|
|
|
config_file = readline()
|
2017-09-04 18:00:18 +00:00
|
|
|
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
|
|
|
|
return plugin_type(; config_file=config_file)
|
|
|
|
end
|