Plugin Development
The best and easiest way to contribute to PkgTemplates
is to write new plugins.
PkgTemplates.Plugin
— Type.A plugin to be added to a Template
, which adds some functionality or integration. New plugins should almost always extend GenericPlugin
or CustomPlugin
.
Generic Plugins
PkgTemplates.GenericPlugin
— Type.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 tonothing
, no file will be generated. When this defaults to an empty string, there should be a default file indefaults
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, aninteractive
method needs to be implemented to callinteractive(; file="file.ext")
.dest::AbstractString
: Path to the generated file, relative to the root of the generated package repository.badges::Vector{Badge}
: Array ofBadge
s containing information used to create Markdown-formatted badges from the plugin. Entries will be run throughsubstitute
, so they may contain placeholder values.view::Dict{String, Any}
: Additional substitutions to make in both the plugin's badges and its associated file. Seesubstitute
for details.
Example
@auto_hash_equals struct MyPlugin <: GenericPlugin
gitignore::Vector{AbstractString}
src::Nullable{AbstractString}
dest::AbstractString
badges::Vector{Badge}
view::Dict{String, Any}
function MyPlugin(; config_file::Union{AbstractString, Void}="")
if config_file != nothing
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,
".my-plugin.toml",
[
Badge(
"My Plugin",
"https://myplugin.com/badge-{{YEAR}}.png",
"https://myplugin.com/{{USER}}/{{PKGNAME}}.jl",
),
],
Dict{String, Any}("YEAR" => Dates.year(Dates.today())),
)
end
end
interactive(plugin_type::Type{MyPlugin}) = interactive(plugin_type; 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 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.
Custom Plugins
PkgTemplates.CustomPlugin
— Type.Custom plugins are plugins whose behaviour does not follow the GenericPlugin
pattern. They can implement gen_plugin
, badges
, and interactive
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
@auto_hash_equals struct MyPlugin <: CustomPlugin
gitignore::Vector{AbstractString}
lucky::Bool
MyPlugin() = new([], rand() > 0.8)
function gen_plugin(
plugin::MyPlugin,
template::Template,
dir::AbstractString,
pkg_name::AbstractString,
)
if plugin.lucky
text = substitute(
"You got lucky with {{PKGNAME}}, {{USER}}!",
template,
)
gen_file(joinpath(dir, pkg_name, ".myplugin.yml"), text)
else
println("Maybe next time.")
end
end
function badges(
plugin::MyPlugin,
user::AbstractString,
pkg_name::AbstractString,
)
if plugin.lucky
return [
format(Badge(
"You got lucky!",
"https://myplugin.com/badge.png",
"https://myplugin.com/$user/$pkg_name.jl",
)),
]
else
return String[]
end
end
end
interactive(plugin_type::Type{MyPlugin}) = MyPlugin()
This plugin doesn't do much, but it demonstrates how gen_plugin
, badges
and interactive
can be implemented using substitute
, gen_file
, Badge
, and format
.
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's syntax to define replacements.
Note: Due to a bug in Mustache
, conditionals can insert undesired newlines (more detail here).
CustomPlugin
Required Methods
gen_plugin
PkgTemplates.gen_plugin
— Function.gen_plugin(
plugin::Plugin,
template::Template,
dir::AbstractString,
pkg_name::AbstractString
) -> Vector{String}
Generate any files associated with a plugin.
Arguments
plugin::Plugin
: Plugin whose files are being generated.template::Template
: Template configuration.dir::AbstractString
: The directory in which the files will be generated. Note that this will be joined topkg_name
.pkg_name::AbstractString
: Name of the package.
Returns an array of generated file/directory names.
PkgTemplates.interactive
— Function.interactive(
plugin_type::Type{<:Plugin};
file::Union{AbstractString, Void}="",
) -> Plugin
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").
Note: interactive
is not strictly required, however without it, your custom plugin will not be available when creating templates with interactive_template
.
badges
PkgTemplates.badges
— Function.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.
Helper Types/Functions
gen_file
PkgTemplates.gen_file
— Function.gen_file(file_path::AbstractString, text::AbstractString) -> Int
Create a new file containing some given text. Always ends the file with a newline.
Arguments
file::AbstractString
: Path to the file to be created.text::AbstractString
: Text to write to the file.
Returns the number of bytes written to the file.
substitute
PkgTemplates.substitute
— Function.substitute(template::AbstractString, view::Dict{String, Any}) -> String
Replace placeholders in template
with values in view
via Mustache
. template
is not modified.
For information on how to structure template
, see "Defining Template Files" section in Custom Plugins.
Note: Conditionals in template
without a corresponding key in view
won't error, but will simply be evaluated as false.
substitute(
template::AbstractString,
pkg_template::Template;
view::Dict{String, Any}=Dict{String, Any}(),
) -> String
Replace placeholders in template
, using some default replacements based on the pkg_template
and additional ones in view
. template
is not modified.
Badge
PkgTemplates.Badge
— Type.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.
format
PkgTemplates.format
— Function.format(b::Badge)
Return badge
's data formatted as a Markdown string.
version_floor
PkgTemplates.version_floor
— Function.version_floor(v::VersionNumber=VERSION) -> String
Format the given Julia version.
Keyword arguments
v::VersionNumber=VERSION
: Version to floor.
Returns "major.minor" for the most recent release version relative to v. For prereleases with v.minor == v.patch == 0, returns "major.minor-".