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,625 @@
1
+ """Argument parsing for batplot CLI.
2
+
3
+ This module handles all command-line argument parsing for batplot. It defines
4
+ the command-line interface, including:
5
+ - All command-line flags and options
6
+ - Help text for each mode (XY, EC, Operando)
7
+ - Argument validation and conversion
8
+ - Colored help output (if rich library is available)
9
+
10
+ HOW COMMAND-LINE ARGUMENTS WORK:
11
+ --------------------------------
12
+ When you run (for example) 'batplot --xaxis 2theta file.xy --i', Python's argparse library:
13
+ 1. Parses the command line into structured arguments
14
+ 2. Validates that required arguments are present
15
+ 3. Converts string arguments to appropriate types (int, float, bool, etc.)
16
+ 4. Groups related arguments together
17
+ 5. Provides helpful error messages if arguments are invalid
18
+
19
+ This module defines all the valid arguments and their meanings.
20
+ """
21
+
22
+ from __future__ import annotations
23
+
24
+ import argparse
25
+ import sys
26
+ import re
27
+
28
+ # ====================================================================
29
+ # COLORED HELP OUTPUT
30
+ # ====================================================================
31
+ # The 'rich' library provides colored terminal output. If available,
32
+ # we use it to make help text more readable by highlighting:
33
+ # - Command-line flags in cyan
34
+ # - File extensions in yellow
35
+ # - Example commands in green
36
+ # - Section headers in blue
37
+ #
38
+ # If rich is not installed, we fall back to plain text (still works fine).
39
+ # ====================================================================
40
+ try:
41
+ from rich.console import Console
42
+ from rich.markup import escape
43
+ _console = Console()
44
+ _HAS_RICH = True
45
+ except ImportError:
46
+ _console = None
47
+ _HAS_RICH = False
48
+
49
+
50
+ def _colorize_help(text: str) -> str:
51
+ """
52
+ Add colors to help text by highlighting flags and special elements.
53
+
54
+ HOW IT WORKS:
55
+ ------------
56
+ Uses regular expressions to find patterns in help text and wrap them
57
+ with rich markup codes for colored output.
58
+
59
+ Patterns colored:
60
+ - Command-line flags: --flag or -f → cyan
61
+ - File extensions: .xy, .csv, etc. → yellow
62
+ - Example commands: batplot ... → green
63
+ - Section headers: lines ending with : → bold blue
64
+ - Bullet points: • → bold
65
+
66
+ Example:
67
+ Input: "batplot file.qye --i"
68
+ Output: "[green]batplot[/green] [yellow]file.qye[/yellow] [cyan]--i[/cyan]"
69
+
70
+ Args:
71
+ text: Plain help text (uncolored)
72
+
73
+ Returns:
74
+ Text with rich markup codes for colored output
75
+ (or original text if rich is not available)
76
+ """
77
+ if not _HAS_RICH:
78
+ return text # No coloring available, return as-is
79
+
80
+ # STEP 1: Escape any existing markup to prevent conflicts
81
+ # This ensures that if the help text already contains rich markup,
82
+ # we don't accidentally break it
83
+ text = escape(text)
84
+
85
+ # STEP 2: Color command-line flags
86
+ # Pattern: --flag-name or -f (single letter flag)
87
+ # Example: "--interactive" → "[cyan]--interactive[/cyan]"
88
+ text = re.sub(r'(--[\w-]+)', r'[cyan]\1[/cyan]', text) # Long flags (--flag)
89
+ text = re.sub(r'(\s-[a-zA-Z]\b)', r'[cyan]\1[/cyan]', text) # Short flags (-f)
90
+
91
+ # STEP 3: Color file extensions
92
+ # Pattern: .extension (2-4 characters)
93
+ # Example: ".xy" → "[yellow].xy[/yellow]"
94
+ text = re.sub(r'(\.\w{2,4}\b)', r'[yellow]\1[/yellow]', text)
95
+
96
+ # STEP 4: Color example commands
97
+ # Pattern: "batplot" followed by arguments
98
+ # Example: "batplot file.xy --i" → "[green]batplot file.xy --i[/green]"
99
+ text = re.sub(r'(batplot\s+[^\n]+)', r'[green]\1[/green]', text)
100
+
101
+ # STEP 5: Color section headers
102
+ # Pattern: Lines that start with capital letter and end with colon
103
+ # Example: "Examples:" → "[bold blue]Examples:[/bold blue]"
104
+ text = re.sub(r'^([A-Z][\w\s/()]+:)$', r'[bold blue]\1[/bold blue]', text, flags=re.MULTILINE)
105
+
106
+ # STEP 6: Make bullet points bold
107
+ text = text.replace('•', '[bold]•[/bold]')
108
+
109
+ return text
110
+
111
+
112
+ def _print_help(text: str) -> None:
113
+ """Print help text with optional coloring.
114
+
115
+ Args:
116
+ text: Help text to print
117
+ """
118
+ if _HAS_RICH and _console:
119
+ colored_text = _colorize_help(text)
120
+ _console.print(colored_text)
121
+ else:
122
+ print(text)
123
+
124
+
125
+ def _print_general_help() -> None:
126
+ # Import version here to avoid circular imports
127
+ try:
128
+ from . import __version__
129
+ version_str = f"batplot v{__version__} — quick plotting for lab data\n\n"
130
+ except ImportError:
131
+ version_str = "batplot — quick plotting for lab data\n\n"
132
+
133
+ msg = (
134
+ version_str +
135
+ "What it does:\n"
136
+ " • XY: XRD/PDF/XAS/User defined curves\n"
137
+ " • EC: Galvanostatic cycling(GC)/Capacity per cycle(CPC)/Diffrential capacity(dQdV)/Cyclic Voltammetry(CV) from Neware (.csv) or Biologic (.mpt)\n"
138
+ " • Operando: contour maps from a folder of .xy/.xye/.dat/.txt and optional file as side panel\n"
139
+ " • Batch: export vector plots for all files in a directory\n\n"
140
+ " • Interactive mode: --i / --interactive flag opens a menu for styling, ranges, export, and save\n\n"
141
+ "How to run (basics):\n"
142
+ " [1D(XY) curves]\n"
143
+ " batplot file1.xy file2.qye [option1] [option2] # 1D curves, read the first two columns as X and Y axis by default\n"
144
+ " batplot allfiles # Plot all files in current directory on same figure\n"
145
+ " batplot allfiles /path/to/dir # Plot all files in specified directory\n"
146
+ " batplot allfiles --i # Plot all files with interactive menu\n"
147
+ " batplot allxyfiles # Plot only .xy files (natural sorted)\n"
148
+ " batplot /path/to/data allnorfiles --i # Plot only .nor files from a directory\n"
149
+ " batplot --all # Batch mode: all XY files → Figures/ as .svg\n"
150
+ " batplot --all --format png # Batch mode: export as .png files\n"
151
+ " batplot --all --xaxis 2theta --xrange 10 80 # Batch mode with custom axis and range\n"
152
+ " batplot --all style.bps # Batch with style: apply style.bps to all files\n"
153
+ " batplot --all ./Style/style.bps # Batch with style: use relative path to style file\n"
154
+ " batplot --all config.bpsg # Batch with style+geom: apply to all XY files\n"
155
+ " batplot file1.xy:1.54 file2.qye --stack # Stack mode: stack all files vertically\n"
156
+ " batplot file1.xy:1.54 file2.qye structure.cif --stack --i # Stack mode: stack all files vertically with cif ticks\n"
157
+ " batplot file1.qye file2.qye style.bps # Apply style to multiple files and export\n"
158
+ " batplot file1.xy file2.xye ./Style/style.bps # Apply style from relative path\n\n"
159
+ " [Electrochemistry]\n"
160
+ " batplot --gc file.mpt --mass 7.0 # EC GC from .mpt (requires --mass mg)\n"
161
+ " batplot --gc file.csv --i # EC GC from supported .csv (no mass required) with interactive menu\n"
162
+ " batplot --gc --all --mass 7.0 # Batch: all .mpt/.csv → Figures/ as .svg\n"
163
+ " batplot --gc --all --mass 7 --format png # Batch: export as .png files\n"
164
+ " batplot --all --dqdv style.bps --mass 7 # Batch with style: apply style.bps to all GC files\n"
165
+ " batplot --all --gc ./Style/style.bps --mass 7 # Batch with style: use relative path\n"
166
+ " batplot --all --cpc config.bpsg # Batch with style+geom: apply to all CV files\n"
167
+ " batplot --all --cv ./Style/config.bpsg # Batch with style+geom: use relative path\n"
168
+ " batplot --dqdv FILE.csv # EC dQ/dV from supported .csv\n"
169
+ " batplot --dqdv --all # Batch: all .csv in directory (dQdV mode)\n"
170
+ " batplot --cv FILE.mpt # EC CV (cyclic voltammetry) from .mpt\n"
171
+ " batplot --cv FILE.txt # EC CV (cyclic voltammetry) from .txt\n"
172
+ " batplot --cv --all # Batch: all .mpt/.txt in directory (CV mode)\n\n"
173
+ " [Operando]\n"
174
+ " batplot --operando --i [FOLDER] # Operando contour (with or without .mpt file)\n\n"
175
+ "Features:\n"
176
+ " • Quick plotting with sensible defaults, no config files needed\n"
177
+ " • Supports many common file formats (see -h xy/ec/op)\n"
178
+ " • Interactive menus (--interactive): styling, ranges, fonts, export, sessions\n"
179
+ " • Batch processing: use 'allfiles' / 'all<ext>files' to plot together, or --all for separate files\n"
180
+ " • Batch exports saved to Figures/ subdirectory (default: .svg format)\n"
181
+ " • Batch styling: apply .bps/.bpsg files to all exports (use --all flag)\n"
182
+ " • Format option: use --format png/pdf/jpg/etc to change export format\n\n"
183
+
184
+ "More help:\n"
185
+ " batplot -h xy # XY file plotting guide\n"
186
+ " batplot -h ec # Electrochemistry (GC/dQdV/CV/CPC) guide\n"
187
+ " batplot -h op # Operando guide\n"
188
+ " batplot -m # Open the illustrated txt manual with highlights\n\n"
189
+
190
+ "Contact & Updates:\n"
191
+ " Subscribe to batplot-lab@kjemi.uio.no for updates\n"
192
+ " (If you are not from UiO, send an email to sympa@kjemi.uio.no with the subject line \"subscribe batplot-lab@kjemi.uio.no your-name\")\n"
193
+ " Kindly cite the pypi package page (https://pypi.org/project/batplot/) if the plot is used for publication\n"
194
+ " Email: tianda@uio.no\n"
195
+ " Personal page: https://www.mn.uio.no/kjemi/english/people/aca/tianda/\n"
196
+ )
197
+ _print_help(msg)
198
+
199
+
200
+ def _print_xy_help() -> None:
201
+ msg = (
202
+ "XY plots (XRD/PDF/XAS and many more)\n\n"
203
+ "Supported files: .xye .xy .qye .dat .csv .gr .nor .chik .chir .txt and other user specified formats. CIF overlays supported.\n\n"
204
+ "Axis detection: .qye→Q, .gr→r, .nor→energy, .chik→k, .chir→r, else use --xaxis (Q, 2theta, r, k, energy, time or user defined).\n"
205
+ "If mixing 2θ data in Q, give wavelength per-file (file.xye:1.5406) or global flag --wl.\n"
206
+ "A wavelength can be converted into a different wave length by file.xye:1.54:0.709.\n"
207
+ "For electrochemistry CSV/MPT time-voltage plots, use --xaxis time.\n\n"
208
+ "Examples:\n"
209
+ " batplot a.xye:1.5406 b.qye --stack --i\n"
210
+ " batplot a.dat b.xy --wl 1.54 --i\n"
211
+ " batplot pattern.qye ticks.cif:1.54 --i\n\n"
212
+ "Plot all files together:\n"
213
+ " batplot allfiles # Plot all XY files on same figure\n"
214
+ " batplot allfiles /path/to/dir # Plot all XY files in specified directory\n"
215
+ " batplot allfiles --stack --interactive # Stack all files with interactive menu\n"
216
+ " batplot allfiles --xaxis 2theta --xrange 10 80 # All files with custom axis and range\n"
217
+ " batplot allfiles --wl 1.5406 --delta 0.2 # All files with wavelength and spacing\n"
218
+ " batplot allxyfiles # Only plot .xy files (natural sorting)\n"
219
+ " batplot \"/path with spaces\" allnorfiles --interactive # Restrict to .nor files in a folder\n\n"
220
+ "Batch mode (separate file for each, saved to Figures/ subdirectory):\n"
221
+ " batplot --all # Export all XY files as .svg (default)\n"
222
+ " batplot --all --format png # Export all XY files as .png\n"
223
+ " batplot --all --xaxis 2theta # Batch mode with custom axis type\n"
224
+ " batplot --all --xrange 10 80 # Batch mode with X-axis range\n"
225
+ " batplot --all --wl 1.5406 # Batch mode with wavelength conversion\n"
226
+ " batplot --all style.bps # Apply style.bps to all XY files\n"
227
+ " batplot --all ./Style/style.bps # Apply style from relative path (e.g., ./Style/style.bps)\n"
228
+ " batplot --all config.bpsg # Apply style+geometry to all XY files\n"
229
+ " batplot --all ./Style/config.bpsg # Apply style+geometry from relative path\n\n"
230
+ "Normal mode with style files (apply style to multiple files and export):\n"
231
+ " batplot file1.xy file2.xye style.bps --out output.svg # Apply style and export\n"
232
+ " batplot file1.xy file2.xye ./Style/style.bps --out output.svg # Style from relative path\n"
233
+ " batplot file1.xy file2.xye style.bpsg --xaxis 2theta # Apply style+geometry\n"
234
+ " batplot file1.xy file2.xye ./Style/style.bpsg --xaxis 2theta # Style+geom from relative path\n\n"
235
+ "Tips and options:\n"
236
+ "[XY plot]\n"
237
+ " --interactive / -i : open interactive menu for styling, ranges, fonts, export, sessions\n"
238
+ " --delta/-d <float> : spacing between curves, e.g. --delta 0.1\n"
239
+ " --norm : normalize intensity to 0-1 range. Stack mode (--stack) auto-normalizes\n"
240
+ " --chik : EXAFS χ(k) plot (sets labels to k (Å⁻¹) vs χ(k))\n"
241
+ " --kchik : multiply y by x for EXAFS kχ(k) plots (sets labels to k (Å⁻¹) vs kχ(k) (Å⁻¹))\n"
242
+ " --k2chik : multiply y by x² for EXAFS k²χ(k) plots (sets labels to k (Å⁻¹) vs k²χ(k) (Å⁻²))\n"
243
+ " --k3chik : multiply y by x³ for EXAFS k³χ(k) plots (sets labels to k (Å⁻¹) vs k³χ(k) (Å⁻³))\n"
244
+ " --xrange/-r <min> <max> : set x-axis range, e.g. --xrange 0 10\n"
245
+ " --out/-o <filename> : save figure to file, e.g. --out file.svg\n"
246
+ " --xaxis <type> : set x-axis type (Q, 2theta, r, k, energy, rft, time, or user defined)\n"
247
+ " e.g. --xaxis 2theta, or --xaxis time for electrochemistry CSV/MPT time-voltage plots\n"
248
+ " --ro : swap x and y axes (exchange x and y values before plotting)\n"
249
+ " e.g. --xaxis time --ro plots time as y-axis and voltage as x-axis\n"
250
+ " --wl <float> : set wavelength for Q conversion for all files, e.g. --wl 1.5406\n"
251
+ " File wavelength syntax : specify wavelength(s) per file using colon syntax:\n"
252
+ " - file:wl : single wavelength (for Q conversion or CIF 2theta calculation)\n"
253
+ " - file:wl1:wl2 : dual wavelength (convert 2theta→Q using wl1, then Q→2theta using wl2)\n"
254
+ " - file.cif:wl : CIF file with wavelength for 2theta tick calculation\n"
255
+ " Examples:\n"
256
+ " batplot data.xye:1.5406 --xaxis 2theta\n"
257
+ " batplot data.xye:0.25:1.54 --xaxis 2theta\n"
258
+ " batplot data.xye pattern.cif:0.25448 --xaxis 2theta\n"
259
+ " --readcol <x_col> <y_col> : specify which columns to read as x and y (1-indexed), e.g. --readcol 2 3\n"
260
+ " --readcolxy <x> <y> : read columns for .xy files only\n"
261
+ " --readcolxye <x> <y> : read columns for .xye files only\n"
262
+ " --readcolqye <x> <y> : read columns for .qye files only\n"
263
+ " --readcolnor <x> <y> : read columns for .nor files only\n"
264
+ " --readcoldat <x> <y> : read columns for .dat files only\n"
265
+ " --readcolcsv <x> <y> : read columns for .csv files only\n"
266
+ " --readcol<ext> <x> <y> : read columns for custom extension (e.g., --readcolafes 2 3 for .afes files)\n"
267
+ " --fullprof <args> : FullProf overlay options\n"
268
+ " --stack : stack curves vertically (auto-enables normalization)\n"
269
+ )
270
+ _print_help(msg)
271
+
272
+
273
+ def _print_ec_help() -> None:
274
+ msg = (
275
+ "Electrochemistry (GC, dQ/dV, CV, and CPC)\n\n"
276
+ "Use --interactive for styling, colors, line widths, axis scales, etc.\n"
277
+ "GC from .mpt: requires active mass in mg to compute mAh g⁻¹.\n"
278
+ " batplot --gc file.mpt --mass 6.5 --interactive\n\n"
279
+ "GC from supported .csv: specific capacity is read directly (no --mass).\n"
280
+ " batplot --gc file.csv\n\n"
281
+ "dQ/dV from supported .csv:\n"
282
+ " batplot --dqdv file.csv\n\n"
283
+ "Cyclic voltammetry (CV) from .mpt or .txt: plots voltage vs current for each cycle.\n"
284
+ " batplot --cv file.mpt\n"
285
+ " batplot --cv file.txt\n\n"
286
+ "Capacity-per-cycle (CPC) with coulombic efficiency from .csv, .xlsx, or .mpt.\n"
287
+ "Supports multiple files with individual color customization:\n"
288
+ " batplot --cpc file.csv # Neware CSV\n"
289
+ " batplot --cpc file.xlsx # Landt/Lanhe Excel (Chinese tester)\n"
290
+ " batplot --cpc file.mpt --mass 1.2 # Biologic MPT\n"
291
+ " batplot --cpc file1.csv file2.xlsx file3.mpt --mass 1.2 --interactive\n\n"
292
+ "Excel support: Landt/Lanhe (蓝电/蓝河) .xlsx files with Chinese headers:\n"
293
+ " Expected structure: Row 1=filename, Row 2=headers, Row 3+=data\n"
294
+ "Batch mode: Process all files and export to Figures/ subdirectory (default: .svg).\n"
295
+ " batplot --gc --all --mass 7.0 # All .mpt/.csv files (.mpt requires --mass)\n"
296
+ " batplot --gc --all --mass 7 --format png # Export as .png instead of .svg\n"
297
+ " batplot --cv --all # All .mpt/.txt files (CV mode)\n"
298
+ " batplot --dqdv --all # All .csv files (dQdV mode)\n"
299
+ " batplot --cpc --all --mass 5.4 # All .mpt/.csv/.xlsx (.mpt requires --mass)\n"
300
+ " batplot --gc /path/to/folder --mass 6 # Process specific directory\n\n"
301
+ "Batch mode with style/geometry: Apply .bps/.bpsg files to all batch exports.\n"
302
+ " batplot --all style.bps --gc --mass 7 # Apply style to all GC plots\n"
303
+ " batplot --all ./Style/style.bps --gc --mass 7 # Apply style from relative path\n"
304
+ " batplot --all config.bpsg --cv # Apply style+geometry to all CV plots\n"
305
+ " batplot --all ./Style/config.bpsg --cv # Apply style+geometry from relative path\n"
306
+ " batplot --all my.bps --dqdv # Apply style to all dQdV plots\n"
307
+ " batplot --all ./Style/my.bps --dqdv # Apply style from relative path\n"
308
+ " batplot --all geom.bpsg --cpc --mass 6 # Apply style+geom to all CPC plots\n"
309
+ " batplot --all ./Style/geom.bpsg --cpc --mass 6 # Apply style+geom from relative path\n\n"
310
+ "Normal mode with style files: Apply style to multiple files and export.\n"
311
+ " batplot file1.csv file2.mpt style.bps --gc --mass 7 --out output.svg # GC mode\n"
312
+ " batplot file1.csv file2.mpt ./Style/style.bps --gc --mass 7 --out output.svg # Style from relative path\n"
313
+ " batplot file1.mpt file2.txt style.bpsg --cv # CV mode\n"
314
+ " batplot file1.mpt file2.txt ./Style/style.bpsg --cv # Style+geom from relative path\n"
315
+ " batplot file1.csv file2.csv style.bps --dqdv # dQdV mode\n"
316
+ " batplot file1.csv file2.csv ./Style/style.bps --dqdv # Style from relative path\n"
317
+ " batplot file1.csv file2.mpt style.bpsg --cpc --mass 6 # CPC mode\n"
318
+ " batplot file1.csv file2.mpt ./Style/style.bpsg --cpc --mass 6 # Style+geom from relative path\n\n"
319
+ "Interactive (--interactive): choose cycles, colors/palettes, line widths, axis scales (linear/log/symlog),\n"
320
+ "rename axes, toggle ticks/titles/spines, print/export/import style (.bps/.bpsg), save session (.pkl).\n"
321
+ "Note: Batch mode (--all) exports SVG files automatically; --interactive is for single-file plotting only.\n\n"
322
+ "Axis swapping:\n"
323
+ " --ro : swap x and y axes (exchange x and y values before plotting)\n"
324
+ " e.g. --gc --ro plots voltage as x-axis and capacity as y-axis\n"
325
+ " e.g. --xaxis time --ro plots time as y-axis and voltage as x-axis\n"
326
+ )
327
+ _print_help(msg)
328
+
329
+
330
+ def _print_op_help() -> None:
331
+ msg = (
332
+ "Operando contour plots\n\n"
333
+ "Example usage:\n"
334
+ " batplot --operando --interactive --wl 0.25995 # Interactive mode with Q conversion\n"
335
+ " batplot --operando --xaxis 2theta # Using 2theta axis\n\n"
336
+ " • Folder should contain XY files (.xy/.xye/.qye/.dat).\n"
337
+ " • Intensity scale is auto-adjusted between min/max values.\n"
338
+ " • If no .qye present, provide --xaxis 2theta or set --wl for Q conversion.\n"
339
+ " • If a .mpt file is present, an EC side panel is added for dual-panel mode.\n"
340
+ " • Without a .mpt file, operando-only mode shows the contour plot alone.\n\n"
341
+ "Interactive (--interactive): resize axes/canvas, change colormap, set intensity range (oz),\n"
342
+ "EC y-axis options (time ↔ ions), geometry tweaks, toggle spines/ticks/labels,\n"
343
+ "print/export/import style, save session.\n"
344
+ )
345
+ _print_help(msg)
346
+
347
+
348
+ def build_parser() -> argparse.ArgumentParser:
349
+ """
350
+ Build the argument parser for batplot command-line interface.
351
+
352
+ HOW ARGUMENT PARSING WORKS:
353
+ --------------------------
354
+ This function creates an ArgumentParser object that defines all valid
355
+ command-line arguments for batplot. When you run 'batplot file.xy --interactive',
356
+ argparse uses this parser to:
357
+ 1. Recognize which arguments are valid
358
+ 2. Extract values from the command line
359
+ 3. Convert them to appropriate Python types (int, float, bool, etc.)
360
+ 4. Store them in a namespace object (args.files, args.interactive, etc.)
361
+
362
+ ARGUMENT TYPES:
363
+ --------------
364
+ - Positional arguments: 'files' - list of file paths (can be 0 or more)
365
+ - Flags (boolean): '--interactive' - True if present, False if absent
366
+ - Options with values: '--mass 7.0' - requires a value (float in this case)
367
+ - Optional arguments: '--help xy' - can have optional value
368
+
369
+ WHY add_help=False?
370
+ -------------------
371
+ We use a custom help system that supports topic-specific help:
372
+ - 'batplot -h' → general help
373
+ - 'batplot -h xy' → XY mode help
374
+ - 'batplot -h ec' → EC mode help
375
+ - 'batplot -h op' → Operando mode help
376
+
377
+ This gives users more targeted help instead of one giant help page.
378
+
379
+ Returns:
380
+ Configured ArgumentParser object ready to parse command-line arguments
381
+ """
382
+ # Create parser with custom help system (we handle help ourselves)
383
+ parser = argparse.ArgumentParser(add_help=False)
384
+
385
+ # ====================================================================
386
+ # TOPIC-AWARE HELP SYSTEM
387
+ # ====================================================================
388
+ # Instead of standard --help, we support topic-specific help:
389
+ # batplot -h → general help
390
+ # batplot -h xy → XY mode help
391
+ # batplot -h ec → EC mode help
392
+ # batplot -h op → Operando mode help
393
+ #
394
+ # nargs="?" means the argument is optional:
395
+ # - If not provided: const="" (empty string)
396
+ # - If provided: uses the value (e.g., "xy", "ec", "op")
397
+ # ====================================================================
398
+ parser.add_argument("--help", "-h", nargs="?", const="", metavar="topic",
399
+ help=argparse.SUPPRESS) # SUPPRESS hides from auto-generated help
400
+ parser.add_argument("--manual", "-m", action="store_true", help=argparse.SUPPRESS)
401
+
402
+ # ====================================================================
403
+ # POSITIONAL ARGUMENTS (FILE PATHS)
404
+ # ====================================================================
405
+ # 'files' is a positional argument, meaning it doesn't need a flag.
406
+ # nargs="*" means it accepts 0 or more values (list).
407
+ # Examples:
408
+ # batplot file1.xy file2.xy → args.files = ['file1.xy', 'file2.xy']
409
+ # batplot allfiles → args.files = ['allfiles']
410
+ # batplot --interactive → args.files = [] (empty list)
411
+ # ====================================================================
412
+ parser.add_argument("files", nargs="*", help=argparse.SUPPRESS)
413
+ parser.add_argument("--delta", "-d", type=float, default=None, help=argparse.SUPPRESS)
414
+ parser.add_argument("--autoscale", action="store_true", help=argparse.SUPPRESS)
415
+ parser.add_argument("--xrange", "-r", nargs=2, type=float, help=argparse.SUPPRESS)
416
+ parser.add_argument("--out", "-o", type=str, help=argparse.SUPPRESS)
417
+ parser.add_argument("--errors", action="store_true", help=argparse.SUPPRESS)
418
+ parser.add_argument("--xaxis", type=str, help=argparse.SUPPRESS)
419
+ parser.add_argument("--convert", "-c", nargs="+", help=argparse.SUPPRESS)
420
+ parser.add_argument("--wl", type=float, help=argparse.SUPPRESS)
421
+ parser.add_argument("--fullprof", nargs="+", type=float, help=argparse.SUPPRESS)
422
+ parser.add_argument("--norm", action="store_true", help=argparse.SUPPRESS)
423
+ parser.add_argument("--chik", action="store_true", help=argparse.SUPPRESS)
424
+ parser.add_argument("--kchik", action="store_true", help=argparse.SUPPRESS)
425
+ parser.add_argument("--k2chik", action="store_true", help=argparse.SUPPRESS)
426
+ parser.add_argument("--k3chik", action="store_true", help=argparse.SUPPRESS)
427
+ parser.add_argument("-i", "--interactive", action="store_true", dest="interactive", help=argparse.SUPPRESS)
428
+ parser.add_argument("--savefig", type=str, help=argparse.SUPPRESS)
429
+ parser.add_argument("--stack", action="store_true", help=argparse.SUPPRESS)
430
+ parser.add_argument("--operando", action="store_true", help=argparse.SUPPRESS)
431
+ parser.add_argument("--gc", action="store_true", help=argparse.SUPPRESS)
432
+ parser.add_argument("--mass", type=float, help=argparse.SUPPRESS)
433
+ parser.add_argument("--dqdv", action="store_true", help=argparse.SUPPRESS)
434
+ parser.add_argument("--cv", action="store_true", help=argparse.SUPPRESS)
435
+ parser.add_argument("--cpc", action="store_true", help=argparse.SUPPRESS)
436
+ parser.add_argument("--ro", action="store_true", help=argparse.SUPPRESS)
437
+ parser.add_argument("--all", type=str, nargs='?', const='all', help=argparse.SUPPRESS)
438
+ parser.add_argument("--format", type=str, default='svg',
439
+ choices=['svg', 'png', 'pdf', 'jpg', 'jpeg', 'eps', 'tif', 'tiff'],
440
+ help=argparse.SUPPRESS)
441
+ parser.add_argument("--readcol", nargs=2, type=int, metavar=('X_COL', 'Y_COL'),
442
+ help=argparse.SUPPRESS)
443
+ # Add extension-specific readcol arguments
444
+ parser.add_argument("--readcolxy", nargs=2, type=int, metavar=('X_COL', 'Y_COL'),
445
+ help=argparse.SUPPRESS)
446
+ parser.add_argument("--readcolxye", nargs=2, type=int, metavar=('X_COL', 'Y_COL'),
447
+ help=argparse.SUPPRESS)
448
+ parser.add_argument("--readcolqye", nargs=2, type=int, metavar=('X_COL', 'Y_COL'),
449
+ help=argparse.SUPPRESS)
450
+ parser.add_argument("--readcolnor", nargs=2, type=int, metavar=('X_COL', 'Y_COL'),
451
+ help=argparse.SUPPRESS)
452
+ parser.add_argument("--readcoldat", nargs=2, type=int, metavar=('X_COL', 'Y_COL'),
453
+ help=argparse.SUPPRESS)
454
+ parser.add_argument("--readcolcsv", nargs=2, type=int, metavar=('X_COL', 'Y_COL'),
455
+ help=argparse.SUPPRESS)
456
+ return parser
457
+
458
+
459
+ def parse_args(argv=None):
460
+ """
461
+ Parse command-line arguments with support for dynamic --readcol<ext> flags.
462
+
463
+ HOW IT WORKS:
464
+ ------------
465
+ This function:
466
+ 1. Scans command line for custom --readcol<ext> flags (e.g., --readcolafes)
467
+ 2. Dynamically adds them to the parser (so argparse recognizes them)
468
+ 3. Parses all arguments using the parser
469
+ 4. Handles topic-specific help requests
470
+
471
+ WHY DYNAMIC ARGUMENTS?
472
+ ---------------------
473
+ We support custom file extensions (e.g., .afes files). Users can specify
474
+ which columns to read using --readcol<ext> syntax:
475
+ batplot file.afes --readcolafes 2 3
476
+
477
+ We can't know all possible extensions ahead of time, so we:
478
+ 1. Scan the command line first to find --readcol<ext> patterns
479
+ 2. Add them to the parser dynamically
480
+ 3. Then parse normally
481
+
482
+ Args:
483
+ argv: Optional list of command-line arguments (for testing).
484
+ If None, uses sys.argv[1:] (skips program name).
485
+
486
+ Returns:
487
+ Parsed arguments namespace object with all arguments as attributes.
488
+ Example: args.files, args.interactive, args.mass, etc.
489
+ """
490
+ import re
491
+
492
+ # ====================================================================
493
+ # STEP 1: SCAN FOR CUSTOM --readcol<ext> FLAGS
494
+ # ====================================================================
495
+ # Before parsing, we need to find any custom --readcol<ext> flags
496
+ # (e.g., --readcolafes) and add them to the parser dynamically.
497
+ #
498
+ # Why? We support arbitrary file extensions, and users can specify
499
+ # column selection for any extension using --readcol<ext> syntax.
500
+ #
501
+ # Example:
502
+ # batplot file.afes --readcolafes 2 3
503
+ # This means: for .afes files, read column 2 as x, column 3 as y
504
+ # ====================================================================
505
+
506
+ # Get command-line arguments (skip program name 'batplot')
507
+ if argv is None:
508
+ argv = sys.argv[1:]
509
+
510
+ # Find all --readcol<ext> patterns in command line
511
+ # Pattern: --readcol followed by lowercase letters/numbers
512
+ # Example: --readcolafes → ext = 'afes'
513
+ custom_readcol_exts = set()
514
+ i = 0
515
+ while i < len(argv):
516
+ arg = argv[i]
517
+ # Match pattern: --readcol<extension>
518
+ match = re.match(r'^--readcol([a-z0-9]+)$', arg)
519
+ if match:
520
+ ext = match.group(1) # Extract extension name
521
+ # Skip predefined extensions (already in parser)
522
+ if ext not in ['xy', 'xye', 'qye', 'nor', 'dat', 'csv']:
523
+ custom_readcol_exts.add(ext)
524
+ i += 1
525
+
526
+ # ====================================================================
527
+ # STEP 2: BUILD PARSER AND ADD DYNAMIC ARGUMENTS
528
+ # ====================================================================
529
+ # Create the base parser (with all standard arguments)
530
+ parser = build_parser()
531
+
532
+ # Add custom --readcol<ext> arguments dynamically
533
+ # This allows argparse to recognize and parse them
534
+ for ext in custom_readcol_exts:
535
+ parser.add_argument(f"--readcol{ext}", nargs=2, type=int, metavar=('X_COL', 'Y_COL'),
536
+ help=argparse.SUPPRESS)
537
+
538
+ # ====================================================================
539
+ # STEP 3: HANDLE HELP REQUESTS (TOPIC-SPECIFIC HELP)
540
+ # ====================================================================
541
+ # We use parse_known_args() first to handle help requests without
542
+ # complaining about unknown arguments. This allows:
543
+ # batplot -h xy → XY mode help
544
+ # batplot -h ec → EC mode help
545
+ # batplot -h op → Operando mode help
546
+ #
547
+ # If help is requested, we print it and exit immediately (don't continue parsing).
548
+ # ====================================================================
549
+
550
+ # Parse with known_args_only=True to avoid errors from unknown arguments
551
+ # This is needed because we might have custom --readcol<ext> flags that
552
+ # weren't in the parser yet when we built it
553
+ ns, _unknown = parser.parse_known_args(argv)
554
+ if getattr(ns, "manual", False):
555
+ try:
556
+ from .manual import show_manual # Lazy import avoids matplotlib startup unless needed
557
+ pdf_path = show_manual(open_viewer=True)
558
+ if _HAS_RICH and _console:
559
+ _console.print(f"\n[green]Opened manual:[/green] {pdf_path}")
560
+ else:
561
+ print(f"\nOpened manual: {pdf_path}")
562
+ except Exception as exc: # pragma: no cover - rendering is best effort
563
+ if _HAS_RICH and _console:
564
+ _console.print(f"\n[red]Failed to open manual:[/red] {exc}")
565
+ else:
566
+ print(f"\nFailed to open manual: {exc}")
567
+ sys.exit(0)
568
+
569
+ topic = getattr(ns, 'help', None)
570
+
571
+ if topic is not None:
572
+ # Help was requested, print topic-specific help and exit
573
+ t = (topic or '').strip().lower()
574
+ if t in ("", "help"):
575
+ _print_general_help() # General help (no topic specified)
576
+ elif t in ("xy",):
577
+ _print_xy_help() # XY mode help
578
+ elif t in ("ec", "gc", "dqdv"):
579
+ _print_ec_help() # EC mode help (GC, dQ/dV, CV, CPC)
580
+ elif t in ("op", "operando"):
581
+ _print_op_help() # Operando mode help
582
+ else:
583
+ # Unknown topic, show general help with warning
584
+ _print_general_help()
585
+ if _HAS_RICH and _console:
586
+ _console.print("\n[yellow]Unknown help topic. Use: xy, ec, op[/yellow]")
587
+ else:
588
+ print("\nUnknown help topic. Use: xy, ec, op")
589
+ sys.exit(0) # Exit after showing help (don't continue to actual plotting)
590
+
591
+ # ====================================================================
592
+ # STEP 4: PARSE ALL ARGUMENTS (NORMAL OPERATION)
593
+ # ====================================================================
594
+ # No help requested, so parse all arguments normally.
595
+ # This will raise an error if required arguments are missing or invalid.
596
+ # ====================================================================
597
+ args = parser.parse_args(argv)
598
+
599
+ # ====================================================================
600
+ # STEP 5: BUILD readcol_by_ext DICTIONARY
601
+ # ====================================================================
602
+ # Collect all --readcol<ext> arguments into a convenient dictionary
603
+ # mapping file extension to (x_col, y_col) tuple.
604
+ #
605
+ # Example:
606
+ # User runs: batplot file.xy --readcolxy 2 3 file.afes --readcolafes 4 5
607
+ # Result: args.readcol_by_ext = {'.xy': (2, 3), '.afes': (4, 5)}
608
+ #
609
+ # This makes it easy to look up column specification for any file extension.
610
+ # ====================================================================
611
+ args.readcol_by_ext = {}
612
+
613
+ # Check all predefined and custom extensions
614
+ for ext in ['xy', 'xye', 'qye', 'nor', 'dat', 'csv'] + list(custom_readcol_exts):
615
+ attr_name = f'readcol{ext}' # e.g., 'readcolxy', 'readcolafes'
616
+ if hasattr(args, attr_name):
617
+ val = getattr(args, attr_name) # Get (x_col, y_col) tuple or None
618
+ if val is not None:
619
+ # Store with dot prefix (e.g., '.xy' not 'xy') for easy matching
620
+ args.readcol_by_ext[f'.{ext}'] = val
621
+
622
+ return args
623
+
624
+
625
+ __all__ = ["build_parser", "parse_args"]