feat(output): render NetBox object colors with an independent toggle#122
Merged
Conversation
Preserve NetBox-native hex colors (role.color, tag.color, any FK/choice object exposing a 6-hex `color` sibling) so role/tag names render in their NetBox color in CLI table output (Rich `[#rrggbb]…[/]`) and TUI DataTable cells. `flatten(..., with_colors=True)` now emits a framework-free `ColoredValue` (or a list of them for tags) instead of collapsing to a bare display string, so the hex survives to the formatters. Bare strings flow exactly as before — existing callers are untouched unless they opt in. Add a dedicated `object_colors` config default (ObjectColorMode auto/on/off) and a `--object-colors/--no-object-colors` root flag, resolved into `RuntimeContext.object_colors`. Object coloring is gated by the existing color / no-color / non-tty resolution (never ANSI when color is off) but can be disabled independently while status colors stay on. JSON/YAML/CSV/JSONL are unchanged — they never request colors. Closes #120 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Object colors made flatten return a raw Python list mixing ColoredValue and str whenever only *some* list items carried a color (e.g. a tag list where one tag has a color and another doesn't). Downstream formatters gate the colored-list path on homogeneity (all() in table._is_colored_list and tui.view._cell), so a mixed list fell through to str(value) and emitted an ugly repr like [ColoredValue(text='a', color='ff0000'), 'b']. Promote plain strings to ColoredValue(text, None) so the list stays uniform whenever any item is colored; both formatters already handle a None color. Also document the resolve_object_colors contract: ON is intentionally identical to AUTO so --object-colors can never override the no-color decision (--no-color, NO_COLOR, non-tty); only OFF diverges. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Owner
Author
|
Fixed the verified review findings (323b6f4):
TDD: added failing-first tests in tests/output/test_flatten.py, tests/output/test_table_color.py, and tests/tui/test_view.py covering the mixed-color list at flatten + table + view layers. lint (ruff + mypy --strict) clean; full suite 1188 passed, 1 skipped. |
# Conflicts: # nsc/cli/tui_commands.py # nsc/tui/__init__.py # nsc/tui/app.py # tests/cli/test_tui_command.py
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Implements #120: render NetBox-native hex colors (role/tag/any
color-bearing FK/choice object) in CLI table output and TUI DataTable cells, with a dedicated toggle to disable it independently of--color.What changed
flatten._displayifyused to collapse{display|label, color}to just the display string, dropping the hex before any formatter saw it.flatten(..., with_colors=True)now emits a framework-freeColoredValue(text, color)(or alist[ColoredValue]for tags) when an object carries a valid 6-hexcolorsibling.with_colors=False(the default) returns today's bare strings, so existing callers are untouched.nsc/output/colors.py—ColoredValuedataclass +normalize_hex(strips a leading#, lowercases, rejects non-hex / wrong-length / empty / non-string). Imports nothing from cli/http/rich.render(..., object_colors=...)passeswith_colors = color and object_colors;_format_cellemits[#rrggbb]escape(text)[/]forColoredValueand comma-joins colored lists. Inner text is escaped on every path; status-color map remains the fallback for plain strings.build_rows(..., object_colors=...)yields RichText(text, style="#rrggbb")cells (comma-joined for tag lists);ListScreenreadsself.app.object_colors, threaded from the runtime context.Defaults.object_colors: ObjectColorMode(auto/on/off, default auto) +--object-colors/--no-object-colorsroot flag, resolved byresolve_object_colors(mode, *, color)intoRuntimeContext.object_colors. Always gated by the global color/no-color/tty resolution (never ANSI when color is off) but can be disabled independently while status colors stay on.ColoredValuenever reaches them.Tests
tests/output/test_colors.py(normalize_hex / ColoredValue).test_flatten.py: with_colors single object, list-of-tags, missing/invalid/empty color fallback, with_colors=False regression guards.test_table_color.py: hex markup, escaping, list join, object-colors-off-keeps-status-on, color-gating, dispatch forwarding, non-table CSV unaffected.test_runtime_color.py:resolve_object_colorsmatrix.test_models.py: default AUTO + parse on/off.test_view.py: styledTextcells + plain-string regression guards.just lint(ruff + ruff format + mypy --strict) andjust test(1183 passed, 1 skipped) both green.Closes #120
🤖 Generated with Claude Code