Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/soupsieve/pretty.py: 100%

Shortcuts on this page

r m x   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

21 statements  

1""" 

2Format a pretty string of a `SoupSieve` object for easy debugging. 

3 

4This won't necessarily support all types and such, and definitely 

5not support custom outputs. 

6 

7It is mainly geared towards our types as the `SelectorList` 

8object is a beast to look at without some indentation and newlines. 

9The format and various output types is fairly known (though it 

10hasn't been tested extensively to make sure we aren't missing corners). 

11 

12Example: 

13------- 

14``` 

15>>> import soupsieve as sv 

16>>> sv.compile('this > that.class[name=value]').selectors.pretty() 

17SelectorList( 

18 selectors=( 

19 Selector( 

20 tag=SelectorTag( 

21 name='that', 

22 prefix=None), 

23 ids=(), 

24 classes=( 

25 'class', 

26 ), 

27 attributes=( 

28 SelectorAttribute( 

29 attribute='name', 

30 prefix='', 

31 pattern=re.compile( 

32 '^value$'), 

33 xml_type_pattern=None), 

34 ), 

35 nth=(), 

36 selectors=(), 

37 relation=SelectorList( 

38 selectors=( 

39 Selector( 

40 tag=SelectorTag( 

41 name='this', 

42 prefix=None), 

43 ids=(), 

44 classes=(), 

45 attributes=(), 

46 nth=(), 

47 selectors=(), 

48 relation=SelectorList( 

49 selectors=(), 

50 is_not=False, 

51 is_html=False), 

52 rel_type='>', 

53 contains=(), 

54 lang=(), 

55 flags=0), 

56 ), 

57 is_not=False, 

58 is_html=False), 

59 rel_type=None, 

60 contains=(), 

61 lang=(), 

62 flags=0), 

63 ), 

64 is_not=False, 

65 is_html=False) 

66``` 

67 

68""" 

69from __future__ import annotations 

70import re 

71from typing import Any 

72 

73RE_CLASS = re.compile(r'(?i)[a-z_][_a-z\d.]+\(') 

74RE_PARAM = re.compile(r'(?i)[_a-z][_a-z\d]+=') 

75RE_EMPTY = re.compile(r'\(\)|\[\]|\{\}') 

76RE_LSTRT = re.compile(r'\[') 

77RE_DSTRT = re.compile(r'\{') 

78RE_TSTRT = re.compile(r'\(') 

79RE_LEND = re.compile(r'\]') 

80RE_DEND = re.compile(r'\}') 

81RE_TEND = re.compile(r'\)') 

82RE_INT = re.compile(r'\d+') 

83RE_KWORD = re.compile(r'(?i)[_a-z][_a-z\d.]+') 

84RE_DQSTR = re.compile(r'"(?:\\.|[^"\\])*"') 

85RE_SQSTR = re.compile(r"'(?:\\.|[^'\\])*'") 

86RE_SEP = re.compile(r'\s*(,)\s*') 

87RE_DSEP = re.compile(r'\s*(:)\s*') 

88RE_PSEP = re.compile(r'\s*(\|)\s*') 

89 

90TOKENS = { 

91 'class': RE_CLASS, 

92 'param': RE_PARAM, 

93 'empty': RE_EMPTY, 

94 'lstrt': RE_LSTRT, 

95 'dstrt': RE_DSTRT, 

96 'tstrt': RE_TSTRT, 

97 'lend': RE_LEND, 

98 'dend': RE_DEND, 

99 'tend': RE_TEND, 

100 'sqstr': RE_SQSTR, 

101 'sep': RE_SEP, 

102 'dsep': RE_DSEP, 

103 'psep': RE_PSEP, 

104 'int': RE_INT, 

105 'kword': RE_KWORD, 

106 'dqstr': RE_DQSTR 

107} 

108 

109 

110def pretty(obj: Any) -> str: # pragma: no cover 

111 """Make the object output string pretty.""" 

112 

113 sel = str(obj) 

114 index = 0 

115 end = len(sel) - 1 

116 indent = 0 

117 output = [] 

118 

119 while index <= end: 

120 m = None 

121 for k, v in TOKENS.items(): 

122 m = v.match(sel, index) 

123 

124 if m: 

125 name = k 

126 index = m.end(0) 

127 if name in ('class', 'lstrt', 'dstrt', 'tstrt'): 

128 indent += 4 

129 output.append(f'{m.group(0)}\n{" " * indent}') 

130 elif name in ('param', 'int', 'kword', 'sqstr', 'dqstr', 'empty'): 

131 output.append(m.group(0)) 

132 elif name in ('lend', 'dend', 'tend'): 

133 indent -= 4 

134 output.append(m.group(0)) 

135 elif name in ('sep',): 

136 output.append(f'{m.group(1)}\n{" " * indent}') 

137 elif name in ('dsep',): 

138 output.append(f'{m.group(1)} ') 

139 elif name in ('psep'): 

140 output.append(f' {m.group(1)} ') 

141 break 

142 

143 # We shouldn't hit this, but if we do, store unrecognized character 

144 if m is None: # pragma: no cover 

145 output.append(sel[index]) 

146 index += 1 

147 

148 return ''.join(output)