mirror of
https://github.com/Tautulli/Tautulli.git
synced 2025-07-15 01:32:57 -07:00
Add soupsieve-1.9.5
This commit is contained in:
parent
0608b2a1df
commit
d21a03905d
6 changed files with 3587 additions and 0 deletions
127
lib/soupsieve/__init__.py
Normal file
127
lib/soupsieve/__init__.py
Normal file
|
@ -0,0 +1,127 @@
|
||||||
|
"""
|
||||||
|
Soup Sieve.
|
||||||
|
|
||||||
|
A CSS selector filter for BeautifulSoup4.
|
||||||
|
|
||||||
|
MIT License
|
||||||
|
|
||||||
|
Copyright (c) 2018 Isaac Muse
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
||||||
|
"""
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
from .__meta__ import __version__, __version_info__ # noqa: F401
|
||||||
|
from . import css_parser as cp
|
||||||
|
from . import css_match as cm
|
||||||
|
from . import css_types as ct
|
||||||
|
from .util import DEBUG, deprecated, SelectorSyntaxError # noqa: F401
|
||||||
|
|
||||||
|
__all__ = (
|
||||||
|
'DEBUG', 'SelectorSyntaxError', 'SoupSieve',
|
||||||
|
'closest', 'comments', 'compile', 'filter', 'icomments',
|
||||||
|
'iselect', 'match', 'select', 'select_one'
|
||||||
|
)
|
||||||
|
|
||||||
|
SoupSieve = cm.SoupSieve
|
||||||
|
|
||||||
|
|
||||||
|
def compile(pattern, namespaces=None, flags=0, **kwargs): # noqa: A001
|
||||||
|
"""Compile CSS pattern."""
|
||||||
|
|
||||||
|
if namespaces is not None:
|
||||||
|
namespaces = ct.Namespaces(**namespaces)
|
||||||
|
|
||||||
|
custom = kwargs.get('custom')
|
||||||
|
if custom is not None:
|
||||||
|
custom = ct.CustomSelectors(**custom)
|
||||||
|
|
||||||
|
if isinstance(pattern, SoupSieve):
|
||||||
|
if flags:
|
||||||
|
raise ValueError("Cannot process 'flags' argument on a compiled selector list")
|
||||||
|
elif namespaces is not None:
|
||||||
|
raise ValueError("Cannot process 'namespaces' argument on a compiled selector list")
|
||||||
|
elif custom is not None:
|
||||||
|
raise ValueError("Cannot process 'custom' argument on a compiled selector list")
|
||||||
|
return pattern
|
||||||
|
|
||||||
|
return cp._cached_css_compile(pattern, namespaces, custom, flags)
|
||||||
|
|
||||||
|
|
||||||
|
def purge():
|
||||||
|
"""Purge cached patterns."""
|
||||||
|
|
||||||
|
cp._purge_cache()
|
||||||
|
|
||||||
|
|
||||||
|
def closest(select, tag, namespaces=None, flags=0, **kwargs):
|
||||||
|
"""Match closest ancestor."""
|
||||||
|
|
||||||
|
return compile(select, namespaces, flags, **kwargs).closest(tag)
|
||||||
|
|
||||||
|
|
||||||
|
def match(select, tag, namespaces=None, flags=0, **kwargs):
|
||||||
|
"""Match node."""
|
||||||
|
|
||||||
|
return compile(select, namespaces, flags, **kwargs).match(tag)
|
||||||
|
|
||||||
|
|
||||||
|
def filter(select, iterable, namespaces=None, flags=0, **kwargs): # noqa: A001
|
||||||
|
"""Filter list of nodes."""
|
||||||
|
|
||||||
|
return compile(select, namespaces, flags, **kwargs).filter(iterable)
|
||||||
|
|
||||||
|
|
||||||
|
@deprecated("'comments' is not related to CSS selectors and will be removed in the future.")
|
||||||
|
def comments(tag, limit=0, flags=0, **kwargs):
|
||||||
|
"""Get comments only."""
|
||||||
|
|
||||||
|
return [comment for comment in cm.CommentsMatch(tag).get_comments(limit)]
|
||||||
|
|
||||||
|
|
||||||
|
@deprecated("'icomments' is not related to CSS selectors and will be removed in the future.")
|
||||||
|
def icomments(tag, limit=0, flags=0, **kwargs):
|
||||||
|
"""Iterate comments only."""
|
||||||
|
|
||||||
|
for comment in cm.CommentsMatch(tag).get_comments(limit):
|
||||||
|
yield comment
|
||||||
|
|
||||||
|
|
||||||
|
def select_one(select, tag, namespaces=None, flags=0, **kwargs):
|
||||||
|
"""Select a single tag."""
|
||||||
|
|
||||||
|
return compile(select, namespaces, flags, **kwargs).select_one(tag)
|
||||||
|
|
||||||
|
|
||||||
|
def select(select, tag, namespaces=None, limit=0, flags=0, **kwargs):
|
||||||
|
"""Select the specified tags."""
|
||||||
|
|
||||||
|
return compile(select, namespaces, flags, **kwargs).select(tag, limit)
|
||||||
|
|
||||||
|
|
||||||
|
def iselect(select, tag, namespaces=None, limit=0, flags=0, **kwargs):
|
||||||
|
"""Iterate the specified tags."""
|
||||||
|
|
||||||
|
for el in compile(select, namespaces, flags, **kwargs).iselect(tag, limit):
|
||||||
|
yield el
|
||||||
|
|
||||||
|
|
||||||
|
def escape(ident):
|
||||||
|
"""Escape identifier."""
|
||||||
|
|
||||||
|
return cp.escape(ident)
|
190
lib/soupsieve/__meta__.py
Normal file
190
lib/soupsieve/__meta__.py
Normal file
|
@ -0,0 +1,190 @@
|
||||||
|
"""Meta related things."""
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
from collections import namedtuple
|
||||||
|
import re
|
||||||
|
|
||||||
|
RE_VER = re.compile(
|
||||||
|
r'''(?x)
|
||||||
|
(?P<major>\d+)(?:\.(?P<minor>\d+))?(?:\.(?P<micro>\d+))?
|
||||||
|
(?:(?P<type>a|b|rc)(?P<pre>\d+))?
|
||||||
|
(?:\.post(?P<post>\d+))?
|
||||||
|
(?:\.dev(?P<dev>\d+))?
|
||||||
|
'''
|
||||||
|
)
|
||||||
|
|
||||||
|
REL_MAP = {
|
||||||
|
".dev": "",
|
||||||
|
".dev-alpha": "a",
|
||||||
|
".dev-beta": "b",
|
||||||
|
".dev-candidate": "rc",
|
||||||
|
"alpha": "a",
|
||||||
|
"beta": "b",
|
||||||
|
"candidate": "rc",
|
||||||
|
"final": ""
|
||||||
|
}
|
||||||
|
|
||||||
|
DEV_STATUS = {
|
||||||
|
".dev": "2 - Pre-Alpha",
|
||||||
|
".dev-alpha": "2 - Pre-Alpha",
|
||||||
|
".dev-beta": "2 - Pre-Alpha",
|
||||||
|
".dev-candidate": "2 - Pre-Alpha",
|
||||||
|
"alpha": "3 - Alpha",
|
||||||
|
"beta": "4 - Beta",
|
||||||
|
"candidate": "4 - Beta",
|
||||||
|
"final": "5 - Production/Stable"
|
||||||
|
}
|
||||||
|
|
||||||
|
PRE_REL_MAP = {"a": 'alpha', "b": 'beta', "rc": 'candidate'}
|
||||||
|
|
||||||
|
|
||||||
|
class Version(namedtuple("Version", ["major", "minor", "micro", "release", "pre", "post", "dev"])):
|
||||||
|
"""
|
||||||
|
Get the version (PEP 440).
|
||||||
|
|
||||||
|
A biased approach to the PEP 440 semantic version.
|
||||||
|
|
||||||
|
Provides a tuple structure which is sorted for comparisons `v1 > v2` etc.
|
||||||
|
(major, minor, micro, release type, pre-release build, post-release build, development release build)
|
||||||
|
Release types are named in is such a way they are comparable with ease.
|
||||||
|
Accessors to check if a development, pre-release, or post-release build. Also provides accessor to get
|
||||||
|
development status for setup files.
|
||||||
|
|
||||||
|
How it works (currently):
|
||||||
|
|
||||||
|
- You must specify a release type as either `final`, `alpha`, `beta`, or `candidate`.
|
||||||
|
- To define a development release, you can use either `.dev`, `.dev-alpha`, `.dev-beta`, or `.dev-candidate`.
|
||||||
|
The dot is used to ensure all development specifiers are sorted before `alpha`.
|
||||||
|
You can specify a `dev` number for development builds, but do not have to as implicit development releases
|
||||||
|
are allowed.
|
||||||
|
- You must specify a `pre` value greater than zero if using a prerelease as this project (not PEP 440) does not
|
||||||
|
allow implicit prereleases.
|
||||||
|
- You can optionally set `post` to a value greater than zero to make the build a post release. While post releases
|
||||||
|
are technically allowed in prereleases, it is strongly discouraged, so we are rejecting them. It should be
|
||||||
|
noted that we do not allow `post0` even though PEP 440 does not restrict this. This project specifically
|
||||||
|
does not allow implicit post releases.
|
||||||
|
- It should be noted that we do not support epochs `1!` or local versions `+some-custom.version-1`.
|
||||||
|
|
||||||
|
Acceptable version releases:
|
||||||
|
|
||||||
|
```
|
||||||
|
Version(1, 0, 0, "final") 1.0
|
||||||
|
Version(1, 2, 0, "final") 1.2
|
||||||
|
Version(1, 2, 3, "final") 1.2.3
|
||||||
|
Version(1, 2, 0, ".dev-alpha", pre=4) 1.2a4
|
||||||
|
Version(1, 2, 0, ".dev-beta", pre=4) 1.2b4
|
||||||
|
Version(1, 2, 0, ".dev-candidate", pre=4) 1.2rc4
|
||||||
|
Version(1, 2, 0, "final", post=1) 1.2.post1
|
||||||
|
Version(1, 2, 3, ".dev") 1.2.3.dev0
|
||||||
|
Version(1, 2, 3, ".dev", dev=1) 1.2.3.dev1
|
||||||
|
```
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __new__(cls, major, minor, micro, release="final", pre=0, post=0, dev=0):
|
||||||
|
"""Validate version info."""
|
||||||
|
|
||||||
|
# Ensure all parts are positive integers.
|
||||||
|
for value in (major, minor, micro, pre, post):
|
||||||
|
if not (isinstance(value, int) and value >= 0):
|
||||||
|
raise ValueError("All version parts except 'release' should be integers.")
|
||||||
|
|
||||||
|
if release not in REL_MAP:
|
||||||
|
raise ValueError("'{}' is not a valid release type.".format(release))
|
||||||
|
|
||||||
|
# Ensure valid pre-release (we do not allow implicit pre-releases).
|
||||||
|
if ".dev-candidate" < release < "final":
|
||||||
|
if pre == 0:
|
||||||
|
raise ValueError("Implicit pre-releases not allowed.")
|
||||||
|
elif dev:
|
||||||
|
raise ValueError("Version is not a development release.")
|
||||||
|
elif post:
|
||||||
|
raise ValueError("Post-releases are not allowed with pre-releases.")
|
||||||
|
|
||||||
|
# Ensure valid development or development/pre release
|
||||||
|
elif release < "alpha":
|
||||||
|
if release > ".dev" and pre == 0:
|
||||||
|
raise ValueError("Implicit pre-release not allowed.")
|
||||||
|
elif post:
|
||||||
|
raise ValueError("Post-releases are not allowed with pre-releases.")
|
||||||
|
|
||||||
|
# Ensure a valid normal release
|
||||||
|
else:
|
||||||
|
if pre:
|
||||||
|
raise ValueError("Version is not a pre-release.")
|
||||||
|
elif dev:
|
||||||
|
raise ValueError("Version is not a development release.")
|
||||||
|
|
||||||
|
return super(Version, cls).__new__(cls, major, minor, micro, release, pre, post, dev)
|
||||||
|
|
||||||
|
def _is_pre(self):
|
||||||
|
"""Is prerelease."""
|
||||||
|
|
||||||
|
return self.pre > 0
|
||||||
|
|
||||||
|
def _is_dev(self):
|
||||||
|
"""Is development."""
|
||||||
|
|
||||||
|
return bool(self.release < "alpha")
|
||||||
|
|
||||||
|
def _is_post(self):
|
||||||
|
"""Is post."""
|
||||||
|
|
||||||
|
return self.post > 0
|
||||||
|
|
||||||
|
def _get_dev_status(self): # pragma: no cover
|
||||||
|
"""Get development status string."""
|
||||||
|
|
||||||
|
return DEV_STATUS[self.release]
|
||||||
|
|
||||||
|
def _get_canonical(self):
|
||||||
|
"""Get the canonical output string."""
|
||||||
|
|
||||||
|
# Assemble major, minor, micro version and append `pre`, `post`, or `dev` if needed..
|
||||||
|
if self.micro == 0:
|
||||||
|
ver = "{}.{}".format(self.major, self.minor)
|
||||||
|
else:
|
||||||
|
ver = "{}.{}.{}".format(self.major, self.minor, self.micro)
|
||||||
|
if self._is_pre():
|
||||||
|
ver += '{}{}'.format(REL_MAP[self.release], self.pre)
|
||||||
|
if self._is_post():
|
||||||
|
ver += ".post{}".format(self.post)
|
||||||
|
if self._is_dev():
|
||||||
|
ver += ".dev{}".format(self.dev)
|
||||||
|
|
||||||
|
return ver
|
||||||
|
|
||||||
|
|
||||||
|
def parse_version(ver, pre=False):
|
||||||
|
"""Parse version into a comparable Version tuple."""
|
||||||
|
|
||||||
|
m = RE_VER.match(ver)
|
||||||
|
|
||||||
|
# Handle major, minor, micro
|
||||||
|
major = int(m.group('major'))
|
||||||
|
minor = int(m.group('minor')) if m.group('minor') else 0
|
||||||
|
micro = int(m.group('micro')) if m.group('micro') else 0
|
||||||
|
|
||||||
|
# Handle pre releases
|
||||||
|
if m.group('type'):
|
||||||
|
release = PRE_REL_MAP[m.group('type')]
|
||||||
|
pre = int(m.group('pre'))
|
||||||
|
else:
|
||||||
|
release = "final"
|
||||||
|
pre = 0
|
||||||
|
|
||||||
|
# Handle development releases
|
||||||
|
dev = m.group('dev') if m.group('dev') else 0
|
||||||
|
if m.group('dev'):
|
||||||
|
dev = int(m.group('dev'))
|
||||||
|
release = '.dev-' + release if pre else '.dev'
|
||||||
|
else:
|
||||||
|
dev = 0
|
||||||
|
|
||||||
|
# Handle post
|
||||||
|
post = int(m.group('post')) if m.group('post') else 0
|
||||||
|
|
||||||
|
return Version(major, minor, micro, release, pre, post, dev)
|
||||||
|
|
||||||
|
|
||||||
|
__version_info__ = Version(1, 9, 5, "final")
|
||||||
|
__version__ = __version_info__._get_canonical()
|
1542
lib/soupsieve/css_match.py
Normal file
1542
lib/soupsieve/css_match.py
Normal file
File diff suppressed because it is too large
Load diff
1213
lib/soupsieve/css_parser.py
Normal file
1213
lib/soupsieve/css_parser.py
Normal file
File diff suppressed because it is too large
Load diff
345
lib/soupsieve/css_types.py
Normal file
345
lib/soupsieve/css_types.py
Normal file
|
@ -0,0 +1,345 @@
|
||||||
|
"""CSS selector structure items."""
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
from . import util
|
||||||
|
|
||||||
|
__all__ = (
|
||||||
|
'Selector',
|
||||||
|
'SelectorNull',
|
||||||
|
'SelectorTag',
|
||||||
|
'SelectorAttribute',
|
||||||
|
'SelectorContains',
|
||||||
|
'SelectorNth',
|
||||||
|
'SelectorLang',
|
||||||
|
'SelectorList',
|
||||||
|
'Namespaces',
|
||||||
|
'CustomSelectors'
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
SEL_EMPTY = 0x1
|
||||||
|
SEL_ROOT = 0x2
|
||||||
|
SEL_DEFAULT = 0x4
|
||||||
|
SEL_INDETERMINATE = 0x8
|
||||||
|
SEL_SCOPE = 0x10
|
||||||
|
SEL_DIR_LTR = 0x20
|
||||||
|
SEL_DIR_RTL = 0x40
|
||||||
|
SEL_IN_RANGE = 0x80
|
||||||
|
SEL_OUT_OF_RANGE = 0x100
|
||||||
|
SEL_DEFINED = 0x200
|
||||||
|
SEL_PLACEHOLDER_SHOWN = 0x400
|
||||||
|
|
||||||
|
|
||||||
|
class Immutable(object):
|
||||||
|
"""Immutable."""
|
||||||
|
|
||||||
|
__slots__ = ('_hash',)
|
||||||
|
|
||||||
|
def __init__(self, **kwargs):
|
||||||
|
"""Initialize."""
|
||||||
|
|
||||||
|
temp = []
|
||||||
|
for k, v in kwargs.items():
|
||||||
|
temp.append(type(v))
|
||||||
|
temp.append(v)
|
||||||
|
super(Immutable, self).__setattr__(k, v)
|
||||||
|
super(Immutable, self).__setattr__('_hash', hash(tuple(temp)))
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def __base__(cls):
|
||||||
|
"""Get base class."""
|
||||||
|
|
||||||
|
return cls
|
||||||
|
|
||||||
|
def __eq__(self, other):
|
||||||
|
"""Equal."""
|
||||||
|
|
||||||
|
return (
|
||||||
|
isinstance(other, self.__base__()) and
|
||||||
|
all([getattr(other, key) == getattr(self, key) for key in self.__slots__ if key != '_hash'])
|
||||||
|
)
|
||||||
|
|
||||||
|
def __ne__(self, other):
|
||||||
|
"""Equal."""
|
||||||
|
|
||||||
|
return (
|
||||||
|
not isinstance(other, self.__base__()) or
|
||||||
|
any([getattr(other, key) != getattr(self, key) for key in self.__slots__ if key != '_hash'])
|
||||||
|
)
|
||||||
|
|
||||||
|
def __hash__(self):
|
||||||
|
"""Hash."""
|
||||||
|
|
||||||
|
return self._hash
|
||||||
|
|
||||||
|
def __setattr__(self, name, value):
|
||||||
|
"""Prevent mutability."""
|
||||||
|
|
||||||
|
raise AttributeError("'{}' is immutable".format(self.__class__.__name__))
|
||||||
|
|
||||||
|
def __repr__(self): # pragma: no cover
|
||||||
|
"""Representation."""
|
||||||
|
|
||||||
|
return "{}({})".format(
|
||||||
|
self.__base__(), ', '.join(["{}={!r}".format(k, getattr(self, k)) for k in self.__slots__[:-1]])
|
||||||
|
)
|
||||||
|
|
||||||
|
__str__ = __repr__
|
||||||
|
|
||||||
|
|
||||||
|
class ImmutableDict(util.Mapping):
|
||||||
|
"""Hashable, immutable dictionary."""
|
||||||
|
|
||||||
|
def __init__(self, *args, **kwargs):
|
||||||
|
"""Initialize."""
|
||||||
|
|
||||||
|
arg = args[0] if args else kwargs
|
||||||
|
is_dict = isinstance(arg, dict)
|
||||||
|
if (
|
||||||
|
is_dict and not all([isinstance(v, util.Hashable) for v in arg.values()]) or
|
||||||
|
not is_dict and not all([isinstance(k, util.Hashable) and isinstance(v, util.Hashable) for k, v in arg])
|
||||||
|
):
|
||||||
|
raise TypeError('All values must be hashable')
|
||||||
|
|
||||||
|
self._d = dict(*args, **kwargs)
|
||||||
|
self._hash = hash(tuple([(type(x), x, type(y), y) for x, y in sorted(self._d.items())]))
|
||||||
|
|
||||||
|
def __iter__(self):
|
||||||
|
"""Iterator."""
|
||||||
|
|
||||||
|
return iter(self._d)
|
||||||
|
|
||||||
|
def __len__(self):
|
||||||
|
"""Length."""
|
||||||
|
|
||||||
|
return len(self._d)
|
||||||
|
|
||||||
|
def __getitem__(self, key):
|
||||||
|
"""Get item: `namespace['key']`."""
|
||||||
|
return self._d[key]
|
||||||
|
|
||||||
|
def __hash__(self):
|
||||||
|
"""Hash."""
|
||||||
|
|
||||||
|
return self._hash
|
||||||
|
|
||||||
|
def __repr__(self): # pragma: no cover
|
||||||
|
"""Representation."""
|
||||||
|
|
||||||
|
return "{!r}".format(self._d)
|
||||||
|
|
||||||
|
__str__ = __repr__
|
||||||
|
|
||||||
|
|
||||||
|
class Namespaces(ImmutableDict):
|
||||||
|
"""Namespaces."""
|
||||||
|
|
||||||
|
def __init__(self, *args, **kwargs):
|
||||||
|
"""Initialize."""
|
||||||
|
|
||||||
|
# If there are arguments, check the first index.
|
||||||
|
# `super` should fail if the user gave multiple arguments,
|
||||||
|
# so don't bother checking that.
|
||||||
|
arg = args[0] if args else kwargs
|
||||||
|
is_dict = isinstance(arg, dict)
|
||||||
|
if is_dict and not all([isinstance(k, util.string) and isinstance(v, util.string) for k, v in arg.items()]):
|
||||||
|
raise TypeError('Namespace keys and values must be Unicode strings')
|
||||||
|
elif not is_dict and not all([isinstance(k, util.string) and isinstance(v, util.string) for k, v in arg]):
|
||||||
|
raise TypeError('Namespace keys and values must be Unicode strings')
|
||||||
|
|
||||||
|
super(Namespaces, self).__init__(*args, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
|
class CustomSelectors(ImmutableDict):
|
||||||
|
"""Custom selectors."""
|
||||||
|
|
||||||
|
def __init__(self, *args, **kwargs):
|
||||||
|
"""Initialize."""
|
||||||
|
|
||||||
|
# If there are arguments, check the first index.
|
||||||
|
# `super` should fail if the user gave multiple arguments,
|
||||||
|
# so don't bother checking that.
|
||||||
|
arg = args[0] if args else kwargs
|
||||||
|
is_dict = isinstance(arg, dict)
|
||||||
|
if is_dict and not all([isinstance(k, util.string) and isinstance(v, util.string) for k, v in arg.items()]):
|
||||||
|
raise TypeError('CustomSelectors keys and values must be Unicode strings')
|
||||||
|
elif not is_dict and not all([isinstance(k, util.string) and isinstance(v, util.string) for k, v in arg]):
|
||||||
|
raise TypeError('CustomSelectors keys and values must be Unicode strings')
|
||||||
|
|
||||||
|
super(CustomSelectors, self).__init__(*args, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
|
class Selector(Immutable):
|
||||||
|
"""Selector."""
|
||||||
|
|
||||||
|
__slots__ = (
|
||||||
|
'tag', 'ids', 'classes', 'attributes', 'nth', 'selectors',
|
||||||
|
'relation', 'rel_type', 'contains', 'lang', 'flags', '_hash'
|
||||||
|
)
|
||||||
|
|
||||||
|
def __init__(
|
||||||
|
self, tag, ids, classes, attributes, nth, selectors,
|
||||||
|
relation, rel_type, contains, lang, flags
|
||||||
|
):
|
||||||
|
"""Initialize."""
|
||||||
|
|
||||||
|
super(Selector, self).__init__(
|
||||||
|
tag=tag,
|
||||||
|
ids=ids,
|
||||||
|
classes=classes,
|
||||||
|
attributes=attributes,
|
||||||
|
nth=nth,
|
||||||
|
selectors=selectors,
|
||||||
|
relation=relation,
|
||||||
|
rel_type=rel_type,
|
||||||
|
contains=contains,
|
||||||
|
lang=lang,
|
||||||
|
flags=flags
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class SelectorNull(Immutable):
|
||||||
|
"""Null Selector."""
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
"""Initialize."""
|
||||||
|
|
||||||
|
super(SelectorNull, self).__init__()
|
||||||
|
|
||||||
|
|
||||||
|
class SelectorTag(Immutable):
|
||||||
|
"""Selector tag."""
|
||||||
|
|
||||||
|
__slots__ = ("name", "prefix", "_hash")
|
||||||
|
|
||||||
|
def __init__(self, name, prefix):
|
||||||
|
"""Initialize."""
|
||||||
|
|
||||||
|
super(SelectorTag, self).__init__(
|
||||||
|
name=name,
|
||||||
|
prefix=prefix
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class SelectorAttribute(Immutable):
|
||||||
|
"""Selector attribute rule."""
|
||||||
|
|
||||||
|
__slots__ = ("attribute", "prefix", "pattern", "xml_type_pattern", "_hash")
|
||||||
|
|
||||||
|
def __init__(self, attribute, prefix, pattern, xml_type_pattern):
|
||||||
|
"""Initialize."""
|
||||||
|
|
||||||
|
super(SelectorAttribute, self).__init__(
|
||||||
|
attribute=attribute,
|
||||||
|
prefix=prefix,
|
||||||
|
pattern=pattern,
|
||||||
|
xml_type_pattern=xml_type_pattern
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class SelectorContains(Immutable):
|
||||||
|
"""Selector contains rule."""
|
||||||
|
|
||||||
|
__slots__ = ("text", "_hash")
|
||||||
|
|
||||||
|
def __init__(self, text):
|
||||||
|
"""Initialize."""
|
||||||
|
|
||||||
|
super(SelectorContains, self).__init__(
|
||||||
|
text=text
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class SelectorNth(Immutable):
|
||||||
|
"""Selector nth type."""
|
||||||
|
|
||||||
|
__slots__ = ("a", "n", "b", "of_type", "last", "selectors", "_hash")
|
||||||
|
|
||||||
|
def __init__(self, a, n, b, of_type, last, selectors):
|
||||||
|
"""Initialize."""
|
||||||
|
|
||||||
|
super(SelectorNth, self).__init__(
|
||||||
|
a=a,
|
||||||
|
n=n,
|
||||||
|
b=b,
|
||||||
|
of_type=of_type,
|
||||||
|
last=last,
|
||||||
|
selectors=selectors
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class SelectorLang(Immutable):
|
||||||
|
"""Selector language rules."""
|
||||||
|
|
||||||
|
__slots__ = ("languages", "_hash",)
|
||||||
|
|
||||||
|
def __init__(self, languages):
|
||||||
|
"""Initialize."""
|
||||||
|
|
||||||
|
super(SelectorLang, self).__init__(
|
||||||
|
languages=tuple(languages)
|
||||||
|
)
|
||||||
|
|
||||||
|
def __iter__(self):
|
||||||
|
"""Iterator."""
|
||||||
|
|
||||||
|
return iter(self.languages)
|
||||||
|
|
||||||
|
def __len__(self): # pragma: no cover
|
||||||
|
"""Length."""
|
||||||
|
|
||||||
|
return len(self.languages)
|
||||||
|
|
||||||
|
def __getitem__(self, index): # pragma: no cover
|
||||||
|
"""Get item."""
|
||||||
|
|
||||||
|
return self.languages[index]
|
||||||
|
|
||||||
|
|
||||||
|
class SelectorList(Immutable):
|
||||||
|
"""Selector list."""
|
||||||
|
|
||||||
|
__slots__ = ("selectors", "is_not", "is_html", "_hash")
|
||||||
|
|
||||||
|
def __init__(self, selectors=tuple(), is_not=False, is_html=False):
|
||||||
|
"""Initialize."""
|
||||||
|
|
||||||
|
super(SelectorList, self).__init__(
|
||||||
|
selectors=tuple(selectors),
|
||||||
|
is_not=is_not,
|
||||||
|
is_html=is_html
|
||||||
|
)
|
||||||
|
|
||||||
|
def __iter__(self):
|
||||||
|
"""Iterator."""
|
||||||
|
|
||||||
|
return iter(self.selectors)
|
||||||
|
|
||||||
|
def __len__(self):
|
||||||
|
"""Length."""
|
||||||
|
|
||||||
|
return len(self.selectors)
|
||||||
|
|
||||||
|
def __getitem__(self, index):
|
||||||
|
"""Get item."""
|
||||||
|
|
||||||
|
return self.selectors[index]
|
||||||
|
|
||||||
|
|
||||||
|
def _pickle(p):
|
||||||
|
return p.__base__(), tuple([getattr(p, s) for s in p.__slots__[:-1]])
|
||||||
|
|
||||||
|
|
||||||
|
def pickle_register(obj):
|
||||||
|
"""Allow object to be pickled."""
|
||||||
|
|
||||||
|
util.copyreg.pickle(obj, _pickle)
|
||||||
|
|
||||||
|
|
||||||
|
pickle_register(Selector)
|
||||||
|
pickle_register(SelectorNull)
|
||||||
|
pickle_register(SelectorTag)
|
||||||
|
pickle_register(SelectorAttribute)
|
||||||
|
pickle_register(SelectorContains)
|
||||||
|
pickle_register(SelectorNth)
|
||||||
|
pickle_register(SelectorLang)
|
||||||
|
pickle_register(SelectorList)
|
170
lib/soupsieve/util.py
Normal file
170
lib/soupsieve/util.py
Normal file
|
@ -0,0 +1,170 @@
|
||||||
|
"""Utility."""
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
from functools import wraps
|
||||||
|
import warnings
|
||||||
|
import sys
|
||||||
|
import struct
|
||||||
|
import os
|
||||||
|
import re
|
||||||
|
MODULE = os.path.dirname(__file__)
|
||||||
|
|
||||||
|
PY3 = sys.version_info >= (3, 0)
|
||||||
|
PY35 = sys.version_info >= (3, 5)
|
||||||
|
PY37 = sys.version_info >= (3, 7)
|
||||||
|
|
||||||
|
if PY3:
|
||||||
|
from functools import lru_cache # noqa F401
|
||||||
|
import copyreg # noqa F401
|
||||||
|
from collections.abc import Hashable, Mapping # noqa F401
|
||||||
|
|
||||||
|
ustr = str
|
||||||
|
bstr = bytes
|
||||||
|
unichar = chr
|
||||||
|
string = str
|
||||||
|
else:
|
||||||
|
from backports.functools_lru_cache import lru_cache # noqa F401
|
||||||
|
import copy_reg as copyreg # noqa F401
|
||||||
|
from collections import Hashable, Mapping # noqa F401
|
||||||
|
|
||||||
|
ustr = unicode # noqa: F821
|
||||||
|
bstr = str
|
||||||
|
unichar = unichr # noqa: F821
|
||||||
|
string = basestring # noqa: F821
|
||||||
|
|
||||||
|
DEBUG = 0x00001
|
||||||
|
|
||||||
|
RE_PATTERN_LINE_SPLIT = re.compile(r'(?:\r\n|(?!\r\n)[\n\r])|$')
|
||||||
|
|
||||||
|
LC_A = ord('a')
|
||||||
|
LC_Z = ord('z')
|
||||||
|
UC_A = ord('A')
|
||||||
|
UC_Z = ord('Z')
|
||||||
|
|
||||||
|
|
||||||
|
def lower(string):
|
||||||
|
"""Lower."""
|
||||||
|
|
||||||
|
new_string = []
|
||||||
|
for c in string:
|
||||||
|
o = ord(c)
|
||||||
|
new_string.append(chr(o + 32) if UC_A <= o <= UC_Z else c)
|
||||||
|
return ''.join(new_string)
|
||||||
|
|
||||||
|
|
||||||
|
def upper(string): # pragma: no cover
|
||||||
|
"""Lower."""
|
||||||
|
|
||||||
|
new_string = []
|
||||||
|
for c in string:
|
||||||
|
o = ord(c)
|
||||||
|
new_string.append(chr(o - 32) if LC_A <= o <= LC_Z else c)
|
||||||
|
return ''.join(new_string)
|
||||||
|
|
||||||
|
|
||||||
|
def uchr(i):
|
||||||
|
"""Allow getting Unicode character on narrow python builds."""
|
||||||
|
|
||||||
|
try:
|
||||||
|
return unichar(i)
|
||||||
|
except ValueError: # pragma: no cover
|
||||||
|
return struct.pack('i', i).decode('utf-32')
|
||||||
|
|
||||||
|
|
||||||
|
def uord(c):
|
||||||
|
"""Get Unicode ordinal."""
|
||||||
|
|
||||||
|
if len(c) == 2: # pragma: no cover
|
||||||
|
high, low = [ord(p) for p in c]
|
||||||
|
ordinal = (high - 0xD800) * 0x400 + low - 0xDC00 + 0x10000
|
||||||
|
else:
|
||||||
|
ordinal = ord(c)
|
||||||
|
|
||||||
|
return ordinal
|
||||||
|
|
||||||
|
|
||||||
|
class SelectorSyntaxError(SyntaxError):
|
||||||
|
"""Syntax error in a CSS selector."""
|
||||||
|
|
||||||
|
def __init__(self, msg, pattern=None, index=None):
|
||||||
|
"""Initialize."""
|
||||||
|
|
||||||
|
self.line = None
|
||||||
|
self.col = None
|
||||||
|
self.context = None
|
||||||
|
|
||||||
|
if pattern is not None and index is not None:
|
||||||
|
# Format pattern to show line and column position
|
||||||
|
self.context, self.line, self.col = get_pattern_context(pattern, index)
|
||||||
|
msg = '{}\n line {}:\n{}'.format(msg, self.line, self.context)
|
||||||
|
|
||||||
|
super(SelectorSyntaxError, self).__init__(msg)
|
||||||
|
|
||||||
|
|
||||||
|
def deprecated(message, stacklevel=2): # pragma: no cover
|
||||||
|
"""
|
||||||
|
Raise a `DeprecationWarning` when wrapped function/method is called.
|
||||||
|
|
||||||
|
Borrowed from https://stackoverflow.com/a/48632082/866026
|
||||||
|
"""
|
||||||
|
|
||||||
|
def _decorator(func):
|
||||||
|
@wraps(func)
|
||||||
|
def _func(*args, **kwargs):
|
||||||
|
warnings.warn(
|
||||||
|
"'{}' is deprecated. {}".format(func.__name__, message),
|
||||||
|
category=DeprecationWarning,
|
||||||
|
stacklevel=stacklevel
|
||||||
|
)
|
||||||
|
return func(*args, **kwargs)
|
||||||
|
return _func
|
||||||
|
return _decorator
|
||||||
|
|
||||||
|
|
||||||
|
def warn_deprecated(message, stacklevel=2): # pragma: no cover
|
||||||
|
"""Warn deprecated."""
|
||||||
|
|
||||||
|
warnings.warn(
|
||||||
|
message,
|
||||||
|
category=DeprecationWarning,
|
||||||
|
stacklevel=stacklevel
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def get_pattern_context(pattern, index):
|
||||||
|
"""Get the pattern context."""
|
||||||
|
|
||||||
|
last = 0
|
||||||
|
current_line = 1
|
||||||
|
col = 1
|
||||||
|
text = []
|
||||||
|
line = 1
|
||||||
|
|
||||||
|
# Split pattern by newline and handle the text before the newline
|
||||||
|
for m in RE_PATTERN_LINE_SPLIT.finditer(pattern):
|
||||||
|
linetext = pattern[last:m.start(0)]
|
||||||
|
if not len(m.group(0)) and not len(text):
|
||||||
|
indent = ''
|
||||||
|
offset = -1
|
||||||
|
col = index - last + 1
|
||||||
|
elif last <= index < m.end(0):
|
||||||
|
indent = '--> '
|
||||||
|
offset = (-1 if index > m.start(0) else 0) + 3
|
||||||
|
col = index - last + 1
|
||||||
|
else:
|
||||||
|
indent = ' '
|
||||||
|
offset = None
|
||||||
|
if len(text):
|
||||||
|
# Regardless of whether we are presented with `\r\n`, `\r`, or `\n`,
|
||||||
|
# we will render the output with just `\n`. We will still log the column
|
||||||
|
# correctly though.
|
||||||
|
text.append('\n')
|
||||||
|
text.append('{}{}'.format(indent, linetext))
|
||||||
|
if offset is not None:
|
||||||
|
text.append('\n')
|
||||||
|
text.append(' ' * (col + offset) + '^')
|
||||||
|
line = current_line
|
||||||
|
|
||||||
|
current_line += 1
|
||||||
|
last = m.end(0)
|
||||||
|
|
||||||
|
return ''.join(text), line, col
|
Loading…
Add table
Add a link
Reference in a new issue