From 3529e55c3f178e4f83f974b289f7afc7e86f9bf8 Mon Sep 17 00:00:00 2001 From: "Peter B. Johnson" Date: Sun, 20 Sep 2026 15:49:27 +0100 Subject: [PATCH 1/2] implement: Stop writing a log file into the working directory (t38) --- .gitignore | 1 - CHANGELOG.md | 1 + in2lambda/katex_convert/katex_convert.py | 22 ++++--------------- tests/test_cli.py | 28 ++++++++++++++++++++++++ 4 files changed, 33 insertions(+), 19 deletions(-) diff --git a/.gitignore b/.gitignore index 28cb702..d745e3c 100644 --- a/.gitignore +++ b/.gitignore @@ -683,7 +683,6 @@ TSWLatexianTemp* .idea -log **/_autosummary *.pdf /tex diff --git a/CHANGELOG.md b/CHANGELOG.md index b2a7883..49f3fe2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,4 +12,5 @@ - `in2lambda spec run SPEC` runs a YAML file of selectors over the frozen source: it says which blocks are questions, parts and solutions, which to ignore, what to strip off the front of each one, and which of the four filters lays the solutions out. It fills in the draft's fields with the markdown of the lines each was taken from, records the spec's name and hash in the log so a replay runs the same file, and reports every block it made nothing of. Running an edited spec over a draft it has already filled in is refused, as freezing a document that has changed is: `in2lambda source add --start-over` begins the draft again. Reading a spec needs pyyaml, which the `convert` extra now installs alongside panflute. See [the spec page](https://lambda-feedback.github.io/in2lambda/spec.html) for the selectors and layouts. - `in2lambda validate` checks a draft over as a whole and writes what it finds into it as a `report`: source blocks in no field and not marked ignore, two fields taken from the same lines, gaps in the numbering of the questions or their parts, parts nothing answers, and fields holding nothing. Each finding names the field and the lines it is about, so it can be acted on without reading the draft. Finding something is not a failure and the command still exits 0; the report is replaced by the next run of the checks and dropped by the next command that changes the draft, since it describes the draft as it stood. - `in2lambda build` writes the draft in this directory out as a Lambda Feedback set: one question per `qN.text` field, holding the parts written for it and the worked solutions, with the images those fields refer to under `media/`, as `in2lambda convert` writes a set - a field naming an image that is not beside the draft is refused saying which file is missing, since the checks read the draft and not the folder it is in, and a question's own solution written beside a solution for every part it has becomes a part of its own holding just that solution, as `convert` pairs them up. It is refused unless `in2lambda validate` has been run since the draft last changed - every command that changes one drops its report - and found nothing, and the refusal prints what the report says so it can be acted on without opening the draft. `in2lambda render` writes each question as a PDF instead, compiled as Lambda Feedback's own PDF generator compiles it, which needs pandoc and xelatex; it is gated on nothing, since looking at a draft is how what the checks found gets fixed. Both take `-o/--out`, as `convert` does. +- Importing `in2lambda.katex_convert` no longer writes a file called `log` into the working directory. What it has to say about a converted expression goes to the `in2lambda.katex_convert` logger, which is silent unless the application configures logging. - The Python API is unchanged: `in2lambda.main.runner` and everything under `in2lambda.api` take the same arguments and return the same objects. diff --git a/in2lambda/katex_convert/katex_convert.py b/in2lambda/katex_convert/katex_convert.py index da5ea4a..543f053 100644 --- a/in2lambda/katex_convert/katex_convert.py +++ b/in2lambda/katex_convert/katex_convert.py @@ -8,22 +8,10 @@ import re from pathlib import Path -# Create a logger object with a name and a level -logger = logging.getLogger("log") -logger.setLevel(logging.INFO) - -# Create a file handler to write the messages to a file -file_handler = logging.FileHandler("log", mode="w") # Clears log with every run -file_handler.setLevel(logging.INFO) - -# Create a formatter to format the messages -formatter = logging.Formatter("%(message)s") - -# Add the formatter to the file handler -file_handler.setFormatter(formatter) - -# Add the file handler to the logger -logger.addHandler(file_handler) +# No handler: one attached here wrote a file called `log` into whatever directory the +# importing process happened to be run from. Where these messages go is the +# application's to decide, by configuring the `in2lambda` logger or this child of it. +logger = logging.getLogger("in2lambda.katex_convert") def latex_to_katex(latex_string: str) -> str: @@ -123,8 +111,6 @@ def replace_functions(latex_string: str) -> str: Returns: The same LaTeX string with some commands replaced where necessary. """ - logger.info("") - # replace the incompatible functions with their KaTeX equivalents using re.sub for old, new in unsupported_commands().items(): if new is None: # Deleted rather than replaced; see delete_functions. diff --git a/tests/test_cli.py b/tests/test_cli.py index fab0b43..35f3ea5 100644 --- a/tests/test_cli.py +++ b/tests/test_cli.py @@ -75,6 +75,34 @@ def test_old_form_fails_when_run_as_the_installed_command( assert not (tmp_path / "out").exists() +def test_convert_leaves_only_what_it_writes(filters_dir: str, tmp_path) -> None: + """Converting in an empty directory leaves the input and the output and nothing else. + + In a subprocess because importing the package is what used to leave a file behind: + within pytest it happens once, before any test can chdir somewhere of its own. + """ + shutil.copy(os.path.join(filters_dir, "PartsSepSol", "example.tex"), tmp_path) + + result = subprocess.run( + [ + sys.executable, + "-c", + "from in2lambda.main import cli; cli()", + "convert", + "example.tex", + "PartsSepSol", + "-o", + "out", + ], + capture_output=True, + text=True, + cwd=tmp_path, + ) + + assert result.returncode == 0, result.stdout + result.stderr + assert {path.name for path in tmp_path.iterdir()} == {"example.tex", "out"} + + def test_completing_the_old_form_offers_the_subcommand() -> None: """Completion resolves half-typed command lines, so the guard must not fire there. From 16c389e385fecd9a25aef06b95ce88fb042310d1 Mon Sep 17 00:00:00 2001 From: "Peter B. Johnson" Date: Sun, 20 Sep 2026 15:52:34 +0100 Subject: [PATCH 2/2] verify: Stop writing a log file into the working directory (t38) --- log | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 log diff --git a/log b/log new file mode 100644 index 0000000..e69de29