-module(version_bump@note).
-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]).
-define(FILEPATH, "src/version_bump/note.gleam").
-export([generate/3]).
-if(?OTP_RELEASE >= 27).
-define(MODULEDOC(Str), -moduledoc(Str)).
-define(DOC(Str), -doc(Str)).
-else.
-define(MODULEDOC(Str), -compile([])).
-define(DOC(Str), -compile([])).
-endif.
?MODULEDOC(
" Generate Markdown release notes from parsed conventional commits.\n"
"\n"
" Commits are grouped into sections by type (Features, Bug Fixes,\n"
" Performance) plus a dedicated \"BREAKING CHANGES\" section that lists the\n"
" breaking-change note text for any breaking commit. Empty sections are\n"
" omitted. All functions here are PURE — given the same input they always\n"
" produce the same Markdown string.\n"
).
-file("src/version_bump/note.gleam", 118).
?DOC(
" Extract the text of the first `BREAKING CHANGE`/`BREAKING CHANGES` note,\n"
" or \"\" when there is no such note.\n"
).
-spec breaking_text(list(version_bump@commit_parser:commit_note())) -> binary().
breaking_text(Notes) ->
Found = gleam@list:find(
Notes,
fun(N) ->
Title = string:uppercase(erlang:element(2, N)),
(Title =:= <<"BREAKING CHANGE"/utf8>>) orelse (Title =:= <<"BREAKING CHANGES"/utf8>>)
end
),
case Found of
{ok, Note} ->
erlang:element(3, Note);
{error, _} ->
<<""/utf8>>
end.
-file("src/version_bump/note.gleam", 103).
?DOC(" Render a single breaking-change bullet using the note text when present.\n").
-spec breaking_line(version_bump@commit_parser:conventional_commit()) -> binary().
breaking_line(C) ->
Text = breaking_text(erlang:element(7, C)),
Body = case Text of
<<""/utf8>> ->
erlang:element(5, C);
_ ->
Text
end,
Scope_prefix = case erlang:element(4, C) of
{some, S} ->
<<<<"**"/utf8, S/binary>>/binary, ":** "/utf8>>;
none ->
<<""/utf8>>
end,
<<<<"* "/utf8, Scope_prefix/binary>>/binary, Body/binary>>.
-file("src/version_bump/note.gleam", 91).
?DOC(
" Build the BREAKING CHANGES section from any breaking commits. The breaking\n"
" note text is preferred; if a breaking commit has no `BREAKING CHANGE` note,\n"
" its subject is used as a fallback. Returns `Error(Nil)` when none break.\n"
).
-spec breaking_section(list(version_bump@commit_parser:conventional_commit())) -> {ok,
binary()} |
{error, nil}.
breaking_section(Commits) ->
Breaking = gleam@list:filter(Commits, fun(C) -> erlang:element(6, C) end),
case Breaking of
[] ->
{error, nil};
_ ->
Lines = gleam@list:map(Breaking, fun breaking_line/1),
{ok,
<<"### BREAKING CHANGES\n\n"/utf8,
(gleam@string:join(Lines, <<"\n"/utf8>>))/binary>>}
end.
-file("src/version_bump/note.gleam", 44).
?DOC(" Keep only commits whose conventional type matches `type_name`.\n").
-spec filter_type(
list(version_bump@commit_parser:conventional_commit()),
binary()
) -> list(version_bump@commit_parser:conventional_commit()).
filter_type(Commits, Type_name) ->
gleam@list:filter(Commits, fun(C) -> case erlang:element(3, C) of
{some, T} ->
T =:= Type_name;
none ->
false
end end).
-file("src/version_bump/note.gleam", 75).
?DOC(
" Render a single commit as a Markdown bullet:\n"
" `* **scope:** subject (shorthash)`\n"
" When there is no scope the `**scope:**` prefix is omitted:\n"
" `* subject (shorthash)`\n"
).
-spec commit_line(version_bump@commit_parser:conventional_commit()) -> binary().
commit_line(C) ->
Scope_prefix = case erlang:element(4, C) of
{some, S} ->
<<<<"**"/utf8, S/binary>>/binary, ":** "/utf8>>;
none ->
<<""/utf8>>
end,
Short = erlang:element(3, erlang:element(2, C)),
Hash_suffix = case Short of
<<""/utf8>> ->
<<""/utf8>>;
_ ->
<<<<" ("/utf8, Short/binary>>/binary, ")"/utf8>>
end,
<<<<<<"* "/utf8, Scope_prefix/binary>>/binary,
(erlang:element(5, C))/binary>>/binary,
Hash_suffix/binary>>.
-file("src/version_bump/note.gleam", 58).
?DOC(
" Build a non-breaking section (Features / Bug Fixes / Performance). Returns\n"
" `Error(Nil)` when there are no commits so the caller can omit it.\n"
).
-spec section(binary(), list(version_bump@commit_parser:conventional_commit())) -> {ok,
binary()} |
{error, nil}.
section(Heading, Commits) ->
case Commits of
[] ->
{error, nil};
_ ->
Lines = gleam@list:map(Commits, fun commit_line/1),
{ok,
<<<<Heading/binary, "\n\n"/utf8>>/binary,
(gleam@string:join(Lines, <<"\n"/utf8>>))/binary>>}
end.
-file("src/version_bump/note.gleam", 19).
?DOC(
" Generate the full Markdown release notes for the given commits and version.\n"
"\n"
" `repo_url` is currently accepted for future link generation; the output is\n"
" stable regardless of whether it is provided. Sections that have no matching\n"
" commits are omitted entirely.\n"
).
-spec generate(
list(version_bump@commit_parser:conventional_commit()),
binary(),
gleam@option:option(binary())
) -> binary().
generate(Commits, Version, Repo_url) ->
_ = Repo_url,
Sections = begin
_pipe = [section(
<<"### Features"/utf8>>,
filter_type(Commits, <<"feat"/utf8>>)
),
section(
<<"### Bug Fixes"/utf8>>,
filter_type(Commits, <<"fix"/utf8>>)
),
section(
<<"### Performance"/utf8>>,
filter_type(Commits, <<"perf"/utf8>>)
),
breaking_section(Commits)],
gleam@list:filter_map(_pipe, fun(S) -> S end)
end,
Heading = <<"## "/utf8, Version/binary>>,
case Sections of
[] ->
Heading;
_ ->
<<<<Heading/binary, "\n\n"/utf8>>/binary,
(gleam@string:join(Sections, <<"\n\n"/utf8>>))/binary>>
end.