Feat: build subjects metadatas
This commit is contained in:
parent
ebf5bd0c7d
commit
1865f9ec63
@ -5,15 +5,8 @@
|
|||||||
Producing then compiling templates
|
Producing then compiling templates
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import csv
|
|
||||||
import os
|
|
||||||
import logging
|
import logging
|
||||||
|
import csv
|
||||||
from pathlib import Path
|
|
||||||
import pytex
|
|
||||||
#from mapytex import Expression, Integer, Decimal, random_list
|
|
||||||
import mapytex
|
|
||||||
import bopytex.filters as filters
|
|
||||||
|
|
||||||
formatter = logging.Formatter("%(name)s :: %(levelname)s :: %(message)s")
|
formatter = logging.Formatter("%(name)s :: %(levelname)s :: %(message)s")
|
||||||
steam_handler = logging.StreamHandler()
|
steam_handler = logging.StreamHandler()
|
||||||
@ -24,222 +17,42 @@ logger.setLevel(logging.DEBUG)
|
|||||||
logger.addHandler(steam_handler)
|
logger.addHandler(steam_handler)
|
||||||
|
|
||||||
|
|
||||||
def setup():
|
def build_subject_list_from_infos(infos: list[dict]) -> list[dict]:
|
||||||
mapytex.Expression.set_render("tex")
|
subjects = []
|
||||||
logger.debug(f"Render for Expression is {mapytex.Expression.RENDER}")
|
digit = len(str(len(infos)))
|
||||||
mapytex_tools = {
|
for i, infos in enumerate(infos):
|
||||||
"Expression": mapytex.Expression,
|
subjects.append({"number": str(i + 1).zfill(digit), **infos})
|
||||||
"Integer": mapytex.Integer,
|
return subjects
|
||||||
"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 get_working_dir(options):
|
def build_subject_list_from_qty(qty: int) -> list[dict]:
|
||||||
""" Get the working directory """
|
subjects = []
|
||||||
if options["working_dir"]:
|
digit = len(str(qty))
|
||||||
working_dir = Path(options["working_dir"])
|
for i in range(qty):
|
||||||
else:
|
subjects.append({"number": str(i + 1).zfill(digit)})
|
||||||
try:
|
return subjects
|
||||||
template = Path(options["template"])
|
|
||||||
except TypeError:
|
def build_subjects(students_csv, quantity_subjects):
|
||||||
raise ValueError(
|
if students_csv:
|
||||||
"Need to set the working directory \
|
with open(students_csv, "r") as csv_file:
|
||||||
or to give a template"
|
infos = csv.DictReader(csv_file)
|
||||||
)
|
return build_subject_list_from_infos(infos)
|
||||||
else:
|
|
||||||
working_dir = template.parent
|
return build_subject_list_from_qty(quantity_subjects)
|
||||||
logger.debug(f"The output directory will be {working_dir}")
|
|
||||||
return working_dir
|
|
||||||
|
|
||||||
|
|
||||||
def activate_printanswers(
|
def bopytex(
|
||||||
texfile,
|
template: str,
|
||||||
noans=r"%\printsolutionstype{exercise}",
|
working_dir: str,
|
||||||
ans=r"\printsolutionstype{exercise}",
|
students_csv: str,
|
||||||
corr_prefix="corr_"
|
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
|
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)
|
|
||||||
|
|
||||||
|
|
||||||
# -----------------------------
|
# -----------------------------
|
||||||
|
@ -5,7 +5,8 @@
|
|||||||
import click
|
import click
|
||||||
import logging
|
import logging
|
||||||
from pathlib import Path
|
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")
|
formatter = logging.Formatter("%(name)s :: %(levelname)s :: %(message)s")
|
||||||
steam_handler = logging.StreamHandler()
|
steam_handler = logging.StreamHandler()
|
||||||
@ -26,6 +27,7 @@ logger.addHandler(steam_handler)
|
|||||||
@click.option(
|
@click.option(
|
||||||
"-w",
|
"-w",
|
||||||
"--working-dir",
|
"--working-dir",
|
||||||
|
default=".",
|
||||||
type=click.Path(exists=True),
|
type=click.Path(exists=True),
|
||||||
)
|
)
|
||||||
@click.option(
|
@click.option(
|
||||||
@ -46,11 +48,11 @@ logger.addHandler(steam_handler)
|
|||||||
help="Do not compile source code",
|
help="Do not compile source code",
|
||||||
)
|
)
|
||||||
@click.option(
|
@click.option(
|
||||||
"-N",
|
"-q",
|
||||||
"--number_subjects",
|
"--quantity_subjects",
|
||||||
type=int,
|
type=int,
|
||||||
default=1,
|
default=1,
|
||||||
help="The number of subjects to make",
|
help="The quantity of subjects to make",
|
||||||
)
|
)
|
||||||
@click.option(
|
@click.option(
|
||||||
"-j",
|
"-j",
|
||||||
@ -81,82 +83,7 @@ logger.addHandler(steam_handler)
|
|||||||
help="Crazy mode. Tries and tries again until template feeding success!",
|
help="Crazy mode. Tries and tries again until template feeding success!",
|
||||||
)
|
)
|
||||||
def new(**options):
|
def new(**options):
|
||||||
""" Bopytex
|
bopytex(**options)
|
||||||
|
|
||||||
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)
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
|
29
test/test_bopytex.py
Normal file
29
test/test_bopytex.py
Normal 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"},
|
||||||
|
]
|
Loading…
Reference in New Issue
Block a user