%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% \iffalse %%%% % % % Copyright (c) 2018 - Michiel Helvensteijn (www.mhelvens.net) % % % % https://github.com/mhelvens/latex-pkgloader % % % % 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 Michiel Helvensteijn. % % % %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% \fi %%%% % \CheckSum{0} % % \CharacterTable % {Upper-case \A\B\C\D\E\F\G\H\I\J\K\L\M\N\O\P\Q\R\S\T\U\V\W\X\Y\Z % Lower-case \a\b\c\d\e\f\g\h\i\j\k\l\m\n\o\p\q\r\s\t\u\v\w\x\y\z % Digits \0\1\2\3\4\5\6\7\8\9 % Exclamation \! Double quote \" Hash (number) \# % Dollar \$ Percent \% Ampersand \& % Acute accent \' Left paren \( Right paren \) % Asterisk \* Plus \+ Comma \, % Minus \- Point \. Solidus \/ % Colon \: Semicolon \; Less than \< % Equals \= Greater than \> Question mark \? % Commercial at \@ Left bracket \[ Backslash \\ % Right bracket \] Circumflex \^ Underscore \_ % Grave accent \` Left brace \{ Vertical bar \| % Right brace \} Tilde \~} %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% % \subsection{Package Info} % %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% % % First, the mandatory package meta-information: % % \begin{macrocode} \NeedsTeXFormat{LaTeX2e} \RequirePackage{expl3} \ProvidesExplPackage{pkgloader}{2018/04/29}{0.7.0} {managing the options and loading order of LaTeX packages} % \end{macrocode} % %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% % \subsection{Required Packages} % %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% % % The following packages are required. Two standard |expl3|-related % packages, one experimental package in |l3regex| and one user-contributed % |expl3| package in |lt3graph|: % % \begin{macrocode} \RequirePackage{xparse} \RequirePackage{l3keys2e} \RequirePackage{l3regex} \RequirePackage{lt3graph} % \end{macrocode} % %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% % \subsection{Package Code} % %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% % % We need two global data-structures. One to keep track of all packages % that are known, one to keep track of the packages that are actually % going to be loaded, and their order: % % \begin{macrocode} \prop_new:N \g__pkgloader_known_pkg_prop \graph_new:N \g__pkgloader_pkg_graph % \end{macrocode} % %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% % % We store pristine versions of the three package loading % commands: % % \begin{macrocode} \cs_gset_eq:NN \__pkgloader_usepkg:wnw \usepackage \cs_gset_eq:NN \__pkgloader_RPkg:wnw \RequirePackage \cs_gset_eq:NN \__pkgloader_RPkgWithOptions:wnw \RequirePackageWithOptions \cs_gset_eq:NN \__pkgloader_doccls:wnw \documentclass \cs_gset_eq:NN \__pkgloader_LCls:wnw \LoadClass \cs_gset_eq:NN \__pkgloader_LClsWithOptions:wnw \LoadClassWithOptions % \end{macrocode} % % And we define a command to clean up any and all commands % that we change or introduce. It will be called when they % are not needed anymore: % % \begin{macrocode} \tl_new:N \__pkgloader_cleanup_commands: \tl_put_right:Nn \__pkgloader_cleanup_commands: { \cs_gset_eq:NN \usepackage \__pkgloader_usepkg:wnw \cs_gset_eq:NN \RequirePackage \__pkgloader_RPkg:wnw \cs_gset_eq:NN \RequirePackageWithOptions \__pkgloader_RPkgWithOptions:wnw \cs_gset_eq:NN \documentclass \__pkgloader_doccls:wnw \cs_gset_eq:NN \LoadClass \__pkgloader_LCls:wnw \cs_gset_eq:NN \LoadClassWithOptions \__pkgloader_LClsWithOptions:wnw } % \end{macrocode} % %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% % % This function globally registers a package loading rule, % which can be created with either the |\Load| command or % any of the hijacked |\usepackage|-like commands: % % \begin{macrocode} \cs_new_protected:Nn \__pkgloader_register_rule:nnnnnnnnn { % #1: package or class name % #2: options % #3: version % #4: condition % #5: compiled condition % #6: packages to load before this one % #7: packages to load after this one % #8: reason % #9: command % \end{macrocode} % % If this package or class hasn't been seen before, % register it and create a rule-counter for it: % % \begin{macrocode} \prop_if_in:NnF \g__pkgloader_known_pkg_prop {#1} { \prop_gput:Nnn \g__pkgloader_known_pkg_prop {#1} {} \int_new:c {g__pkgloader_count_(#1)_int} } % \end{macrocode} % % Increment the rule-counter: % % \begin{macrocode} \int_incr:c {g__pkgloader_count_(#1)_int} % \end{macrocode} % % Then, we set all properties of the % % \begin{macrocode} \tl_set:Nf \l_tmpa_tl {\int_use:c {g__pkgloader_count_(#1)_int}} \tl_set:cn {g__pkgloader_options_ (#1)_(\l_tmpa_tl)_tl} {#2} \tl_set:cn {g__pkgloader_version_ (#1)_(\l_tmpa_tl)_tl} {#3} \tl_set:cn {g__pkgloader_condition_ (#1)_(\l_tmpa_tl)_tl} {#4} \tl_set:cn {g__pkgloader_compiled_condition_(#1)_(\l_tmpa_tl)_tl} {#5} \tl_set:cn {g__pkgloader_predecessors_ (#1)_(\l_tmpa_tl)_tl} {#6} \tl_set:cn {g__pkgloader_successors_ (#1)_(\l_tmpa_tl)_tl} {#7} \tl_set:cn {g__pkgloader_reason_ (#1)_(\l_tmpa_tl)_tl} {#8} \tl_set:cn {g__pkgloader_command_ (#1)_(\l_tmpa_tl)_tl} {#9} \bool_new:c {g__pkgloader_used_ (#1)_(\l_tmpa_tl)_bool} % \end{macrocode} % \uninteresting\begin{macrocode} } % \end{macrocode} % %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% % % These six macros are redefined to just register % the loading information rather than load the % package or class immediately. The distinction between % package and class is made by prefixing the name % with either |.sty| or |.cls| (which will be stripped % off before the file is actually loaded): % % \begin{macrocode} \RenewDocumentCommand {\usepackage} { o m o } { \__pkgloader_usepackage_cmd:nnnnnn {\usepackage} {#1} {#2.sty} {#3} {pkgloader-cls-pkg.sty} {} } \RenewDocumentCommand {\RequirePackage} { o m o } { \__pkgloader_usepackage_cmd:nnnnnn {\RequirePackage} {#1} {#2.sty} {#3} {} {} } \RenewDocumentCommand {\RequirePackageWithOptions} { o m o } { \__pkgloader_usepackage_cmd:nnnnnn {\RequirePackageWithOptions} {#1} {#2.sty} {#3} {} {} } \RenewDocumentCommand {\documentclass} { o m o } { \__pkgloader_usepackage_cmd:nnnnnn {\documentclass} {#1} {#2.cls} {#3} {} {pkgloader-cls-pkg.sty} } \RenewDocumentCommand {\LoadClass} { o m o } { \__pkgloader_usepackage_cmd:nnnnnn {\LoadClass} {#1} {#2.cls} {#3} {} {pkgloader-cls-pkg.sty} } \RenewDocumentCommand {\LoadClassWithOptions} { o m o } { \__pkgloader_usepackage_cmd:nnnnnn {\LoadClassWithOptions} {#1} {#2.cls} {#3} {} {pkgloader-cls-pkg.sty} } % \end{macrocode} % % Storing this information is delegated to the % |\__pkgloader_register_rule:nnnnnnnnn| function: % % \begin{macrocode} \cs_new:Nn \__pkgloader_usepackage_cmd:nnnnnn { \__pkgloader_register_rule:nnnnnnnnn {#3} {#2} {#4} % package name, options, version {pkgloader-true.sty} % condition {\c_true_bool} % compiled condition {#5} {#6} % predecessors, successors {it~is~requested~by~the~author} % reason {#1} % command } % \end{macrocode} % %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% % % This is a sophisticated user-level command for manipulating % package loading order and conditions. It has a `non-standard' % but convenient syntax, which scans for clauses rather than % taking standard parameters: % % \begin{macrocode} \NewDocumentCommand {\Load} {} { % \end{macrocode} % % Initialize the variables used for storing given data: % % \begin{macrocode} \tl_clear:N \l__pkgloader_load_extension_tl \tl_clear:N \l__pkgloader_load_options_tl \tl_clear:N \l__pkgloader_load_name_tl \tl_clear:N \l__pkgloader_load_version_tl \clist_clear:N \l__pkgloader_load_pred_clist \clist_clear:N \l__pkgloader_load_succ_clist \tl_clear:N \l__pkgloader_load_cond_tl \tl_clear:N \l__pkgloader_load_because_tl \tl_clear:N \l__pkgloader_load_cmd_tl \bool_set_false:N \l__pkgloader_early_late_used_bool % \end{macrocode} % % Start scanning for input: % % \begin{macrocode} \__pkgloader_load_scan_ext_:w % \end{macrocode} % \uninteresting\begin{macrocode} } % \end{macrocode} % % %%%%%%%%% % % This function checks if the |class| keyword is given. % % \begin{macrocode} \cs_new_protected_nopar:Npn \__pkgloader_load_scan_ext_:w { \peek_charcode_remove_ignore_spaces:NTF c {% % % class \__pkgloader_load_scan_ext_c:w }{ % % % % % % % % % % % % % % % % % % % % % % % package details \tl_set:Nn \l__pkgloader_load_extension_tl {.sty} \tl_set:Nn \l__pkgloader_load_cmd_tl {\RequirePackage} \__pkgloader_load_scan_pkg_:w } } % \end{macrocode} % % %%%%%%%%% % % The |class| keyword indicates that this is a document % class loading rule, rather than a package loading rule. % We record this and then goes on to scan the details: % % \begin{macrocode} \cs_new_protected_nopar:Npn \__pkgloader_load_scan_ext_c:w lass { \tl_set:Nn \l__pkgloader_load_extension_tl {.cls} \tl_set:Nn \l__pkgloader_load_cmd_tl {\LoadClass} \clist_put_right:Nn \l__pkgloader_load_succ_clist {pkgloader-cls-pkg.sty} \__pkgloader_load_scan_pkg_:w } % \end{macrocode} % % %%%%%%%%% % % The following function starts scanning for the name of the package % central to this rule, as well as the options and minimum % version proposed for it. It also checks if the |error| % keyword is given. % % \begin{macrocode} \cs_new_protected_nopar:Npn \__pkgloader_load_scan_pkg_:w { \peek_charcode_remove_ignore_spaces:NTF e {% % % error \__pkgloader_load_scan_pkg_e:w }{\peek_charcode_remove_ignore_spaces:NTF [ {% % package options \__pkgloader_load_scan_pkg_options:nw }{ % % % % % % % % % % % % % % % % % % % % % % % package name \__pkgloader_load_scan_pkg_:nw }} } % \end{macrocode} % % %%%%%%%%% % % The |error| keyword can take the place of a package name, % options and version. It is shorthand for the |pkgloader-error| % file, and then jumps ahead to scanning clauses: % % \begin{macrocode} \cs_new_protected_nopar:Npn \__pkgloader_load_scan_pkg_e:w rror { \tl_set:Nn \l__pkgloader_load_name_tl {pkgloader-error.sty} \__pkgloader_load_scan_clause_:w } % \end{macrocode} % % %%%%%%%%% % % This scans the options and goes ahead to scan the package name: % % \begin{macrocode} \cs_new_protected_nopar:Npn \__pkgloader_load_scan_pkg_options:nw #1 ] { \tl_set:Nn \l__pkgloader_load_options_tl {#1} \__pkgloader_load_scan_pkg_:nw } % \end{macrocode} % % %%%%%%%%% % % This scans the package name (and adds the proper extension), % peeks ahead for a minimum version, and otherwise goes on to % scanning for clauses: % % \begin{macrocode} \cs_new_protected_nopar:Npn \__pkgloader_load_scan_pkg_:nw #1 { \tl_set:Nn \l__pkgloader_load_name_tl {#1} \tl_put_right:NV \l__pkgloader_load_name_tl \l__pkgloader_load_extension_tl \peek_charcode_remove_ignore_spaces:NTF [ {% % % % package version \__pkgloader_load_scan_version:nw }{ % % % % % % % % % % % % % % % % % % % % % % % % clauses \__pkgloader_load_scan_clause_:w } } % \end{macrocode} % % %%%%%%%%% % % This scans the version, and then goes ahead to scan for clauses: % % \begin{macrocode} \cs_new_protected_nopar:Npn \__pkgloader_load_scan_version:nw #1 ] { \tl_set:Nn \l__pkgloader_load_version_tl {#1} \__pkgloader_load_scan_clause_:w } % \end{macrocode} % % %%%%%%%%% % % This is the start- and return-point used to scan % for (additional) |\Load| clauses: % % \begin{macrocode} \cs_new_protected_nopar:Npn \__pkgloader_load_scan_clause_:w { \peek_charcode_remove_ignore_spaces:NTF a { \peek_charcode_remove:NTF l { % % % % % % % % % always \__pkgloader_load_scan_clause_al:w }{\peek_charcode_remove:NTF f { % % % % % % % % after \__pkgloader_load_scan_clause_af:w }{ \__pkgloader_load_end: a }} }{\peek_charcode_remove_ignore_spaces:NTF b { % \peek_charcode_remove:NTF e { \peek_charcode_remove:NTF c { % % % % % % % because \__pkgloader_load_scan_clause_bec:w }{\peek_charcode_remove:NTF f { % % % % % % before \__pkgloader_load_scan_clause_bef:w }{ \__pkgloader_load_end: be }} }{ \__pkgloader_load_end: b } }{\peek_charcode_remove_ignore_spaces:NTF e { % % % early \__pkgloader_load_scan_clause_e:w }{\peek_charcode_remove_ignore_spaces:NTF i { % % % if \__pkgloader_load_scan_clause_i:w }{\peek_charcode_remove_ignore_spaces:NTF l { % % % late \__pkgloader_load_scan_clause_l:w }{ \__pkgloader_load_end: }}}}} } % \end{macrocode} % % %%%%%%%%% % % This processes the ``|always|'' clause, which loads this % package conditional on the ``|pkgloader-true|'' package being % loaded (which always is): % % \begin{macrocode} \cs_new_protected_nopar:Npn \__pkgloader_load_scan_clause_al:w ways { \tl_put_right:Nn \l__pkgloader_load_cond_tl {~||~pkgloader-true} \__pkgloader_load_scan_clause_:w } % \end{macrocode} % % %%%%%%%%% % % This processes the ``|after|'' clause: % % \begin{macrocode} \cs_new_protected_nopar:Npn \__pkgloader_load_scan_clause_af:w ter #1 { \clist_map_inline:nn {#1} { \clist_put_right:Nn \l__pkgloader_load_pred_clist {##1.sty} } \__pkgloader_load_scan_clause_:w } % \end{macrocode} % % %%%%%%%%% % % This processes the ``|because|'' clause: % % \begin{macrocode} \cs_new_protected_nopar:Npn \__pkgloader_load_scan_clause_bec:w ause #1 { \tl_set:Nn \l__pkgloader_load_because_tl {#1} \__pkgloader_load_scan_clause_:w } % \end{macrocode} % % %%%%%%%%% % % This processes the ``|before|'' clause: % % \begin{macrocode} \cs_new_protected_nopar:Npn \__pkgloader_load_scan_clause_bef:w ore #1 { \clist_map_inline:nn {#1} { \clist_put_right:Nn \l__pkgloader_load_succ_clist {##1.sty} } \__pkgloader_load_scan_clause_:w } % \end{macrocode} % % %%%%%%%%% % % This processes the ``|early|'' clause, which orders % this package before the ``|pkgloader-early|'' stub: % % \begin{macrocode} \cs_new_protected_nopar:Npn \__pkgloader_load_scan_clause_e:w arly { \bool_set_true:N \l__pkgloader_early_late_used_bool \clist_put_right:Nn \l__pkgloader_load_succ_clist {pkgloader-early.sty} \__pkgloader_load_scan_clause_:w } % \end{macrocode} % % %%%%%%%%% % % This processes the ``|if|'' clause, which may still be % a manual condition or the ``|loaded|'' keyword: % % \begin{macrocode} \cs_new_protected_nopar:Npn \__pkgloader_load_scan_clause_i:w f { \peek_charcode_remove_ignore_spaces:NTF l { \__pkgloader_load_scan_clause_if_l:w }{ \__pkgloader_load_scan_clause_if_:nw } } % \end{macrocode} % % %%%%%%%%% % % This processes the ``|if loaded|'' clause, which uses % this package being loaded as the condition for the rule being used: % % \begin{macrocode} \cs_new_protected_nopar:Npn \__pkgloader_load_scan_clause_if_l:w oaded { \tl_put_right:Nn \l__pkgloader_load_cond_tl {~||~} \tl_put_right:NV \l__pkgloader_load_cond_tl \l__pkgloader_load_name_tl \__pkgloader_load_scan_clause_:w } % \end{macrocode} % % %%%%%%%%% % % This processes the ``|if|'' clause with a manual condition: % % \begin{macrocode} \cs_new_protected_nopar:Npn \__pkgloader_load_scan_clause_if_:nw #1 { \tl_put_right:Nn \l__pkgloader_load_cond_tl {~||~#1} \__pkgloader_load_scan_clause_:w } % \end{macrocode} % % %%%%%%%%% % % This processes the ``|late|'' clause, which orders % this package after the ``|pkgloader-late|'' stub: % % \begin{macrocode} \cs_new_protected_nopar:Npn \__pkgloader_load_scan_clause_l:w ate { \bool_set_true:N \l__pkgloader_early_late_used_bool \clist_put_right:Nn \l__pkgloader_load_pred_clist {pkgloader-late.sty} \__pkgloader_load_scan_clause_:w } % \end{macrocode} % % %%%%%%%%% %%%%%%%%% %%%%%%%%% %%%%%%%%% %%%%%%%%% %%%%%%%%% % % This function processes the collected data and registers it: % % \begin{macrocode} \cs_new_protected_nopar:Nn \__pkgloader_load_end: { % \end{macrocode} % % We remove the leading ``\texttt{\ \textbar\textbar\ }'' from the condition: % % \begin{macrocode} \tl_remove_once:Nn \l__pkgloader_load_cond_tl {~||~} % \end{macrocode} % % If no condition is given at all, the default is: ``|if loaded|'' % % \begin{macrocode} \tl_if_empty:NT \l__pkgloader_load_cond_tl { \tl_set_eq:NN \l__pkgloader_load_cond_tl \l__pkgloader_load_name_tl } % \end{macrocode} % % We now take the condition and compile it to a |\bool_if:| % kind of syntax. The original syntax is preserved to use in % error messages and such: % % \begin{macrocode} \tl_set_eq:NN \l__pkgloader_load_compd_cond_tl \l__pkgloader_load_cond_tl \regex_replace_all:nnN { [^\&\|\(\)\!\s]+ } { (\c{graph_if_vertex_exist_p:Nn} \c{g__pkgloader_pkg_graph}\cB\{\0\.sty\cE\}) } \l__pkgloader_load_compd_cond_tl % \end{macrocode} % % If no reason was given for this rule, it was obviously % `because of reasons': % % \begin{macrocode} \tl_if_empty:NT \l__pkgloader_load_because_tl { \tl_set:Nn \l__pkgloader_load_because_tl {of~reasons} } % \end{macrocode} % % Having gathered and processed the data, the rule is % registered: % % \begin{macrocode} \__pkgloader_register_rule:VVVVVVVVV \l__pkgloader_load_name_tl \l__pkgloader_load_options_tl \l__pkgloader_load_version_tl \l__pkgloader_load_cond_tl \l__pkgloader_load_compd_cond_tl \l__pkgloader_load_pred_clist \l__pkgloader_load_succ_clist \l__pkgloader_load_because_tl \l__pkgloader_load_cmd_tl % \end{macrocode} % \uninteresting\begin{macrocode} } \cs_generate_variant:Nn \tl_if_eq:nnF {VnF} \cs_generate_variant:Nn \graph_gput_vertex:Nn {NV} \cs_generate_variant:Nn \graph_gput_edge:Nnn {NnV,NVn} \cs_generate_variant:Nn \seq_gput_right:Nn {NV} \cs_generate_variant:Nn \__pkgloader_register_rule:nnnnnnnnn {VVVVVVVVV} \tl_new:N \l__pkgloader_load_extension_tl \tl_new:N \l__pkgloader_load_options_tl \tl_new:N \l__pkgloader_load_name_tl \tl_new:N \l__pkgloader_load_version_tl \clist_new:N \l__pkgloader_load_pred_clist \clist_new:N \l__pkgloader_load_succ_clist \tl_new:N \l__pkgloader_load_cond_tl \tl_new:N \l__pkgloader_load_because_tl \tl_new:N \l__pkgloader_load_cmd_tl \bool_new:N \l__pkgloader_early_late_used_bool % \end{macrocode} % % %%%%%%%%% %%%%%%%%% %%%%%%%%% %%%%%%%%% %%%%%%%%% %%%%%%%%% % % And here's the instruction to clean up the % |\Load| command-name at the end: % % \begin{macrocode} \tl_put_right:Nn \__pkgloader_cleanup_commands: { \cs_undefine:N \Load } % \end{macrocode} % %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% % % This function decides, based on all loaded rules and package % requests, which packages, options and versions end up being % loaded, and in which order. % % \begin{macrocode} \cs_new_protected:Nn \__pkgloader_select_packages: { % \end{macrocode} % % We first set up a graph to record accepted orderings: % % \begin{macrocode} \graph_clear:N \l__pkgloader_order_graph % \end{macrocode} % % We then start a loop that runs at least once, then repeats % while additional package configurations are still being added % to the set. Eventually the loop reaches a fixed point and terminates. % % \begin{macrocode} \bool_do_while:Nn \l__pkgloader_selection_changed_bool { \bool_set_false:N \l__pkgloader_selection_changed_bool % \end{macrocode} % % Then first, for all possible package configurations (a nested % loop, but not doubly indented because it feels like one loop): % % \begin{macrocode} \prop_map_inline:Nn \g__pkgloader_known_pkg_prop {%%%%%%%%%%%%%% ##1 \int_step_inline:nncn {1} {1} {g__pkgloader_count_(##1)_int} {%% ####1 % \end{macrocode} % % If the current configuration should be loaded but still % isn't selected (nested conditional; but again, not indented): % % \begin{macrocode} \bool_if:cF {g__pkgloader_used_(##1)_(####1)_bool} { \bool_if:vT {g__pkgloader_compiled_condition_(##1)_(####1)_tl} { % \end{macrocode} % % We mark the package configuration as being used: % % \begin{macrocode} \bool_set_true:c {g__pkgloader_used_(##1)_(####1)_bool} % \end{macrocode} % % We record the configuration in the main package graph, which maps % each package to a clist of selected configurations: % % \begin{macrocode} \graph_get_vertex:NnNTF \g__pkgloader_pkg_graph {##1} \l__pkgloader_used_configs_tl { \tl_if_empty:NTF \l__pkgloader_used_configs_tl { \graph_gput_vertex:Nnf \g__pkgloader_pkg_graph {##1} {####1} } { \graph_gput_vertex:Nnf \g__pkgloader_pkg_graph {##1} {\l__pkgloader_used_configs_tl, ####1} } } { \graph_gput_vertex:Nnf \g__pkgloader_pkg_graph {##1} {####1} } % \end{macrocode} % % In a separate graph, we record the associated (now activated) % package loading orders. We don't do this in the main graph, % because it may involve packages that are themselves not yet % selected. These edges are later filtered and added to the main graph: % % \begin{macrocode} \graph_put_vertex:Nn \l__pkgloader_order_graph {##1} \clist_map_inline:cn {g__pkgloader_predecessors_(##1)_(####1)_tl} { \graph_put_vertex:Nn \l__pkgloader_order_graph {########1} \graph_get_edge:NnnNTF \l__pkgloader_order_graph {########1} {##1} \l__pkgloader_used_configs_tl { \graph_put_edge:Nnnn \l__pkgloader_order_graph {########1} {##1} {\l__pkgloader_used_configs_tl,####1} } { \graph_put_edge:Nnnn \l__pkgloader_order_graph {########1} {##1} {####1} } } \clist_map_inline:cn {g__pkgloader_successors_(##1)_(####1)_tl} { \graph_put_vertex:Nn \l__pkgloader_order_graph {########1} \graph_get_edge:NnnNTF \l__pkgloader_order_graph {##1} {########1} \l__pkgloader_used_configs_tl { \graph_put_edge:Nnnn \l__pkgloader_order_graph {##1} {########1} {\l__pkgloader_used_configs_tl,####1} } { \graph_put_edge:Nnnn \l__pkgloader_order_graph {##1} {########1} {####1} } } % \end{macrocode} % % We then mark the change, so a next iteration will be entered: % % \begin{macrocode} \bool_set_true:N \l__pkgloader_selection_changed_bool % \end{macrocode} % \uninteresting\begin{macrocode} }} }} } % \end{macrocode} % % We put the applicable proposed orderings into the % graph of selected packages: % % \begin{macrocode} \graph_gput_edges_from:NN \g__pkgloader_pkg_graph \l__pkgloader_order_graph % \end{macrocode} % % If there is a cycle in the derived package loading order: ERROR % % \begin{macrocode} \graph_if_cyclic:NT \g__pkgloader_pkg_graph { \msg_fatal:nn {pkgloader} {cyclic-order} } % \end{macrocode} % % Finally, we apply some default orderings where needed: % \begin{itemize} % \item If a package should not specifically % go early or late, it goes inbetween; and % \item if a package should not specifically % go before a class, it goes after. % \end{itemize} % % \begin{macrocode} \graph_map_vertices_inline:Nn \g__pkgloader_pkg_graph { \seq_if_in:NnF \g__pkgloader_system_packages_seq {##1} { \graph_acyclic_if_path_exist:NnnF \g__pkgloader_pkg_graph {##1} {pkgloader-early.sty} { \graph_acyclic_if_path_exist:NnnF \g__pkgloader_pkg_graph {pkgloader-late.sty} {##1} { \graph_put_edge:Nnn \g__pkgloader_pkg_graph {pkgloader-early.sty} {##1} \graph_put_edge:Nnn \g__pkgloader_pkg_graph {##1} {pkgloader-late.sty} } } \graph_acyclic_if_path_exist:NnnF \g__pkgloader_pkg_graph {##1} {pkgloader-cls-pkg.sty} { \graph_put_edge:Nnn \g__pkgloader_pkg_graph {pkgloader-cls-pkg.sty} {##1} } } } % \end{macrocode} % \uninteresting\begin{macrocode} } \cs_generate_variant:Nn \int_step_inline:nnnn {nncn} \cs_generate_variant:Nn \bool_if:nT {vT} \cs_generate_variant:Nn \withargs:nnn {vvn} \cs_generate_variant:Nn \graph_gput_vertex:Nnn {Nnf} \graph_new:N \l__pkgloader_order_graph \tl_new:N \l__pkgloader_used_configs_tl \bool_new:N \l__pkgloader_selection_changed_bool % \end{macrocode} % %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% % % Now follows the user command to consolidate all package % loading requests and do the `right thing' (tm). Invoking % this command ends the work of |pkgloader|. % % \begin{macrocode} \NewDocumentCommand {\LoadPackagesNow} {} { % \end{macrocode} % % We first select package configurations by their loading conditions: % % \begin{macrocode} \__pkgloader_select_packages: % \end{macrocode} % % Now to clean up after |pkgloader|, restoring % and removing various command-names. % % \begin{macrocode} \__pkgloader_cleanup_commands: % \end{macrocode} % % Then, for all used packages, in topological order\ldots % % \begin{macrocode} \graph_map_topological_order_inline:Nn \g__pkgloader_pkg_graph { % \end{macrocode} % % \ldots load that package. Though note that this code is still quite % incomplete, because it loads the first viable configuration. It should: % \begin{enumerate} % \item use the |WithOptions| version of the command % if necessary, % \item allow custom merging schemes for options, and % \item use the latest required version. % \end{enumerate} % \begin{macrocode} \withargs:xn { \clist_item:nn{##2}{1} } { \withargs:vvfvn {g__pkgloader_command_(##1)_(####1)_tl} {g__pkgloader_options_(##1)_(####1)_tl} {\__pkgloader_strip_extension:f{##1}} {g__pkgloader_version_(##1)_(####1)_tl} { \IfValueTF {########2} { \IfValueTF {########4} { ########1 [########2] {########3} [########4] } { ########1 [########2] {########3} } } { \IfValueTF {########4} { ########1 {########3} [########4] } { ########1 {########3} } } % \end{macrocode} % \uninteresting\begin{macrocode} } } } } \cs_generate_variant:Nn \withargs:nn {xn} \cs_generate_variant:Nn \withargs:nnnnn {vvfvn} % \end{macrocode} % % And it needs the following auxiliary function to strip % filenames from their four character extension: % % \begin{macrocode} \cs_new:Npn \__pkgloader_strip_extension:f #1 { \tl_reverse:f{ \tl_tail:f{\tl_tail:f{\tl_tail:f{\tl_tail:f{ \tl_reverse:n{#1} }}}} } } \cs_generate_variant:Nn \tl_reverse:n {f} % \end{macrocode} % % It's a bit clunky. Is there a substring function % in |expl3| we could use that I don't know about? % % And here's the instruction to clean up the % |\LoadPackagesNow| command-name at the end: % % \begin{macrocode} \tl_put_right:Nn \__pkgloader_cleanup_commands: { \cs_undefine:N \LoadPackagesNow } % \end{macrocode} % %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% % % % % \begin{macrocode} \cs_gset_eq:NN \__pkgloader_begin_env:n \begin \RenewDocumentCommand {\begin} {m} { \tl_if_eq:nnT {#1} {document} {\LoadPackagesNow} \__pkgloader_begin_env:n {#1} } \tl_put_right:Nn \__pkgloader_cleanup_commands: { \cs_gset_eq:NN \begin \__pkgloader_begin_env:n } % \end{macrocode} % %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% % % Bootstrap |pkgloader| by inserting |pkgloader-true| in % the graph directly, so all other packages can be inserted % with rules, possibly using the |always| clause. % % \begin{macrocode} \graph_gput_vertex:Nn \g__pkgloader_pkg_graph {pkgloader-true.sty} % \end{macrocode} % % We keep a list of all pkgloader dummy packages: % % \begin{macrocode} \seq_new:N \g__pkgloader_system_packages_seq \cs_generate_variant:Nn \seq_gset_from_clist:Nn {Nx} \seq_gset_from_clist:Nx \g__pkgloader_system_packages_seq {\tl_to_str:n {pkgloader-true.sty, pkgloader-false.sty, pkgloader-early.sty, pkgloader-late.sty, pkgloader-cls-pkg.sty}} % \end{macrocode} % % We then register the core logical rules of |pkgloader|, % regarding fundamental package `stubs' like |pkgloader-false|, % |pkgloader-error|, |pkgloader-early|, and so on. % % \begin{macrocode} \withargs:nn {of~the~mandatory~core~rules~of~pkgloader} { \Load error if {pkgloader-false} because {#1} \Load {pkgloader-true} always because {#1} \Load {pkgloader-early} always because {#1} \Load {pkgloader-late} always because {#1} \Load {pkgloader-cls-pkg} always because {#1} \Load {pkgloader-early} before {pkgloader-late} because {#1} } % \end{macrocode} % %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% % % We process the options passed to |pkgloader| as |.sty| files % to be loaded before pkgloader does its thing. This % should be used to define new |pkgloader| rules. Note, % particularly, that any |\usepackage|-like command inside % those |.sty| files is registered and processed by % |pkgloader|; not loaded directly. % % First, we define the functions used to handle an option. % % \begin{macrocode} \cs_new:Nn \__pkgloader_process_option:n { \__pkgloader_process_option:nn {#1} {true} } \cs_new:Nn \__pkgloader_process_option:nn { \tl_if_eq:nnTF {#2} {true} { \seq_put_right:Nn \l__pkgloader_rule_packages_seq {#1} } { \seq_remove_all:Nn \l__pkgloader_rule_packages_seq {#1} } } % \end{macrocode} % % The |recommended| rules are loaded unless explicitly turned off. % % \begin{macrocode} \seq_new:N \l__pkgloader_rule_packages_seq \seq_put_right:Nn \l__pkgloader_rule_packages_seq {recommended} % \end{macrocode} % % Process the options to populate |\l__pkgloader_rule_packages_seq|. % % \begin{macrocode} \cs_generate_variant:Nn \keyval_parse:NNn {NNv} \keyval_parse:NNv \__pkgloader_process_option:n \__pkgloader_process_option:nn {opt@pkgloader.sty} \seq_remove_duplicates:N \l__pkgloader_rule_packages_seq % \end{macrocode} % % Actually load the |.sty| files in |\l__pkgloader_rule_packages_seq|. % Note that the actual file needs the |pkgloader-| prefix. % % \begin{macrocode} \seq_map_inline:Nn \l__pkgloader_rule_packages_seq { \__pkgloader_RPkg:wnw {pkgloader-#1} } % \end{macrocode} % % Finally, we make a show of using the proper macros for \LaTeX's % benefit. If we don't, a \LaTeX\ error is issued. % % \begin{macrocode} \DeclareOption*{} \ProcessOptions\relax % \end{macrocode} % %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% % % Finally, here are the error messages this package can generate. % First a simple error for cycles, which should be improved to % show the cause of the cycle. % % \begin{macrocode} \msg_new:nnn {pkgloader} {cyclic-order} { There~is~a~cycle~in~the~requested~package~loading~order. } % \end{macrocode} % % And the following is the error reported for certain package % combinations that have been forbidden through an |error| rule. % % \begin{macrocode} \msg_new:nnnn {pkgloader} {illegal-combination} { A~combination~of~packages~fitting~the~following~condition~ was~requested: \\\\\ \ \ \ #1\\\\ This~is~an~error~because~#2. } { A~pkgloader~rule~was~requested~that~prohibits~the~logical~ combination\\above~for~the~specified~reason.~It~is~probably~ a~good~reason. } % \end{macrocode} % %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%