11import os .path
2- import glob
32import tempfile
43import shutil
54from pathlib import Path
@@ -10,89 +9,98 @@ class Template:
109
1110 Our problemset.cls latex class was originally written to make it easy to
1211 render a problemset pdf from a bunch of problems for a contest. When we
13- want to render a pdf for a single problem, we need to dump a small,
14- temporary tex file in the parent directory (essentially a minified
15- problemset with just one problem). This class deals with creating and
16- cleaning up that template. The template has to be written in the parent
17- directory of problem_root.
12+ want to render a pdf for a single problem, we essentially create a minified
13+ problemset with a single problem.
14+
15+ This class creates a temporary directory where it writes a .tex file and a
16+ problemset.cls file. Run latex on that tex file to render the problem statement.
17+ The temporary directory and its contents are removed on exit.
18+
19+ We still support the user providing their own problemset.cls in the parent
20+ directory of the problem. This will likely be removed at some point (I don't
21+ think anyone uses this). It can be turned off by setting ignore_parent_cls=True
1822
1923 Usage:
2024 with Template(problem_root, texfile) as templ:
21- texfile = templ.get_file_name()
22- os.chdir(os.path.dirname(texfile))
23- subprocess.call(['pdflatex', texfile])
25+ texfile_path = templ.get_file_name()
26+ os.chdir(os.path.dirname(texfile_path))
27+ subprocess.call(['pdflatex', texfile_path])
28+ # Copy the resulting pdf elsewhere before closing the context
2429 """
2530
26- def __init__ (self , problem_root : Path , texfile : Path , language : str , force_copy_cls = False ):
31+ TEMPLATE_FILENAME = 'template.tex'
32+ CLS_FILENAME = 'problemset.cls'
33+
34+ def __init__ (self , problem_root : Path , texfile : Path , language : str , ignore_parent_cls = False ):
2735 assert texfile .suffix == '.tex' , f'Template asked to render { texfile } , which does not end in .tex'
2836 assert texfile .is_relative_to (problem_root ), f'Template called with tex { texfile } outside of problem { problem_root } '
2937
3038 self .problem_root = problem_root
3139 self .statement_directory = texfile .relative_to (problem_root ).parent
3240 self .statement_filename = texfile .name
33- self .templatefile = 'template.tex'
34- self .clsfile = 'problemset.cls'
3541 self .language = language
3642
37- templatepaths = [
38- os .path .join (os .path .dirname (__file__ ), 'templates/latex' ),
39- os .path .join (os .path .dirname (__file__ ), '../templates/latex' ),
40- '/usr/lib/problemtools/templates/latex' ,
41- ]
43+ self ._tempdir : tempfile .TemporaryDirectory | None = None
44+ self .texfile : Path | None = None
45+
46+ templatepaths = map (
47+ Path ,
48+ [
49+ os .path .join (os .path .dirname (__file__ ), 'templates/latex' ),
50+ os .path .join (os .path .dirname (__file__ ), '../templates/latex' ),
51+ '/usr/lib/problemtools/templates/latex' ,
52+ ],
53+ )
4254 try :
43- self .templatepath = next (
44- (p for p in templatepaths if os .path .isdir (p ) and os .path .isfile (os .path .join (p , self .templatefile )))
45- )
55+ templatepath = next (p for p in templatepaths if p .is_dir () and (p / self .TEMPLATE_FILENAME ).is_file ())
4656 except StopIteration :
47- raise Exception ('Could not find directory with latex template "%s"' % self .templatefile )
57+ raise Exception ('Could not find directory with latex template "%s"' % self .TEMPLATE_FILENAME )
58+ self .templatefile = templatepath / self .TEMPLATE_FILENAME
4859
4960 sample_dir = problem_root / 'data' / 'sample'
5061 if sample_dir .is_dir ():
5162 self .samples = sorted ({file .stem for file in sample_dir .iterdir () if file .suffix in ['.in' , '.interaction' ]})
5263 else :
5364 self .samples = []
5465
55- self .problemset_cls = problem_root .parent / 'problemset.cls'
56- self .copy_cls = True
57- if self .problemset_cls .is_file () and not force_copy_cls :
58- print (f'{ self .problemset_cls } exists, will not copy it -- in case of weirdness this is likely culprit' )
59- self .copy_cls = False
66+ problemset_cls_parent = problem_root .parent / 'problemset.cls'
67+ if not ignore_parent_cls and problemset_cls_parent .is_file ():
68+ print (f'{ problemset_cls_parent } exists, using it -- in case of weirdness this is likely culprit' )
69+ self .clsfile = problemset_cls_parent
70+ else :
71+ self .clsfile = templatepath / self .CLS_FILENAME
6072
6173 def __enter__ (self ):
62- if self .copy_cls :
63- shutil .copyfile (os .path .join (self .templatepath , self .clsfile ), self .problemset_cls )
64-
65- (templfd , self .filename ) = tempfile .mkstemp (suffix = '.tex' , dir = self .problem_root .parent )
66- templout = os .fdopen (templfd , 'w' )
67- templin = open (os .path .join (self .templatepath , self .templatefile ))
68- data = {
69- 'directory' : self .problem_root .name ,
70- 'statement_directory' : self .statement_directory ,
71- 'statement_filename' : self .statement_filename ,
72- 'language' : self .language ,
73- }
74- for line in templin :
75- try :
76- templout .write (line % data )
77- except KeyError :
78- # This is a bit ugly I guess
79- for sample in self .samples :
80- data ['sample' ] = sample
74+ self ._tempdir = tempfile .TemporaryDirectory (prefix = 'problemtools-' )
75+ temp_dir_path = Path (self ._tempdir .name )
76+
77+ shutil .copyfile (self .clsfile , temp_dir_path / self .CLS_FILENAME )
78+
79+ self .texfile = temp_dir_path / 'main.tex'
80+ with open (self .texfile , 'w' ) as templout , open (self .templatefile ) as templin :
81+ data = {
82+ 'problemparent' : str (self .problem_root .parent .resolve ()),
83+ 'directory' : self .problem_root .name ,
84+ 'statement_directory' : self .statement_directory .as_posix (),
85+ 'statement_filename' : self .statement_filename ,
86+ 'language' : self .language ,
87+ }
88+ for line in templin :
89+ try :
8190 templout .write (line % data )
82- if self .samples :
83- del data ['sample' ]
84- templout .close ()
85- templin .close ()
91+ except KeyError :
92+ # This is a bit ugly I guess
93+ for sample in self .samples :
94+ data ['sample' ] = sample
95+ templout .write (line % data )
96+ if self .samples :
97+ del data ['sample' ]
8698 return self
8799
88100 def __exit__ (self , exc_type , exc_value , exc_traceback ):
89- if self .problemset_cls is not None and self .copy_cls and os .path .isfile (self .problemset_cls ):
90- os .remove (self .problemset_cls )
91- if self .filename is not None :
92- for f in glob .glob (os .path .splitext (self .filename )[0 ] + '.*' ):
93- if os .path .isfile (f ):
94- os .remove (f )
95-
96- def get_file_name (self ) -> str : # We should later change this to a Path
97- assert os .path .isfile (self .filename )
98- return self .filename
101+ if self ._tempdir :
102+ self ._tempdir .cleanup ()
103+
104+ def get_file_name (self ) -> Path :
105+ assert self .texfile and self .texfile .is_file ()
106+ return self .texfile
0 commit comments