Skip to content

Commit 414196f

Browse files
committed
Add basic CLI
1 parent ab2f416 commit 414196f

File tree

3 files changed

+120
-0
lines changed

3 files changed

+120
-0
lines changed

foamlib/__main__.py

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
"""A command-line interface for the 'foamlib' package."""
2+
3+
from __future__ import annotations
4+
5+
import asyncio
6+
import sys
7+
from pathlib import Path
8+
9+
import typer
10+
11+
if sys.version_info >= (3, 9):
12+
from typing import Annotated
13+
else:
14+
from typing_extensions import Annotated
15+
16+
from . import AsyncFoamCase, AsyncSlurmFoamCase, __version__
17+
from ._util import async_to_sync
18+
19+
app = typer.Typer(help=__doc__)
20+
21+
22+
@app.command()
23+
@async_to_sync
24+
async def run(
25+
cases: Annotated[
26+
list[Path] | None,
27+
typer.Argument(help="Case directories", show_default="current directory"),
28+
] = None,
29+
slurm: Annotated[
30+
bool | None,
31+
typer.Option(
32+
help="Use Slurm for running cases.", show_default="use Slurm if available"
33+
),
34+
] = None,
35+
max_cpus: Annotated[
36+
int,
37+
typer.Option(
38+
help="Maximum number of concurrent processes (for non-Slurm runs).",
39+
),
40+
] = AsyncFoamCase.max_cpus,
41+
) -> None:
42+
"""Run one or more OpenFOAM cases."""
43+
if cases is None:
44+
cases = [Path.cwd()]
45+
46+
AsyncFoamCase.max_cpus = max_cpus
47+
48+
if slurm is None:
49+
await asyncio.gather(
50+
*(AsyncSlurmFoamCase(case).run(fallback=True) for case in cases)
51+
)
52+
elif slurm:
53+
await asyncio.gather(*(AsyncSlurmFoamCase(case).run() for case in cases))
54+
else:
55+
await asyncio.gather(*(AsyncFoamCase(case).run() for case in cases))
56+
57+
58+
@app.command()
59+
@async_to_sync
60+
async def clean(
61+
cases: Annotated[
62+
list[Path] | None,
63+
typer.Argument(
64+
help="Case directories", show_default="current working directory"
65+
),
66+
],
67+
) -> None:
68+
"""Clean one or more OpenFOAM cases."""
69+
if cases is None:
70+
cases = [Path.cwd()]
71+
72+
await asyncio.gather(*(AsyncFoamCase(case).clean() for case in cases))
73+
74+
75+
def _version_callback(*, show: bool) -> None:
76+
if show:
77+
typer.echo(f"foamlib {__version__}")
78+
raise typer.Exit
79+
80+
81+
@app.callback()
82+
def common( # noqa: D103
83+
*,
84+
version: Annotated[
85+
bool,
86+
typer.Option(
87+
"--version", help="Show version and exit.", callback=_version_callback
88+
),
89+
] = False,
90+
) -> None:
91+
pass
92+
93+
94+
if __name__ == "__main__":
95+
app()

foamlib/_util.py

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
from __future__ import annotations
2+
3+
import asyncio
4+
import sys
5+
from functools import wraps
6+
from typing import Any, TypeVar
7+
8+
if sys.version_info >= (3, 9):
9+
from collections.abc import Callable, Coroutine
10+
else:
11+
from typing import Callable, Coroutine
12+
13+
R = TypeVar("R")
14+
15+
16+
def async_to_sync(coro: Callable[..., Coroutine[Any, Any, R]]) -> Callable[..., R]:
17+
@wraps(coro)
18+
def wrapper(*args: Any, **kwargs: Any) -> R:
19+
return asyncio.run(coro(*args, **kwargs))
20+
21+
return wrapper

pyproject.toml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ dependencies = [
3333
"numpy>=1.25.0,<3; python_version>='3.10'",
3434
"numpy>=1,<3",
3535
"pyparsing>=3.1.2,<4",
36+
"typer-slim>=0.13,<0.16",
3637
"typing-extensions>=4,<5; python_version<'3.11'",
3738
"rich>=13,<15",
3839
]
@@ -66,6 +67,9 @@ Homepage = "https://github.com/gerlero/foamlib"
6667
Repository = "https://github.com/gerlero/foamlib"
6768
Documentation = "https://foamlib.readthedocs.io"
6869

70+
[project.scripts]
71+
foamlib = "foamlib.__main__:app"
72+
6973
[tool.hatch.version]
7074
path = "foamlib/__init__.py"
7175

0 commit comments

Comments
 (0)