1"""
2Soup Sieve.
3
4A CSS selector filter for BeautifulSoup4.
5
6MIT License
7
8Copyright (c) 2018 Isaac Muse
9
10Permission is hereby granted, free of charge, to any person obtaining a copy
11of this software and associated documentation files (the "Software"), to deal
12in the Software without restriction, including without limitation the rights
13to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
14copies of the Software, and to permit persons to whom the Software is
15furnished to do so, subject to the following conditions:
16
17The above copyright notice and this permission notice shall be included in all
18copies or substantial portions of the Software.
19
20THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
21IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
22FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
23AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
24LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
25OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
26SOFTWARE.
27"""
28from .__meta__ import __version__, __version_info__  # noqa: F401
29from . import css_parser as cp
30from . import css_match as cm
31from . import css_types as ct
32from .util import DEBUG, SelectorSyntaxError  # noqa: F401
33import bs4  # type: ignore[import]
34from typing import Dict, Optional, Any, List, Iterator, Iterable
35
36__all__ = (
37    'DEBUG', 'SelectorSyntaxError', 'SoupSieve',
38    'closest', 'compile', 'filter', 'iselect',
39    'match', 'select', 'select_one'
40)
41
42SoupSieve = cm.SoupSieve
43
44
45def compile(  # noqa: A001
46    pattern: str,
47    namespaces: Optional[Dict[str, str]] = None,
48    flags: int = 0,
49    *,
50    custom: Optional[Dict[str, str]] = None,
51    **kwargs: Any
52) -> cm.SoupSieve:
53    """Compile CSS pattern."""
54
55    ns = ct.Namespaces(namespaces) if namespaces is not None else namespaces  # type: Optional[ct.Namespaces]
56    cs = ct.CustomSelectors(custom) if custom is not None else custom  # type: Optional[ct.CustomSelectors]
57
58    if isinstance(pattern, SoupSieve):
59        if flags:
60            raise ValueError("Cannot process 'flags' argument on a compiled selector list")
61        elif namespaces is not None:
62            raise ValueError("Cannot process 'namespaces' argument on a compiled selector list")
63        elif custom is not None:
64            raise ValueError("Cannot process 'custom' argument on a compiled selector list")
65        return pattern
66
67    return cp._cached_css_compile(pattern, ns, cs, flags)
68
69
70def purge() -> None:
71    """Purge cached patterns."""
72
73    cp._purge_cache()
74
75
76def closest(
77    select: str,
78    tag: 'bs4.Tag',
79    namespaces: Optional[Dict[str, str]] = None,
80    flags: int = 0,
81    *,
82    custom: Optional[Dict[str, str]] = None,
83    **kwargs: Any
84) -> 'bs4.Tag':
85    """Match closest ancestor."""
86
87    return compile(select, namespaces, flags, **kwargs).closest(tag)
88
89
90def match(
91    select: str,
92    tag: 'bs4.Tag',
93    namespaces: Optional[Dict[str, str]] = None,
94    flags: int = 0,
95    *,
96    custom: Optional[Dict[str, str]] = None,
97    **kwargs: Any
98) -> bool:
99    """Match node."""
100
101    return compile(select, namespaces, flags, **kwargs).match(tag)
102
103
104def filter(  # noqa: A001
105    select: str,
106    iterable: Iterable['bs4.Tag'],
107    namespaces: Optional[Dict[str, str]] = None,
108    flags: int = 0,
109    *,
110    custom: Optional[Dict[str, str]] = None,
111    **kwargs: Any
112) -> List['bs4.Tag']:
113    """Filter list of nodes."""
114
115    return compile(select, namespaces, flags, **kwargs).filter(iterable)
116
117
118def select_one(
119    select: str,
120    tag: 'bs4.Tag',
121    namespaces: Optional[Dict[str, str]] = None,
122    flags: int = 0,
123    *,
124    custom: Optional[Dict[str, str]] = None,
125    **kwargs: Any
126) -> 'bs4.Tag':
127    """Select a single tag."""
128
129    return compile(select, namespaces, flags, **kwargs).select_one(tag)
130
131
132def select(
133    select: str,
134    tag: 'bs4.Tag',
135    namespaces: Optional[Dict[str, str]] = None,
136    limit: int = 0,
137    flags: int = 0,
138    *,
139    custom: Optional[Dict[str, str]] = None,
140    **kwargs: Any
141) -> List['bs4.Tag']:
142    """Select the specified tags."""
143
144    return compile(select, namespaces, flags, **kwargs).select(tag, limit)
145
146
147def iselect(
148    select: str,
149    tag: 'bs4.Tag',
150    namespaces: Optional[Dict[str, str]] = None,
151    limit: int = 0,
152    flags: int = 0,
153    *,
154    custom: Optional[Dict[str, str]] = None,
155    **kwargs: Any
156) -> Iterator['bs4.Tag']:
157    """Iterate the specified tags."""
158
159    for el in compile(select, namespaces, flags, **kwargs).iselect(tag, limit):
160        yield el
161
162
163def escape(ident: str) -> str:
164    """Escape identifier."""
165
166    return cp.escape(ident)
167