Skip to content

Commit 53f97bb

Browse files
heikkitoivonencodex
andcommitted
Fix: Add all/brief output options to introspect
Co-Authored-By: Codex <codex@openai.com>
1 parent c754a7b commit 53f97bb

2 files changed

Lines changed: 165 additions & 32 deletions

File tree

scripts/introspect.py

Lines changed: 52 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -134,22 +134,29 @@ def build_item_list() -> list[tuple[str, object]]:
134134
return items
135135

136136

137-
def format_module(module: ModuleType) -> list[str]:
137+
def format_module(module: ModuleType, brief: bool) -> list[str]:
138138
"""Format a module's contents."""
139-
lines = ["Type: module"]
140139
contents = get_module_contents(module)
140+
if brief:
141+
return [f" {c}" for c in contents]
142+
143+
lines = ["Type: module"]
141144
if contents:
142145
lines.append("")
143146
lines.append("Contents:")
144147
lines.extend(f" {c}" for c in contents)
145148
return lines
146149

147150

148-
def format_class(cls: type) -> list[str]:
151+
def format_class(cls: type, brief: bool) -> list[str]:
149152
"""Format a class's methods and attributes."""
150-
lines = ["Type: class"]
151153
methods, attributes = get_direct_members(cls)
152154

155+
if brief:
156+
return [f" {name}" for name in (methods + attributes)]
157+
158+
lines = ["Type: class"]
159+
153160
if methods:
154161
lines.append("")
155162
lines.append("Methods:")
@@ -167,21 +174,31 @@ def format_class(cls: type) -> list[str]:
167174
return lines
168175

169176

170-
def format_item(name: str, obj: object, next_name: str | None) -> str:
177+
def format_item(
178+
name: str,
179+
obj: object,
180+
next_name: str | None,
181+
*,
182+
brief: bool,
183+
include_next: bool,
184+
) -> str:
171185
"""Format an item for output."""
172-
lines = [f"=== {name} ==="]
186+
lines = [name] if brief else [f"=== {name} ==="]
173187

174188
if isinstance(obj, ModuleType):
175-
lines.extend(format_module(obj))
189+
lines.extend(format_module(obj, brief))
176190
elif isinstance(obj, type):
177-
lines.extend(format_class(obj))
191+
lines.extend(format_class(obj, brief))
178192
elif callable(obj):
179-
lines.append("Type: function")
193+
if not brief:
194+
lines.append("Type: function")
180195
else:
181-
lines.append(f"Type: {type(obj).__name__}")
196+
if not brief:
197+
lines.append(f"Type: {type(obj).__name__}")
182198

183-
lines.append("")
184-
lines.append(f"Next: {next_name}" if next_name else "This is the last item.")
199+
if include_next and not brief:
200+
lines.append("")
201+
lines.append(f"Next: {next_name}" if next_name else "This is the last item.")
185202

186203
return "\n".join(lines)
187204

@@ -194,7 +211,7 @@ def find_item_index(items: list[tuple[str, object]], target: str) -> int | None:
194211
return None
195212

196213

197-
def handle_next(items: list[tuple[str, object]], target: str) -> None:
214+
def handle_next(items: list[tuple[str, object]], target: str, *, brief: bool) -> None:
198215
"""Handle --next command."""
199216
found_idx = find_item_index(items, target)
200217

@@ -211,7 +228,7 @@ def handle_next(items: list[tuple[str, object]], target: str) -> None:
211228
next_idx = found_idx + 1
212229
name, obj = items[next_idx]
213230
next_name = items[next_idx + 1][0] if next_idx + 1 < len(items) else None
214-
print(format_item(name, obj, next_name))
231+
print(format_item(name, obj, next_name, brief=brief, include_next=True))
215232

216233

217234
def main() -> None:
@@ -222,19 +239,38 @@ def main() -> None:
222239
group.add_argument("--start", action="store_true", help="Output the first item")
223240
group.add_argument("--next", metavar="NAME", help="Output the item after NAME")
224241
group.add_argument("--count", action="store_true", help="Output total item count")
242+
group.add_argument("--all", action="store_true", help="Output all items")
243+
parser.add_argument(
244+
"--brief",
245+
action="store_true",
246+
help="Output only names (no descriptive text)",
247+
)
225248

226249
args = parser.parse_args()
227250

228251
items = build_item_list()
229252

230253
if args.count:
231254
print(f"Total items: {len(items)}")
255+
elif args.all:
256+
formatted = [
257+
format_item(
258+
name,
259+
obj,
260+
None,
261+
brief=args.brief,
262+
include_next=False,
263+
)
264+
for name, obj in items
265+
]
266+
separator = "\n" if args.brief else "\n\n"
267+
print(separator.join(formatted))
232268
elif args.start:
233269
name, obj = items[0]
234270
next_name = items[1][0] if len(items) > 1 else None
235-
print(format_item(name, obj, next_name))
271+
print(format_item(name, obj, next_name, brief=args.brief, include_next=True))
236272
else:
237-
handle_next(items, args.next)
273+
handle_next(items, args.next, brief=args.brief)
238274

239275

240276
if __name__ == "__main__":

tests/test_introspect.py

Lines changed: 113 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
"""Unit tests for the introspection script."""
22

3+
import sys
34
from unittest.mock import MagicMock
45

56
from scripts.introspect import (
@@ -13,6 +14,7 @@
1314
get_module_contents,
1415
get_stdlib_modules,
1516
is_public,
17+
main,
1618
should_include_member,
1719
)
1820

@@ -253,37 +255,46 @@ class TestFormatModule:
253255
def test_includes_type_line(self) -> None:
254256
import collections
255257

256-
lines = format_module(collections)
258+
lines = format_module(collections, False)
257259
assert "Type: module" in lines
258260

259261
def test_includes_contents_header(self) -> None:
260262
import collections
261263

262-
lines = format_module(collections)
264+
lines = format_module(collections, False)
263265
assert "Contents:" in lines
264266

265267
def test_includes_module_contents(self) -> None:
266268
import collections
267269

268-
lines = format_module(collections)
270+
lines = format_module(collections, False)
269271
output = "\n".join(lines)
270272
assert "Counter" in output
271273
assert "deque" in output
272274

275+
def test_brief_outputs_names_only(self) -> None:
276+
import collections
277+
278+
lines = format_module(collections, True)
279+
output = "\n".join(lines)
280+
assert "Type: module" not in output
281+
assert "Contents:" not in output
282+
assert "Counter" in output
283+
273284

274285
class TestFormatClass:
275286
"""Tests for format_class function."""
276287

277288
def test_includes_type_line(self) -> None:
278-
lines = format_class(list)
289+
lines = format_class(list, False)
279290
assert "Type: class" in lines
280291

281292
def test_includes_methods_header(self) -> None:
282-
lines = format_class(list)
293+
lines = format_class(list, False)
283294
assert "Methods:" in lines
284295

285296
def test_includes_class_methods(self) -> None:
286-
lines = format_class(list)
297+
lines = format_class(list, False)
287298
output = "\n".join(lines)
288299
assert "append" in output
289300
assert "pop" in output
@@ -292,44 +303,110 @@ def test_empty_class_shows_no_members_message(self) -> None:
292303
class EmptyClass:
293304
pass
294305

295-
lines = format_class(EmptyClass)
306+
lines = format_class(EmptyClass, False)
296307
assert "(no public direct members)" in lines
297308

309+
def test_brief_outputs_names_only(self) -> None:
310+
lines = format_class(list, True)
311+
output = "\n".join(lines)
312+
assert "Type: class" not in output
313+
assert "Methods:" not in output
314+
assert "append" in output
315+
298316

299317
class TestFormatItem:
300318
"""Tests for format_item function."""
301319

302320
def test_includes_name_header(self) -> None:
303-
output = format_item("builtins.list", list, "builtins.locals")
321+
output = format_item(
322+
"builtins.list",
323+
list,
324+
"builtins.locals",
325+
brief=False,
326+
include_next=True,
327+
)
304328
assert "=== builtins.list ===" in output
305329

306330
def test_includes_next_line(self) -> None:
307-
output = format_item("builtins.list", list, "builtins.locals")
331+
output = format_item(
332+
"builtins.list",
333+
list,
334+
"builtins.locals",
335+
brief=False,
336+
include_next=True,
337+
)
308338
assert "Next: builtins.locals" in output
309339

310340
def test_last_item_message(self) -> None:
311-
output = format_item("builtins.list", list, None)
341+
output = format_item("builtins.list", list, None, brief=False, include_next=True)
312342
assert "This is the last item." in output
313343

314344
def test_formats_module(self) -> None:
315345
import collections
316346

317-
output = format_item("collections", collections, "collections.Counter")
347+
output = format_item(
348+
"collections",
349+
collections,
350+
"collections.Counter",
351+
brief=False,
352+
include_next=True,
353+
)
318354
assert "Type: module" in output
319355

320356
def test_formats_class(self) -> None:
321-
output = format_item("builtins.list", list, "builtins.locals")
357+
output = format_item(
358+
"builtins.list",
359+
list,
360+
"builtins.locals",
361+
brief=False,
362+
include_next=True,
363+
)
322364
assert "Type: class" in output
323365
assert "Methods:" in output
324366

325367
def test_formats_function(self) -> None:
326-
output = format_item("builtins.len", len, "builtins.license")
368+
output = format_item(
369+
"builtins.len",
370+
len,
371+
"builtins.license",
372+
brief=False,
373+
include_next=True,
374+
)
327375
assert "Type: function" in output
328376

329377
def test_formats_other_types(self) -> None:
330-
output = format_item("builtins.True", True, "builtins.tuple")
378+
output = format_item(
379+
"builtins.True",
380+
True,
381+
"builtins.tuple",
382+
brief=False,
383+
include_next=True,
384+
)
331385
assert "Type: bool" in output
332386

387+
def test_brief_formats_name_only_header(self) -> None:
388+
output = format_item(
389+
"builtins.list",
390+
list,
391+
"builtins.locals",
392+
brief=True,
393+
include_next=True,
394+
)
395+
assert output.splitlines()[0] == "builtins.list"
396+
assert "Type:" not in output
397+
assert "Next:" not in output
398+
399+
def test_brief_formats_function_as_name_only(self) -> None:
400+
output = format_item(
401+
"builtins.len",
402+
len,
403+
"builtins.license",
404+
brief=True,
405+
include_next=True,
406+
)
407+
assert output.strip() == "builtins.len"
408+
assert "Type:" not in output
409+
333410

334411
class TestFindItemIndex:
335412
"""Tests for find_item_index function."""
@@ -364,7 +441,13 @@ def test_full_workflow(self) -> None:
364441
# First item should be formattable
365442
first_name, first_obj = items[0]
366443
second_name = items[1][0] if len(items) > 1 else None
367-
output = format_item(first_name, first_obj, second_name)
444+
output = format_item(
445+
first_name,
446+
first_obj,
447+
second_name,
448+
brief=False,
449+
include_next=True,
450+
)
368451
assert first_name in output
369452

370453
# Should be able to find items
@@ -373,7 +456,13 @@ def test_full_workflow(self) -> None:
373456

374457
def test_builtins_list_format(self) -> None:
375458
"""Test that builtins.list formats correctly with expected methods."""
376-
output = format_item("builtins.list", list, "builtins.locals")
459+
output = format_item(
460+
"builtins.list",
461+
list,
462+
"builtins.locals",
463+
brief=False,
464+
include_next=True,
465+
)
377466

378467
assert "=== builtins.list ===" in output
379468
assert "Type: class" in output
@@ -390,3 +479,11 @@ def test_builtins_list_format(self) -> None:
390479
assert "reverse" in output
391480
assert "sort" in output
392481
assert "Next: builtins.locals" in output
482+
483+
def test_all_brief_has_no_blank_lines(self, monkeypatch, capsys) -> None:
484+
monkeypatch.setattr(sys, "argv", ["introspect.py", "--all", "--brief"])
485+
main()
486+
output = capsys.readouterr().out.strip()
487+
lines = output.splitlines()
488+
assert len(lines) > 1
489+
assert "" not in lines

0 commit comments

Comments
 (0)