-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, <<<>/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; _ -> <<<>/binary, (gleam@string:join(Sections, <<"\n\n"/utf8>>))/binary>> end.