tlaplus-cli 0.4.2__tar.gz → 0.6.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.
Files changed (85) hide show
  1. {tlaplus_cli-0.4.2/src/tlaplus_cli.egg-info → tlaplus_cli-0.6.0}/PKG-INFO +16 -3
  2. {tlaplus_cli-0.4.2 → tlaplus_cli-0.6.0}/README.md +14 -2
  3. {tlaplus_cli-0.4.2 → tlaplus_cli-0.6.0}/pyproject.toml +2 -1
  4. {tlaplus_cli-0.4.2 → tlaplus_cli-0.6.0}/src/tlaplus_cli/cli.py +1 -1
  5. {tlaplus_cli-0.4.2 → tlaplus_cli-0.6.0}/src/tlaplus_cli/cmd/modules/__init__.py +1 -1
  6. {tlaplus_cli-0.4.2 → tlaplus_cli-0.6.0}/src/tlaplus_cli/cmd/modules/build.py +12 -1
  7. tlaplus_cli-0.6.0/src/tlaplus_cli/cmd/modules/install.py +75 -0
  8. tlaplus_cli-0.6.0/src/tlaplus_cli/cmd/modules/path.py +72 -0
  9. tlaplus_cli-0.6.0/src/tlaplus_cli/cmd/tlc.py +159 -0
  10. {tlaplus_cli-0.4.2 → tlaplus_cli-0.6.0}/src/tlaplus_cli/cmd/tools/meta/sync.py +2 -1
  11. {tlaplus_cli-0.4.2 → tlaplus_cli-0.6.0}/src/tlaplus_cli/cmd/tools/uninstall.py +3 -2
  12. {tlaplus_cli-0.4.2 → tlaplus_cli-0.6.0}/src/tlaplus_cli/config/schema.py +2 -0
  13. tlaplus_cli-0.6.0/src/tlaplus_cli/java/classpath.py +174 -0
  14. {tlaplus_cli-0.4.2 → tlaplus_cli-0.6.0}/src/tlaplus_cli/resources/default_config.yaml +5 -0
  15. tlaplus_cli-0.6.0/src/tlaplus_cli/tlc/codes.py +314 -0
  16. {tlaplus_cli-0.4.2 → tlaplus_cli-0.6.0}/src/tlaplus_cli/tlc/compiler.py +18 -25
  17. tlaplus_cli-0.6.0/src/tlaplus_cli/tlc/formatter.py +409 -0
  18. tlaplus_cli-0.6.0/src/tlaplus_cli/tlc/models.py +132 -0
  19. tlaplus_cli-0.6.0/src/tlaplus_cli/tlc/parser.py +258 -0
  20. tlaplus_cli-0.6.0/src/tlaplus_cli/tlc/runner.py +151 -0
  21. tlaplus_cli-0.6.0/src/tlaplus_cli/tlc/sany.py +94 -0
  22. tlaplus_cli-0.6.0/src/tlaplus_cli/tlc/values.py +186 -0
  23. tlaplus_cli-0.6.0/src/tlaplus_cli/ui.py +21 -0
  24. {tlaplus_cli-0.4.2 → tlaplus_cli-0.6.0}/src/tlaplus_cli/versioning/api.py +9 -6
  25. {tlaplus_cli-0.4.2 → tlaplus_cli-0.6.0}/src/tlaplus_cli/versioning/downloader.py +15 -18
  26. {tlaplus_cli-0.4.2 → tlaplus_cli-0.6.0}/src/tlaplus_cli/versioning/paths.py +10 -4
  27. {tlaplus_cli-0.4.2 → tlaplus_cli-0.6.0/src/tlaplus_cli.egg-info}/PKG-INFO +16 -3
  28. {tlaplus_cli-0.4.2 → tlaplus_cli-0.6.0}/src/tlaplus_cli.egg-info/SOURCES.txt +13 -1
  29. {tlaplus_cli-0.4.2 → tlaplus_cli-0.6.0}/src/tlaplus_cli.egg-info/requires.txt +1 -0
  30. {tlaplus_cli-0.4.2 → tlaplus_cli-0.6.0}/tests/test_build_both.py +4 -3
  31. {tlaplus_cli-0.4.2 → tlaplus_cli-0.6.0}/tests/test_modules_build_lib.py +4 -4
  32. tlaplus_cli-0.6.0/tests/test_modules_build_show_command.py +27 -0
  33. tlaplus_cli-0.6.0/tests/test_modules_install_community.py +86 -0
  34. tlaplus_cli-0.6.0/tests/test_modules_path_resolved.py +33 -0
  35. {tlaplus_cli-0.4.2 → tlaplus_cli-0.6.0}/tests/test_tlc_modules_path.py +11 -4
  36. tlaplus_cli-0.6.0/tests/test_tlc_show_command.py +25 -0
  37. tlaplus_cli-0.4.2/src/tlaplus_cli/cmd/modules/path.py +0 -39
  38. tlaplus_cli-0.4.2/src/tlaplus_cli/cmd/tlc.py +0 -51
  39. tlaplus_cli-0.4.2/src/tlaplus_cli/tlc/runner.py +0 -102
  40. tlaplus_cli-0.4.2/src/tlaplus_cli/ui.py +0 -6
  41. {tlaplus_cli-0.4.2 → tlaplus_cli-0.6.0}/LICENSE +0 -0
  42. {tlaplus_cli-0.4.2 → tlaplus_cli-0.6.0}/MANIFEST.in +0 -0
  43. {tlaplus_cli-0.4.2 → tlaplus_cli-0.6.0}/setup.cfg +0 -0
  44. {tlaplus_cli-0.4.2 → tlaplus_cli-0.6.0}/src/tlaplus_cli/__init__.py +0 -0
  45. {tlaplus_cli-0.4.2 → tlaplus_cli-0.6.0}/src/tlaplus_cli/cache/__init__.py +0 -0
  46. {tlaplus_cli-0.4.2 → tlaplus_cli-0.6.0}/src/tlaplus_cli/cache/github.py +0 -0
  47. {tlaplus_cli-0.4.2 → tlaplus_cli-0.6.0}/src/tlaplus_cli/cmd/__init__.py +0 -0
  48. {tlaplus_cli-0.4.2 → tlaplus_cli-0.6.0}/src/tlaplus_cli/cmd/check_java.py +0 -0
  49. {tlaplus_cli-0.4.2 → tlaplus_cli-0.6.0}/src/tlaplus_cli/cmd/config/__init__.py +0 -0
  50. {tlaplus_cli-0.4.2 → tlaplus_cli-0.6.0}/src/tlaplus_cli/cmd/config/edit.py +0 -0
  51. {tlaplus_cli-0.4.2 → tlaplus_cli-0.6.0}/src/tlaplus_cli/cmd/config/list.py +0 -0
  52. {tlaplus_cli-0.4.2 → tlaplus_cli-0.6.0}/src/tlaplus_cli/cmd/fetch_cache/__init__.py +0 -0
  53. {tlaplus_cli-0.4.2 → tlaplus_cli-0.6.0}/src/tlaplus_cli/cmd/fetch_cache/clear.py +0 -0
  54. {tlaplus_cli-0.4.2 → tlaplus_cli-0.6.0}/src/tlaplus_cli/cmd/modules/lib.py +0 -0
  55. {tlaplus_cli-0.4.2 → tlaplus_cli-0.6.0}/src/tlaplus_cli/cmd/tools/__init__.py +0 -0
  56. {tlaplus_cli-0.4.2 → tlaplus_cli-0.6.0}/src/tlaplus_cli/cmd/tools/dir.py +0 -0
  57. {tlaplus_cli-0.4.2 → tlaplus_cli-0.6.0}/src/tlaplus_cli/cmd/tools/install.py +0 -0
  58. {tlaplus_cli-0.4.2 → tlaplus_cli-0.6.0}/src/tlaplus_cli/cmd/tools/list.py +0 -0
  59. {tlaplus_cli-0.4.2 → tlaplus_cli-0.6.0}/src/tlaplus_cli/cmd/tools/meta/__init__.py +0 -0
  60. {tlaplus_cli-0.4.2 → tlaplus_cli-0.6.0}/src/tlaplus_cli/cmd/tools/path.py +0 -0
  61. {tlaplus_cli-0.4.2 → tlaplus_cli-0.6.0}/src/tlaplus_cli/cmd/tools/pin.py +0 -0
  62. {tlaplus_cli-0.4.2 → tlaplus_cli-0.6.0}/src/tlaplus_cli/cmd/tools/upgrade.py +0 -0
  63. {tlaplus_cli-0.4.2 → tlaplus_cli-0.6.0}/src/tlaplus_cli/config/__init__.py +0 -0
  64. {tlaplus_cli-0.4.2 → tlaplus_cli-0.6.0}/src/tlaplus_cli/config/loader.py +0 -0
  65. {tlaplus_cli-0.4.2 → tlaplus_cli-0.6.0}/src/tlaplus_cli/java/__init__.py +0 -0
  66. {tlaplus_cli-0.4.2 → tlaplus_cli-0.6.0}/src/tlaplus_cli/java/inspector.py +0 -0
  67. {tlaplus_cli-0.4.2 → tlaplus_cli-0.6.0}/src/tlaplus_cli/project/__init__.py +0 -0
  68. {tlaplus_cli-0.4.2 → tlaplus_cli-0.6.0}/src/tlaplus_cli/project/core.py +0 -0
  69. {tlaplus_cli-0.4.2 → tlaplus_cli-0.6.0}/src/tlaplus_cli/py.typed +0 -0
  70. {tlaplus_cli-0.4.2 → tlaplus_cli-0.6.0}/src/tlaplus_cli/resources/__init__.py +0 -0
  71. {tlaplus_cli-0.4.2 → tlaplus_cli-0.6.0}/src/tlaplus_cli/tlc/__init__.py +0 -0
  72. {tlaplus_cli-0.4.2 → tlaplus_cli-0.6.0}/src/tlaplus_cli/versioning/__init__.py +0 -0
  73. {tlaplus_cli-0.4.2 → tlaplus_cli-0.6.0}/src/tlaplus_cli/versioning/metadata.py +0 -0
  74. {tlaplus_cli-0.4.2 → tlaplus_cli-0.6.0}/src/tlaplus_cli/versioning/resolver.py +0 -0
  75. {tlaplus_cli-0.4.2 → tlaplus_cli-0.6.0}/src/tlaplus_cli/versioning/schema.py +0 -0
  76. {tlaplus_cli-0.4.2 → tlaplus_cli-0.6.0}/src/tlaplus_cli.egg-info/dependency_links.txt +0 -0
  77. {tlaplus_cli-0.4.2 → tlaplus_cli-0.6.0}/src/tlaplus_cli.egg-info/entry_points.txt +0 -0
  78. {tlaplus_cli-0.4.2 → tlaplus_cli-0.6.0}/src/tlaplus_cli.egg-info/top_level.txt +0 -0
  79. {tlaplus_cli-0.4.2 → tlaplus_cli-0.6.0}/tests/test_build_modules_path.py +0 -0
  80. {tlaplus_cli-0.4.2 → tlaplus_cli-0.6.0}/tests/test_cli.py +0 -0
  81. {tlaplus_cli-0.4.2 → tlaplus_cli-0.6.0}/tests/test_config.py +0 -0
  82. {tlaplus_cli-0.4.2 → tlaplus_cli-0.6.0}/tests/test_config_suite.py +0 -0
  83. {tlaplus_cli-0.4.2 → tlaplus_cli-0.6.0}/tests/test_modules_lib.py +0 -0
  84. {tlaplus_cli-0.4.2 → tlaplus_cli-0.6.0}/tests/test_modules_path.py +0 -0
  85. {tlaplus_cli-0.4.2 → tlaplus_cli-0.6.0}/tests/test_settings.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: tlaplus-cli
3
- Version: 0.4.2
3
+ Version: 0.6.0
4
4
  Summary: TLA+ tools: download TLC, compile custom modules, run model checker
5
5
  Author-email: Denis Nikolskiy <codeomatics@gmail.com>
6
6
  License-Expression: MIT
@@ -25,6 +25,7 @@ Requires-Dist: pyyaml<7.0,>=6.0
25
25
  Requires-Dist: platformdirs<5.0,>=4.0
26
26
  Requires-Dist: pydantic<3.0.0,>=2.12.5
27
27
  Requires-Dist: truststore<1.0.0,>=0.9.0
28
+ Requires-Dist: rich<14.0.0,>=13.7.0
28
29
  Dynamic: license-file
29
30
 
30
31
  # TLA+ CLI
@@ -156,6 +157,12 @@ To check the currently pinned `tla2tools.jar` path and its TLC version:
156
157
  tla tlc --version
157
158
  ```
158
159
 
160
+ To inspect the exact Java command that will be executed without running it:
161
+
162
+ ```bash
163
+ tla tlc <spec_name> --show-command
164
+ ```
165
+
159
166
  ### Compile Custom Java Modules
160
167
 
161
168
  Java modules (overrides) are compiled using the pinned version of the toolset.
@@ -177,8 +184,9 @@ tla modules path /path/to/custom/modules
177
184
 
178
185
  View or reset the custom modules path:
179
186
  ```bash
180
- tla modules path # View current path
181
- tla modules path none # Reset to default
187
+ tla modules path # View current path
188
+ tla modules path none # Reset to default
189
+ tla modules path --resolved # Show fully resolved paths for current context
182
190
  ```
183
191
 
184
192
  Configure a persistent custom modules dependencies (lib) path:
@@ -200,6 +208,11 @@ Verbose output:
200
208
  tla modules build --verbose
201
209
  ```
202
210
 
211
+ Show compilation command:
212
+ ```bash
213
+ tla modules build --show-command
214
+ ```
215
+
203
216
  The build command:
204
217
  1. Resolves the project root (defaults to `workspace.root` from config).
205
218
  2. Includes `lib/*.jar` files from the project root in the `javac` classpath.
@@ -127,6 +127,12 @@ To check the currently pinned `tla2tools.jar` path and its TLC version:
127
127
  tla tlc --version
128
128
  ```
129
129
 
130
+ To inspect the exact Java command that will be executed without running it:
131
+
132
+ ```bash
133
+ tla tlc <spec_name> --show-command
134
+ ```
135
+
130
136
  ### Compile Custom Java Modules
131
137
 
132
138
  Java modules (overrides) are compiled using the pinned version of the toolset.
@@ -148,8 +154,9 @@ tla modules path /path/to/custom/modules
148
154
 
149
155
  View or reset the custom modules path:
150
156
  ```bash
151
- tla modules path # View current path
152
- tla modules path none # Reset to default
157
+ tla modules path # View current path
158
+ tla modules path none # Reset to default
159
+ tla modules path --resolved # Show fully resolved paths for current context
153
160
  ```
154
161
 
155
162
  Configure a persistent custom modules dependencies (lib) path:
@@ -171,6 +178,11 @@ Verbose output:
171
178
  tla modules build --verbose
172
179
  ```
173
180
 
181
+ Show compilation command:
182
+ ```bash
183
+ tla modules build --show-command
184
+ ```
185
+
174
186
  The build command:
175
187
  1. Resolves the project root (defaults to `workspace.root` from config).
176
188
  2. Includes `lib/*.jar` files from the project root in the `javac` classpath.
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "tlaplus-cli"
7
- version = "0.4.2"
7
+ version = "0.6.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"
@@ -15,6 +15,7 @@ dependencies = [
15
15
  "platformdirs>=4.0,<5.0",
16
16
  "pydantic>=2.12.5,<3.0.0",
17
17
  "truststore>=0.9.0,<1.0.0",
18
+ "rich>=13.7.0,<14.0.0",
18
19
  ]
19
20
  authors = [
20
21
  {name = "Denis Nikolskiy", email = "codeomatics@gmail.com"}
@@ -30,7 +30,7 @@ def version_callback(value: bool) -> None:
30
30
 
31
31
  @app.callback()
32
32
  def root(
33
- version: bool = typer.Option( # noqa: ARG001
33
+ _: bool = typer.Option(
34
34
  None,
35
35
  "--version",
36
36
  "-v",
@@ -2,4 +2,4 @@ import typer
2
2
 
3
3
  app = typer.Typer(name="modules", help="Manage TLA+ Java modules.", no_args_is_help=True)
4
4
 
5
- from . import build, lib, path # noqa: F401, E402
5
+ from . import build, install, lib, path # noqa: F401, E402
@@ -4,17 +4,28 @@ from pathlib import Path
4
4
  import typer
5
5
 
6
6
  from tlaplus_cli.cmd.modules import app
7
- from tlaplus_cli.tlc.compiler import compile_modules
7
+ from tlaplus_cli.tlc.compiler import build_compile_command, compile_modules
8
8
 
9
9
 
10
10
  @app.command(name="build")
11
11
  def build(
12
12
  path: str | None = typer.Argument(None, help="Project root directory (defaults to workspace root)."),
13
13
  verbose: bool = typer.Option(False, "--verbose", "-v", help="Show compilation output."),
14
+ show_command: bool = typer.Option(False, "--show-command", help="Print the command instead of executing it."),
14
15
  ) -> None:
15
16
  """Compile custom Java modules."""
16
17
  base_dir = Path(path).resolve() if path is not None else None
17
18
 
19
+ if show_command:
20
+ try:
21
+ cmd = build_compile_command(base_dir)
22
+ except FileNotFoundError as e:
23
+ typer.echo(f"Error: {e}", err=True)
24
+ raise typer.Exit(1) from None
25
+ if cmd:
26
+ typer.echo(" ".join(cmd))
27
+ return
28
+
18
29
  typer.echo("Compiling Java files ...")
19
30
  try:
20
31
  classes_dir = compile_modules(base_dir, verbose)
@@ -0,0 +1,75 @@
1
+ import requests
2
+ import typer
3
+
4
+ from tlaplus_cli import ui
5
+ from tlaplus_cli.cmd.modules import app
6
+ from tlaplus_cli.config.loader import load_config
7
+ from tlaplus_cli.versioning import (
8
+ download_version,
9
+ download_version_from_url,
10
+ fetch_remote_versions,
11
+ is_url,
12
+ )
13
+ from tlaplus_cli.versioning.paths import get_modules_dir
14
+
15
+
16
+ @app.command(name="install-community")
17
+ def install_community(
18
+ version: str = typer.Argument(
19
+ None, help="Version tag (e.g. 'v202412171801') or a direct URL to CommunityModules.jar."
20
+ ),
21
+ force: bool = typer.Option(False, "--force", "-f", help="Re-download if already installed."),
22
+ ) -> None:
23
+ """Download and install a specific version of CommunityModules."""
24
+ base_dir = get_modules_dir()
25
+ jar_name = "CommunityModules.jar"
26
+
27
+ if version and is_url(version):
28
+ try:
29
+ version_dir = download_version_from_url(version, base_dir=base_dir, jar_name=jar_name)
30
+ except (requests.RequestException, OSError, ValueError) as e:
31
+ ui.error(f"Failed to download: {e}")
32
+ raise typer.Exit(1) from e
33
+ else:
34
+ ui.success(f"Successfully installed from URL to {version_dir}")
35
+ return
36
+
37
+ config = load_config()
38
+ if not config.tla.community_modules:
39
+ ui.error("Community modules URLs not configured in settings.")
40
+ raise typer.Exit(1)
41
+
42
+ versions, status = fetch_remote_versions(
43
+ config.tla.community_modules.tags,
44
+ config.tla.community_modules.releases,
45
+ config.tla.community_modules.per_page,
46
+ asset_name=jar_name,
47
+ )
48
+
49
+ if not versions:
50
+ ui.error(f"Could not fetch remote versions (status: {status.value})")
51
+ raise typer.Exit(1)
52
+
53
+ target = None
54
+ if version:
55
+ target = next((v for v in versions if v.name == version), None)
56
+ if not target:
57
+ ui.error(f"Version {version} not found.")
58
+ raise typer.Exit(1)
59
+ else:
60
+ # Default to latest non-prerelease
61
+ target = next((v for v in versions if not v.prerelease), versions[0])
62
+ ui.info(f"No version specified, selecting latest stable release: {target.name}")
63
+
64
+ # Check if already installed
65
+ target_dir = base_dir / f"{target.name}-{target.short_sha}"
66
+ if target_dir.exists() and not force:
67
+ ui.info(f"Version {target.name} is already installed at {target_dir}")
68
+ return
69
+
70
+ try:
71
+ version_dir = download_version(target, force=force, base_dir=base_dir, jar_name=jar_name)
72
+ ui.success(f"Successfully installed {target.name} to {version_dir}")
73
+ except (requests.RequestException, OSError) as e:
74
+ ui.error(f"Failed to download: {e}")
75
+ raise typer.Exit(1) from e
@@ -0,0 +1,72 @@
1
+ from pathlib import Path
2
+
3
+ import typer
4
+
5
+ from tlaplus_cli.cmd.modules import app
6
+ from tlaplus_cli.config.loader import load_config, save_config, workspace_root
7
+ from tlaplus_cli.java.classpath import ClasspathResolver, ResolveMode, SubdirType
8
+ from tlaplus_cli.tlc.compiler import get_tlc_jar_path
9
+
10
+
11
+ @app.command(name="path")
12
+ def set_modules_path(
13
+ path: str | None = typer.Argument(None, help="Path to the custom Java modules directory, or 'none' to reset."),
14
+ resolved: bool = typer.Option(False, "--resolved", help="Show the actual resolved paths for the current context."),
15
+ ) -> None:
16
+ """Configure or view custom Java modules source path."""
17
+ config_obj = load_config()
18
+
19
+ if resolved:
20
+ resolver = ClasspathResolver(config_obj, project_root=workspace_root(), tool_jar=get_tlc_jar_path())
21
+
22
+ # Source path (modules)
23
+ tla_library = resolver.get_tla_library_property()
24
+ typer.echo(f"Source Path: {tla_library or 'None'}")
25
+
26
+ # Classes path
27
+ classes_dir = resolver.get_project_path(SubdirType.CLASSES)
28
+ typer.echo(f"Classes Path: {classes_dir or 'None'}")
29
+
30
+ # Library path (JARs)
31
+ libs = resolver.resolve(ResolveMode.RUNTIME)
32
+ # Filter out classes_dir and tool_jar to show only libs
33
+ tool_jar = get_tlc_jar_path()
34
+ only_libs = [p for p in libs if p != str(classes_dir) and p != str(tool_jar)]
35
+
36
+ lib_str = "None"
37
+ if only_libs:
38
+ lib_str = f"{len(only_libs)} JARs"
39
+
40
+ typer.echo(f"Library Path: {lib_str}")
41
+ if only_libs:
42
+ for lib in only_libs:
43
+ typer.echo(f" - {lib}")
44
+
45
+ # Tool Jar
46
+ typer.echo(f"Tool Jar: {tool_jar}")
47
+ return
48
+
49
+ if path is None:
50
+ if config_obj.module_path:
51
+ typer.echo(f"Current modules path: {config_obj.module_path}")
52
+ else:
53
+ default_path = workspace_root() / config_obj.workspace.modules_dir
54
+ typer.echo("Custom modules path is not set.")
55
+ typer.echo(f"Defaulting to: {default_path}")
56
+ return
57
+
58
+ if path.lower() == "none":
59
+ config_obj.module_path = None
60
+ save_config(config_obj)
61
+ typer.echo("Custom modules path reset to default.")
62
+ return
63
+
64
+ p = Path(path).resolve()
65
+
66
+ if not p.is_dir():
67
+ typer.echo(f"Error: Path does not exist or is not a directory: {p}", err=True)
68
+ raise typer.Exit(1)
69
+
70
+ config_obj.module_path = str(p)
71
+ save_config(config_obj)
72
+ typer.echo(f"Modules path updated to: {p}")
@@ -0,0 +1,159 @@
1
+ import typer
2
+ from rich.console import Console
3
+ from rich.live import Live
4
+
5
+ from tlaplus_cli import ui
6
+ from tlaplus_cli.config.loader import load_config
7
+ from tlaplus_cli.java.classpath import ClasspathResolver
8
+ from tlaplus_cli.project import find_project_root
9
+ from tlaplus_cli.tlc.compiler import get_tlc_jar_path
10
+ from tlaplus_cli.tlc.formatter import TlcFormatter
11
+ from tlaplus_cli.tlc.models import CheckState, ModelCheckResult
12
+ from tlaplus_cli.tlc.runner import (
13
+ build_tlc_command,
14
+ get_tlc_version,
15
+ resolve_spec_file,
16
+ run_tlc,
17
+ )
18
+
19
+
20
+ def version_callback(value: bool) -> None:
21
+ if value:
22
+ jar_path = get_tlc_jar_path()
23
+ typer.echo(f"tla2tools.jar path: {jar_path}")
24
+
25
+ if not jar_path.exists():
26
+ ui.error(f"tla2tools.jar not found at {jar_path}")
27
+ typer.echo("Run 'tla tools install' first.", err=True)
28
+ raise typer.Exit(1)
29
+
30
+ version_str = get_tlc_version()
31
+ if version_str:
32
+ typer.echo(version_str)
33
+
34
+ raise typer.Exit(0)
35
+
36
+
37
+ def _show_diagnostic_info(spec: str, spec_name: str) -> None:
38
+ """Show diagnostic info before running TLC."""
39
+ version_str = get_tlc_version() or "unknown"
40
+ typer.echo(f"Running TLC ({version_str}) on {spec_name} ...")
41
+
42
+ # Check if we have TLA-Library
43
+ try:
44
+ config = load_config()
45
+ spec_file, _ = resolve_spec_file(spec)
46
+ project_root = find_project_root(
47
+ spec_file,
48
+ modules_dir=config.workspace.modules_dir,
49
+ classes_dir=config.workspace.classes_dir,
50
+ )
51
+ jar_path = get_tlc_jar_path()
52
+ resolver = ClasspathResolver(config, project_root=project_root, tool_jar=jar_path)
53
+ tla_library = resolver.get_tla_library_property()
54
+ if tla_library:
55
+ ui.info(f"TLA-Library: {tla_library}")
56
+ except Exception:
57
+ pass # nosec
58
+
59
+
60
+ # PLR0912: This command handler orchestrates multiple output types (errors, warnings, results, progress);
61
+ # splitting it would fragment the CLI's primary user-facing logic.
62
+ # PLR0915: Too many statements.
63
+ def tlc( # noqa: PLR0912, PLR0915
64
+ spec: str = typer.Argument(help="Name of the TLA+ specification (without .tla extension)."),
65
+ # Typer requires the parameter in the signature to register the CLI option;
66
+ # since the logic is in the eager callback, the value is not used here.
67
+ version: bool | None = typer.Option( # noqa: ARG001
68
+ None,
69
+ "--version",
70
+ help="Print the path to tla2tools.jar and its version.",
71
+ callback=version_callback,
72
+ is_eager=True,
73
+ ),
74
+ show_command: bool = typer.Option(False, "--show-command", help="Print the command instead of executing it."),
75
+ _refresh_interval: float | None = typer.Option(
76
+ None,
77
+ "--refresh-interval",
78
+ help="Refresh interval for the output (seconds).",
79
+ ),
80
+ coverage: bool = typer.Option(False, "--coverage", help="Enable code coverage visualization."),
81
+ ) -> None:
82
+ """Run TLC model checker on a TLA+ specification."""
83
+ # version argument is handled by version_callback (is_eager=True)
84
+ try:
85
+ _, _spec_name = resolve_spec_file(spec)
86
+ except FileNotFoundError as e:
87
+ ui.error(str(e))
88
+ raise typer.Exit(1) from None
89
+
90
+ if show_command:
91
+ cmd = build_tlc_command(spec)
92
+ if coverage:
93
+ cmd.extend(["-coverage", "1"])
94
+ if not cmd:
95
+ raise typer.Exit(1) from None
96
+ typer.echo(" ".join(cmd))
97
+ raise typer.Exit(0)
98
+
99
+ tlc_version = get_tlc_version() or "unknown"
100
+ formatter = TlcFormatter(tlc_version=tlc_version)
101
+ layout = formatter.create_layout()
102
+
103
+ try:
104
+ final_result: list[ModelCheckResult] = []
105
+
106
+ with Live(layout, refresh_per_second=4, screen=False):
107
+
108
+ def wrapped_callback(res: ModelCheckResult) -> None:
109
+ formatter.update(layout, res)
110
+
111
+ if not final_result:
112
+ final_result.append(res)
113
+ else:
114
+ final_result[0] = res
115
+
116
+ exit_code = run_tlc(spec, callback=wrapped_callback, coverage=coverage)
117
+
118
+ if final_result:
119
+ res = final_result[0]
120
+
121
+ if res.sany_errors:
122
+ console = Console()
123
+ spec_file, _ = resolve_spec_file(spec)
124
+ for sany_err in res.sany_errors:
125
+ console.print(formatter.render_sany_error(sany_err, spec_file))
126
+
127
+ if res.state == CheckState.Success:
128
+ ui.success("Model checking completed successfully.")
129
+ elif res.state == CheckState.Error:
130
+ ui.error(f"Model checking failed with {len(res.errors)} error(s).")
131
+ for err in res.errors:
132
+ if err.error_trace:
133
+ console = Console()
134
+ console.print(formatter.render_error_trace(err.error_trace))
135
+ else:
136
+ for line in err.lines:
137
+ typer.echo(f" {line}", err=True)
138
+
139
+ if res.warnings:
140
+ ui.warn(f"Found {len(res.warnings)} warning(s).")
141
+ for warn_info in res.warnings:
142
+ for line in warn_info.lines:
143
+ typer.echo(f" {line}", err=True)
144
+
145
+ if res.coverage_stat:
146
+ console = Console()
147
+ console.print(formatter.render_coverage_table(res.coverage_stat))
148
+
149
+ # Show inline coverage for the main spec
150
+ spec_file, _ = resolve_spec_file(spec)
151
+ console.print(formatter.render_inline_coverage(res.coverage_stat, spec_file))
152
+
153
+ if res.duration:
154
+ ui.info(f"Duration: {res.duration}ms")
155
+ except FileNotFoundError as e:
156
+ ui.error(str(e))
157
+ raise typer.Exit(1) from None
158
+
159
+ raise typer.Exit(exit_code)
@@ -2,6 +2,7 @@ import typer
2
2
 
3
3
  from tlaplus_cli.cmd.tools.meta import app
4
4
  from tlaplus_cli.config.loader import load_config
5
+ from tlaplus_cli.ui import warn
5
6
  from tlaplus_cli.versioning import (
6
7
  fetch_remote_versions,
7
8
  list_local_versions,
@@ -28,6 +29,6 @@ def meta_sync() -> None:
28
29
  write_version_metadata(lv.path, target)
29
30
  typer.echo(f"Synced metadata for {lv.path.name}")
30
31
  else:
31
- typer.echo(f"⚠ Warning: Could not find remote data for {lv.name}", err=True)
32
+ warn(f"Could not find remote data for {lv.name}")
32
33
 
33
34
  typer.echo("Metadata sync complete.")
@@ -5,6 +5,7 @@ import typer
5
5
 
6
6
  from tlaplus_cli.cmd.tools import app
7
7
  from tlaplus_cli.config.loader import cache_dir
8
+ from tlaplus_cli.ui import warn
8
9
  from tlaplus_cli.versioning import (
9
10
  clear_pin,
10
11
  get_pinned_version_dir,
@@ -24,12 +25,12 @@ def _resolve_uninstall_targets(versions: list[str]) -> list[Path]:
24
25
  if legacy.exists():
25
26
  targets.append(legacy)
26
27
  else:
27
- typer.echo("⚠ Warning: Default (legacy) tla2tools.jar not found.", err=True)
28
+ warn("Default (legacy) tla2tools.jar not found.")
28
29
  continue
29
30
 
30
31
  matching = sorted([lv for lv in local_versions if lv.name == v], key=lambda x: x.path.name)
31
32
  if not matching:
32
- typer.echo(f"⚠ Warning: Version {v} not found locally.", err=True)
33
+ warn(f"Version {v} not found locally.")
33
34
  continue
34
35
 
35
36
  if len(matching) > 1:
@@ -13,6 +13,7 @@ class TlaUrls(BaseModel):
13
13
 
14
14
  class TlaConfig(BaseModel):
15
15
  urls: TlaUrls
16
+ community_modules: TlaUrls | None = None
16
17
 
17
18
 
18
19
  class WorkspaceConfig(BaseModel):
@@ -25,6 +26,7 @@ class WorkspaceConfig(BaseModel):
25
26
  class TlcConfig(BaseModel):
26
27
  java_class: str = "tlc2.TLC"
27
28
  overrides_class: str = "tlc2.overrides.TLCOverrides"
29
+ refresh_interval: float = 1.0
28
30
 
29
31
 
30
32
  class JavaConfig(BaseModel):