PkgTemplates.jl/dev/developer/index.html
2019-11-12 13:41:12 +00:00

128 lines
39 KiB
HTML

<!DOCTYPE html>
<html lang="en"><head><meta charset="UTF-8"/><meta name="viewport" content="width=device-width, initial-scale=1.0"/><title>Developer Guide · PkgTemplates.jl</title><link rel="canonical" href="https://invenia.github.io/PkgTemplates.jl/developer/index.html"/><link href="https://cdnjs.cloudflare.com/ajax/libs/normalize/4.2.0/normalize.min.css" rel="stylesheet" type="text/css"/><link href="https://fonts.googleapis.com/css?family=Lato|Roboto+Mono" rel="stylesheet" type="text/css"/><link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.6.3/css/font-awesome.min.css" rel="stylesheet" type="text/css"/><link href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/9.12.0/styles/default.min.css" rel="stylesheet" type="text/css"/><script>documenterBaseURL=".."</script><script src="https://cdnjs.cloudflare.com/ajax/libs/require.js/2.2.0/require.min.js" data-main="../assets/documenter.js"></script><script src="../siteinfo.js"></script><script src="../../versions.js"></script><link href="../assets/documenter.css" rel="stylesheet" type="text/css"/></head><body><nav class="toc"><h1>PkgTemplates.jl</h1><select id="version-selector" onChange="window.location.href=this.value" style="visibility: hidden"></select><form class="search" id="search-form" action="../search/"><input id="search-query" name="q" type="text" placeholder="Search docs"/></form><ul><li><a class="toctext" href="../">Home</a></li><li><a class="toctext" href="../user/">User Guide</a></li><li class="current"><a class="toctext" href>Developer Guide</a><ul class="internal"><li><a class="toctext" href="#Template-Package-Creation-Pipeline-1">Template + Package Creation Pipeline</a></li><li><a class="toctext" href="#Plugin-Walkthrough-1"><code>Plugin</code> Walkthrough</a></li><li><a class="toctext" href="#BasicPlugin-Walkthrough-1"><code>BasicPlugin</code> Walkthrough</a></li><li><a class="toctext" href="#Doing-Extra-Work-With-BasicPlugins-1">Doing Extra Work With <code>BasicPlugin</code>s</a></li><li><a class="toctext" href="#Miscellaneous-Tips-1">Miscellaneous Tips</a></li></ul></li><li><a class="toctext" href="../migrating/">Migrating To PkgTemplates 0.7+</a></li></ul></nav><article id="docs"><header><nav><ul><li><a href>Developer Guide</a></li></ul><a class="edit-page" href="https://github.com/invenia/PkgTemplates.jl/blob/master/docs/src/developer.md#L"><span class="fa"></span> Edit on GitHub</a></nav><hr/><div id="topbar"><span>Developer Guide</span><a class="fa fa-bars" href="#"></a></div></header><h1><a class="nav-anchor" id="PkgTemplates-Developer-Guide-1" href="#PkgTemplates-Developer-Guide-1">PkgTemplates Developer Guide</a></h1><ul><li><a href="#PkgTemplates-Developer-Guide-1">PkgTemplates Developer Guide</a></li><ul><li><a href="#Template-Package-Creation-Pipeline-1">Template + Package Creation Pipeline</a></li><li><a href="#Plugin-Walkthrough-1"><code>Plugin</code> Walkthrough</a></li><li><a href="#BasicPlugin-Walkthrough-1"><code>BasicPlugin</code> Walkthrough</a></li><li><a href="#Doing-Extra-Work-With-BasicPlugins-1">Doing Extra Work With <code>BasicPlugin</code>s</a></li><li><a href="#Miscellaneous-Tips-1">Miscellaneous Tips</a></li></ul></ul><p>PkgTemplates can be easily extended by adding new <a href="#PkgTemplates.Plugin"><code>Plugin</code></a>s.</p><p>There are two types of plugins: <a href="#PkgTemplates.Plugin"><code>Plugin</code></a> and <a href="#PkgTemplates.BasicPlugin"><code>BasicPlugin</code></a>.</p><section class="docstring"><div class="docstring-header"><a class="docstring-binding" id="PkgTemplates.Plugin" href="#PkgTemplates.Plugin"><code>PkgTemplates.Plugin</code></a><span class="docstring-category">Type</span>.</div><div><div><p>Plugins are PkgTemplates&#39; source of customization and extensibility. Add plugins to your <a href="../user/#PkgTemplates.Template"><code>Template</code></a>s to enable extra pieces of repository setup.</p><p>When implementing a new plugin, subtype this type to have full control over its behaviour.</p></div></div><a class="source-link" target="_blank" href="https://github.com/invenia/PkgTemplates.jl/blob/39350c10de378964a3fa673176602c93d532acdb/src/PkgTemplates.jl#LL34-L39">source</a></section><section class="docstring"><div class="docstring-header"><a class="docstring-binding" id="PkgTemplates.BasicPlugin" href="#PkgTemplates.BasicPlugin"><code>PkgTemplates.BasicPlugin</code></a><span class="docstring-category">Type</span>.</div><div><div><p>A simple plugin that, in general, creates a single file.</p></div></div><a class="source-link" target="_blank" href="https://github.com/invenia/PkgTemplates.jl/blob/39350c10de378964a3fa673176602c93d532acdb/src/plugin.jl#LL4-L6">source</a></section><h2><a class="nav-anchor" id="Template-Package-Creation-Pipeline-1" href="#Template-Package-Creation-Pipeline-1">Template + Package Creation Pipeline</a></h2><p>The <a href="../user/#PkgTemplates.Template"><code>Template</code></a> constructor basically does this:</p><pre><code class="language-none">- extract values from keyword arguments
- create a Template from the values
- for each plugin:
- validate plugin against the template</code></pre><p>The plugin validation step uses the <a href="#PkgTemplates.validate"><code>validate</code></a> function. It lets us catch mistakes before we try to generate packages.</p><section class="docstring"><div class="docstring-header"><a class="docstring-binding" id="PkgTemplates.validate" href="#PkgTemplates.validate"><code>PkgTemplates.validate</code></a><span class="docstring-category">Function</span>.</div><div><div><pre><code class="language-julia">validate(::Plugin, ::Template)</code></pre><p>Perform any required validation for a <a href="#PkgTemplates.Plugin"><code>Plugin</code></a>.</p><p>It is preferred to do validation here instead of in <a href="#PkgTemplates.prehook"><code>prehook</code></a>, because this function is called at <a href="../user/#PkgTemplates.Template"><code>Template</code></a> construction time, whereas the prehook is only run at package generation time.</p></div></div><a class="source-link" target="_blank" href="https://github.com/invenia/PkgTemplates.jl/blob/39350c10de378964a3fa673176602c93d532acdb/src/plugin.jl#LL145-L153">source</a></section><p>The package generation process looks like this:</p><pre><code class="language-none">- create empty directory for the package
- for each plugin, ordered by priority:
- run plugin prehook
- for each plugin, ordered by priority:
- run plugin hook
- for each plugin, ordered by priority:
- run plugin posthook</code></pre><p>As you can tell, plugins play a central role in setting up a package.</p><p>The three main entrypoints for plugins to do work are the <a href="#PkgTemplates.prehook"><code>prehook</code></a>, the <a href="#PkgTemplates.hook"><code>hook</code></a>, and the <a href="#PkgTemplates.posthook"><code>posthook</code></a>. As the names might imply, they basically mean &quot;before the main stage&quot;, &quot;the main stage&quot;, and &quot;after the main stage&quot;, respectively.</p><p>Each stage is basically identical, since the functions take the exact same arguments. However, the multiple stages allow us to depend on artifacts of the previous stages. For example, the <a href="../user/#PkgTemplates.Git"><code>Git</code></a> plugin uses <a href="#PkgTemplates.posthook"><code>posthook</code></a> to commit all generated files, but it wouldn&#39;t make sense to do that before the files are generated.</p><p>But what about dependencies within the same stage? In this case, we have <a href="#PkgTemplates.priority"><code>priority</code></a> to define which plugins go when. The <a href="../user/#PkgTemplates.Git"><code>Git</code></a> plugin also uses this function to lower its posthook&#39;s priority, so that even if other plugins generate files in their posthooks, they still get committed (provided that those plugins didn&#39;t set an even lower priority).</p><section class="docstring"><div class="docstring-header"><a class="docstring-binding" id="PkgTemplates.prehook" href="#PkgTemplates.prehook"><code>PkgTemplates.prehook</code></a><span class="docstring-category">Function</span>.</div><div><div><pre><code class="language-julia">prehook(::Plugin, ::Template, pkg_dir::AbstractString)</code></pre><p>Stage 1 of the package generation process (the &quot;before&quot; stage, in general). At this point, <code>pkg_dir</code> is an empty directory that will eventually contain the package, and neither the <a href="#PkgTemplates.hook"><code>hook</code></a>s nor the <a href="#PkgTemplates.posthook"><code>posthook</code></a>s have run.</p><div class="admonition note"><div class="admonition-title">Note</div><div class="admonition-text"><p><code>pkg_dir</code> only stays empty until the first plugin chooses to create a file. See also: <a href="#PkgTemplates.priority"><code>priority</code></a>.</p></div></div></div></div><a class="source-link" target="_blank" href="https://github.com/invenia/PkgTemplates.jl/blob/39350c10de378964a3fa673176602c93d532acdb/src/plugin.jl#LL156-L166">source</a></section><section class="docstring"><div class="docstring-header"><a class="docstring-binding" id="PkgTemplates.hook" href="#PkgTemplates.hook"><code>PkgTemplates.hook</code></a><span class="docstring-category">Function</span>.</div><div><div><pre><code class="language-julia">hook(::Plugin, ::Template, pkg_dir::AbstractString)</code></pre><p>Stage 2 of the package generation pipeline (the &quot;main&quot; stage, in general). At this point, the <a href="#PkgTemplates.prehook"><code>prehook</code></a>s have run, but not the <a href="#PkgTemplates.posthook"><code>posthook</code></a>s.</p><p><code>pkg_dir</code> is the directory in which the package is being generated (so <code>basename(pkg_dir)</code> is the package name).</p><div class="admonition note"><div class="admonition-title">Note</div><div class="admonition-text"><p>You usually shouldn&#39;t implement this function for <a href="#PkgTemplates.BasicPlugin"><code>BasicPlugin</code></a>s. If you do, it should probably <code>invoke</code> the generic method (otherwise, there&#39;s not much reason to subtype <code>BasicPlugin</code>).</p></div></div></div></div><a class="source-link" target="_blank" href="https://github.com/invenia/PkgTemplates.jl/blob/39350c10de378964a3fa673176602c93d532acdb/src/plugin.jl#LL169-L182">source</a></section><section class="docstring"><div class="docstring-header"><a class="docstring-binding" id="PkgTemplates.posthook" href="#PkgTemplates.posthook"><code>PkgTemplates.posthook</code></a><span class="docstring-category">Function</span>.</div><div><div><pre><code class="language-julia">posthook(::Plugin, ::Template, pkg_dir::AbstractString)</code></pre><p>Stage 3 of the package generation pipeline (the &quot;after&quot; stage, in general). At this point, both the <a href="#PkgTemplates.prehook"><code>prehook</code></a>s and <a href="#PkgTemplates.hook"><code>hook</code></a>s have run.</p></div></div><a class="source-link" target="_blank" href="https://github.com/invenia/PkgTemplates.jl/blob/39350c10de378964a3fa673176602c93d532acdb/src/plugin.jl#LL185-L190">source</a></section><section class="docstring"><div class="docstring-header"><a class="docstring-binding" id="PkgTemplates.priority" href="#PkgTemplates.priority"><code>PkgTemplates.priority</code></a><span class="docstring-category">Function</span>.</div><div><div><pre><code class="language-julia">priority(::Plugin, ::Union{typeof(prehook), typeof(hook), typeof(posthook)}) -&gt; Int</code></pre><p>Determines the order in which plugins are processed (higher goes first). The default priority (<code>DEFAULT_PRIORITY</code>), is <code>1000</code>.</p><p>You can implement this function per-stage (by using <code>::typeof(hook)</code>, for example), or for all stages by simply using <code>::Function</code>.</p></div></div><a class="source-link" target="_blank" href="https://github.com/invenia/PkgTemplates.jl/blob/39350c10de378964a3fa673176602c93d532acdb/src/plugin.jl#LL69-L77">source</a></section><h2><a class="nav-anchor" id="Plugin-Walkthrough-1" href="#Plugin-Walkthrough-1"><code>Plugin</code> Walkthrough</a></h2><p>Concrete types that subtype <a href="#PkgTemplates.Plugin"><code>Plugin</code></a> directly are free to do almost anything. To understand how they&#39;re implemented, let&#39;s look at simplified versions of two plugins: <a href="../user/#PkgTemplates.Documenter"><code>Documenter</code></a> to explore templating, and <a href="../user/#PkgTemplates.Git"><code>Git</code></a> to further clarify the multi-stage pipeline.</p><h3><a class="nav-anchor" id="Example:-Documenter-1" href="#Example:-Documenter-1">Example: <code>Documenter</code></a></h3><pre><code class="language-julia">@with_kw_noshow struct Documenter &lt;: Plugin
make_jl::String = default_file(&quot;docs&quot;, &quot;make.jl&quot;) &lt;- &quot;Path to make.jl template&quot;
index_md::String = default_file(&quot;docs&quot;, &quot;src&quot;, &quot;index.md&quot;) &lt;- &quot;Path to index.md template&quot;
end
gitignore(::Documenter) = [&quot;/docs/build/&quot;]
badges(::Documenter) = [
Badge(
&quot;Stable&quot;,
&quot;https://img.shields.io/badge/docs-stable-blue.svg&quot;,
&quot;https://{{{USER}}}.github.io/{{{PKG}}}.jl/stable&quot;,
),
Badge(
&quot;Dev&quot;,
&quot;https://img.shields.io/badge/docs-dev-blue.svg&quot;,
&quot;https://{{{USER}}}.github.io/{{{PKG}}}.jl/dev&quot;,
),
]
view(p::Documenter, t::Template, pkg::AbstractString) = Dict(
&quot;AUTHORS&quot; =&gt; join(t.authors, &quot;, &quot;),
&quot;PKG&quot; =&gt; pkg,
&quot;REPO&quot; =&gt; &quot;$(t.host)/$(t.user)/$pkg.jl&quot;,
&quot;USER&quot; =&gt; t.user,
)
function hook(p::Documenter, t::Template, pkg_dir::AbstractString)
pkg = basename(pkg_dir)
docs_dir = joinpath(pkg_dir, &quot;docs&quot;)
make = render_file(p.make_jl, combined_view(p, t, pkg), tags(p))
gen_file(joinpath(docs_dir, &quot;make.jl&quot;), make)
index = render_file(p.index_md, combined_view(p, t, pkg), tags(p))
gen_file(joinpath(docs_dir, &quot;src&quot;, &quot;index.md&quot;), index)
# What this function does is not relevant here.
create_documentation_project()
end</code></pre><p>The <code>@with_kw_noshow</code> macro defines keyword constructors for us. Inside of our struct definition, we&#39;re using <a href="#PkgTemplates.default_file"><code>default_file</code></a> to refer to files in this repository.</p><section class="docstring"><div class="docstring-header"><a class="docstring-binding" id="PkgTemplates.default_file" href="#PkgTemplates.default_file"><code>PkgTemplates.default_file</code></a><span class="docstring-category">Function</span>.</div><div><div><pre><code class="language-julia">default_file(paths::AbstractString...) -&gt; String</code></pre><p>Return a path relative to the default template file directory (<code>~/build/invenia/PkgTemplates.jl/templates</code>).</p></div></div><a class="source-link" target="_blank" href="https://github.com/invenia/PkgTemplates.jl/blob/39350c10de378964a3fa673176602c93d532acdb/src/plugin.jl#LL9-L14">source</a></section><p>The first method we implement for <code>Documenter</code> is <a href="#PkgTemplates.gitignore"><code>gitignore</code></a>, so that packages created with this plugin ignore documentation build artifacts.</p><section class="docstring"><div class="docstring-header"><a class="docstring-binding" id="PkgTemplates.gitignore" href="#PkgTemplates.gitignore"><code>PkgTemplates.gitignore</code></a><span class="docstring-category">Function</span>.</div><div><div><pre><code class="language-julia">gitignore(::Plugin) -&gt; Vector{String}</code></pre><p>Return patterns that should be added to <code>.gitignore</code>. These are used by the <a href="../user/#PkgTemplates.Git"><code>Git</code></a> plugin.</p><p>By default, an empty list is returned.</p></div></div><a class="source-link" target="_blank" href="https://github.com/invenia/PkgTemplates.jl/blob/39350c10de378964a3fa673176602c93d532acdb/src/plugin.jl#LL80-L87">source</a></section><p>Second, we implement <a href="#PkgTemplates.badges"><code>badges</code></a> to add a couple of badges to new packages&#39; README files.</p><section class="docstring"><div class="docstring-header"><a class="docstring-binding" id="PkgTemplates.badges" href="#PkgTemplates.badges"><code>PkgTemplates.badges</code></a><span class="docstring-category">Function</span>.</div><div><div><pre><code class="language-julia">badges(::Plugin) -&gt; Union{Badge, Vector{Badge}}</code></pre><p>Return a list of <a href="#PkgTemplates.Badge"><code>Badge</code></a>s, or just one, to be added to <code>README.md</code>. These are used by the <a href="../user/#PkgTemplates.Readme"><code>Readme</code></a> plugin to add badges to the README.</p><p>By default, an empty list is returned.</p></div></div><a class="source-link" target="_blank" href="https://github.com/invenia/PkgTemplates.jl/blob/39350c10de378964a3fa673176602c93d532acdb/src/plugin.jl#LL90-L97">source</a></section><section class="docstring"><div class="docstring-header"><a class="docstring-binding" id="PkgTemplates.Badge" href="#PkgTemplates.Badge"><code>PkgTemplates.Badge</code></a><span class="docstring-category">Type</span>.</div><div><div><pre><code class="language-julia">Badge(hover::AbstractString, image::AbstractString, link::AbstractString)</code></pre><p>Container for Markdown badge data. Each argument can contain placeholders, which will be filled in with values from <a href="#PkgTemplates.combined_view"><code>combined_view</code></a>.</p><p><strong>Arguments</strong></p><ul><li><code>hover::AbstractString</code>: Text to appear when the mouse is hovered over the badge.</li><li><code>image::AbstractString</code>: URL to the image to display.</li><li><code>link::AbstractString</code>: URL to go to upon clicking the badge.</li></ul></div></div><a class="source-link" target="_blank" href="https://github.com/invenia/PkgTemplates.jl/blob/39350c10de378964a3fa673176602c93d532acdb/src/plugin.jl#LL118-L129">source</a></section><p>These two functions, <a href="#PkgTemplates.gitignore"><code>gitignore</code></a> and <a href="#PkgTemplates.badges"><code>badges</code></a>, are currently the only &quot;special&quot; functions for cross-plugin interactions. In other cases, you can still access the <a href="../user/#PkgTemplates.Template"><code>Template</code></a>&#39;s plugins to depend on the presence/properties of other plugins, although that&#39;s less powerful.</p><p>Third, we implement <a href="#PkgTemplates.view"><code>view</code></a>, which is used to fill placeholders in badges and rendered files.</p><section class="docstring"><div class="docstring-header"><a class="docstring-binding" id="PkgTemplates.view" href="#PkgTemplates.view"><code>PkgTemplates.view</code></a><span class="docstring-category">Function</span>.</div><div><div><pre><code class="language-julia">view(::Plugin, ::Template, pkg::AbstractString) -&gt; Dict{String, Any}</code></pre><p>Return the view to be passed to the text templating engine for this plugin. <code>pkg</code> is the name of the package being generated.</p><p>For <a href="#PkgTemplates.BasicPlugin"><code>BasicPlugin</code></a>s, this is used for both the plugin badges (see <a href="#PkgTemplates.badges"><code>badges</code></a>) and the template file (see <a href="#PkgTemplates.source"><code>source</code></a>). For other <a href="#PkgTemplates.Plugin"><code>Plugin</code></a>s, it is used only for badges, but you can always call it yourself as part of your <a href="#PkgTemplates.hook"><code>hook</code></a> implementation.</p><p>By default, an empty <code>Dict</code> is returned.</p></div></div><a class="source-link" target="_blank" href="https://github.com/invenia/PkgTemplates.jl/blob/39350c10de378964a3fa673176602c93d532acdb/src/plugin.jl#LL17-L29">source</a></section><p>Finally, we implement <a href="#PkgTemplates.hook"><code>hook</code></a>, which is the real workhorse for the plugin. Inside of this function, we generate a couple of files with the help of a few more text templating functions.</p><section class="docstring"><div class="docstring-header"><a class="docstring-binding" id="PkgTemplates.render_file" href="#PkgTemplates.render_file"><code>PkgTemplates.render_file</code></a><span class="docstring-category">Function</span>.</div><div><div><pre><code class="language-julia">render_file(file::AbstractString view::Dict{&lt;:AbstractString}, tags) -&gt; String</code></pre><p>Render a template file with the data in <code>view</code>. <code>tags</code> should be a tuple of two strings, which are the opening and closing delimiters, or <code>nothing</code> to use the default delimiters.</p></div></div><a class="source-link" target="_blank" href="https://github.com/invenia/PkgTemplates.jl/blob/39350c10de378964a3fa673176602c93d532acdb/src/plugin.jl#LL223-L229">source</a></section><section class="docstring"><div class="docstring-header"><a class="docstring-binding" id="PkgTemplates.render_text" href="#PkgTemplates.render_text"><code>PkgTemplates.render_text</code></a><span class="docstring-category">Function</span>.</div><div><div><pre><code class="language-julia">render_text(text::AbstractString, view::Dict{&lt;:AbstractString}, tags=nothing) -&gt; String</code></pre><p>Render some text with the data in <code>view</code>. <code>tags</code> should be a tuple of two strings, which are the opening and closing delimiters, or <code>nothing</code> to use the default delimiters.</p></div></div><a class="source-link" target="_blank" href="https://github.com/invenia/PkgTemplates.jl/blob/39350c10de378964a3fa673176602c93d532acdb/src/plugin.jl#LL234-L240">source</a></section><section class="docstring"><div class="docstring-header"><a class="docstring-binding" id="PkgTemplates.gen_file" href="#PkgTemplates.gen_file"><code>PkgTemplates.gen_file</code></a><span class="docstring-category">Function</span>.</div><div><div><pre><code class="language-julia">gen_file(file::AbstractString, text::AbstractString)</code></pre><p>Create a new file containing some given text. Trailing whitespace is removed, and the file will end with a newline.</p></div></div><a class="source-link" target="_blank" href="https://github.com/invenia/PkgTemplates.jl/blob/39350c10de378964a3fa673176602c93d532acdb/src/plugin.jl#LL211-L216">source</a></section><section class="docstring"><div class="docstring-header"><a class="docstring-binding" id="PkgTemplates.combined_view" href="#PkgTemplates.combined_view"><code>PkgTemplates.combined_view</code></a><span class="docstring-category">Function</span>.</div><div><div><pre><code class="language-julia">combined_view(::Plugin, ::Template, pkg::AbstractString) -&gt; Dict{String, Any}</code></pre><p>This function combines <a href="#PkgTemplates.view"><code>view</code></a> and <a href="../user/#PkgTemplates.user_view"><code>user_view</code></a> for use in text templating. If you&#39;re doing manual file creation or text templating (i.e. writing <a href="#PkgTemplates.Plugin"><code>Plugin</code></a>s that are not <a href="#PkgTemplates.BasicPlugin"><code>BasicPlugin</code></a>s), then you should use this function rather than either of the former two.</p><div class="admonition note"><div class="admonition-title">Note</div><div class="admonition-text"><p>Do not implement this function yourself! If you&#39;re implementing a plugin, you should implement <a href="#PkgTemplates.view"><code>view</code></a>. If you&#39;re customizing a plugin as a user, you should implement <a href="../user/#PkgTemplates.user_view"><code>user_view</code></a>.</p></div></div></div></div><a class="source-link" target="_blank" href="https://github.com/invenia/PkgTemplates.jl/blob/39350c10de378964a3fa673176602c93d532acdb/src/plugin.jl#LL42-L54">source</a></section><section class="docstring"><div class="docstring-header"><a class="docstring-binding" id="PkgTemplates.tags" href="#PkgTemplates.tags"><code>PkgTemplates.tags</code></a><span class="docstring-category">Function</span>.</div><div><div><pre><code class="language-julia">tags(::Plugin) -&gt; Tuple{String, String}</code></pre><p>Return the delimiters used for text templating. See the <a href="../user/#PkgTemplates.Citation"><code>Citation</code></a> plugin for a rare case where changing the tags is necessary.</p><p>By default, the tags are <code>&quot;{{&quot;</code> and <code>&quot;}}&quot;</code>.</p></div></div><a class="source-link" target="_blank" href="https://github.com/invenia/PkgTemplates.jl/blob/39350c10de378964a3fa673176602c93d532acdb/src/plugin.jl#LL59-L66">source</a></section><p>For more information on text templating, see the <a href="#BasicPlugin-Walkthrough-1"><code>BasicPlugin</code> Walkthrough</a> and the section on <a href="../user/#Custom-Template-Files-1">Custom Template Files</a>.</p><h3><a class="nav-anchor" id="Example:-Git-1" href="#Example:-Git-1">Example: <code>Git</code></a></h3><pre><code class="language-julia">struct Git &lt;: Plugin end
priority(::Git, ::typeof(posthook)) = 5
function validate(::Git, ::Template)
foreach((&quot;user.name&quot;, &quot;user.email&quot;)) do k
if isempty(LibGit2.getconfig(k, &quot;&quot;))
throw(ArgumentError(&quot;Git: Global Git config is missing required value &#39;$k&#39;&quot;))
end
end
end
function prehook(::Git, t::Template, pkg_dir::AbstractString)
LibGit2.with(LibGit2.init(pkg_dir)) do repo
LibGit2.commit(repo, &quot;Initial commit&quot;)
pkg = basename(pkg_dir)
url = &quot;https://$(t.host)/$(t.user)/$pkg.jl&quot;
close(GitRemote(repo, &quot;origin&quot;, url))
end
end
function hook(::Git, t::Template, pkg_dir::AbstractString)
ignore = mapreduce(gitignore, append!, t.plugins)
unique!(sort!(ignore))
gen_file(joinpath(pkg_dir, &quot;.gitignore&quot;), join(ignore, &quot;\n&quot;))
end
function posthook(::Git, ::Template, pkg_dir::AbstractString)
LibGit2.with(GitRepo(pkg_dir)) do repo
LibGit2.add!(repo, &quot;.&quot;)
LibGit2.commit(repo, &quot;Files generated by PkgTemplates&quot;)
end
end</code></pre><p>Validation and all three hooks are implemented:</p><ul><li><a href="#PkgTemplates.validate"><code>validate</code></a> makes sure that all required Git configuration is present.</li><li><a href="#PkgTemplates.prehook"><code>prehook</code></a> creates the Git repository for the package.</li><li><a href="#PkgTemplates.hook"><code>hook</code></a> generates the <code>.gitignore</code> file, using the special <a href="#PkgTemplates.gitignore"><code>gitignore</code></a> function.</li><li><a href="#PkgTemplates.posthook"><code>posthook</code></a> adds and commits all the generated files.</li></ul><p>As previously mentioned, we use <a href="#PkgTemplates.priority"><code>priority</code></a> to make sure that we wait until all other plugins are finished their work before committing files.</p><p>Hopefully, this demonstrates the level of control you have over the package generation process when developing plugins, and when it makes sense to exercise that power!</p><h2><a class="nav-anchor" id="BasicPlugin-Walkthrough-1" href="#BasicPlugin-Walkthrough-1"><code>BasicPlugin</code> Walkthrough</a></h2><p>Most of the time, you don&#39;t really need all of the control that we showed off above. Plugins that subtype <a href="#PkgTemplates.BasicPlugin"><code>BasicPlugin</code></a> perform a much more limited task. In general, they just generate one templated file.</p><p>To illustrate, let&#39;s look at the <a href="../user/#PkgTemplates.Citation"><code>Citation</code></a> plugin, which creates a <code>CITATION.bib</code> file.</p><pre><code class="language-julia">@with_kw_noshow struct Citation &lt;: BasicPlugin
file::String = default_file(&quot;CITATION.bib&quot;)
end
source(p::Citation) = p.file
destination(::Citation) = &quot;CITATION.bib&quot;
tags(::Citation) = &quot;&lt;&lt;&quot;, &quot;&gt;&gt;&quot;
view(::Citation, t::Template, pkg::AbstractString) = Dict(
&quot;AUTHORS&quot; =&gt; join(t.authors, &quot;, &quot;),
&quot;MONTH&quot; =&gt; month(today()),
&quot;PKG&quot; =&gt; pkg,
&quot;URL&quot; =&gt; &quot;https://$(t.host)/$(t.user)/$pkg.jl&quot;,
&quot;YEAR&quot; =&gt; year(today()),
)</code></pre><p>Similar to the <code>Documenter</code> example above, we&#39;re defining a keyword constructor, and assigning a default template file from this repository. This plugin adds nothing to <code>.gitignore</code>, and it doesn&#39;t add any badges, so implementations for <a href="#PkgTemplates.gitignore"><code>gitignore</code></a> and <a href="#PkgTemplates.badges"><code>badges</code></a> are omitted.</p><p>First, we implement <a href="#PkgTemplates.source"><code>source</code></a> and <a href="#PkgTemplates.destination"><code>destination</code></a> to define where the template file comes from, and where it goes. These functions are specific to <a href="#PkgTemplates.BasicPlugin"><code>BasicPlugin</code></a>s, and have no effect on regular <a href="#PkgTemplates.Plugin"><code>Plugin</code></a>s by default.</p><section class="docstring"><div class="docstring-header"><a class="docstring-binding" id="PkgTemplates.source" href="#PkgTemplates.source"><code>PkgTemplates.source</code></a><span class="docstring-category">Function</span>.</div><div><div><pre><code class="language-julia">source(::BasicPlugin) -&gt; Union{String, Nothing}</code></pre><p>Return the path to a plugin&#39;s template file, or <code>nothing</code> to indicate no file.</p><p>By default, <code>nothing</code> is returned.</p></div></div><a class="source-link" target="_blank" href="https://github.com/invenia/PkgTemplates.jl/blob/39350c10de378964a3fa673176602c93d532acdb/src/plugin.jl#LL100-L106">source</a></section><section class="docstring"><div class="docstring-header"><a class="docstring-binding" id="PkgTemplates.destination" href="#PkgTemplates.destination"><code>PkgTemplates.destination</code></a><span class="docstring-category">Function</span>.</div><div><div><pre><code class="language-julia">destination(::BasicPlugin) -&gt; String</code></pre><p>Return the destination, relative to the package root, of a plugin&#39;s configuration file.</p><p>This function <strong>must</strong> be implemented.</p></div></div><a class="source-link" target="_blank" href="https://github.com/invenia/PkgTemplates.jl/blob/39350c10de378964a3fa673176602c93d532acdb/src/plugin.jl#LL109-L115">source</a></section><p>Next, we implement <a href="#PkgTemplates.tags"><code>tags</code></a>. We briefly saw this function earlier, but in this case it&#39;s necessary to change its behaviour from the default. To see why, it might help to see the template file in its entirety:</p><pre><code class="language-none">@misc{&lt;&lt;&amp;PKG&gt;&gt;.jl,
author = {&lt;&lt;&amp;AUTHORS&gt;&gt;},
title = {&lt;&lt;&amp;PKG&gt;&gt;.jl},
url = {&lt;&lt;&amp;URL&gt;&gt;},
version = {v0.1.0},
year = {&lt;&lt;&amp;YEAR&gt;&gt;},
month = {&lt;&lt;&amp;MONTH&gt;&gt;}
}</code></pre><p>Because the file contains its own <code>{}</code> delimiters, we need to use different ones for templating to work properly.</p><p>Finally, we implement <a href="#PkgTemplates.view"><code>view</code></a> to fill in the placeholders that we saw in the template file.</p><h2><a class="nav-anchor" id="Doing-Extra-Work-With-BasicPlugins-1" href="#Doing-Extra-Work-With-BasicPlugins-1">Doing Extra Work With <code>BasicPlugin</code>s</a></h2><p>Notice that we didn&#39;t have to implement <a href="#PkgTemplates.hook"><code>hook</code></a> for our plugin. It&#39;s implemented for all <a href="#PkgTemplates.BasicPlugin"><code>BasicPlugin</code></a>s, like so:</p><pre><code class="language-julia">function render_plugin(p::BasicPlugin, t::Template, pkg::AbstractString)
return render_file(source(p), combined_view(p, t, pkg), tags(p))
end
function hook(p::BasicPlugin, t::Template, pkg_dir::AbstractString)
source(p) === nothing &amp;&amp; return
pkg = basename(pkg_dir)
path = joinpath(pkg_dir, destination(p))
text = render_plugin(p, t, pkg)
gen_file(path, text)
end</code></pre><p>But what if we want to do a little more than just generate one file?</p><p>A good example of this is the <a href="../user/#PkgTemplates.Tests"><code>Tests</code></a> plugin. It creates <code>runtests.jl</code>, but it also modifies the <code>Project.toml</code> to include the <code>Test</code> dependency.</p><p>Of course, we could use a normal <a href="#PkgTemplates.Plugin"><code>Plugin</code></a>, but it turns out there&#39;s a way to avoid that while still getting the extra capbilities that we want.</p><p>The plugin implements its own <code>hook</code>, but uses <code>invoke</code> to avoid duplicating the file creation code:</p><pre><code class="language-julia">@with_kw_noshow struct Tests &lt;: BasicPlugin
file::String = default_file(&quot;runtests.jl&quot;)
end
source(p::Tests) = p.file
destination(::Tests) = joinpath(&quot;test&quot;, &quot;runtests.jl&quot;)
view(::Tests, ::Template, pkg::AbstractString) = Dict(&quot;PKG&quot; =&gt; pkg)
function hook(p::Tests, t::Template, pkg_dir::AbstractString)
# Do the normal BasicPlugin behaviour to create the test script.
invoke(hook, Tuple{BasicPlugin, Template, AbstractString}, p, t, pkg_dir)
# Do some other work.
add_test_dependency()
end</code></pre><p>There is also a default <a href="#PkgTemplates.validate"><code>validate</code></a> implementation for <a href="#PkgTemplates.BasicPlugin"><code>BasicPlugin</code></a>s, which checks that the plugin&#39;s <a href="#PkgTemplates.source"><code>source</code></a> file exists, and throws an <code>ArgumentError</code> otherwise. If you want to extend the validation but keep the file existence check, use the <code>invoke</code> method as described above.</p><p>For more examples, see the plugins in the <a href="../user/#Continuous-Integration-(CI)-1">Continuous Integration (CI)</a> and <a href="../user/#Code-Coverage-1">Code Coverage</a> sections.</p><h2><a class="nav-anchor" id="Miscellaneous-Tips-1" href="#Miscellaneous-Tips-1">Miscellaneous Tips</a></h2><h3><a class="nav-anchor" id="Writing-Template-Files-1" href="#Writing-Template-Files-1">Writing Template Files</a></h3><p>For an overview of writing template files for Mustache.jl, see <a href="../user/#Custom-Template-Files-1">Custom Template Files</a> in the user guide.</p><h3><a class="nav-anchor" id="Predicates-1" href="#Predicates-1">Predicates</a></h3><p>There are a few predicate functions for plugins that are occasionally used to answer questions like &quot;does this <code>Template</code> have any code coverage plugins?&quot;. If you&#39;re implementing a plugin that fits into one of the following categories, it would be wise to implement the corresponding predicate function to return <code>true</code> for instances of your type.</p><section class="docstring"><div class="docstring-header"><a class="docstring-binding" id="PkgTemplates.needs_username" href="#PkgTemplates.needs_username"><code>PkgTemplates.needs_username</code></a><span class="docstring-category">Function</span>.</div><div><div><pre><code class="language-julia">needs_username(::Plugin) -&gt; Bool</code></pre><p>Determine whether or not a plugin needs a Git hosting service username to function correctly. If you are implementing a plugin that uses the <code>user</code> field of a <a href="../user/#PkgTemplates.Template"><code>Template</code></a>, you should implement this function and return <code>true</code>.</p></div></div><a class="source-link" target="_blank" href="https://github.com/invenia/PkgTemplates.jl/blob/39350c10de378964a3fa673176602c93d532acdb/src/plugin.jl#LL245-L251">source</a></section><section class="docstring"><div class="docstring-header"><a class="docstring-binding" id="PkgTemplates.is_ci" href="#PkgTemplates.is_ci"><code>PkgTemplates.is_ci</code></a><span class="docstring-category">Function</span>.</div><div><div><pre><code class="language-julia">is_ci(::Plugin) -&gt; Bool</code></pre><p>Determine whether or not a plugin is a CI plugin. If you are adding a CI plugin, you should implement this function and return <code>true</code>.</p></div></div><a class="source-link" target="_blank" href="https://github.com/invenia/PkgTemplates.jl/blob/39350c10de378964a3fa673176602c93d532acdb/src/plugins/ci.jl#LL403-L408">source</a></section><section class="docstring"><div class="docstring-header"><a class="docstring-binding" id="PkgTemplates.is_coverage" href="#PkgTemplates.is_coverage"><code>PkgTemplates.is_coverage</code></a><span class="docstring-category">Function</span>.</div><div><div><pre><code class="language-julia">is_coverage(::Plugin) -&gt; Bool</code></pre><p>Determine whether or not a plugin is a coverage plugin. If you are adding a coverage plugin, you should implement this function and return <code>true</code>.</p></div></div><a class="source-link" target="_blank" href="https://github.com/invenia/PkgTemplates.jl/blob/39350c10de378964a3fa673176602c93d532acdb/src/plugins/coverage.jl#LL49-L54">source</a></section><h3><a class="nav-anchor" id="Formatting-Version-Numbers-1" href="#Formatting-Version-Numbers-1">Formatting Version Numbers</a></h3><p>When writing configuration files for CI services, working with version numbers is often needed. There are a few convenience functions that can be used to make this a little bit easier.</p><section class="docstring"><div class="docstring-header"><a class="docstring-binding" id="PkgTemplates.compat_version" href="#PkgTemplates.compat_version"><code>PkgTemplates.compat_version</code></a><span class="docstring-category">Function</span>.</div><div><div><pre><code class="language-julia">compat_version(v::VersionNumber) -&gt; String</code></pre><p>Format a <code>VersionNumber</code> to exclude trailing zero components.</p></div></div><a class="source-link" target="_blank" href="https://github.com/invenia/PkgTemplates.jl/blob/39350c10de378964a3fa673176602c93d532acdb/src/plugins/project_file.jl#LL27-L31">source</a></section><section class="docstring"><div class="docstring-header"><a class="docstring-binding" id="PkgTemplates.format_version" href="#PkgTemplates.format_version"><code>PkgTemplates.format_version</code></a><span class="docstring-category">Function</span>.</div><div><div><pre><code class="language-julia">format_version(v::Union{VersionNumber, AbstractString}) -&gt; String</code></pre><p>Strip everything but the major and minor release from a <code>VersionNumber</code>. Strings are left in their original form.</p></div></div><a class="source-link" target="_blank" href="https://github.com/invenia/PkgTemplates.jl/blob/39350c10de378964a3fa673176602c93d532acdb/src/plugins/ci.jl#LL1-L6">source</a></section><section class="docstring"><div class="docstring-header"><a class="docstring-binding" id="PkgTemplates.collect_versions" href="#PkgTemplates.collect_versions"><code>PkgTemplates.collect_versions</code></a><span class="docstring-category">Function</span>.</div><div><div><pre><code class="language-julia">collect_versions(t::Template, versions::Vector) -&gt; Vector{String}</code></pre><p>Combine <code>t</code>&#39;s Julia version with <code>versions</code>, and format them as <code>major.minor</code>. This is useful for creating lists of versions to be included in CI configurations.</p></div></div><a class="source-link" target="_blank" href="https://github.com/invenia/PkgTemplates.jl/blob/39350c10de378964a3fa673176602c93d532acdb/src/plugins/ci.jl#LL392-L397">source</a></section><footer><hr/><a class="previous" href="../user/"><span class="direction">Previous</span><span class="title">User Guide</span></a><a class="next" href="../migrating/"><span class="direction">Next</span><span class="title">Migrating To PkgTemplates 0.7+</span></a></footer></article></body></html>