fix: make script help paths lightweight (#1937)

## Summary

Consolidate the script help/startup fixes so standalone helper commands
can show usage without requiring runtime-only dependencies or generating
files.

## Details

Several helper scripts imported optional packages, evaluated newer
annotations, or started generation before users could inspect CLI usage.
On a fresh checkout, this made common help paths fail on missing
packages such as `cairosvg`, `Pillow`, `freetype-py`, `fontTools`,
`pyphen`, `pyserial`, or on Python 3.9 annotation evaluation. A few
generators also treated `--help` as a normal output argument and wrote
files instead of printing usage.

This change keeps runtime dependencies on the code paths that need them,
handles lightweight help/list commands first, and adds explicit `--help`
handling for generator scripts that previously started work.

## Validation

- `python3 <script> --help` across tracked Python scripts, excluding
`scripts/patch_jpegdec.py` because it is a PlatformIO pre-build hook
rather than a standalone CLI
- `python3 scripts/convert_icon.py` still exits with usage for missing
required arguments
- `python3 lib/EpdFont/scripts/fontconvert_sdcard.py --list-presets`
- `python3 -m py_compile` for the changed scripts
This commit is contained in:
Danila Yudin
2026-05-11 11:21:13 -05:00
committed by GitHub
parent accd50b593
commit 63e92ec74e
10 changed files with 81 additions and 40 deletions
+3 -2
View File
@@ -1,12 +1,10 @@
#!python3 #!python3
import freetype
import zlib import zlib
import sys import sys
import re import re
import math import math
import argparse import argparse
from collections import namedtuple from collections import namedtuple
from fontTools.ttLib import TTFont
# Force UTF-8 stdout so that `python fontconvert.py … > foo.h` on Windows # Force UTF-8 stdout so that `python fontconvert.py … > foo.h` on Windows
# (default cp1252) doesn't emit UTF-16 LE / replacement chars in the generated # (default cp1252) doesn't emit UTF-16 LE / replacement chars in the generated
@@ -27,6 +25,9 @@ parser.add_argument("--force-autohint", dest="force_autohint", action="store_tru
parser.add_argument("--pnum", dest="pnum", action="store_true", help="Use proportional numerals (pnum OpenType feature) instead of default tabular figures. Reduces visual gaps between digits in running prose.") parser.add_argument("--pnum", dest="pnum", action="store_true", help="Use proportional numerals (pnum OpenType feature) instead of default tabular figures. Reduces visual gaps between digits in running prose.")
args = parser.parse_args() args = parser.parse_args()
import freetype
from fontTools.ttLib import TTFont
GlyphProps = namedtuple("GlyphProps", ["width", "height", "advance_x", "left", "top", "data_length", "data_offset", "code_point"]) GlyphProps = namedtuple("GlyphProps", ["width", "height", "advance_x", "left", "top", "data_length", "data_offset", "code_point"])
font_stack = [freetype.Face(f) for f in args.fontstack] font_stack = [freetype.Face(f) for f in args.fontstack]
+8 -3
View File
@@ -22,7 +22,8 @@ Usage:
""" """
import freetype from __future__ import annotations
import struct import struct
import sys import sys
import os import os
@@ -31,8 +32,6 @@ import math
import argparse import argparse
from collections import namedtuple from collections import namedtuple
from fontTools.ttLib import TTFont
from cpfont_version import CPFONT_VERSION from cpfont_version import CPFONT_VERSION
# --- Unicode interval presets --- # --- Unicode interval presets ---
@@ -252,6 +251,8 @@ def extract_kerning_fonttools(font_path, codepoints, ppem):
codepoints. Values are scaled from font design units to integer codepoints. Values are scaled from font design units to integer
pixels at ppem. pixels at ppem.
""" """
from fontTools.ttLib import TTFont
font = TTFont(font_path) font = TTFont(font_path)
units_per_em = font['head'].unitsPerEm units_per_em = font['head'].unitsPerEm
cmap = font.getBestCmap() or {} cmap = font.getBestCmap() or {}
@@ -402,6 +403,8 @@ def extract_ligatures_fonttools(font_path, codepoints):
Returns list of (packed_pair, ligature_codepoint) for the given codepoints. Returns list of (packed_pair, ligature_codepoint) for the given codepoints.
Multi-character ligatures are decomposed into chained pairs. Multi-character ligatures are decomposed into chained pairs.
""" """
from fontTools.ttLib import TTFont
font = TTFont(font_path) font = TTFont(font_path)
cmap = font.getBestCmap() or {} cmap = font.getBestCmap() or {}
@@ -514,6 +517,8 @@ def extract_ligatures_fonttools(font_path, codepoints):
def rasterize_font_style(fontfile, size, intervals, style_id=0, force_autohint=False): def rasterize_font_style(fontfile, size, intervals, style_id=0, force_autohint=False):
"""Rasterize all glyphs for one font style. Returns StyleRasterData.""" """Rasterize all glyphs for one font style. Returns StyleRasterData."""
import freetype
style_names = {0: "regular", 1: "bold", 2: "italic", 3: "bolditalic"} style_names = {0: "regular", 1: "bold", 2: "italic", 3: "bolditalic"}
style_label = style_names.get(style_id, str(style_id)) style_label = style_names.get(style_id, str(style_id))
@@ -234,6 +234,9 @@ def main():
if len(sys.argv) < 2: if len(sys.argv) < 2:
print(f"Usage: {sys.argv[0]} <font_headers_directory>", file=sys.stderr) print(f"Usage: {sys.argv[0]} <font_headers_directory>", file=sys.stderr)
sys.exit(1) sys.exit(1)
if sys.argv[1] in ("-h", "--help"):
print(f"Usage: {sys.argv[0]} <font_headers_directory>")
sys.exit(0)
font_dir = sys.argv[1] font_dir = sys.argv[1]
if not os.path.isdir(font_dir): if not os.path.isdir(font_dir):
+12 -6
View File
@@ -1,18 +1,21 @@
import sys
import os import os
from PIL import Image
import cairosvg
import io import io
import sys
threshold = 128 threshold = 128
USAGE = 'Usage: python scripts/convert_icon.py input.png|input.svg output_name width height'
def svg_to_png_bytes(svg_path, width, height): def svg_to_png_bytes(svg_path, width, height):
import cairosvg
with open(svg_path, 'rb') as f: with open(svg_path, 'rb') as f:
svg_data = f.read() svg_data = f.read()
png_bytes = cairosvg.svg2png(bytestring=svg_data, output_width=width, output_height=height) png_bytes = cairosvg.svg2png(bytestring=svg_data, output_width=width, output_height=height)
return png_bytes return png_bytes
def load_image(path, width, height): def load_image(path, width, height):
from PIL import Image
ext = os.path.splitext(path)[1].lower() ext = os.path.splitext(path)[1].lower()
if ext == '.svg': if ext == '.svg':
png_bytes = svg_to_png_bytes(path, width, height) png_bytes = svg_to_png_bytes(path, width, height)
@@ -58,8 +61,11 @@ def image_to_c_array(img, array_name):
return c return c
def main(): def main():
if len(sys.argv) < 5: if any(arg in ('-h', '--help') for arg in sys.argv[1:]):
print('Usage: python convert_image.py input.png output_name width height') print(USAGE)
sys.exit(0)
if len(sys.argv) != 5:
print(USAGE)
sys.exit(1) sys.exit(1)
input_path, output_name, width, height = sys.argv[1:5] input_path, output_name, width, height = sys.argv[1:5]
array_name = output_name.capitalize() + 'Icon' array_name = output_name.capitalize() + 'Icon'
@@ -77,4 +83,4 @@ def main():
print(f'Wrote {output_path}') print(f'Wrote {output_path}')
if __name__ == '__main__': if __name__ == '__main__':
main() main()
+38 -28
View File
@@ -35,6 +35,43 @@ import threading
from collections import deque from collections import deque
from datetime import datetime from datetime import datetime
DEFAULT_BAUDRATE = 115200
def build_arg_parser() -> argparse.ArgumentParser:
parser = argparse.ArgumentParser(
description="ESP32 Serial Monitor with Memory Graph - Real-time monitoring, graphing, and command interface"
)
parser.add_argument(
"port",
nargs="?",
default=None,
help="Serial port (leave empty for autodetection)",
)
parser.add_argument(
"--baud",
type=int,
default=DEFAULT_BAUDRATE,
help=f"Baud rate (default: {DEFAULT_BAUDRATE})",
)
parser.add_argument(
"--filter",
type=str,
default="",
help="Only display lines containing this keyword (case-insensitive)",
)
parser.add_argument(
"--suppress",
type=str,
default="",
help="Suppress lines containing this keyword (case-insensitive)",
)
return parser
if any(arg in ("-h", "--help") for arg in sys.argv[1:]):
build_arg_parser().parse_args()
# Try to import potentially missing packages # Try to import potentially missing packages
PACKAGE_MAPPING: dict[str, str] = { PACKAGE_MAPPING: dict[str, str] = {
"serial": "pyserial", "serial": "pyserial",
@@ -401,34 +438,7 @@ def main() -> None:
- Screenshot capture capability - Screenshot capture capability
- Graceful shutdown on Ctrl-C or window close - Graceful shutdown on Ctrl-C or window close
""" """
parser = argparse.ArgumentParser( parser = build_arg_parser()
description="ESP32 Serial Monitor with Memory Graph - Real-time monitoring, graphing, and command interface"
)
default_baudrate = 115200
parser.add_argument(
"port",
nargs="?",
default=None,
help="Serial port (leave empty for autodetection)",
)
parser.add_argument(
"--baud",
type=int,
default=default_baudrate,
help=f"Baud rate (default: {default_baudrate})",
)
parser.add_argument(
"--filter",
type=str,
default="",
help="Only display lines containing this keyword (case-insensitive)",
)
parser.add_argument(
"--suppress",
type=str,
default="",
help="Suppress lines containing this keyword (case-insensitive)",
)
args = parser.parse_args() args = parser.parse_args()
port = args.port port = args.port
if port is None: if port is None:
+2
View File
@@ -16,6 +16,8 @@ The input directory may be flat (all .cpfont files in one dir) or nested
convention <FamilyName>_<size>.cpfont. convention <FamilyName>_<size>.cpfont.
""" """
from __future__ import annotations
import argparse import argparse
import json import json
import os import os
@@ -137,10 +137,15 @@ Also includes:
import io import io
import os import os
import sys
import zipfile import zipfile
import uuid import uuid
from datetime import datetime from datetime import datetime
if any(arg in ("-h", "--help") for arg in sys.argv[1:]):
print(__doc__.strip())
sys.exit(0)
try: try:
from PIL import Image, ImageDraw, ImageFont from PIL import Image, ImageDraw, ImageFont
except ImportError: except ImportError:
+3
View File
@@ -326,4 +326,7 @@ def main():
if __name__ == '__main__': if __name__ == '__main__':
if any(arg in ("-h", "--help") for arg in sys.argv[1:]):
print(__doc__.strip())
sys.exit(0)
main() main()
+5
View File
@@ -11,9 +11,14 @@ Creates EPUBs with annotated JPEG and PNG images to verify:
""" """
import os import os
import sys
import zipfile import zipfile
from pathlib import Path from pathlib import Path
if any(arg in ("-h", "--help") for arg in sys.argv[1:]):
print(__doc__.strip())
sys.exit(0)
try: try:
from PIL import Image, ImageDraw, ImageFont from PIL import Image, ImageDraw, ImageFont
except ImportError: except ImportError:
@@ -16,7 +16,6 @@ Requirements:
import argparse import argparse
import re import re
from collections import Counter from collections import Counter
import pyphen
from pathlib import Path from pathlib import Path
import zipfile import zipfile
@@ -75,6 +74,8 @@ def generate_hyphenation_data(
min_prefix: Minimum characters allowed before the first hyphen (default: 2) min_prefix: Minimum characters allowed before the first hyphen (default: 2)
min_suffix: Minimum characters allowed after the last hyphen (default: 2) min_suffix: Minimum characters allowed after the last hyphen (default: 2)
""" """
import pyphen
print(f"Reading from: {input_file}") print(f"Reading from: {input_file}")
# Read the input file # Read the input file