% \iffalse meta-comment % Copyright (C) 2026 Andreas Willibald Weber % % This work may be distributed and/or modified under the conditions of the % LaTeX Project Public License, either version 1.3c of this license or (at your % option) any later version. The latest version of the license is available at % https://www.latex-project.org/lppl.txt. % % This work has the LPPL maintenance status `maintained'. The Current % Maintainer of this work is Andreas Willibald Weber. % % This file is part of the `kanban-board' package. The complete list of files % constituting the work appears in the main source file kanban-board.dtx. % \fi % % \begin{macrocode} %<*package> % \end{macrocode} % % \subsection{Persistent configuration} % % Built-in palettes and the default column list are registered through the % same mechanisms that documents use. Mode, date format, start date, % reference date, column list, palette, semantic labels, and gap defaults % then enter the dedicated |kanban/setup| key family. Package options are % accumulated and passed through the same transaction, so package-level and % document-level setup have identical validation and scope. Throughout this % part, a package option or |\kbset| changes the source for later snapshots, % whereas an environment key changes only the snapshot being constructed. % % \paragraph{Built-in palettes and default columns.} % The default, muted, and monochrome palettes obey exactly the same % validation and registry rules as user-defined palettes, because they are % registered through the public command. The default palette is selected % immediately so that a document with no configuration produces the % documented colors. The default column list is installed through the % persistent column selector for the same reason. Its spelling is the text % shown in the headings, and its uppercase form is the lookup key. % \begin{macrocode} \kanbandefinepalette{default}{% kanbanSlate,kanbanBlue,kanbanTeal,kanbanSage, kanbanAmber,kanbanClay,kanbanRose,kanbanPlum} \kanbandefinepalette{muted}{kanbanMutedBlue,kanbanMutedGreen, kanbanMutedPurple,kanbanMutedBrown} \kanbandefinepalette{monochrome}{black!85,black!75,black!65,black!55} \kanban@selectpalette{default} \kanban@selectcolumns{Backlog,Doing,Done} % \end{macrocode} % % \paragraph{Mode selection.} % Visual mode is a constrained textual choice. The persistent selector % records a global |light| or |dark| default and the board selector records % the same choice locally. Unknown values keep the previous selection and % are reported through the shared unknown-value message, which names the % scope of the setting. % \begin{macrocode} \newcommand\kanban@selectmode[1]{% \kanban@normalizechoice{#1}% \ifdefstring{\kb@choice}{light}{\gdef\kanban@mode{light}}{% \ifdefstring{\kb@choice}{dark}{\gdef\kanban@mode{dark}}{% \kanban@warning{unknown-value}{mode}{#1}}}} \newcommand\kanban@selectboardmode[1]{% \kanban@normalizechoice{#1}% \ifdefstring{\kb@choice}{light}{\def\kanban@boardmode{light}}{% \ifdefstring{\kb@choice}{dark}{\def\kanban@boardmode{dark}}{% \kanban@warning{unknown-value}{board mode}{#1}}}} % \end{macrocode} % % \paragraph{Date-format canonicalization and selection.} % Five canonical format names are supported together with a small set of % readable aliases. Canonicalization is separated from assignment so that the % same alias table serves persistent and board-local selection. A recognized % value writes one of |us|, |english|, |german|, |iso|, or |ymd|. An unknown % value leaves the result empty and is diagnosed by the selector. % \begin{macrocode} \newcommand\kanban@canonicaldateformat[1]{% \kanban@normalizechoice{#1}% \def\kb@datechoice{}% \ifdefstring{\kb@choice}{us}{\def\kb@datechoice{us}}{}% \ifdefstring{\kb@choice}{american}{\def\kb@datechoice{us}}{}% \ifdefstring{\kb@choice}{english}{\def\kb@datechoice{english}}{}% \ifdefstring{\kb@choice}{uk}{\def\kb@datechoice{english}}{}% \ifdefstring{\kb@choice}{british}{\def\kb@datechoice{english}}{}% \ifdefstring{\kb@choice}{german}{\def\kb@datechoice{german}}{}% \ifdefstring{\kb@choice}{de}{\def\kb@datechoice{german}}{}% \ifdefstring{\kb@choice}{deutsch}{\def\kb@datechoice{german}}{}% \ifdefstring{\kb@choice}{iso}{\def\kb@datechoice{iso}}{}% \ifdefstring{\kb@choice}{ymd}{\def\kb@datechoice{ymd}}{}% \ifdefstring{\kb@choice}{year-first}{\def\kb@datechoice{ymd}}{}} \newcommand\kanban@selectdateformat[1]{% \kanban@canonicaldateformat{#1}% \ifdefempty\kb@datechoice {\kanban@warning{unknown-value}{date format}{#1}} {\global\let\kanban@dateformat\kb@datechoice}} \newcommand\kanban@selectboarddateformat[1]{% \kanban@canonicaldateformat{#1}% \ifdefempty\kb@datechoice {\kanban@warning{unknown-value}{board date format}{#1}} {\let\kanban@boarddateformat\kb@datechoice}} % \end{macrocode} % % \paragraph{Validated column-gap assignment.} % A column gap must be a non-negative dimension. The persistent and % board-local setters share the safe grammar of the deadline part, assign the % accepted input to a candidate register, and compare the evaluated length % with zero before committing it to the appropriate scope. % \begin{macrocode} \newcommand\kanban@setcolumnsep[1]{% \kanban@validatedimensionTF{#1}{% \setlength\kanban@candidatecolumnsep{#1}% \ifdim\kanban@candidatecolumnsep<0pt \kanban@dimensionwarning{columnsep}{#1}% \else \global\kanban@columnsep=\kanban@candidatecolumnsep \fi }{% \kanban@dimensionwarning{columnsep}{#1}}} \newcommand\kanban@setboardcolumnsep[1]{% \kanban@validatedimensionTF{#1}{% \setlength\kanban@candidatecolumnsep{#1}% \ifdim\kanban@candidatecolumnsep<0pt \kanban@dimensionwarning{board columnsep}{#1}% \else \kanban@boardcolumnsep=\kanban@candidatecolumnsep \fi }{% \kanban@dimensionwarning{board columnsep}{#1}}} % \end{macrocode} % % \paragraph{Materialization of the visual mode.} % The mode name is semantic state. Rendering needs concrete text, surface, % border, and default heading colors, which this function derives from the % active board mode after all board options have been processed. The tint % percentage used for optional card backgrounds also depends on the mode. A % light surface needs only a touch of color, whereas a dark surface needs a % stronger share to stay visible while keeping the text contrast. Metadata % chip colors are absent here because their light styling is stable in both % modes. % \begin{macrocode} \newcommand\kanban@applymode{% \ifdefstring{\kanban@boardmode}{dark}{% \def\kanban@textcolor{white!88!black}% \def\kanban@cardcolor{kanbanInk}% \def\kanban@cardtint{20}% \def\kanban@bordercolor{white!18!black}% \def\kanban@headingtext{white}% }{% \def\kanban@textcolor{kanbanInk}% \def\kanban@cardcolor{white}% \def\kanban@cardtint{15}% \def\kanban@bordercolor{black!15}% \def\kanban@headingtext{white}% }} % \end{macrocode} % % \paragraph{Persistent and board-local semantic labels.} % Priority and completion labels are customizable text, but an empty label % would create an invisible chip with misleading spacing. Both setters reject % blank input. The label identifier supplied by the key family selects the % persistent or board-local macro by name. % \begin{macrocode} \newcommand\kanban@setlabel[2]{% \ifblank{#2} {\kanban@warning{empty-label}{label}{#1}} {\csgdef{kanban@#1label}{#2}}} \newcommand\kanban@setboardlabel[2]{% \ifblank{#2} {\kanban@warning{empty-label}{board label}{#1}} {\csdef{kanban@board#1label}{#2}}} % \end{macrocode} % % \paragraph{Nested label key families.} % Label customization is expressed as a subordinate key list such as % |labels={high=URGENT,done=FINISHED}|. Separate persistent and board-local % families direct identical key names to setters with different assignment % scope. Each family owns its unknown-key diagnostic, which states whether % the mistake occurred in global setup or in a board override. The key name % is passed by expansion, because \textsf{l3msg} shows message arguments % without expanding them. % \begin{macrocode} \ExplSyntaxOn \keys_define:nn {kanban/setup-labels} { high .code:n = {\kanban@setlabel{high}{#1}}, medium .code:n = {\kanban@setlabel{medium}{#1}}, low .code:n = {\kanban@setlabel{low}{#1}}, done .code:n = {\kanban@setlabel{done}{#1}}, unknown .code:n = {\use:x {\msg_warning:nnnn {kanban-board}{unknown-key} {label}{\l_keys_key_str}}} } \keys_define:nn {kanban/board-labels} { high .code:n = {\kanban@setboardlabel{high}{#1}}, medium .code:n = {\kanban@setboardlabel{medium}{#1}}, low .code:n = {\kanban@setboardlabel{low}{#1}}, done .code:n = {\kanban@setboardlabel{done}{#1}}, unknown .code:n = {\use:x {\msg_warning:nnnn {kanban-board}{unknown-key} {board~label}{\l_keys_key_str}}} } % \end{macrocode} % % \paragraph{The persistent setup key family.} % The setup family is the single entry point for package options and for % |\kanbansetup|. Immediate keys delegate to their validated selectors. Date % values are staged rather than applied at once, because a |date-format| key % appearing later in the same list must take effect before an ambiguous start % or reference date is interpreted. Alias keys provide the compact and the % legacy spellings without a second implementation. % \begin{macrocode} \keys_define:nn {kanban/setup} { columns .code:n = {\kanban@selectcolumns{#1}}, palette .code:n = {\kanban@selectpalette{#1}}, date-format .code:n = {\kanban@selectdateformat{#1}}, dateformat .meta:n = {date-format={#1}}, startdate .code:n = { \tl_set:Nn \l__kanban_pending_startdate_tl {#1} \bool_set_true:N \l__kanban_pending_startdate_bool }, reference-date .code:n = { \tl_set:Nn \l__kanban_pending_reference_date_tl {#1} \bool_set_true:N \l__kanban_pending_reference_date_bool }, today .meta:n = {reference-date={#1}}, mode .code:n = {\kanban@selectmode{#1}}, columnsep .code:n = {\kanban@setcolumnsep{#1}}, labels .code:n = {\keys_set:nn {kanban/setup-labels}{#1}}, unknown .code:n = {\use:x {\msg_warning:nnnn {kanban-board}{unknown-key} {setup}{\l_keys_key_str}}} } % \end{macrocode} % % \paragraph{A deferred setup transaction.} % Processing a setup list is a small transaction. It clears both staging % flags, processes the complete list in source order, and only then applies % any staged dates under the final selected format. Consequently % |startdate=07/08/2026,date-format=english| and the reversed key order have % the same interpretation. % \begin{macrocode} \cs_new_protected:Npn \kanban@processsetup #1 { \bool_set_false:N \l__kanban_pending_startdate_bool \bool_set_false:N \l__kanban_pending_reference_date_bool \keys_set:nn {kanban/setup}{#1} \bool_if:NT \l__kanban_pending_startdate_bool {\exp_args:NV \kanban@setstartdate \l__kanban_pending_startdate_tl} \bool_if:NT \l__kanban_pending_reference_date_bool {\exp_args:NV \kanban@setreferencedate \l__kanban_pending_reference_date_tl} } \ExplSyntaxOff % \end{macrocode} % % \begin{macro}{\kanbansetup} % \begin{macro}{\kbset} % The canonical setup command exposes the transaction with one mandatory % key-list argument. The short alias follows the same collision-tolerant % policy as the palette alias. Equivalence by |\let| gives both names exactly % the same argument parser and behavior. % \begin{macrocode} \NewDocumentCommand\kanbansetup{m}{\kanban@processsetup{#1}} \@ifundefined{kbset} {\let\kbset\kanbansetup} {\kanban@warning{alias-taken}{kbset}{kanbansetup}} % \end{macrocode} % % \paragraph{Collection and application of package options.} % LaTeX2e presents package options one at a time. The package collects them % into one token list and applies that list through the deferred setup % transaction after option processing has finished. Package options % therefore share validation, aliases, key-order independence, and % diagnostics with |\kanbansetup|. The transaction runs while |kanban-board.sty| % is still being read, so the line number at that moment points into the % package rather than into the document. The application therefore runs in % a group that redefines the line context of \textsf{l3msg} to name the % package options. Every assignment made by the setup transaction is % global, so the group does not shorten the lifetime of any setting. % % Formats dated 2021-06-01 or later parse raw option lists with the % brace-aware key--value processor, so the catch-all collector receives every % option intact, including values with embedded commas such as % |columns={A,B}|. The 2020-10-01 format splits raw option lists at every % comma and mishandles brace groups in its option bookkeeping. On such % formats the package routes its options through \textsf{kvoptions}. Every % documented setup key is declared as a \textsf{keyval} key whose handler % re-wraps the brace-stripped value before appending it to the same % accumulator, and unrecognized options fall through to the same catch-all % collector. Unknown package options consequently produce the same warning % as unknown setup keys on both paths. % \begin{macrocode} \ExplSyntaxOn \tl_new:N \g__kanban_package_options_tl \cs_new_protected:Npn \kanban@collectpackageoption { \tl_if_empty:NF \g__kanban_package_options_tl {\tl_gput_right:Nn \g__kanban_package_options_tl {,}} \exp_args:NNo \tl_gput_right:Nn \g__kanban_package_options_tl {\CurrentOption} } \cs_new_protected:Npn \kanban@collectkeyvalue #1#2 { \tl_if_empty:NF \g__kanban_package_options_tl {\tl_gput_right:Nn \g__kanban_package_options_tl {,}} \tl_gput_right:Nn \g__kanban_package_options_tl {#1={#2}} } \cs_new_protected:Npn \kanban@applypackageoptions { \tl_if_empty:NF \g__kanban_package_options_tl { \group_begin: \cs_set:Npn \msg_line_context: {in~the~package~options} \exp_args:NV \kanban@processsetup \g__kanban_package_options_tl \group_end: } } \ExplSyntaxOff \IfFormatAtLeastTF{2021-06-01} {% \DeclareOption*{\kanban@collectpackageoption}% \ProcessOptions\relax } {% \RequirePackage{kvoptions}% \SetupKeyvalOptions{family=kanban,prefix=kanban@pkgopt@}% \DeclareDefaultOption{\kanban@collectpackageoption}% \define@key{kanban}{columns}% {\kanban@collectkeyvalue{columns}{#1}}% \define@key{kanban}{palette}% {\kanban@collectkeyvalue{palette}{#1}}% \define@key{kanban}{date-format}% {\kanban@collectkeyvalue{date-format}{#1}}% \define@key{kanban}{dateformat}% {\kanban@collectkeyvalue{dateformat}{#1}}% \define@key{kanban}{startdate}% {\kanban@collectkeyvalue{startdate}{#1}}% \define@key{kanban}{reference-date}% {\kanban@collectkeyvalue{reference-date}{#1}}% \define@key{kanban}{today}% {\kanban@collectkeyvalue{today}{#1}}% \define@key{kanban}{mode}% {\kanban@collectkeyvalue{mode}{#1}}% \define@key{kanban}{columnsep}% {\kanban@collectkeyvalue{columnsep}{#1}}% \define@key{kanban}{labels}% {\kanban@collectkeyvalue{labels}{#1}}% \ProcessKeyvalOptions*\relax } \kanban@applypackageoptions % \end{macrocode} % \end{macro} % \end{macro} % % \begin{macrocode} % % \end{macrocode}