43
43
from typing import Any , AnyStr , Union , Callable , Dict , Iterable , Sequence , Optional , List , Tuple , cast
44
44
45
45
import attr
46
- import astor .op_util
47
46
from docutils import nodes
48
47
from twisted .web .template import Tag
49
48
50
49
from pydoctor .epydoc import sre_parse36 , sre_constants36 as sre_constants
51
50
from pydoctor .epydoc .markup import DocstringLinker
52
51
from pydoctor .epydoc .markup .restructuredtext import ParsedRstDocstring
53
52
from pydoctor .epydoc .docutils import set_node_attributes , wbr , obj_reference , new_document
54
- from pydoctor .astutils import node2dottedname , bind_args , Parentage , get_parents
53
+ from pydoctor .astutils import node2dottedname , bind_args , Parentage , get_parents , unparse , op_util
55
54
56
55
def decode_with_backslashreplace (s : bytes ) -> str :
57
56
r"""
@@ -76,6 +75,7 @@ class _MarkedColorizerState:
76
75
charpos : int
77
76
lineno : int
78
77
linebreakok : bool
78
+ stacklength : int
79
79
80
80
class _ColorizerState :
81
81
"""
@@ -87,18 +87,20 @@ class _ColorizerState:
87
87
then fall back on a multi-line output if that fails.
88
88
"""
89
89
def __init__ (self ) -> None :
90
- self .result : List [nodes .Node ] = []
90
+ self .result : list [nodes .Node ] = []
91
91
self .charpos = 0
92
92
self .lineno = 1
93
93
self .linebreakok = True
94
- self .warnings : List [str ] = []
94
+ self .warnings : list [str ] = []
95
+ self .stack : list [ast .AST ] = []
95
96
96
97
def mark (self ) -> _MarkedColorizerState :
97
98
return _MarkedColorizerState (
98
99
length = len (self .result ),
99
100
charpos = self .charpos ,
100
101
lineno = self .lineno ,
101
- linebreakok = self .linebreakok )
102
+ linebreakok = self .linebreakok ,
103
+ stacklength = len (self .stack ))
102
104
103
105
def restore (self , mark : _MarkedColorizerState ) -> List [nodes .Node ]:
104
106
"""
@@ -109,16 +111,17 @@ def restore(self, mark: _MarkedColorizerState) -> List[nodes.Node]:
109
111
mark .linebreakok )
110
112
trimmed = self .result [mark .length :]
111
113
del self .result [mark .length :]
114
+ del self .stack [mark .stacklength :]
112
115
return trimmed
113
116
114
117
# TODO: add support for comparators when needed.
115
118
# _OperatorDelimitier is needed for:
116
- # - IfExp
117
- # - UnaryOp
118
- # - BinOp, needs special handling for power operator
119
- # - Compare
120
- # - BoolOp
121
- # - Lambda
119
+ # - IfExp (TODO)
120
+ # - UnaryOp (DONE)
121
+ # - BinOp, needs special handling for power operator (DONE)
122
+ # - Compare (TODO)
123
+ # - BoolOp (DONE)
124
+ # - Lambda (TODO)
122
125
class _OperatorDelimiter :
123
126
"""
124
127
A context manager that can add enclosing delimiters to nested operators when needed.
@@ -145,14 +148,14 @@ def __init__(self, colorizer: 'PyvalColorizer', state: _ColorizerState,
145
148
146
149
# avoid needless parenthesis, since we now collect parents for every nodes
147
150
if isinstance (parent_node , (ast .expr , ast .keyword , ast .comprehension )):
148
- precedence = astor . op_util .get_op_precedence (node .op )
151
+ precedence = op_util .get_op_precedence (node .op )
149
152
if isinstance (parent_node , (ast .UnaryOp , ast .BinOp , ast .BoolOp )):
150
- parent_precedence = astor . op_util .get_op_precedence (parent_node .op )
153
+ parent_precedence = op_util .get_op_precedence (parent_node .op )
151
154
if isinstance (parent_node .op , ast .Pow ) or isinstance (parent_node , ast .BoolOp ):
152
155
parent_precedence += 1
153
156
else :
154
157
parent_precedence = colorizer .explicit_precedence .get (
155
- node , astor . op_util .Precedence .highest )
158
+ node , op_util .Precedence .highest )
156
159
157
160
if precedence < parent_precedence :
158
161
self .discard = False
@@ -460,7 +463,7 @@ def _colorize_ast_dict(self, items: Iterable[Tuple[Optional[ast.AST], ast.AST]],
460
463
self ._insert_comma (indent , state )
461
464
state .result .append (self .WORD_BREAK_OPPORTUNITY )
462
465
if key :
463
- self ._set_precedence (astor . op_util .Precedence .Comma , val )
466
+ self ._set_precedence (op_util .Precedence .Comma , val )
464
467
self ._colorize (key , state )
465
468
self ._output (': ' , self .COLON_TAG , state )
466
469
else :
@@ -545,6 +548,7 @@ def _colorize_ast_constant(self, pyval: ast.AST, state: _ColorizerState) -> None
545
548
self ._output ('...' , self .ELLIPSIS_TAG , state )
546
549
547
550
def _colorize_ast (self , pyval : ast .AST , state : _ColorizerState ) -> None :
551
+ state .stack .append (pyval )
548
552
# Set nodes parent in order to check theirs precedences and add delimiters when needed.
549
553
try :
550
554
next (get_parents (pyval ))
@@ -588,6 +592,7 @@ def _colorize_ast(self, pyval: ast.AST, state: _ColorizerState) -> None:
588
592
self ._colorize_ast (pyval .value , state )
589
593
else :
590
594
self ._colorize_ast_generic (pyval , state )
595
+ assert state .stack .pop () is pyval
591
596
592
597
def _colorize_ast_unary_op (self , pyval : ast .UnaryOp , state : _ColorizerState ) -> None :
593
598
with _OperatorDelimiter (self , state , pyval ):
@@ -761,7 +766,13 @@ def _colorize_ast_re(self, node:ast.Call, state: _ColorizerState) -> None:
761
766
762
767
def _colorize_ast_generic (self , pyval : ast .AST , state : _ColorizerState ) -> None :
763
768
try :
764
- source = astor .to_source (pyval ).strip ()
769
+ # Always wrap the expression inside parenthesis because we can't be sure
770
+ # if there are required since we don;t have support for all operators
771
+ # See TODO comment in _OperatorDelimiter.
772
+ source = unparse (pyval ).strip ()
773
+ if sys .version_info > (3 ,9 ) and isinstance (pyval ,
774
+ (ast .IfExp , ast .Compare , ast .Lambda )) and len (state .stack )> 1 :
775
+ source = f'({ source } )'
765
776
except Exception : # No defined handler for node of type <type>
766
777
state .result .append (self .UNKNOWN_REPR )
767
778
else :
0 commit comments