Skip to content

Instantly share code, notes, and snippets.

@ptoche
Last active December 23, 2025 02:20
Show Gist options
  • Select an option

  • Save ptoche/b6a985a91f8c6f71b0e85a674be20be9 to your computer and use it in GitHub Desktop.

Select an option

Save ptoche/b6a985a91f8c6f71b0e85a674be20be9 to your computer and use it in GitHub Desktop.
\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