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
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
1"""
2Format a pretty string of a `SoupSieve` object for easy debugging.
4This won't necessarily support all types and such, and definitely
5not support custom outputs.
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).
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```
68"""
69from __future__ import annotations
70import re
71from typing import Any
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*')
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}
110def pretty(obj: Any) -> str: # pragma: no cover
111 """Make the object output string pretty."""
113 sel = str(obj)
114 index = 0
115 end = len(sel) - 1
116 indent = 0
117 output = []
119 while index <= end:
120 m = None
121 for k, v in TOKENS.items():
122 m = v.match(sel, index)
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
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
148 return ''.join(output)