Source code for click_extra.output

# Copyright Kevin Deldycke <kevin@deldycke.com> and contributors.
#
# This program is Free Software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
"""Send command output to a file path or to stdout.

Helpers for the common `--output` option pattern, where a `-` value means
"write to stdout" instead of creating a file literally named `-`.
"""

from __future__ import annotations

import sys

TYPE_CHECKING = False
if TYPE_CHECKING:
    from pathlib import Path
    from typing import IO


STDOUT_SENTINEL = "-"
"""Conventional `--output` value asking for stdout instead of a file on disk."""


[docs] def is_stdout(path: Path) -> bool: """Return `True` when *path* is the stdout sentinel `-`. Guards against accidentally creating a file literally named `-` in the current directory. """ return str(path) == STDOUT_SENTINEL
[docs] def prep_path(path: Path) -> IO[str]: """Open *path* for writing as UTF-8 text, or return stdout for `-`. Always yields a UTF-8 stream, stdout included, sidestepping the `UnicodeEncodeError` a non-ASCII payload triggers on Windows, where the console defaults to `cp1252`. For a real path, missing parent directories are created first, absorbing the `mkdir -p` a caller would otherwise need. ```{note} When stdout is an in-memory capture with no backing file descriptor (Click's test runner, the Sphinx `{click:run}` directive that live-renders CLI output in the docs), `fileno()` raises and the existing stream is returned as-is. Such streams are already Python text objects, so the Windows `cp1252` concern does not apply: that only bites a real terminal, which always has a descriptor. ``` :param path: The destination path, or `-` for stdout. :return: A writable text stream. The caller closes it; the stdout stream is wrapped with `closefd=False`, so closing it leaves the real stdout open. """ if is_stdout(path): try: fd = sys.stdout.fileno() except (OSError, ValueError): return sys.stdout return open(fd, "w", encoding="utf-8", closefd=False) path.parent.mkdir(parents=True, exist_ok=True) return path.open("w", encoding="utf-8")