Skip to content

Commit 50b3a30

Browse files
Improved CLI parsing and added support for extra Typst arguments (#1)
* Formatting * Updated parsing and added arg for extra typst args * Formatting * Update README.md * Fixed program description * Fixed extra_args type * Bumped version to 0.1.0 Breaking changes to the CLI commands. Moving from `typst_pyimage compile/watch` to `typst_pyimage --compile/--watch`. * Moved back from flags to subcommands * Fixing formatting
1 parent 94cbd75 commit 50b3a30

File tree

4 files changed

+109
-24
lines changed

4 files changed

+109
-24
lines changed

README.md

+15-9
Original file line numberDiff line numberDiff line change
@@ -61,20 +61,24 @@ This requires that you're using Typst locally -- it won't work with the web app.
6161

6262
2. Use these functions.
6363

64-
a. `pyimage(string, ..arguments) -> content`. The positional string should be a Python program that creates a single matplotlib figure. Any named arguments are forwarded on to Typst's built-in `image` function. You can use it just like the normal `image` function, e.g. `#align(center, pyimage("..."))`.
64+
a. `pyimage(string, ..arguments) -> content`. The positional string should be a Python program that creates a single matplotlib figure. Any named arguments are forwarded on to Typst's built-in `image` function. You can use it just like the normal `image` function, e.g. `#align(center, pyimage("..."))`.
6565

66-
b. `pycontent(string)`. The positional string should be a Python program that produces a string on its final line. This string will be treated as Typst code.
66+
b. `pycontent(string)`. The positional string should be a Python program that produces a string on its final line. This string will be treated as Typst code.
6767

68-
c. `pyinit(string)`. The positional string should be a Python program. This will be evaluated before all `pyimage` or `pycontent` calls, e.g. to perform imports or other setup.
68+
c. `pyinit(string)`. The positional string should be a Python program. This will be evaluated before all `pyimage` or `pycontent` calls, e.g. to perform imports or other setup.
6969

7070
3. Compile or watch. Run either of the following two commands:
71-
```
72-
python -m typst_pyimage compile your_file.typ
73-
python -m typst_pyimage watch your_file.typ
74-
```
75-
This will extract and run all your Python code. In addition it will call either `typst compile your_file.typ` or `typst watch your_file.typ`.
7671

77-
The resulting images are saved in the `.typst_pyimage` folder.
72+
```
73+
python -m typst_pyimage compile your_file.typ
74+
python -m typst_pyimage watch your_file.typ
75+
```
76+
77+
This will extract and run all your Python code. In addition it will call either `typst compile your_file.typ` or `typst watch your_file.typ`.
78+
79+
The resulting images are saved in the `.typst_pyimage` folder.
80+
81+
For more information on the available arguments, run `python -m typst_pyimage -h`.
7882

7983
## Notes
8084

@@ -83,6 +87,7 @@ It's common to have an initial block of code that is in common to all `#pyimage(
8387
Each `#pyimage("...")` block is executed as a fresh module (i.e. as if each was a separate Python file), but with the same Python interpreter.
8488

8589
Overall, this is essentially equivalent to the following Python code:
90+
8691
```
8792
# main.py
8893
import pyinit
@@ -100,6 +105,7 @@ from pyinit import *
100105
from pyinit import *
101106
... # your second #pyimage("...") code
102107
```
108+
103109
This means that e.g. any global caches will be shared across all `#pyimage("...")` calls. (Useful when using a library like JAX, which has a JIT compilation cache.)
104110

105111
## Limitations

pyproject.toml

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[project]
22
name = "typst_pyimage"
3-
version = "0.0.3"
3+
version = "0.0.4"
44
description = "Typst extension, adding support for generating figures using inline Python code"
55
readme = "README.md"
66
requires-python = "~=3.8"

typst_pyimage/__main__.py

+81-10
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,85 @@
1-
import sys
1+
import argparse
2+
import os
23

34
from .run import compile as compile, watch as watch
45

56

6-
if len(sys.argv) != 3:
7-
raise RuntimeError("Usage is `python -m typst_pyimage <command> <file>`")
8-
_, command, filename = sys.argv
9-
if command == "watch":
10-
watch(filename)
11-
elif command == "compile":
12-
compile(filename)
13-
else:
14-
raise RuntimeError(f"Invalid command {command}")
7+
def parse_args(args):
8+
filepath = args.filepath
9+
10+
# Check if the file exists
11+
if not os.path.exists(filepath):
12+
raise FileNotFoundError(f"File '{filepath}' not found")
13+
14+
extra_args = args.extra_args.replace("~", os.path.expanduser("~")).split()
15+
16+
return filepath, extra_args
17+
18+
19+
def _compile_wrapper(args):
20+
try:
21+
filepath, extra_args = parse_args(args)
22+
except Exception as e:
23+
print(e)
24+
return
25+
26+
compile(filepath, extra_args)
27+
28+
29+
def _watch_wrapper(args):
30+
try:
31+
filepath, extra_args = parse_args(args)
32+
except Exception as e:
33+
print(e)
34+
return
35+
36+
watch(filepath, extra_args)
37+
38+
39+
program_description = (
40+
"Typst extension, adding support for generating"
41+
+ " figures using inline Python code."
42+
)
43+
44+
45+
# Create the parser
46+
parser = argparse.ArgumentParser(
47+
prog="typst_pyimage",
48+
description=program_description,
49+
)
50+
51+
# Create subparsers
52+
subparser = parser.add_subparsers(help="sub-command help")
53+
54+
# Create subparsers for the subcommands
55+
subparser_compile = subparser.add_parser(
56+
"compile", aliases=["c"], help="Compile the typst file"
57+
)
58+
subparser_watch = subparser.add_parser(
59+
"watch", aliases=["w"], help="Watch the typst file"
60+
)
61+
62+
# Set the subparsers to call the appropriate function
63+
subparser_compile.set_defaults(func=_compile_wrapper)
64+
subparser_watch.set_defaults(func=_watch_wrapper)
65+
66+
# Required positional argument
67+
parser.add_argument(
68+
"filepath",
69+
action="store",
70+
type=str,
71+
default=".",
72+
help="The path to the typst file to be compiled/watched",
73+
)
74+
75+
# Optional argument
76+
parser.add_argument(
77+
"-a",
78+
"--extra-args",
79+
type=str,
80+
help="Extra arguments to be passed to typst",
81+
)
82+
83+
# Parse the arguments
84+
args = parser.parse_args()
85+
args.func(args)

typst_pyimage/run.py

+12-4
Original file line numberDiff line numberDiff line change
@@ -154,7 +154,13 @@ def _initial(
154154
return filepath, dirpath, init_code, init_scope
155155

156156

157-
def watch(filename: Union[str, pathlib.Path], timeout_s: Optional[int] = None):
157+
def watch(
158+
filename: Union[str, pathlib.Path],
159+
extra_args: Optional[list[str]] = None,
160+
timeout_s: Optional[int] = None,
161+
):
162+
if extra_args is None:
163+
extra_args = []
158164
figcache = {}
159165
contentcache = {}
160166
filepath, dirpath, init_code, init_scope = _initial(
@@ -165,7 +171,7 @@ def watch(filename: Union[str, pathlib.Path], timeout_s: Optional[int] = None):
165171
file_time = last_time = _get_file_time(filepath)
166172
keep_running = True
167173
need_update = False
168-
process = subprocess.Popen(["typst", "watch", str(filepath)])
174+
process = subprocess.Popen(["typst", "watch"] + extra_args + [str(filepath)])
169175
try:
170176
while keep_running:
171177
if need_update:
@@ -184,6 +190,8 @@ def watch(filename: Union[str, pathlib.Path], timeout_s: Optional[int] = None):
184190
process.kill()
185191

186192

187-
def compile(filename: Union[str, pathlib.Path]):
193+
def compile(filename: Union[str, pathlib.Path], extra_args: Optional[list[str]] = None):
194+
if extra_args is None:
195+
extra_args = []
188196
filepath, _, _, _ = _initial(filename, figcache=None, contentcache=None)
189-
subprocess.run(["typst", "compile", str(filepath)])
197+
subprocess.run(["typst", "compile"] + extra_args + [str(filepath)])

0 commit comments

Comments
 (0)