tlaplus-cli 0.7.0__tar.gz → 0.8.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 (71) hide show
  1. {tlaplus_cli-0.7.0/src/tlaplus_cli.egg-info → tlaplus_cli-0.8.1}/PKG-INFO +30 -11
  2. tlaplus_cli-0.8.1/README.md +40 -0
  3. {tlaplus_cli-0.7.0 → tlaplus_cli-0.8.1}/pyproject.toml +1 -1
  4. {tlaplus_cli-0.7.0 → tlaplus_cli-0.8.1}/src/tlaplus_cli/cli.py +3 -1
  5. {tlaplus_cli-0.7.0 → tlaplus_cli-0.8.1}/src/tlaplus_cli/cmd/modules/add.py +2 -0
  6. {tlaplus_cli-0.7.0 → tlaplus_cli-0.8.1}/src/tlaplus_cli/cmd/modules/remove.py +2 -0
  7. {tlaplus_cli-0.7.0 → tlaplus_cli-0.8.1}/src/tlaplus_cli/cmd/tlc.py +37 -7
  8. {tlaplus_cli-0.7.0 → tlaplus_cli-0.8.1}/src/tlaplus_cli/cmd/tools/list.py +1 -0
  9. tlaplus_cli-0.8.1/src/tlaplus_cli/tlc/run_cache.py +61 -0
  10. {tlaplus_cli-0.7.0 → tlaplus_cli-0.8.1}/src/tlaplus_cli/tlc/runner.py +62 -4
  11. {tlaplus_cli-0.7.0 → tlaplus_cli-0.8.1}/src/tlaplus_cli/versioning/__init__.py +8 -0
  12. tlaplus_cli-0.8.1/src/tlaplus_cli/versioning/app_version.py +62 -0
  13. {tlaplus_cli-0.7.0 → tlaplus_cli-0.8.1}/src/tlaplus_cli/versioning/paths.py +8 -0
  14. {tlaplus_cli-0.7.0 → tlaplus_cli-0.8.1/src/tlaplus_cli.egg-info}/PKG-INFO +30 -11
  15. {tlaplus_cli-0.7.0 → tlaplus_cli-0.8.1}/src/tlaplus_cli.egg-info/SOURCES.txt +2 -0
  16. tlaplus_cli-0.7.0/README.md +0 -21
  17. {tlaplus_cli-0.7.0 → tlaplus_cli-0.8.1}/LICENSE +0 -0
  18. {tlaplus_cli-0.7.0 → tlaplus_cli-0.8.1}/MANIFEST.in +0 -0
  19. {tlaplus_cli-0.7.0 → tlaplus_cli-0.8.1}/setup.cfg +0 -0
  20. {tlaplus_cli-0.7.0 → tlaplus_cli-0.8.1}/src/tlaplus_cli/__init__.py +0 -0
  21. {tlaplus_cli-0.7.0 → tlaplus_cli-0.8.1}/src/tlaplus_cli/cache/__init__.py +0 -0
  22. {tlaplus_cli-0.7.0 → tlaplus_cli-0.8.1}/src/tlaplus_cli/cache/github.py +0 -0
  23. {tlaplus_cli-0.7.0 → tlaplus_cli-0.8.1}/src/tlaplus_cli/cmd/__init__.py +0 -0
  24. {tlaplus_cli-0.7.0 → tlaplus_cli-0.8.1}/src/tlaplus_cli/cmd/check_java.py +0 -0
  25. {tlaplus_cli-0.7.0 → tlaplus_cli-0.8.1}/src/tlaplus_cli/cmd/config/__init__.py +0 -0
  26. {tlaplus_cli-0.7.0 → tlaplus_cli-0.8.1}/src/tlaplus_cli/cmd/config/edit.py +0 -0
  27. {tlaplus_cli-0.7.0 → tlaplus_cli-0.8.1}/src/tlaplus_cli/cmd/config/list.py +0 -0
  28. {tlaplus_cli-0.7.0 → tlaplus_cli-0.8.1}/src/tlaplus_cli/cmd/fetch_cache/__init__.py +0 -0
  29. {tlaplus_cli-0.7.0 → tlaplus_cli-0.8.1}/src/tlaplus_cli/cmd/fetch_cache/clear.py +0 -0
  30. {tlaplus_cli-0.7.0 → tlaplus_cli-0.8.1}/src/tlaplus_cli/cmd/modules/__init__.py +0 -0
  31. {tlaplus_cli-0.7.0 → tlaplus_cli-0.8.1}/src/tlaplus_cli/cmd/modules/list.py +0 -0
  32. {tlaplus_cli-0.7.0 → tlaplus_cli-0.8.1}/src/tlaplus_cli/cmd/modules/path.py +0 -0
  33. {tlaplus_cli-0.7.0 → tlaplus_cli-0.8.1}/src/tlaplus_cli/cmd/tools/__init__.py +0 -0
  34. {tlaplus_cli-0.7.0 → tlaplus_cli-0.8.1}/src/tlaplus_cli/cmd/tools/dir.py +0 -0
  35. {tlaplus_cli-0.7.0 → tlaplus_cli-0.8.1}/src/tlaplus_cli/cmd/tools/install.py +0 -0
  36. {tlaplus_cli-0.7.0 → tlaplus_cli-0.8.1}/src/tlaplus_cli/cmd/tools/meta/__init__.py +0 -0
  37. {tlaplus_cli-0.7.0 → tlaplus_cli-0.8.1}/src/tlaplus_cli/cmd/tools/meta/sync.py +0 -0
  38. {tlaplus_cli-0.7.0 → tlaplus_cli-0.8.1}/src/tlaplus_cli/cmd/tools/path.py +0 -0
  39. {tlaplus_cli-0.7.0 → tlaplus_cli-0.8.1}/src/tlaplus_cli/cmd/tools/pin.py +0 -0
  40. {tlaplus_cli-0.7.0 → tlaplus_cli-0.8.1}/src/tlaplus_cli/cmd/tools/uninstall.py +0 -0
  41. {tlaplus_cli-0.7.0 → tlaplus_cli-0.8.1}/src/tlaplus_cli/cmd/tools/upgrade.py +0 -0
  42. {tlaplus_cli-0.7.0 → tlaplus_cli-0.8.1}/src/tlaplus_cli/config/__init__.py +0 -0
  43. {tlaplus_cli-0.7.0 → tlaplus_cli-0.8.1}/src/tlaplus_cli/config/loader.py +0 -0
  44. {tlaplus_cli-0.7.0 → tlaplus_cli-0.8.1}/src/tlaplus_cli/config/schema.py +0 -0
  45. {tlaplus_cli-0.7.0 → tlaplus_cli-0.8.1}/src/tlaplus_cli/java/__init__.py +0 -0
  46. {tlaplus_cli-0.7.0 → tlaplus_cli-0.8.1}/src/tlaplus_cli/java/classpath.py +0 -0
  47. {tlaplus_cli-0.7.0 → tlaplus_cli-0.8.1}/src/tlaplus_cli/java/inspector.py +0 -0
  48. {tlaplus_cli-0.7.0 → tlaplus_cli-0.8.1}/src/tlaplus_cli/project/__init__.py +0 -0
  49. {tlaplus_cli-0.7.0 → tlaplus_cli-0.8.1}/src/tlaplus_cli/project/core.py +0 -0
  50. {tlaplus_cli-0.7.0 → tlaplus_cli-0.8.1}/src/tlaplus_cli/py.typed +0 -0
  51. {tlaplus_cli-0.7.0 → tlaplus_cli-0.8.1}/src/tlaplus_cli/resources/__init__.py +0 -0
  52. {tlaplus_cli-0.7.0 → tlaplus_cli-0.8.1}/src/tlaplus_cli/resources/default_config.yaml +0 -0
  53. {tlaplus_cli-0.7.0 → tlaplus_cli-0.8.1}/src/tlaplus_cli/tlc/__init__.py +0 -0
  54. {tlaplus_cli-0.7.0 → tlaplus_cli-0.8.1}/src/tlaplus_cli/tlc/codes.py +0 -0
  55. {tlaplus_cli-0.7.0 → tlaplus_cli-0.8.1}/src/tlaplus_cli/tlc/compiler.py +0 -0
  56. {tlaplus_cli-0.7.0 → tlaplus_cli-0.8.1}/src/tlaplus_cli/tlc/components.py +0 -0
  57. {tlaplus_cli-0.7.0 → tlaplus_cli-0.8.1}/src/tlaplus_cli/tlc/formatter.py +0 -0
  58. {tlaplus_cli-0.7.0 → tlaplus_cli-0.8.1}/src/tlaplus_cli/tlc/models.py +0 -0
  59. {tlaplus_cli-0.7.0 → tlaplus_cli-0.8.1}/src/tlaplus_cli/tlc/parser.py +0 -0
  60. {tlaplus_cli-0.7.0 → tlaplus_cli-0.8.1}/src/tlaplus_cli/tlc/sany.py +0 -0
  61. {tlaplus_cli-0.7.0 → tlaplus_cli-0.8.1}/src/tlaplus_cli/tlc/values.py +0 -0
  62. {tlaplus_cli-0.7.0 → tlaplus_cli-0.8.1}/src/tlaplus_cli/ui.py +0 -0
  63. {tlaplus_cli-0.7.0 → tlaplus_cli-0.8.1}/src/tlaplus_cli/versioning/api.py +0 -0
  64. {tlaplus_cli-0.7.0 → tlaplus_cli-0.8.1}/src/tlaplus_cli/versioning/downloader.py +0 -0
  65. {tlaplus_cli-0.7.0 → tlaplus_cli-0.8.1}/src/tlaplus_cli/versioning/metadata.py +0 -0
  66. {tlaplus_cli-0.7.0 → tlaplus_cli-0.8.1}/src/tlaplus_cli/versioning/resolver.py +0 -0
  67. {tlaplus_cli-0.7.0 → tlaplus_cli-0.8.1}/src/tlaplus_cli/versioning/schema.py +0 -0
  68. {tlaplus_cli-0.7.0 → tlaplus_cli-0.8.1}/src/tlaplus_cli.egg-info/dependency_links.txt +0 -0
  69. {tlaplus_cli-0.7.0 → tlaplus_cli-0.8.1}/src/tlaplus_cli.egg-info/entry_points.txt +0 -0
  70. {tlaplus_cli-0.7.0 → tlaplus_cli-0.8.1}/src/tlaplus_cli.egg-info/requires.txt +0 -0
  71. {tlaplus_cli-0.7.0 → tlaplus_cli-0.8.1}/src/tlaplus_cli.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: tlaplus-cli
3
- Version: 0.7.0
3
+ Version: 0.8.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
@@ -32,20 +32,39 @@ Dynamic: license-file
32
32
 
33
33
  Command-line tool for working with TLA+ specifications and the TLC model checker.
34
34
 
35
- ## Project goals
35
+ ## Project Goals
36
36
 
37
37
  1. Simplify managing TLA+ tools such as TLC, community modules, custom modules, etc.
38
38
  2. Run TLA tools in a single CLI interface.
39
- 3. Manage and display the tools outputs.
39
+ 3. Manage and display the tool outputs.
40
40
 
41
- ## Features
41
+ ## Quick Start & Usage
42
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.
43
+ ### 1. Toolset Management (`tla tools`)
44
+ * `tla check-java`: Verify Java installation and compatibility.
45
+ * `tla tools list`: List available and installed toolset versions (`tla2tools.jar`).
46
+ * `tla tools install [vX.Y.Z]`: Download and install a toolset version.
47
+ * `tla tools pin <version>`: Set default toolset version for TLC runs.
46
48
 
47
- ## Installation
49
+ ### 2. Model Checking (`tla tlc`)
50
+ * `tla tlc <spec>`: Execute TLC model checker on a specification.
51
+ * `tla tlc <spec> --show-command`: Inspect exact JVM command before execution.
48
52
 
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)
53
+ ### 3. Custom Java Modules (`tla modules`)
54
+ * `tla modules add <path>`: Compile Java operator overrides and register module in CLI cache.
55
+ * `tla modules list`: Display cached custom modules.
56
+ * `tla modules remove <name>`: Remove custom module from cache.
57
+
58
+ For complete, runnable specifications and Java module examples, see [docs/examples](docs/examples/).
59
+
60
+ ## Quick Installation
61
+
62
+ ### Via `uv tool`
63
+ ```bash
64
+ uv tool install tlaplus-cli
65
+ ```
66
+
67
+ ### Via Nix Flakes
68
+ ```bash
69
+ nix profile install github:nikolskiy/tlaplus-cli
70
+ ```
@@ -0,0 +1,40 @@
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 tool outputs.
10
+
11
+ ## Quick Start & Usage
12
+
13
+ ### 1. Toolset Management (`tla tools`)
14
+ * `tla check-java`: Verify Java installation and compatibility.
15
+ * `tla tools list`: List available and installed toolset versions (`tla2tools.jar`).
16
+ * `tla tools install [vX.Y.Z]`: Download and install a toolset version.
17
+ * `tla tools pin <version>`: Set default toolset version for TLC runs.
18
+
19
+ ### 2. Model Checking (`tla tlc`)
20
+ * `tla tlc <spec>`: Execute TLC model checker on a specification.
21
+ * `tla tlc <spec> --show-command`: Inspect exact JVM command before execution.
22
+
23
+ ### 3. Custom Java Modules (`tla modules`)
24
+ * `tla modules add <path>`: Compile Java operator overrides and register module in CLI cache.
25
+ * `tla modules list`: Display cached custom modules.
26
+ * `tla modules remove <name>`: Remove custom module from cache.
27
+
28
+ For complete, runnable specifications and Java module examples, see [docs/examples](docs/examples/).
29
+
30
+ ## Quick Installation
31
+
32
+ ### Via `uv tool`
33
+ ```bash
34
+ uv tool install tlaplus-cli
35
+ ```
36
+
37
+ ### Via Nix Flakes
38
+ ```bash
39
+ nix profile install github:nikolskiy/tlaplus-cli
40
+ ```
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "tlaplus-cli"
7
- version = "0.7.0"
7
+ version = "0.8.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"
@@ -11,6 +11,7 @@ from tlaplus_cli.cmd.modules import app as modules_app
11
11
  from tlaplus_cli.cmd.tlc import tlc as run_tlc_cmd
12
12
  from tlaplus_cli.cmd.tools import app as tools_app
13
13
  from tlaplus_cli.config.loader import load_config
14
+ from tlaplus_cli.versioning import get_app_version
14
15
 
15
16
  app = typer.Typer(
16
17
  name="tla",
@@ -23,7 +24,8 @@ app = typer.Typer(
23
24
  def version_callback(value: bool) -> None:
24
25
  if value:
25
26
  meta = importlib.metadata.metadata("tlaplus-cli")
26
- typer.echo(f"{meta['Name']} v{meta['Version']}")
27
+ app_ver = get_app_version(base_version=meta["Version"])
28
+ typer.echo(f"{meta['Name']} v{app_ver}")
27
29
  typer.echo(meta["Summary"])
28
30
  raise typer.Exit()
29
31
 
@@ -11,6 +11,7 @@ import typer
11
11
  from tlaplus_cli.config.loader import load_config
12
12
  from tlaplus_cli.java.classpath import ClasspathResolver, ResolveMode
13
13
  from tlaplus_cli.tlc.compiler import get_tlc_jar_path
14
+ from tlaplus_cli.tlc.run_cache import clear_tlc_run_cache
14
15
  from tlaplus_cli.versioning.paths import get_modules_dir
15
16
 
16
17
 
@@ -135,4 +136,5 @@ def add_module( # noqa: PLR0912, PLR0915
135
136
  with (target_dir / "metadata.json").open("w") as f:
136
137
  json.dump(metadata, f, indent=2)
137
138
 
139
+ clear_tlc_run_cache()
138
140
  typer.echo(f"Module '{module_name}' successfully added to cache.")
@@ -2,6 +2,7 @@ import shutil
2
2
 
3
3
  import typer
4
4
 
5
+ from tlaplus_cli.tlc.run_cache import clear_tlc_run_cache
5
6
  from tlaplus_cli.versioning.paths import get_modules_dir
6
7
 
7
8
 
@@ -14,6 +15,7 @@ def remove_module(module_name: str = typer.Argument(..., help="Name of the modul
14
15
 
15
16
  try:
16
17
  shutil.rmtree(target_dir)
18
+ clear_tlc_run_cache()
17
19
  typer.echo(f"Module '{module_name}' successfully deleted from cache.")
18
20
  except OSError as e:
19
21
  typer.echo(f"Error: Failed to delete module: {e}", err=True)
@@ -12,9 +12,11 @@ from tlaplus_cli.tlc.compiler import get_tlc_jar_path
12
12
  from tlaplus_cli.tlc.components import StatsTableFormatter, StatusFooterRenderable
13
13
  from tlaplus_cli.tlc.formatter import TlcFormatter
14
14
  from tlaplus_cli.tlc.models import CheckState, ModelCheckResult
15
+ from tlaplus_cli.tlc.run_cache import clear_tlc_run_cache
15
16
  from tlaplus_cli.tlc.runner import (
16
17
  build_tlc_command,
17
18
  get_tlc_version,
19
+ resolve_cfg_file,
18
20
  resolve_spec_file,
19
21
  run_tlc,
20
22
  )
@@ -62,9 +64,13 @@ def _show_diagnostic_info(spec: str, spec_name: str) -> None:
62
64
 
63
65
  # PLR0912: This command handler orchestrates multiple output types (errors, warnings, results, progress);
64
66
  # splitting it would fragment the CLI's primary user-facing logic.
67
+ # PLR0913: CLI command function accepts options as parameters.
65
68
  # PLR0915: Too many statements.
66
- def tlc( # noqa: PLR0912, PLR0915
67
- spec: str = typer.Argument(help="Name of the TLA+ specification (without .tla extension)."),
69
+ def tlc( # noqa: PLR0912, PLR0913, PLR0915
70
+ spec: str | None = typer.Argument(
71
+ None,
72
+ help="Name of the TLA+ specification (without .tla extension).",
73
+ ),
68
74
  # Typer requires the parameter in the signature to register the CLI option;
69
75
  # since the logic is in the eager callback, the value is not used here.
70
76
  version: bool | None = typer.Option( # noqa: ARG001
@@ -75,6 +81,7 @@ def tlc( # noqa: PLR0912, PLR0915
75
81
  is_eager=True,
76
82
  ),
77
83
  show_command: bool = typer.Option(False, "--show-command", help="Print the command instead of executing it."),
84
+ cleanup: bool = typer.Option(False, "--cleanup", help="Purge all cached TLC run artifacts from cache."),
78
85
  _refresh_interval: float | None = typer.Option(
79
86
  None,
80
87
  "--refresh-interval",
@@ -82,17 +89,39 @@ def tlc( # noqa: PLR0912, PLR0915
82
89
  ),
83
90
  coverage: bool = typer.Option(False, "--coverage", help="Enable code coverage visualization."),
84
91
  raw: bool = typer.Option(False, "--raw", help="Run TLC in raw mode without structured tool mode formatting."),
92
+ cfg: str | None = typer.Option(
93
+ None,
94
+ "--cfg",
95
+ help="Custom TLC configuration file name or path (e.g. spec-v1 or spec-v1.cfg).",
96
+ ),
85
97
  ) -> None:
86
98
  """Run TLC model checker on a TLA+ specification."""
99
+ if cleanup:
100
+ clear_tlc_run_cache()
101
+ ui.success("TLC run cache cleared successfully.")
102
+ raise typer.Exit(0)
103
+
104
+ if not spec:
105
+ ui.error("Missing argument 'SPEC'.")
106
+ raise typer.Exit(1)
107
+
87
108
  # version argument is handled by version_callback (is_eager=True)
88
109
  try:
89
- _, _spec_name = resolve_spec_file(spec)
110
+ spec_file, _spec_name = resolve_spec_file(spec)
111
+ if cfg:
112
+ config_obj = load_config()
113
+ project_root = find_project_root(
114
+ spec_file,
115
+ modules_dir=config_obj.workspace.modules_dir,
116
+ classes_dir=config_obj.workspace.classes_dir,
117
+ )
118
+ resolve_cfg_file(cfg, spec_file, project_root=project_root)
90
119
  except FileNotFoundError as e:
91
120
  ui.error(str(e))
92
121
  raise typer.Exit(1) from None
93
122
 
94
123
  if show_command:
95
- cmd = build_tlc_command(spec)
124
+ cmd = build_tlc_command(spec, cfg=cfg)
96
125
  if coverage:
97
126
  cmd.extend(["-coverage", "1"])
98
127
  if not cmd:
@@ -101,9 +130,10 @@ def tlc( # noqa: PLR0912, PLR0915
101
130
  raise typer.Exit(0)
102
131
 
103
132
  if raw:
104
- exit_code = run_tlc(spec, coverage=coverage, raw=True)
133
+ exit_code = run_tlc(spec, coverage=coverage, raw=True, cfg=cfg)
105
134
  raise typer.Exit(exit_code)
106
135
 
136
+
107
137
  tlc_version = get_tlc_version() or "unknown"
108
138
  formatter = TlcFormatter(tlc_version=tlc_version)
109
139
  layout = formatter.create_layout()
@@ -164,7 +194,7 @@ def tlc( # noqa: PLR0912, PLR0915
164
194
  live.console.print(table_formatter.get_border())
165
195
  stats_border_printed = True
166
196
 
167
- exit_code = run_tlc(spec, callback=wrapped_callback, coverage=coverage)
197
+ exit_code = run_tlc(spec, callback=wrapped_callback, coverage=coverage, cfg=cfg)
168
198
  else:
169
199
  # Non-TTY mode: no Live block
170
200
  table_formatter = StatsTableFormatter()
@@ -207,7 +237,7 @@ def tlc( # noqa: PLR0912, PLR0915
207
237
  print(table_formatter.get_border())
208
238
  stats_border_printed = True
209
239
 
210
- exit_code = run_tlc(spec, callback=wrapped_callback, coverage=coverage)
240
+ exit_code = run_tlc(spec, callback=wrapped_callback, coverage=coverage, cfg=cfg)
211
241
 
212
242
  # Fallback print of border if not printed yet
213
243
  if stats_header_printed and not stats_border_printed:
@@ -15,6 +15,7 @@ from tlaplus_cli.versioning import (
15
15
 
16
16
  @app.command(name="list")
17
17
  def list_versions() -> None:
18
+ """List available and installed TLC versions."""
18
19
  config = load_config()
19
20
  versions, status = fetch_remote_versions(config.tla.urls.tags, config.tla.urls.releases, config.tla.urls.per_page)
20
21
 
@@ -0,0 +1,61 @@
1
+ import contextlib
2
+ import hashlib
3
+ import shutil
4
+ from pathlib import Path
5
+
6
+ from tlaplus_cli.versioning.paths import get_tlc_run_dir
7
+
8
+
9
+ def compute_spec_hash(spec_file: Path) -> str:
10
+ """Compute an 8-character SHA-256 short hash for a TLA+ specification and its configuration file."""
11
+ hasher = hashlib.sha256()
12
+
13
+ # Include absolute path string
14
+ hasher.update(str(spec_file.absolute()).encode("utf-8"))
15
+
16
+ # Include spec file contents if it exists
17
+ with contextlib.suppress(OSError):
18
+ hasher.update(spec_file.read_bytes())
19
+
20
+ # Include .cfg file contents if present
21
+ cfg_file = spec_file.with_suffix(".cfg")
22
+ if cfg_file.is_file():
23
+ with contextlib.suppress(OSError):
24
+ hasher.update(cfg_file.read_bytes())
25
+
26
+ return hasher.hexdigest()[:8]
27
+
28
+
29
+ def _cleanup_stale_spec_dirs(tlc_run_dir: Path, spec_stem: str, current_hash: str) -> None:
30
+ """Remove older cached run directories for the spec stem that do not match the current hash."""
31
+ if not tlc_run_dir.is_dir():
32
+ return
33
+
34
+ prefix = f"{spec_stem}_"
35
+ current_dir_name = f"{spec_stem}_{current_hash}"
36
+
37
+ for item in tlc_run_dir.iterdir():
38
+ if item.is_dir() and item.name.startswith(prefix) and item.name != current_dir_name:
39
+ with contextlib.suppress(OSError):
40
+ shutil.rmtree(item)
41
+
42
+
43
+ def get_spec_run_dir(spec_file: Path) -> Path:
44
+ """Get or create the isolated run directory for a spec and purge stale runs for the same spec."""
45
+ spec_file = spec_file.absolute()
46
+ short_hash = compute_spec_hash(spec_file)
47
+ tlc_run_dir = get_tlc_run_dir()
48
+
49
+ _cleanup_stale_spec_dirs(tlc_run_dir, spec_file.stem, short_hash)
50
+
51
+ target_dir = tlc_run_dir / f"{spec_file.stem}_{short_hash}"
52
+ target_dir.mkdir(parents=True, exist_ok=True)
53
+ return target_dir
54
+
55
+
56
+ def clear_tlc_run_cache() -> None:
57
+ """Purge all cached TLC run state directories."""
58
+ tlc_run_dir = get_tlc_run_dir()
59
+ if tlc_run_dir.exists():
60
+ with contextlib.suppress(OSError):
61
+ shutil.rmtree(tlc_run_dir)
@@ -12,6 +12,7 @@ from tlaplus_cli.project import find_project_root
12
12
  from tlaplus_cli.tlc.compiler import get_tlc_jar_path
13
13
  from tlaplus_cli.tlc.models import CheckState, ModelCheckResult
14
14
  from tlaplus_cli.tlc.parser import TlcParser
15
+ from tlaplus_cli.tlc.run_cache import get_spec_run_dir
15
16
  from tlaplus_cli.tlc.sany import SanyParser
16
17
 
17
18
 
@@ -36,6 +37,51 @@ def resolve_spec_file(spec: str) -> tuple[Path, str]:
36
37
  return spec_file.absolute(), spec_file.name
37
38
 
38
39
 
40
+ def resolve_cfg_file(cfg: str, spec_file: Path, project_root: Path | None = None) -> tuple[Path, str]:
41
+ """Resolve a .cfg configuration file from a string name or path.
42
+
43
+ Returns (absolute_path, cfg_arg_for_tlc).
44
+ Raises FileNotFoundError if not found.
45
+ """
46
+ cfg_path = Path(cfg)
47
+ cfg_names: list[str] = [cfg_path.name]
48
+ if cfg_path.suffix != ".cfg":
49
+ cfg_names.append(cfg_path.name + ".cfg")
50
+
51
+ candidates: list[Path] = []
52
+ if cfg_path.is_absolute():
53
+ candidates.append(cfg_path)
54
+ if cfg_path.suffix != ".cfg":
55
+ candidates.append(cfg_path.with_suffix(".cfg"))
56
+ else:
57
+ search_dirs: list[Path] = []
58
+ base_parent = cfg_path.parent
59
+ search_dirs.append(spec_file.parent / base_parent)
60
+ search_dirs.append(Path.cwd() / base_parent)
61
+ if project_root:
62
+ search_dirs.append(project_root / base_parent)
63
+
64
+ seen_dirs: set[Path] = set()
65
+ for sdir in search_dirs:
66
+ resolved_dir = sdir.resolve()
67
+ if resolved_dir not in seen_dirs:
68
+ seen_dirs.add(resolved_dir)
69
+ for cname in cfg_names:
70
+ candidates.append(sdir / cname)
71
+
72
+ cfg_file = next((c for c in candidates if c.is_file()), None)
73
+ if not cfg_file:
74
+ msg = f"Could not find TLC configuration file '{cfg}'"
75
+ raise FileNotFoundError(msg)
76
+
77
+ cfg_arg = cfg_file.name
78
+ if project_root:
79
+ with contextlib.suppress(ValueError):
80
+ cfg_arg = str(cfg_file.relative_to(project_root))
81
+
82
+ return cfg_file.absolute(), cfg_arg
83
+
84
+
39
85
  def get_override_classes(classpath: list[str]) -> list[str]:
40
86
  """Inspect all classpath directories and locate override classes."""
41
87
  classes = []
@@ -63,7 +109,7 @@ def get_override_classes(classpath: list[str]) -> list[str]:
63
109
  return deduped
64
110
 
65
111
 
66
- def build_tlc_command(spec: str) -> list[str]:
112
+ def build_tlc_command(spec: str, cfg: str | None = None) -> list[str]:
67
113
  """Build the java command for running TLC."""
68
114
  config = load_config()
69
115
 
@@ -95,29 +141,41 @@ def build_tlc_command(spec: str) -> list[str]:
95
141
  with contextlib.suppress(ValueError):
96
142
  spec_arg = str(spec_file.relative_to(project_root))
97
143
 
98
- return [
144
+ run_dir = get_spec_run_dir(spec_file)
145
+
146
+ cmd = [
99
147
  "java",
100
148
  *config.java.opts,
101
149
  *extra_jvm_opts,
102
150
  "-cp",
103
151
  classpath,
104
152
  config.tlc.java_class,
105
- spec_arg,
153
+ "-metadir",
154
+ str(run_dir),
106
155
  ]
107
156
 
157
+ if cfg:
158
+ _, cfg_arg = resolve_cfg_file(cfg, spec_file, project_root=project_root)
159
+ cmd.extend(["-config", cfg_arg])
160
+
161
+ cmd.append(spec_arg)
162
+ return cmd
163
+
108
164
 
109
165
  def run_tlc( # noqa: PLR0912, PLR0915
110
166
  spec: str,
111
167
  callback: Callable[[ModelCheckResult], None] | None = None,
112
168
  coverage: bool = False,
113
169
  raw: bool = False,
170
+ cfg: str | None = None,
114
171
  ) -> int:
115
172
  """Run TLC model checker on a TLA+ specification. Returns exit code."""
116
173
  config = load_config()
117
174
  validate_java_version(config.java.min_version)
118
175
 
119
176
  spec_file, _ = resolve_spec_file(spec)
120
- cmd = build_tlc_command(spec)
177
+ cmd = build_tlc_command(spec, cfg=cfg)
178
+
121
179
 
122
180
  # Ensure tool mode is enabled for structured output (unless in raw mode)
123
181
  if not raw and "-tool" not in cmd:
@@ -1,4 +1,9 @@
1
1
  from tlaplus_cli.versioning.api import fetch_remote_versions
2
+ from tlaplus_cli.versioning.app_version import (
3
+ get_app_version,
4
+ get_git_commit_hash,
5
+ is_editable_install,
6
+ )
2
7
  from tlaplus_cli.versioning.downloader import (
3
8
  download_version,
4
9
  download_version_from_url,
@@ -39,10 +44,13 @@ __all__ = [
39
44
  "download_version_from_url",
40
45
  "extract_version_from_url",
41
46
  "fetch_remote_versions",
47
+ "get_app_version",
48
+ "get_git_commit_hash",
42
49
  "get_github_cache_file",
43
50
  "get_pinned_path",
44
51
  "get_pinned_version_dir",
45
52
  "get_tools_dir",
53
+ "is_editable_install",
46
54
  "is_url",
47
55
  "list_local_versions",
48
56
  "read_version_metadata",
@@ -0,0 +1,62 @@
1
+ """App version discovery and git commit resolution for development installations."""
2
+
3
+ import importlib.metadata
4
+ import json
5
+ import subprocess
6
+ from pathlib import Path
7
+
8
+
9
+ def is_editable_install() -> bool:
10
+ """Check if the tlaplus-cli package is installed in editable / development mode."""
11
+ try:
12
+ dist = importlib.metadata.distribution("tlaplus-cli")
13
+ raw = dist.read_text("direct_url.json")
14
+ if raw:
15
+ data = json.loads(raw)
16
+ if isinstance(data, dict) and data.get("dir_info", {}).get("editable") is True:
17
+ return True
18
+
19
+ origin = getattr(dist, "origin", None)
20
+ if origin is not None:
21
+ dir_info = getattr(origin, "dir_info", None)
22
+ if dir_info is not None and getattr(dir_info, "editable", None) is True:
23
+ return True
24
+ except (importlib.metadata.PackageNotFoundError, json.JSONDecodeError, TypeError, KeyError, AttributeError):
25
+ pass
26
+ return False
27
+
28
+
29
+ def get_git_commit_hash(base_dir: Path | None = None) -> str | None:
30
+ """Attempt to retrieve short git commit hash using git rev-parse --short HEAD."""
31
+ cmd = ["git", "rev-parse", "--short", "HEAD"]
32
+ cwd = base_dir if (base_dir and base_dir.is_dir()) else Path(__file__).parent
33
+ try:
34
+ res = subprocess.run(cmd, capture_output=True, text=True, check=False, cwd=cwd)
35
+ if res.returncode == 0:
36
+ commit = res.stdout.strip()
37
+ if commit and len(commit) >= 7:
38
+ return commit
39
+ except (subprocess.SubprocessError, OSError):
40
+ pass
41
+ return None
42
+
43
+
44
+ def get_app_version(base_version: str | None = None, base_dir: Path | None = None) -> str:
45
+ """Return the application version string.
46
+
47
+ Appends short git commit hash if running in an editable/development installation.
48
+ """
49
+ if not base_version:
50
+ try:
51
+ meta = importlib.metadata.metadata("tlaplus-cli")
52
+ base_version = meta["Version"]
53
+ except (importlib.metadata.PackageNotFoundError, KeyError):
54
+ base_version = "0.0.0"
55
+
56
+ git_hash = get_git_commit_hash(base_dir)
57
+ repo_git = (Path(__file__).parent / "../../..").resolve() / ".git"
58
+ is_dev = is_editable_install() or base_dir is not None or repo_git.exists()
59
+ if git_hash and is_dev:
60
+ return f"{base_version}+{git_hash}"
61
+
62
+ return base_version
@@ -17,6 +17,14 @@ def get_modules_dir() -> Path:
17
17
  return cache_dir() / "modules"
18
18
 
19
19
 
20
+ def get_run_dir() -> Path:
21
+ return cache_dir() / "run"
22
+
23
+
24
+ def get_tlc_run_dir() -> Path:
25
+ return get_run_dir() / "tlc"
26
+
27
+
20
28
  def get_pinned_path() -> Path:
21
29
  """Returns path to the pin marker file."""
22
30
  return get_tools_dir() / "tools-pinned-version.txt"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: tlaplus-cli
3
- Version: 0.7.0
3
+ Version: 0.8.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
@@ -32,20 +32,39 @@ Dynamic: license-file
32
32
 
33
33
  Command-line tool for working with TLA+ specifications and the TLC model checker.
34
34
 
35
- ## Project goals
35
+ ## Project Goals
36
36
 
37
37
  1. Simplify managing TLA+ tools such as TLC, community modules, custom modules, etc.
38
38
  2. Run TLA tools in a single CLI interface.
39
- 3. Manage and display the tools outputs.
39
+ 3. Manage and display the tool outputs.
40
40
 
41
- ## Features
41
+ ## Quick Start & Usage
42
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.
43
+ ### 1. Toolset Management (`tla tools`)
44
+ * `tla check-java`: Verify Java installation and compatibility.
45
+ * `tla tools list`: List available and installed toolset versions (`tla2tools.jar`).
46
+ * `tla tools install [vX.Y.Z]`: Download and install a toolset version.
47
+ * `tla tools pin <version>`: Set default toolset version for TLC runs.
46
48
 
47
- ## Installation
49
+ ### 2. Model Checking (`tla tlc`)
50
+ * `tla tlc <spec>`: Execute TLC model checker on a specification.
51
+ * `tla tlc <spec> --show-command`: Inspect exact JVM command before execution.
48
52
 
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)
53
+ ### 3. Custom Java Modules (`tla modules`)
54
+ * `tla modules add <path>`: Compile Java operator overrides and register module in CLI cache.
55
+ * `tla modules list`: Display cached custom modules.
56
+ * `tla modules remove <name>`: Remove custom module from cache.
57
+
58
+ For complete, runnable specifications and Java module examples, see [docs/examples](docs/examples/).
59
+
60
+ ## Quick Installation
61
+
62
+ ### Via `uv tool`
63
+ ```bash
64
+ uv tool install tlaplus-cli
65
+ ```
66
+
67
+ ### Via Nix Flakes
68
+ ```bash
69
+ nix profile install github:nikolskiy/tlaplus-cli
70
+ ```
@@ -54,11 +54,13 @@ src/tlaplus_cli/tlc/components.py
54
54
  src/tlaplus_cli/tlc/formatter.py
55
55
  src/tlaplus_cli/tlc/models.py
56
56
  src/tlaplus_cli/tlc/parser.py
57
+ src/tlaplus_cli/tlc/run_cache.py
57
58
  src/tlaplus_cli/tlc/runner.py
58
59
  src/tlaplus_cli/tlc/sany.py
59
60
  src/tlaplus_cli/tlc/values.py
60
61
  src/tlaplus_cli/versioning/__init__.py
61
62
  src/tlaplus_cli/versioning/api.py
63
+ src/tlaplus_cli/versioning/app_version.py
62
64
  src/tlaplus_cli/versioning/downloader.py
63
65
  src/tlaplus_cli/versioning/metadata.py
64
66
  src/tlaplus_cli/versioning/paths.py
@@ -1,21 +0,0 @@
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)
File without changes
File without changes
File without changes