Skip to content

Commit 7680622

Browse files
committed
Made traceback_width a Settable.
1 parent 74803c1 commit 7680622

5 files changed

Lines changed: 130 additions & 2 deletions

File tree

cmd2/cmd2.py

Lines changed: 45 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1332,7 +1332,21 @@ def allow_style_type(value: str) -> ru.AllowStyle:
13321332
)
13331333
self.add_settable(Settable("debug", bool, "Show full traceback on exception", self))
13341334
self.add_settable(Settable("echo", bool, "Echo command issued into output", self))
1335-
self.add_settable(Settable("editor", str, "Program used by 'edit'", self))
1335+
1336+
editor_description = Text.assemble(
1337+
"Program used by ",
1338+
("'edit'", Style(bold=True)),
1339+
" command",
1340+
)
1341+
self.add_settable(
1342+
Settable(
1343+
"editor",
1344+
str,
1345+
ru.rich_text_to_string(editor_description),
1346+
self,
1347+
)
1348+
)
1349+
13361350
self.add_settable(
13371351
Settable(
13381352
"max_completion_table_items",
@@ -1354,6 +1368,20 @@ def allow_style_type(value: str) -> ru.AllowStyle:
13541368
self.add_settable(Settable("timing", bool, "Report execution times", self))
13551369
self.add_settable(Settable("traceback_show_locals", bool, "Display local variables in tracebacks", self))
13561370

1371+
traceback_width_description = Text.assemble(
1372+
"Maximum display width for tracebacks. Set to ",
1373+
("None", Style(bold=True)),
1374+
" (case-insensitive) to fill entire terminal width.",
1375+
)
1376+
self.add_settable(
1377+
Settable(
1378+
"traceback_width",
1379+
utils.optional_int,
1380+
ru.rich_text_to_string(traceback_width_description),
1381+
self,
1382+
)
1383+
)
1384+
13571385
@property
13581386
def allow_style(self) -> ru.AllowStyle:
13591387
"""Property needed to support do_set when it reads allow_style."""
@@ -1380,6 +1408,22 @@ def traceback_show_locals(self, value: bool) -> None:
13801408
"""Setter property needed to support do_set when it updates traceback_show_locals."""
13811409
self.traceback_kwargs["show_locals"] = value
13821410

1411+
@property
1412+
def traceback_width(self) -> int | None:
1413+
"""Property needed to support do_set when it reads traceback_width."""
1414+
if "width" in self.traceback_kwargs:
1415+
return cast(int | None, self.traceback_kwargs["width"])
1416+
1417+
# If setting is not present, then return its default value.
1418+
traceback_sig = inspect.signature(Traceback.__init__)
1419+
width = traceback_sig.parameters["width"].default
1420+
return cast(int | None, width)
1421+
1422+
@traceback_width.setter
1423+
def traceback_width(self, value: int | None) -> None:
1424+
"""Setter property needed to support do_set when it updates traceback_width."""
1425+
self.traceback_kwargs["width"] = value
1426+
13831427
@property
13841428
def visible_prompt(self) -> str:
13851429
"""Read-only property to get the visible prompt with any ANSI style sequences stripped.

cmd2/pt_utils.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -175,7 +175,7 @@ def get_completions(self, document: Document, _complete_event: object) -> Iterab
175175
print_formatted_text(pt_filter_style("\n" + capture.get()))
176176

177177
if not completions:
178-
# # Print hint if present
178+
# Print hint if present
179179
if completions.hint:
180180
print_formatted_text(pt_filter_style(completions.hint))
181181
return

cmd2/utils.py

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,23 @@ def to_bool(val: Any) -> bool:
6464
return bool(val)
6565

6666

67+
def optional_int(val: Any) -> int | None:
68+
"""Convert a value to an integer or None if it's "None" (case-insensitive).
69+
70+
:param val: value being converted
71+
:return: int or None
72+
:raises ValueError: if the value is not "None" and cannot be converted to an integer
73+
"""
74+
if val is None:
75+
return None
76+
if isinstance(val, str) and val.lower() == "none":
77+
return None
78+
try:
79+
return int(val)
80+
except (ValueError, TypeError):
81+
raise ValueError("must be an integer or None (case-insensitive)") from None
82+
83+
6784
class Settable:
6885
"""Used to configure an attribute to be settable via the set command in the CLI."""
6986

tests/test_cmd2.py

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -267,6 +267,51 @@ def test_set_traceback_show_locals(base_app: cmd2.Cmd) -> None:
267267
assert base_app.traceback_show_locals is new_val
268268
assert base_app.traceback_kwargs["show_locals"] is new_val
269269

270+
# Test invalid input
271+
_out, err = run_cmd(base_app, "set traceback_show_locals invalid")
272+
assert base_app.last_result is False
273+
assert "must be True or False (case-insensitive)" in err[0]
274+
275+
276+
def test_set_traceback_width(base_app: cmd2.Cmd) -> None:
277+
"""Test the set command for reading and setting traceback_width."""
278+
279+
import inspect
280+
281+
from rich.traceback import Traceback
282+
283+
# Get Traceback's default value for "width"
284+
traceback_sig = inspect.signature(Traceback.__init__)
285+
default_val = traceback_sig.parameters["width"].default
286+
287+
# Clear any existing value
288+
base_app.traceback_kwargs.pop("width", None)
289+
assert "width" not in base_app.traceback_kwargs
290+
291+
# Test that we receive the default value if not present
292+
orig_val = base_app.traceback_width
293+
assert orig_val == default_val
294+
assert "width" not in base_app.traceback_kwargs
295+
296+
# Test setting it to an integer (handling case where orig_val is None)
297+
new_val = (orig_val or 0) + 100
298+
run_cmd(base_app, f"set traceback_width {new_val}")
299+
assert base_app.traceback_width == new_val
300+
assert base_app.traceback_kwargs["width"] == new_val
301+
302+
# Test setting to "None" (case-insensitive)
303+
run_cmd(base_app, "set traceback_width None")
304+
assert base_app.traceback_width is None
305+
assert base_app.traceback_kwargs["width"] is None # type: ignore[unreachable]
306+
307+
run_cmd(base_app, "set traceback_width none")
308+
assert base_app.traceback_width is None
309+
310+
# Test invalid input
311+
_out, err = run_cmd(base_app, "set traceback_width invalid")
312+
assert base_app.last_result is False
313+
assert "must be an integer or None" in err[0]
314+
270315

271316
def test_set_with_choices(base_app) -> None:
272317
"""Test choices validation of Settables"""

tests/test_utils.py

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -285,6 +285,28 @@ def test_to_bool_float() -> None:
285285
assert not cu.to_bool(0)
286286

287287

288+
def test_optional_int_none() -> None:
289+
assert cu.optional_int(None) is None
290+
assert cu.optional_int("none") is None
291+
assert cu.optional_int("None") is None
292+
assert cu.optional_int("nOnE") is None
293+
294+
295+
def test_optional_int_int() -> None:
296+
assert cu.optional_int(5) == 5
297+
assert cu.optional_int("5") == 5
298+
assert cu.optional_int("-10") == -10
299+
300+
301+
def test_optional_int_invalid() -> None:
302+
with pytest.raises(ValueError, match="must be an integer or None"):
303+
cu.optional_int("abc")
304+
with pytest.raises(ValueError, match="must be an integer or None"):
305+
cu.optional_int("3.14")
306+
with pytest.raises(ValueError, match="must be an integer or None"):
307+
cu.optional_int([])
308+
309+
288310
def test_find_editor_specified() -> None:
289311
expected_editor = os.path.join("fake_dir", "editor")
290312
with mock.patch.dict(os.environ, {"EDITOR": expected_editor}):

0 commit comments

Comments
 (0)