diff --git a/colorama/__init__.pyi b/colorama/__init__.pyi new file mode 100644 index 0000000..e6d15ec --- /dev/null +++ b/colorama/__init__.pyi @@ -0,0 +1,9 @@ +from .ansi import Back as Back, Cursor as Cursor, Fore as Fore, Style as Style +from .ansitowin32 import AnsiToWin32 as AnsiToWin32 +from .initialise import ( + colorama_text as colorama_text, + deinit as deinit, + init as init, + just_fix_windows_console as just_fix_windows_console, + reinit as reinit, +) diff --git a/colorama/ansi.pyi b/colorama/ansi.pyi new file mode 100644 index 0000000..aba73e0 --- /dev/null +++ b/colorama/ansi.pyi @@ -0,0 +1,71 @@ +from typing_extensions import Final + +CSI: str +OSC: str +BEL: str + +def code_to_chars(code: int) -> str: ... +def set_title(title: str) -> str: ... +def clear_screen(mode: int = 2) -> str: ... +def clear_line(mode: int = 2) -> str: ... + +class AnsiCodes: + def __init__(self) -> None: ... + +class AnsiCursor: + def UP(self, n: int = 1) -> str: ... + def DOWN(self, n: int = 1) -> str: ... + def FORWARD(self, n: int = 1) -> str: ... + def BACK(self, n: int = 1) -> str: ... + def POS(self, x: int = 1, y: int = 1) -> str: ... + +# All attributes in the following classes are string in instances and int in the class. +# We use str since that is the common case for users. +class AnsiFore(AnsiCodes): + BLACK: Final = 30 + RED: Final = 31 + GREEN: Final = 32 + YELLOW: Final = 33 + BLUE: Final = 34 + MAGENTA: Final = 35 + CYAN: Final = 36 + WHITE: Final = 37 + RESET: Final = 39 + LIGHTBLACK_EX: Final = 90 + LIGHTRED_EX: Final = 91 + LIGHTGREEN_EX: Final = 92 + LIGHTYELLOW_EX: Final = 93 + LIGHTBLUE_EX: Final = 94 + LIGHTMAGENTA_EX: Final = 95 + LIGHTCYAN_EX: Final = 96 + LIGHTWHITE_EX: Final = 97 + +class AnsiBack(AnsiCodes): + BLACK: Final = 40 + RED: Final = 41 + GREEN: Final = 42 + YELLOW: Final = 43 + BLUE: Final = 44 + MAGENTA: Final = 45 + CYAN: Final = 46 + WHITE: Final = 47 + RESET: Final = 49 + LIGHTBLACK_EX: Final = 100 + LIGHTRED_EX: Final = 101 + LIGHTGREEN_EX: Final = 102 + LIGHTYELLOW_EX: Final = 103 + LIGHTBLUE_EX: Final = 104 + LIGHTMAGENTA_EX: Final = 105 + LIGHTCYAN_EX: Final = 106 + LIGHTWHITE_EX: Final = 107 + +class AnsiStyle(AnsiCodes): + BRIGHT: Final = 1 + DIM: Final = 2 + NORMAL: Final = 22 + RESET_ALL: Final = 0 + +Fore: AnsiFore +Back: AnsiBack +Style: AnsiStyle +Cursor: AnsiCursor diff --git a/colorama/ansitowin32.pyi b/colorama/ansitowin32.pyi new file mode 100644 index 0000000..094a08e --- /dev/null +++ b/colorama/ansitowin32.pyi @@ -0,0 +1,54 @@ +import sys +from _typeshed import SupportsWrite +from collections.abc import Callable, Sequence +from re import Pattern +from types import TracebackType +from typing import Any, ClassVar, TextIO +from typing_extensions import TypeAlias + +if sys.platform == "win32": + from .winterm import WinTerm + + winterm: WinTerm +else: + winterm: None + +class StreamWrapper: + def __init__(self, wrapped: TextIO, converter: SupportsWrite[str]) -> None: ... + def __getattr__(self, name: str) -> Any: ... + def __enter__(self, *args: object, **kwargs: object) -> TextIO: ... + def __exit__( + self, __t: type[BaseException] | None, __value: BaseException | None, __traceback: TracebackType | None, **kwargs: Any + ) -> None: ... + def write(self, text: str) -> None: ... + def isatty(self) -> bool: ... + @property + def closed(self) -> bool: ... + +_WinTermCall: TypeAlias = Callable[[int | None, bool, bool], None] +_WinTermCallDict: TypeAlias = dict[int, tuple[_WinTermCall] | tuple[_WinTermCall, int] | tuple[_WinTermCall, int, bool]] + +class AnsiToWin32: + ANSI_CSI_RE: ClassVar[Pattern[str]] + ANSI_OSC_RE: ClassVar[Pattern[str]] + wrapped: TextIO + autoreset: bool + stream: StreamWrapper + strip: bool + convert: bool + win32_calls: _WinTermCallDict + on_stderr: bool + def __init__( + self, wrapped: TextIO, convert: bool | None = None, strip: bool | None = None, autoreset: bool = False + ) -> None: ... + def should_wrap(self) -> bool: ... + def get_win32_calls(self) -> _WinTermCallDict: ... + def write(self, text: str) -> None: ... + def reset_all(self) -> None: ... + def write_and_convert(self, text: str) -> None: ... + def write_plain_text(self, text: str, start: int, end: int) -> None: ... + def convert_ansi(self, paramstring: str, command: str) -> None: ... + def extract_params(self, command: str, paramstring: str) -> tuple[int, ...]: ... + def call_win32(self, command: str, params: Sequence[int]) -> None: ... + def convert_osc(self, text: str) -> str: ... + def flush(self) -> None: ... diff --git a/colorama/initialise.pyi b/colorama/initialise.pyi new file mode 100644 index 0000000..bd22891 --- /dev/null +++ b/colorama/initialise.pyi @@ -0,0 +1,23 @@ +from contextlib import AbstractContextManager +from typing import Any, TextIO, TypeVar + +from .ansitowin32 import StreamWrapper + +_TextIOT = TypeVar("_TextIOT", bound=TextIO) + +orig_stdout: TextIO | None +orig_stderr: TextIO | None +wrapped_stdout: TextIO | StreamWrapper | None +wrapped_stderr: TextIO | StreamWrapper | None +atexit_done: bool +fixed_windows_console: bool + +def reset_all() -> None: ... +def init(autoreset: bool = False, convert: bool | None = None, strip: bool | None = None, wrap: bool = True) -> None: ... +def deinit() -> None: ... +def colorama_text(*args: Any, **kwargs: Any) -> AbstractContextManager[None]: ... +def reinit() -> None: ... +def wrap_stream( + stream: _TextIOT, convert: bool | None, strip: bool | None, autoreset: bool, wrap: bool +) -> _TextIOT | StreamWrapper: ... +def just_fix_windows_console() -> None: ... diff --git a/colorama/py.typed b/colorama/py.typed new file mode 100644 index 0000000..e69de29 diff --git a/colorama/tests/__init__.pyi b/colorama/tests/__init__.pyi new file mode 100644 index 0000000..e69de29 diff --git a/colorama/tests/ansi_test.pyi b/colorama/tests/ansi_test.pyi new file mode 100644 index 0000000..d135046 --- /dev/null +++ b/colorama/tests/ansi_test.pyi @@ -0,0 +1,14 @@ +from ..ansi import Back as Back, Fore as Fore, Style as Style +from ..ansitowin32 import AnsiToWin32 as AnsiToWin32 +from _typeshed import Incomplete +from unittest import TestCase + +stdout_orig: Incomplete +stderr_orig: Incomplete + +class AnsiTest(TestCase): + def setUp(self) -> None: ... + def tearDown(self) -> None: ... + def testForeAttributes(self) -> None: ... + def testBackAttributes(self) -> None: ... + def testStyleAttributes(self) -> None: ... diff --git a/colorama/tests/ansitowin32_test.pyi b/colorama/tests/ansitowin32_test.pyi new file mode 100644 index 0000000..4dda256 --- /dev/null +++ b/colorama/tests/ansitowin32_test.pyi @@ -0,0 +1,32 @@ +from ..ansitowin32 import AnsiToWin32 as AnsiToWin32, StreamWrapper as StreamWrapper +from ..win32 import ENABLE_VIRTUAL_TERMINAL_PROCESSING as ENABLE_VIRTUAL_TERMINAL_PROCESSING +from .utils import osname as osname +from unittest import TestCase + +class StreamWrapperTest(TestCase): + def testIsAProxy(self) -> None: ... + def testDelegatesWrite(self) -> None: ... + def testDelegatesContext(self) -> None: ... + def testProxyNoContextManager(self) -> None: ... + def test_closed_shouldnt_raise_on_closed_stream(self) -> None: ... + def test_closed_shouldnt_raise_on_detached_stream(self) -> None: ... + +class AnsiToWin32Test(TestCase): + def testInit(self) -> None: ... + def testStripIsTrueOnWindows(self) -> None: ... + def testStripIsFalseOffWindows(self) -> None: ... + def testWriteStripsAnsi(self) -> None: ... + def testWriteDoesNotStripAnsi(self) -> None: ... + def assert_autoresets(self, convert: bool, autoreset: bool = ...) -> None: ... + def testWriteAutoresets(self) -> None: ... + def testWriteAndConvertWritesPlainText(self) -> None: ... + def testWriteAndConvertStripsAllValidAnsi(self) -> None: ... + def testWriteAndConvertSkipsEmptySnippets(self) -> None: ... + def testWriteAndConvertCallsWin32WithParamsAndCommand(self) -> None: ... + def test_reset_all_shouldnt_raise_on_closed_orig_stdout(self) -> None: ... + def test_wrap_shouldnt_raise_on_closed_orig_stdout(self) -> None: ... + def test_wrap_shouldnt_raise_on_missing_closed_attr(self) -> None: ... + def testExtractParams(self) -> None: ... + def testCallWin32UsesLookup(self) -> None: ... + def test_osc_codes(self) -> None: ... + def test_native_windows_ansi(self) -> None: ... diff --git a/colorama/tests/initialise_test.pyi b/colorama/tests/initialise_test.pyi new file mode 100644 index 0000000..0e4dc85 --- /dev/null +++ b/colorama/tests/initialise_test.pyi @@ -0,0 +1,29 @@ +from ..ansitowin32 import StreamWrapper as StreamWrapper +from ..initialise import init as init, just_fix_windows_console as just_fix_windows_console +from .utils import osname as osname, replace_by as replace_by +from _typeshed import Incomplete +from unittest import TestCase +from unittest.mock import Mock as Mock + +orig_stdout: Incomplete +orig_stderr: Incomplete + +class InitTest(TestCase): + def setUp(self) -> None: ... + def tearDown(self) -> None: ... + def assertWrapped(self) -> None: ... + def assertNotWrapped(self) -> None: ... + def testInitWrapsOnWindows(self, _: Mock) -> None: ... + def testInitDoesntWrapOnEmulatedWindows(self, _: Mock) -> None: ... + def testInitDoesntWrapOnNonWindows(self) -> None: ... + def testInitDoesntWrapIfNone(self) -> None: ... + def testInitAutoresetOnWrapsOnAllPlatforms(self) -> None: ... + def testInitWrapOffDoesntWrapOnWindows(self) -> None: ... + def testInitWrapOffIncompatibleWithAutoresetOn(self) -> None: ... + def testAutoResetPassedOn(self, mockATW32: Mock, _: Mock) -> None: ... + def testAutoResetChangeable(self, mockATW32: Mock) -> None: ... + def testAtexitRegisteredOnlyOnce(self, mockRegister: Mock) -> None: ... + +class JustFixWindowsConsoleTest(TestCase): + def tearDown(self) -> None: ... + def testJustFixWindowsConsole(self) -> None: ... diff --git a/colorama/tests/isatty_test.pyi b/colorama/tests/isatty_test.pyi new file mode 100644 index 0000000..941cfbf --- /dev/null +++ b/colorama/tests/isatty_test.pyi @@ -0,0 +1,14 @@ +from ..ansitowin32 import AnsiToWin32 as AnsiToWin32, StreamWrapper as StreamWrapper +from .utils import StreamNonTTY as StreamNonTTY, StreamTTY as StreamTTY, pycharm as pycharm, replace_by as replace_by, replace_original_by as replace_original_by +from unittest import TestCase + +def is_a_tty(stream: StreamTTY) -> bool: ... + +class IsattyTest(TestCase): + def test_TTY(self) -> None: ... + def test_nonTTY(self) -> None: ... + def test_withPycharm(self) -> None: ... + def test_withPycharmTTYOverride(self) -> None: ... + def test_withPycharmNonTTYOverride(self) -> None: ... + def test_withPycharmNoneOverride(self) -> None: ... + def test_withPycharmStreamWrapped(self) -> None: ... diff --git a/colorama/tests/utils.pyi b/colorama/tests/utils.pyi new file mode 100644 index 0000000..2e087ef --- /dev/null +++ b/colorama/tests/utils.pyi @@ -0,0 +1,13 @@ +from collections.abc import Generator +from io import StringIO + +class StreamTTY(StringIO): + def isatty(self) -> bool: ... + +class StreamNonTTY(StringIO): + def isatty(self) -> bool: ... + +def osname(name: str) -> Generator[None, None, None]: ... +def replace_by(stream: StreamTTY) -> Generator[None, None, None]: ... +def replace_original_by(stream: StreamTTY) -> Generator[None, None, None]: ... +def pycharm() -> Generator[None, None, None]: ... diff --git a/colorama/tests/winterm_test.pyi b/colorama/tests/winterm_test.pyi new file mode 100644 index 0000000..19c5e0c --- /dev/null +++ b/colorama/tests/winterm_test.pyi @@ -0,0 +1,12 @@ +from unittest import TestCase +from unittest.mock import Mock + +class WinTermTest(TestCase): + def testInit(self, mockWin32: Mock) -> None: ... + def testGetAttrs(self) -> None: ... + def testResetAll(self, mockWin32: Mock) -> None: ... + def testFore(self) -> None: ... + def testBack(self) -> None: ... + def testStyle(self) -> None: ... + def testSetConsole(self, mockWin32: Mock) -> None: ... + def testSetConsoleOnStderr(self, mockWin32: Mock) -> None: ... diff --git a/colorama/win32.pyi b/colorama/win32.pyi new file mode 100644 index 0000000..7423c14 --- /dev/null +++ b/colorama/win32.pyi @@ -0,0 +1,34 @@ +import sys +from collections.abc import Callable +from typing_extensions import Literal + +STDOUT: Literal[-11] +STDERR: Literal[-12] +ENABLE_VIRTUAL_TERMINAL_PROCESSING: int + +if sys.platform == "win32": + from ctypes import LibraryLoader, Structure, WinDLL, wintypes + + windll: LibraryLoader[WinDLL] + COORD = wintypes._COORD + + class CONSOLE_SCREEN_BUFFER_INFO(Structure): + dwSize: COORD + dwCursorPosition: COORD + wAttributes: wintypes.WORD + srWindow: wintypes.SMALL_RECT + dwMaximumWindowSize: COORD + def winapi_test() -> bool: ... + def GetConsoleScreenBufferInfo(stream_id: int = -11) -> CONSOLE_SCREEN_BUFFER_INFO: ... + def SetConsoleTextAttribute(stream_id: int, attrs: wintypes.WORD) -> wintypes.BOOL: ... + def SetConsoleCursorPosition(stream_id: int, position: COORD, adjust: bool = True) -> wintypes.BOOL: ... + def FillConsoleOutputCharacter(stream_id: int, char: str, length: int, start: COORD) -> int: ... + def FillConsoleOutputAttribute(stream_id: int, attr: int, length: int, start: COORD) -> wintypes.BOOL: ... + def SetConsoleTitle(title: str) -> wintypes.BOOL: ... + def GetConsoleMode(handle: int) -> int: ... + def SetConsoleMode(handle: int, mode: int) -> None: ... + +else: + windll: None + SetConsoleTextAttribute: Callable[..., None] + winapi_test: Callable[..., None] diff --git a/colorama/winterm.pyi b/colorama/winterm.pyi new file mode 100644 index 0000000..038f398 --- /dev/null +++ b/colorama/winterm.pyi @@ -0,0 +1,38 @@ +import sys +from typing_extensions import Final + +if sys.platform == "win32": + from . import win32 + + class WinColor: + BLACK: Final = 0 + BLUE: Final = 1 + GREEN: Final = 2 + CYAN: Final = 3 + RED: Final = 4 + MAGENTA: Final = 5 + YELLOW: Final = 6 + GREY: Final = 7 + + class WinStyle: + NORMAL: Final = 0x00 + BRIGHT: Final = 0x08 + BRIGHT_BACKGROUND: Final = 0x80 + + class WinTerm: + def __init__(self) -> None: ... + def get_attrs(self) -> int: ... + def set_attrs(self, value: int) -> None: ... + def reset_all(self, on_stderr: bool | None = None) -> None: ... + def fore(self, fore: int | None = None, light: bool = False, on_stderr: bool = False) -> None: ... + def back(self, back: int | None = None, light: bool = False, on_stderr: bool = False) -> None: ... + def style(self, style: int | None = None, on_stderr: bool = False) -> None: ... + def set_console(self, attrs: int | None = None, on_stderr: bool = False) -> None: ... + def get_position(self, handle: int) -> win32.COORD: ... + def set_cursor_position(self, position: win32.COORD | None = None, on_stderr: bool = False) -> None: ... + def cursor_adjust(self, x: int, y: int, on_stderr: bool = False) -> None: ... + def erase_screen(self, mode: int = 0, on_stderr: bool = False) -> None: ... + def erase_line(self, mode: int = 0, on_stderr: bool = False) -> None: ... + def set_title(self, title: str) -> None: ... + +def enable_vt_processing(fd: int) -> bool: ... diff --git a/pyproject.toml b/pyproject.toml index 4e6a996..efa188a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -63,3 +63,7 @@ include = [ include = [ "/colorama/*", ] + +[tool.mypy] +strict = true +exclude = 'demos/*' diff --git a/tox.ini b/tox.ini index 769212a..b7f4a3d 100644 --- a/tox.ini +++ b/tox.ini @@ -7,3 +7,9 @@ deps = py27,pypy: mock py27,pypy: contextlib2 commands = python -m unittest discover -p *_test.py + +[testenv:mypy] +deps = mypy +commands = + mypy . + python -m mypy.stubtest --ignore-missing-stub colorama