"""The main input for tex2lambda, defining both the CLT and main library function."""
import importlib
import pkgutil
from typing import Optional
import panflute as pf
import rich_click as click
import tex2lambda.subjects
from tex2lambda.api.module import Module
from tex2lambda.json_convert import json_convert
[docs]def runner(
question_file: str,
subject: str,
output_dir: Optional[str] = None,
answer_file: Optional[str] = None,
) -> Module:
"""Takes in a TeX file for a given subject and outputs how it's broken down within Lambda Feedback.
Args:
question_file: The absolute path to a TeX question file.
subject: The subject which the TeX file contains questions for.
output_dir: An optional argument for where to output the Lambda Feedback compatible json/zip files.
answer_file: The absolute path to a TeX answer file.
Returns:
A list of questions and how they would be broken down into different Lambda Feedback sections
in a Python-readable format. If `output_dir` is specified, the corresponding json/zip files are
produced.
"""
# The list of questions for Lambda Feedback as a Python API.
module = Module()
# Dynamically import the correct pandoc filter depending on the subject.
subject_module = importlib.import_module(f"tex2lambda.subjects.{subject.lower()}")
with open(question_file, "r", encoding="utf-8") as file:
text = file.read()
# Parse the Pandoc AST using the relevant panflute filter.
pf.run_filter(
subject_module.pandoc_filter,
doc=pf.convert_text(text, input_format="latex", standalone=True),
module=module,
tex_file=question_file,
parsing_answers=False,
)
# If separate answer TeX file provided, parse that as well.
if answer_file:
with open(answer_file, "r", encoding="utf-8") as file:
answer_text = file.read()
pf.run_filter(
subject_module.pandoc_filter,
doc=pf.convert_text(answer_text, input_format="latex", standalone=True),
module=module,
tex_file=answer_file,
parsing_answers=True,
)
# Read the Python API format and convert to JSON.
if output_dir is not None:
json_convert.main(module.questions, output_dir)
return module
@click.command(
epilog="See the docs at https://lambda-feedback.github.io/user-documentation/ for more details."
)
@click.argument( # Use resolve_path to get absolute path
"question_file", type=click.Path(exists=True, readable=True, resolve_path=True)
)
# Python files in the subjects directory
@click.argument(
"subject",
type=click.Choice(
[
i.name.capitalize()
for i in pkgutil.iter_modules(tex2lambda.subjects.__path__)
if i.name[0] != "_"
],
case_sensitive=False,
),
)
@click.option(
"--out",
"-o",
"output_dir",
default="./out",
show_default=True,
help="Directory to output json/zip files to.",
type=click.Path(resolve_path=True),
)
@click.option(
"--answers",
"-a",
"answer_file",
default=None,
help="File containing solutions for QUESTION_FILE.",
type=click.Path(resolve_path=True, exists=True, dir_okay=False),
)
def cli(
question_file: str, subject: str, output_dir: str, answer_file: Optional[str]
) -> None:
"""Takes in a QUESTION_FILE for a given SUBJECT and produces Lambda Feedback compatible json/zip files."""
# main() is made separate from click() so that it can be easily imported as part of a library.
runner(question_file, subject, output_dir, answer_file)
if __name__ == "__main__":
cli()