Skip to content

Commit 3cc0770

Browse files
committed
Move MetaWarning classes into new cli module
Moving them out of __main__.py will make it easier to re-use them across different command-line interfaces, and may make them easier to test. While moving them, I also added the missing docstrings. Signed-off-by: John Pennycook <john.pennycook@intel.com>
1 parent 00f4a4f commit 3cc0770

File tree

2 files changed

+171
-95
lines changed

2 files changed

+171
-95
lines changed

codebasin/__main__.py

Lines changed: 1 addition & 95 deletions
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,10 @@
88
import argparse
99
import logging
1010
import os
11-
import re
1211
import sys
1312

1413
from codebasin import CodeBase, config, finder, report, util
14+
from codebasin.cli import Formatter, WarningAggregator
1515

1616
log = logging.getLogger("codebasin")
1717
version = "1.2.0"
@@ -54,100 +54,6 @@ def _help_string(*lines: str, is_long=False, is_last=False):
5454
return result
5555

5656

57-
class Formatter(logging.Formatter):
58-
def __init__(self, *, colors: bool = False):
59-
self.colors = colors
60-
61-
def format(self, record: logging.LogRecord) -> str:
62-
msg = record.msg
63-
level = record.levelname.lower()
64-
65-
# Display info messages with no special formatting.
66-
if level == "info":
67-
return f"{msg}"
68-
69-
# Drop colors if requested.
70-
if not self.colors:
71-
return f"{level}: {msg}"
72-
73-
# Otherwise, use ASCII codes to improve readability.
74-
BOLD = "\033[1m"
75-
DEFAULT = "\033[39m"
76-
YELLOW = "\033[93m"
77-
RED = "\033[91m"
78-
RESET = "\033[0m"
79-
80-
if level == "warning":
81-
color = YELLOW
82-
elif level == "error":
83-
color = RED
84-
else:
85-
color = DEFAULT
86-
return f"{BOLD}{color}{level}{RESET}: {msg}"
87-
88-
89-
class MetaWarning:
90-
"""
91-
A MetaWarning is used to represent multiple warnings, and provide suggested
92-
actions to the user.
93-
"""
94-
95-
def __init__(self, regex: str, msg: str):
96-
self.regex = re.compile(regex)
97-
self.msg = msg
98-
self._count = 0
99-
100-
def inspect(self, record: logging.LogRecord):
101-
if self.regex.search(record.msg):
102-
self._count += 1
103-
104-
def warn(self):
105-
if self._count == 0:
106-
return
107-
log.warning(self.msg.format(self._count))
108-
109-
110-
class WarningAggregator(logging.Filter):
111-
"""
112-
Inspect warnings to generate meta-warnings and statistics.
113-
"""
114-
115-
def __init__(self):
116-
self.meta_warnings = [
117-
MetaWarning(".", "{} warnings generated during preprocessing."),
118-
MetaWarning(
119-
"user include",
120-
"{} user include files could not be found.\n"
121-
+ " These could contain important macros and includes.\n"
122-
+ " Suggested solutions:\n"
123-
+ " - Check that the file(s) exist in the code base.\n"
124-
+ " - Check the include paths in the compilation database.\n"
125-
+ " - Check if the include(s) should have used '<>'.",
126-
),
127-
MetaWarning(
128-
"system include",
129-
"{} system include files could not be found.\n"
130-
+ " These could define important feature macros.\n"
131-
+ " Suggested solutions:\n"
132-
+ " - Check that the file(s) exist on your system.\n"
133-
+ " - Use .cbi/config to define system include paths.\n"
134-
+ " - Use .cbi/config to define important macros.",
135-
),
136-
]
137-
138-
def filter(self, record: logging.LogRecord) -> bool:
139-
if record.levelno == logging.WARNING:
140-
for meta_warning in self.meta_warnings:
141-
meta_warning.inspect(record)
142-
143-
# Do not filter anything.
144-
return True
145-
146-
def warn(self):
147-
for meta_warning in self.meta_warnings:
148-
meta_warning.warn()
149-
150-
15157
def _main():
15258
# Read command-line arguments
15359
parser = argparse.ArgumentParser(

codebasin/cli.py

Lines changed: 170 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,170 @@
1+
#!/usr/bin/env python3
2+
# Copyright (C) 2019-2024 Intel Corporation
3+
# SPDX-License-Identifier: BSD-3-Clause
4+
5+
import logging
6+
import re
7+
8+
log = logging.getLogger(__name__)
9+
10+
11+
class Formatter(logging.Formatter):
12+
"""
13+
A Formatter that formats LogRecords using a format inspired by compilers
14+
like gcc/clang, with optional colors.
15+
"""
16+
17+
def __init__(self, *, colors: bool = False):
18+
"""
19+
Initialize this Formatter.
20+
21+
Parameters
22+
----------
23+
colors: bool, default: False
24+
Whether to colorize the output.
25+
"""
26+
self.colors = colors
27+
28+
def format(self, record: logging.LogRecord) -> str:
29+
"""
30+
Format the specified record.
31+
32+
Parameters
33+
----------
34+
record: logging.LogRecord
35+
The record to format.
36+
37+
Returns
38+
-------
39+
str
40+
The formatted output string.
41+
"""
42+
msg = record.msg
43+
level = record.levelname.lower()
44+
45+
# Display info messages with no special formatting.
46+
if level == "info":
47+
return f"{msg}"
48+
49+
# Drop colors if requested.
50+
if not self.colors:
51+
return f"{level}: {msg}"
52+
53+
# Otherwise, use ASCII codes to improve readability.
54+
BOLD = "\033[1m"
55+
DEFAULT = "\033[39m"
56+
YELLOW = "\033[93m"
57+
RED = "\033[91m"
58+
RESET = "\033[0m"
59+
60+
if level == "warning":
61+
color = YELLOW
62+
elif level == "error":
63+
color = RED
64+
else:
65+
color = DEFAULT
66+
return f"{BOLD}{color}{level}{RESET}: {msg}"
67+
68+
69+
class MetaWarning:
70+
"""
71+
A MetaWarning is used to represent multiple warnings, and to provide
72+
suggested actions to the user.
73+
"""
74+
75+
def __init__(self, regex: str, msg: str):
76+
"""
77+
Initialize a new MetaWarning.
78+
79+
Parameters
80+
----------
81+
regex: str
82+
A regular expression used to identify constituent warnings.
83+
If any warning matches `regex`, this MetaWarning will trigger.
84+
85+
msg: str
86+
The message to display when this MetaWarning is triggered.
87+
"""
88+
self.regex = re.compile(regex)
89+
self.msg = msg
90+
self._count = 0
91+
92+
def inspect(self, record: logging.LogRecord):
93+
"""
94+
Inspect a LogRecord to determine if it matches this MetaWarning.
95+
96+
Parameters
97+
----------
98+
record: logging.LogRecord
99+
The LogRecord to inspect.
100+
"""
101+
if self.regex.search(record.msg):
102+
self._count += 1
103+
104+
def warn(self):
105+
"""
106+
Produce the warning associated with this MetaWarning.
107+
"""
108+
if self._count == 0:
109+
return
110+
log.warning(self.msg.format(self._count))
111+
112+
113+
class WarningAggregator(logging.Filter):
114+
"""
115+
A WarningAggregator is a logging.Filter that inspects warnings to generate
116+
meta-warnings and statistics. It does not perform any filtering, but uses
117+
the logging.Filter mechanism as a hook to automatically inspect every
118+
warning passing through a logger.
119+
"""
120+
121+
def __init__(self):
122+
self.meta_warnings = [
123+
MetaWarning(".", "{} warnings generated during preprocessing."),
124+
MetaWarning(
125+
"user include",
126+
"{} user include files could not be found.\n"
127+
+ " These could contain important macros and includes.\n"
128+
+ " Suggested solutions:\n"
129+
+ " - Check that the file(s) exist in the code base.\n"
130+
+ " - Check the include paths in the compilation database.\n"
131+
+ " - Check if the include(s) should have used '<>'.",
132+
),
133+
MetaWarning(
134+
"system include",
135+
"{} system include files could not be found.\n"
136+
+ " These could define important feature macros.\n"
137+
+ " Suggested solutions:\n"
138+
+ " - Check that the file(s) exist on your system.\n"
139+
+ " - Use .cbi/config to define system include paths.\n"
140+
+ " - Use .cbi/config to define important macros.",
141+
),
142+
]
143+
144+
def filter(self, record: logging.LogRecord) -> bool:
145+
"""
146+
Inspect the specified LogRecord, attempting to match it against each
147+
possible MetaWarning.
148+
149+
Parameters
150+
----------
151+
record: logging.LogRecord
152+
The record to inspect.
153+
154+
Returns
155+
-------
156+
bool
157+
True, to prevent any warnings from being filtered.
158+
"""
159+
if record.levelno == logging.WARNING:
160+
for meta_warning in self.meta_warnings:
161+
meta_warning.inspect(record)
162+
return True
163+
164+
def warn(self):
165+
"""
166+
Produce the warning associated with any MetaWarning(s) that were
167+
matched by this WarningAggregator.
168+
"""
169+
for meta_warning in self.meta_warnings:
170+
meta_warning.warn()

0 commit comments

Comments
 (0)