-
Notifications
You must be signed in to change notification settings - Fork 1
/
solprint.sty
281 lines (228 loc) · 8.74 KB
/
solprint.sty
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
\ProvidesPackage{solprint}
% we write to a file called \jobname.sol
\newwrite\@solout
% boolean for whether the file is the source file or a separate solutions document
\newif\ifsource\sourcetrue
% counter for prob files
\newcounter{probfile}
\setcounter{probfile}{0}
% counter for sol files
\newcounter{solfile}
\setcounter{solfile}{0}
% - Options
% Used to change the default environment name in case of conflicts
\RequirePackage{kvoptions}
\DeclareBoolOption[false]{showsol}
\ProcessKeyvalOptions*
\RequirePackage{fancyvrb} % provides the VerbatimOut environment
\RequirePackage{fvextra} % patches fancyverb
\def\solprint@envname{solu}
\def\solprintenvname#1{\edef\solprint@envname{#1}}
% Makes an environment part of solprint
% Any environment that goes under this command
% will be redefined to print out its contents
% verbatim to a \jobname.prob file.
\RequirePackage{environ}
% Used to temporarily redefine solu to swallow the body
% when the file we are compiling from
% is not the source.
\RequirePackage{xpatch} % Necessary in order to
% patch the environments correctly
% so that they still take in the correct number [and type] of arguments.
% Solify - makes an environment work with solprint
%
% Usage: \solify[optional prefix]{envvar}{envname}{numref}
% The default prefix is Solution to
%
% #1 is prefix (optional)
% #2 is environment variable (say exer)
% #3 is name of environment (say Exercise)
% #4 is number to reference (usually should contain some sort of counter)
% Note that the package assumes that the first argument of the environment
% is the source.
%
% titledefined checks whether title is defined
% 0 means undefined, 1 means defined
% Used to throw errors
\def\titledefined{0}
% This is more robust and allows you to print problem statements in an external file,
% at the cost of being less flexible.
% In particular, you cannot solify most tcolorbox or mdframed environments,
% and you NEVER can solify an environment with one optional argument and no mandatory arguments.
\newcommand\solify[4][Solution to]{
% we define this because we are going to change the catcode of # later on
% and need to pass it into \solifyhack.
\def\env@var{#2}
\expandafter\xapptocmd\csname \env@var\endcsname{% We insert comments
\stepcounter{probfile}% after each line,
\gdef\titledefined{1}% so as to not
\gdef\solprint@envprefix{#1}% run into spacing issues.
\gdef\solprint@envproblem{#3 #4}%
\gdef\solprint@envassoc{\theprobfile}%
}{}{}
\solifyhack
\expandafter\xpretocmd\csname end\env@var\endcsname{%
\endVerbatimOut% The opening VerbatimOut is in solifyhack
\input{\jobname-\theprobfile.prob}%
\gdef\titledefined{0}%
}{}{}
}
% We change the catcode so that #1 isn't interpreted
% as an argument of \solifyhack.
\catcode`\#=12
\newcommand\solifyhack{
% We put this hack here because xpatch cannot deal with nested macros
% if the outer macro has arguments.
\expandafter\xapptocmd\csname \env@var\endcsname{%
\xdef\solprint@envsource{#1}%
\VerbatimOut{\jobname-\theprobfile.prob}%
}{}{}
}
\catcode`\#=6
% This is less robust but can be used on a number of different environments.
% The cost is we cannot print the problem statement anywhere else.
\newcommand\safesolify[4][Solution to]{
\def\env@var{#2}
\expandafter\xapptocmd\csname \env@var\endcsname{% We insert comments
\gdef\titledefined{1}% so as to not
\gdef\solprint@envprefix{#1}% run into spacing issues.
\gdef\solprint@envproblem{#3 #4}%
}{}{}
\safesolifyhack
\expandafter\xpretocmd\csname end\env@var\endcsname{%
\gdef\titledefined{0}%
}{}{}
}
\catcode`\#=12
\newcommand\safesolifyhack{
% We put this hack here because xpatch cannot deal with nested macros
% if the outer macro has arguments.
\expandafter\xapptocmd\csname \env@var\endcsname{%
\xdef\solprint@envsource{#1}%
}{}{}
}
\catcode`\#=6
% define env based on envname passed in
\newenvironment{\solprint@envname}
{
\ifnum\titledefined=0
% Throw an error if \solprint@envtitle is undefined
% This is done for user convenience
\PackageError{solprint}{The environment ``\solprint@envname'' must be in an environment that handles solutions}{Developers should see \protect\solify in the package documentation.}
\fi
\stepcounter{solfile}
\immediate\write\@solout{% we comment after each line to prevent extra spaces from being inserted
% ^^J prints a linebreak
\string\expandafter\string\xdef\noexpand\csname solprint@prefix\thesolfile\string\endcsname{\solprint@envprefix}^^J%
\string\expandafter\string\xdef\noexpand\csname solprint@problem\thesolfile\string\endcsname{\solprint@envproblem}^^J%
\ifdefined\solprint@envassoc
\string\expandafter\string\xdef\noexpand\csname solprint@assoc\thesolfile\string\endcsname{\solprint@envassoc}^^J%
\fi
\string\expandafter\string\xdef\noexpand\csname solprint@source\thesolfile\string\endcsname{\solprint@envsource}
}
% creates refs for hyperlinks (used only if file is source)
\label{solu:\thesolfile}
\VerbatimOut{\jobname-\thesolfile.sol}% need % here because VerbatimOut behaves weirdly with spaces
}
{
\endVerbatimOut
}
% Define titles for solprint
\newcommand{\solprint@title}{
\ifsource
\newpage
\section{Solutions}
\else
\maketitle
\ifdefined\toc % we expect people to use \toc
\else
\newcommand{\toc}{\newpage\tableofcontents\newpage}
\fi
\toc
\fi
}
\newcommand{\solprint@sol}[1]{
\def\temp@prefix{\csname solprint@prefix#1\endcsname}
\def\temp@problem{\csname solprint@problem#1\endcsname}
\edef\temp@source{\csname solprint@source#1\endcsname}
\edef\sol@filename{\sol@prefix-#1.sol}% Comments are needed
\IfFileExists{\sol@filename}{% To eliminate any spaces
% That follow the ends of the lines
\ifsource
\subsection{\temp@prefix{} \ifsource\hyperref[solu:#1]{\temp@problem}\else\temp@problem\fi{} \ifx\empty\temp@source\else(\temp@source)\fi}
\input{\sol@filename}
\else
\section{\temp@prefix{} \temp@problem{} (\temp@source)}
\expandafter\ifdefined\csname solprint@assoc#1\endcsname
\edef\prob@filename{\sol@prefix-\csname solprint@assoc#1\endcsname.prob}
\begingroup
\RenewEnviron{\solprint@envname}{}
\input{\prob@filename}
\endgroup
\fi
\subsection{Solution}
\input{\sol@filename}
\fi
}{}
}
% Includes all sol files in order
\ifsolprint@showsol
\newcommand\solprint[1][\jobname]{
% defines the file to look for solutions to be the optional argument
% checks if file is source of the problems and solutions
\ifnum\pdfstrcmp{#1}{\jobname}=0
\else
\sourcefalse
\fi
\immediate\write\@auxout{\string\gdef\string\sol@prefix{#1}}
% We need this \ifnum here because TeX will treat a foreach from
% 1 to 0 as including 1
\ifdefined\sol@count
\ifnum\sol@count>0
\solprint@title % prints solution title
\foreach \i in {1, ..., \sol@count} {
\solprint@sol{\i}
}
\fi
\fi
}
\else
\newcommand\solprint[1]{}
\fi
% inputs solout at beginning of document if it exists (requires running twice)
\AtBeginDocument{
\ifdefined\sol@prefix
\IfFileExists{\sol@prefix.sol}
{
% because we are hooking into the beginning of the document,
% we are making @ a letter and making @ an other afterwards
% This is because we are inputting \sol@prefix.sol,
% which may (and usually does) contain commands with the @ character.
% It is NOT because the string "\sol@prefix.sol" contains an @ character!
\makeatletter
\input{\sol@prefix.sol}
% put openout here so tex can process the .sol file first
\makeatother
}
{
\PackageError{solprint}{Make sure the .sol file you passed into \protect\solprint exists.}
}
\fi
\immediate\openout\@solout = \jobname.sol
}
% Writes to @solout at end of document
\AtEndDocument{
\ifdefined\sol@prefix
% We don't need to check for file existence because we already did in AtBeginDocument
\makeatletter
\input{\sol@prefix.sol}
\makeatother
\fi
\ifsource
\immediate\write\@solout{% we comment to prevent latex from inserting spaces after linebreaks
\string\gdef\string\sol@count{\thesolfile} % and we use \string to tell latex to read the next control sequence literally
}
% closes @solout afterwards
\closeout\@solout
\fi
}