tlaplus-cli 0.6.0__tar.gz → 0.7.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- tlaplus_cli-0.7.0/PKG-INFO +51 -0
- tlaplus_cli-0.7.0/README.md +21 -0
- {tlaplus_cli-0.6.0 → tlaplus_cli-0.7.0}/pyproject.toml +2 -2
- tlaplus_cli-0.7.0/src/tlaplus_cli/cmd/modules/__init__.py +16 -0
- tlaplus_cli-0.7.0/src/tlaplus_cli/cmd/modules/add.py +138 -0
- tlaplus_cli-0.7.0/src/tlaplus_cli/cmd/modules/list.py +45 -0
- tlaplus_cli-0.7.0/src/tlaplus_cli/cmd/modules/path.py +68 -0
- tlaplus_cli-0.7.0/src/tlaplus_cli/cmd/modules/remove.py +20 -0
- {tlaplus_cli-0.6.0 → tlaplus_cli-0.7.0}/src/tlaplus_cli/cmd/tlc.py +103 -1
- {tlaplus_cli-0.6.0 → tlaplus_cli-0.7.0}/src/tlaplus_cli/java/classpath.py +27 -4
- tlaplus_cli-0.7.0/src/tlaplus_cli/tlc/compiler.py +12 -0
- tlaplus_cli-0.7.0/src/tlaplus_cli/tlc/components.py +287 -0
- {tlaplus_cli-0.6.0 → tlaplus_cli-0.7.0}/src/tlaplus_cli/tlc/formatter.py +19 -80
- {tlaplus_cli-0.6.0 → tlaplus_cli-0.7.0}/src/tlaplus_cli/tlc/runner.py +82 -6
- tlaplus_cli-0.7.0/src/tlaplus_cli.egg-info/PKG-INFO +51 -0
- {tlaplus_cli-0.6.0 → tlaplus_cli-0.7.0}/src/tlaplus_cli.egg-info/SOURCES.txt +5 -18
- tlaplus_cli-0.6.0/PKG-INFO +0 -296
- tlaplus_cli-0.6.0/README.md +0 -266
- tlaplus_cli-0.6.0/src/tlaplus_cli/cmd/modules/__init__.py +0 -5
- tlaplus_cli-0.6.0/src/tlaplus_cli/cmd/modules/build.py +0 -41
- tlaplus_cli-0.6.0/src/tlaplus_cli/cmd/modules/install.py +0 -75
- tlaplus_cli-0.6.0/src/tlaplus_cli/cmd/modules/lib.py +0 -46
- tlaplus_cli-0.6.0/src/tlaplus_cli/cmd/modules/path.py +0 -72
- tlaplus_cli-0.6.0/src/tlaplus_cli/tlc/compiler.py +0 -81
- tlaplus_cli-0.6.0/src/tlaplus_cli.egg-info/PKG-INFO +0 -296
- tlaplus_cli-0.6.0/tests/test_build_both.py +0 -33
- tlaplus_cli-0.6.0/tests/test_build_modules_path.py +0 -50
- tlaplus_cli-0.6.0/tests/test_cli.py +0 -39
- tlaplus_cli-0.6.0/tests/test_config.py +0 -59
- tlaplus_cli-0.6.0/tests/test_config_suite.py +0 -74
- tlaplus_cli-0.6.0/tests/test_modules_build_lib.py +0 -88
- tlaplus_cli-0.6.0/tests/test_modules_build_show_command.py +0 -27
- tlaplus_cli-0.6.0/tests/test_modules_install_community.py +0 -86
- tlaplus_cli-0.6.0/tests/test_modules_lib.py +0 -106
- tlaplus_cli-0.6.0/tests/test_modules_path.py +0 -101
- tlaplus_cli-0.6.0/tests/test_modules_path_resolved.py +0 -33
- tlaplus_cli-0.6.0/tests/test_settings.py +0 -38
- tlaplus_cli-0.6.0/tests/test_tlc_modules_path.py +0 -50
- tlaplus_cli-0.6.0/tests/test_tlc_show_command.py +0 -25
- {tlaplus_cli-0.6.0 → tlaplus_cli-0.7.0}/LICENSE +0 -0
- {tlaplus_cli-0.6.0 → tlaplus_cli-0.7.0}/MANIFEST.in +0 -0
- {tlaplus_cli-0.6.0 → tlaplus_cli-0.7.0}/setup.cfg +0 -0
- {tlaplus_cli-0.6.0 → tlaplus_cli-0.7.0}/src/tlaplus_cli/__init__.py +0 -0
- {tlaplus_cli-0.6.0 → tlaplus_cli-0.7.0}/src/tlaplus_cli/cache/__init__.py +0 -0
- {tlaplus_cli-0.6.0 → tlaplus_cli-0.7.0}/src/tlaplus_cli/cache/github.py +0 -0
- {tlaplus_cli-0.6.0 → tlaplus_cli-0.7.0}/src/tlaplus_cli/cli.py +0 -0
- {tlaplus_cli-0.6.0 → tlaplus_cli-0.7.0}/src/tlaplus_cli/cmd/__init__.py +0 -0
- {tlaplus_cli-0.6.0 → tlaplus_cli-0.7.0}/src/tlaplus_cli/cmd/check_java.py +0 -0
- {tlaplus_cli-0.6.0 → tlaplus_cli-0.7.0}/src/tlaplus_cli/cmd/config/__init__.py +0 -0
- {tlaplus_cli-0.6.0 → tlaplus_cli-0.7.0}/src/tlaplus_cli/cmd/config/edit.py +0 -0
- {tlaplus_cli-0.6.0 → tlaplus_cli-0.7.0}/src/tlaplus_cli/cmd/config/list.py +0 -0
- {tlaplus_cli-0.6.0 → tlaplus_cli-0.7.0}/src/tlaplus_cli/cmd/fetch_cache/__init__.py +0 -0
- {tlaplus_cli-0.6.0 → tlaplus_cli-0.7.0}/src/tlaplus_cli/cmd/fetch_cache/clear.py +0 -0
- {tlaplus_cli-0.6.0 → tlaplus_cli-0.7.0}/src/tlaplus_cli/cmd/tools/__init__.py +0 -0
- {tlaplus_cli-0.6.0 → tlaplus_cli-0.7.0}/src/tlaplus_cli/cmd/tools/dir.py +0 -0
- {tlaplus_cli-0.6.0 → tlaplus_cli-0.7.0}/src/tlaplus_cli/cmd/tools/install.py +0 -0
- {tlaplus_cli-0.6.0 → tlaplus_cli-0.7.0}/src/tlaplus_cli/cmd/tools/list.py +0 -0
- {tlaplus_cli-0.6.0 → tlaplus_cli-0.7.0}/src/tlaplus_cli/cmd/tools/meta/__init__.py +0 -0
- {tlaplus_cli-0.6.0 → tlaplus_cli-0.7.0}/src/tlaplus_cli/cmd/tools/meta/sync.py +0 -0
- {tlaplus_cli-0.6.0 → tlaplus_cli-0.7.0}/src/tlaplus_cli/cmd/tools/path.py +0 -0
- {tlaplus_cli-0.6.0 → tlaplus_cli-0.7.0}/src/tlaplus_cli/cmd/tools/pin.py +0 -0
- {tlaplus_cli-0.6.0 → tlaplus_cli-0.7.0}/src/tlaplus_cli/cmd/tools/uninstall.py +0 -0
- {tlaplus_cli-0.6.0 → tlaplus_cli-0.7.0}/src/tlaplus_cli/cmd/tools/upgrade.py +0 -0
- {tlaplus_cli-0.6.0 → tlaplus_cli-0.7.0}/src/tlaplus_cli/config/__init__.py +0 -0
- {tlaplus_cli-0.6.0 → tlaplus_cli-0.7.0}/src/tlaplus_cli/config/loader.py +0 -0
- {tlaplus_cli-0.6.0 → tlaplus_cli-0.7.0}/src/tlaplus_cli/config/schema.py +0 -0
- {tlaplus_cli-0.6.0 → tlaplus_cli-0.7.0}/src/tlaplus_cli/java/__init__.py +0 -0
- {tlaplus_cli-0.6.0 → tlaplus_cli-0.7.0}/src/tlaplus_cli/java/inspector.py +0 -0
- {tlaplus_cli-0.6.0 → tlaplus_cli-0.7.0}/src/tlaplus_cli/project/__init__.py +0 -0
- {tlaplus_cli-0.6.0 → tlaplus_cli-0.7.0}/src/tlaplus_cli/project/core.py +0 -0
- {tlaplus_cli-0.6.0 → tlaplus_cli-0.7.0}/src/tlaplus_cli/py.typed +0 -0
- {tlaplus_cli-0.6.0 → tlaplus_cli-0.7.0}/src/tlaplus_cli/resources/__init__.py +0 -0
- {tlaplus_cli-0.6.0 → tlaplus_cli-0.7.0}/src/tlaplus_cli/resources/default_config.yaml +0 -0
- {tlaplus_cli-0.6.0 → tlaplus_cli-0.7.0}/src/tlaplus_cli/tlc/__init__.py +0 -0
- {tlaplus_cli-0.6.0 → tlaplus_cli-0.7.0}/src/tlaplus_cli/tlc/codes.py +0 -0
- {tlaplus_cli-0.6.0 → tlaplus_cli-0.7.0}/src/tlaplus_cli/tlc/models.py +0 -0
- {tlaplus_cli-0.6.0 → tlaplus_cli-0.7.0}/src/tlaplus_cli/tlc/parser.py +0 -0
- {tlaplus_cli-0.6.0 → tlaplus_cli-0.7.0}/src/tlaplus_cli/tlc/sany.py +0 -0
- {tlaplus_cli-0.6.0 → tlaplus_cli-0.7.0}/src/tlaplus_cli/tlc/values.py +0 -0
- {tlaplus_cli-0.6.0 → tlaplus_cli-0.7.0}/src/tlaplus_cli/ui.py +0 -0
- {tlaplus_cli-0.6.0 → tlaplus_cli-0.7.0}/src/tlaplus_cli/versioning/__init__.py +0 -0
- {tlaplus_cli-0.6.0 → tlaplus_cli-0.7.0}/src/tlaplus_cli/versioning/api.py +0 -0
- {tlaplus_cli-0.6.0 → tlaplus_cli-0.7.0}/src/tlaplus_cli/versioning/downloader.py +0 -0
- {tlaplus_cli-0.6.0 → tlaplus_cli-0.7.0}/src/tlaplus_cli/versioning/metadata.py +0 -0
- {tlaplus_cli-0.6.0 → tlaplus_cli-0.7.0}/src/tlaplus_cli/versioning/paths.py +0 -0
- {tlaplus_cli-0.6.0 → tlaplus_cli-0.7.0}/src/tlaplus_cli/versioning/resolver.py +0 -0
- {tlaplus_cli-0.6.0 → tlaplus_cli-0.7.0}/src/tlaplus_cli/versioning/schema.py +0 -0
- {tlaplus_cli-0.6.0 → tlaplus_cli-0.7.0}/src/tlaplus_cli.egg-info/dependency_links.txt +0 -0
- {tlaplus_cli-0.6.0 → tlaplus_cli-0.7.0}/src/tlaplus_cli.egg-info/entry_points.txt +0 -0
- {tlaplus_cli-0.6.0 → tlaplus_cli-0.7.0}/src/tlaplus_cli.egg-info/requires.txt +0 -0
- {tlaplus_cli-0.6.0 → tlaplus_cli-0.7.0}/src/tlaplus_cli.egg-info/top_level.txt +0 -0
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: tlaplus-cli
|
|
3
|
+
Version: 0.7.0
|
|
4
|
+
Summary: TLA+ tools: download TLC, compile custom modules, run model checker
|
|
5
|
+
Author-email: Denis Nikolskiy <codeomatics@gmail.com>
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/nikolskiy/tlaplus-cli
|
|
8
|
+
Project-URL: Repository, https://github.com/nikolskiy/tlaplus-cli
|
|
9
|
+
Project-URL: Issues, https://github.com/nikolskiy/tlaplus-cli/issues
|
|
10
|
+
Project-URL: Changelog, https://github.com/nikolskiy/tlaplus-cli/blob/main/CHANGELOG.md
|
|
11
|
+
Classifier: Development Status :: 3 - Alpha
|
|
12
|
+
Classifier: Programming Language :: Python :: 3
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
16
|
+
Classifier: Operating System :: OS Independent
|
|
17
|
+
Classifier: Topic :: Software Development :: Build Tools
|
|
18
|
+
Classifier: Topic :: Utilities
|
|
19
|
+
Requires-Python: >=3.11
|
|
20
|
+
Description-Content-Type: text/markdown
|
|
21
|
+
License-File: LICENSE
|
|
22
|
+
Requires-Dist: typer<1.0.0,>=0.22.0
|
|
23
|
+
Requires-Dist: requests<3.0.0,>=2.31.0
|
|
24
|
+
Requires-Dist: pyyaml<7.0,>=6.0
|
|
25
|
+
Requires-Dist: platformdirs<5.0,>=4.0
|
|
26
|
+
Requires-Dist: pydantic<3.0.0,>=2.12.5
|
|
27
|
+
Requires-Dist: truststore<1.0.0,>=0.9.0
|
|
28
|
+
Requires-Dist: rich<14.0.0,>=13.7.0
|
|
29
|
+
Dynamic: license-file
|
|
30
|
+
|
|
31
|
+
# TLA+ CLI
|
|
32
|
+
|
|
33
|
+
Command-line tool for working with TLA+ specifications and the TLC model checker.
|
|
34
|
+
|
|
35
|
+
## Project goals
|
|
36
|
+
|
|
37
|
+
1. Simplify managing TLA+ tools such as TLC, community modules, custom modules, etc.
|
|
38
|
+
2. Run TLA tools in a single CLI interface.
|
|
39
|
+
3. Manage and display the tools outputs.
|
|
40
|
+
|
|
41
|
+
## Features
|
|
42
|
+
|
|
43
|
+
* [Managing TLA+ Tools](docs/managing-tlaplus-tools/managing-tla2tools.md): Verify Java requirements, manage/pin `tla2tools.jar` versions, and handle cache settings.
|
|
44
|
+
* [Running TLC](docs/tlc/tlc.md): Run the TLC model checker, inspect commands, and execute model checking with custom modules.
|
|
45
|
+
* [Custom Java Modules](docs/java-modules-for-tlc/README.md): Compile, register, and manage custom Java operator overrides for TLC using automated CLI commands.
|
|
46
|
+
|
|
47
|
+
## Installation
|
|
48
|
+
|
|
49
|
+
For detailed installation instructions, please refer to:
|
|
50
|
+
* [Installation via uv tool](docs/installation/uv-tool.md)
|
|
51
|
+
* [Nix Flakes Installation & Development](docs/installation/nix.md)
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# TLA+ CLI
|
|
2
|
+
|
|
3
|
+
Command-line tool for working with TLA+ specifications and the TLC model checker.
|
|
4
|
+
|
|
5
|
+
## Project goals
|
|
6
|
+
|
|
7
|
+
1. Simplify managing TLA+ tools such as TLC, community modules, custom modules, etc.
|
|
8
|
+
2. Run TLA tools in a single CLI interface.
|
|
9
|
+
3. Manage and display the tools outputs.
|
|
10
|
+
|
|
11
|
+
## Features
|
|
12
|
+
|
|
13
|
+
* [Managing TLA+ Tools](docs/managing-tlaplus-tools/managing-tla2tools.md): Verify Java requirements, manage/pin `tla2tools.jar` versions, and handle cache settings.
|
|
14
|
+
* [Running TLC](docs/tlc/tlc.md): Run the TLC model checker, inspect commands, and execute model checking with custom modules.
|
|
15
|
+
* [Custom Java Modules](docs/java-modules-for-tlc/README.md): Compile, register, and manage custom Java operator overrides for TLC using automated CLI commands.
|
|
16
|
+
|
|
17
|
+
## Installation
|
|
18
|
+
|
|
19
|
+
For detailed installation instructions, please refer to:
|
|
20
|
+
* [Installation via uv tool](docs/installation/uv-tool.md)
|
|
21
|
+
* [Nix Flakes Installation & Development](docs/installation/nix.md)
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "tlaplus-cli"
|
|
7
|
-
version = "0.
|
|
7
|
+
version = "0.7.0"
|
|
8
8
|
description = "TLA+ tools: download TLC, compile custom modules, run model checker"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.11"
|
|
@@ -68,7 +68,7 @@ dev = [
|
|
|
68
68
|
|
|
69
69
|
[tool.pytest.ini_options]
|
|
70
70
|
addopts = "--cov=tlaplus_cli --cov-report=term-missing"
|
|
71
|
-
testpaths = ["tests"]
|
|
71
|
+
testpaths = ["tests/unit"]
|
|
72
72
|
|
|
73
73
|
[tool.ruff]
|
|
74
74
|
line-length = 120
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import typer
|
|
2
|
+
|
|
3
|
+
app = typer.Typer(name="modules", help="Manage TLA+ Java modules.", no_args_is_help=True)
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
# Import functions for new commands
|
|
7
|
+
from .add import add_module # noqa: E402
|
|
8
|
+
from .list import list_modules # noqa: E402
|
|
9
|
+
from .path import show_modules_path # noqa: E402
|
|
10
|
+
from .remove import remove_module # noqa: E402
|
|
11
|
+
|
|
12
|
+
# Register new commands with the typer app
|
|
13
|
+
app.command(name="add")(add_module)
|
|
14
|
+
app.command(name="list")(list_modules)
|
|
15
|
+
app.command(name="remove")(remove_module)
|
|
16
|
+
app.command(name="path")(show_modules_path)
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
import datetime
|
|
2
|
+
import json
|
|
3
|
+
import os
|
|
4
|
+
import re
|
|
5
|
+
import shutil
|
|
6
|
+
import subprocess
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
|
|
9
|
+
import typer
|
|
10
|
+
|
|
11
|
+
from tlaplus_cli.config.loader import load_config
|
|
12
|
+
from tlaplus_cli.java.classpath import ClasspathResolver, ResolveMode
|
|
13
|
+
from tlaplus_cli.tlc.compiler import get_tlc_jar_path
|
|
14
|
+
from tlaplus_cli.versioning.paths import get_modules_dir
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def find_override_classes(java_files: list[Path]) -> list[str]:
|
|
18
|
+
"""Find all class names implementing ITLCOverrides in Java files."""
|
|
19
|
+
override_classes = []
|
|
20
|
+
package_pattern = re.compile(r"package\s+([a-zA-Z0-9_.]+)\s*;")
|
|
21
|
+
class_pattern = re.compile(
|
|
22
|
+
r"\bclass\s+([a-zA-Z0-9_]+)\b[^{]*\bimplements\b[^{]*\b(tlc2\.overrides\.)?ITLCOverrides\b"
|
|
23
|
+
)
|
|
24
|
+
|
|
25
|
+
for jf in java_files:
|
|
26
|
+
try:
|
|
27
|
+
content = jf.read_text(encoding="utf-8", errors="ignore")
|
|
28
|
+
except OSError:
|
|
29
|
+
continue
|
|
30
|
+
|
|
31
|
+
# Strip comments to prevent false matches
|
|
32
|
+
content_no_comments = re.sub(r"//.*", "", content)
|
|
33
|
+
content_no_comments = re.sub(r"/\*.*?\*/", "", content_no_comments, flags=re.DOTALL)
|
|
34
|
+
|
|
35
|
+
package_match = package_pattern.search(content_no_comments)
|
|
36
|
+
package_name = package_match.group(1) if package_match else ""
|
|
37
|
+
|
|
38
|
+
for match in class_pattern.finditer(content_no_comments):
|
|
39
|
+
class_name = match.group(1)
|
|
40
|
+
full_class_name = f"{package_name}.{class_name}" if package_name else class_name
|
|
41
|
+
override_classes.append(full_class_name)
|
|
42
|
+
|
|
43
|
+
return override_classes
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def add_module( # noqa: PLR0912, PLR0915
|
|
47
|
+
path: str = typer.Argument(..., help="Path to the module directory to add."),
|
|
48
|
+
) -> None:
|
|
49
|
+
"""Compile custom Java overrides and add/update the module in the cache."""
|
|
50
|
+
source_dir = Path(path).resolve()
|
|
51
|
+
if not source_dir.is_dir():
|
|
52
|
+
typer.echo(f"Error: Path does not exist or is not a directory: {source_dir}", err=True)
|
|
53
|
+
raise typer.Exit(1)
|
|
54
|
+
|
|
55
|
+
config = load_config()
|
|
56
|
+
module_name = source_dir.name
|
|
57
|
+
target_dir = get_modules_dir() / module_name
|
|
58
|
+
|
|
59
|
+
# Overwrite if exists
|
|
60
|
+
if target_dir.exists():
|
|
61
|
+
shutil.rmtree(target_dir)
|
|
62
|
+
target_dir.mkdir(parents=True, exist_ok=True)
|
|
63
|
+
|
|
64
|
+
# 1. Copy directories (lib, modules, tlc2, and test/tests)
|
|
65
|
+
def copy_subfolder(subname: str) -> None:
|
|
66
|
+
src = source_dir / subname
|
|
67
|
+
if src.is_dir():
|
|
68
|
+
shutil.copytree(src, target_dir / subname)
|
|
69
|
+
|
|
70
|
+
typer.echo("Copying subfolders (lib, modules, test, tlc2)...")
|
|
71
|
+
copy_subfolder("lib")
|
|
72
|
+
copy_subfolder("modules")
|
|
73
|
+
copy_subfolder("tlc2")
|
|
74
|
+
|
|
75
|
+
if (source_dir / "test").is_dir():
|
|
76
|
+
copy_subfolder("test")
|
|
77
|
+
elif (source_dir / "tests").is_dir():
|
|
78
|
+
copy_subfolder("tests")
|
|
79
|
+
|
|
80
|
+
# 2. Compile Java overrides in cached version if tlc2/overrides exists
|
|
81
|
+
java_files = []
|
|
82
|
+
for candidate in [target_dir / "modules" / "tlc2" / "overrides", target_dir / "tlc2" / "overrides"]:
|
|
83
|
+
if candidate.is_dir():
|
|
84
|
+
java_files.extend(list(candidate.rglob("*.java")))
|
|
85
|
+
|
|
86
|
+
if java_files:
|
|
87
|
+
# Build classpath including tool_jar and local libs from the cached folder
|
|
88
|
+
cached_lib = target_dir / "lib"
|
|
89
|
+
extra_libs = []
|
|
90
|
+
if cached_lib.is_dir():
|
|
91
|
+
extra_libs.extend([str(j.absolute()) for j in cached_lib.glob("*.jar")])
|
|
92
|
+
|
|
93
|
+
tool_jar = get_tlc_jar_path()
|
|
94
|
+
if not tool_jar.exists():
|
|
95
|
+
typer.echo("Error: tla2tools.jar not found. Run 'tla tools install' first.", err=True)
|
|
96
|
+
shutil.rmtree(target_dir, ignore_errors=True)
|
|
97
|
+
raise typer.Exit(1)
|
|
98
|
+
|
|
99
|
+
resolver = ClasspathResolver(config, project_root=None, tool_jar=tool_jar, extra_libs=extra_libs)
|
|
100
|
+
classpath = os.pathsep.join(resolver.resolve(ResolveMode.COMPILE))
|
|
101
|
+
|
|
102
|
+
classes_dir = target_dir / "classes"
|
|
103
|
+
classes_dir.mkdir(parents=True, exist_ok=True)
|
|
104
|
+
|
|
105
|
+
cmd = ["javac", "-cp", classpath, "-d", str(classes_dir), *[str(f) for f in java_files]]
|
|
106
|
+
typer.echo("Compiling Java files in cached tlc2/overrides...")
|
|
107
|
+
try:
|
|
108
|
+
subprocess.run(cmd, check=True, capture_output=True, text=True)
|
|
109
|
+
except FileNotFoundError as err:
|
|
110
|
+
typer.echo("Error: 'javac' not found. Ensure JDK is installed.", err=True)
|
|
111
|
+
shutil.rmtree(target_dir, ignore_errors=True)
|
|
112
|
+
raise typer.Exit(1) from err
|
|
113
|
+
except subprocess.CalledProcessError as err:
|
|
114
|
+
typer.echo("Compilation failed!", err=True)
|
|
115
|
+
typer.echo(err.stdout, err=True)
|
|
116
|
+
typer.echo(err.stderr, err=True)
|
|
117
|
+
shutil.rmtree(target_dir, ignore_errors=True)
|
|
118
|
+
raise typer.Exit(1) from err
|
|
119
|
+
|
|
120
|
+
# Write service file
|
|
121
|
+
meta_inf = classes_dir / "META-INF" / "services"
|
|
122
|
+
meta_inf.mkdir(parents=True, exist_ok=True)
|
|
123
|
+
service_file = meta_inf / "tlc2.overrides.ITLCOverrides"
|
|
124
|
+
|
|
125
|
+
override_classes = find_override_classes(java_files)
|
|
126
|
+
if not override_classes:
|
|
127
|
+
override_classes = [config.tlc.overrides_class]
|
|
128
|
+
|
|
129
|
+
with service_file.open("w") as f:
|
|
130
|
+
for cls in override_classes:
|
|
131
|
+
f.write(f"{cls}\n")
|
|
132
|
+
|
|
133
|
+
# 3. Create metadata.json
|
|
134
|
+
metadata = {"name": module_name, "built_at": datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")}
|
|
135
|
+
with (target_dir / "metadata.json").open("w") as f:
|
|
136
|
+
json.dump(metadata, f, indent=2)
|
|
137
|
+
|
|
138
|
+
typer.echo(f"Module '{module_name}' successfully added to cache.")
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import json
|
|
2
|
+
|
|
3
|
+
import typer
|
|
4
|
+
from rich.console import Console
|
|
5
|
+
from rich.table import Table
|
|
6
|
+
|
|
7
|
+
from tlaplus_cli.versioning.paths import get_modules_dir
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def list_modules() -> None:
|
|
11
|
+
"""List all custom TLA+ modules added to the cache."""
|
|
12
|
+
modules_dir = get_modules_dir()
|
|
13
|
+
|
|
14
|
+
if not modules_dir.is_dir():
|
|
15
|
+
typer.echo("No modules currently added.")
|
|
16
|
+
return
|
|
17
|
+
|
|
18
|
+
modules_data = []
|
|
19
|
+
for item in sorted(modules_dir.iterdir()):
|
|
20
|
+
if item.is_dir():
|
|
21
|
+
metadata_file = item / "metadata.json"
|
|
22
|
+
if metadata_file.exists():
|
|
23
|
+
try:
|
|
24
|
+
meta = json.loads(metadata_file.read_text())
|
|
25
|
+
name = meta.get("name", item.name)
|
|
26
|
+
built_at = meta.get("built_at", "Unknown")
|
|
27
|
+
modules_data.append((name, built_at))
|
|
28
|
+
except Exception:
|
|
29
|
+
modules_data.append((item.name, "Corrupt Metadata"))
|
|
30
|
+
else:
|
|
31
|
+
modules_data.append((item.name, "Unknown"))
|
|
32
|
+
|
|
33
|
+
if not modules_data:
|
|
34
|
+
typer.echo("No modules currently added.")
|
|
35
|
+
return
|
|
36
|
+
|
|
37
|
+
table = Table()
|
|
38
|
+
table.add_column("Module Name", style="cyan")
|
|
39
|
+
table.add_column("Last Built/Modified", style="magenta")
|
|
40
|
+
|
|
41
|
+
for name, built_at in modules_data:
|
|
42
|
+
table.add_row(name, built_at)
|
|
43
|
+
|
|
44
|
+
console = Console()
|
|
45
|
+
console.print(table)
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import os
|
|
2
|
+
from pathlib import Path
|
|
3
|
+
|
|
4
|
+
import typer
|
|
5
|
+
|
|
6
|
+
from tlaplus_cli.config.loader import load_config
|
|
7
|
+
from tlaplus_cli.java.classpath import ClasspathResolver, ResolveMode, SubdirType
|
|
8
|
+
from tlaplus_cli.tlc.compiler import get_tlc_jar_path
|
|
9
|
+
from tlaplus_cli.versioning.paths import get_modules_dir
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def show_modules_path() -> None: # noqa: PLR0912
|
|
13
|
+
"""Display the active paths that will be included when running TLC."""
|
|
14
|
+
config_obj = load_config()
|
|
15
|
+
tool_jar = get_tlc_jar_path()
|
|
16
|
+
|
|
17
|
+
resolver = ClasspathResolver(config_obj, project_root=None, tool_jar=tool_jar)
|
|
18
|
+
|
|
19
|
+
# 1. Source Paths (-DTLA-Library)
|
|
20
|
+
tla_library = resolver.get_tla_library_property()
|
|
21
|
+
typer.echo("Source Paths (-DTLA-Library):")
|
|
22
|
+
if tla_library:
|
|
23
|
+
for p in tla_library.split(os.pathsep):
|
|
24
|
+
typer.echo(f" - {p}")
|
|
25
|
+
else:
|
|
26
|
+
typer.echo(" None")
|
|
27
|
+
|
|
28
|
+
# 2. Classes Paths
|
|
29
|
+
classes_paths = []
|
|
30
|
+
project_classes = resolver.get_project_path(SubdirType.CLASSES)
|
|
31
|
+
if project_classes:
|
|
32
|
+
classes_paths.append(str(project_classes.absolute()))
|
|
33
|
+
|
|
34
|
+
managed_dir = get_modules_dir()
|
|
35
|
+
if managed_dir.is_dir():
|
|
36
|
+
for item in sorted(managed_dir.iterdir()):
|
|
37
|
+
if item.is_dir():
|
|
38
|
+
mod_classes = item / "classes"
|
|
39
|
+
if mod_classes.is_dir():
|
|
40
|
+
classes_paths.append(str(mod_classes.absolute()))
|
|
41
|
+
|
|
42
|
+
typer.echo("\nClasses Paths:")
|
|
43
|
+
if classes_paths:
|
|
44
|
+
for p in classes_paths:
|
|
45
|
+
typer.echo(f" - {p}")
|
|
46
|
+
else:
|
|
47
|
+
typer.echo(" None")
|
|
48
|
+
|
|
49
|
+
# 3. Library Paths (JARs)
|
|
50
|
+
libs = resolver.resolve(ResolveMode.RUNTIME)
|
|
51
|
+
tool_jar_abs = str(tool_jar.absolute()) if tool_jar.exists() else ""
|
|
52
|
+
|
|
53
|
+
# Filter out classes directories and tool_jar to show only external library JARs
|
|
54
|
+
only_libs = []
|
|
55
|
+
for p in libs:
|
|
56
|
+
if p == tool_jar_abs:
|
|
57
|
+
continue
|
|
58
|
+
p_path = Path(p)
|
|
59
|
+
if p_path.name == "classes" and p_path.is_dir():
|
|
60
|
+
continue
|
|
61
|
+
only_libs.append(p)
|
|
62
|
+
|
|
63
|
+
typer.echo("\nLibrary Paths (JARs):")
|
|
64
|
+
if only_libs:
|
|
65
|
+
for p in only_libs:
|
|
66
|
+
typer.echo(f" - {p}")
|
|
67
|
+
else:
|
|
68
|
+
typer.echo(" None")
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import shutil
|
|
2
|
+
|
|
3
|
+
import typer
|
|
4
|
+
|
|
5
|
+
from tlaplus_cli.versioning.paths import get_modules_dir
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def remove_module(module_name: str = typer.Argument(..., help="Name of the module to remove from cache.")) -> None:
|
|
9
|
+
"""Remove a custom module from the cache."""
|
|
10
|
+
target_dir = get_modules_dir() / module_name
|
|
11
|
+
if not target_dir.is_dir():
|
|
12
|
+
typer.echo(f"Error: Module '{module_name}' not found in cache.", err=True)
|
|
13
|
+
raise typer.Exit(1)
|
|
14
|
+
|
|
15
|
+
try:
|
|
16
|
+
shutil.rmtree(target_dir)
|
|
17
|
+
typer.echo(f"Module '{module_name}' successfully deleted from cache.")
|
|
18
|
+
except OSError as e:
|
|
19
|
+
typer.echo(f"Error: Failed to delete module: {e}", err=True)
|
|
20
|
+
raise typer.Exit(1) from e
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import sys
|
|
2
|
+
|
|
1
3
|
import typer
|
|
2
4
|
from rich.console import Console
|
|
3
5
|
from rich.live import Live
|
|
@@ -7,6 +9,7 @@ from tlaplus_cli.config.loader import load_config
|
|
|
7
9
|
from tlaplus_cli.java.classpath import ClasspathResolver
|
|
8
10
|
from tlaplus_cli.project import find_project_root
|
|
9
11
|
from tlaplus_cli.tlc.compiler import get_tlc_jar_path
|
|
12
|
+
from tlaplus_cli.tlc.components import StatsTableFormatter, StatusFooterRenderable
|
|
10
13
|
from tlaplus_cli.tlc.formatter import TlcFormatter
|
|
11
14
|
from tlaplus_cli.tlc.models import CheckState, ModelCheckResult
|
|
12
15
|
from tlaplus_cli.tlc.runner import (
|
|
@@ -78,6 +81,7 @@ def tlc( # noqa: PLR0912, PLR0915
|
|
|
78
81
|
help="Refresh interval for the output (seconds).",
|
|
79
82
|
),
|
|
80
83
|
coverage: bool = typer.Option(False, "--coverage", help="Enable code coverage visualization."),
|
|
84
|
+
raw: bool = typer.Option(False, "--raw", help="Run TLC in raw mode without structured tool mode formatting."),
|
|
81
85
|
) -> None:
|
|
82
86
|
"""Run TLC model checker on a TLA+ specification."""
|
|
83
87
|
# version argument is handled by version_callback (is_eager=True)
|
|
@@ -96,16 +100,80 @@ def tlc( # noqa: PLR0912, PLR0915
|
|
|
96
100
|
typer.echo(" ".join(cmd))
|
|
97
101
|
raise typer.Exit(0)
|
|
98
102
|
|
|
103
|
+
if raw:
|
|
104
|
+
exit_code = run_tlc(spec, coverage=coverage, raw=True)
|
|
105
|
+
raise typer.Exit(exit_code)
|
|
106
|
+
|
|
99
107
|
tlc_version = get_tlc_version() or "unknown"
|
|
100
108
|
formatter = TlcFormatter(tlc_version=tlc_version)
|
|
101
109
|
layout = formatter.create_layout()
|
|
102
110
|
|
|
111
|
+
is_tty = sys.stdout.isatty()
|
|
112
|
+
|
|
103
113
|
try:
|
|
104
114
|
final_result: list[ModelCheckResult] = []
|
|
115
|
+
printed_logs_count = 0
|
|
116
|
+
printed_stats_count = 0
|
|
117
|
+
stats_header_printed = False
|
|
118
|
+
stats_border_printed = False
|
|
119
|
+
|
|
120
|
+
if is_tty:
|
|
121
|
+
table_formatter = StatsTableFormatter()
|
|
122
|
+
footer_renderable = StatusFooterRenderable(ModelCheckResult())
|
|
123
|
+
console = Console()
|
|
124
|
+
|
|
125
|
+
with Live(footer_renderable, refresh_per_second=4, screen=False) as live:
|
|
126
|
+
|
|
127
|
+
def wrapped_callback(res: ModelCheckResult) -> None:
|
|
128
|
+
nonlocal printed_logs_count, printed_stats_count, stats_header_printed, stats_border_printed
|
|
129
|
+
|
|
130
|
+
# Keep tests happy by updating the formatter
|
|
131
|
+
formatter.update(layout, res)
|
|
132
|
+
|
|
133
|
+
if not final_result:
|
|
134
|
+
final_result.append(res)
|
|
135
|
+
else:
|
|
136
|
+
final_result[0] = res
|
|
137
|
+
|
|
138
|
+
# Update the live status footer dynamic data
|
|
139
|
+
footer_renderable.result = res
|
|
105
140
|
|
|
106
|
-
|
|
141
|
+
# Stream any new logs
|
|
142
|
+
if len(res.output_lines) > printed_logs_count:
|
|
143
|
+
new_logs = res.output_lines[printed_logs_count:]
|
|
144
|
+
for log in new_logs:
|
|
145
|
+
live.console.print(log.strip())
|
|
146
|
+
printed_logs_count = len(res.output_lines)
|
|
147
|
+
|
|
148
|
+
# Stream any new progress stats
|
|
149
|
+
if len(res.initial_states_stat) > printed_stats_count:
|
|
150
|
+
new_stats = res.initial_states_stat[printed_stats_count:]
|
|
151
|
+
for stat in new_stats:
|
|
152
|
+
if not stats_header_printed:
|
|
153
|
+
live.console.print("\nState Space Progress:\n")
|
|
154
|
+
live.console.print(table_formatter.get_header())
|
|
155
|
+
live.console.print(table_formatter.get_border())
|
|
156
|
+
stats_header_printed = True
|
|
157
|
+
|
|
158
|
+
row_str = table_formatter.format_row(stat)
|
|
159
|
+
live.console.print(row_str)
|
|
160
|
+
printed_stats_count = len(res.initial_states_stat)
|
|
161
|
+
|
|
162
|
+
# Print bottom border when execution changes state to non-running
|
|
163
|
+
if res.state != CheckState.Running and stats_header_printed and not stats_border_printed:
|
|
164
|
+
live.console.print(table_formatter.get_border())
|
|
165
|
+
stats_border_printed = True
|
|
166
|
+
|
|
167
|
+
exit_code = run_tlc(spec, callback=wrapped_callback, coverage=coverage)
|
|
168
|
+
else:
|
|
169
|
+
# Non-TTY mode: no Live block
|
|
170
|
+
table_formatter = StatsTableFormatter()
|
|
171
|
+
print(f"Starting TLC model checker (version {tlc_version}).")
|
|
107
172
|
|
|
108
173
|
def wrapped_callback(res: ModelCheckResult) -> None:
|
|
174
|
+
nonlocal printed_logs_count, printed_stats_count, stats_header_printed, stats_border_printed
|
|
175
|
+
|
|
176
|
+
# Keep tests happy by updating the formatter
|
|
109
177
|
formatter.update(layout, res)
|
|
110
178
|
|
|
111
179
|
if not final_result:
|
|
@@ -113,8 +181,42 @@ def tlc( # noqa: PLR0912, PLR0915
|
|
|
113
181
|
else:
|
|
114
182
|
final_result[0] = res
|
|
115
183
|
|
|
184
|
+
# Stream any new logs
|
|
185
|
+
if len(res.output_lines) > printed_logs_count:
|
|
186
|
+
new_logs = res.output_lines[printed_logs_count:]
|
|
187
|
+
for log in new_logs:
|
|
188
|
+
print(log.strip())
|
|
189
|
+
printed_logs_count = len(res.output_lines)
|
|
190
|
+
|
|
191
|
+
# Stream any new progress stats
|
|
192
|
+
if len(res.initial_states_stat) > printed_stats_count:
|
|
193
|
+
new_stats = res.initial_states_stat[printed_stats_count:]
|
|
194
|
+
for stat in new_stats:
|
|
195
|
+
if not stats_header_printed:
|
|
196
|
+
print("\nState Space Progress:\n")
|
|
197
|
+
print(table_formatter.get_header())
|
|
198
|
+
print(table_formatter.get_border())
|
|
199
|
+
stats_header_printed = True
|
|
200
|
+
|
|
201
|
+
row_str = table_formatter.format_row(stat)
|
|
202
|
+
print(row_str)
|
|
203
|
+
printed_stats_count = len(res.initial_states_stat)
|
|
204
|
+
|
|
205
|
+
# Print bottom border when execution changes state to non-running
|
|
206
|
+
if res.state != CheckState.Running and stats_header_printed and not stats_border_printed:
|
|
207
|
+
print(table_formatter.get_border())
|
|
208
|
+
stats_border_printed = True
|
|
209
|
+
|
|
116
210
|
exit_code = run_tlc(spec, callback=wrapped_callback, coverage=coverage)
|
|
117
211
|
|
|
212
|
+
# Fallback print of border if not printed yet
|
|
213
|
+
if stats_header_printed and not stats_border_printed:
|
|
214
|
+
if is_tty:
|
|
215
|
+
console.print(table_formatter.get_border())
|
|
216
|
+
else:
|
|
217
|
+
print(table_formatter.get_border())
|
|
218
|
+
stats_border_printed = True
|
|
219
|
+
|
|
118
220
|
if final_result:
|
|
119
221
|
res = final_result[0]
|
|
120
222
|
|
|
@@ -84,7 +84,7 @@ class ClasspathResolver:
|
|
|
84
84
|
|
|
85
85
|
return None
|
|
86
86
|
|
|
87
|
-
def _resolve_paths(self, cli_paths: list[str], subdir: SubdirType, config_path: str | None) -> list[str]:
|
|
87
|
+
def _resolve_paths(self, cli_paths: list[str], subdir: SubdirType, config_path: str | None) -> list[str]: # noqa: PLR0912
|
|
88
88
|
"""
|
|
89
89
|
Resolves a set of paths following the hierarchy.
|
|
90
90
|
Deduplicates while preserving order.
|
|
@@ -106,10 +106,19 @@ class ClasspathResolver:
|
|
|
106
106
|
# 4. Managed Modules (Global Cache)
|
|
107
107
|
managed_dir = get_modules_dir()
|
|
108
108
|
if managed_dir.is_dir():
|
|
109
|
-
# Add all subdirectories of managed_dir to raw_paths so they are expanded for JARs
|
|
110
|
-
for item in managed_dir.iterdir():
|
|
109
|
+
# Add all subdirectories of managed_dir to raw_paths so they are expanded for JARs/TLA files
|
|
110
|
+
for item in sorted(managed_dir.iterdir()):
|
|
111
111
|
if item.is_dir():
|
|
112
|
-
|
|
112
|
+
if subdir == SubdirType.LIB:
|
|
113
|
+
mod_lib = item / "lib"
|
|
114
|
+
if mod_lib.is_dir():
|
|
115
|
+
raw_paths.append(str(mod_lib.absolute()))
|
|
116
|
+
raw_paths.append(str(item.absolute())) # Legacy fallback
|
|
117
|
+
elif subdir == SubdirType.MODULES:
|
|
118
|
+
mod_modules = item / "modules"
|
|
119
|
+
if mod_modules.is_dir():
|
|
120
|
+
raw_paths.append(str(mod_modules.absolute()))
|
|
121
|
+
raw_paths.append(str(item.absolute())) # Legacy fallback
|
|
113
122
|
|
|
114
123
|
# Deduplicate while preserving order
|
|
115
124
|
seen = set()
|
|
@@ -141,10 +150,24 @@ class ClasspathResolver:
|
|
|
141
150
|
|
|
142
151
|
# In RUNTIME mode, custom classes shadow everything
|
|
143
152
|
if mode == ResolveMode.RUNTIME:
|
|
153
|
+
# Project-local classes
|
|
144
154
|
classes_dir = self.get_project_path(SubdirType.CLASSES)
|
|
145
155
|
if classes_dir:
|
|
146
156
|
classpath.append(str(classes_dir.absolute()))
|
|
147
157
|
|
|
158
|
+
# Module source directories containing .tla files
|
|
159
|
+
modules = self._resolve_paths(self.extra_modules, SubdirType.MODULES, self.config.module_path)
|
|
160
|
+
classpath.extend(modules)
|
|
161
|
+
|
|
162
|
+
# Cached module classes are included as dependencies in all modes (RUNTIME & COMPILE)
|
|
163
|
+
managed_dir = get_modules_dir()
|
|
164
|
+
if managed_dir.is_dir():
|
|
165
|
+
for item in sorted(managed_dir.iterdir()):
|
|
166
|
+
if item.is_dir():
|
|
167
|
+
mod_classes = item / "classes"
|
|
168
|
+
if mod_classes.is_dir():
|
|
169
|
+
classpath.append(str(mod_classes.absolute()))
|
|
170
|
+
|
|
148
171
|
# Tool JAR
|
|
149
172
|
if self.tool_jar:
|
|
150
173
|
classpath.append(str(self.tool_jar.absolute()))
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
from pathlib import Path
|
|
2
|
+
|
|
3
|
+
from tlaplus_cli.config.loader import cache_dir
|
|
4
|
+
from tlaplus_cli.versioning import get_pinned_version_dir
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def get_tlc_jar_path() -> Path:
|
|
8
|
+
"""Resolve the path to tla2tools.jar using the fallback chain: pinned -> legacy."""
|
|
9
|
+
pinned_dir = get_pinned_version_dir()
|
|
10
|
+
pinned_jar = pinned_dir / "tla2tools.jar" if pinned_dir else None
|
|
11
|
+
legacy = cache_dir() / "tla2tools.jar"
|
|
12
|
+
return pinned_jar if (pinned_jar and pinned_jar.exists()) else legacy
|