Skip to content

Commit

Permalink
Merge pull request #294 from AdmiringWorm/template-improvements
Browse files Browse the repository at this point in the history
(GH-217) Additional improvements to fine-grained templates
  • Loading branch information
gep13 authored Dec 30, 2020
2 parents ab75056 + 23d0f7b commit bac02d9
Show file tree
Hide file tree
Showing 57 changed files with 1,427 additions and 156 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -327,3 +327,4 @@ config.wyam.packages.xml
# Integration Tests
tests/integration/tools
tests/integration/output
*.g.cs
2 changes: 2 additions & 0 deletions docs/input/docs/commands/init.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ controls the configurable options of GitReleaseManager
executed. Defaults to current directory.
- `-l, --logFilePath`: Path to where log file should be created. Defaults to
logging to console.
- `--templates`: Extract all embedded resource templates to disk. Defaults to false.
(_Will not overwrite existing files_).

## **Examples**

Expand Down
9 changes: 8 additions & 1 deletion docs/input/docs/configuration/create-configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,13 @@ release can be located.
Take for example the GitReleaseManager configuration file which is used by the
[Chocolatey GUI](https://github.com/chocolatey/ChocolateyGUI) project:

:::{.alert .alert-warning}
Configuring the footer is no longer recommended. Going forward the recommendation
is to configure the footer using separate template files.
Please see [Template Configuration](template-configuration#editing-the-templates)
for more information.
:::

```yaml
create:
include-footer: true
Expand All @@ -28,7 +35,7 @@ This would result in the following
[release notes](https://github.com/chocolatey/ChocolateyGUI/releases/tag/0.13.1)
being generated:
![Example Release Notes](../images/example-release-notes.png)
![Example Release Notes](../images/example-release-notes.png){.img-responsive}
:::{.alert .alert-info}
The generated URL for the link to Chocolatey.org includes the milestone number.
Expand Down
7 changes: 3 additions & 4 deletions docs/input/docs/configuration/default-configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,10 @@ when no yaml file is placed in the root directory):

```yaml
create:
# Please see
# https://gittools.github.io/GitReleaseManager/docs/configuration/template-configuration#editing-the-templates
# configuration for configuring footers
include-footer: false
footer-heading: ''
footer-content: ''
footer-includes-milestone: false
milestone-replace-text: ''
include-sha-section: false
sha-section-heading: "SHA256 Hashes of the release artifacts"
sha-section-line-format: "- `{1}\t{0}`"
Expand Down
259 changes: 259 additions & 0 deletions docs/input/docs/configuration/template-configuration.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,259 @@
---
Order: 11
Title: Template Configuration
---

Welcome to the documentation on how to configure each available step
of release notes generation by using Scriban templates.
While you can still use the old way of configuring templates (footer only) in
the yaml file, going forward it is recommended to use the new approach by
extracting and editing the template files you wish to change instead.

## How are templates resolved

:::{.alert .alert-info}
If you will be passing in a custom absolute path to a template,
then you can skip this section entirely (assuming you won't be importing
any files either).
:::

Before we can go into how you can edit your templates, we first need to talk
about how are the templates resolved.
First of all, there is a new property that can be used to specify the base
directory of the templates directory located in the yaml configuration file.
This configuration is called `templates-dir` and will be used for all relative
paths (and for template names).

The templates are resolved in the following order (`<base>` is the value
specified in the `templates-dir` directory, and `<name>` is the name passed
to GitReleaseManager. _defaults to `default`_).

We will be using the [create](../commands/create) as an example in these paths.
Additional commands are planned to be supported, but for now these templates
can only be used when calling `GitReleaseManager create`.

**For abbreviation, file extensions are omitted from the paths.
File extension are expected to be either `.sbn` or `.scriban` unless otherwise
specified when passing in the template name when calling `GitReleaseManager`.**

### Resolving the initial index file

1. `<base>/<name>/create/index`
2. `<base>/<name>/create/<name>`
3. `<base>/create/<name>/index`
4. `<base>/create/<name>/<name>`
5. `<base>/<name>/index`
6. `<base>/<name>/<name>`
7. `<base>/create/index` (_will disable fallback to embedded resource if found_)
8. `<base>/create/<name>` (_will disable fallback to embedded resource if found_)
9. `<base>/<name>` (_will disable fallback to embedded resource if found_)

In the above resolution, if there is no file that can be found, GitReleaseManager
will try to fall back into the resources that are embedded within.

### Resolving child files

Each index file (and subsequent children) can include additional files that
needs to be imported (Please see the [Scriban documentation](https://github.com/scriban/scriban/blob/master/doc/language.md#99-include-name-arg1argn) for this).

When this is being used, some paths are removed and others may be added.
In these paths the following substitution values are being used.

- `<base>` == The base directory as specified by the `templates-dir` property.
- `<template>` == The name of the template being used without a file extension
(_may not be available in all scenarios_)
- `<relative-path>` == The directory that the previous template file was
located in.
- `<name>` == The name of the file (excluding any directories)
- `<name-dir>` == The parent directory of the file specified in the `<name>` argument.

Resolution is done in two separate ways, depending on where the previous template
file was located.

Let us start with the simplest form, when the previous file was located on the
file system.
In this case, the resolution will only probe two distinct paths with the folliwng:

1. `<relative-path>/<name>`
2. `<relative-path>/<name-dir>/<name>`
3. Fallback to embedded resource when possible.

When the previous template file was an embedded resource file, then the resolution
follows a similar procedure as the initial resolution of the main/index template.

1. `<base>/<template>/create/<name>`
2. `<base>/create/<template>/<name>`
3. `<base>/<template>/<name>`
4. `<base>/create/<name>` (_will disable fallback to embedded resource if found_)
5. `<base>/<name>` (_will disable fallback to embedded resource if found_)
6. Fallback to embedded resource when possible

## Extracting embedded templates

Now on to the more fun parts, we will start exploring how you can make changes to
the template.
But first, we need some templates to work on.
While you could manually create and edit them by hand, the easier solution would
be to extract the existing templates that GitReleaseManager already makes use of.

This is very simple to do.
First ensure that you have set the `template-dir` property in the yaml configuration
file to the directory you want to use as the base of the templates (_this defaults
to `.target`_).
Then just run the following command:

```console
GitReleaseManager init --templates
```

or

```console
dotnet gitreleasemanager init --templates
```

The above commands will extract all embedded templates that GitReleaseManager
are aware of to you local file system (_you can delete the template files
you are not interested in_).

## Editing the templates

The following json object is made available on the global state in each template.
Some template files may have additional objects available (_assuming parent template
have not been modified_).

```json
{
"commits": {
"count": 5, // The amount of commits since the last tag
"html_url": "<compare_commits>" // The URL to show/compare commits since last tag
},
"issue_labels": [
// This list contains all of the labels associated with a closed issue/pr
// for the current release
"Bug",
"Feature",
],
"issues": {
"count": 2, // The amount of issues/prs being closed since last tag
"items": {
// Key is the substitution of the issue label found for these issues,
"<key>": [
{
"title": "First Issues", // The title of the issue
"number": 54, // The issue number as shown on the VCS Provider
"html_url": "<issue_link>", // The link to the issue
"labels": [ // All of the labels associated with this issue
{
"name": "Bug", // The name of the label
"color": "#456789", // The color of the label
"description": "Important, must fix" // The description of the label
}
]
}
]
}
},
"milestone": {
// The previous milestone, if one was found; otherwise it will be null
"previous": {
"title": "Version 5.2.3", // The title of the previous milestone
"description": "Some description", // The description of the previous milestone
"number": 3243, // The number/identifier of the previous milestone
"html_url": "<url>", // The url to issues associated with the previous milestone
"url": "<url>",
"version": {
"major": 5, // The major version of the previous milestone
"minor": 2, // The minor version of the previous milestone
"build": 3, // The patch/build version of the previous milestone
"revision": 0 // The revision of the previous milestone (typically 0)
}
},
// The current milestone for this release
"target": {
"title": "Version 5.3.0", // The title of the current/next milestone
"description": "I am up", // The description of the current/next milestone
"number": 3265, // The number/identifier of the current/next milestone
"html_url": "<url>", // The url to issues associated with the current/next milestone
"url": "<url>",
"version": {
"major": 5, // The major version of the current/next milestone
"minor": 3, // The minor version of the current/next milestone
"build": 0, // The patch/build version of the current/next milestone
"revision": 0 // The revision of the current/next milestone (typically 0)
}
}
}
}
```

### Using a single file

While GitReleaseManager makes use of multiple template files to achieve what
a single template file could do, there is nothing that stops you to make the
necessary changes to make this happen.
Just add a single file in one of the mentioned
[index resolution paths](#resolving-the-initial-index-file) above,
and delete all of the other files (remember to not make use of the `include`
function in the template as well).

### Editing the index template

In most cases, there is no need to edit the index file, other than if you want to
include/remove other templates or merge all templates into one.
The name of the embedded/extracted index file is called `index.sbn` as its only
purpose is to bind together all the children together.

### Editing the commits/issues intro

The commits/issues intro template (called `release-info.sbn`) purpose is to
give a small intro on how many commits was included in the release, and
how many issues/prs was closed.

While the file can look a bit intimitading at first, it is actually quite simple.
The default template checks if there are any issues or commits and makes decisions
based on the if there were any.
It additionally pluralizes (simplified) when the issue/commit count is
more than 1.

This file can be used if you wish to change how the text should be displayed.

### Editing the milestone

By default, this template is very simple (called `milestone.sbn`).
It outputs only the description of the current/next milestone (or empty when
there is no description).

This file can be used if you want to provide additional details regarding the
milestone.

### Editing the issues

Now we are getting into the meat of the templates.
The generation of the issues (actual Release Notes) are made possible in three
separate files.
Each with their own purpose.

- We have the template `issues.sbn` which only have the purpose of iterating through
each issue label for grouping purposes, then calling a child template to do the
actual rendering.
- Next up is the `issue-details.sbn` template, which have the purpose of rendering
the issue label (or category if you want), then calling another child template
for rendering the actual issues (passing in a single issue to the child at a time).
Additionally thanks to the parent, this template also have another global variable
added to the global namespace called `issue_label`. The variable is the current
label/category for the issues that should be used.
- Finally we have the `issue-note.sbn` template which renders the actual line of
the issue, including its issue number, issue url and issue title.
This issue template have even one more global variable called `issue` being
made available thanks to its parent.
This issue variable is a single item located in the `issues.items` json object.

### Editing the footer

Finally for the last embedded template available, we have the footer template.
This template is responsible for generating the footer of the Release Notes.
By default it makes use of the old properties specified in the yaml configuration
file, but going forward it is recommended to edit this file manually instead of
using the yaml configuration properties.
This file is called simply `footer.sbn` and is located in the `create` directory.
13 changes: 13 additions & 0 deletions recipe.cake
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
#load nuget:?package=Cake.Recipe&version=2.1.0
#tool dotnet:?package=dotnet-t4&version=2.0.5

Environment.SetVariableNames(githubTokenVariable: "GITTOOLS_GITHUB_TOKEN");

Expand Down Expand Up @@ -43,6 +44,18 @@ BuildParameters.Tasks.DotNetCoreBuildTask.Does((context) =>
BuildParameters.Tasks.CreateReleaseNotesTask
.IsDependentOn(BuildParameters.Tasks.DotNetCoreBuildTask); // We need to be sure that the executable exist, and have been registered before using it

Task("Transform-TextTemplates")
.IsDependeeOf(BuildParameters.Tasks.DotNetCoreBuildTask.Task.Name)
.Does(() =>
{
var templates = GetFiles("src/**/*.tt");
foreach (var template in templates)
{
TransformTemplate(template);
}
});

((CakeTask)BuildParameters.Tasks.ExportReleaseNotesTask.Task).ErrorHandler = null;
((CakeTask)BuildParameters.Tasks.PublishGitHubReleaseTask.Task).ErrorHandler = null;
BuildParameters.Tasks.PublishPreReleasePackagesTask.IsDependentOn(BuildParameters.Tasks.PublishGitHubReleaseTask);
Expand Down
9 changes: 8 additions & 1 deletion src/GitReleaseManager.Cli/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ namespace GitReleaseManager.Cli
using GitReleaseManager.Core.Options;
using GitReleaseManager.Core.Provider;
using GitReleaseManager.Core.ReleaseNotes;
using GitReleaseManager.Core.Templates;
using Microsoft.Extensions.DependencyInjection;
using Octokit;
using Serilog;
Expand Down Expand Up @@ -78,7 +79,7 @@ private static async Task<int> Main(string[] args)

private static void RegisterServices(BaseSubOptions options)
{
var fileSystem = new FileSystem();
var fileSystem = new FileSystem(options);
var logger = Log.ForContext<VcsService>();
var mapper = AutoMapperConfiguration.Configure();
var configuration = ConfigurationProvider.Provide(options.TargetDirectory ?? Environment.CurrentDirectory, fileSystem);
Expand Down Expand Up @@ -116,6 +117,12 @@ private static void RegisterServices(BaseSubOptions options)
.AddSingleton<IGitHubClient>(gitHubClient);
}

if (options is CreateSubOptions)
{
serviceCollection = serviceCollection
.AddTransient((services) => new TemplateFactory(services.GetRequiredService<IFileSystem>(), services.GetRequiredService<Config>(), TemplateKind.Create));
}

_serviceProvider = serviceCollection.BuildServiceProvider();
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,13 +50,13 @@ public async Task Should_Create_Release_From_Milestone(string name, int logVerbo

var releaseName = options.Name ?? options.Milestone;

_vcsService.CreateReleaseFromMilestoneAsync(options.RepositoryOwner, options.RepositoryName, options.Milestone, releaseName, options.TargetCommitish, options.AssetPaths, options.Prerelease, options.TemplateFilePath)
_vcsService.CreateReleaseFromMilestoneAsync(options.RepositoryOwner, options.RepositoryName, options.Milestone, releaseName, options.TargetCommitish, options.AssetPaths, options.Prerelease, options.Template)
.Returns(_release);

var result = await _command.Execute(options).ConfigureAwait(false);
result.ShouldBe(0);

await _vcsService.Received(1).CreateReleaseFromMilestoneAsync(options.RepositoryOwner, options.RepositoryName, options.Milestone, releaseName, options.TargetCommitish, options.AssetPaths, options.Prerelease, options.TemplateFilePath).ConfigureAwait(false);
await _vcsService.Received(1).CreateReleaseFromMilestoneAsync(options.RepositoryOwner, options.RepositoryName, options.Milestone, releaseName, options.TargetCommitish, options.AssetPaths, options.Prerelease, options.Template).ConfigureAwait(false);
_logger.Received(1).Information(Arg.Any<string>());
_logger.Received(logVerboseCount).Verbose(Arg.Any<string>(), options.Milestone);
_logger.Received(1).Information(Arg.Any<string>(), _release.HtmlUrl);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ public class InitCommandTests
[SetUp]
public void Setup()
{
_fileSystem = new FileSystem();
_fileSystem = new FileSystem(Substitute.For<BaseSubOptions>());
_logger = Substitute.For<ILogger>();
_command = new InitCommand(_fileSystem, _logger);
_targetDirectory = Path.GetTempPath();
Expand Down
Loading

0 comments on commit bac02d9

Please sign in to comment.