Skip to content

Commit 66f60aa

Browse files
committed
Replace colored-logs with Rich
1 parent ff350cd commit 66f60aa

File tree

6 files changed

+128
-101
lines changed

6 files changed

+128
-101
lines changed

CHANGELOG.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ Release 0.12.0 (unreleased)
1111
* Skip patches outside manifest dir (#942)
1212
* Make patch path in metadata platform independent (#937)
1313
* Fix extra newlines in patch for new files (#945)
14+
* Replace colored-logs with Rich
1415

1516
Release 0.11.0 (released 2026-01-03)
1617
====================================

dfetch/__main__.py

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,7 @@
1818
import dfetch.commands.validate
1919
import dfetch.log
2020
import dfetch.util.cmdline
21-
22-
logger = dfetch.log.setup_root(__name__)
21+
from dfetch.log import DLogger
2322

2423

2524
class DfetchFatalException(Exception):
@@ -34,6 +33,9 @@ def create_parser() -> argparse.ArgumentParser:
3433
parser.add_argument(
3534
"--verbose", "-v", action="store_true", help="Increase verbosity"
3635
)
36+
parser.add_argument(
37+
"--no-color", action="store_true", help="Disable colored output"
38+
)
3739
parser.set_defaults(func=_help)
3840
subparsers = parser.add_subparsers(help="commands")
3941

@@ -50,16 +52,21 @@ def create_parser() -> argparse.ArgumentParser:
5052
return parser
5153

5254

53-
def _help(args: argparse.Namespace) -> None:
54-
"""Show the help."""
55-
raise RuntimeError("Select a function")
55+
def _help(_: argparse.Namespace) -> None:
56+
"""Show help if no subcommand was selected."""
57+
parser = create_parser()
58+
parser.print_help()
5659

5760

5861
def run(argv: Sequence[str]) -> None:
5962
"""Start dfetch."""
60-
logger.print_title()
6163
args = create_parser().parse_args(argv)
6264

65+
console = dfetch.log.make_console(no_color=args.no_color)
66+
logger: DLogger = dfetch.log.setup_root(__name__, console=console)
67+
68+
logger.print_title()
69+
6370
if args.verbose:
6471
dfetch.log.increase_verbosity()
6572

dfetch/log.py

Lines changed: 88 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -1,70 +1,125 @@
11
"""Logging related items."""
22

33
import logging
4-
from typing import cast
4+
import os
5+
import sys
6+
from typing import Any, Optional, cast
57

6-
import coloredlogs
7-
from colorama import Fore
8+
from rich.console import Console
9+
from rich.highlighter import NullHighlighter
10+
from rich.logging import RichHandler
811

912
from dfetch import __version__
1013

1114

15+
def make_console(no_color: bool = False) -> Console:
16+
"""Create a Rich Console with proper color handling."""
17+
return Console(
18+
no_color=no_color
19+
or os.getenv("NO_COLOR") is not None
20+
or not sys.stdout.isatty()
21+
)
22+
23+
24+
def configure_root_logger(console: Optional[Console] = None) -> None:
25+
"""Configure the root logger with RichHandler using the provided Console."""
26+
console = console or make_console()
27+
28+
handler = RichHandler(
29+
console=console,
30+
show_time=False,
31+
show_path=False,
32+
show_level=False,
33+
markup=True,
34+
rich_tracebacks=True,
35+
highlighter=NullHighlighter(),
36+
)
37+
38+
logging.basicConfig(
39+
level=logging.INFO,
40+
format="%(message)s",
41+
handlers=[handler],
42+
force=True,
43+
)
44+
45+
1246
class DLogger(logging.Logger):
1347
"""Logging class extended with specific log items for dfetch."""
1448

1549
def print_info_line(self, name: str, info: str) -> None:
1650
"""Print a line of info."""
17-
self.info(f" {Fore.GREEN}{name:20s}:{Fore.BLUE} {info}")
51+
self.info(
52+
f" [bold][bright_green]{name:20s}:[/bright_green][blue] {info}[/blue][/bold]"
53+
)
1854

1955
def print_warning_line(self, name: str, info: str) -> None:
20-
"""Print a line of info."""
21-
self.info(f" {Fore.GREEN}{name:20s}:{Fore.YELLOW} {info}")
56+
"""Print a warning line: green name, yellow value."""
57+
self.warning(
58+
f" [bold][bright_green]{name:20s}:[/bright_green][bright_yellow] {info}[/bright_yellow][/bold]"
59+
)
2260

2361
def print_title(self) -> None:
2462
"""Print the DFetch tool title and version."""
25-
self.info(f"{Fore.BLUE}Dfetch ({__version__})")
63+
self.info(f"[bold blue]Dfetch ({__version__})[/bold blue]")
2664

2765
def print_info_field(self, field_name: str, field: str) -> None:
2866
"""Print a field with corresponding value."""
2967
self.print_info_line(field_name, field if field else "<none>")
3068

69+
def warning(self, msg: object, *args: Any, **kwargs: Any) -> None:
70+
"""Log warning."""
71+
super().warning(
72+
f"[bold bright_yellow]{msg}[/bold bright_yellow]", *args, **kwargs
73+
)
3174

32-
def setup_root(name: str) -> DLogger:
33-
"""Create the root logger."""
34-
logger = get_logger(name)
35-
36-
msg_format = "%(message)s"
75+
def error(self, msg: object, *args: Any, **kwargs: Any) -> None:
76+
"""Log error."""
77+
super().error(f"[red]{msg}[/red]", *args, **kwargs)
3778

38-
level_style = {
39-
"critical": {"color": "magenta", "bright": True, "bold": True},
40-
"debug": {"color": "green", "bright": True, "bold": True},
41-
"error": {"color": "red", "bright": True, "bold": True},
42-
"info": {"color": 4, "bright": True, "bold": True},
43-
"notice": {"color": "magenta", "bright": True, "bold": True},
44-
"spam": {"color": "green", "faint": True},
45-
"success": {"color": "green", "bright": True, "bold": True},
46-
"verbose": {"color": "blue", "bright": True, "bold": True},
47-
"warning": {"color": "yellow", "bright": True, "bold": True},
48-
}
4979

50-
coloredlogs.install(fmt=msg_format, level_styles=level_style, level="INFO")
51-
52-
return logger
80+
def setup_root(name: str, console: Optional[Console] = None) -> DLogger:
81+
"""Create and return the root logger."""
82+
logging.setLoggerClass(DLogger)
83+
configure_root_logger(console)
84+
logger = logging.getLogger(name)
85+
return cast(DLogger, logger)
5386

5487

5588
def increase_verbosity() -> None:
56-
"""Increase the verbosity of the logger."""
57-
coloredlogs.increase_verbosity()
58-
59-
60-
def get_logger(name: str) -> DLogger:
61-
"""Get logger for a module."""
89+
"""Increase verbosity of the root logger."""
90+
levels = [
91+
logging.CRITICAL,
92+
logging.ERROR,
93+
logging.WARNING,
94+
logging.INFO,
95+
logging.DEBUG,
96+
]
97+
logger_ = logging.getLogger()
98+
current_level = logger_.getEffectiveLevel()
99+
try:
100+
idx = levels.index(current_level)
101+
if idx < len(levels) - 1:
102+
new_level = levels[idx + 1]
103+
else:
104+
new_level = levels[-1]
105+
except ValueError:
106+
new_level = logging.DEBUG
107+
logger_.setLevel(new_level)
108+
109+
110+
def get_logger(name: str, console: Optional[Console] = None) -> DLogger:
111+
"""Get logger for a module, optionally configuring console colors."""
62112
logging.setLoggerClass(DLogger)
63-
return cast(DLogger, logging.getLogger(name))
113+
logger = logging.getLogger(name)
114+
logger.propagate = True
115+
if console:
116+
configure_root_logger(console)
117+
return cast(DLogger, logger)
64118

65119

66120
def configure_external_logger(name: str, level: int = logging.INFO) -> None:
67121
"""Configure an external logger from a third party package."""
68122
logger = logging.getLogger(name)
69123
logger.setLevel(level)
70124
logger.propagate = True
125+
logger.handlers.clear()

doc/legal.rst

Lines changed: 20 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -77,34 +77,33 @@ We use `PyYAML`_ for parsing manifests (which are YAML). This uses the MIT licen
7777

7878
.. _`PyYAML`: https://pyyaml.org/
7979

80-
python-coloredlogs
81-
~~~~~~~~~~~~~~~~~~
82-
`Colored logs`_ is used for the colored text output.
80+
Rich
81+
~~~~
82+
`Rich`_ is used for the colored text output.
8383

8484
::
8585

86-
Copyright (c) 2020 Peter Odding
86+
Copyright (c) 2020 Will McGugan
8787

88-
Permission is hereby granted, free of charge, to any person obtaining
89-
a copy of this software and associated documentation files (the
90-
"Software"), to deal in the Software without restriction, including
91-
without limitation the rights to use, copy, modify, merge, publish,
92-
distribute, sublicense, and/or sell copies of the Software, and to
93-
permit persons to whom the Software is furnished to do so, subject to
94-
the following conditions:
88+
Permission is hereby granted, free of charge, to any person obtaining a copy
89+
of this software and associated documentation files (the "Software"), to deal
90+
in the Software without restriction, including without limitation the rights
91+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
92+
copies of the Software, and to permit persons to whom the Software is
93+
furnished to do so, subject to the following conditions:
9594

96-
The above copyright notice and this permission notice shall be
97-
included in all copies or substantial portions of the Software.
95+
The above copyright notice and this permission notice shall be included in all
96+
copies or substantial portions of the Software.
9897

99-
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
100-
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
101-
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
102-
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
103-
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
104-
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
105-
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
98+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
99+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
100+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
101+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
102+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
103+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
104+
SOFTWARE.
106105

107-
.. _`Colored logs`: https://coloredlogs.readthedocs.io/en/latest/
106+
.. _`Rich`: https://rich.readthedocs.io/en/latest/
108107

109108
pykwalify
110109
~~~~~~~~~
@@ -137,41 +136,7 @@ pykwalify
137136

138137
.. _`pykwalify`: https://github.com/Grokzen/pykwalify
139138

140-
Colorama
141-
~~~~~~~~
142-
`colorama`_ is also used for the colored text output.
143-
144-
::
145-
146-
Copyright (c) 2010 Jonathan Hartley
147-
All rights reserved.
148-
149-
Redistribution and use in source and binary forms, with or without
150-
modification, are permitted provided that the following conditions are met:
151-
152-
* Redistributions of source code must retain the above copyright notice, this
153-
list of conditions and the following disclaimer.
154-
155-
* Redistributions in binary form must reproduce the above copyright notice,
156-
this list of conditions and the following disclaimer in the documentation
157-
and/or other materials provided with the distribution.
158-
159-
* Neither the name of the copyright holders, nor those of its contributors
160-
may be used to endorse or promote products derived from this software without
161-
specific prior written permission.
162-
163-
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
164-
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
165-
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
166-
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
167-
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
168-
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
169-
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
170-
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
171-
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
172-
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
173139

174-
.. _`colorama`: https://github.com/tartley/colorama
175140

176141

177142
Typing-extensions

features/steps/generic_steps.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
from dfetch.__main__ import DfetchFatalException, run
1818
from dfetch.util.util import in_directory
1919

20-
ansi_escape = re.compile(r"\x1b(?:[@A-Z\\-_]|\[[0-9:;<=>?]*[ -/]*[@-~])")
20+
ansi_escape = re.compile(r"\[/?[a-z\_ ]+\]")
2121
dfetch_title = re.compile(r"Dfetch \(\d+.\d+.\d+\)")
2222
timestamp = re.compile(r"\d+\/\d+\/\d+, \d+:\d+:\d+")
2323
git_hash = re.compile(r"(\s?)[a-f0-9]{40}(\s?)")
@@ -81,7 +81,7 @@ def check_content(
8181
):
8282
expected = multisub(
8383
patterns=[
84-
(git_hash, r"\1[commit hash]\2"),
84+
(git_hash, r"\1[commit-hash]\2"),
8585
(iso_timestamp, "[timestamp]"),
8686
(urn_uuid, "[urn-uuid]"),
8787
(bom_ref, "[bom-ref]"),
@@ -91,7 +91,7 @@ def check_content(
9191

9292
actual = multisub(
9393
patterns=[
94-
(git_hash, r"\1[commit hash]\2"),
94+
(git_hash, r"\1[commit-hash]\2"),
9595
(iso_timestamp, "[timestamp]"),
9696
(urn_uuid, "[urn-uuid]"),
9797
(bom_ref, "[bom-ref]"),
@@ -164,7 +164,7 @@ def check_output(context, line_count=None):
164164
"""
165165
expected_text = multisub(
166166
patterns=[
167-
(git_hash, r"\1[commit hash]\2"),
167+
(git_hash, r"\1[commit-hash]\2"),
168168
(timestamp, "[timestamp]"),
169169
(dfetch_title, ""),
170170
(svn_error, "svn: EXXXXXX: <some error text>"),
@@ -174,7 +174,7 @@ def check_output(context, line_count=None):
174174

175175
actual_text = multisub(
176176
patterns=[
177-
(git_hash, r"\1[commit hash]\2"),
177+
(git_hash, r"\1[commit-hash]\2"),
178178
(timestamp, "[timestamp]"),
179179
(ansi_escape, ""),
180180
(

pyproject.toml

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -40,10 +40,9 @@ classifiers = [
4040
]
4141
dependencies = [
4242
"PyYAML==6.0.3",
43-
"coloredlogs==15.0.1",
4443
"strictyaml==1.7.3",
4544
"halo==0.0.31",
46-
"colorama==0.4.6",
45+
"rich==14.2.0",
4746
"typing-extensions==4.15.0",
4847
"tldextract==5.3.0",
4948
"sarif-om==1.0.4",

0 commit comments

Comments
 (0)