Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 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
61 changes: 61 additions & 0 deletions python/packages/jumpstarter/jumpstarter/client/grpc.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

import json
import logging
import os
import sys
from collections import OrderedDict
from dataclasses import InitVar, dataclass, field
from datetime import datetime, timedelta
Expand All @@ -20,6 +22,39 @@
logger = logging.getLogger(__name__)


_EMOJI_TERM_PREFIXES = (
"xterm",
"screen",
"tmux",
"rxvt",
"alacritty",
"kitty",
"wezterm",
"foot",
"ghostty",
"contour",
"rio",
)
"""Terminal type prefixes whose modern implementations reliably render emoji."""


def _use_emoji() -> bool:
"""Return True when the output terminal is likely to support emoji.

Falls back to ASCII indicators when any of the following is true:
* ``NO_COLOR`` environment variable is set (spirit: plain text output).
* ``stdout`` is not a TTY (output piped to a file / another process).
* ``TERM`` is not set or does not match a known emoji-capable prefix
(e.g. ``linux``, ``vt100``, ``dumb``, ``ansi`` all fall back to ASCII).
"""
if os.environ.get("NO_COLOR") is not None:
return False
if not hasattr(sys.stdout, "isatty") or not sys.stdout.isatty():
return False
term = os.environ.get("TERM", "")
return term.startswith(_EMOJI_TERM_PREFIXES)


@dataclass
class WithOptions:
show_online: bool = False
Expand All @@ -31,6 +66,7 @@ class WithOptions:
def add_display_columns(table, options: WithOptions = None):
if options is None:
options = WithOptions()
table.add_column(" ")
table.add_column("NAME")
if options.show_disabled:
table.add_column("ENABLED")
Expand All @@ -49,6 +85,7 @@ def add_exporter_row(table, exporter, options: WithOptions = None, lease_info: t
if options is None:
options = WithOptions()
row_data = []
row_data.append(exporter.status_icon())
row_data.append(exporter.name)
if options.show_disabled:
row_data.append("yes" if exporter.enabled else "no")
Expand Down Expand Up @@ -149,6 +186,30 @@ def rich_add_rows(self, table, options: WithOptions = None):
lease_info = ("", "Available", "")
add_exporter_row(table, self, options, lease_info)

def status_icon(self) -> str:
"""Return an icon representing the exporter's runtime status.

Uses emoji when the terminal supports it, otherwise falls back to
ASCII characters (respects ``TERM=dumb``, ``NO_COLOR``, and non-TTY
output).
"""
emoji = _use_emoji()
if self.status is None:
return "❓" if emoji else "?"
match self.status:
case ExporterStatus.AVAILABLE:
return "🟒" if emoji else "+"
case ExporterStatus.OFFLINE:
return "πŸ”΄" if emoji else "-"
case ExporterStatus.BEFORE_LEASE_HOOK | ExporterStatus.AFTER_LEASE_HOOK:
return "βš™οΈ" if emoji else "*"
case ExporterStatus.LEASE_READY:
return "⏳" if emoji else "~"
case ExporterStatus.BEFORE_LEASE_HOOK_FAILED | ExporterStatus.AFTER_LEASE_HOOK_FAILED:
return "❗" if emoji else "!"
case _:
return "❓" if emoji else "?"

def rich_add_names(self, names):
names.append(self.name)

Expand Down
Loading
Loading