tlaplus-cli 0.3.5__tar.gz → 0.4.1__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 (75) hide show
  1. {tlaplus_cli-0.3.5/src/tlaplus_cli.egg-info → tlaplus_cli-0.4.1}/PKG-INFO +29 -3
  2. {tlaplus_cli-0.3.5 → tlaplus_cli-0.4.1}/README.md +28 -2
  3. {tlaplus_cli-0.3.5 → tlaplus_cli-0.4.1}/pyproject.toml +7 -2
  4. tlaplus_cli-0.4.1/src/tlaplus_cli/cache/github.py +28 -0
  5. tlaplus_cli-0.4.1/src/tlaplus_cli/cli.py +60 -0
  6. tlaplus_cli-0.4.1/src/tlaplus_cli/cmd/__init__.py +0 -0
  7. tlaplus_cli-0.4.1/src/tlaplus_cli/cmd/check_java.py +19 -0
  8. tlaplus_cli-0.4.1/src/tlaplus_cli/cmd/config/__init__.py +6 -0
  9. tlaplus_cli-0.4.1/src/tlaplus_cli/cmd/config/edit.py +34 -0
  10. tlaplus_cli-0.4.1/src/tlaplus_cli/cmd/config/list.py +16 -0
  11. tlaplus_cli-0.4.1/src/tlaplus_cli/cmd/fetch_cache/__init__.py +5 -0
  12. tlaplus_cli-0.4.1/src/tlaplus_cli/cmd/fetch_cache/clear.py +11 -0
  13. tlaplus_cli-0.4.1/src/tlaplus_cli/cmd/modules/__init__.py +5 -0
  14. tlaplus_cli-0.4.1/src/tlaplus_cli/cmd/modules/build.py +30 -0
  15. tlaplus_cli-0.4.1/src/tlaplus_cli/cmd/modules/lib.py +46 -0
  16. tlaplus_cli-0.4.1/src/tlaplus_cli/cmd/modules/path.py +39 -0
  17. tlaplus_cli-0.4.1/src/tlaplus_cli/cmd/tlc.py +51 -0
  18. tlaplus_cli-0.4.1/src/tlaplus_cli/cmd/tools/__init__.py +8 -0
  19. tlaplus_cli-0.4.1/src/tlaplus_cli/cmd/tools/dir.py +16 -0
  20. tlaplus_cli-0.4.1/src/tlaplus_cli/cmd/tools/install.py +79 -0
  21. tlaplus_cli-0.4.1/src/tlaplus_cli/cmd/tools/list.py +80 -0
  22. tlaplus_cli-0.4.1/src/tlaplus_cli/cmd/tools/meta/__init__.py +5 -0
  23. tlaplus_cli-0.4.1/src/tlaplus_cli/cmd/tools/meta/sync.py +33 -0
  24. tlaplus_cli-0.4.1/src/tlaplus_cli/cmd/tools/path.py +33 -0
  25. tlaplus_cli-0.4.1/src/tlaplus_cli/cmd/tools/pin.py +31 -0
  26. tlaplus_cli-0.4.1/src/tlaplus_cli/cmd/tools/uninstall.py +103 -0
  27. tlaplus_cli-0.4.1/src/tlaplus_cli/cmd/tools/upgrade.py +76 -0
  28. tlaplus_cli-0.4.1/src/tlaplus_cli/config/__init__.py +0 -0
  29. tlaplus_cli-0.3.5/src/tlaplus_cli/config.py → tlaplus_cli-0.4.1/src/tlaplus_cli/config/loader.py +3 -3
  30. tlaplus_cli-0.3.5/src/tlaplus_cli/settings.py → tlaplus_cli-0.4.1/src/tlaplus_cli/config/schema.py +1 -0
  31. tlaplus_cli-0.4.1/src/tlaplus_cli/java/__init__.py +7 -0
  32. tlaplus_cli-0.3.5/src/tlaplus_cli/check_java.py → tlaplus_cli-0.4.1/src/tlaplus_cli/java/inspector.py +15 -11
  33. tlaplus_cli-0.4.1/src/tlaplus_cli/project/__init__.py +3 -0
  34. tlaplus_cli-0.4.1/src/tlaplus_cli/py.typed +0 -0
  35. tlaplus_cli-0.4.1/src/tlaplus_cli/tlc/__init__.py +0 -0
  36. tlaplus_cli-0.4.1/src/tlaplus_cli/tlc/compiler.py +58 -0
  37. tlaplus_cli-0.4.1/src/tlaplus_cli/tlc/runner.py +102 -0
  38. tlaplus_cli-0.4.1/src/tlaplus_cli/ui.py +6 -0
  39. tlaplus_cli-0.4.1/src/tlaplus_cli/versioning/__init__.py +53 -0
  40. tlaplus_cli-0.4.1/src/tlaplus_cli/versioning/api.py +110 -0
  41. tlaplus_cli-0.4.1/src/tlaplus_cli/versioning/downloader.py +93 -0
  42. tlaplus_cli-0.4.1/src/tlaplus_cli/versioning/metadata.py +92 -0
  43. tlaplus_cli-0.4.1/src/tlaplus_cli/versioning/paths.py +83 -0
  44. tlaplus_cli-0.4.1/src/tlaplus_cli/versioning/resolver.py +92 -0
  45. tlaplus_cli-0.4.1/src/tlaplus_cli/versioning/schema.py +27 -0
  46. {tlaplus_cli-0.3.5 → tlaplus_cli-0.4.1/src/tlaplus_cli.egg-info}/PKG-INFO +29 -3
  47. tlaplus_cli-0.4.1/src/tlaplus_cli.egg-info/SOURCES.txt +66 -0
  48. {tlaplus_cli-0.3.5 → tlaplus_cli-0.4.1}/tests/test_build_modules_path.py +5 -5
  49. {tlaplus_cli-0.3.5 → tlaplus_cli-0.4.1}/tests/test_config.py +7 -47
  50. tlaplus_cli-0.4.1/tests/test_config_suite.py +74 -0
  51. tlaplus_cli-0.4.1/tests/test_modules_build_lib.py +88 -0
  52. tlaplus_cli-0.4.1/tests/test_modules_lib.py +106 -0
  53. tlaplus_cli-0.4.1/tests/test_modules_path.py +101 -0
  54. {tlaplus_cli-0.3.5 → tlaplus_cli-0.4.1}/tests/test_settings.py +1 -1
  55. {tlaplus_cli-0.3.5 → tlaplus_cli-0.4.1}/tests/test_tlc_modules_path.py +5 -5
  56. tlaplus_cli-0.3.5/src/tlaplus_cli/build_tlc_module.py +0 -86
  57. tlaplus_cli-0.3.5/src/tlaplus_cli/cli.py +0 -76
  58. tlaplus_cli-0.3.5/src/tlaplus_cli/run_tlc.py +0 -120
  59. tlaplus_cli-0.3.5/src/tlaplus_cli/tools_manager.py +0 -385
  60. tlaplus_cli-0.3.5/src/tlaplus_cli/version_manager.py +0 -502
  61. tlaplus_cli-0.3.5/src/tlaplus_cli.egg-info/SOURCES.txt +0 -29
  62. tlaplus_cli-0.3.5/tests/test_modules_path.py +0 -62
  63. {tlaplus_cli-0.3.5 → tlaplus_cli-0.4.1}/LICENSE +0 -0
  64. {tlaplus_cli-0.3.5 → tlaplus_cli-0.4.1}/MANIFEST.in +0 -0
  65. {tlaplus_cli-0.3.5 → tlaplus_cli-0.4.1}/setup.cfg +0 -0
  66. {tlaplus_cli-0.3.5 → tlaplus_cli-0.4.1}/src/tlaplus_cli/__init__.py +0 -0
  67. /tlaplus_cli-0.3.5/src/tlaplus_cli/py.typed → /tlaplus_cli-0.4.1/src/tlaplus_cli/cache/__init__.py +0 -0
  68. /tlaplus_cli-0.3.5/src/tlaplus_cli/project.py → /tlaplus_cli-0.4.1/src/tlaplus_cli/project/core.py +0 -0
  69. {tlaplus_cli-0.3.5 → tlaplus_cli-0.4.1}/src/tlaplus_cli/resources/__init__.py +0 -0
  70. {tlaplus_cli-0.3.5 → tlaplus_cli-0.4.1}/src/tlaplus_cli/resources/default_config.yaml +0 -0
  71. {tlaplus_cli-0.3.5 → tlaplus_cli-0.4.1}/src/tlaplus_cli.egg-info/dependency_links.txt +0 -0
  72. {tlaplus_cli-0.3.5 → tlaplus_cli-0.4.1}/src/tlaplus_cli.egg-info/entry_points.txt +0 -0
  73. {tlaplus_cli-0.3.5 → tlaplus_cli-0.4.1}/src/tlaplus_cli.egg-info/requires.txt +0 -0
  74. {tlaplus_cli-0.3.5 → tlaplus_cli-0.4.1}/src/tlaplus_cli.egg-info/top_level.txt +0 -0
  75. {tlaplus_cli-0.3.5 → tlaplus_cli-0.4.1}/tests/test_cli.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: tlaplus-cli
3
- Version: 0.3.5
3
+ Version: 0.4.1
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
@@ -175,8 +175,25 @@ Configure a persistent custom modules path:
175
175
  tla modules path /path/to/custom/modules
176
176
  ```
177
177
 
178
+ View or reset the custom modules path:
179
+ ```bash
180
+ tla modules path # View current path
181
+ tla modules path none # Reset to default
182
+ ```
183
+
184
+ Configure a persistent custom modules dependencies (lib) path:
185
+ ```bash
186
+ tla modules lib /path/to/custom/libs
187
+ ```
188
+
189
+ View or reset the custom lib path:
190
+ ```bash
191
+ tla modules lib # View current path
192
+ tla modules lib none # Reset to default
193
+ ```
194
+
178
195
  > [!TIP]
179
- > Setting a persistent `module_path` allows `tla modules build` and `tla tlc` to resolve your Java overrides regardless of where the commands are executed.
196
+ > Setting a persistent `module_path` (and optionally `module_lib_path`) allows `tla modules build` and `tla tlc` to resolve your Java overrides regardless of where the commands are executed.
180
197
 
181
198
  Verbose output:
182
199
  ```bash
@@ -211,7 +228,15 @@ On first run, a default config is created at:
211
228
  ~/.config/tla/config.yaml
212
229
  ```
213
230
 
214
- Edit this file to set your workspace path and TLC options:
231
+ You can manage the configuration using the `tla config` command:
232
+
233
+ ```bash
234
+ tla config list # Display current configuration
235
+ tla config edit # Open config in default editor ($EDITOR or vim)
236
+ tla config edit nano # Open config in a specific editor
237
+ ```
238
+
239
+ Example configuration (`config.yaml`):
215
240
 
216
241
  ```yaml
217
242
  tla:
@@ -230,6 +255,7 @@ tlc:
230
255
  overrides_class: tlc2.overrides.TLCOverrides
231
256
 
232
257
  module_path: null # (Optional) Persistent custom modules path
258
+ module_lib_path: null # (Optional) Persistent custom modules lib path
233
259
 
234
260
  java:
235
261
  min_version: 11
@@ -146,8 +146,25 @@ Configure a persistent custom modules path:
146
146
  tla modules path /path/to/custom/modules
147
147
  ```
148
148
 
149
+ View or reset the custom modules path:
150
+ ```bash
151
+ tla modules path # View current path
152
+ tla modules path none # Reset to default
153
+ ```
154
+
155
+ Configure a persistent custom modules dependencies (lib) path:
156
+ ```bash
157
+ tla modules lib /path/to/custom/libs
158
+ ```
159
+
160
+ View or reset the custom lib path:
161
+ ```bash
162
+ tla modules lib # View current path
163
+ tla modules lib none # Reset to default
164
+ ```
165
+
149
166
  > [!TIP]
150
- > Setting a persistent `module_path` allows `tla modules build` and `tla tlc` to resolve your Java overrides regardless of where the commands are executed.
167
+ > Setting a persistent `module_path` (and optionally `module_lib_path`) allows `tla modules build` and `tla tlc` to resolve your Java overrides regardless of where the commands are executed.
151
168
 
152
169
  Verbose output:
153
170
  ```bash
@@ -182,7 +199,15 @@ On first run, a default config is created at:
182
199
  ~/.config/tla/config.yaml
183
200
  ```
184
201
 
185
- Edit this file to set your workspace path and TLC options:
202
+ You can manage the configuration using the `tla config` command:
203
+
204
+ ```bash
205
+ tla config list # Display current configuration
206
+ tla config edit # Open config in default editor ($EDITOR or vim)
207
+ tla config edit nano # Open config in a specific editor
208
+ ```
209
+
210
+ Example configuration (`config.yaml`):
186
211
 
187
212
  ```yaml
188
213
  tla:
@@ -201,6 +226,7 @@ tlc:
201
226
  overrides_class: tlc2.overrides.TLCOverrides
202
227
 
203
228
  module_path: null # (Optional) Persistent custom modules path
229
+ module_lib_path: null # (Optional) Persistent custom modules lib path
204
230
 
205
231
  java:
206
232
  min_version: 11
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "tlaplus-cli"
7
- version = "0.3.5"
7
+ version = "0.4.1"
8
8
  description = "TLA+ tools: download TLC, compile custom modules, run model checker"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.11"
@@ -62,6 +62,7 @@ dev = [
62
62
  "pre-commit>=3.5.0,<4.0.0",
63
63
  "pre-commit-hooks>=6.0.0,<7.0.0",
64
64
  "twine>=6.0.0,<7.0.0",
65
+ "bandit>=1.9.4",
65
66
  ]
66
67
 
67
68
  [tool.pytest.ini_options]
@@ -105,10 +106,14 @@ ignore = [
105
106
  "PLR0913", # Many arguments are common in tests (fixtures)
106
107
  ]
107
108
 
109
+ [tool.bandit]
110
+ skips = ["B404", "B603", "B607"]
111
+
108
112
  [tool.poe.tasks]
109
113
  check = "pre-commit run --all-files"
110
- lint = ["lint-ruff", "type-check"]
114
+ lint = ["lint-ruff", "lint-bandit", "type-check"]
111
115
  lint-ruff = "ruff check ."
116
+ lint-bandit = "bandit -c pyproject.toml -r src/tlaplus_cli"
112
117
  type-check = "mypy ."
113
118
  fix = ["format", "lint-fix"]
114
119
  format = "ruff format ."
@@ -0,0 +1,28 @@
1
+ import json
2
+ from dataclasses import asdict
3
+ from pathlib import Path
4
+
5
+ from tlaplus_cli.ui import warn
6
+ from tlaplus_cli.versioning.schema import RemoteVersion
7
+
8
+
9
+ def load_github_cache(cache_file: Path) -> list[RemoteVersion] | None:
10
+ """Load the remote versions cache from disk."""
11
+ if cache_file.exists():
12
+ try:
13
+ with cache_file.open("r", encoding="utf-8") as f:
14
+ data = json.load(f)
15
+ return [RemoteVersion(**item) for item in data]
16
+ except (json.JSONDecodeError, OSError, KeyError, TypeError) as e:
17
+ warn(f"Failed to read cache: {e}")
18
+ return None
19
+
20
+
21
+ def save_github_cache(cache_file: Path, versions: list[RemoteVersion]) -> None:
22
+ """Save the remote versions cache to disk."""
23
+ try:
24
+ cache_file.parent.mkdir(parents=True, exist_ok=True)
25
+ with cache_file.open("w", encoding="utf-8") as f:
26
+ json.dump([asdict(v) for v in versions], f)
27
+ except OSError as e:
28
+ warn(f"Failed to save cache: {e}")
@@ -0,0 +1,60 @@
1
+ """TLA+ CLI tool - entry point."""
2
+
3
+ import importlib.metadata
4
+
5
+ import typer
6
+
7
+ from tlaplus_cli.cmd.check_java import check_java
8
+ from tlaplus_cli.cmd.config import app as config_app
9
+ from tlaplus_cli.cmd.fetch_cache import app as fetch_cache_app
10
+ from tlaplus_cli.cmd.modules import app as modules_app
11
+ from tlaplus_cli.cmd.tlc import tlc as run_tlc_cmd
12
+ from tlaplus_cli.cmd.tools import app as tools_app
13
+ from tlaplus_cli.config.loader import load_config
14
+
15
+ app = typer.Typer(
16
+ name="tla",
17
+ help="TLA+ tools: download TLA+ toolset distribution, compile custom modules, run model checker.",
18
+ no_args_is_help=True,
19
+ add_completion=False,
20
+ )
21
+
22
+
23
+ def version_callback(value: bool) -> None:
24
+ if value:
25
+ meta = importlib.metadata.metadata("tlaplus-cli")
26
+ typer.echo(f"{meta['Name']} v{meta['Version']}")
27
+ typer.echo(meta["Summary"])
28
+ raise typer.Exit()
29
+
30
+
31
+ @app.callback()
32
+ def root(
33
+ version: bool = typer.Option( # noqa: ARG001
34
+ None,
35
+ "--version",
36
+ "-v",
37
+ help="Show the application's version and exit.",
38
+ callback=version_callback,
39
+ is_eager=True,
40
+ ),
41
+ ) -> None:
42
+ """TLA+ CLI tool."""
43
+ # Load config early to trigger first-run copy
44
+ load_config()
45
+
46
+
47
+ # --- Subcommands ---
48
+
49
+ app.add_typer(modules_app, name="modules")
50
+ app.add_typer(tools_app, name="tools")
51
+ app.add_typer(fetch_cache_app, name="fetch-cache")
52
+ app.add_typer(config_app, name="config")
53
+
54
+ app.command(name="tlc")(run_tlc_cmd)
55
+ app.command(name="check-java")(check_java)
56
+
57
+
58
+ def main() -> None:
59
+ """Entry point for [project.scripts]."""
60
+ app()
File without changes
@@ -0,0 +1,19 @@
1
+ import typer
2
+
3
+ from tlaplus_cli.config.loader import load_config
4
+ from tlaplus_cli.java import get_java_version, validate_java_version
5
+
6
+
7
+ def check_java() -> None:
8
+ """Check if Java is installed and meets the minimum version requirement."""
9
+ config = load_config()
10
+ version = get_java_version()
11
+ if version:
12
+ typer.echo(f"Detected Java version: {version}")
13
+
14
+ try:
15
+ validate_java_version(config.java.min_version)
16
+ typer.echo(f"Java version is compatible (>= {config.java.min_version}).")
17
+ except RuntimeError as e:
18
+ typer.echo(f"Error: {e}", err=True)
19
+ raise typer.Exit(1) from None
@@ -0,0 +1,6 @@
1
+ import typer
2
+
3
+ app = typer.Typer(name="config", help="Manage application configuration.", no_args_is_help=True)
4
+
5
+ from . import edit # noqa: F401, E402
6
+ from . import list as list_cmd # noqa: F401, E402
@@ -0,0 +1,34 @@
1
+ import os
2
+ import shutil
3
+ import subprocess
4
+
5
+ import typer
6
+
7
+ from tlaplus_cli.cmd.config import app
8
+ from tlaplus_cli.config.loader import config_path, load_config
9
+
10
+
11
+ @app.command(name="edit")
12
+ def edit_config(
13
+ editor: str | None = typer.Argument(None, help="The editor to use (defaults to $EDITOR or 'vim')."),
14
+ ) -> None:
15
+ """Open the configuration file in an editor."""
16
+ cp = config_path()
17
+
18
+ # Ensure config exists by loading it
19
+ load_config()
20
+
21
+ if not editor:
22
+ editor = os.environ.get("EDITOR", "vim")
23
+
24
+ editor_path = shutil.which(editor)
25
+ if not editor_path:
26
+ typer.echo(f"Error: Editor '{editor}' not found in PATH.", err=True)
27
+ raise typer.Exit(1)
28
+
29
+ typer.echo(f"Opening {cp} with {editor} ...")
30
+ try:
31
+ subprocess.run([editor_path, str(cp)], check=True)
32
+ except subprocess.CalledProcessError as e:
33
+ typer.echo(f"Editor exited with error: {e}", err=True)
34
+ raise typer.Exit(1) from None
@@ -0,0 +1,16 @@
1
+ import typer
2
+
3
+ from tlaplus_cli.cmd.config import app
4
+ from tlaplus_cli.config.loader import config_path
5
+
6
+
7
+ @app.command(name="list")
8
+ def list_config() -> None:
9
+ """Display the current configuration file content."""
10
+ cp = config_path()
11
+ if not cp.exists():
12
+ typer.echo("Configuration file not found.", err=True)
13
+ raise typer.Exit(1)
14
+
15
+ with cp.open(encoding="utf-8") as f:
16
+ typer.echo(f.read())
@@ -0,0 +1,5 @@
1
+ import typer
2
+
3
+ app = typer.Typer(name="fetch-cache", help="Manage API fetch cache.", no_args_is_help=True)
4
+
5
+ from . import clear # noqa: F401, E402
@@ -0,0 +1,11 @@
1
+ import typer
2
+
3
+ from tlaplus_cli.cmd.fetch_cache import app
4
+ from tlaplus_cli.versioning import clear_cache
5
+
6
+
7
+ @app.command(name="clear")
8
+ def cmd_clear_cache() -> None:
9
+ """Clear the local cache of remote GitHub versions."""
10
+ clear_cache()
11
+ typer.echo("GitHub versions cache cleared.")
@@ -0,0 +1,5 @@
1
+ import typer
2
+
3
+ app = typer.Typer(name="modules", help="Manage TLA+ Java modules.", no_args_is_help=True)
4
+
5
+ from . import build, lib, path # noqa: F401, E402
@@ -0,0 +1,30 @@
1
+ import subprocess
2
+ from pathlib import Path
3
+
4
+ import typer
5
+
6
+ from tlaplus_cli.cmd.modules import app
7
+ from tlaplus_cli.tlc.compiler import compile_modules
8
+
9
+
10
+ @app.command(name="build")
11
+ def build(
12
+ path: str | None = typer.Argument(None, help="Project root directory (defaults to workspace root)."),
13
+ verbose: bool = typer.Option(False, "--verbose", "-v", help="Show compilation output."),
14
+ ) -> None:
15
+ """Compile custom Java modules."""
16
+ base_dir = Path(path).resolve() if path is not None else None
17
+
18
+ typer.echo("Compiling Java files ...")
19
+ try:
20
+ classes_dir = compile_modules(base_dir, verbose)
21
+ except FileNotFoundError as e:
22
+ typer.echo(f"Error: {e}", err=True)
23
+ raise typer.Exit(1) from None
24
+ except subprocess.CalledProcessError:
25
+ typer.echo("Compilation failed!", err=True)
26
+ raise typer.Exit(1) from None
27
+
28
+ typer.echo(f"Successfully compiled to {classes_dir}")
29
+ service_file = classes_dir / "META-INF" / "services" / "tlc2.overrides.ITLCOverrides"
30
+ typer.echo(f"Created service file at {service_file}")
@@ -0,0 +1,46 @@
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
+
8
+
9
+ @app.command(name="lib")
10
+ def set_modules_lib_path(
11
+ path: str | None = typer.Argument(
12
+ None, help="Path to the custom Java modules dependencies directory, or 'none' to reset."
13
+ ),
14
+ ) -> None:
15
+ """Configure or view a persistent custom Java modules dependencies (lib) path."""
16
+ config_obj = load_config()
17
+
18
+ if path is None:
19
+ if config_obj.module_lib_path:
20
+ typer.echo(f"Current modules lib path: {config_obj.module_lib_path}")
21
+ else:
22
+ modules_dir = (
23
+ Path(config_obj.module_path)
24
+ if config_obj.module_path
25
+ else workspace_root() / config_obj.workspace.modules_dir
26
+ )
27
+ default_path = modules_dir / "lib"
28
+ typer.echo("Custom modules lib path is not set.")
29
+ typer.echo(f"Defaulting to: {default_path}")
30
+ return
31
+
32
+ if path.lower() == "none":
33
+ config_obj.module_lib_path = None
34
+ save_config(config_obj)
35
+ typer.echo("Custom modules lib path reset to default.")
36
+ return
37
+
38
+ p = Path(path).resolve()
39
+
40
+ if not p.is_dir():
41
+ typer.echo(f"Error: Path does not exist or is not a directory: {p}", err=True)
42
+ raise typer.Exit(1)
43
+
44
+ config_obj.module_lib_path = str(p)
45
+ save_config(config_obj)
46
+ typer.echo(f"Modules lib path updated to: {p}")
@@ -0,0 +1,39 @@
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
+
8
+
9
+ @app.command(name="path")
10
+ def set_modules_path(
11
+ path: str | None = typer.Argument(None, help="Path to the custom Java modules directory, or 'none' to reset."),
12
+ ) -> None:
13
+ """Configure or view a persistent custom Java modules source path."""
14
+ config_obj = load_config()
15
+
16
+ if path is None:
17
+ if config_obj.module_path:
18
+ typer.echo(f"Current modules path: {config_obj.module_path}")
19
+ else:
20
+ default_path = workspace_root() / config_obj.workspace.modules_dir
21
+ typer.echo("Custom modules path is not set.")
22
+ typer.echo(f"Defaulting to: {default_path}")
23
+ return
24
+
25
+ if path.lower() == "none":
26
+ config_obj.module_path = None
27
+ save_config(config_obj)
28
+ typer.echo("Custom modules path reset to default.")
29
+ return
30
+
31
+ p = Path(path).resolve()
32
+
33
+ if not p.is_dir():
34
+ typer.echo(f"Error: Path does not exist or is not a directory: {p}", err=True)
35
+ raise typer.Exit(1)
36
+
37
+ config_obj.module_path = str(p)
38
+ save_config(config_obj)
39
+ typer.echo(f"Modules path updated to: {p}")
@@ -0,0 +1,51 @@
1
+ import typer
2
+
3
+ from tlaplus_cli.tlc.compiler import get_tlc_jar_path
4
+ from tlaplus_cli.tlc.runner import get_tlc_version, resolve_spec_file, run_tlc
5
+
6
+
7
+ def version_callback(value: bool) -> None:
8
+ if value:
9
+ jar_path = get_tlc_jar_path()
10
+ typer.echo(f"tla2tools.jar path: {jar_path}")
11
+
12
+ if not jar_path.exists():
13
+ typer.echo(f"Error: tla2tools.jar not found at {jar_path}", err=True)
14
+ typer.echo("Run 'tla tools install' first.", err=True)
15
+ raise typer.Exit(1)
16
+
17
+ version_str = get_tlc_version()
18
+ if version_str:
19
+ typer.echo(version_str)
20
+
21
+ raise typer.Exit(0)
22
+
23
+
24
+ def tlc(
25
+ spec: str = typer.Argument(help="Name of the TLA+ specification (without .tla extension)."),
26
+ version: bool | None = typer.Option(
27
+ None,
28
+ "--version",
29
+ help="Print the path to tla2tools.jar and its version.",
30
+ callback=version_callback,
31
+ is_eager=True,
32
+ ),
33
+ ) -> None:
34
+ """Run TLC model checker on a TLA+ specification."""
35
+ if version:
36
+ pass
37
+
38
+ try:
39
+ _, spec_name = resolve_spec_file(spec)
40
+ except FileNotFoundError as e:
41
+ typer.echo(f"Error: {e}", err=True)
42
+ raise typer.Exit(1) from None
43
+
44
+ typer.echo(f"Running TLC on {spec_name} ...")
45
+ try:
46
+ exit_code = run_tlc(spec)
47
+ except FileNotFoundError as e:
48
+ typer.echo(f"Error: {e}", err=True)
49
+ raise typer.Exit(1) from None
50
+
51
+ raise typer.Exit(exit_code)
@@ -0,0 +1,8 @@
1
+ import typer
2
+
3
+ from tlaplus_cli.cmd.tools.meta import app as meta_app
4
+
5
+ app = typer.Typer(name="tools", help="Manage TLC tools (tla2tools.jar).", no_args_is_help=True)
6
+ app.add_typer(meta_app, name="meta")
7
+
8
+ from . import dir, install, list, path, pin, uninstall, upgrade # noqa: F401, E402
@@ -0,0 +1,16 @@
1
+ import typer
2
+
3
+ from tlaplus_cli.cmd.tools import app
4
+ from tlaplus_cli.versioning import get_tools_dir
5
+
6
+
7
+ @app.command(name="dir")
8
+ def show_dir() -> None:
9
+ """Show the absolute path to the local TLC tools cache directory."""
10
+ tools_dir = get_tools_dir()
11
+ typer.echo(str(tools_dir))
12
+
13
+ if tools_dir.is_dir():
14
+ versions = sorted([d.name for d in tools_dir.iterdir() if d.is_dir()])
15
+ for v in versions:
16
+ typer.echo(f" {v}")
@@ -0,0 +1,79 @@
1
+ from pathlib import Path
2
+
3
+ import requests
4
+ import typer
5
+
6
+ from tlaplus_cli.cmd.tools import app
7
+ from tlaplus_cli.config.loader import load_config
8
+ from tlaplus_cli.versioning import (
9
+ download_version,
10
+ download_version_from_url,
11
+ fetch_remote_versions,
12
+ get_pinned_version_dir,
13
+ get_tools_dir,
14
+ is_url,
15
+ set_pin,
16
+ )
17
+
18
+
19
+ def _auto_pin_if_needed(version_dir: Path) -> None:
20
+ """Pin the version if no version is currently pinned."""
21
+ pinned_dir = get_pinned_version_dir()
22
+ if pinned_dir is None:
23
+ typer.echo(f"Auto-pinning newly installed version {version_dir.name}")
24
+ set_pin(version_dir)
25
+
26
+
27
+ @app.command()
28
+ def install(
29
+ version: str = typer.Argument(None, help="Version tag (e.g. 'v1.8.0') or a direct URL to tla2tools.jar."),
30
+ force: bool = typer.Option(False, "--force", "-f", help="Re-download if already installed."),
31
+ ) -> None:
32
+ """Download and install a specific TLC version."""
33
+ if version and is_url(version):
34
+ try:
35
+ version_dir = download_version_from_url(version)
36
+ except (requests.RequestException, OSError, ValueError) as e:
37
+ typer.echo(f"Error: Failed to download: {e}", err=True)
38
+ raise typer.Exit(1) from e
39
+ else:
40
+ typer.echo("Download complete.")
41
+ typer.echo(f"Successfully installed from URL to {version_dir}")
42
+ _auto_pin_if_needed(version_dir)
43
+ return
44
+
45
+ config = load_config()
46
+ versions, status = fetch_remote_versions(config.tla.urls.tags, config.tla.urls.releases, config.tla.urls.per_page)
47
+
48
+ if not versions:
49
+ typer.echo(f"Error: Could not fetch remote versions (status: {status.value})", err=True)
50
+ raise typer.Exit(1)
51
+
52
+ target = None
53
+ if version:
54
+ target = next((v for v in versions if v.name == version), None)
55
+ if not target:
56
+ typer.echo(f"Error: Version {version} not found.", err=True)
57
+ raise typer.Exit(1)
58
+ else:
59
+ typer.echo("No version specified, selecting latest stable release.")
60
+ # Default to latest non-prerelease
61
+ target = next((v for v in versions if not v.prerelease), versions[0])
62
+
63
+ # Check if already installed
64
+ target_dir = get_tools_dir() / f"{target.name}-{target.short_sha}"
65
+ if target_dir.exists() and not force:
66
+ typer.echo(f"Version {target.name} is already installed.")
67
+ typer.echo(f"Successfully installed {target.name} to {target_dir}")
68
+ _auto_pin_if_needed(target_dir)
69
+ return
70
+
71
+ try:
72
+ version_dir = download_version(target, force=force)
73
+ typer.echo("Download complete.")
74
+ typer.echo(f"Successfully installed {target.name} to {version_dir}")
75
+ except (requests.RequestException, OSError) as e:
76
+ typer.echo(f"Error: Failed to download: {e}", err=True)
77
+ raise typer.Exit(1) from e
78
+
79
+ _auto_pin_if_needed(version_dir)