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 ADDED
@@ -0,0 +1,3 @@
1
+ """gridcalc -- a terminal spreadsheet."""
2
+
3
+ __version__ = "0.1.1"
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