clitint 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- clitint/__init__.py +42 -0
- clitint/_tint.py +291 -0
- clitint/py.typed +0 -0
- clitint-0.1.0.dist-info/METADATA +92 -0
- clitint-0.1.0.dist-info/RECORD +8 -0
- clitint-0.1.0.dist-info/WHEEL +5 -0
- clitint-0.1.0.dist-info/licenses/LICENSE +21 -0
- clitint-0.1.0.dist-info/top_level.txt +1 -0
clitint/__init__.py
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
"""clitint: coloured terminal text with no dependencies, plain wherever it isn't read on a terminal."""
|
|
2
|
+
from ._tint import (
|
|
3
|
+
formatter,
|
|
4
|
+
strip_colours,
|
|
5
|
+
colours_on,
|
|
6
|
+
turn_colours_off,
|
|
7
|
+
turn_colours_on,
|
|
8
|
+
let_the_environment_decide,
|
|
9
|
+
coloured,
|
|
10
|
+
decide,
|
|
11
|
+
decision,
|
|
12
|
+
release,
|
|
13
|
+
add_off_switch,
|
|
14
|
+
escapes,
|
|
15
|
+
one_decision,
|
|
16
|
+
ATTRIBUTES,
|
|
17
|
+
COLOURS,
|
|
18
|
+
BACKGROUNDS,
|
|
19
|
+
RESET,
|
|
20
|
+
)
|
|
21
|
+
|
|
22
|
+
__version__ = "0.1.0"
|
|
23
|
+
|
|
24
|
+
__all__ = [
|
|
25
|
+
"formatter",
|
|
26
|
+
"strip_colours",
|
|
27
|
+
"colours_on",
|
|
28
|
+
"turn_colours_off",
|
|
29
|
+
"turn_colours_on",
|
|
30
|
+
"let_the_environment_decide",
|
|
31
|
+
"one_decision",
|
|
32
|
+
"coloured",
|
|
33
|
+
"decide",
|
|
34
|
+
"decision",
|
|
35
|
+
"release",
|
|
36
|
+
"add_off_switch",
|
|
37
|
+
"escapes",
|
|
38
|
+
"ATTRIBUTES",
|
|
39
|
+
"COLOURS",
|
|
40
|
+
"BACKGROUNDS",
|
|
41
|
+
"RESET",
|
|
42
|
+
]
|
clitint/_tint.py
ADDED
|
@@ -0,0 +1,291 @@
|
|
|
1
|
+
from typing import Any
|
|
2
|
+
import re
|
|
3
|
+
import os
|
|
4
|
+
import sys
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
#* The variables that decide whether text is coloured (see ``colours_on``), in the order they are asked.
|
|
8
|
+
COLOURS_OFF = "CLITINT_COLOURS_OFF"
|
|
9
|
+
NO_COLOR = "NO_COLOR"
|
|
10
|
+
ANSI_COLORS_DISABLED = "ANSI_COLORS_DISABLED"
|
|
11
|
+
FORCE_COLOR = "FORCE_COLOR"
|
|
12
|
+
TERM = "TERM"
|
|
13
|
+
|
|
14
|
+
#* The attributes a spec may name, and the sequence that switches each on. Written in front of a colour.
|
|
15
|
+
ATTRIBUTES: dict[str, str] = {
|
|
16
|
+
"bold": "\x1b[1m", "dim": "\x1b[2m", "italic": "\x1b[3m", "underline": "\x1b[4m",
|
|
17
|
+
}
|
|
18
|
+
#* The colours, as the text's colour. ``highlight_<colour>`` is the same colour as its background.
|
|
19
|
+
#* The sequences are the ones termcolor writes (``orange`` is its (255, 165, 0)), so text made
|
|
20
|
+
#* with termcolor and with clitint is the same, byte for byte.
|
|
21
|
+
COLOURS: dict[str, str] = {
|
|
22
|
+
"black": "\x1b[30m", "red": "\x1b[31m", "green": "\x1b[32m", "yellow": "\x1b[33m", "blue": "\x1b[34m",
|
|
23
|
+
"magenta": "\x1b[35m", "cyan": "\x1b[36m", "white": "\x1b[37m", "grey": "\x1b[90m",
|
|
24
|
+
"orange": "\x1b[38;2;255;165;0m",
|
|
25
|
+
}
|
|
26
|
+
BACKGROUNDS: dict[str, str] = {
|
|
27
|
+
"black": "\x1b[40m", "red": "\x1b[41m", "green": "\x1b[42m", "yellow": "\x1b[43m", "blue": "\x1b[44m",
|
|
28
|
+
"magenta": "\x1b[45m", "cyan": "\x1b[46m", "white": "\x1b[47m", "grey": "\x1b[100m",
|
|
29
|
+
"orange": "\x1b[48;2;255;165;0m",
|
|
30
|
+
}
|
|
31
|
+
RESET = "\x1b[0m"
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
#* What ``turn_colours_off`` or ``turn_colours_on`` chose, or None: the environment and the streams decide.
|
|
35
|
+
_CHOSEN: bool | None = None
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def turn_colours_off() -> None:
|
|
39
|
+
"""Plain text from now on, whatever the environment and the streams say (as ``CLITINT_COLOURS_OFF=1``)."""
|
|
40
|
+
global _CHOSEN
|
|
41
|
+
_CHOSEN = False
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def turn_colours_on() -> None:
|
|
45
|
+
"""Coloured text from now on, also where it goes to no terminal (as ``FORCE_COLOR``)."""
|
|
46
|
+
global _CHOSEN
|
|
47
|
+
_CHOSEN = True
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def let_the_environment_decide() -> None:
|
|
51
|
+
"""Undo ``turn_colours_off`` / ``turn_colours_on``: the environment and the streams decide again."""
|
|
52
|
+
global _CHOSEN
|
|
53
|
+
_CHOSEN = None
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def add_off_switch(name: str) -> None:
|
|
57
|
+
"""
|
|
58
|
+
Make ``name=1`` switch colours off too, as ``CLITINT_COLOURS_OFF=1`` does: for a library
|
|
59
|
+
that offers its users a variable of its own (``add_off_switch("MYLIB_COLOURS_OFF")``). Only
|
|
60
|
+
the value ``"1"`` counts. Asked with the other off switches, before ``FORCE_COLOR``.
|
|
61
|
+
"""
|
|
62
|
+
global _OFF, _K_OFFS
|
|
63
|
+
if not isinstance(name, str) or not name:
|
|
64
|
+
raise TypeError(f"an off switch is the name of an environment variable, not {name!r}")
|
|
65
|
+
if name in _OFF:
|
|
66
|
+
return
|
|
67
|
+
_OFF = (*_OFF, name)
|
|
68
|
+
if _FAST:
|
|
69
|
+
_K_OFFS = (*_K_OFFS, _encode_key(name))
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def colours_on() -> bool:
|
|
73
|
+
"""
|
|
74
|
+
Whether text is coloured now.
|
|
75
|
+
|
|
76
|
+
A string doesn't know where it will end up: an uncaught error goes to stderr, ``print`` to
|
|
77
|
+
stdout, a logger to a file. So colours are used only when both standard streams are
|
|
78
|
+
terminals. Redirect either one (``2> errors.log``, ``| less``, a CI job) and text is plain:
|
|
79
|
+
no escape sequence lands in a file.
|
|
80
|
+
|
|
81
|
+
``turn_colours_off`` and ``turn_colours_on`` decide before anything else. Then the
|
|
82
|
+
environment, in this order: ``CLITINT_COLOURS_OFF=1`` (and each ``add_off_switch``), ``NO_COLOR`` and
|
|
83
|
+
``ANSI_COLORS_DISABLED`` switch colours off, ``FORCE_COLOR`` switches them on whatever the
|
|
84
|
+
streams are, and ``TERM=dumb`` switches them off. Read at every call, so a change takes
|
|
85
|
+
effect at once.
|
|
86
|
+
"""
|
|
87
|
+
if _CHOSEN is not None: # turn_colours_off / turn_colours_on: the program has said
|
|
88
|
+
return _CHOSEN
|
|
89
|
+
env = os.environ
|
|
90
|
+
#* Read from ``os.environ`` as it is now: a program may have put another mapping in its place.
|
|
91
|
+
data = getattr(env, "_data", None) if _FAST else None
|
|
92
|
+
if type(data) is not dict:
|
|
93
|
+
if NO_COLOR in env or ANSI_COLORS_DISABLED in env:
|
|
94
|
+
return False
|
|
95
|
+
for name in _OFF:
|
|
96
|
+
if env.get(name) == "1":
|
|
97
|
+
return False
|
|
98
|
+
if FORCE_COLOR in env:
|
|
99
|
+
return True
|
|
100
|
+
if env.get(TERM) == "dumb":
|
|
101
|
+
return False
|
|
102
|
+
return _both_terminals()
|
|
103
|
+
if _K_NO_COLOR in data or _K_ANSI in data:
|
|
104
|
+
return False
|
|
105
|
+
for key in _K_OFFS:
|
|
106
|
+
if data.get(key) == _V_ONE:
|
|
107
|
+
return False
|
|
108
|
+
if _K_FORCE in data:
|
|
109
|
+
return True
|
|
110
|
+
if data.get(_K_TERM) == _V_DUMB:
|
|
111
|
+
return False
|
|
112
|
+
return _both_terminals()
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
#* ``os.environ`` encodes the name at every lookup and decodes the value: five of them took 1 µs.
|
|
116
|
+
#* Below it is the dictionary it keeps, which is asked directly, with the names and the two
|
|
117
|
+
#* values encoded once, the way ``os.environ`` does it on this platform. It is the same data:
|
|
118
|
+
#* what ``os.environ`` is given lands there. Where a Python has no such dictionary (it is a
|
|
119
|
+
#* CPython detail), ``os.environ`` is asked as usual.
|
|
120
|
+
#* The variables that switch colours off with the value "1": clitint's own, then each ``add_off_switch``.
|
|
121
|
+
_OFF: tuple[str, ...] = (COLOURS_OFF,)
|
|
122
|
+
_K_OFFS: tuple[Any, ...] = ()
|
|
123
|
+
_FAST = False
|
|
124
|
+
try:
|
|
125
|
+
_encode_key, _encode_value = os.environ.encodekey, os.environ.encodevalue
|
|
126
|
+
_K_OFFS = (_encode_key(COLOURS_OFF),)
|
|
127
|
+
_K_NO_COLOR, _K_ANSI, _K_FORCE, _K_TERM = (
|
|
128
|
+
_encode_key(name) for name in (NO_COLOR, ANSI_COLORS_DISABLED, FORCE_COLOR, TERM)
|
|
129
|
+
)
|
|
130
|
+
_V_ONE, _V_DUMB = _encode_value("1"), _encode_value("dumb")
|
|
131
|
+
_FAST = type(os.environ._data) is dict # type: ignore[attr-defined]
|
|
132
|
+
except Exception:
|
|
133
|
+
_FAST = False
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
#* (sys.stdout, sys.stderr, whether both are terminals): asked again only when a stream was replaced.
|
|
137
|
+
_streams: tuple[Any, Any, bool] = (None, None, False)
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
def _both_terminals() -> bool:
|
|
141
|
+
global _streams
|
|
142
|
+
out, err = sys.stdout, sys.stderr
|
|
143
|
+
if _streams[0] is out and _streams[1] is err:
|
|
144
|
+
return _streams[2]
|
|
145
|
+
answer = _is_terminal(out) and _is_terminal(err)
|
|
146
|
+
_streams = (out, err, answer)
|
|
147
|
+
return answer
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
def _is_terminal(stream: Any) -> bool:
|
|
151
|
+
try:
|
|
152
|
+
return bool(stream.isatty())
|
|
153
|
+
except Exception: # no stream at all (pythonw), a closed one, an object without isatty
|
|
154
|
+
return False
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
#* While one message is built: the answer of ``colours_on`` for all of it (None: ask each time).
|
|
158
|
+
_decided: bool | None = None
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
def decide() -> bool:
|
|
162
|
+
"""
|
|
163
|
+
Ask ``colours_on`` once and keep the answer: every ``formatter`` and ``coloured`` call gives
|
|
164
|
+
it until ``release()``. For a message built of many coloured words (the environment is slow
|
|
165
|
+
to read). The functions are the fast form of ``one_decision``; mind to call ``release``.
|
|
166
|
+
"""
|
|
167
|
+
global _decided
|
|
168
|
+
_decided = answer = colours_on()
|
|
169
|
+
return answer
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
def decision() -> bool | None:
|
|
173
|
+
"""The answer ``decide`` kept, or None where nothing has been decided."""
|
|
174
|
+
return _decided
|
|
175
|
+
|
|
176
|
+
|
|
177
|
+
def release() -> None:
|
|
178
|
+
"""Forget the answer ``decide`` kept: each call asks ``colours_on`` again."""
|
|
179
|
+
global _decided
|
|
180
|
+
_decided = None
|
|
181
|
+
|
|
182
|
+
|
|
183
|
+
class one_decision:
|
|
184
|
+
"""
|
|
185
|
+
``with one_decision():`` around the building of one message asks once whether it is
|
|
186
|
+
coloured, where every ``formatter`` call in it would ask again. Inside another one it keeps
|
|
187
|
+
the outer answer, and leaves it in place. Not for threads that build messages at once: the
|
|
188
|
+
decision is the module's.
|
|
189
|
+
"""
|
|
190
|
+
__slots__ = ("_outer",)
|
|
191
|
+
|
|
192
|
+
def __enter__(self) -> bool:
|
|
193
|
+
global _decided
|
|
194
|
+
self._outer = _decided
|
|
195
|
+
if _decided is None:
|
|
196
|
+
_decided = colours_on()
|
|
197
|
+
return _decided
|
|
198
|
+
|
|
199
|
+
def __exit__(self, *_: Any) -> None:
|
|
200
|
+
global _decided
|
|
201
|
+
_decided = self._outer
|
|
202
|
+
return
|
|
203
|
+
|
|
204
|
+
|
|
205
|
+
def coloured() -> bool:
|
|
206
|
+
"""Whether the message being built is coloured: the one decision made for it, or ``colours_on()``."""
|
|
207
|
+
return colours_on() if _decided is None else _decided
|
|
208
|
+
|
|
209
|
+
|
|
210
|
+
def _escapes(spec: str) -> tuple[str, str]:
|
|
211
|
+
"""
|
|
212
|
+
What is written in front of and behind a text for ``spec``: the attributes' sequences, then
|
|
213
|
+
the colour's, then the background's, and the sequence that switches everything off.
|
|
214
|
+
"""
|
|
215
|
+
attributes: list[str] = []
|
|
216
|
+
colour = background = ""
|
|
217
|
+
names = spec.split("_")
|
|
218
|
+
i = 0
|
|
219
|
+
while i < len(names):
|
|
220
|
+
name = names[i]
|
|
221
|
+
if name == "highlight" and i + 1 < len(names) and names[i + 1] in BACKGROUNDS:
|
|
222
|
+
if background:
|
|
223
|
+
raise ValueError(f"two backgrounds in {spec!r}")
|
|
224
|
+
background = BACKGROUNDS[names[i + 1]]
|
|
225
|
+
i += 2
|
|
226
|
+
continue
|
|
227
|
+
if name in ATTRIBUTES:
|
|
228
|
+
if ATTRIBUTES[name] not in attributes:
|
|
229
|
+
attributes.append(ATTRIBUTES[name])
|
|
230
|
+
elif name in COLOURS:
|
|
231
|
+
if colour and colour != COLOURS[name]:
|
|
232
|
+
raise ValueError(f"two colours in {spec!r}: name one, and 'highlight_<colour>' for a background")
|
|
233
|
+
colour = COLOURS[name]
|
|
234
|
+
else:
|
|
235
|
+
raise ValueError(
|
|
236
|
+
f"unknown style {name!r} in {spec!r}: a spec is made of {', '.join(ATTRIBUTES)}, "
|
|
237
|
+
f"{', '.join(COLOURS)} and highlight_<colour>, joined by '_'"
|
|
238
|
+
)
|
|
239
|
+
i += 1
|
|
240
|
+
order = list(ATTRIBUTES.values())
|
|
241
|
+
attributes.sort(key = order.index)
|
|
242
|
+
return "".join(attributes) + colour + background, RESET
|
|
243
|
+
|
|
244
|
+
|
|
245
|
+
#* spec -> what ``_escapes`` answered.
|
|
246
|
+
_ESCAPES: dict[str, tuple[str, str]] = {}
|
|
247
|
+
|
|
248
|
+
|
|
249
|
+
def formatter(text: Any, spec: str) -> str:
|
|
250
|
+
"""
|
|
251
|
+
``text`` in the styles ``spec`` names, or as it is when colours are off (see ``colours_on``).
|
|
252
|
+
|
|
253
|
+
Args:
|
|
254
|
+
text (Any): The text, or any object: its ``str`` is what is written.
|
|
255
|
+
spec (str): Styles joined by ``_``, in any order: ``"bold_red"``, ``"cyan"``,
|
|
256
|
+
``"underline_white_highlight_blue"``. Attributes: ``bold``, ``dim``, ``italic``,
|
|
257
|
+
``underline``. Colours: ``black``, ``red``, ``green``, ``yellow``, ``blue``,
|
|
258
|
+
``magenta``, ``cyan``, ``white``, ``grey``, ``orange``; ``highlight_<colour>`` is a
|
|
259
|
+
background. Anything else is a ``ValueError``.
|
|
260
|
+
|
|
261
|
+
Returns:
|
|
262
|
+
str: The text between the escape sequences of its styles, or the plain text.
|
|
263
|
+
|
|
264
|
+
Example:
|
|
265
|
+
>>> formatter("hello", "bold_red")
|
|
266
|
+
'\\x1b[1m\\x1b[31mhello\\x1b[0m'
|
|
267
|
+
"""
|
|
268
|
+
if not (colours_on() if _decided is None else _decided):
|
|
269
|
+
return str(text)
|
|
270
|
+
escapes = _ESCAPES.get(spec)
|
|
271
|
+
if escapes is None:
|
|
272
|
+
escapes = _ESCAPES[spec] = _escapes(spec)
|
|
273
|
+
return escapes[0] + str(text) + escapes[1]
|
|
274
|
+
|
|
275
|
+
|
|
276
|
+
def escapes(*specs: str) -> tuple[str, ...]:
|
|
277
|
+
"""
|
|
278
|
+
What is written in front of and behind a text for each of ``specs``, in one flat tuple:
|
|
279
|
+
``before, after, before, after ...``. For a message put together in one f-string, with the
|
|
280
|
+
sequences as constants: cheaper than a ``formatter`` call per coloured word. Always the
|
|
281
|
+
sequences, whatever ``colours_on`` says: the caller decides (``coloured()``).
|
|
282
|
+
"""
|
|
283
|
+
return tuple([part for spec in specs for part in _escapes(spec)])
|
|
284
|
+
|
|
285
|
+
|
|
286
|
+
_ANSI = re.compile(r"\x1b\[[0-9;]*m")
|
|
287
|
+
|
|
288
|
+
|
|
289
|
+
def strip_colours(text: str) -> str:
|
|
290
|
+
"""``text`` without its colours: every ANSI style sequence taken out, the words left as they are."""
|
|
291
|
+
return _ANSI.sub("", text)
|
clitint/py.typed
ADDED
|
File without changes
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: clitint
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Coloured terminal text with no dependencies, plain wherever it isn't read on a terminal.
|
|
5
|
+
License-Expression: MIT
|
|
6
|
+
Keywords: terminal,colour,color,ansi,cli
|
|
7
|
+
Classifier: Development Status :: 3 - Alpha
|
|
8
|
+
Classifier: Intended Audience :: Developers
|
|
9
|
+
Classifier: Operating System :: OS Independent
|
|
10
|
+
Classifier: Programming Language :: Python :: 3
|
|
11
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
12
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
14
|
+
Classifier: Topic :: Terminals
|
|
15
|
+
Classifier: Typing :: Typed
|
|
16
|
+
Requires-Python: >=3.13
|
|
17
|
+
Description-Content-Type: text/markdown
|
|
18
|
+
License-File: LICENSE
|
|
19
|
+
Provides-Extra: dev
|
|
20
|
+
Requires-Dist: pytest; extra == "dev"
|
|
21
|
+
Requires-Dist: mypy; extra == "dev"
|
|
22
|
+
Requires-Dist: build; extra == "dev"
|
|
23
|
+
Requires-Dist: twine; extra == "dev"
|
|
24
|
+
Dynamic: license-file
|
|
25
|
+
|
|
26
|
+
# clitint
|
|
27
|
+
|
|
28
|
+
Coloured terminal text for Python 3.13+, with no dependencies. Text is coloured only when someone is reading it
|
|
29
|
+
on a terminal: redirect the output to a file, a pipe or a CI log and it comes out plain.
|
|
30
|
+
|
|
31
|
+
```python
|
|
32
|
+
from clitint import formatter
|
|
33
|
+
|
|
34
|
+
print(formatter("error:", "bold_red"), "file not found")
|
|
35
|
+
print(formatter("warning", "underline_black_highlight_yellow"))
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
## Install
|
|
39
|
+
|
|
40
|
+
```bash
|
|
41
|
+
pip install clitint
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
## Specs
|
|
45
|
+
|
|
46
|
+
A spec is a set of styles joined by `_`, in any order: `"red"`, `"bold_red"`, `"red_bold"`, `"italic_white_highlight_blue"`.
|
|
47
|
+
|
|
48
|
+
| Kind | Names |
|
|
49
|
+
|---|---|
|
|
50
|
+
| Attributes | `bold`, `dim`, `italic`, `underline` |
|
|
51
|
+
| Colours | `black`, `red`, `green`, `yellow`, `blue`, `magenta`, `cyan`, `white`, `grey`, `orange` |
|
|
52
|
+
| Backgrounds | `highlight_` followed by a colour: `highlight_blue` |
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
You can also combine: bold_highlight_blue, italic_red, underline_green, etc.
|
|
56
|
+
|
|
57
|
+
An unknown name, two colours or two backgrounds raise a `ValueError` that names the problem. `formatter` takes any
|
|
58
|
+
object and writes its `str`. Each spec is worked out once and remembered.
|
|
59
|
+
|
|
60
|
+
## When text is coloured
|
|
61
|
+
|
|
62
|
+
`formatter` decides at every call, in this order:
|
|
63
|
+
|
|
64
|
+
1. what the program chose: `turn_colours_off()` or `turn_colours_on()` (`let_the_environment_decide()` undoes it);
|
|
65
|
+
2. `CLITINT_COLOURS_OFF=1`, `NO_COLOR` or `ANSI_COLORS_DISABLED`: plain;
|
|
66
|
+
3. `FORCE_COLOR`: coloured;
|
|
67
|
+
4. `TERM=dumb`: plain;
|
|
68
|
+
5. otherwise, coloured only when **both** stdout and stderr are terminals.
|
|
69
|
+
|
|
70
|
+
The last rule is there because a string doesn't know where it is going. An error message might be printed,
|
|
71
|
+
logged or raised. `2> errors.log` with stdout on a terminal still gives a plain message, so no escape codes end up
|
|
72
|
+
in the log.
|
|
73
|
+
|
|
74
|
+
`colours_on()` tells you the answer now.
|
|
75
|
+
|
|
76
|
+
## Other tools
|
|
77
|
+
|
|
78
|
+
```python
|
|
79
|
+
from clitint import strip_colours, escapes, one_decision, coloured
|
|
80
|
+
|
|
81
|
+
strip_colours(formatter("x", "red")) # 'x': every ANSI style sequence removed (for tests and logs)
|
|
82
|
+
|
|
83
|
+
with one_decision(): # one message: decide once, not at every formatter call
|
|
84
|
+
line = formatter("a", "red") + " " + formatter("b", "blue")
|
|
85
|
+
|
|
86
|
+
red, reset = escapes("red") # the raw sequences, for a hot f-string
|
|
87
|
+
text = f"{red}x{reset}" if coloured() else "x"
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
## Licence
|
|
91
|
+
|
|
92
|
+
MIT
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
clitint/__init__.py,sha256=-CjTMDWll75iU2mBfRpbRv5SQ8CH2cBr-8K0lxHPYZM,779
|
|
2
|
+
clitint/_tint.py,sha256=hQFQ56BfW8U8zbXXq3nhK8DP8cABmMHsjqSj12RnKWE,11140
|
|
3
|
+
clitint/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
4
|
+
clitint-0.1.0.dist-info/licenses/LICENSE,sha256=hFFm9WCRjRtJHi6-i6r1o3VSfTMU5i4iml8q2N19vy4,1075
|
|
5
|
+
clitint-0.1.0.dist-info/METADATA,sha256=OSWjuT-71_JnpecPlOsiLBgOajlOSxMJCGKCo94LgEc,3162
|
|
6
|
+
clitint-0.1.0.dist-info/WHEEL,sha256=mOdXR9C3wMVov7cSY8PY-XCzo3SmmWCHvGQr5csJoao,91
|
|
7
|
+
clitint-0.1.0.dist-info/top_level.txt,sha256=MwsrlcC5Rw6pKcD4N7MsTJHTQ-bhVYkLeVyZZ8YFnHM,8
|
|
8
|
+
clitint-0.1.0.dist-info/RECORD,,
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Christos Synodinos
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
clitint
|