3
3
import configparser
4
4
import re
5
5
from itertools import chain , groupby , repeat
6
- from typing import TYPE_CHECKING , Dict , Iterator , List , Optional
6
+ from typing import TYPE_CHECKING , Dict , List , Optional , Union
7
7
8
8
from pip ._vendor .requests .models import Request , Response
9
+ from pip ._vendor .rich .console import Console , ConsoleOptions , RenderResult
10
+ from pip ._vendor .rich .text import Text
9
11
10
12
if TYPE_CHECKING :
11
13
from hashlib import _Hash
@@ -22,75 +24,140 @@ def _is_kebab_case(s: str) -> bool:
22
24
return re .match (r"^[a-z]+(-[a-z]+)*$" , s ) is not None
23
25
24
26
25
- def _prefix_with_indent (prefix : str , s : str , indent : Optional [str ] = None ) -> str :
26
- if indent is None :
27
- indent = " " * len (prefix )
27
+ def _prefix_with_indent (
28
+ s : Union [Text , str ],
29
+ console : Console ,
30
+ * ,
31
+ prefix : str ,
32
+ indent : str ,
33
+ ) -> Text :
34
+ if isinstance (s , Text ):
35
+ text = s
28
36
else :
29
- assert len (indent ) == len (prefix )
30
- message = s .replace ("\n " , "\n " + indent )
31
- return f"{ prefix } { message } \n "
37
+ text = console .render_str (s )
38
+
39
+ lines = text .wrap (console , console .width - width_offset )
40
+
41
+ return console .render_str (prefix ) + console .render_str (f"\n { indent } " ).join (lines )
32
42
33
43
34
44
class PipError (Exception ):
35
45
"""The base pip error."""
36
46
37
47
38
48
class DiagnosticPipError (PipError ):
39
- """A pip error, that presents diagnostic information to the user.
49
+ """An error, that presents diagnostic information to the user.
40
50
41
51
This contains a bunch of logic, to enable pretty presentation of our error
42
52
messages. Each error gets a unique reference. Each error can also include
43
53
additional context, a hint and/or a note -- which are presented with the
44
54
main error message in a consistent style.
55
+
56
+ This is adapted from the error output styling in `sphinx-theme-builder`.
45
57
"""
46
58
47
59
reference : str
48
60
49
61
def __init__ (
50
62
self ,
51
63
* ,
52
- message : str ,
53
- context : Optional [str ],
54
- hint_stmt : Optional [str ],
55
- attention_stmt : Optional [str ] = None ,
56
- reference : Optional [str ] = None ,
57
64
kind : 'Literal["error", "warning"]' = "error" ,
65
+ reference : Optional [str ] = None ,
66
+ message : Union [str , Text ],
67
+ context : Optional [Union [str , Text ]],
68
+ hint_stmt : Optional [Union [str , Text ]],
69
+ attention_stmt : Optional [Union [str , Text ]] = None ,
70
+ link : Optional [str ] = None ,
58
71
) -> None :
59
-
60
72
# Ensure a proper reference is provided.
61
73
if reference is None :
62
74
assert hasattr (self , "reference" ), "error reference not provided!"
63
75
reference = self .reference
64
76
assert _is_kebab_case (reference ), "error reference must be kebab-case!"
65
77
66
- super ().__init__ (f"{ reference } : { message } " )
67
-
68
78
self .kind = kind
79
+ self .reference = reference
80
+
69
81
self .message = message
70
82
self .context = context
71
83
72
- self .reference = reference
73
84
self .attention_stmt = attention_stmt
74
85
self .hint_stmt = hint_stmt
75
86
76
- def __str__ (self ) -> str :
77
- return "" .join (self ._string_parts ())
87
+ self .link = link
78
88
79
- def _string_parts (self ) -> Iterator [str ]:
80
- # Present the main message, with relevant context indented.
81
- yield f"{ self .message } \n "
82
- if self .context is not None :
83
- yield f"\n { self .context } \n "
89
+ super ().__init__ (f"<{ self .__class__ .__name__ } : { self .reference } >" )
90
+
91
+ def __repr__ (self ) -> str :
92
+ return (
93
+ f"<{ self .__class__ .__name__ } ("
94
+ f"reference={ self .reference !r} , "
95
+ f"message={ self .message !r} , "
96
+ f"context={ self .context !r} , "
97
+ f"attention_stmt={ self .attention_stmt !r} , "
98
+ f"hint_stmt={ self .hint_stmt !r} "
99
+ ")>"
100
+ )
101
+
102
+ def __rich_console__ (
103
+ self ,
104
+ console : Console ,
105
+ options : ConsoleOptions ,
106
+ ) -> RenderResult :
107
+ colour = "red" if self .kind == "error" else "yellow"
108
+
109
+ yield f"[{ colour } bold]{ self .kind } [/]: [bold]{ self .reference } [/]"
110
+ yield ""
111
+
112
+ if not options .ascii_only :
113
+ # Present the main message, with relevant context indented.
114
+ if self .context is not None :
115
+ yield _prefix_with_indent (
116
+ self .message ,
117
+ console ,
118
+ prefix = f"[{ colour } ]×[/] " ,
119
+ indent = f"[{ colour } ]│[/] " ,
120
+ )
121
+ yield _prefix_with_indent (
122
+ self .context ,
123
+ console ,
124
+ prefix = f"[{ colour } ]╰─>[/] " ,
125
+ indent = f"[{ colour } ] [/] " ,
126
+ )
127
+ else :
128
+ yield _prefix_with_indent (
129
+ self .message ,
130
+ console ,
131
+ prefix = "[red]×[/] " ,
132
+ indent = " " ,
133
+ )
134
+ else :
135
+ yield self .message
136
+ if self .context is not None :
137
+ yield ""
138
+ yield self .context
84
139
85
- # Space out the note/hint messages.
86
140
if self .attention_stmt is not None or self .hint_stmt is not None :
87
- yield "\n "
141
+ yield ""
88
142
89
143
if self .attention_stmt is not None :
90
- yield _prefix_with_indent ("Note: " , self .attention_stmt )
91
-
144
+ yield _prefix_with_indent (
145
+ self .attention_stmt ,
146
+ console ,
147
+ prefix = "[magenta bold]note[/]: " ,
148
+ indent = " " ,
149
+ )
92
150
if self .hint_stmt is not None :
93
- yield _prefix_with_indent ("Hint: " , self .hint_stmt )
151
+ yield _prefix_with_indent (
152
+ self .hint_stmt ,
153
+ console ,
154
+ prefix = "[cyan bold]hint[/]: " ,
155
+ indent = " " ,
156
+ )
157
+
158
+ if self .link is not None :
159
+ yield ""
160
+ yield f"Link: { self .link } "
94
161
95
162
96
163
#
@@ -118,12 +185,12 @@ def __init__(self, *, package: str) -> None:
118
185
message = f"Can not process { package } " ,
119
186
context = (
120
187
"This package has an invalid pyproject.toml file.\n "
121
- "The [build-system] table is missing the mandatory `requires` key."
188
+ R "The \ [build-system] table is missing the mandatory `requires` key."
122
189
),
123
190
attention_stmt = (
124
191
"This is an issue with the package mentioned above, not pip."
125
192
),
126
- hint_stmt = "See PEP 518 for the detailed specification." ,
193
+ hint_stmt = Text ( "See PEP 518 for the detailed specification." ) ,
127
194
)
128
195
129
196
@@ -135,12 +202,12 @@ class InvalidPyProjectBuildRequires(DiagnosticPipError):
135
202
def __init__ (self , * , package : str , reason : str ) -> None :
136
203
super ().__init__ (
137
204
message = f"Can not process { package } " ,
138
- context = (
205
+ context = Text (
139
206
"This package has an invalid `build-system.requires` key in "
140
207
"pyproject.toml.\n "
141
208
f"{ reason } "
142
209
),
143
- hint_stmt = "See PEP 518 for the detailed specification." ,
210
+ hint_stmt = Text ( "See PEP 518 for the detailed specification." ) ,
144
211
attention_stmt = (
145
212
"This is an issue with the package mentioned above, not pip."
146
213
),
0 commit comments