% numodel-coach.dtx % % Docstrip source for numodel-coach.sty. % Run % tex numodel-coach.ins % to extract the derived file. User-facing documentation lives % in numodel-coach-manual.tex (a stand-alone LaTeX file). % % Copyright (C) 2026 Paul Zuurbier % % 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 this license is in https://www.latex-project.org/lppl.txt % % This work has the LPPL maintenance status 'maintained'. % The Current Maintainer of this work is Paul Zuurbier. % % This work consists of the files numodel-coach.dtx, % numodel-coach.ins, the derived file numodel-coach.sty, and the % companion Lua files numodel-coach.lua and numodel-coach-template.lua. % \section{Implementation} % % numodel-coach writes a numodel model as a CMA Coach 7 modelling % activity (.cma7) in text mode. It reads the model only through % numodel's Lua export API (numodel.get_model, numodel.plaintext) and % the public variable \cs{g_numodel_current_prefix_tl}; the Coach file % format lives entirely in numodel-coach.lua. % % \begin{macrocode} \NeedsTeXFormat{LaTeX2e} \ProvidesExplPackage{numodel-coach}{2026/10/11}{0.11.0} {Export numodel models to CMA Coach 7} \sys_if_engine_luatex:F { \msg_new:nnn { numodel-coach } { luatex-only } { numodel-coach~requires~LuaLaTeX. } \msg_critical:nn { numodel-coach } { luatex-only } } \RequirePackage{numodel} \RequirePackage{embedfile} \lua_load_module:n { numodel-coach } % \end{macrocode} % % \subsection{Options} % % \begin{description} % \item[dir] directory the files are written to, relative to the % working directory (default: empty, the working directory itself). % Created when it does not exist. % \item[attach] also embed the file in the PDF (default false). % \item[switch] let pupils switch between the text and the graphical % view in Coach (default false). Coach derives the text view from % the graphical model, which numodel-coach leaves empty, so % switching loses the model. % \end{description} % % \begin{macrocode} \tl_new:N \g__numodelcoach_dir_tl \bool_new:N \g__numodelcoach_attach_bool \bool_new:N \g__numodelcoach_switch_bool \tl_new:N \l__numodelcoach_prefix_tl \tl_new:N \l__numodelcoach_dir_tl \bool_new:N \l__numodelcoach_attach_bool \bool_new:N \l__numodelcoach_switch_bool \tl_new:N \l__numodelcoach_name_tl \tl_new:N \l__numodelcoach_path_tl \keys_define:nn { numodel-coach / setup } { dir .tl_gset:N = \g__numodelcoach_dir_tl, attach .bool_gset:N = \g__numodelcoach_attach_bool, switch .bool_gset:N = \g__numodelcoach_switch_bool, } \keys_define:nn { numodel-coach / model } { prefix .tl_set:N = \l__numodelcoach_prefix_tl, dir .tl_set:N = \l__numodelcoach_dir_tl, attach .bool_set:N = \l__numodelcoach_attach_bool, switch .bool_set:N = \l__numodelcoach_switch_bool, % blanks=open|filled for this file; empty: numodel's blanks setting. blanks .choices:nn = { open , filled } { \tl_set:Ne \l__numodelcoach_blanks_tl { \l_keys_choice_tl } }, } \tl_new:N \l__numodelcoach_blanks_tl \NewDocumentCommand \coachsetup { m } { \keys_set:nn { numodel-coach / setup } {#1} } % \end{macrocode} % % \subsection{\cs{coachmodel}} % % \cs{coachmodel}\oarg{options}\marg{name} writes the model with the % current prefix (or \texttt{prefix=}) to \meta{dir}/\meta{name}.cma7. % Anything that cannot be exported exactly is reported as a warning. % The iteration count is numodel's \texttt{maxiter}. % % \begin{macrocode} \msg_new:nnn { numodel-coach } { no-model } { No~numodel~model~with~prefix~'#1'.~ Declare~it~with~\token_to_str:N \newmodelprefix\space first. } \msg_new:nnn { numodel-coach } { export } { Model~'#1'~(#2):~#3 } \msg_new:nnn { numodel-coach } { written } { Wrote~Coach~activity~'#1'. } % Called from Lua (numodelcoach.tex_write) with plain-letter names, so % they can be used without expl3 catcodes; the arguments arrive as % catcode-12 strings. \cs_new_protected:Npn \numodelcoachwarn #1#2#3 { \msg_warning:nnnnn { numodel-coach } { export } {#1} {#2} {#3} } \cs_new_protected:Npn \numodelcoachwritten #1 { \msg_info:nnn { numodel-coach } { written } {#1} } \cs_new_protected:Npn \numodelcoachnomodel #1 { \msg_error:nnn { numodel-coach } { no-model } {#1} } % \end{macrocode} % % \subsection{Instruction text} % % The environment \texttt{coachinstruction} defines the instruction text % of a model (current prefix or \texttt{prefix=}); it typesets nothing. % \cs{coachinstructiontext} shows it in the PDF, wherever and as often % as wanted, and \cs{coachmodel} writes it to Coach's instruction window % (converted to HTML by numodelcoach.instruction_html). One source, two % outputs. The body is kept twice: as tokens for the PDF, and % detokenized for the conversion. One instruction per model: a second % definition replaces the first with a warning, as \cs{mvar} does. % % \begin{macrocode} \msg_new:nnn { numodel-coach } { instruction-redefined } { The~instruction~text~of~model~'#1'~is~being~redefined. } \msg_new:nnn { numodel-coach } { no-instruction } { Model~'#1'~has~no~instruction~text~(coachinstruction). } \keys_define:nn { numodel-coach / instruction } { prefix .tl_set:N = \l__numodelcoach_prefix_tl } \NewDocumentEnvironment { coachinstruction } { O{} +b } { \tl_set_eq:NN \l__numodelcoach_prefix_tl \g_numodel_current_prefix_tl \keys_set:nn { numodel-coach / instruction } {#1} \tl_if_exist:cTF { g__numodelcoach_instruction_ \l__numodelcoach_prefix_tl _tl } { \msg_warning:nne { numodel-coach } { instruction-redefined } { \l__numodelcoach_prefix_tl } } { \tl_new:c { g__numodelcoach_instruction_ \l__numodelcoach_prefix_tl _tl } } \tl_gset:cn { g__numodelcoach_instruction_ \l__numodelcoach_prefix_tl _tl } {#2} \lua_now:e { numodelcoach.set_instruction( "\lua_escape:e { \l__numodelcoach_prefix_tl }", "\lua_escape:e { \detokenize {#2} }") } } { } \NewDocumentCommand \coachinstructiontext { O{} } { \tl_set_eq:NN \l__numodelcoach_prefix_tl \g_numodel_current_prefix_tl \keys_set:nn { numodel-coach / instruction } {#1} \tl_if_exist:cTF { g__numodelcoach_instruction_ \l__numodelcoach_prefix_tl _tl } { \tl_use:c { g__numodelcoach_instruction_ \l__numodelcoach_prefix_tl _tl } } { \msg_warning:nne { numodel-coach } { no-instruction } { \l__numodelcoach_prefix_tl } } } % \end{macrocode} % % \begin{macrocode} \NewDocumentCommand \coachmodel { O{} m } { \tl_set_eq:NN \l__numodelcoach_prefix_tl \g_numodel_current_prefix_tl \tl_set_eq:NN \l__numodelcoach_dir_tl \g__numodelcoach_dir_tl \bool_set_eq:NN \l__numodelcoach_attach_bool \g__numodelcoach_attach_bool \bool_set_eq:NN \l__numodelcoach_switch_bool \g__numodelcoach_switch_bool \tl_clear:N \l__numodelcoach_blanks_tl \keys_set:nn { numodel-coach / model } {#1} \__numodelcoach_write:n {#2} } \cs_new_protected:Npn \__numodelcoach_write:n #1 { \tl_set:Nn \l__numodelcoach_name_tl {#1} \str_if_in:nnF {#1} { . } { \tl_put_right:Nn \l__numodelcoach_name_tl { .cma7 } } \tl_set:Ne \l__numodelcoach_path_tl { \tl_if_empty:NF \l__numodelcoach_dir_tl { \tl_use:N \l__numodelcoach_dir_tl / } \tl_use:N \l__numodelcoach_name_tl } \lua_now:e { numodelcoach.tex_write( "\lua_escape:e { \l__numodelcoach_prefix_tl }", "\lua_escape:e { \l__numodelcoach_path_tl }", \bool_if:NTF \l__numodelcoach_switch_bool { true } { false }, "\tl_use:N \l__numodelcoach_blanks_tl") } \bool_if:NT \l__numodelcoach_attach_bool { \use:e { \exp_not:N \embedfile [ filespec = { \tl_use:N \l__numodelcoach_name_tl } , mimetype = application/octet-stream , desc = { Coach~7~model~\tl_to_str:n {#1} } ] { \tl_use:N \l__numodelcoach_path_tl } } } } % \end{macrocode}