Skip to content

Add configurable color support to newa list command#349

Merged
kkaarreell merged 1 commit into
mainfrom
ks_list_color
Apr 17, 2026
Merged

Add configurable color support to newa list command#349
kkaarreell merged 1 commit into
mainfrom
ks_list_color

Conversation

@kkaarreell

@kkaarreell kkaarreell commented Apr 17, 2026

Copy link
Copy Markdown
Collaborator

This commit introduces a comprehensive color configuration system for the 'newa list' output, providing users with customizable color schemes while maintaining sensible defaults.

Features:

  • Automatic terminal color detection with TTY support
  • Environment variable controls (NO_COLOR, FORCE_COLOR)
  • Customizable color schemes via YAML configuration files
  • Separate color configuration file support
  • Built-in default colors that match current color scheme

Implementation:

  • Created color_utils.py with color detection and formatting functions
  • Created color_config.py for YAML-based configuration loading
  • Added newa_color_config setting to Settings model
  • Updated list_cmd.py to apply colors to all output elements
  • Added type annotations for mypy compatibility

Color categories:

  • Palette colors: state_dir, event, issue, request_id, reportportal
  • State colors: not_executed, running, complete, error, default
  • Result colors: passed, failed, none, cancelled, default

Configuration:
Users can specify color config via:

  • NEWA config file: [newa] color_config = /path/to/colors.yaml
  • Environment variable: NEWA_COLOR_CONFIG=/path/to/colors.yaml

If no color config is specified or a color is omitted from the config, built-in defaults are used, ensuring backward compatibility.

Documentation:

  • Added comprehensive README section explaining color configuration
  • Included example color config file with helpful comments
  • Documented ANSI color code reference for user convenience

All changes pass pre-commit checks (autopep8, mypy, ruff) and unit tests.

🤖 Generated with Claude Code

Summary by Sourcery

Add configurable ANSI color support to the newa list command, with auto-detection and YAML-based theming while preserving sensible defaults and a no-color mode.

New Features:

  • Introduce a color configuration system for CLI output driven by ANSI codes and YAML theme files.
  • Allow users to specify a color configuration path via config file or NEWA_COLOR_CONFIG environment variable.
  • Add per-category coloring for list output elements such as state directories, events, issues, request IDs, states, and results.

Enhancements:

  • Update the list command output to consistently apply colorization to key elements including states, results, events, issues, and ReportPortal info based on the active color scheme.
  • Add terminal capability and environment-variable–aware logic to enable, force, or disable colored output automatically.

Documentation:

  • Extend the README with usage and reference documentation for color configuration, including examples and ANSI color code guidance.
  • Add an example colors.yaml file demonstrating customizable palettes for the list command.

@kkaarreell kkaarreell self-assigned this Apr 17, 2026
@sourcery-ai

sourcery-ai Bot commented Apr 17, 2026

Copy link
Copy Markdown

Reviewer's Guide

Adds a configurable color system for the newa list command by introducing terminal-aware color utilities, YAML-based color configuration, a new settings option for color config path, and applying colorization throughout list output while documenting usage and providing an example config.

Sequence diagram for newa list color initialization and usage

sequenceDiagram
    actor User
    participant Shell
    participant NewaCLI as NewaCLI_process
    participant ListCmd as list_cmd
    participant ColorUtils as color_utils
    participant Colors
    participant ColorConfig
    participant YAMLParser as yaml_parser

    User->>Shell: run newa list
    Shell->>NewaCLI: invoke entrypoint
    NewaCLI->>ListCmd: cmd_list(ctx, ...)
    activate ListCmd
    ListCmd->>ColorUtils: init_colors(ctx.settings.newa_color_config)
    activate ColorUtils
    ColorUtils->>ColorUtils: should_use_colors()
    ColorUtils-->>ColorUtils: check NO_COLOR, FORCE_COLOR, isatty(), TERM
    alt colors_disabled
        ColorUtils->>Colors: disable()
        Colors-->>ColorUtils: colors set to empty strings
        ColorUtils-->>ListCmd: return
    else colors_enabled
        alt config_path_provided
            ColorUtils->>ColorConfig: load_from_file(Path(color_config_path))
            activate ColorConfig
            ColorConfig->>YAMLParser: yaml_parser().load(file)
            YAMLParser-->>ColorConfig: config_dict
            ColorConfig-->>ColorUtils: ColorConfig instance
            deactivate ColorConfig
            ColorUtils->>Colors: set palette, state, result attributes
        else no_config
            ColorUtils-->>ListCmd: use default Colors
        end
    end
    deactivate ColorUtils

    loop for each state_dir
        ListCmd->>Colors: read STATE_DIR or ORANGE
        ListCmd->>ColorUtils: colorize_text(state_dir, color_code)
        ColorUtils-->>ListCmd: colored_state_dir
        ListCmd->>stdout: print colored_state_dir

        loop for each event_job, jira_job, execute_job
            ListCmd->>ColorUtils: colorize_text(..., event/issue/request_id colors)
            ListCmd->>ColorUtils: colorize_state(state)
            ListCmd->>ColorUtils: colorize_result(result)
            ColorUtils-->>ListCmd: colored strings
            ListCmd->>stdout: print colored output
        end
    end
    deactivate ListCmd
Loading

Class diagram for NEWA color configuration and utilities

classDiagram
    class Settings {
        +Path newa_statedir_topdir
        +bool newa_clear_on_subcommand
        +str newa_color_config
        +str et_url
        +bool et_enable_comments
        +bool et_deduplicate_releases
    }

    class Colors {
        +str RESET
        +str RED
        +str GREEN
        +str YELLOW
        +str BLUE
        +str ORANGE
        +str PURPLE
        +str STATE_DIR
        +str EVENT
        +str ISSUE
        +str REQUEST_ID
        +str REPORTPORTAL
        +str STATE_NOT_EXECUTED
        +str STATE_RUNNING
        +str STATE_COMPLETE
        +str STATE_ERROR
        +str STATE_DEFAULT
        +str RESULT_PASSED
        +str RESULT_FAILED
        +str RESULT_NONE
        +str RESULT_CANCELLED
        +str RESULT_DEFAULT
        +disable() void
    }

    class ColorConfig {
        -dict~str, dict~str, str~~ config
        +ColorConfig(config_dict dict~str, dict~str, str~~)
        +get_color(category str, key str) str
        +load_from_file(filepath Path) ColorConfig
    }

    class ColorUtilsModule {
        +should_use_colors() bool
        +colorize(text str, color_code str) str
        +colorize_state(state str) str
        +colorize_result(result str) str
        +colorize_text(text str, color_code str) str
        +init_colors(color_config_path str) void
    }

    Settings --> ColorUtilsModule : uses init_colors
    ColorUtilsModule --> Colors : configures
    ColorUtilsModule --> ColorConfig : loads
    ColorConfig --> yaml_parser : uses
Loading

File-Level Changes

Change Details Files
Introduce reusable color utilities and configuration loading for CLI output
  • Added color_utils module with ANSI color constants, terminal color detection (NO_COLOR, FORCE_COLOR, TERM, TTY), and helpers to colorize generic text, states, and results
  • Defined a Colors class with default palette/state/result colors and the ability to globally disable colors when unsupported or opted out
  • Added init_colors to wire environment/terminal detection with optional YAML-based overrides, populating palette/state/result color attributes from config
newa/cli/color_utils.py
Add YAML-based color configuration support
  • Created ColorConfig class to wrap a nested dict of color categories and keys loaded from YAML
  • Implemented get_color to safely fetch colors per category/key and gracefully handle missing/malformed sections
  • Implemented load_from_file using the shared yaml_parser, returning an empty config on missing file or parse errors so defaults are preserved
newa/cli/color_config.py
Wire color configuration into settings and initialization of the list command
  • Extended Settings with a new newa_color_config field for configuring the color YAML path
  • Hooked newa_color_config into config/environment reading via _get, supporting both [newa] color_config and NEWA_COLOR_CONFIG
  • Initialized colors at the start of cmd_list using ctx.settings.newa_color_config so later output can rely on configured/default colors
newa/models/settings.py
newa/cli/commands/list_cmd.py
Apply colorization to list output while preserving sensible defaults
  • Colorized state directory names, events, issues, ReportPortal launch text, and request IDs using palette colors with fallbacks to base colors when no override is set
  • Updated printing of execution state and result to use colorize_state and colorize_result, and "not executed" to be rendered via the state colorizer for consistency
  • Kept formatting and content of messages intact aside from color codes, ensuring backward compatibility when colors are disabled
newa/cli/commands/list_cmd.py
newa/cli/color_utils.py
Document color configuration and provide an example configuration file
  • Added README section describing default color behavior, environment flags, configuration options, YAML format, and ANSI code reference
  • Provided an examples/colors.yaml file with a complete sample palette/states/results configuration and inline comments for common usage patterns
README.md
examples/colors.yaml

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

@sourcery-ai sourcery-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey - I've found 2 issues, and left some high level feedback:

  • The should_use_colors logic allows FORCE_COLOR to enable colors even when not in a TTY, but the README currently says colors are enabled only when FORCE_COLOR is not set; please adjust the docs or behavior so the precedence and effect of FORCE_COLOR are clearly consistent.
  • In ColorConfig.load_from_file, all exceptions are swallowed and the defaults are used silently; consider logging or surfacing a clear error when the YAML is malformed so users understand why their custom color config is not taking effect.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- The `should_use_colors` logic allows `FORCE_COLOR` to enable colors even when not in a TTY, but the README currently says colors are enabled only when `FORCE_COLOR` is not set; please adjust the docs or behavior so the precedence and effect of `FORCE_COLOR` are clearly consistent.
- In `ColorConfig.load_from_file`, all exceptions are swallowed and the defaults are used silently; consider logging or surfacing a clear error when the YAML is malformed so users understand why their custom color config is not taking effect.

## Individual Comments

### Comment 1
<location path="newa/cli/commands/list_cmd.py" line_range="150-153" />
<code_context>
                 jira_summary = f'- {jira_job.jira.summary}' if jira_job.jira.summary else ''
                 jira_action_id = jira_job.jira.action_id or 'no action id'
-                _print(4, f'issue {jira_job.jira.id} ({jira_action_id}) {jira_summary}')
+                _print(
+                    4,
+                    colorize_text(
+                        f'issue {
+                            jira_job.jira.id} ({jira_action_id}) {jira_summary}',
+                        issue_color))
</code_context>
<issue_to_address>
**issue (bug_risk):** The split f-string for the issue line appears syntactically invalid and may raise at import time.

The `f'issue {` string is broken across lines so that `{` is followed by a newline before the expression, which is invalid Python and will raise a `SyntaxError` at import. Please keep the f-string on a single logical line (using parentheses if needed) or build it first and then pass it to `colorize_text`, e.g.:

```python
iissue_line = f'issue {jira_job.jira.id} ({jira_action_id}) {jira_summary}'
_print(4, colorize_text(issue_line, issue_color))
```
</issue_to_address>

### Comment 2
<location path="newa/cli/color_config.py" line_range="55-59" />
<code_context>
+        if not filepath.exists():
+            return cls()
+
+        try:
+            with open(filepath) as f:
+                config_dict = yaml_parser().load(f)
+                return cls(config_dict or {})
+        except Exception:
+            # If file is malformed, return empty config (use defaults)
+            return cls()
</code_context>
<issue_to_address>
**suggestion (bug_risk):** Catching bare `Exception` on config load may hide actionable errors.

This `except Exception:` will hide real problems (e.g., permission issues, bad YAML, IO errors) and just fall back to defaults, making failures hard to diagnose. Please either catch the specific exceptions you expect (`OSError`, `yaml.YAMLError`, etc.) or log a warning before falling back so users can see why their config was ignored.

Suggested implementation:

```python
        try:
            with open(filepath) as f:
                config_dict = yaml_parser().load(f)
                return cls(config_dict or {})
        except (OSError, yaml.YAMLError) as exc:
            # If file is malformed or cannot be read, return empty config (use defaults)
            logger.warning(
                "Failed to load color configuration from %s: %s. Falling back to defaults.",
                filepath,
                exc,
            )
            return cls()

```

To make this compile and behave correctly, you will also need to:

1. Ensure `yaml.YAMLError` is available in this module, e.g. at the top of the file:
   - `import yaml` (or adjust the exception type if you are using a different YAML library/namespace).
2. Ensure a logger is defined in this module, e.g. near the imports:
   - `import logging`
   - `logger = logging.getLogger(__name__)`
3. If your project uses a different logging convention (e.g., `log` instead of `logger` or a shared logger utility), adjust the `logger.warning(...)` call to match that convention.
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

Comment thread newa/cli/commands/list_cmd.py Outdated
Comment thread newa/cli/color_config.py Outdated
This commit introduces a comprehensive color configuration system for the
'newa list' output, providing users with customizable color schemes while
maintaining sensible defaults.

Features:
- Automatic terminal color detection with TTY support
- Environment variable controls (NO_COLOR, FORCE_COLOR)
- Customizable color schemes via YAML configuration files
- Separate color configuration file support
- Built-in default colors that match current color scheme

Implementation:
- Created color_utils.py with color detection and formatting functions
- Created color_config.py for YAML-based configuration loading
- Added newa_color_config setting to Settings model
- Updated list_cmd.py to apply colors to all output elements
- Added type annotations for mypy compatibility

Color categories:
- Palette colors: state_dir, event, issue, request_id, reportportal
- State colors: not_executed, running, complete, error, default
- Result colors: passed, failed, none, cancelled, default

Configuration:
Users can specify color config via:
- NEWA config file: [newa] color_config = /path/to/colors.yaml
- Environment variable: NEWA_COLOR_CONFIG=/path/to/colors.yaml

If no color config is specified or a color is omitted from the config,
built-in defaults are used, ensuring backward compatibility.

Documentation:
- Added comprehensive README section explaining color configuration
- Included example color config file with helpful comments
- Documented ANSI color code reference for user convenience

All changes pass pre-commit checks (autopep8, mypy, ruff) and unit tests.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
@kkaarreell kkaarreell merged commit 48af733 into main Apr 17, 2026
18 checks passed
@kkaarreell kkaarreell deleted the ks_list_color branch April 17, 2026 13:17
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant