Tutorial¶
This tutorial dives right into the core concepts you need in order to:
Collect notes matching a schema from contributors.
Lint notes, ensuring that contributors provide notes and that they match the schema.
Render notes in however you need, whether that’s for changelog generation or other automations such as version bumping.
We’ll first cover an example schema and how users can create or update notes.
An Example Schema and Note¶
detail
works by reading a schema that’s specified in the
.detail/schema.yaml
file. The schema represents a
formaldict schema, providing
the means to prompt users for relevant information in the schema.
In this example, we want to ensure that our notes all have the following information:
A summary about the contribution.
An optional description.
A required type. We want to prompt the user to categorize the type of contribution with the following choices - api-break, bug, feature, or trivial.
Our schema looks like this:
- label: type
name: Type
help: The type of change.
type: string
choices:
- api-break
- bug
- feature
- trivial
- label: summary
name: Summary
help: A high-level summary.
type: string
- label: description
name: Description
help: An in-depth description.
type: string
condition: ['!=', 'type', 'trivial']
multiline: True
required: False
In the above, our schema has a “type” attribute that will be collected. When we prompt the user for the type, the user will see the help text and will only be able to enter one of the four choices.
The formaldict library that parses the schema is directly integrated with python-prompt-toolkit, meaning any information entered gathered by the prompt will validate the schema (and our linting we will do later!).
When the schema is stored in the .detail/schema.yaml
of a project, a user will
go through this flow when creating a note (i.e. calling detail
from the command line):
The note is stored under .detail/notes/<year>_<month>_<day>_<hash>.yaml
and looks like this:
> cat .detail/notes/2022-03-21-67c3d1.yaml
"description": |-
Wow, what a wonderful feature. This
message can span multiple lines too.
"summary": |-
This is a great feature
"type": |-
feature
Now that the note is saved, you can git commit
it to the project and use the
other detail
subcommands.
Linting Notes¶
detail
comes with a linter, allowing verification that at least one
note was provided, along with ensuring that each note adheres to the schema.
When running the linter, one must supply a range of commits
to validate. Normally this is done against the branch over which a pull
request will be submitted. For example, detail lint origin/develop..
will lint all of the commits on your branch since origin/develop
.
Linting will pass if any of the following are true:
There are no commits in the range. For example, running
detail lint main..
while on themain
branch will result in linting passing since there are no commits.There is at least one commit, at least one note, and all notes present in the commit range adhere to the
detail
schema.
detail
comes built in with Github support. In order to lint against all
of the commits against the base branch of an open pull request, run
detail lint :github/pr
. The special “:github/pr” range tells
detail
to look up the base branch of the open pull request.
Note
One must provision a GITHUB_API_TOKEN
environment variable with
a personal access token from Github in order to enable this feature.
Rendering Notes¶
Want to render a changelog or extract information from your notes for
performing automations? Notes can be rendered by a user-supplied Jinja
template and the detail log
subcommand.
By default, detail
checks the .detail/log.tpl
for the log to use
when calling detail log
. This Jinja template is supplied the following
variables:
output
- The output file, if any, supplied todetail log
.range
- The range of commits (if any) being logged.notes
- Thedetail.NoteRange
object retrieved when parsing any git filters provided todetail log
. Thedetail.NoteRange
object has methods for grouping and filtering notes by their values and associated git properties.
Similar to linting, detail log
takes a range of commits as an argument.
For example, to render a log with everything after tag v1
, do
detail log v1..
.
Here’s an example of a
Jinja
template to render our example schema. Again, this
template is stored in our repo at .detail/log.tpl
:
{% for tag, notes_by_tag in notes.group('commit_tag').items() %}
## {{ tag|default('Unreleased', True) }} {% if tag.date %}({{ tag.date.date() }}){% endif %}
{% for type, notes_by_type in notes_by_tag.group('type', ascending_keys=True, none_key_last=True).items() %}
### {{ type|default('Other', True)|title }}
{% for note in notes_by_type %}
- {{ note.summary }} [{{ note.commit_author_name }}, {{ note.commit_sha[:7] }}]
{% if note.description %}
{{ note.description|indent(2) }}
{% endif %}
{% endfor %}
{% endfor %}
{% endfor %}
In the above template, we are grouping our notes by tag first and by type second. Here’s what it looks like to log two example notes using this template:
Note
Remember that notes must be committed before they can be linted or logged. This is what allows us to attached git information to notes, such as tags and authors.
In the above example, we’re rendering our summary, description, and other information gathered by our schema. We’re also rendering git-based information such as tags and commit authors.
detail
collects git-based information under the commit_*
attribute of each
note. Below are the git-based attributes you can group by and render.
commit_sha
- Full SHA of the commit.commit_author_name
- The author name of the commit.commit_author_email
- The author email of the commit.commit_author_date
- The time at which the commit was authored.commit_committer_name
- The name of the person who performed the commit.commit_committer_email
- The email of the person who performed the commit.commit_committer_date
- The time at which the commit was performed.commit_tag
- The tag, if any, that contains the commit.
Along with commit information, one can also render if notes didn’t pass vaildation. In theory one shouldn’t let notes that don’t pass schema validation through. These attributes, however, allow one to render invalid notes and errors:
is_valid
-True
if the note passed validation,False
otherwise. IfFalse
, some of the user-supplied attributes may not be available.validation_errors
- The validation errors, if any, that happened.
Note
All of these attributes, along with any defined in the commit,
can be used for grouping and filtering detail.NoteRange
objects.
If one wishes to have separate templates for separate types of renderings
(e.g. a verbose change log vs a summary), use the --style
option.
The --style
option will use a template under the path of
.detail/log_{{ style }}.tpl
.
A template definition can also be passed directly to the command line
via the --template
option.
For example, let’s say one wishes to print off all type
attributes
of notes in our example schema:
detail log --template '{% for note in notes %}{{ note.type }}\n{% endfor %}'
detail log
takes other parameters for filtering notes based on commits.
It also takes parameters to ensure proper tags are matched whenever mutliple
tags might reference the same note. Use detail log --help
to see
all options or check out the Detail CLI docs.
Note
detail log
has the ability to redirect the log to a file with the
-o
option. One can also use detail log :github/pr -o :github/pr
to generate a log based on a Github pull request and also send the output
as a Github pull request comment. This can be used for integrating log
previews into your continuous integration and pull request review process.
More Examples¶
For more examples of schema specifications, check out the
formaldict Docs.
Check the docs for detail.NoteRange
for all of the available methods one can
use when rendering commits in templates. And finally, brush up on your
Jinja for
more expressive log rendering.