Skip to content

Commit 51defbf

Browse files
Freeze a source document into a draft (t22)
Freeze a source document into a draft
2 parents aab451b + 9ebaaf1 commit 51defbf

21 files changed

Lines changed: 988 additions & 68 deletions

File tree

‎.gitattributes‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
# The fixture is CRLF because that is the thing it is for; nothing may rewrite it.
2+
tests/fixtures/sources/crlf/source.md -text

‎CHANGELOG.md‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,4 +5,5 @@
55
- 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.
66
- `in2lambda FILE FILTER` exits with an error naming the command to run instead, rather than printing its usage and exiting successfully.
77
- 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.
8+
- `in2lambda source add FILE` freezes a document: it converts .docx and .tex to markdown beside the file, and writes a `draft.json` 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. `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.
89
- The Python API is unchanged: `in2lambda.main.runner` and everything under `in2lambda.api` take the same arguments and return the same objects.

‎in2lambda/main.py‎

Lines changed: 42 additions & 67 deletions
Original file line numberDiff line numberDiff line change
@@ -7,32 +7,24 @@
77
# sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '..')))
88

99
import importlib
10-
import importlib.util
1110
import shlex
12-
import shutil
13-
import subprocess
1411
from typing import Optional
1512

1613
import rich_click as click
1714

1815
import in2lambda.filters
16+
import in2lambda.source
1917
from in2lambda.api.set import Set
2018

21-
22-
class ConversionToolsMissing(RuntimeError):
23-
"""Document conversion was asked for without pandoc or panflute installed."""
24-
25-
26-
def _require_conversion_tools() -> None:
27-
missing = []
28-
if shutil.which("pandoc") is None:
29-
missing.append("pandoc (see https://pandoc.org/installing.html)")
30-
if importlib.util.find_spec("panflute") is None:
31-
missing.append("panflute (pip install 'in2lambda[convert]')")
32-
if missing:
33-
raise ConversionToolsMissing(
34-
f"Converting documents needs {' and '.join(missing)}."
35-
)
19+
# Both were defined here before there was an in2lambda.source, and are in other
20+
# people's scripts as in2lambda.main names.
21+
from in2lambda.source import (
22+
ConversionToolsMissing,
23+
SourceError,
24+
_pandoc,
25+
_require_conversion_tools,
26+
file_type,
27+
)
3628

3729

3830
def docx_to_md(docx_file: str) -> str:
@@ -44,53 +36,7 @@ def docx_to_md(docx_file: str) -> str:
4436
Returns:
4537
the contents of the .docx file in markdown formatting
4638
"""
47-
md_output = subprocess.check_output(["pandoc", docx_file, "-t", "markdown"])
48-
return md_output.decode("utf-8")
49-
50-
51-
def file_type(file: str) -> str:
52-
"""Determines which pandoc file format to use for a given file.
53-
54-
See https://github.com/jgm/pandoc/blob/bad922a69236e22b20d51c4ec0b90c5a6c038433/src/Text/Pandoc/Format.hs#L171
55-
(or any newer commit) for pandoc's supported file extensions.
56-
57-
Args:
58-
file: A file path with the file extension included.
59-
60-
Returns:
61-
An option in `pandoc --list-input-formats` that matches the given file type
62-
63-
Examples:
64-
>>> from in2lambda.main import file_type
65-
>>> file_type("example.tex")
66-
'latex'
67-
>>> file_type("/some/random/path/demo.md")
68-
'markdown'
69-
>>> file_type("no_extension")
70-
Traceback (most recent call last):
71-
RuntimeError: Unsupported file extension: .no_extension
72-
>>> file_type("demo.unknown_extension")
73-
Traceback (most recent call last):
74-
RuntimeError: Unsupported file extension: .unknown_extension
75-
"""
76-
match (extension := file.split(".")[-1].lower()):
77-
case "tex" | "latex" | "ltx":
78-
return "latex"
79-
case (
80-
"md"
81-
| "rmd"
82-
| "markdown"
83-
| "mdown"
84-
| "mdwn"
85-
| "mkd"
86-
| "mkdn"
87-
| "text"
88-
| "txt"
89-
):
90-
return "markdown"
91-
case "docx":
92-
return "docx" # Pandoc doesn't seem to support .doc, and panflute doesn't like .docx.
93-
raise RuntimeError(f"Unsupported file extension: .{extension}")
39+
return _pandoc(docx_file, "markdown").decode("utf-8")
9440

9541

9642
def runner(
@@ -154,9 +100,7 @@ def runner(
154100

155101
# If separate answer TeX file provided, parse that as well.
156102
if answer_file:
157-
158103
if file_type(answer_file) == "docx":
159-
160104
answer_text = docx_to_md(answer_file)
161105
answer_format = "markdown"
162106
else:
@@ -251,5 +195,36 @@ def convert(
251195
raise click.ClickException(str(error)) from None
252196

253197

198+
@cli.group("source")
199+
def source_group() -> None:
200+
"""Freezes a source document, so its text can be quoted by line range."""
201+
202+
203+
@source_group.command("add")
204+
@click.argument("file", type=click.Path(exists=True, dir_okay=False, resolve_path=True))
205+
@click.option(
206+
"--start-over",
207+
is_flag=True,
208+
help="Freeze FILE again, discarding the draft already there.",
209+
)
210+
def source_add(file: str, start_over: bool) -> None:
211+
"""Converts FILE to markdown and records its blocks in draft.json beside it."""
212+
try:
213+
draft = in2lambda.source.add(file, start_over)
214+
except SourceError as error:
215+
# Exit with what to do about it rather than a traceback.
216+
raise click.ClickException(str(error)) from None
217+
click.echo(f"Wrote {draft}")
218+
219+
220+
@source_group.command("show")
221+
def source_show() -> None:
222+
"""Prints the frozen markdown of the draft in this directory, numbered."""
223+
try:
224+
click.echo(in2lambda.source.show())
225+
except SourceError as error:
226+
raise click.ClickException(str(error)) from None
227+
228+
254229
if __name__ == "__main__":
255230
cli()

0 commit comments

Comments
 (0)