Skip to content

formatters

ruff_sync.formatters

Output formatters for CLI results.

LOGGER module-attribute

LOGGER = getLogger(__name__)

ResultFormatter

Bases: Protocol

Protocol for output formatters.

Source code in src/ruff_sync/formatters.py
class ResultFormatter(Protocol):
    """Protocol for output formatters."""

    def note(self, message: str) -> None:
        """Print a status note (unconditional)."""

    def info(self, message: str, logger: logging.Logger | None = None) -> None:
        """Print an informational message."""

    def success(self, message: str) -> None:
        """Print a success message."""

    def error(
        self,
        message: str,
        file_path: pathlib.Path | None = None,
        logger: logging.Logger | None = None,
    ) -> None:
        """Print an error message."""

    def warning(
        self,
        message: str,
        file_path: pathlib.Path | None = None,
        logger: logging.Logger | None = None,
    ) -> None:
        """Print a warning message."""

    def debug(self, message: str, logger: logging.Logger | None = None) -> None:
        """Print a debug message."""

    def diff(self, diff_text: str) -> None:
        """Print a unified diff between configurations."""

note

note(message)

Print a status note (unconditional).

Source code in src/ruff_sync/formatters.py
def note(self, message: str) -> None:
    """Print a status note (unconditional)."""

info

info(message, logger=None)

Print an informational message.

Source code in src/ruff_sync/formatters.py
def info(self, message: str, logger: logging.Logger | None = None) -> None:
    """Print an informational message."""

success

success(message)

Print a success message.

Source code in src/ruff_sync/formatters.py
def success(self, message: str) -> None:
    """Print a success message."""

error

error(message, file_path=None, logger=None)

Print an error message.

Source code in src/ruff_sync/formatters.py
def error(
    self,
    message: str,
    file_path: pathlib.Path | None = None,
    logger: logging.Logger | None = None,
) -> None:
    """Print an error message."""

warning

warning(message, file_path=None, logger=None)

Print a warning message.

Source code in src/ruff_sync/formatters.py
def warning(
    self,
    message: str,
    file_path: pathlib.Path | None = None,
    logger: logging.Logger | None = None,
) -> None:
    """Print a warning message."""

debug

debug(message, logger=None)

Print a debug message.

Source code in src/ruff_sync/formatters.py
def debug(self, message: str, logger: logging.Logger | None = None) -> None:
    """Print a debug message."""

diff

diff(diff_text)

Print a unified diff between configurations.

Source code in src/ruff_sync/formatters.py
def diff(self, diff_text: str) -> None:
    """Print a unified diff between configurations."""

TextFormatter

Standard text output formatter.

Delegates diagnostic messages (info, warning, error, debug) to the project logger to ensure they benefit from standard logging configuration (colors, streams). Primary command feedback (note, success) is printed to stdout.

Source code in src/ruff_sync/formatters.py
class TextFormatter:
    """Standard text output formatter.

    Delegates diagnostic messages (info, warning, error, debug) to the project logger
     to ensure they benefit from standard logging configuration (colors, streams).
    Primary command feedback (note, success) is printed to stdout.
    """

    def note(self, message: str) -> None:
        """Print a status note to stdout."""
        print(message)

    def info(self, message: str, logger: logging.Logger | None = None) -> None:
        """Log an info message."""
        (logger or LOGGER).info(message)

    def success(self, message: str) -> None:
        """Print a success message to stdout."""
        print(message)

    def error(
        self,
        message: str,
        file_path: pathlib.Path | None = None,
        logger: logging.Logger | None = None,
    ) -> None:
        """Log an error message."""
        (logger or LOGGER).error(message)

    def warning(
        self,
        message: str,
        file_path: pathlib.Path | None = None,
        logger: logging.Logger | None = None,
    ) -> None:
        """Log a warning message."""
        (logger or LOGGER).warning(message)

    def debug(self, message: str, logger: logging.Logger | None = None) -> None:
        """Log a debug message."""
        (logger or LOGGER).debug(message)

    def diff(self, diff_text: str) -> None:
        """Print a unified diff directly to stdout."""
        print(diff_text, end="")

note

note(message)

Print a status note to stdout.

Source code in src/ruff_sync/formatters.py
def note(self, message: str) -> None:
    """Print a status note to stdout."""
    print(message)

info

info(message, logger=None)

Log an info message.

Source code in src/ruff_sync/formatters.py
def info(self, message: str, logger: logging.Logger | None = None) -> None:
    """Log an info message."""
    (logger or LOGGER).info(message)

success

success(message)

Print a success message to stdout.

Source code in src/ruff_sync/formatters.py
def success(self, message: str) -> None:
    """Print a success message to stdout."""
    print(message)

error

error(message, file_path=None, logger=None)

Log an error message.

Source code in src/ruff_sync/formatters.py
def error(
    self,
    message: str,
    file_path: pathlib.Path | None = None,
    logger: logging.Logger | None = None,
) -> None:
    """Log an error message."""
    (logger or LOGGER).error(message)

warning

warning(message, file_path=None, logger=None)

Log a warning message.

Source code in src/ruff_sync/formatters.py
def warning(
    self,
    message: str,
    file_path: pathlib.Path | None = None,
    logger: logging.Logger | None = None,
) -> None:
    """Log a warning message."""
    (logger or LOGGER).warning(message)

debug

debug(message, logger=None)

Log a debug message.

Source code in src/ruff_sync/formatters.py
def debug(self, message: str, logger: logging.Logger | None = None) -> None:
    """Log a debug message."""
    (logger or LOGGER).debug(message)

diff

diff(diff_text)

Print a unified diff directly to stdout.

Source code in src/ruff_sync/formatters.py
def diff(self, diff_text: str) -> None:
    """Print a unified diff directly to stdout."""
    print(diff_text, end="")

GithubFormatter

GitHub Actions output formatter.

Emits ::error:: and ::warning:: workflow commands for inline annotations.

Source code in src/ruff_sync/formatters.py
class GithubFormatter:
    """GitHub Actions output formatter.

    Emits `::error::` and `::warning::` workflow commands for inline annotations.
    """

    @staticmethod
    def _escape(value: str, is_property: bool = False) -> str:
        r"""Escapes characters for GitHub Actions workflow commands.

        GitHub requires percent-encoding for '%', '\r', and '\n' in all messages.
        Additionally, property values (like file and title) require escaping for ':' and ','.
        """
        escaped = value.replace("%", "%25").replace("\r", "%0D").replace("\n", "%0A")
        if is_property:
            return escaped.replace(":", "%3A").replace(",", "%2C")
        return escaped

    def note(self, message: str) -> None:
        """Print a status note (standard stdout)."""
        print(message)

    def info(self, message: str, logger: logging.Logger | None = None) -> None:
        """Print an info message (delegates to logger)."""
        (logger or LOGGER).info(message)

    def success(self, message: str) -> None:
        """Print a success message (standard stdout)."""
        print(message)

    def error(
        self,
        message: str,
        file_path: pathlib.Path | None = None,
        logger: logging.Logger | None = None,
    ) -> None:
        """Print an error message as a GitHub Action error annotation."""
        # Delegate standard log output to the logger to preserve context
        (logger or LOGGER).error(message)

        # Strip emoji/symbols if any for the raw title, or just use a generic title
        file_val = self._escape(str(file_path), is_property=True) if file_path else ""
        file_arg = f"file={file_val},line=1," if file_path else ""
        title_val = self._escape("Ruff Sync Error", is_property=True)

        # The message is technically what we pass after ::
        # E.g. ::error file=app.js,line=1::Missing semicolon
        clean_msg = message.removeprefix("❌ ").removeprefix("⚠️ ")
        escaped_msg = self._escape(clean_msg)
        print(f"::error {file_arg}title={title_val}::{escaped_msg}")

    def warning(
        self,
        message: str,
        file_path: pathlib.Path | None = None,
        logger: logging.Logger | None = None,
    ) -> None:
        """Print a warning message as a GitHub Action warning annotation."""
        (logger or LOGGER).warning(message)

        file_val = self._escape(str(file_path), is_property=True) if file_path else ""
        file_arg = f"file={file_val},line=1," if file_path else ""
        title_val = self._escape("Ruff Sync Warning", is_property=True)

        clean_msg = message.removeprefix("❌ ").removeprefix("⚠️ ")
        escaped_msg = self._escape(clean_msg)
        print(f"::warning {file_arg}title={title_val}::{escaped_msg}")

    def debug(self, message: str, logger: logging.Logger | None = None) -> None:
        """Print a debug message as a GitHub Action debug annotation."""
        (logger or LOGGER).debug(message)
        escaped_msg = self._escape(message)
        print(f"::debug::{escaped_msg}")

    def diff(self, diff_text: str) -> None:
        """Print a unified diff in GitHub Actions logs (standard stdout)."""
        print(diff_text, end="")

note

note(message)

Print a status note (standard stdout).

Source code in src/ruff_sync/formatters.py
def note(self, message: str) -> None:
    """Print a status note (standard stdout)."""
    print(message)

info

info(message, logger=None)

Print an info message (delegates to logger).

Source code in src/ruff_sync/formatters.py
def info(self, message: str, logger: logging.Logger | None = None) -> None:
    """Print an info message (delegates to logger)."""
    (logger or LOGGER).info(message)

success

success(message)

Print a success message (standard stdout).

Source code in src/ruff_sync/formatters.py
def success(self, message: str) -> None:
    """Print a success message (standard stdout)."""
    print(message)

error

error(message, file_path=None, logger=None)

Print an error message as a GitHub Action error annotation.

Source code in src/ruff_sync/formatters.py
def error(
    self,
    message: str,
    file_path: pathlib.Path | None = None,
    logger: logging.Logger | None = None,
) -> None:
    """Print an error message as a GitHub Action error annotation."""
    # Delegate standard log output to the logger to preserve context
    (logger or LOGGER).error(message)

    # Strip emoji/symbols if any for the raw title, or just use a generic title
    file_val = self._escape(str(file_path), is_property=True) if file_path else ""
    file_arg = f"file={file_val},line=1," if file_path else ""
    title_val = self._escape("Ruff Sync Error", is_property=True)

    # The message is technically what we pass after ::
    # E.g. ::error file=app.js,line=1::Missing semicolon
    clean_msg = message.removeprefix("❌ ").removeprefix("⚠️ ")
    escaped_msg = self._escape(clean_msg)
    print(f"::error {file_arg}title={title_val}::{escaped_msg}")

warning

warning(message, file_path=None, logger=None)

Print a warning message as a GitHub Action warning annotation.

Source code in src/ruff_sync/formatters.py
def warning(
    self,
    message: str,
    file_path: pathlib.Path | None = None,
    logger: logging.Logger | None = None,
) -> None:
    """Print a warning message as a GitHub Action warning annotation."""
    (logger or LOGGER).warning(message)

    file_val = self._escape(str(file_path), is_property=True) if file_path else ""
    file_arg = f"file={file_val},line=1," if file_path else ""
    title_val = self._escape("Ruff Sync Warning", is_property=True)

    clean_msg = message.removeprefix("❌ ").removeprefix("⚠️ ")
    escaped_msg = self._escape(clean_msg)
    print(f"::warning {file_arg}title={title_val}::{escaped_msg}")

debug

debug(message, logger=None)

Print a debug message as a GitHub Action debug annotation.

Source code in src/ruff_sync/formatters.py
def debug(self, message: str, logger: logging.Logger | None = None) -> None:
    """Print a debug message as a GitHub Action debug annotation."""
    (logger or LOGGER).debug(message)
    escaped_msg = self._escape(message)
    print(f"::debug::{escaped_msg}")

diff

diff(diff_text)

Print a unified diff in GitHub Actions logs (standard stdout).

Source code in src/ruff_sync/formatters.py
def diff(self, diff_text: str) -> None:
    """Print a unified diff in GitHub Actions logs (standard stdout)."""
    print(diff_text, end="")

JsonFormatter

JSON output formatter.

Source code in src/ruff_sync/formatters.py
class JsonFormatter:
    """JSON output formatter."""

    def note(self, message: str) -> None:
        """Print a status note as JSON."""
        print(json.dumps({"level": "note", "message": message}))

    def info(self, message: str, logger: logging.Logger | None = None) -> None:
        """Print an info message as JSON."""
        data = {"level": "info", "message": message}
        if logger:
            data["logger"] = logger.name
        print(json.dumps(data))

    def success(self, message: str) -> None:
        """Print a success message as JSON."""
        print(json.dumps({"level": "success", "message": message}))

    def error(
        self,
        message: str,
        file_path: pathlib.Path | None = None,
        logger: logging.Logger | None = None,
    ) -> None:
        """Print an error message as JSON."""
        data = {"level": "error", "message": message}
        if file_path:
            data["file"] = str(file_path)
        if logger:
            data["logger"] = logger.name
        print(json.dumps(data))

    def warning(
        self,
        message: str,
        file_path: pathlib.Path | None = None,
        logger: logging.Logger | None = None,
    ) -> None:
        """Print a warning message as JSON."""
        data = {"level": "warning", "message": message}
        if file_path:
            data["file"] = str(file_path)
        if logger:
            data["logger"] = logger.name
        print(json.dumps(data))

    def debug(self, message: str, logger: logging.Logger | None = None) -> None:
        """Print a debug message as JSON."""
        data = {"level": "debug", "message": message}
        if logger:
            data["logger"] = logger.name
        print(json.dumps(data))

    def diff(self, diff_text: str) -> None:
        """Print a unified diff as JSON."""
        # Strip trailing newline if any, as it's common in diff text
        print(json.dumps({"level": "diff", "message": diff_text.strip()}))

note

note(message)

Print a status note as JSON.

Source code in src/ruff_sync/formatters.py
def note(self, message: str) -> None:
    """Print a status note as JSON."""
    print(json.dumps({"level": "note", "message": message}))

info

info(message, logger=None)

Print an info message as JSON.

Source code in src/ruff_sync/formatters.py
def info(self, message: str, logger: logging.Logger | None = None) -> None:
    """Print an info message as JSON."""
    data = {"level": "info", "message": message}
    if logger:
        data["logger"] = logger.name
    print(json.dumps(data))

success

success(message)

Print a success message as JSON.

Source code in src/ruff_sync/formatters.py
def success(self, message: str) -> None:
    """Print a success message as JSON."""
    print(json.dumps({"level": "success", "message": message}))

error

error(message, file_path=None, logger=None)

Print an error message as JSON.

Source code in src/ruff_sync/formatters.py
def error(
    self,
    message: str,
    file_path: pathlib.Path | None = None,
    logger: logging.Logger | None = None,
) -> None:
    """Print an error message as JSON."""
    data = {"level": "error", "message": message}
    if file_path:
        data["file"] = str(file_path)
    if logger:
        data["logger"] = logger.name
    print(json.dumps(data))

warning

warning(message, file_path=None, logger=None)

Print a warning message as JSON.

Source code in src/ruff_sync/formatters.py
def warning(
    self,
    message: str,
    file_path: pathlib.Path | None = None,
    logger: logging.Logger | None = None,
) -> None:
    """Print a warning message as JSON."""
    data = {"level": "warning", "message": message}
    if file_path:
        data["file"] = str(file_path)
    if logger:
        data["logger"] = logger.name
    print(json.dumps(data))

debug

debug(message, logger=None)

Print a debug message as JSON.

Source code in src/ruff_sync/formatters.py
def debug(self, message: str, logger: logging.Logger | None = None) -> None:
    """Print a debug message as JSON."""
    data = {"level": "debug", "message": message}
    if logger:
        data["logger"] = logger.name
    print(json.dumps(data))

diff

diff(diff_text)

Print a unified diff as JSON.

Source code in src/ruff_sync/formatters.py
def diff(self, diff_text: str) -> None:
    """Print a unified diff as JSON."""
    # Strip trailing newline if any, as it's common in diff text
    print(json.dumps({"level": "diff", "message": diff_text.strip()}))

get_formatter

get_formatter(output_format)

Return the corresponding formatter for the given format.

Source code in src/ruff_sync/formatters.py
def get_formatter(output_format: OutputFormat) -> ResultFormatter:
    """Return the corresponding formatter for the given format."""
    if output_format == OutputFormat.GITHUB:
        return GithubFormatter()
    if output_format == OutputFormat.JSON:
        return JsonFormatter()
    return TextFormatter()