gridcalc 0.1.1__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.
- gridcalc/__init__.py +3 -0
- gridcalc/__main__.py +11 -0
- gridcalc/config.py +109 -0
- gridcalc/engine.py +908 -0
- gridcalc/libs/__init__.py +24 -0
- gridcalc/libs/xlsx.py +430 -0
- gridcalc/sandbox.py +301 -0
- gridcalc/tui.py +1609 -0
- gridcalc-0.1.1.dist-info/METADATA +290 -0
- gridcalc-0.1.1.dist-info/RECORD +13 -0
- gridcalc-0.1.1.dist-info/WHEEL +4 -0
- gridcalc-0.1.1.dist-info/entry_points.txt +3 -0
- gridcalc-0.1.1.dist-info/licenses/LICENSE +21 -0
gridcalc/__init__.py
ADDED
gridcalc/__main__.py
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import sys
|
|
3
|
+
|
|
4
|
+
# Support both `python -m gridcalc` (relative import) and direct execution
|
|
5
|
+
if __package__:
|
|
6
|
+
from .tui import main
|
|
7
|
+
else:
|
|
8
|
+
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
|
9
|
+
from gridcalc.tui import main
|
|
10
|
+
|
|
11
|
+
main()
|
gridcalc/config.py
ADDED
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
"""Configuration file loading for gridcalc.
|
|
2
|
+
|
|
3
|
+
Lookup order (first found wins, CWD overrides user config):
|
|
4
|
+
1. ./gridcalc.toml
|
|
5
|
+
2. $XDG_CONFIG_HOME/gridcalc/gridcalc.toml (default: ~/.config/gridcalc/gridcalc.toml)
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
import os
|
|
11
|
+
from dataclasses import dataclass, field
|
|
12
|
+
from pathlib import Path
|
|
13
|
+
from typing import Any
|
|
14
|
+
|
|
15
|
+
try:
|
|
16
|
+
import tomllib # type: ignore[import-not-found]
|
|
17
|
+
except ModuleNotFoundError:
|
|
18
|
+
import tomli as tomllib # type: ignore[import-not-found]
|
|
19
|
+
|
|
20
|
+
CONFIG_FILENAME = "gridcalc.toml"
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
@dataclass
|
|
24
|
+
class Config:
|
|
25
|
+
editor: str = ""
|
|
26
|
+
sandbox: bool = False
|
|
27
|
+
width: int = 0
|
|
28
|
+
format: str = ""
|
|
29
|
+
libs: list[str] = field(default_factory=list)
|
|
30
|
+
allowed_modules: list[str] = field(default_factory=list)
|
|
31
|
+
config_path: str = ""
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def user_config_dir() -> Path:
|
|
35
|
+
"""Return the user-level config directory (XDG_CONFIG_HOME/gridcalc)."""
|
|
36
|
+
xdg = os.environ.get("XDG_CONFIG_HOME")
|
|
37
|
+
if xdg:
|
|
38
|
+
return Path(xdg) / "gridcalc"
|
|
39
|
+
return Path.home() / ".config" / "gridcalc"
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def find_config() -> Path | None:
|
|
43
|
+
"""Find the first gridcalc.toml in the lookup order."""
|
|
44
|
+
cwd_config = Path.cwd() / CONFIG_FILENAME
|
|
45
|
+
if cwd_config.is_file():
|
|
46
|
+
return cwd_config
|
|
47
|
+
|
|
48
|
+
user_config = user_config_dir() / CONFIG_FILENAME
|
|
49
|
+
if user_config.is_file():
|
|
50
|
+
return user_config
|
|
51
|
+
|
|
52
|
+
return None
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def _parse_config(data: dict[str, Any]) -> Config:
|
|
56
|
+
"""Parse a TOML dict into a Config, ignoring unknown keys."""
|
|
57
|
+
cfg = Config()
|
|
58
|
+
|
|
59
|
+
if "editor" in data and isinstance(data["editor"], str):
|
|
60
|
+
cfg.editor = data["editor"]
|
|
61
|
+
|
|
62
|
+
if "sandbox" in data and isinstance(data["sandbox"], bool):
|
|
63
|
+
cfg.sandbox = data["sandbox"]
|
|
64
|
+
|
|
65
|
+
if "width" in data:
|
|
66
|
+
try:
|
|
67
|
+
w = int(data["width"])
|
|
68
|
+
if 4 <= w <= 40:
|
|
69
|
+
cfg.width = w
|
|
70
|
+
except (ValueError, TypeError):
|
|
71
|
+
pass
|
|
72
|
+
|
|
73
|
+
if "format" in data and isinstance(data["format"], str) and len(data["format"]) == 1:
|
|
74
|
+
cfg.format = data["format"]
|
|
75
|
+
|
|
76
|
+
if "libs" in data and isinstance(data["libs"], list):
|
|
77
|
+
cfg.libs = [str(lib) for lib in data["libs"]]
|
|
78
|
+
|
|
79
|
+
if "allowed_modules" in data and isinstance(data["allowed_modules"], list):
|
|
80
|
+
cfg.allowed_modules = [str(m) for m in data["allowed_modules"]]
|
|
81
|
+
|
|
82
|
+
return cfg
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
def load_config(path: Path | str | None = None) -> Config:
|
|
86
|
+
"""Load configuration from a TOML file.
|
|
87
|
+
|
|
88
|
+
If path is None, uses the standard lookup order.
|
|
89
|
+
Returns a default Config if no file is found or parsing fails.
|
|
90
|
+
"""
|
|
91
|
+
if path is None:
|
|
92
|
+
resolved = find_config()
|
|
93
|
+
else:
|
|
94
|
+
resolved = Path(path) if not isinstance(path, Path) else path
|
|
95
|
+
if not resolved.is_file():
|
|
96
|
+
return Config()
|
|
97
|
+
|
|
98
|
+
if resolved is None:
|
|
99
|
+
return Config()
|
|
100
|
+
|
|
101
|
+
try:
|
|
102
|
+
with open(resolved, "rb") as f:
|
|
103
|
+
data = tomllib.load(f)
|
|
104
|
+
except (OSError, tomllib.TOMLDecodeError):
|
|
105
|
+
return Config()
|
|
106
|
+
|
|
107
|
+
cfg = _parse_config(data)
|
|
108
|
+
cfg.config_path = str(resolved)
|
|
109
|
+
return cfg
|