Source code for in2lambda.main

"""The main input for in2lambda, 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 in2lambda.filters
from in2lambda.api.module import Module


[docs] def file_type(file: str) -> str: """Determines which pandoc file format to use for a given file. See https://github.com/jgm/pandoc/blob/bad922a69236e22b20d51c4ec0b90c5a6c038433/src/Text/Pandoc/Format.hs#L171 (or any newer commit) for pandoc's supported file extensions. Args: file: A file path with the file extension included. Returns: An option in `pandoc --list-input-formats` that matches the given file type Examples: >>> from in2lambda.main import file_type >>> file_type("example.tex") 'latex' >>> file_type("/some/random/path/demo.md") 'markdown' >>> file_type("no_extension") Traceback (most recent call last): RuntimeError: Unsupported file extension: .no_extension >>> file_type("demo.unknown_extension") Traceback (most recent call last): RuntimeError: Unsupported file extension: .unknown_extension """ match (extension := file.split(".")[-1].lower()): case "tex" | "latex" | "ltx": return "latex" case ( "md" | "rmd" | "markdown" | "mdown" | "mdwn" | "mkd" | "mkdn" | "text" | "txt" ): return "markdown" case "docx": return "docx" # Pandoc doesn't seem to support doc raise RuntimeError(f"Unsupported file extension: .{extension}")
[docs] def runner( question_file: str, chosen_filter: str, output_dir: Optional[str] = None, answer_file: Optional[str] = None, ) -> Module: r"""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. chosen_filter: The filter chosen to parse the TeX file. 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. Examples: >>> import os >>> from in2lambda.main import runner >>> # Retrieve an example TeX file and run the given filter. >>> runner(f"{os.path.dirname(in2lambda.__file__)}/filters/PartsSepSol/example.tex", "PartsSepSol") # doctest: +ELLIPSIS Module(questions=[Question(title='', parts=[Part(text=..., worked_solution=''), ...], images=[], main_text='This is a sample question\n\n'), ...]) >>> runner(f"{os.path.dirname(in2lambda.__file__)}/filters/PartsOneSol/example.tex", "PartsOneSol") # doctest: +ELLIPSIS Module(questions=[Question(title='', parts=[Part(text='This is part (a)\n\n', worked_solution=''), ...], images=[], main_text='Here is some preliminary question information that might be useful.'), ...) """ # The list of questions for Lambda Feedback as a Python API. module = Module() # Dynamically import the correct pandoc filter depending on the subject. filter_module = importlib.import_module(f"in2lambda.filters.{chosen_filter}.filter") 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( filter_module.pandoc_filter, doc=pf.convert_text( text, input_format=file_type(question_file), 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( filter_module.pandoc_filter, doc=pf.convert_text( answer_text, input_format=file_type(answer_file), 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: module.to_json(output_dir) return module
@click.command( no_args_is_help=True, epilog="See the docs at https://lambda-feedback.github.io/in2lambda/ 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( "chosen_filter", type=click.Choice( [ i.name for i in pkgutil.iter_modules(in2lambda.filters.__path__) if i.name != "markdown" ], 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, chosen_filter: 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, chosen_filter, output_dir, answer_file) if __name__ == "__main__": cli()