tlaup 0.8.2__py3-none-any.whl

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 (64) hide show
  1. tlaup/__init__.py +6 -0
  2. tlaup/cache/__init__.py +0 -0
  3. tlaup/cache/github.py +28 -0
  4. tlaup/cli.py +62 -0
  5. tlaup/cmd/__init__.py +0 -0
  6. tlaup/cmd/check_java.py +19 -0
  7. tlaup/cmd/config/__init__.py +6 -0
  8. tlaup/cmd/config/edit.py +34 -0
  9. tlaup/cmd/config/list.py +16 -0
  10. tlaup/cmd/fetch_cache/__init__.py +5 -0
  11. tlaup/cmd/fetch_cache/clear.py +11 -0
  12. tlaup/cmd/modules/__init__.py +16 -0
  13. tlaup/cmd/modules/add.py +140 -0
  14. tlaup/cmd/modules/list.py +45 -0
  15. tlaup/cmd/modules/path.py +68 -0
  16. tlaup/cmd/modules/remove.py +22 -0
  17. tlaup/cmd/tlc.py +291 -0
  18. tlaup/cmd/tools/__init__.py +8 -0
  19. tlaup/cmd/tools/dir.py +16 -0
  20. tlaup/cmd/tools/install.py +79 -0
  21. tlaup/cmd/tools/list.py +81 -0
  22. tlaup/cmd/tools/meta/__init__.py +5 -0
  23. tlaup/cmd/tools/meta/sync.py +34 -0
  24. tlaup/cmd/tools/path.py +33 -0
  25. tlaup/cmd/tools/pin.py +31 -0
  26. tlaup/cmd/tools/uninstall.py +104 -0
  27. tlaup/cmd/tools/upgrade.py +76 -0
  28. tlaup/config/__init__.py +0 -0
  29. tlaup/config/loader.py +87 -0
  30. tlaup/config/schema.py +52 -0
  31. tlaup/java/__init__.py +7 -0
  32. tlaup/java/classpath.py +197 -0
  33. tlaup/java/inspector.py +86 -0
  34. tlaup/project/__init__.py +3 -0
  35. tlaup/project/core.py +31 -0
  36. tlaup/py.typed +0 -0
  37. tlaup/resources/__init__.py +1 -0
  38. tlaup/resources/default_config.yaml +28 -0
  39. tlaup/tlc/__init__.py +0 -0
  40. tlaup/tlc/codes.py +314 -0
  41. tlaup/tlc/compiler.py +12 -0
  42. tlaup/tlc/components.py +287 -0
  43. tlaup/tlc/formatter.py +348 -0
  44. tlaup/tlc/models.py +132 -0
  45. tlaup/tlc/parser.py +258 -0
  46. tlaup/tlc/run_cache.py +61 -0
  47. tlaup/tlc/runner.py +285 -0
  48. tlaup/tlc/sany.py +94 -0
  49. tlaup/tlc/values.py +186 -0
  50. tlaup/ui.py +21 -0
  51. tlaup/versioning/__init__.py +61 -0
  52. tlaup/versioning/api.py +113 -0
  53. tlaup/versioning/app_version.py +62 -0
  54. tlaup/versioning/downloader.py +90 -0
  55. tlaup/versioning/metadata.py +92 -0
  56. tlaup/versioning/paths.py +97 -0
  57. tlaup/versioning/resolver.py +92 -0
  58. tlaup/versioning/schema.py +27 -0
  59. tlaup-0.8.2.dist-info/METADATA +73 -0
  60. tlaup-0.8.2.dist-info/RECORD +64 -0
  61. tlaup-0.8.2.dist-info/WHEEL +5 -0
  62. tlaup-0.8.2.dist-info/entry_points.txt +2 -0
  63. tlaup-0.8.2.dist-info/licenses/LICENSE +21 -0
  64. tlaup-0.8.2.dist-info/top_level.txt +1 -0
tlaup/__init__.py ADDED
@@ -0,0 +1,6 @@
1
+ import truststore
2
+
3
+ # Inject truststore as early as possible so that all standard library
4
+ # and third-party tools relying on `ssl` (such as `requests` and `urllib3`)
5
+ # utilize the native OS trust store.
6
+ truststore.inject_into_ssl()
File without changes
tlaup/cache/github.py ADDED
@@ -0,0 +1,28 @@
1
+ import json
2
+ from dataclasses import asdict
3
+ from pathlib import Path
4
+
5
+ from tlaup.ui import warn
6
+ from tlaup.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}")
tlaup/cli.py ADDED
@@ -0,0 +1,62 @@
1
+ """TLA+ CLI tool - entry point."""
2
+
3
+ import importlib.metadata
4
+
5
+ import typer
6
+
7
+ from tlaup.cmd.check_java import check_java
8
+ from tlaup.cmd.config import app as config_app
9
+ from tlaup.cmd.fetch_cache import app as fetch_cache_app
10
+ from tlaup.cmd.modules import app as modules_app
11
+ from tlaup.cmd.tlc import tlc as run_tlc_cmd
12
+ from tlaup.cmd.tools import app as tools_app
13
+ from tlaup.config.loader import load_config
14
+ from tlaup.versioning import get_app_version
15
+
16
+ app = typer.Typer(
17
+ name="tlaup",
18
+ help="TLA+ tools: download TLA+ toolset distribution, compile custom modules, run model checker.",
19
+ no_args_is_help=True,
20
+ add_completion=False,
21
+ )
22
+
23
+
24
+ def version_callback(value: bool) -> None:
25
+ if value:
26
+ meta = importlib.metadata.metadata("tlaup")
27
+ app_ver = get_app_version(base_version=meta["Version"])
28
+ typer.echo(f"{meta['Name']} v{app_ver}")
29
+ typer.echo(meta["Summary"])
30
+ raise typer.Exit()
31
+
32
+
33
+ @app.callback()
34
+ def root(
35
+ _: bool = typer.Option(
36
+ None,
37
+ "--version",
38
+ "-v",
39
+ help="Show the application's version and exit.",
40
+ callback=version_callback,
41
+ is_eager=True,
42
+ ),
43
+ ) -> None:
44
+ """TLA+ CLI tool."""
45
+ # Load config early to trigger first-run copy
46
+ load_config()
47
+
48
+
49
+ # --- Subcommands ---
50
+
51
+ app.add_typer(modules_app, name="modules")
52
+ app.add_typer(tools_app, name="tools")
53
+ app.add_typer(fetch_cache_app, name="fetch-cache")
54
+ app.add_typer(config_app, name="config")
55
+
56
+ app.command(name="tlc")(run_tlc_cmd)
57
+ app.command(name="check-java")(check_java)
58
+
59
+
60
+ def main() -> None:
61
+ """Entry point for [project.scripts]."""
62
+ app()
tlaup/cmd/__init__.py ADDED
File without changes
@@ -0,0 +1,19 @@
1
+ import typer
2
+
3
+ from tlaup.config.loader import load_config
4
+ from tlaup.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 tlaup.cmd.config import app
8
+ from tlaup.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 tlaup.cmd.config import app
4
+ from tlaup.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 tlaup.cmd.fetch_cache import app
4
+ from tlaup.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,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,140 @@
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 tlaup.config.loader import load_config
12
+ from tlaup.java.classpath import ClasspathResolver, ResolveMode
13
+ from tlaup.tlc.compiler import get_tlc_jar_path
14
+ from tlaup.tlc.run_cache import clear_tlc_run_cache
15
+ from tlaup.versioning.paths import get_modules_dir
16
+
17
+
18
+ def find_override_classes(java_files: list[Path]) -> list[str]:
19
+ """Find all class names implementing ITLCOverrides in Java files."""
20
+ override_classes = []
21
+ package_pattern = re.compile(r"package\s+([a-zA-Z0-9_.]+)\s*;")
22
+ class_pattern = re.compile(
23
+ r"\bclass\s+([a-zA-Z0-9_]+)\b[^{]*\bimplements\b[^{]*\b(tlc2\.overrides\.)?ITLCOverrides\b"
24
+ )
25
+
26
+ for jf in java_files:
27
+ try:
28
+ content = jf.read_text(encoding="utf-8", errors="ignore")
29
+ except OSError:
30
+ continue
31
+
32
+ # Strip comments to prevent false matches
33
+ content_no_comments = re.sub(r"//.*", "", content)
34
+ content_no_comments = re.sub(r"/\*.*?\*/", "", content_no_comments, flags=re.DOTALL)
35
+
36
+ package_match = package_pattern.search(content_no_comments)
37
+ package_name = package_match.group(1) if package_match else ""
38
+
39
+ for match in class_pattern.finditer(content_no_comments):
40
+ class_name = match.group(1)
41
+ full_class_name = f"{package_name}.{class_name}" if package_name else class_name
42
+ override_classes.append(full_class_name)
43
+
44
+ return override_classes
45
+
46
+
47
+ def add_module( # noqa: PLR0912, PLR0915
48
+ path: str = typer.Argument(..., help="Path to the module directory to add."),
49
+ ) -> None:
50
+ """Compile custom Java overrides and add/update the module in the cache."""
51
+ source_dir = Path(path).resolve()
52
+ if not source_dir.is_dir():
53
+ typer.echo(f"Error: Path does not exist or is not a directory: {source_dir}", err=True)
54
+ raise typer.Exit(1)
55
+
56
+ config = load_config()
57
+ module_name = source_dir.name
58
+ target_dir = get_modules_dir() / module_name
59
+
60
+ # Overwrite if exists
61
+ if target_dir.exists():
62
+ shutil.rmtree(target_dir)
63
+ target_dir.mkdir(parents=True, exist_ok=True)
64
+
65
+ # 1. Copy directories (lib, modules, tlc2, and test/tests)
66
+ def copy_subfolder(subname: str) -> None:
67
+ src = source_dir / subname
68
+ if src.is_dir():
69
+ shutil.copytree(src, target_dir / subname)
70
+
71
+ typer.echo("Copying subfolders (lib, modules, test, tlc2)...")
72
+ copy_subfolder("lib")
73
+ copy_subfolder("modules")
74
+ copy_subfolder("tlc2")
75
+
76
+ if (source_dir / "test").is_dir():
77
+ copy_subfolder("test")
78
+ elif (source_dir / "tests").is_dir():
79
+ copy_subfolder("tests")
80
+
81
+ # 2. Compile Java overrides in cached version if tlc2/overrides exists
82
+ java_files = []
83
+ for candidate in [target_dir / "modules" / "tlc2" / "overrides", target_dir / "tlc2" / "overrides"]:
84
+ if candidate.is_dir():
85
+ java_files.extend(list(candidate.rglob("*.java")))
86
+
87
+ if java_files:
88
+ # Build classpath including tool_jar and local libs from the cached folder
89
+ cached_lib = target_dir / "lib"
90
+ extra_libs = []
91
+ if cached_lib.is_dir():
92
+ extra_libs.extend([str(j.absolute()) for j in cached_lib.glob("*.jar")])
93
+
94
+ tool_jar = get_tlc_jar_path()
95
+ if not tool_jar.exists():
96
+ typer.echo("Error: tla2tools.jar not found. Run 'tlaup tools install' first.", err=True)
97
+ shutil.rmtree(target_dir, ignore_errors=True)
98
+ raise typer.Exit(1)
99
+
100
+ resolver = ClasspathResolver(config, project_root=None, tool_jar=tool_jar, extra_libs=extra_libs)
101
+ classpath = os.pathsep.join(resolver.resolve(ResolveMode.COMPILE))
102
+
103
+ classes_dir = target_dir / "classes"
104
+ classes_dir.mkdir(parents=True, exist_ok=True)
105
+
106
+ cmd = ["javac", "-cp", classpath, "-d", str(classes_dir), *[str(f) for f in java_files]]
107
+ typer.echo("Compiling Java files in cached tlc2/overrides...")
108
+ try:
109
+ subprocess.run(cmd, check=True, capture_output=True, text=True)
110
+ except FileNotFoundError as err:
111
+ typer.echo("Error: 'javac' not found. Ensure JDK is installed.", err=True)
112
+ shutil.rmtree(target_dir, ignore_errors=True)
113
+ raise typer.Exit(1) from err
114
+ except subprocess.CalledProcessError as err:
115
+ typer.echo("Compilation failed!", err=True)
116
+ typer.echo(err.stdout, err=True)
117
+ typer.echo(err.stderr, err=True)
118
+ shutil.rmtree(target_dir, ignore_errors=True)
119
+ raise typer.Exit(1) from err
120
+
121
+ # Write service file
122
+ meta_inf = classes_dir / "META-INF" / "services"
123
+ meta_inf.mkdir(parents=True, exist_ok=True)
124
+ service_file = meta_inf / "tlc2.overrides.ITLCOverrides"
125
+
126
+ override_classes = find_override_classes(java_files)
127
+ if not override_classes:
128
+ override_classes = [config.tlc.overrides_class]
129
+
130
+ with service_file.open("w") as f:
131
+ for cls in override_classes:
132
+ f.write(f"{cls}\n")
133
+
134
+ # 3. Create metadata.json
135
+ metadata = {"name": module_name, "built_at": datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")}
136
+ with (target_dir / "metadata.json").open("w") as f:
137
+ json.dump(metadata, f, indent=2)
138
+
139
+ clear_tlc_run_cache()
140
+ 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 tlaup.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 tlaup.config.loader import load_config
7
+ from tlaup.java.classpath import ClasspathResolver, ResolveMode, SubdirType
8
+ from tlaup.tlc.compiler import get_tlc_jar_path
9
+ from tlaup.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,22 @@
1
+ import shutil
2
+
3
+ import typer
4
+
5
+ from tlaup.tlc.run_cache import clear_tlc_run_cache
6
+ from tlaup.versioning.paths import get_modules_dir
7
+
8
+
9
+ def remove_module(module_name: str = typer.Argument(..., help="Name of the module to remove from cache.")) -> None:
10
+ """Remove a custom module from the cache."""
11
+ target_dir = get_modules_dir() / module_name
12
+ if not target_dir.is_dir():
13
+ typer.echo(f"Error: Module '{module_name}' not found in cache.", err=True)
14
+ raise typer.Exit(1)
15
+
16
+ try:
17
+ shutil.rmtree(target_dir)
18
+ clear_tlc_run_cache()
19
+ typer.echo(f"Module '{module_name}' successfully deleted from cache.")
20
+ except OSError as e:
21
+ typer.echo(f"Error: Failed to delete module: {e}", err=True)
22
+ raise typer.Exit(1) from e