Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 3 additions & 4 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
# Changelog

## 2.0.0
## 1.1.0

- Converting a document is now `in2lambda convert FILE FILTER`, with the same options as before (`-o/--out`, `-a/--answers`). Scripts and Docker invocations that run `in2lambda FILE FILTER` need the extra word.
- `in2lambda FILE FILTER` exits with an error naming the command to run instead, rather than printing its usage and exiting successfully.
- beartype is now `^0.22`. At 0.20.0 and below its import hook leaves `cli` a plain function rather than a group, so the new command line either fails to import or runs `convert` whatever the arguments; 0.20.1 is the first version that works.
- Converting a document is now `in2lambda convert FILE FILTER`, with the same options as before (`-o/--out`, `-a/--answers`). `in2lambda FILE FILTER` still converts the file, printing one line to stderr naming the `in2lambda convert` command to run instead, so scripts and Docker invocations written before this keep working. A first argument that is neither a command nor a file is refused with that same line, rather than printing the usage and exiting successfully.
- beartype is now `^0.22`. At 0.20.0 and below its import hook leaves `cli` a plain function rather than a group, so the command line either fails to import or runs `convert` whatever the arguments; 0.20.1 is the first version that works.
- `in2lambda source add FILE` freezes a document: it converts .docx and .tex to markdown beside the file, and writes a `FILE.draft.json` beside it, holding the markdown's hash and every block in it with the lines it spans, so that another tool can quote the source by line range. The draft is named after the source, so a folder holding a term's worth of sheets holds a draft for each. `in2lambda source show` prints that markdown numbered with the block ids. Freezing a file that has changed since is refused unless `--start-over` says to discard the draft, and so is showing one, since its block ids would name lines they are not the ids of. Both need pandoc and the `convert` extra, as `convert` does.
- A draft now holds a `log` of every command that changed it and a `fields` map of what those commands wrote, each field recording which layer wrote it (1 a spec, 2 a predicate, 3 a line range, 4 a literal), the source ranges it was copied from, whether it has been edited and by whom. `in2lambda draft mark ignore BLOCK` is the first such command, and `in2lambda draft replay` rebuilds the draft from the frozen markdown and the log, refusing unless what it builds is the draft that is there, byte for byte. A draft written before this has no `log` in it and is refused as one nothing here wrote; `in2lambda source add --start-over` freezes the document again.
- A draft is filled in by `in2lambda draft question add`, `in2lambda draft part add QUESTION` and `in2lambda draft question solution QUESTION`. Each takes `--text` to copy the wording out of the frozen source, as a block id such as `b3` or as lines such as `s10:14`, or `--literal TEXT` where the source does not say it in a form the field can take, which records the field as edited and written by layer 4 rather than 3. Question and part numbers are worked out from the fields already written rather than given, so a replay arrives at the same ids. `in2lambda draft split block BLOCK AT` cuts a block the parser made one of two things into `b3a` and `b3b`, so that each half can be quoted on its own. A command writing a field that is already written, or quoting lines another field was taken from, is refused: the first naming the field, the second naming both.
Expand Down
11 changes: 9 additions & 2 deletions in2lambda/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@

import getpass
import importlib
import os
import shlex
import warnings
from collections.abc import Callable # Rather than typing's, which beartype warns on.
Expand Down Expand Up @@ -178,17 +179,23 @@ def runner(


class _Cli(click.RichGroup):
"""The in2lambda group, which says what to run when given the pre-2.0 command line."""
"""The in2lambda group, which says what to run when given the old command line."""

def resolve_command(self, ctx, args): # type: ignore[no-untyped-def]
"""Fail with the new command line rather than click's handling of an unknown name.
"""Run ``convert`` for the old form, and name it for a first argument that is neither.

Click resolves a first argument starting with ``/`` or ``.`` by printing the
group's help and exiting successfully, so `in2lambda /path/to/questions.tex
PartsSepSol` would look like it had worked while converting nothing.
"""
# Shell completion resolves partial command lines, and must not raise.
if not ctx.resilient_parsing and self.get_command(ctx, args[0]) is None:
if os.path.isfile(args[0]):
click.echo(
f"in2lambda FILE FILTER is the old form. Run: in2lambda convert {shlex.join(args)}",
err=True,
)
return super().resolve_command(ctx, ["convert", *args])
raise click.UsageError(
f"in2lambda no longer takes a file directly. Run: in2lambda convert {shlex.join(args)}"
)
Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[tool.poetry]
name = "in2lambda"
version = "2.0.0"
version = "1.1.0"
description = "Converts content ready for import into Lambda Feedback"
authors = []
license = "MIT"
Expand Down
34 changes: 24 additions & 10 deletions tests/test_cli.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
"""What the command line does with the current and the pre-2.0 form."""
"""What the command line does with the current and the old form."""

import os
import shutil
Expand All @@ -25,10 +25,10 @@ def test_convert_writes_the_set(filters_dir: str, tmp_path) -> None:


@pytest.mark.parametrize("path", ["example.tex", "./example.tex", "ABSOLUTE"])
def test_old_form_fails_and_names_convert(
def test_old_form_converts_with_a_deprecation_line(
path: str, filters_dir: str, monkeypatch, tmp_path
) -> None:
"""The pre-2.0 form errors out whatever the file path looks like.
"""The old form converts whatever the file path looks like, saying so on stderr.

A path starting with ``/`` or ``.`` used to make click print the help and exit 0,
so every script passing a full path appeared to succeed without converting anything.
Expand All @@ -42,12 +42,12 @@ def test_old_form_fails_and_names_convert(

result = CliRunner().invoke(cli, [path, "PartsSepSol"])

assert result.exit_code != 0
assert "in2lambda convert" in result.output
assert not (tmp_path / "out").exists()
assert result.exit_code == 0, result.output
assert "in2lambda FILE FILTER is the old form" in result.stderr
assert (tmp_path / "out" / "set.zip").exists()


def test_old_form_fails_when_run_as_the_installed_command(
def test_old_form_works_when_run_as_the_installed_command(
filters_dir: str, tmp_path
) -> None:
"""The same holds for ``cli()``, which is what the installed command runs.
Expand All @@ -70,9 +70,23 @@ def test_old_form_fails_when_run_as_the_installed_command(
env={**os.environ, "COLUMNS": "200"},
)

assert result.returncode != 0
assert "in2lambda convert" in result.stdout + result.stderr
assert not (tmp_path / "out").exists()
assert result.returncode == 0, result.stdout + result.stderr
assert "in2lambda FILE FILTER is the old form" in result.stderr
assert "old form" not in result.stdout
assert (tmp_path / "out" / "set.zip").exists()


def test_first_argument_that_is_neither_still_names_convert(
monkeypatch, tmp_path
) -> None:
"""A first argument that is neither a subcommand nor a file is refused."""
monkeypatch.setenv("COLUMNS", "200") # So the message is not wrapped mid-sentence.
monkeypatch.chdir(tmp_path)

result = CliRunner().invoke(cli, ["missing.tex", "PartsSepSol"])

assert result.exit_code != 0
assert "in2lambda convert" in result.output


def test_convert_leaves_only_what_it_writes(filters_dir: str, tmp_path) -> None:
Expand Down
Loading