Skip to content

Commit d4b5186

Browse files
committed
refactor: reduce main() complexity and fix lint issues
- Split main() into _handle_install/_handle_wheel/_handle_binary/ _handle_auto_detect handlers (fixes C901 complexity) - Replace blind except Exception with specific OSError/ValueError/ SystemExit (fixes BLE001) - Remove unnecessary else after return (fixes RET505) - Remove unused MIN_VERSION/MAX_VERSION imports - Update gen_cli_docs.py to handle subparsers - Extract _validate_wheel_tool helper
1 parent 29569e2 commit d4b5186

2 files changed

Lines changed: 174 additions & 127 deletions

File tree

clang_tools/main.py

Lines changed: 114 additions & 101 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
from typing import Optional
1313

1414
from .install import install_clang_tools, uninstall_clang_tools
15-
from . import RESET_COLOR, YELLOW, MIN_VERSION, MAX_VERSION
15+
from . import RESET_COLOR, YELLOW
1616
from .util import Version
1717

1818

@@ -51,6 +51,118 @@ def _wheel_install(tools: list[str], version: Optional[str]) -> int:
5151
return 0 if ok else 1
5252

5353

54+
def _validate_wheel_tool(target: str) -> bool:
55+
"""Print an error and return `False` if *target* is not a wheel tool.
56+
"""
57+
if target not in WHEEL_TOOLS:
58+
print(
59+
f"{YELLOW}Error: '{target}' is not available as a"
60+
f" wheel. Supported: "
61+
f"{', '.join(sorted(WHEEL_TOOLS))}{RESET_COLOR}",
62+
file=sys.stderr,
63+
)
64+
return False
65+
return True
66+
67+
68+
def _handle_wheel(args: argparse.Namespace) -> int:
69+
"""Handle ``install --wheel`` subcommand."""
70+
target: str = args.target
71+
if _is_version_like(target):
72+
return _wheel_install(args.tool, target)
73+
if not _validate_wheel_tool(target):
74+
return 1
75+
return _wheel_install([target], args.explicit_version)
76+
77+
78+
def _handle_binary(args: argparse.Namespace) -> int:
79+
"""Handle ``install --binary`` subcommand."""
80+
target: str = args.target
81+
if not _is_version_like(target):
82+
print(
83+
f"{YELLOW}Error: --binary requires a version number"
84+
f" (got '{target}'){RESET_COLOR}",
85+
file=sys.stderr,
86+
)
87+
return 1
88+
version = Version(target)
89+
if version.info == (0, 0, 0):
90+
print(
91+
f"{YELLOW}The version specified is not a semantic"
92+
f" specification{RESET_COLOR}",
93+
file=sys.stderr,
94+
)
95+
return 1
96+
install_clang_tools(
97+
version,
98+
args.tool,
99+
args.directory,
100+
args.overwrite,
101+
args.no_progress_bar,
102+
)
103+
return 0
104+
105+
106+
def _handle_auto_detect(args: argparse.Namespace) -> int:
107+
"""Handle ``install`` without --binary/--wheel (auto-detect mode)."""
108+
target: str = args.target
109+
if not _is_version_like(target):
110+
if target not in WHEEL_TOOLS:
111+
print(
112+
f"{YELLOW}Unknown target '{target}'. Expected a"
113+
f" version number or one of: "
114+
f"{', '.join(sorted(WHEEL_TOOLS))}{RESET_COLOR}",
115+
file=sys.stderr,
116+
)
117+
return 1
118+
return _wheel_install([target], args.explicit_version)
119+
120+
version = Version(target)
121+
if version.info == (0, 0, 0):
122+
print(
123+
f"{YELLOW}The version specified is not a semantic"
124+
f" specification{RESET_COLOR}",
125+
file=sys.stderr,
126+
)
127+
return 1
128+
129+
# try binary first, fall back to wheel
130+
try:
131+
install_clang_tools(
132+
version,
133+
args.tool,
134+
args.directory,
135+
args.overwrite,
136+
args.no_progress_bar,
137+
)
138+
return 0
139+
except (OSError, ValueError, SystemExit) as exc:
140+
print(
141+
f"{YELLOW}Binary install failed"
142+
f" ({exc}), falling back to"
143+
f" wheel...{RESET_COLOR}",
144+
file=sys.stderr,
145+
)
146+
return _wheel_install(args.tool, target)
147+
148+
149+
def _handle_install(args: argparse.Namespace) -> int:
150+
"""Dispatch ``install`` subcommand based on flags."""
151+
if args.binary and args.wheel:
152+
print(
153+
f"{YELLOW}Error: --binary and --wheel are mutually"
154+
f" exclusive{RESET_COLOR}",
155+
file=sys.stderr,
156+
)
157+
return 1
158+
159+
if args.wheel:
160+
return _handle_wheel(args)
161+
if args.binary:
162+
return _handle_binary(args)
163+
return _handle_auto_detect(args)
164+
165+
54166
def get_parser() -> argparse.ArgumentParser:
55167
"""Get a parser to interpret CLI args (unified entry point)."""
56168
parser = argparse.ArgumentParser(
@@ -139,7 +251,6 @@ def main() -> int:
139251
parser = get_parser()
140252
args = parser.parse_args()
141253

142-
# ---- No subcommand → nothing to do ----------------------------------
143254
if args.command is None:
144255
print(
145256
f"{YELLOW}Nothing to do. Use 'install' or 'uninstall'"
@@ -149,110 +260,12 @@ def main() -> int:
149260
parser.print_help()
150261
return 0
151262

152-
# ---- ``uninstall`` subcommand ------------------------------------
153263
if args.command == "uninstall":
154264
uninstall_clang_tools(args.version, args.tool, args.directory)
155265
return 0
156266

157-
# ---- ``install`` subcommand --------------------------------------
158267
if args.command == "install":
159-
target: str = args.target
160-
binary: bool = args.binary
161-
wheel: bool = args.wheel
162-
163-
# safety: --binary and --wheel are mutually exclusive
164-
if binary and wheel:
165-
print(
166-
f"{YELLOW}Error: --binary and --wheel are mutually"
167-
f" exclusive{RESET_COLOR}",
168-
file=sys.stderr,
169-
)
170-
return 1
171-
172-
# ---- Case: --wheel (target may be tool name or version) -----
173-
if wheel:
174-
if _is_version_like(target):
175-
# ``clang-tools install 18 --wheel``
176-
return _wheel_install(args.tool, target)
177-
else:
178-
# ``clang-tools install clang-format --wheel``
179-
if target not in WHEEL_TOOLS:
180-
print(
181-
f"{YELLOW}Error: '{target}' is not available as a"
182-
f" wheel. Supported: "
183-
f"{', '.join(sorted(WHEEL_TOOLS))}{RESET_COLOR}",
184-
file=sys.stderr,
185-
)
186-
return 1
187-
return _wheel_install([target], args.explicit_version)
188-
189-
# ---- Case: --binary (target must be a version) --------------
190-
if binary:
191-
if not _is_version_like(target):
192-
print(
193-
f"{YELLOW}Error: --binary requires a version number"
194-
f" (got '{target}'){RESET_COLOR}",
195-
file=sys.stderr,
196-
)
197-
return 1
198-
version = Version(target)
199-
if version.info != (0, 0, 0):
200-
install_clang_tools(
201-
version,
202-
args.tool,
203-
args.directory,
204-
args.overwrite,
205-
args.no_progress_bar,
206-
)
207-
return 0
208-
print(
209-
f"{YELLOW}The version specified is not a semantic"
210-
f" specification{RESET_COLOR}",
211-
file=sys.stderr,
212-
)
213-
return 1
214-
215-
# ---- Auto-detect (no --binary or --wheel flag) --------------
216-
if _is_version_like(target):
217-
version = Version(target)
218-
if version.info != (0, 0, 0):
219-
# try binary first, fall back to wheel
220-
try:
221-
install_clang_tools(
222-
version,
223-
args.tool,
224-
args.directory,
225-
args.overwrite,
226-
args.no_progress_bar,
227-
)
228-
return 0
229-
except Exception as exc:
230-
print(
231-
f"{YELLOW}Binary install failed"
232-
f" ({exc}), falling back to"
233-
f" wheel...{RESET_COLOR}",
234-
file=sys.stderr,
235-
)
236-
# fallback to wheel
237-
return _wheel_install(args.tool, target)
238-
else:
239-
print(
240-
f"{YELLOW}The version specified is not a semantic"
241-
f" specification{RESET_COLOR}",
242-
file=sys.stderr,
243-
)
244-
return 1
245-
else:
246-
# target is a tool name → install via wheel
247-
if target not in WHEEL_TOOLS:
248-
print(
249-
f"{YELLOW}Unknown target '{target}'. Expected a"
250-
f" version number or one of: "
251-
f"{', '.join(sorted(WHEEL_TOOLS))}{RESET_COLOR}",
252-
file=sys.stderr,
253-
)
254-
return 1
255-
return _wheel_install([target], args.explicit_version)
268+
return _handle_install(args)
256269

257270
return 0 # unreachable
258271

docs/gen_cli_docs.py

Lines changed: 60 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -17,11 +17,43 @@
1717
}
1818

1919

20+
def _action_doc(arg) -> str:
21+
"""Generate markdown for a single argparse action."""
22+
aliases = arg.option_strings
23+
if not aliases or arg.default == "==SUPPRESS==":
24+
return ""
25+
if arg.help is None:
26+
msg = f"Argument {aliases[0] if aliases else arg.dest} missing help text"
27+
raise ValueError(msg)
28+
29+
lines = [f"### `{', '.join(aliases)}`\n"]
30+
31+
req_ver = next(
32+
(ver for ver, names in REQUIRED_VERSIONS.items() if arg.dest in names),
33+
"0.1.0",
34+
)
35+
badges = [f":material-tag-outline: **v{req_ver}**"]
36+
37+
if arg.default is not None:
38+
default = (
39+
" ".join(arg.default) if isinstance(arg.default, list) else arg.default
40+
)
41+
badges.append(f"Default: `{default}`")
42+
if arg.default is False and arg.const is True:
43+
badges.append("Accepts no value")
44+
45+
lines.append("   ".join(badges) + "\n")
46+
lines.append(arg.help + "\n")
47+
return "\n".join(lines)
48+
49+
2050
def _write_cli_doc(parser, prog_name: str) -> str:
2151
"""Generate markdown documentation from an argparse parser."""
2252
lines = []
2353
lines.append(f"---\ntitle: {prog_name} CLI\n---\n")
2454
lines.append(f"# `{prog_name}` CLI Reference\n")
55+
56+
# Top-level usage
2557
lines.append("## Usage\n")
2658
lines.append("```text")
2759
parser.prog = prog_name
@@ -32,34 +64,36 @@ def _write_cli_doc(parser, prog_name: str) -> str:
3264
for line in usage.splitlines():
3365
lines.append(f" {line[start:]}")
3466
lines.append("```\n")
35-
lines.append("## Options\n")
3667

37-
args = parser._actions
38-
for arg in args:
39-
aliases = arg.option_strings
40-
if not aliases or arg.default == "==SUPPRESS==":
68+
# Top-level options (subcommands placeholder)
69+
subparsers_action = None
70+
for action in parser._actions:
71+
if isinstance(action, argparse._SubParsersAction):
72+
subparsers_action = action
4173
continue
42-
if arg.help is None:
43-
msg = f"Argument {aliases[0] if aliases else arg.dest} missing help text"
44-
raise ValueError(msg)
45-
lines.append(f"### `{', '.join(aliases)}`\n")
46-
47-
req_ver = next(
48-
(ver for ver, names in REQUIRED_VERSIONS.items() if arg.dest in names),
49-
"0.1.0",
50-
)
51-
badges = [f":material-tag-outline: **v{req_ver}**"]
52-
53-
if arg.default is not None:
54-
default = (
55-
" ".join(arg.default) if isinstance(arg.default, list) else arg.default
56-
)
57-
badges.append(f"Default: `{default}`")
58-
if arg.default is False and arg.const is True:
59-
badges.append("Accepts no value")
60-
61-
lines.append("   ".join(badges) + "\n")
62-
lines.append(arg.help + "\n")
74+
doc = _action_doc(action)
75+
if doc:
76+
lines.append(doc)
77+
78+
# Subcommand details
79+
if subparsers_action is not None:
80+
for name, sub in subparsers_action.choices.items():
81+
lines.append(f"---\n\n## `{prog_name} {name}`\n")
82+
lines.append(f"```text")
83+
sub.prog = f"{prog_name} {name}"
84+
str_buf = StringIO()
85+
sub.print_usage(str_buf)
86+
sub_usage = str_buf.getvalue()
87+
sub_start = sub_usage.find(sub.prog)
88+
lines.append(f" {sub_usage[sub_start:]}")
89+
lines.append("```\n")
90+
91+
for action in sub._actions:
92+
if not action.option_strings or action.default == "==SUPPRESS==":
93+
continue
94+
doc = _action_doc(action)
95+
if doc:
96+
lines.append(doc)
6397

6498
return "\n".join(lines)
6599

0 commit comments

Comments
 (0)