batplot 1.8.1__py3-none-any.whl → 1.8.3__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.

Potentially problematic release.


This version of batplot might be problematic. Click here for more details.

Files changed (42) hide show
  1. batplot/__init__.py +1 -1
  2. batplot/args.py +2 -0
  3. batplot/batch.py +23 -0
  4. batplot/batplot.py +101 -12
  5. batplot/cpc_interactive.py +25 -3
  6. batplot/electrochem_interactive.py +20 -4
  7. batplot/interactive.py +19 -15
  8. batplot/modes.py +12 -12
  9. batplot/operando_ec_interactive.py +4 -4
  10. batplot/session.py +218 -0
  11. batplot/style.py +21 -2
  12. batplot/version_check.py +1 -1
  13. {batplot-1.8.1.dist-info → batplot-1.8.3.dist-info}/METADATA +1 -1
  14. batplot-1.8.3.dist-info/RECORD +75 -0
  15. {batplot-1.8.1.dist-info → batplot-1.8.3.dist-info}/top_level.txt +1 -0
  16. batplot_backup_20251221_101150/__init__.py +5 -0
  17. batplot_backup_20251221_101150/args.py +625 -0
  18. batplot_backup_20251221_101150/batch.py +1176 -0
  19. batplot_backup_20251221_101150/batplot.py +3589 -0
  20. batplot_backup_20251221_101150/cif.py +823 -0
  21. batplot_backup_20251221_101150/cli.py +149 -0
  22. batplot_backup_20251221_101150/color_utils.py +547 -0
  23. batplot_backup_20251221_101150/config.py +198 -0
  24. batplot_backup_20251221_101150/converters.py +204 -0
  25. batplot_backup_20251221_101150/cpc_interactive.py +4409 -0
  26. batplot_backup_20251221_101150/electrochem_interactive.py +4520 -0
  27. batplot_backup_20251221_101150/interactive.py +3894 -0
  28. batplot_backup_20251221_101150/manual.py +323 -0
  29. batplot_backup_20251221_101150/modes.py +799 -0
  30. batplot_backup_20251221_101150/operando.py +603 -0
  31. batplot_backup_20251221_101150/operando_ec_interactive.py +5487 -0
  32. batplot_backup_20251221_101150/plotting.py +228 -0
  33. batplot_backup_20251221_101150/readers.py +2607 -0
  34. batplot_backup_20251221_101150/session.py +2951 -0
  35. batplot_backup_20251221_101150/style.py +1441 -0
  36. batplot_backup_20251221_101150/ui.py +790 -0
  37. batplot_backup_20251221_101150/utils.py +1046 -0
  38. batplot_backup_20251221_101150/version_check.py +253 -0
  39. batplot-1.8.1.dist-info/RECORD +0 -52
  40. {batplot-1.8.1.dist-info → batplot-1.8.3.dist-info}/WHEEL +0 -0
  41. {batplot-1.8.1.dist-info → batplot-1.8.3.dist-info}/entry_points.txt +0 -0
  42. {batplot-1.8.1.dist-info → batplot-1.8.3.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,323 @@
1
+ """Manual utilities for batplot.
2
+
3
+ Instead of generating a PDF, this module exposes the packaged Markdown
4
+ manual as a plain text file and opens it in the user's preferred editor.
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ from pathlib import Path
10
+ from typing import List
11
+
12
+ import hashlib
13
+ import os
14
+ import shlex
15
+ import subprocess
16
+ import sys
17
+ import tempfile
18
+
19
+ try: # Python 3.9+ exposes .files API directly
20
+ from importlib import resources as importlib_resources # type: ignore[attr-defined]
21
+ except ImportError: # pragma: no cover
22
+ import importlib_resources # type: ignore
23
+
24
+
25
+ MANUAL_RESOURCE = ("batplot.data", "USER_MANUAL.md")
26
+ TEXT_NAME = "batplot_manual.txt"
27
+ HASH_NAME = "batplot_manual.sha256"
28
+ MANUAL_RENDER_VERSION = "4"
29
+
30
+
31
+ def _manual_text() -> str:
32
+ """
33
+ Load the Markdown manual bundled with the package.
34
+
35
+ HOW IT WORKS:
36
+ ------------
37
+ This function tries multiple methods to find and load the USER_MANUAL.md file:
38
+
39
+ 1. **Modern method (Python 3.9+)**: Uses importlib.resources.files()
40
+ - This is the recommended way to access package data files
41
+ - Works with both regular packages and namespace packages
42
+ - Handles zipped packages (when installed as .egg or .whl)
43
+
44
+ 2. **Fallback method (older Python)**: Uses importlib.resources.open_text()
45
+ - Compatible with Python 3.7+
46
+ - Works for most package structures
47
+
48
+ 3. **Local file fallback**: Tries to read from batplot/data/ directory
49
+ - Used during development (when package isn't installed)
50
+ - Path(__file__) gets the directory containing this file
51
+ - .parent / "data" goes up one level and into data/ folder
52
+
53
+ WHY MULTIPLE METHODS?
54
+ --------------------
55
+ Different Python versions and installation methods require different approaches.
56
+ We try the most modern method first, then fall back to older methods.
57
+ This ensures the manual can be loaded regardless of Python version or
58
+ how batplot was installed (pip, editable install, or running from source).
59
+
60
+ Returns:
61
+ String containing the full manual text (Markdown format)
62
+
63
+ Raises:
64
+ FileNotFoundError: If manual cannot be found by any method
65
+ """
66
+ package, resource = MANUAL_RESOURCE # Unpack tuple: ("batplot.data", "USER_MANUAL.md")
67
+
68
+ # METHOD 1: Try modern importlib.resources.files() API (Python 3.9+)
69
+ try:
70
+ # .files() gets a Traversable object representing the package directory
71
+ # .joinpath() navigates to the resource file
72
+ # .read_text() reads the file as a string
73
+ data = importlib_resources.files(package).joinpath(resource).read_text(encoding="utf-8") # type: ignore[attr-defined]
74
+ return data
75
+ except Exception:
76
+ # Method 1 failed, try next method
77
+ pass
78
+
79
+ # METHOD 2: Try older importlib.resources.open_text() API (Python 3.7+)
80
+ try:
81
+ # open_text() opens the file as a text file (handles encoding)
82
+ # 'with' statement ensures file is closed automatically
83
+ with importlib_resources.open_text(package, resource, encoding="utf-8") as handle: # type: ignore[attr-defined]
84
+ return handle.read() # Read entire file contents
85
+ except Exception:
86
+ # Method 2 failed, try next method
87
+ pass
88
+
89
+ # METHOD 3: Fallback to local file system (for development)
90
+ # Path(__file__) = path to this file (manual.py)
91
+ # .resolve() = convert to absolute path
92
+ # .parent = go up one directory (from batplot/ to batplot_script/)
93
+ # / "data" / resource = navigate to batplot/data/USER_MANUAL.md
94
+ local_path = Path(__file__).resolve().parent / "data" / resource
95
+ if local_path.exists():
96
+ return local_path.read_text(encoding="utf-8")
97
+
98
+ # All methods failed - raise error with helpful message
99
+ raise FileNotFoundError(f"Unable to locate manual resource '{resource}'. Tried package '{package}' and {local_path}")
100
+
101
+
102
+ def _manual_hash(text: str) -> str:
103
+ """
104
+ Calculate SHA256 hash of manual content for cache validation.
105
+
106
+ HOW IT WORKS:
107
+ ------------
108
+ This function creates a "fingerprint" of the manual content. If the content
109
+ changes, the hash changes. We use this to detect when the manual has been
110
+ updated and needs to be regenerated.
111
+
112
+ WHY INCLUDE VERSION?
113
+ -------------------
114
+ We include MANUAL_RENDER_VERSION in the hash. This means if we change how
115
+ the manual is formatted (even if content is the same), the hash changes
116
+ and cache is invalidated. This ensures users always get the latest format.
117
+
118
+ HASH ALGORITHM:
119
+ --------------
120
+ SHA256 produces a 256-bit (32-byte) hash. It's:
121
+ - Deterministic: Same input always produces same hash
122
+ - One-way: Can't reverse hash to get original text
123
+ - Collision-resistant: Very unlikely two different texts produce same hash
124
+
125
+ Args:
126
+ text: Manual content (Markdown text)
127
+
128
+ Returns:
129
+ 64-character hexadecimal string (SHA256 hash)
130
+ Example: "a3f5b2c1d4e6f7a8b9c0d1e2f3a4b5c6d7e8f9a0b1c2d3e4f5a6b7c8d9e0f1a2"
131
+ """
132
+ # Create SHA256 hash object
133
+ digest = hashlib.sha256()
134
+ # Add version to hash (ensures cache invalidates when format changes)
135
+ digest.update(MANUAL_RENDER_VERSION.encode("utf-8"))
136
+ # Add manual text to hash (ensures cache invalidates when content changes)
137
+ digest.update(text.encode("utf-8"))
138
+ # Return hash as hexadecimal string (64 characters)
139
+ return digest.hexdigest()
140
+
141
+
142
+ def _cache_dir() -> Path:
143
+ """
144
+ Get the directory where manual cache files are stored.
145
+
146
+ HOW IT WORKS:
147
+ ------------
148
+ This function determines where to store cached manual files. It tries multiple
149
+ locations in order of preference:
150
+
151
+ 1. **Environment variable override**: BATPLOT_CACHE_DIR (user-specified)
152
+ 2. **Platform-specific standard locations**:
153
+ - Windows: %LOCALAPPDATA%\\batplot (usually C:\\Users\\username\\AppData\\Local\\batplot)
154
+ - Linux/macOS: ~/.cache/batplot (or $XDG_CACHE_HOME/batplot if set)
155
+ 3. **Temporary directory fallback**: If standard locations aren't writable
156
+
157
+ WHY CACHE?
158
+ ---------
159
+ The manual is generated from Markdown and cached to avoid regenerating it
160
+ on every run. This speeds up the 'batplot -m' command.
161
+
162
+ WHY PLATFORM-SPECIFIC?
163
+ ----------------------
164
+ Different operating systems have different conventions for where application
165
+ data should be stored. We follow platform conventions for better integration.
166
+
167
+ Returns:
168
+ Path object pointing to cache directory (always writable)
169
+ """
170
+ # Check for user override via environment variable
171
+ env_override = os.environ.get("BATPLOT_CACHE_DIR")
172
+ if env_override:
173
+ # User specified custom location - use it
174
+ # expanduser() converts ~ to actual home directory path
175
+ target = Path(env_override).expanduser()
176
+ elif os.name == "nt":
177
+ # Windows: Use LOCALAPPDATA (standard location for app data)
178
+ # Fallback to AppData/Local if environment variable not set
179
+ base = Path(os.environ.get("LOCALAPPDATA", Path.home() / "AppData" / "Local"))
180
+ target = base / "batplot"
181
+ else:
182
+ # Linux/macOS: Use XDG_CACHE_HOME (standard location) or ~/.cache
183
+ # XDG_CACHE_HOME is a Linux standard, ~/.cache is the default
184
+ base = Path(os.environ.get("XDG_CACHE_HOME", Path.home() / ".cache"))
185
+ target = base / "batplot"
186
+
187
+ # Try to create and use the preferred directory
188
+ try:
189
+ # mkdir(parents=True, exist_ok=True) creates directory and all parent directories
190
+ # parents=True: Create parent dirs if they don't exist
191
+ # exist_ok=True: Don't raise error if directory already exists
192
+ target.mkdir(parents=True, exist_ok=True)
193
+ # Check if directory is writable (permissions check)
194
+ if os.access(target, os.W_OK):
195
+ return target
196
+ except OSError:
197
+ # Creation failed or not writable, fall back to temp directory
198
+ pass
199
+
200
+ # Fallback: Use system temporary directory
201
+ # This is always writable, but files may be cleaned up by system
202
+ fallback = Path(tempfile.gettempdir()) / "batplot-manual"
203
+ fallback.mkdir(parents=True, exist_ok=True)
204
+ return fallback
205
+
206
+
207
+ def _resolve_editor_command() -> List[str]:
208
+ """
209
+ Determine which text editor command to use for opening the manual.
210
+
211
+ HOW IT WORKS:
212
+ ------------
213
+ This function checks environment variables and platform defaults to find
214
+ the best text editor to use. It follows this priority order:
215
+
216
+ 1. BATPLOT_MANUAL_EDITOR: User's explicit choice for batplot
217
+ 2. VISUAL: Standard environment variable (preferred for GUI editors)
218
+ 3. EDITOR: Standard environment variable (fallback, often terminal editor)
219
+ 4. Platform defaults:
220
+ - Windows: notepad.exe (built-in text editor)
221
+ - macOS: open (opens with default app for .txt files)
222
+ - Linux: xdg-open (opens with default app for .txt files)
223
+
224
+ WHY SHLEX.SPLIT()?
225
+ -----------------
226
+ Environment variables might contain commands with arguments:
227
+ Example: EDITOR="code --wait" or EDITOR="vim -R"
228
+ shlex.split() properly splits the command string into a list:
229
+ "code --wait" → ["code", "--wait"]
230
+ This prevents errors when the command has spaces or arguments.
231
+
232
+ Returns:
233
+ List of command and arguments, ready for subprocess.Popen()
234
+ Example: ["code", "--wait"] or ["notepad.exe"]
235
+ """
236
+ # Check environment variables in priority order
237
+ for var in ("BATPLOT_MANUAL_EDITOR", "VISUAL", "EDITOR"):
238
+ cmd = os.environ.get(var)
239
+ if cmd:
240
+ # Split command string into list (handles arguments properly)
241
+ # Example: "code --wait" → ["code", "--wait"]
242
+ return shlex.split(cmd)
243
+
244
+ # No environment variable set - use platform defaults
245
+ if sys.platform.startswith("win"):
246
+ # Windows: Use built-in Notepad
247
+ return ["notepad.exe"]
248
+ if sys.platform.startswith("darwin"):
249
+ # macOS: Use 'open' command (opens with default app)
250
+ return ["open"]
251
+ # Linux/other: Use xdg-open (opens with default app)
252
+ return ["xdg-open"]
253
+
254
+
255
+ def _open_editor(path: Path) -> None:
256
+ cmd = _resolve_editor_command()
257
+ try:
258
+ process_args = cmd + [str(path)]
259
+ subprocess.Popen(process_args)
260
+ except Exception as exc: # pragma: no cover - best effort
261
+ print(f"Manual saved to {path} (auto-open failed: {exc})")
262
+
263
+
264
+ def show_manual(open_viewer: bool = True) -> str:
265
+ """
266
+ Ensure a plain-text manual exists and optionally open it.
267
+
268
+ HOW IT WORKS:
269
+ ------------
270
+ This function manages the manual cache and opens the manual for viewing:
271
+
272
+ 1. **Load manual text**: Read USER_MANUAL.md from package
273
+ 2. **Check cache**: See if we already have a cached .txt file
274
+ 3. **Validate cache**: Compare hash of current content vs cached hash
275
+ 4. **Regenerate if needed**: If content changed, create new .txt file
276
+ 5. **Open editor**: Launch user's preferred text editor (if requested)
277
+
278
+ CACHING STRATEGY:
279
+ ---------------
280
+ We cache the manual to avoid regenerating it on every run. The cache is
281
+ invalidated when:
282
+ - Manual content changes (detected by hash comparison)
283
+ - Manual format version changes (MANUAL_RENDER_VERSION)
284
+
285
+ This means users get fresh manual when it's updated, but don't regenerate
286
+ unnecessarily when nothing has changed.
287
+
288
+ Args:
289
+ open_viewer: If True, automatically open manual in text editor.
290
+ If False, just ensure file exists but don't open it.
291
+
292
+ Returns:
293
+ Path to the manual .txt file (as string)
294
+ Example: "/Users/username/.cache/batplot/batplot_manual.txt"
295
+ """
296
+ text = _manual_text()
297
+ target_dir = _cache_dir()
298
+ txt_path = target_dir / TEXT_NAME
299
+ hash_path = target_dir / HASH_NAME
300
+ content_hash = _manual_hash(text)
301
+ cached_hash = hash_path.read_text(encoding="utf-8").strip() if hash_path.exists() else ""
302
+
303
+ if (not txt_path.exists()) or (cached_hash != content_hash):
304
+ txt_path.write_text(text, encoding="utf-8")
305
+ hash_path.write_text(content_hash, encoding="utf-8")
306
+
307
+ if open_viewer:
308
+ _open_editor(txt_path)
309
+ return str(txt_path)
310
+
311
+
312
+ def main(argv: list[str] | None = None) -> int:
313
+ path = show_manual(open_viewer=True)
314
+ print(f"batplot manual ready at {path}")
315
+ return 0
316
+
317
+
318
+ __all__ = ["show_manual", "main"]
319
+
320
+
321
+ if __name__ == "__main__": # pragma: no cover
322
+ sys.exit(main())
323
+