diff --git a/pyproject.toml b/pyproject.toml index c620336..5584e7a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "Coquille" -version = "1.0.2" +version = "1.1.0" authors = [{ name = "Qexat" }] description = "Coquille is a library that wraps terminal escape sequences as convenient functions." readme = "README.md" diff --git a/src/coquille/coquille.py b/src/coquille/coquille.py index e203583..b0f8996 100644 --- a/src/coquille/coquille.py +++ b/src/coquille/coquille.py @@ -2,9 +2,11 @@ from __future__ import annotations import sys +from abc import abstractmethod from collections.abc import Callable from dataclasses import dataclass from typing import overload +from typing import Protocol from typing import TYPE_CHECKING from coquille.sequences import EscapeSequence @@ -88,34 +90,24 @@ def apply( target.write(string) -class _ContextCoquille: - __slots__ = ("__sequences", "__file") # private slots +class CoquilleLike(Protocol): + sequences: list[EscapeSequence] + file: SupportsWrite[str] | None - def __init__( + @abstractmethod + def print( self, - sequences: list[EscapeSequence], - file: SupportsWrite[str] | None, + *values: object, + sep: str | None = None, + end: str | None = "\n", ) -> None: - self.__sequences = sequences - self.__file = file - - @property - def sequences(self) -> list[EscapeSequence]: - """ - Read-only ; the base sequences that were applied at the - beginning of the `with` block. They are reset when the - block ends. - """ + pass - return self.__sequences - @property - def file(self) -> SupportsWrite[str] | None: - """ - Read-only ; the file where the sequences are printed in. - """ - - return self.__file +@dataclass(slots=True) +class _ContextCoquille: + sequences: list[EscapeSequence] + file: SupportsWrite[str] | None def apply(self, sequence: EscapeSequence) -> None: """ @@ -153,7 +145,7 @@ def print( ``` """ - print(*values, sep=sep, end=end, file=self.file) + Coquille.print(self, *values, sep=sep, end=end) @dataclass(slots=True) @@ -187,28 +179,52 @@ def new( return cls(list(sequences), file) - @staticmethod + def print( + self: CoquilleLike, + *values: object, + sep: str | None = " ", + end: str | None = "\n", + ) -> None: + """ + Convenient function to print in the same file as the coquille's one. + + ## Example + + ```py + >>> my_coquille = Coquille.new(bold, fg_red, file=sys.stderr) + >>> # same as: print("My pretty error message", file=my_coquille.file) + >>> my_coquille.print("My pretty error message") + ``` + """ + + for sequence in self.sequences: + apply(sequence, self.file) + + print(*values, sep=sep, end=end, file=self.file) + apply(soft_reset) + def write( + self, text: str, - *sequences: EscapeSequence, end: str | None = "\n", - file: SupportsWrite[str] | None = None, ) -> None: """ - A function relatively similar to built-in `print`, but with - support of escape sequences that are prepended to the printed - text. + A function relatively similar to built-in `print`. + It is the same as naked `write`, but it uses the coquille's + registered sequences. Example: ```py + >>> from coquille import Coquille >>> from coquille.sequences import fg_magenta, italic - >>> Coquille.write("Hello World!", fg_magenta, italic) + >>> my_coquille = Coquille.new(fg_magenta, italic) + >>> my_coquille.write("Hello World!") Hello World! ``` Here, "Hello World!" is printed in italic and magenta, but this cannot be reproduced exactly in docstrings. - The previous example is roughly the same as doing: + The previous example is roughly equivalent to: ```py >>> print("\x1b[35m", end="") >>> print("\x1b[3m", end="") @@ -220,13 +236,9 @@ def write( because the range of allowed escape sequences is larger than SGR. """ - for sequence in sequences: - apply(sequence, file) - - print(text, end=end, file=file) - apply(soft_reset) + self.print(text, end=end) - def __enter__(self): + def __enter__(self) -> CoquilleLike: """ Set up a context for a Coquille. @@ -250,4 +262,40 @@ def __exit__(self, *_) -> None: apply(soft_reset, self.file) -write = Coquille.write +def write( + text: str, + *sequences: EscapeSequence, + end: str | None = "\n", + file: SupportsWrite[str] | None = None, +) -> None: + """ + A function relatively similar to built-in `print`, but with + support of escape sequences that are prepended to the printed + text. + + Example: + ```py + >>> from coquille.sequences import fg_magenta, italic + >>> Coquille.write("Hello World!", fg_magenta, italic) + Hello World! + ``` + Here, "Hello World!" is printed in italic and magenta, but this + cannot be reproduced exactly in docstrings. + + The previous example is roughly the same as doing: + ```py + >>> print("\x1b[35m", end="") + >>> print("\x1b[3m", end="") + >>> print("Hello World!") + >>> print("\x1b[!p", end="") + ``` + + Note that the soft reset sequence is used rather than SGR reset `x1b[0m`, + because the range of allowed escape sequences is larger than SGR. + """ + + for sequence in sequences: + apply(sequence, file) + + print(text, end=end, file=file) + apply(soft_reset) diff --git a/src/coquille/typeshed.py b/src/coquille/typeshed.py index 880eaa4..b6a736e 100644 --- a/src/coquille/typeshed.py +++ b/src/coquille/typeshed.py @@ -2,6 +2,7 @@ Typeshed stuff that Coquille needs but that are not available on the Python standard library. """ +from abc import abstractmethod from typing import Protocol from typing import TypeVar @@ -10,8 +11,9 @@ class SupportsWrite(Protocol[_T_contra]): + @abstractmethod def write(self, __s: _T_contra) -> object: - ... + pass Self = TypeVar("Self")