Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(cli): Improve colors in CLI #960

Merged
merged 4 commits into from
Jan 8, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion skore/src/skore/cli/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,15 @@
import argparse
from importlib.metadata import version

from skore.cli.color_format import ColorArgumentParser
from skore.cli.launch_dashboard import __launch
from skore.cli.quickstart_command import __quickstart
from skore.project.create import _create


def cli(args: list[str]):
"""CLI for Skore."""
parser = argparse.ArgumentParser(prog="skore")
parser = ColorArgumentParser(prog="skore")

parser.add_argument(
"--version", action="version", version=f"%(prog)s {version('skore')}"
Expand Down
106 changes: 106 additions & 0 deletions skore/src/skore/cli/color_format.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
"""Custom help formatter for the CLI."""

import re
import shutil
from argparse import ArgumentParser, HelpFormatter

from rich.console import Console
from rich.theme import Theme

skore_console_theme = Theme(
{
"repr.str": "cyan",
"rule.line": "orange1",
"repr.url": "orange1",
}
)


class RichColorHelpFormatter(HelpFormatter):
"""Custom help formatter for the CLI."""

def __init__(self, prog, indent_increment=2, max_help_position=24, width=None):
width = shutil.get_terminal_size()[0] if width is None else width
super().__init__(prog, indent_increment, max_help_position, width)
self.console = Console(theme=skore_console_theme)

def _format_action_invocation(self, action):
"""Format the action invocation (flags and arguments)."""
if not action.option_strings:
metavar = self._metavar_formatter(action, action.dest)(1)[0]
return metavar
else:
parts = []
# Format short options
if action.option_strings:
parts.extend(
f"[cyan bold]{opt}[/cyan bold]" for opt in action.option_strings
)
# Format argument
if action.nargs != 0:
default = self._get_default_metavar_for_optional(action)
args_string = self._format_args(action, default)
parts.append(f"[orange1 bold]{args_string}[/orange1 bold]")

return " ".join(parts)

def _format_usage(self, usage, actions, groups, prefix):
"""Format the usage line."""
if prefix is None:
prefix = "usage: "

# Format the usage line
formatted = super()._format_usage(usage, actions, groups, prefix)

# Apply rich formatting
formatted = re.sub(r"usage:", "[orange1 bold]usage:[/orange1 bold]", formatted)
formatted = re.sub(
r"(?<=\[)[A-Z_]+(?=\])", lambda m: f"[cyan]{m.group()}[/cyan]", formatted
)

return formatted

def format_help(self):
"""Format the help message."""
help_text = super().format_help()

# Format section headers
help_text = re.sub(
r"^([a-zA-Z ]+ arguments:)$",
r"[dim]\1[/dim]",
help_text,
flags=re.MULTILINE,
)

# Format default values
help_text = re.sub(
r"\(default: .*?\)", lambda m: f"[dim]{m.group()}[/dim]", help_text
)

# Color the subcommands in cyan
help_text = re.sub(
r"(?<=\s)(launch|create|quickstart)(?=\s+)",
r"[cyan bold]\1[/cyan bold]",
help_text,
)

# Color "options" in orange1
help_text = re.sub(
r"(?<=\s)(options)(?=:)",
r"[orange1]\1[/orange1]",
help_text,
)

return help_text


class ColorArgumentParser(ArgumentParser):
"""Custom argument parser for the CLI."""

def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs, formatter_class=RichColorHelpFormatter)

def print_help(self, file=None):
"""Print the help message."""
console = Console(file=file)
console.print(self.format_help())
Loading