Last active
December 23, 2025 02:20
-
-
Save ptoche/b6a985a91f8c6f71b0e85a674be20be9 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| \begin{filecontents}[overwrite]{mcstyles.sty} | |
| \ProvidesPackage{mcstyles}[2025/11/18 v1.4] | |
| \RequirePackage{expl3,xparse} | |
| \ExplSyntaxOn | |
| %------------------------------------------------------------ | |
| % Storage | |
| %------------------------------------------------------------ | |
| \prop_new:N \g_mymc_questions_prop | |
| \prop_new:N \g_mymc_solutions_prop | |
| \tl_new:N \l_mymc_current_id_tl | |
| %------------------------------------------------------------ | |
| % Keys: just set ID | |
| %------------------------------------------------------------ | |
| \keys_define:nn { mymc/mc } | |
| { | |
| ID .tl_set:N = \l_mymc_current_id_tl | |
| } | |
| %------------------------------------------------------------ | |
| % mc environment captures question body | |
| %------------------------------------------------------------ | |
| \NewDocumentEnvironment{mc}{ O{} +b } | |
| { | |
| \keys_set:nn { mymc/mc } { #1 } | |
| \prop_gput:NVn \g_mymc_questions_prop \l_mymc_current_id_tl {#2} | |
| } | |
| {} | |
| %------------------------------------------------------------ | |
| % mcsol environment captures solution body | |
| %------------------------------------------------------------ | |
| \NewDocumentEnvironment{mcsol}{ O{} +b } | |
| { | |
| \keys_set:nn { mymc/mc } { #1 } | |
| \prop_gput:NVn \g_mymc_solutions_prop \l_mymc_current_id_tl {#2} | |
| } | |
| {} | |
| %------------------------------------------------------------ | |
| % Include question by ID with optional points | |
| %------------------------------------------------------------ | |
| \NewDocumentCommand{\IncludeQuestion}{m o} | |
| { | |
| \prop_get:NnN \g_mymc_questions_prop {#1} \l_tmpa_tl | |
| \tl_if_blank:VTF \l_tmpa_tl | |
| { } % do nothing if missing | |
| { | |
| % If optional argument is given, prints it above the question | |
| \IfValueT{#2}{\noindent#2} | |
| \tl_use:N \l_tmpa_tl | |
| } | |
| } | |
| %------------------------------------------------------------ | |
| % Include solution by ID | |
| %------------------------------------------------------------ | |
| \NewDocumentCommand{\IncludeSolution}{m} | |
| { | |
| \prop_get:NnN \g_mymc_solutions_prop {#1} \l_tmpa_tl | |
| \tl_if_blank:VTF \l_tmpa_tl | |
| { } % do nothing if missing | |
| { \tl_use:N \l_tmpa_tl } | |
| } | |
| %------------------------------------------------------------ | |
| % Global variables | |
| %------------------------------------------------------------ | |
| \seq_new:N \g_mymc_pts_seq % stores points in current counting block | |
| \seq_new:N \g_mymc_subtotals_seq % stores subtotals | |
| \dim_new:N \g_mymc_pointsparsep_dim % horizontal margin spacing | |
| \dim_gset:Nn \g_mymc_pointsparsep_dim {1em} % default | |
| %------------------------------------------------------------ | |
| % User-level command to control horizontal spacing of margin points | |
| %------------------------------------------------------------ | |
| \NewDocumentCommand{\pointsparsep}{ m } | |
| { | |
| \dim_gset:Nn \g_mymc_pointsparsep_dim {#1} | |
| } | |
| %------------------------------------------------------------ | |
| % Start and end counting for a block | |
| %------------------------------------------------------------ | |
| \NewDocumentCommand{\startpointscount}{} { \seq_clear:N \g_mymc_pts_seq } | |
| \NewDocumentCommand{\endpointscount}{} { | |
| % sum the points in this block and store as a subtotal | |
| \dim_zero:N \l_tmpa_dim | |
| \seq_map_inline:Nn \g_mymc_pts_seq { \dim_add:Nn \l_tmpa_dim {##1pt} } | |
| \seq_gput_right:Nn \g_mymc_subtotals_seq {\l_tmpa_dim} | |
| \seq_clear:N \g_mymc_pts_seq | |
| } | |
| %------------------------------------------------------------ | |
| % Print subtotal or grand total | |
| %------------------------------------------------------------ | |
| \NewDocumentCommand{\printpointscount}{} { | |
| % sum current block if not empty | |
| \dim_zero:N \l_tmpa_dim | |
| \seq_map_inline:Nn \g_mymc_pts_seq { \dim_add:Nn \l_tmpa_dim {##1pt} } | |
| \textbf{Subtotal:~\dim_use:N \l_tmpa_dim~pts} | |
| } | |
| \NewDocumentCommand{\printpointstotal}{} { | |
| \dim_zero:N \l_tmpa_dim | |
| \seq_map_inline:Nn \g_mymc_subtotals_seq { \dim_add:Nn \l_tmpa_dim {##1} } | |
| \textbf{Total:~\dim_use:N \l_tmpa_dim~pts} | |
| } | |
| %------------------------------------------------------------ | |
| % Main points command for questions | |
| %------------------------------------------------------------ | |
| \NewDocumentCommand{\pts}{ m } | |
| { | |
| % store points in current block | |
| \seq_gput_right:Nn \g_mymc_pts_seq {#1} | |
| % print in margin | |
| \mymc_pts_margin:n {#1} | |
| } | |
| %------------------------------------------------------------ | |
| % Internal margin note function | |
| %------------------------------------------------------------ | |
| \cs_new_protected:Nn \mymc_pts_margin:n | |
| { | |
| %\reversemarginpar % toggle | |
| \normalmarginpar % Explicitly set to normal behavior (usually right/outside) | |
| \marginpar | |
| { | |
| \hspace*{\g_mymc_pointsparsep_dim} | |
| \raggedright | |
| \footnotesize | |
| \parbox{\marginparwidth}{ | |
| \raggedleft | |
| \textbf{[#1]}%\par % \par is needed inside a parbox to finalize the paragraph alignment | |
| } | |
| } | |
| } | |
| \ExplSyntaxOff | |
| \endinput | |
| \end{filecontents} | |
| \begin{filecontents}[overwrite]{bank.tex} | |
| \begin{mc}[ID=Q1001] | |
| Which of the following is correct? | |
| \begin{enumerate} | |
| \item A | |
| \item B | |
| \end{enumerate} | |
| \end{mc} | |
| \begin{mcsol}[ID=S1001] | |
| The correct answer is B. | |
| \end{mcsol} | |
| \begin{mc}[ID=Q1002] | |
| \begin{minipage}[t]{0.48\textwidth}\vspace{0pt}% | |
| In the figure is the clue to this question: | |
| \begin{enumerate} | |
| \item The first item in the list. | |
| \item The second item in the list. | |
| \end{enumerate} | |
| \end{minipage} | |
| \hfill% | |
| \begin{minipage}[t]{0.20\textwidth}\vspace{0pt}% | |
| \begin{figure}[H] | |
| \includegraphics[width=\linewidth]{example-image-a} | |
| \captionof{figure}{Caption.} | |
| \end{figure} | |
| \end{minipage} | |
| \end{mc} | |
| \begin{mcsol}[ID=S1002] | |
| The correct answer is: | |
| \begin{align*} | |
| e^{\pi i} + 1 = 0 | |
| \end{align*} | |
| \end{mcsol} | |
| \end{filecontents} | |
| \documentclass[10pt]{article} | |
| \usepackage[showframe]{geometry} | |
| \usepackage{amsmath} | |
| \usepackage{mathtools} | |
| \usepackage{graphicx} | |
| \usepackage{caption} | |
| \graphicspath{{./Figures/}} | |
| \usepackage{float}% [H] option to figures | |
| \setlength{\textfloatsep}{1ex} | |
| \setlength{\intextsep}{0ex} | |
| \setlength{\abovecaptionskip}{1ex} | |
| \setlength{\parindent}{0pt} | |
| \usepackage{ragged2e}% \justifying | |
| \usepackage[shortlabels]{enumitem} | |
| \usepackage{mcstyles} | |
| \pointsparsep{3em}% reset distance between printed points and margin | |
| \input{bank.tex} | |
| \begin{document} | |
| \section*{Questions Only} | |
| \begin{enumerate}[1.] | |
| \item \IncludeQuestion{Q1001}[\pts{5}] | |
| \item \IncludeQuestion{Q1002}[\pts{20}] | |
| \end{enumerate} | |
| \section*{Questions with Solutions} | |
| % Set enumitem list settings | |
| \setlist[enumerate,1]{label=\textbf{B\arabic*}} | |
| \setlist*[enumerate,1]{align=left} | |
| \setlist[enumerate,2]{label=(\alph*)} | |
| \begin{enumerate} | |
| \item \IncludeQuestion{Q1001}[\pts{5}] | |
| \textbf{Solution:} \IncludeSolution{S1001} | |
| \item \IncludeQuestion{Q1002}[\pts{20}] | |
| \textbf{Solution:} \IncludeSolution{S1002} | |
| \end{enumerate} | |
| \end{document} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment