Feat: build subjects metadatas

This commit is contained in:
Bertrand Benjamin 2022-04-09 07:30:13 +02:00
parent ebf5bd0c7d
commit 1865f9ec63
3 changed files with 68 additions and 299 deletions

View File

@ -5,15 +5,8 @@
Producing then compiling templates
"""
import csv
import os
import logging
from pathlib import Path
import pytex
#from mapytex import Expression, Integer, Decimal, random_list
import mapytex
import bopytex.filters as filters
import csv
formatter = logging.Formatter("%(name)s :: %(levelname)s :: %(message)s")
steam_handler = logging.StreamHandler()
@ -24,222 +17,42 @@ logger.setLevel(logging.DEBUG)
logger.addHandler(steam_handler)
def setup():
mapytex.Expression.set_render("tex")
logger.debug(f"Render for Expression is {mapytex.Expression.RENDER}")
mapytex_tools = {
"Expression": mapytex.Expression,
"Integer": mapytex.Integer,
"Decimal": mapytex.Decimal,
"random_list": mapytex.random_list,
"random_pythagore": mapytex.random_pythagore,
"Dataset": mapytex.Dataset,
"WeightedDataset": mapytex.WeightedDataset,
}
pytex.update_export_dict(mapytex_tools)
pytex.add_filter("calculus", filters.do_calculus)
def build_subject_list_from_infos(infos: list[dict]) -> list[dict]:
subjects = []
digit = len(str(len(infos)))
for i, infos in enumerate(infos):
subjects.append({"number": str(i + 1).zfill(digit), **infos})
return subjects
def get_working_dir(options):
""" Get the working directory """
if options["working_dir"]:
working_dir = Path(options["working_dir"])
else:
try:
template = Path(options["template"])
except TypeError:
raise ValueError(
"Need to set the working directory \
or to give a template"
)
else:
working_dir = template.parent
logger.debug(f"The output directory will be {working_dir}")
return working_dir
def build_subject_list_from_qty(qty: int) -> list[dict]:
subjects = []
digit = len(str(qty))
for i in range(qty):
subjects.append({"number": str(i + 1).zfill(digit)})
return subjects
def build_subjects(students_csv, quantity_subjects):
if students_csv:
with open(students_csv, "r") as csv_file:
infos = csv.DictReader(csv_file)
return build_subject_list_from_infos(infos)
return build_subject_list_from_qty(quantity_subjects)
def activate_printanswers(
texfile,
noans=r"%\printsolutionstype{exercise}",
ans=r"\printsolutionstype{exercise}",
corr_prefix="corr_"
def bopytex(
template: str,
working_dir: str,
students_csv: str,
quantity_subjects: int,
corr: bool,
dirty: bool,
only_corr: bool,
crazy: bool,
no_join: bool,
):
""" Activate printanswers mod in texfile
:param texfile: path to the latex file
:param noans: string that prevent printing solution
:param ans: string that activate printing solution
:param corr_prefix: correction file prefix
"""
output_fname = corr_prefix + texfile
with open(texfile, "r") as input_f:
with open(output_fname, "w") as output_f:
for line in input_f.readlines():
output_f.write(line.replace(noans, ans))
return output_fname
def deactivate_printanswers(corr_fname):
""" Activate printanswers mod in texfile """
Path(corr_fname).remove()
def pdfjoin(pdf_files, destname, working_dir=".", rm_pdfs=1):
"""TODO: Docstring for pdfjoin.
:param pdf_files: list of pdf files to join
:param destname: name for joined pdf
:param working_dir: the working directory
:param rm_pdfs: Remove pdf_files after joining them
:returns: TODO
"""
joined_pdfs = Path(working_dir) / Path(destname)
pdf_files_str = " ".join(pdf_files)
pdfjam = f"pdfjam {pdf_files_str} -o {joined_pdfs}"
logger.debug(f"Run {pdfjam}")
logger.info("Joining pdf files")
os.system(pdfjam)
if rm_pdfs:
logger.info(f"Remove {pdf_files_str}")
os.system(f"rm {pdf_files_str}")
def extract_student_csv(csv_filename):
""" Extract student list from csv_filename """
with open(csv_filename, "r") as csvfile:
reader = csv.DictReader(csvfile)
return [r for r in reader]
def subject_metadatas(options):
""" Return metadata on subject to produce
if csv is given it will based on is
otherwise it will be based on quantity
:example:
>>> subject_metadata(10)
"""
if options["students_csv"]:
metadatas = []
for (i, s) in enumerate(extract_student_csv(options["students_csv"])):
d = {"num": f"{i+1:02d}"}
d.update(s)
metadatas.append(d)
elif options["number_subjects"] > 0:
metadatas = [{"num": f"{i+1:02d}"}
for i in range(options["number_subjects"])]
else:
raise ValueError("Need metacsv or quantity to build subject metadata")
for meta in metadatas:
meta.update(
{
"template": str(Path(options["template"]).name),
"texfile": str(Path(options["template"]).name).replace(
"tpl", meta["num"]
),
"directory": str(Path(options["template"]).parent),
}
)
return metadatas
def feed(*args, **kwrds):
""" Nice and smooth pytex feed """
pytex.feed(*args, **kwrds)
def crazy_feed(*args, **kwrds):
""" Crazy mod for pytex feed """
while True:
try:
pytex.feed(*args, **kwrds)
except:
logger.debug(f"Crazy feed is working hard...! {args} {kwrds}")
else:
break
def clean(directory):
pytex.clean(directory)
def texcompile(filename):
logger.debug(f"Start compiling {filename}")
pytex.pdflatex(Path(filename))
logger.debug(f"End compiling")
def produce_and_compile(options):
""" Produce and compile subjects
"""
logger.debug(f"CI parser gets {options}")
template = Path(options["template"]).name
directory = Path(options["template"]).parent
metadatas = subject_metadatas(options)
logger.debug(f"Metadata {metadatas}")
for meta in metadatas:
logger.debug(f"Feeding template toward {meta['texfile']}")
if options["crazy"]:
crazy_feed(
template=Path(meta["directory"]) / meta["template"],
data=meta,
output=meta["texfile"],
force=1,
)
else:
feed(
template=Path(meta["directory"]) / meta["template"],
data=meta,
output=meta["texfile"],
force=1,
)
assert(Path(meta["texfile"]).exists())
logger.debug(f"{meta['texfile']} fed")
if options["corr"]:
logger.debug(f"Building correction for {meta['texfile']}")
meta.update({
"corr_texfile": activate_printanswers(meta["texfile"]),
})
if not options["no_compile"]:
for prefix in ["", "corr_"]:
key = prefix + "texfile"
try:
meta[key]
except KeyError:
pass
else:
texcompile(meta[key])
meta.update({
prefix+'pdffile': meta[key].replace('tex', 'pdf')
})
if not options["no_join"]:
for prefix in ["", "corr_"]:
key = prefix + "pdffile"
try:
pdfs = [m[key] for m in metadatas]
except KeyError:
pass
else:
pdfjoin(
pdfs,
template.replace(
"tpl", prefix+"all").replace(".tex", ".pdf"),
directory,
rm_pdfs=1,
)
if not options["dirty"]:
clean(directory)
pass
# -----------------------------

View File

@ -5,7 +5,8 @@
import click
import logging
from pathlib import Path
from .bopytex import subject_metadatas, crazy_feed, pdfjoin, feed, clean, texcompile, setup, activate_printanswers
from bopytex.bopytex import bopytex
formatter = logging.Formatter("%(name)s :: %(levelname)s :: %(message)s")
steam_handler = logging.StreamHandler()
@ -26,6 +27,7 @@ logger.addHandler(steam_handler)
@click.option(
"-w",
"--working-dir",
default=".",
type=click.Path(exists=True),
)
@click.option(
@ -46,11 +48,11 @@ logger.addHandler(steam_handler)
help="Do not compile source code",
)
@click.option(
"-N",
"--number_subjects",
"-q",
"--quantity_subjects",
type=int,
default=1,
help="The number of subjects to make",
help="The quantity of subjects to make",
)
@click.option(
"-j",
@ -81,82 +83,7 @@ logger.addHandler(steam_handler)
help="Crazy mode. Tries and tries again until template feeding success!",
)
def new(**options):
""" Bopytex
Feed the template (tpl_...) and then compile it with latex.
"""
setup()
logger.debug(f"CI parser gets {options}")
template = Path(options["template"]).name
directory = Path(options["template"]).parent
metadatas = subject_metadatas(options)
logger.debug(f"Metadata {metadatas}")
for meta in metadatas:
if not options["only_corr"]:
logger.debug(f"Feeding template toward {meta['texfile']}")
if options["crazy"]:
crazy_feed(
template=Path(meta["directory"]) / meta["template"],
data=meta,
output=meta["texfile"],
force=1,
)
else:
feed(
template=Path(meta["directory"]) / meta["template"],
data=meta,
output=meta["texfile"],
force=1,
)
assert(Path(meta["texfile"]).exists())
logger.debug(f"{meta['texfile']} fed")
if options["corr"] or options["only_corr"]:
logger.debug(f"Building correction for {meta['texfile']}")
meta.update({
"corr_texfile": activate_printanswers(meta["texfile"]),
})
if not options["no_compile"]:
if options["only_corr"]:
to_compile = ["corr_"]
else:
to_compile = ["", "corr_"]
for prefix in to_compile:
key = prefix + "texfile"
try:
meta[key]
except KeyError:
pass
else:
texcompile(meta[key])
meta.update({
prefix+'pdffile': meta[key].replace('tex', 'pdf')
})
if not options["no_join"]:
for prefix in ["", "corr_"]:
key = prefix + "pdffile"
try:
pdfs = [m[key] for m in metadatas]
except KeyError:
pass
else:
pdfjoin(
pdfs,
template.replace(
"tpl", prefix+"all").replace(".tex", ".pdf"),
directory,
rm_pdfs=1,
)
if not options["dirty"]:
clean(directory)
bopytex(**options)
if __name__ == "__main__":

29
test/test_bopytex.py Normal file
View File

@ -0,0 +1,29 @@
from bopytex.bopytex import build_subject_list_from_infos, build_subject_list_from_qty
def test_build_subject_list_from_qty():
subjects = build_subject_list_from_qty(10)
assert subjects == [
{"number": "01"},
{"number": "02"},
{"number": "03"},
{"number": "04"},
{"number": "05"},
{"number": "06"},
{"number": "07"},
{"number": "08"},
{"number": "09"},
{"number": "10"},
]
def test_build_subject_list_from_infos():
infos = [
{"name": "test1", "date": "today"},
{"name": "test2", "date": "tomorow"},
]
subjects = build_subject_list_from_infos(infos)
assert subjects == [
{"name": "test1", "date": "today", "number": "1"},
{"name": "test2", "date": "tomorow", "number": "2"},
]