\RequirePackage{expl3}
\ProvidesExplPackage{changelog}{2024/12/18}{2.6.0}{Typesetting changelogs}
% Description: Provides the changelog environment for typesetting changelogs
% License:     LPPL 1.3c
% Homepage:    https://github.com/9999years/latex-changelog
%              https://ctan.org/pkg/changelog
% Maintainer:  Rebecca Turner <rbt@sent.as>
%
% This work may be distributed and/or modified under the
% conditions of the LaTeX Project Public License, either version 1.3
% of this license or (at your option) any later version.
% The latest version of this license is in
%   http://www.latex-project.org/lppl.txt
% and version 1.3 or later is part of all distributions of LaTeX
% version 2005/12/01 or later.
%
% This work has the LPPL maintenance status `maintained'.
%
% The Current Maintainer of this work is Rebecca Turner <rbt@sent.as>.
%
% This work consists of the files changelog.sty and changelog.tex.

\RequirePackage{translations}

% See https://github.com/olivierlacan/keep-a-changelog/issues/195
% for a discussion about the term yanked

% Fallback translations will be used if there is no translation for
% the current document language
\DeclareTranslationFallback{changelog}{Changelog}
\DeclareTranslationFallback{changelog-Added}{Added}
\DeclareTranslationFallback{changelog-Changed}{Changed}
\DeclareTranslationFallback{changelog-Deprecated}{Deprecated}
\DeclareTranslationFallback{changelog-Removed}{Removed}
\DeclareTranslationFallback{changelog-Fixed}{Fixed}
\DeclareTranslationFallback{changelog-Security}{Security}
\DeclareTranslationFallback{changelog-Miscellaneous}{Miscellaneous}
\DeclareTranslationFallback{changelog-Unreleased}{Unreleased}
\DeclareTranslationFallback{changelog-Yanked}{YANKED}
% English translations by Rebecca Turner <rbt@sent.as>
\DeclareTranslation{English}{changelog}{Changelog}
\DeclareTranslation{English}{changelog-Added}{Added}
\DeclareTranslation{English}{changelog-Changed}{Changed}
\DeclareTranslation{English}{changelog-Deprecated}{Deprecated}
\DeclareTranslation{English}{changelog-Removed}{Removed}
\DeclareTranslation{English}{changelog-Fixed}{Fixed}
\DeclareTranslation{English}{changelog-Security}{Security}
\DeclareTranslation{English}{changelog-Miscellaneous}{Miscellaneous}
\DeclareTranslation{English}{changelog-Unreleased}{Unreleased}
\DeclareTranslation{English}{changelog-Yanked}{YANKED}
% German translations by Holger Schieferdecker
% Alternative german translations as comment at the end of the line
\DeclareTranslation{German}{changelog}{\"Anderungsnachweis}
\DeclareTranslation{German}{changelog-Added}{Hinzugef\"{u}gt}% Neu
\DeclareTranslation{German}{changelog-Changed}{Ge\"{a}ndert}
\DeclareTranslation{German}{changelog-Deprecated}{Überholt}% Veraltet
\DeclareTranslation{German}{changelog-Removed}{Entfernt}
\DeclareTranslation{German}{changelog-Fixed}{Behoben}% Fehlerbehebung
\DeclareTranslation{German}{changelog-Security}{Sicherheit}
\DeclareTranslation{German}{changelog-Miscellaneous}{Verschiedenes}
\DeclareTranslation{German}{changelog-Unreleased}{Unver\"{o}ffentlicht}
\DeclareTranslation{German}{changelog-Yanked}{Zur{\"u}ckgezogen}
% Spanish translations by David Arnold <dar@xoe.solutions>
\DeclareTranslation{Spanish}{changelog}{Registro de cambios}
\DeclareTranslation{Spanish}{changelog-Added}{Agregado}
\DeclareTranslation{Spanish}{changelog-Changed}{Cambiado}
\DeclareTranslation{Spanish}{changelog-Deprecated}{Obsoleto}
\DeclareTranslation{Spanish}{changelog-Removed}{Removido}
\DeclareTranslation{Spanish}{changelog-Fixed}{Arreglado}
\DeclareTranslation{Spanish}{changelog-Security}{Seguridad}
\DeclareTranslation{Spanish}{changelog-Miscellaneous}{Miscel{\'a}neo}
\DeclareTranslation{Spanish}{changelog-Unreleased}{No Publicado}
\DeclareTranslation{Spanish}{changelog-Yanked}{REVOCADO}
% French translations by Damien Calesse <github.com/kranack>
\DeclareTranslation{French}{changelog}{Journal des modifications}
\DeclareTranslation{French}{changelog-Added}{Ajouté}
\DeclareTranslation{French}{changelog-Changed}{Modifié}
\DeclareTranslation{French}{changelog-Deprecated}{Déprécié}
\DeclareTranslation{French}{changelog-Removed}{Supprimé}
\DeclareTranslation{French}{changelog-Fixed}{Corrigé}
\DeclareTranslation{French}{changelog-Security}{Sécurité}
\DeclareTranslation{French}{changelog-Miscellaneous}{Divers}
\DeclareTranslation{French}{changelog-Unreleased}{Non publié}
\DeclareTranslation{French}{changelog-Yanked}{Annulé}
% Japanese translations by cmplstofB <github.com/cmplstofB>
\DeclareTranslation{Japanese}{changelog}{変更履歴}
\DeclareTranslation{Japanese}{changelog-Added}{追加}
\DeclareTranslation{Japanese}{changelog-Changed}{変更}
\DeclareTranslation{Japanese}{changelog-Deprecated}{非推奨}
\DeclareTranslation{Japanese}{changelog-Removed}{削除}
\DeclareTranslation{Japanese}{changelog-Fixed}{修正}
\DeclareTranslation{Japanese}{changelog-Security}{安全性}
\DeclareTranslation{Japanese}{changelog-Miscellaneous}{雑題}
\DeclareTranslation{Japanese}{changelog-Unreleased}{未公開}
\DeclareTranslation{Japanese}{changelog-Yanked}{緊急変更}

\msg_new:nnnn { changelog } { missing_section }
  {
    Something's~wrong~in~version~environment;~perhaps~a~missing~
    \protect\added,~\protect\changed,~\protect\deprecated,~
    \protect\removed,~\protect\fixed,~\protect\security,~
    or~\protect\misc?
  }
  {
    A~version~environment~needs~to~introduce~its~
    \protect\item-ized~lists~with~one~of~the~provided~section~commands;~
    maybe~you~meant~to~use~the~[simple]~option?
  }

\msg_new:nnnn { changelog } { missing_version }
  {
    No~versions~listed~in~changelog~environment~body.
  }
  {
    A~changelog~environment~must~have~at~least~one~version~environment~or~
    \protect\shortversion~command~in~it.
  }

\bool_new:N \g__changelog_version_first_bool
\cs_set:Npn \changelog__item:n #1
  {
    \noindent
    \bool_if:NTF \g__changelog_version_first_bool
      {
        \bool_gset_false:N \g__changelog_version_first_bool
      }
      {
        \end{changelogitemize}
      }
    \textbf{#1}
    \begin{changelogitemize}
  }

\cs_set:Npn \changelogremark #1
  {
    \fbox{\textbf{#1}}
  }

\cs_set:Npn \changelogyanked
  {
    \changelogremark{\GetTranslation{changelog-Yanked}}
  }

\tl_new:N \g__changelog_extra_section_cmds
\cs_set:Npn \changelog__define_section_cmds
  {
    \cs_set:Npn \added      {\changelog__item:n{\GetTranslation{changelog-Added}}}
    \cs_set:Npn \changed    {\changelog__item:n{\GetTranslation{changelog-Changed}}}
    \cs_set:Npn \deprecated {\changelog__item:n{\GetTranslation{changelog-Deprecated}}}
    \cs_set:Npn \removed    {\changelog__item:n{\GetTranslation{changelog-Removed}}}
    \cs_set:Npn \fixed      {\changelog__item:n{\GetTranslation{changelog-Fixed}}}
    \cs_set:Npn \security   {\changelog__item:n{\GetTranslation{changelog-Security}}}
    \cs_set:Npn \misc       {\changelog__item:n{\GetTranslation{changelog-Miscellaneous}}}
    \tl_use:N \g__changelog_extra_section_cmds
  }

\NewDocumentCommand{\newchangelogsection}{m m}
  {
    \tl_gput_right:Nn \g__changelog_extra_section_cmds
      {
        \cs_set:Npn #1
          {
            \changelog__item:n { #2 }
          }
      }
  }

\keys_define:nn { changelog_version }
  {
    author  .tl_set:N    = \g__changelog_author_tl ,
    v       .tl_set:N    = \g__changelog_version_tl ,
    version .tl_set:N    = \g__changelog_version_tl ,
    date    .tl_set:N    = \g__changelog_date_tl ,
    changes .tl_set:N    = \g__changelog_changes_tl ,
    yanked  .bool_set:N  = \g__changelog_yanked_bool ,
    simple  .bool_set:N  = \g__changelog_simple_bool ,
    short   .bool_set:N  = \g__changelog_short_bool ,
    remark  .tl_set:N    = \g__changelog_remark_tl ,
    remarks .clist_set:N = \g__changelog_remarks_clist ,
  }

\keys_define:nn { changelog_changelog }
  {
    sectioncmd .tl_set:N    = \g__changelog_sectioncmd_tl ,
    sectioncmd .initial:n   = \section ,
    title      .tl_set:N    = \g__changelog_title_tl ,
    title      .initial:n   = \GetTranslation{changelog} ,
    label      .tl_set:N    = \g__changelog_label_tl ,
    label      .initial:n   = sec:changelog ,
    section    .bool_gset:N = \g__changelog_section_bool ,
    section    .initial:n   = true ,
  }

\keys_define:nn { } { changelog_changelog .inherit:n = changelog_version }

\newenvironment{changelogdescription}
  {\begin{description}}
  {\end{description}}
\newenvironment{changelogitemize}
  {\begin{itemize}}
  {\end{itemize}}

\cs_set:Npn \changelog__section_maybe
  {
    \bool_if:NTF \g__changelog_section_bool
      {
        \exp_after:wN \g__changelog_sectioncmd_tl { \g__changelog_title_tl }
        \tl_if_empty:NF \g__changelog_label_tl
          {
            \exp_after:wN \label { \tl_use:N \g__changelog_label_tl }
          }
      }
      {}
  }

% Display the current version, which may be a version number, a date, or "Unreleased".
\cs_set:Npn \changelog__display_version
  {
    \tl_if_empty:NTF \g__changelog_version_tl
      {
        \tl_if_empty:NTF \g__changelog_date_tl
          {
            \GetTranslation{changelog-Unreleased}
            \tl_set:Nn \g__changelog_date_tl { \today }
          }
          {
            \tl_use:N \g__changelog_date_tl
          }
      }
      {
        \tl_use:N \g__changelog_version_tl
      }
  }

\cs_set:Npn \changelog__short_version_item
  {
    \changelog__display_version
    \bool_if:NT \g__changelog_yanked_bool
      {
        \ \changelogyanked
      }
    \tl_if_empty:NF \g__changelog_remark_tl
      {
        \ \changelogremark{\tl_use:N \g__changelog_remark_tl}
      }
    \clist_if_empty:NF \g__changelog_remarks_clist
      {
        \clist_map_variable:NNn \g__changelog_remarks_clist \l__changelog_remark_tl
          {
            \ \changelogremark{\tl_use:N \l__changelog_remark_tl}
          }
      }
  }

\cs_set:Npn \changelog__short_version_author_date
  {
    % This might output nothing if there's no author and the date is being used
    % as a version number, so make sure to `\leavevmode` to avoid a weird
    % indent at the start of the item.
    \mode_leave_vertical:
    \tl_use:N \g__changelog_author_tl
    \tl_if_empty:NF \g__changelog_date_tl
      {
        % If no version number is given, the date is used instead, so we don't
        % want to show the date twice.
        \tl_if_empty:NF \g__changelog_version_tl
          {
            \tl_if_empty:NF \g__changelog_author_tl
              { ~ }
            (\tl_use:N \g__changelog_date_tl)
          }
      }
  }

\cs_set:Npn \changelog__version_pre
  {
    \par
    \bool_gset_true:N \g__changelog_version_first_bool
    \changelog__define_section_cmds
    \bool_if:NT \g__changelog_simple_bool
      {
        \begin{changelogitemize}
      }
  }

\cs_set:Npn \changelog__version_post
  {
    \bool_if:NT \g__changelog_version_first_bool
      {
        \bool_if:NF \g__changelog_simple_bool
          {
            \msg_error:nn { changelog } { missing_section }
          }
      }
    \end{changelogitemize}
  }

\bool_new:N \g__changelog_seen_version_bool
\NewDocumentEnvironment{changelog}{o}
  {
    \bool_gset_false:N \g__changelog_seen_version_bool
    \IfValueT{#1}{\keys_set:nn{changelog_changelog}{#1}}
    \changelog__section_maybe

    % Define the version environment, which wraps an `itemize`-style list.
    \NewDocumentEnvironment{version}{ O{} }
      {
        \keys_set:nn { changelog_version } { ##1 }
        \changelog__short_version

        \bool_if:NF \g__changelog_short_bool
          {
            \changelog__version_pre
          }
      }
      {
        \bool_if:NF \g__changelog_short_bool
          {
            \changelog__version_post
          }
      }

    % doesn't set keys so this can share code with the version
    % environment
    \cs_set:Npn \changelog__short_version
      {
        \bool_gset_true:N \g__changelog_seen_version_bool

        \item[\changelog__short_version_item]
        \changelog__short_version_author_date

        \bool_if:NT \g__changelog_short_bool
          {
            \ ---\ \tl_use:N \g__changelog_changes_tl
          }
      }

    % A short version; "like a 1-bullet list.
    \NewDocumentCommand{\shortversion}{m}
      % The extra braces here keep the keys we set local.
      {{
        \keys_set:nn{changelog_version}{##1, short}
        \changelog__short_version
      }}

    \begin{changelogdescription}
  }
  {
    \bool_if:NF \g__changelog_seen_version_bool
      {
        \msg_error:nn { changelog } { missing_version }
      }

    \end{changelogdescription}
  }
