batplot 1.8.40__tar.gz → 1.8.41__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 (135) hide show
  1. batplot-1.8.41/MANIFEST.in +3 -0
  2. {batplot-1.8.40/batplot.egg-info → batplot-1.8.41}/PKG-INFO +10 -8
  3. {batplot-1.8.40 → batplot-1.8.41}/README.md +6 -7
  4. batplot-1.8.41/batplot/__init__.py +74 -0
  5. {batplot-1.8.40 → batplot-1.8.41}/batplot/args.py +100 -85
  6. batplot-1.8.41/batplot/batplot.py +642 -0
  7. {batplot-1.8.40 → batplot-1.8.41}/batplot/canvas_interactive.py +7 -7
  8. {batplot-1.8.40 → batplot-1.8.41}/batplot/data/CHANGELOG.md +9 -0
  9. {batplot-1.8.40 → batplot-1.8.41}/batplot/dev_upgrade.py +350 -103
  10. batplot-1.8.41/batplot/ec_common.py +103 -0
  11. batplot-1.8.41/batplot/modes.py +37 -0
  12. batplot-1.8.41/batplot/plot_modes/__init__.py +1 -0
  13. batplot-1.8.41/batplot/plot_modes/common/__init__.py +1 -0
  14. batplot-1.8.41/batplot/plot_modes/common/axis_state.py +128 -0
  15. batplot-1.8.41/batplot/plot_modes/common/files.py +42 -0
  16. batplot-1.8.41/batplot/plot_modes/common/fonts.py +132 -0
  17. batplot-1.8.41/batplot/plot_modes/common/interactive_state.py +99 -0
  18. batplot-1.8.41/batplot/plot_modes/common/menu_rendering.py +84 -0
  19. batplot-1.8.41/batplot/plot_modes/common/menus.py +437 -0
  20. batplot-1.8.41/batplot/plot_modes/common/palettes.py +215 -0
  21. batplot-1.8.41/batplot/plot_modes/common/smoothing.py +38 -0
  22. batplot-1.8.41/batplot/plot_modes/common/sources.py +68 -0
  23. batplot-1.8.41/batplot/plot_modes/common/spines.py +758 -0
  24. batplot-1.8.41/batplot/plot_modes/common/terminal.py +128 -0
  25. batplot-1.8.41/batplot/plot_modes/common/title_offsets.py +65 -0
  26. batplot-1.8.41/batplot/plot_modes/cpc/__init__.py +5 -0
  27. batplot-1.8.41/batplot/plot_modes/cpc/actions.py +963 -0
  28. batplot-1.8.41/batplot/plot_modes/cpc/colors.py +363 -0
  29. batplot-1.8.41/batplot/plot_modes/cpc/interactive.py +2822 -0
  30. batplot-1.8.41/batplot/plot_modes/cpc/labels.py +257 -0
  31. batplot-1.8.41/batplot/plot_modes/cpc/legend.py +480 -0
  32. batplot-1.8.41/batplot/plot_modes/cpc/menu.py +74 -0
  33. batplot-1.8.41/batplot/plot_modes/cpc/routing.py +516 -0
  34. batplot-1.8.41/batplot/plot_modes/cpc/session.py +15 -0
  35. batplot-1.8.41/batplot/plot_modes/cpc/snapshots.py +119 -0
  36. batplot-1.8.41/batplot/plot_modes/electrochem/__init__.py +5 -0
  37. batplot-1.8.41/batplot/plot_modes/electrochem/actions.py +1246 -0
  38. batplot-1.8.41/batplot/plot_modes/electrochem/colors.py +722 -0
  39. batplot-1.8.41/batplot/plot_modes/electrochem/dqdv_2d.py +689 -0
  40. batplot-1.8.41/batplot/plot_modes/electrochem/export.py +65 -0
  41. batplot-1.8.41/batplot/plot_modes/electrochem/interactive.py +3220 -0
  42. batplot-1.8.41/batplot/plot_modes/electrochem/labels.py +224 -0
  43. batplot-1.8.41/batplot/plot_modes/electrochem/legend.py +269 -0
  44. batplot-1.8.41/batplot/plot_modes/electrochem/legend_order.py +56 -0
  45. batplot-1.8.41/batplot/plot_modes/electrochem/line_style.py +269 -0
  46. batplot-1.8.41/batplot/plot_modes/electrochem/menu.py +111 -0
  47. batplot-1.8.41/batplot/plot_modes/electrochem/routing.py +1540 -0
  48. batplot-1.8.41/batplot/plot_modes/electrochem/session.py +15 -0
  49. batplot-1.8.41/batplot/plot_modes/electrochem/spine_colors.py +108 -0
  50. batplot-1.8.41/batplot/plot_modes/electrochem/style.py +651 -0
  51. batplot-1.8.41/batplot/plot_modes/operando/__init__.py +5 -0
  52. batplot-1.8.41/batplot/plot_modes/operando/actions.py +1565 -0
  53. batplot-1.8.41/batplot/plot_modes/operando/colors.py +174 -0
  54. batplot-1.8.41/batplot/plot_modes/operando/grid.py +127 -0
  55. batplot-1.8.41/batplot/plot_modes/operando/interactive.py +3761 -0
  56. batplot-1.8.41/batplot/plot_modes/operando/ions_axis.py +141 -0
  57. batplot-1.8.41/batplot/plot_modes/operando/labels.py +173 -0
  58. batplot-1.8.41/batplot/plot_modes/operando/layout.py +409 -0
  59. batplot-1.8.41/batplot/plot_modes/operando/line_style.py +103 -0
  60. batplot-1.8.41/batplot/plot_modes/operando/menu.py +114 -0
  61. batplot-1.8.41/batplot/plot_modes/operando/peaks.py +289 -0
  62. batplot-1.8.40/batplot/operando.py → batplot-1.8.41/batplot/plot_modes/operando/plot.py +14 -8
  63. batplot-1.8.41/batplot/plot_modes/operando/routing.py +148 -0
  64. batplot-1.8.41/batplot/plot_modes/operando/session.py +15 -0
  65. batplot-1.8.41/batplot/plot_modes/operando/style.py +389 -0
  66. batplot-1.8.41/batplot/plot_modes/operando/visibility.py +285 -0
  67. batplot-1.8.41/batplot/plot_modes/session_routing.py +1072 -0
  68. batplot-1.8.41/batplot/plot_modes/xy/__init__.py +5 -0
  69. batplot-1.8.41/batplot/plot_modes/xy/actions.py +483 -0
  70. batplot-1.8.41/batplot/plot_modes/xy/arrange.py +131 -0
  71. batplot-1.8.41/batplot/plot_modes/xy/axis_range.py +624 -0
  72. batplot-1.8.41/batplot/plot_modes/xy/cif.py +465 -0
  73. batplot-1.8.41/batplot/plot_modes/xy/colors.py +336 -0
  74. batplot-1.8.41/batplot/plot_modes/xy/data_ops.py +114 -0
  75. batplot-1.8.41/batplot/plot_modes/xy/derivative.py +128 -0
  76. batplot-1.8.41/batplot/plot_modes/xy/game.py +125 -0
  77. batplot-1.8.41/batplot/plot_modes/xy/interactive.py +2441 -0
  78. batplot-1.8.41/batplot/plot_modes/xy/labels.py +160 -0
  79. batplot-1.8.41/batplot/plot_modes/xy/line_style.py +257 -0
  80. batplot-1.8.41/batplot/plot_modes/xy/menu.py +49 -0
  81. batplot-1.8.41/batplot/plot_modes/xy/peaks.py +174 -0
  82. batplot-1.8.41/batplot/plot_modes/xy/pipeline.py +1470 -0
  83. batplot-1.8.41/batplot/plot_modes/xy/session.py +15 -0
  84. batplot-1.8.41/batplot/plot_modes/xy/smoothing.py +716 -0
  85. {batplot-1.8.40/batplot → batplot-1.8.41/batplot/plot_modes/xy}/style.py +72 -60
  86. {batplot-1.8.40 → batplot-1.8.41}/batplot/plotting.py +32 -1
  87. {batplot-1.8.40 → batplot-1.8.41}/batplot/readers.py +48 -0
  88. {batplot-1.8.40 → batplot-1.8.41}/batplot/session.py +523 -255
  89. {batplot-1.8.40 → batplot-1.8.41}/batplot/showcol.py +8 -7
  90. batplot-1.8.41/batplot/style.py +19 -0
  91. {batplot-1.8.40 → batplot-1.8.41}/batplot/ui.py +0 -2
  92. {batplot-1.8.40 → batplot-1.8.41}/batplot/version_check.py +2 -1
  93. {batplot-1.8.40 → batplot-1.8.41/batplot.egg-info}/PKG-INFO +10 -8
  94. batplot-1.8.41/batplot.egg-info/SOURCES.txt +121 -0
  95. {batplot-1.8.40 → batplot-1.8.41}/batplot.egg-info/entry_points.txt +0 -1
  96. {batplot-1.8.40 → batplot-1.8.41}/batplot.egg-info/requires.txt +4 -0
  97. batplot-1.8.41/batplot.egg-info/top_level.txt +1 -0
  98. {batplot-1.8.40 → batplot-1.8.41}/pyproject.toml +24 -2
  99. batplot-1.8.41/tests/test_cli_smoke.py +183 -0
  100. batplot-1.8.41/tests/test_common_files.py +60 -0
  101. batplot-1.8.41/tests/test_common_palettes.py +209 -0
  102. batplot-1.8.41/tests/test_contracts.py +402 -0
  103. batplot-1.8.41/tests/test_cpc_roundtrip.py +458 -0
  104. batplot-1.8.41/tests/test_csv_readers.py +38 -0
  105. batplot-1.8.41/tests/test_dev_upgrade.py +163 -0
  106. batplot-1.8.41/tests/test_ec_roundtrip.py +553 -0
  107. batplot-1.8.41/tests/test_interactive_menu_smoke.py +236 -0
  108. batplot-1.8.41/tests/test_interactive_state.py +822 -0
  109. batplot-1.8.41/tests/test_operando_roundtrip.py +696 -0
  110. batplot-1.8.41/tests/test_xy_modules.py +246 -0
  111. batplot-1.8.41/tests/test_xy_roundtrip.py +267 -0
  112. batplot-1.8.40/MANIFEST.in +0 -2
  113. batplot-1.8.40/batplot/__init__.py +0 -5
  114. batplot-1.8.40/batplot/batplot.py +0 -5107
  115. batplot-1.8.40/batplot/cpc_interactive.py +0 -5750
  116. batplot-1.8.40/batplot/data/USER_MANUAL.md +0 -624
  117. batplot-1.8.40/batplot/electrochem_interactive.py +0 -7601
  118. batplot-1.8.40/batplot/interactive.py +0 -6342
  119. batplot-1.8.40/batplot/manual.py +0 -342
  120. batplot-1.8.40/batplot/modes.py +0 -862
  121. batplot-1.8.40/batplot/operando_ec_interactive.py +0 -7497
  122. batplot-1.8.40/batplot.egg-info/SOURCES.txt +0 -40
  123. batplot-1.8.40/batplot.egg-info/top_level.txt +0 -2
  124. {batplot-1.8.40 → batplot-1.8.41}/LICENSE +0 -0
  125. {batplot-1.8.40 → batplot-1.8.41}/NOTICE +0 -0
  126. {batplot-1.8.40 → batplot-1.8.41}/batplot/batch.py +0 -0
  127. {batplot-1.8.40 → batplot-1.8.41}/batplot/cif.py +0 -0
  128. {batplot-1.8.40 → batplot-1.8.41}/batplot/cli.py +0 -0
  129. {batplot-1.8.40 → batplot-1.8.41}/batplot/color_utils.py +0 -0
  130. {batplot-1.8.40 → batplot-1.8.41}/batplot/config.py +0 -0
  131. {batplot-1.8.40 → batplot-1.8.41}/batplot/converters.py +0 -0
  132. {batplot-1.8.40 → batplot-1.8.41}/batplot/utils.py +0 -0
  133. {batplot-1.8.40 → batplot-1.8.41}/batplot.egg-info/dependency_links.txt +0 -0
  134. {batplot-1.8.40 → batplot-1.8.41}/setup.cfg +0 -0
  135. {batplot-1.8.40 → batplot-1.8.41}/setup.py +0 -0
@@ -0,0 +1,3 @@
1
+ include README.md LICENSE
2
+ recursive-include batplot/data CHANGELOG.md
3
+ exclude batplot/data/USER_MANUAL.md
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: batplot
3
- Version: 1.8.40
3
+ Version: 1.8.41
4
4
  Summary: Interactive plotting tool for material science (1D plot) and electrochemistry (GC, CV, dQ/dV, CPC, operando) with batch processing
5
5
  Author-email: Tian Dai <tianda@uio.no>
6
6
  License: MIT License
@@ -47,6 +47,9 @@ Requires-Dist: numpy>=1.21.0
47
47
  Requires-Dist: matplotlib>=3.5.0
48
48
  Requires-Dist: rich>=10.0.0
49
49
  Requires-Dist: openpyxl>=3.0.0
50
+ Provides-Extra: test
51
+ Requires-Dist: pytest>=7.0; extra == "test"
52
+ Requires-Dist: basedpyright>=1.28.0; extra == "test"
50
53
  Dynamic: license-file
51
54
 
52
55
  # batplot
@@ -213,7 +216,6 @@ batplot battery.mpt --gc --mass 7.0 --i
213
216
 
214
217
  ```bash
215
218
  batplot cyclic.mpt --cv --i
216
- batplot cyclic.mpt --cv --i
217
219
  ```
218
220
 
219
221
  ### Differential capacity (dQ/dV)
@@ -356,12 +358,12 @@ With `--interactive`:
356
358
  ## Help & Documentation
357
359
 
358
360
  ```bash
359
- batplot --help # General help
360
- batplot --help xy # XY mode guide
361
- batplot --help ec # Electrochemistry guide
362
- batplot --help op # Operando guide
363
- batplot --version # Version and release notes
364
- batplot --manual # Open illustrated manual
361
+ batplot --h # General help
362
+ batplot --h xy # XY mode guide
363
+ batplot --h ec # Electrochemistry guide
364
+ batplot --h op # Operando guide
365
+ batplot --v # Version and release notes
366
+ batplot --m # Open illustrated manual
365
367
  ```
366
368
 
367
369
  - [USER_MANUAL.md](USER_MANUAL.md) — Detailed usage and workflows
@@ -162,7 +162,6 @@ batplot battery.mpt --gc --mass 7.0 --i
162
162
 
163
163
  ```bash
164
164
  batplot cyclic.mpt --cv --i
165
- batplot cyclic.mpt --cv --i
166
165
  ```
167
166
 
168
167
  ### Differential capacity (dQ/dV)
@@ -305,12 +304,12 @@ With `--interactive`:
305
304
  ## Help & Documentation
306
305
 
307
306
  ```bash
308
- batplot --help # General help
309
- batplot --help xy # XY mode guide
310
- batplot --help ec # Electrochemistry guide
311
- batplot --help op # Operando guide
312
- batplot --version # Version and release notes
313
- batplot --manual # Open illustrated manual
307
+ batplot --h # General help
308
+ batplot --h xy # XY mode guide
309
+ batplot --h ec # Electrochemistry guide
310
+ batplot --h op # Operando guide
311
+ batplot --v # Version and release notes
312
+ batplot --m # Open illustrated manual
314
313
  ```
315
314
 
316
315
  - [USER_MANUAL.md](USER_MANUAL.md) — Detailed usage and workflows
@@ -0,0 +1,74 @@
1
+ """batplot: Interactive plotting for battery data visualization."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import importlib
6
+ import importlib.abc
7
+ import importlib.machinery
8
+ import sys
9
+
10
+ __version__ = "1.8.41"
11
+
12
+
13
+ _LEGACY_MODULE_ALIASES = {
14
+ "batplot.interactive": "batplot.plot_modes.xy.interactive",
15
+ "batplot.electrochem_interactive": "batplot.plot_modes.electrochem.interactive",
16
+ "batplot.cpc_interactive": "batplot.plot_modes.cpc.interactive",
17
+ "batplot.cpc_menu": "batplot.plot_modes.cpc.menu",
18
+ "batplot.operando_ec_interactive": "batplot.plot_modes.operando.interactive",
19
+ "batplot.operando_layout": "batplot.plot_modes.operando.layout",
20
+ "batplot.operando_menu": "batplot.plot_modes.operando.menu",
21
+ "batplot.operando_style": "batplot.plot_modes.operando.style",
22
+ "batplot.interactive_state": "batplot.plot_modes.common.interactive_state",
23
+ }
24
+
25
+
26
+ class _LegacyModuleAliasLoader(importlib.abc.Loader):
27
+ """Lazy compatibility loader for mode modules moved under ``plot_modes``."""
28
+
29
+ def __init__(self, fullname: str, target_name: str):
30
+ self.fullname = fullname
31
+ self.target_name = target_name
32
+
33
+ def create_module(self, spec):
34
+ module = importlib.import_module(self.target_name)
35
+ self._apply_extra_exports(module)
36
+ sys.modules[self.fullname] = module
37
+ parent_name, _, child_name = self.fullname.rpartition(".")
38
+ parent = sys.modules.get(parent_name)
39
+ if parent is not None:
40
+ setattr(parent, child_name, module)
41
+ return module
42
+
43
+ def exec_module(self, module) -> None:
44
+ return None
45
+
46
+ def _apply_extra_exports(self, module) -> None:
47
+ if self.fullname == "batplot.cpc_interactive":
48
+ menu = importlib.import_module("batplot.plot_modes.cpc.menu")
49
+ module.print_cpc_menu = menu.print_cpc_menu
50
+ module.build_cpc_menu_columns = menu.build_cpc_menu_columns
51
+ elif self.fullname == "batplot.operando_ec_interactive":
52
+ style = importlib.import_module("batplot.plot_modes.operando.style")
53
+ module.build_operando_ec_style_config_v2 = style.build_operando_ec_style_config_v2
54
+
55
+
56
+ class _LegacyModuleAliasFinder(importlib.abc.MetaPathFinder):
57
+ def find_spec(self, fullname, path=None, target=None):
58
+ target_name = _LEGACY_MODULE_ALIASES.get(fullname)
59
+ if target_name is None:
60
+ return None
61
+ loader = _LegacyModuleAliasLoader(fullname, target_name)
62
+ spec = importlib.machinery.ModuleSpec(fullname, loader)
63
+ spec.origin = f"legacy-alias:{target_name}"
64
+ return spec
65
+
66
+
67
+ def _install_legacy_alias_finder() -> None:
68
+ if not any(isinstance(finder, _LegacyModuleAliasFinder) for finder in sys.meta_path):
69
+ sys.meta_path.insert(0, _LegacyModuleAliasFinder())
70
+
71
+
72
+ _install_legacy_alias_finder()
73
+
74
+ __all__ = ["__version__"]
@@ -24,6 +24,7 @@ from __future__ import annotations
24
24
  import argparse
25
25
  import sys
26
26
  import re
27
+ import webbrowser
27
28
 
28
29
  # ====================================================================
29
30
  # HELP OUTPUT
@@ -169,13 +170,13 @@ def _print_general_help() -> None:
169
170
  " • More: --help xy / --help ec / --help op\n\n"
170
171
 
171
172
  "More help:\n"
172
- " batplot --version # Version and release info (with option to show full release notes)\n"
173
+ " batplot --v # Version and release info (with option to show full release notes)\n"
173
174
  " batplot --showcol FILE [FILE...] # Preview column names + first 10 values per column\n"
174
- " batplot --help # This help\n"
175
- " batplot --help xy # XY file plotting guide\n"
176
- " batplot --help ec # Electrochemistry (GC/dQdV/CV/CPC) guide\n"
177
- " batplot --help op # Operando contour guide (also: batplot --help contour)\n"
178
- " batplot --manual # Open the illustrated txt manual with highlights\n\n"
175
+ " batplot --h # This help\n"
176
+ " batplot --h xy # XY file plotting guide\n"
177
+ " batplot --h ec # Electrochemistry (GC/dQdV/CV/CPC) guide\n"
178
+ " batplot --h op # Operando contour guide (also: batplot --help contour)\n"
179
+ " batplot --m # Open the illustrated PDF manual\n\n"
179
180
 
180
181
  "Contact & Updates:\n"
181
182
  " Subscribe to batplot-lab@kjemi.uio.no for updates\n"
@@ -246,10 +247,14 @@ def _print_xy_help() -> None:
246
247
  " - file:wl : single wavelength (for Q conversion or CIF 2theta calculation)\n"
247
248
  " - file:wl1:wl2 : dual wavelength (convert 2theta→Q using wl1, then Q→2theta using wl2)\n"
248
249
  " - file.cif:wl : CIF file with wavelength for 2theta tick calculation\n"
250
+ " - file:q : mark file as already in Q (no conversion; implies Q axis)\n"
251
+ " Any file:wl or file:q suffix implies Q mode automatically (no --xaxis q needed);\n"
252
+ " files without wavelength info are then assumed to be already in Q.\n"
249
253
  " Examples:\n"
250
254
  " batplot data.xye:1.5406 --xaxis 2theta\n"
251
255
  " batplot data.xye:0.25:1.54 --xaxis 2theta\n"
252
256
  " batplot data.xye pattern.cif:0.25448 --xaxis 2theta\n"
257
+ " batplot scan.xy:0.709 sim.csv:q --stack --i\n"
253
258
  " --readcol <x_col> <y_col> : specify which columns to read as x and y (1-indexed)\n"
254
259
  " Per-file: file1.xy --readcol 2 3 file2.xy --readcol 4 5 (different cols per file)\n"
255
260
  " Multi-curve: file.xy --readcol 1 2 1 3 (plot cols 1,2 and 1,3 as two curves)\n"
@@ -375,58 +380,10 @@ def _print_op_help() -> None:
375
380
  _print_help(msg)
376
381
 
377
382
 
378
- def build_parser() -> argparse.ArgumentParser:
379
- """
380
- Build the argument parser for batplot command-line interface.
381
-
382
- HOW ARGUMENT PARSING WORKS:
383
- --------------------------
384
- This function creates an ArgumentParser object that defines all valid
385
- command-line arguments for batplot. When you run 'batplot file.xy --i',
386
- argparse uses this parser to:
387
- 1. Recognize which arguments are valid
388
- 2. Extract values from the command line
389
- 3. Convert them to appropriate Python types (int, float, bool, etc.)
390
- 4. Store them in a namespace object (args.files, args.interactive, etc.)
391
-
392
- ARGUMENT TYPES:
393
- --------------
394
- - Positional arguments: 'files' - list of file paths (can be 0 or more)
395
- - Flags (boolean): '--i' - True if present, False if absent
396
- - Options with values: '--mass 7.0' or '--mass 0.01g' - mass in mg, or grams with a ``g`` suffix
397
- - Optional arguments: '--help xy' - can have optional value
398
-
399
- WHY add_help=False?
400
- -------------------
401
- We use a custom help system that supports topic-specific help:
402
- - 'batplot --help' → general help
403
- - 'batplot --help xy' → XY mode help
404
- - 'batplot --help ec' → EC mode help
405
- - 'batplot --help op' → Operando mode help
406
-
407
- This gives users more targeted help instead of one giant help page.
408
-
409
- Returns:
410
- Configured ArgumentParser object ready to parse command-line arguments
411
- """
412
- # Create parser with custom help system (we handle help ourselves)
413
- parser = argparse.ArgumentParser(add_help=False)
414
-
415
- # ====================================================================
416
- # TOPIC-AWARE HELP SYSTEM
417
- # ====================================================================
418
- # Instead of standard --help, we support topic-specific help:
419
- # batplot --help → general help
420
- # batplot --help xy → XY mode help
421
- # batplot --help ec → EC mode help
422
- # batplot --help op → Operando mode help
423
- #
424
- # nargs="?" means the argument is optional:
425
- # - If not provided: const="" (empty string)
426
- # - If provided: uses the value (e.g., "xy", "ec", "op")
427
- # ====================================================================
383
+ def _add_help_and_entry_arguments(parser: argparse.ArgumentParser) -> None:
384
+ """Register help/version/manual flags and positional file inputs."""
428
385
  parser.add_argument("--help", nargs="?", const="", metavar="topic",
429
- help=argparse.SUPPRESS) # SUPPRESS hides from auto-generated help
386
+ help=argparse.SUPPRESS)
430
387
  parser.add_argument("--version", action="store_true", dest="version",
431
388
  help="Show version and current release info, then exit.")
432
389
  parser.add_argument(
@@ -435,18 +392,11 @@ def build_parser() -> argparse.ArgumentParser:
435
392
  help=argparse.SUPPRESS,
436
393
  )
437
394
  parser.add_argument("--manual", action="store_true", help=argparse.SUPPRESS)
438
-
439
- # ====================================================================
440
- # POSITIONAL ARGUMENTS (FILE PATHS)
441
- # ====================================================================
442
- # 'files' is a positional argument, meaning it doesn't need a flag.
443
- # nargs="*" means it accepts 0 or more values (list).
444
- # Examples:
445
- # batplot file1.xy file2.xy → args.files = ['file1.xy', 'file2.xy']
446
- # batplot allfiles → args.files = ['allfiles']
447
- # batplot --i → args.files = [] (empty list)
448
- # ====================================================================
449
395
  parser.add_argument("files", nargs="*", help=argparse.SUPPRESS)
396
+
397
+
398
+ def _add_xy_arguments(parser: argparse.ArgumentParser) -> None:
399
+ """Register XY/general plotting arguments."""
450
400
  parser.add_argument("--delta", type=float, default=None, help=argparse.SUPPRESS)
451
401
  parser.add_argument("--autoscale", action="store_true", help=argparse.SUPPRESS)
452
402
  parser.add_argument("--xrange", nargs=2, type=float, help=argparse.SUPPRESS)
@@ -464,15 +414,29 @@ def build_parser() -> argparse.ArgumentParser:
464
414
  parser.add_argument("--kchik", action="store_true", help=argparse.SUPPRESS)
465
415
  parser.add_argument("--k2chik", action="store_true", help=argparse.SUPPRESS)
466
416
  parser.add_argument("--k3chik", action="store_true", help=argparse.SUPPRESS)
417
+ parser.add_argument("--1d", action="store_true", dest="derivative_1d", help=argparse.SUPPRESS)
418
+ parser.add_argument("--2d", action="store_true", dest="derivative_2d", help=argparse.SUPPRESS)
419
+
420
+
421
+ def _add_interactive_export_arguments(parser: argparse.ArgumentParser) -> None:
422
+ """Register interactive, export, and batch-output arguments."""
467
423
  parser.add_argument("--i", "--interactive", action="store_true", dest="interactive", help=argparse.SUPPRESS)
468
424
  parser.add_argument("--savefig", type=str, help=argparse.SUPPRESS)
469
425
  parser.add_argument("--stack", action="store_true", help=argparse.SUPPRESS)
470
426
  parser.add_argument("--ry", action="store_true", help=argparse.SUPPRESS)
471
427
  parser.add_argument("--txaxis", action="store_true", help=argparse.SUPPRESS)
472
- parser.add_argument("--operando", "--contour", action="store_true", dest="operando", help=argparse.SUPPRESS)
473
- parser.add_argument("--average", type=int, help=argparse.SUPPRESS)
474
- parser.add_argument("--sum", dest="scan_sum", type=int, help=argparse.SUPPRESS)
475
428
  parser.add_argument("--debug", action="store_true", help=argparse.SUPPRESS)
429
+ parser.add_argument("--ro", action="store_true", help=argparse.SUPPRESS)
430
+ parser.add_argument("--all", type=str, nargs='?', const='all', help=argparse.SUPPRESS)
431
+ parser.add_argument("--format", type=str, default='svg',
432
+ choices=['svg', 'png', 'pdf', 'jpg', 'jpeg', 'eps', 'tif', 'tiff'],
433
+ help=argparse.SUPPRESS)
434
+ parser.add_argument("--canvas", action="store_true", dest="canvas",
435
+ help="Canvas mode: combine multiple .pkl sessions into one layout. Use numbers to edit each panel.")
436
+
437
+
438
+ def _add_electrochem_arguments(parser: argparse.ArgumentParser) -> None:
439
+ """Register electrochemistry mode arguments."""
476
440
  parser.add_argument("--gc", action="store_true", help=argparse.SUPPRESS)
477
441
  parser.add_argument("--mass", type=parse_mass_mg_from_cli, action='append', help=argparse.SUPPRESS)
478
442
  parser.add_argument("--dqdv", action="store_true", help=argparse.SUPPRESS)
@@ -486,14 +450,19 @@ def build_parser() -> argparse.ArgumentParser:
486
450
  help=argparse.SUPPRESS)
487
451
  parser.add_argument("--anode", action="store_true", help=argparse.SUPPRESS)
488
452
  parser.add_argument("--cathode", action="store_true", help=argparse.SUPPRESS)
489
- parser.add_argument("--ro", action="store_true", help=argparse.SUPPRESS)
490
- parser.add_argument("--all", type=str, nargs='?', const='all', help=argparse.SUPPRESS)
491
- parser.add_argument("--format", type=str, default='svg',
492
- choices=['svg', 'png', 'pdf', 'jpg', 'jpeg', 'eps', 'tif', 'tiff'],
493
- help=argparse.SUPPRESS)
453
+
454
+
455
+ def _add_operando_arguments(parser: argparse.ArgumentParser) -> None:
456
+ """Register operando contour arguments."""
457
+ parser.add_argument("--operando", "--contour", action="store_true", dest="operando", help=argparse.SUPPRESS)
458
+ parser.add_argument("--average", type=int, help=argparse.SUPPRESS)
459
+ parser.add_argument("--sum", dest="scan_sum", type=int, help=argparse.SUPPRESS)
460
+
461
+
462
+ def _add_read_column_arguments(parser: argparse.ArgumentParser) -> None:
463
+ """Register column-selection arguments."""
494
464
  parser.add_argument("--readcol", nargs=2, type=int, metavar=('X_COL', 'Y_COL'),
495
465
  help=argparse.SUPPRESS)
496
- # Add extension-specific readcol arguments
497
466
  parser.add_argument("--readcolxy", nargs=2, type=int, metavar=('X_COL', 'Y_COL'),
498
467
  help=argparse.SUPPRESS)
499
468
  parser.add_argument("--readcolxye", nargs=2, type=int, metavar=('X_COL', 'Y_COL'),
@@ -510,10 +479,51 @@ def build_parser() -> argparse.ArgumentParser:
510
479
  help=argparse.SUPPRESS)
511
480
  parser.add_argument("--readcols", nargs=2, type=int, metavar=('X_COL', 'Y_COL'),
512
481
  help=argparse.SUPPRESS)
513
- parser.add_argument("--1d", action="store_true", dest="derivative_1d", help=argparse.SUPPRESS)
514
- parser.add_argument("--2d", action="store_true", dest="derivative_2d", help=argparse.SUPPRESS)
515
- parser.add_argument("--canvas", action="store_true", dest="canvas",
516
- help="Canvas mode: combine multiple .pkl sessions into one layout. Use numbers to edit each panel.")
482
+
483
+
484
+ def build_parser() -> argparse.ArgumentParser:
485
+ """
486
+ Build the argument parser for batplot command-line interface.
487
+
488
+ HOW ARGUMENT PARSING WORKS:
489
+ --------------------------
490
+ This function creates an ArgumentParser object that defines all valid
491
+ command-line arguments for batplot. When you run 'batplot file.xy --i',
492
+ argparse uses this parser to:
493
+ 1. Recognize which arguments are valid
494
+ 2. Extract values from the command line
495
+ 3. Convert them to appropriate Python types (int, float, bool, etc.)
496
+ 4. Store them in a namespace object (args.files, args.interactive, etc.)
497
+
498
+ ARGUMENT TYPES:
499
+ --------------
500
+ - Positional arguments: 'files' - list of file paths (can be 0 or more)
501
+ - Flags (boolean): '--i' - True if present, False if absent
502
+ - Options with values: '--mass 7.0' or '--mass 0.01g' - mass in mg, or grams with a ``g`` suffix
503
+ - Optional arguments: '--help xy' - can have optional value
504
+
505
+ WHY add_help=False?
506
+ -------------------
507
+ We use a custom help system that supports topic-specific help:
508
+ - 'batplot --help' → general help
509
+ - 'batplot --help xy' → XY mode help
510
+ - 'batplot --help ec' → EC mode help
511
+ - 'batplot --help op' → Operando mode help
512
+
513
+ This gives users more targeted help instead of one giant help page.
514
+
515
+ Returns:
516
+ Configured ArgumentParser object ready to parse command-line arguments
517
+ """
518
+ # Create parser with custom help system (we handle help ourselves)
519
+ parser = argparse.ArgumentParser(add_help=False)
520
+
521
+ _add_help_and_entry_arguments(parser)
522
+ _add_xy_arguments(parser)
523
+ _add_interactive_export_arguments(parser)
524
+ _add_operando_arguments(parser)
525
+ _add_electrochem_arguments(parser)
526
+ _add_read_column_arguments(parser)
517
527
  return parser
518
528
 
519
529
 
@@ -711,18 +721,23 @@ def parse_args(argv=None):
711
721
  # weren't in the parser yet when we built it
712
722
  ns, _unknown = parser.parse_known_args(argv)
713
723
  if getattr(ns, "manual", False):
724
+ manual_url = "https://github.com/chem-plot/batplot/blob/main/batplot_user_manual.pdf"
714
725
  try:
715
- from .manual import open_manual_url # Lazy import avoids matplotlib startup unless needed
716
- open_manual_url()
726
+ opened = webbrowser.open(manual_url)
717
727
  if _HAS_RICH and _console:
718
- _console.print("\n[green]Opened manual in browser[/green]")
728
+ if opened:
729
+ _console.print("\n[green]Opened PDF manual in browser[/green]")
730
+ else:
731
+ _console.print(f"\n[yellow]Manual PDF:[/yellow] {manual_url}")
719
732
  else:
720
- print("\nOpened manual in browser")
733
+ print("\nOpened PDF manual in browser" if opened else f"\nManual PDF: {manual_url}")
721
734
  except Exception as exc: # pragma: no cover - best effort
722
735
  if _HAS_RICH and _console:
723
736
  _console.print(f"\n[red]Failed to open manual:[/red] {exc}")
737
+ _console.print(f"[yellow]Manual PDF:[/yellow] {manual_url}")
724
738
  else:
725
739
  print(f"\nFailed to open manual: {exc}")
740
+ print(f"Manual PDF: {manual_url}")
726
741
  sys.exit(0)
727
742
 
728
743
  topic = getattr(ns, 'help', None)