Bug 1779473 - Add TOML support to manifestparser r=jmaher,ahal

ManifestParser will read TOML files, if present, when use_toml=True
Added tomlkit as a third_party python package
Added poetry-core and tomlkit to pypi (separately as Bug 1845383, Bug 1844787)
Adds TOML test coverage
Adds tomlkit as a dependency of mozharness (in test_archive.py)
Added tomlkit to virtualenv_modules in testing/mozharness/configs/unittests
Removes dependency on six

testing/tools/mach_test_package_initialize.py
- Corrected SEARCH_PATHS

testing/mozharness/mozharness/mozilla/testing/per_test_base.py
- moved `from manifestparser import TestManifest` into function call
  to avoid harness inability to locate the internal artifact
- Removed linter warnings

testing/mozbase/manifestparser/manifestparser/manifestparser.py
- Removed linter warnings
- Updated logger usage pattern
- Simplifed _read logic, refactored get_fp_filename()
- Improve context for `include:` logging message
- Defer `import mozlog` until the point of use

testing/mozbase/manifestparser/manifestparser/toml.py
- Removed linter warnings
- Removed unused logger
- Improved readability of read_toml()

testing/mozbase/manifestparser/manifestparser/ini.py
- Removed linter warnings
- Removed unused logger

testing/mozbase/manifestparser/manifestparser/filters.py
- Removed linter warnings

testing/mozbase/manifestparser/tests/test_chunking.py
- Removed linter warnings

Bumped manifestparser version to 2.2.31

Differential Revision: https://phabricator.services.mozilla.com/D184020
This commit is contained in:
Tom Marble 2023-07-27 20:16:19 +00:00
Родитель bbdc4e1f0a
Коммит b51988220c
77 изменённых файлов: 6347 добавлений и 125 удалений

Просмотреть файл

@ -313,6 +313,16 @@ ARCHIVE_FILES = {
"base": "testing/mozbase/mozinfo",
"pattern": "mozinfo/**",
},
{
"source": buildconfig.topsrcdir,
"base": "testing/mozbase/mozlog",
"pattern": "mozlog/**",
},
{
"source": buildconfig.topsrcdir,
"base": "python/mozterm",
"pattern": "mozterm/**",
},
{
"source": buildconfig.topsrcdir,
"base": "testing/mozbase/mozprocess",
@ -323,6 +333,11 @@ ARCHIVE_FILES = {
"base": "third_party/python/six",
"pattern": "six.py",
},
{
"source": buildconfig.topsrcdir,
"base": "third_party/python/tomlkit",
"pattern": "**",
},
{
"source": buildconfig.topsrcdir,
"base": "third_party/python/distro",

Просмотреть файл

@ -25,8 +25,6 @@ vendored:third_party/python/fluent.syntax
vendored:third_party/python/giturlparse
vendored:third_party/python/glean_parser
vendored:third_party/python/gyp/pylib
vendored:third_party/python/importlib_metadata
vendored:third_party/python/importlib_resources
vendored:third_party/python/jinja2_time
vendored:third_party/python/json-e
vendored:third_party/python/jsonschema
@ -51,8 +49,6 @@ vendored:third_party/python/taskcluster_taskgraph
vendored:third_party/python/taskcluster_urls
vendored:third_party/python/text_unidecode
vendored:third_party/python/toml
vendored:third_party/python/typing_extensions
vendored:third_party/python/voluptuous
vendored:third_party/python/yamllint
vendored:third_party/python/yarl
vendored:third_party/python/zipp

Просмотреть файл

@ -28,8 +28,6 @@ vendored:third_party/python/fluent.syntax
vendored:third_party/python/giturlparse
vendored:third_party/python/glean_parser
vendored:third_party/python/gyp/pylib
vendored:third_party/python/importlib_metadata
vendored:third_party/python/importlib_resources
vendored:third_party/python/jinja2_time
vendored:third_party/python/json-e
vendored:third_party/python/jsonschema
@ -56,8 +54,6 @@ vendored:third_party/python/taskcluster_taskgraph
vendored:third_party/python/taskcluster_urls
vendored:third_party/python/text_unidecode
vendored:third_party/python/toml
vendored:third_party/python/typing_extensions
vendored:third_party/python/voluptuous
vendored:third_party/python/yamllint
vendored:third_party/python/yarl
vendored:third_party/python/zipp

Просмотреть файл

@ -58,8 +58,6 @@ vendored:third_party/python/chardet
vendored:third_party/python/cookiecutter
vendored:third_party/python/fluent.syntax
vendored:third_party/python/giturlparse
vendored:third_party/python/importlib_metadata
vendored:third_party/python/importlib_resources
vendored:third_party/python/jinja2_time
vendored:third_party/python/json-e
vendored:third_party/python/jsonschema
@ -84,4 +82,3 @@ vendored:third_party/python/text_unidecode
vendored:third_party/python/toml
vendored:third_party/python/urllib3
vendored:third_party/python/voluptuous
vendored:third_party/python/zipp

Просмотреть файл

@ -29,7 +29,6 @@ vendored:third_party/python/taskcluster_taskgraph
vendored:third_party/python/taskcluster_urls
vendored:third_party/python/text_unidecode
vendored:third_party/python/toml
vendored:third_party/python/typing_extensions
vendored:third_party/python/voluptuous
vendored:third_party/python/yamllint
vendored:third_party/python/yarl

Просмотреть файл

@ -65,6 +65,8 @@ vendored:third_party/python/click
vendored:third_party/python/colorama
vendored:third_party/python/distro
vendored:third_party/python/idna
vendored:third_party/python/importlib_metadata
vendored:third_party/python/importlib_resources
vendored:third_party/python/jinxed
vendored:third_party/python/jsmin
vendored:third_party/python/looseversion
@ -78,10 +80,13 @@ vendored:third_party/python/requests
vendored:third_party/python/sentry_sdk
vendored:third_party/python/setuptools
vendored:third_party/python/six
vendored:third_party/python/tomlkit
vendored:third_party/python/tqdm
vendored:third_party/python/typing_extensions
vendored:third_party/python/urllib3
vendored:third_party/python/wcwidth
vendored:third_party/python/wheel
vendored:third_party/python/zipp
# glean-sdk may not be installable if a wheel isn't available
# and it has to be built from source.
pypi-optional:glean-sdk==53.1.0:telemetry will not be collected

Просмотреть файл

@ -3,8 +3,7 @@ pypi:coverage==5.1
vendored:third_party/python/PyYAML/lib/
vendored:third_party/python/dlmanager
vendored:third_party/python/esprima
vendored:third_party/python/importlib_resources
vendored:third_party/python/jsonschema
vendored:third_party/python/pyrsistent
vendored:third_party/python/redo
vendored:third_party/python/responses
vendored:third_party/python/responses

Просмотреть файл

@ -29,8 +29,6 @@ vendored:third_party/python/fluent.syntax
vendored:third_party/python/giturlparse
vendored:third_party/python/glean_parser
vendored:third_party/python/gyp/pylib
vendored:third_party/python/importlib_metadata
vendored:third_party/python/importlib_resources
vendored:third_party/python/jinja2_time
vendored:third_party/python/json-e
vendored:third_party/python/jsonschema
@ -57,8 +55,6 @@ vendored:third_party/python/taskcluster_taskgraph
vendored:third_party/python/taskcluster_urls
vendored:third_party/python/text_unidecode
vendored:third_party/python/toml
vendored:third_party/python/typing_extensions
vendored:third_party/python/voluptuous
vendored:third_party/python/yamllint
vendored:third_party/python/yarl
vendored:third_party/python/zipp

Просмотреть файл

@ -1,5 +1,5 @@
../tools/mozterm
../tools/geckoprocesstypes_generator
../tools/mozterm
../mozbase/manifestparser
../mozbase/mozcrash
@ -28,4 +28,5 @@ aiohttp==3.7.4.post0; sys_platform != 'darwin'
https://pypi.pub.build.mozilla.org/pub/arsenic-19.1-py3-none-any.whl; sys_platform != 'darwin'
requests==2.22.0; sys_platform != 'darwin'
pyyaml==5.1.2; sys_platform != 'darwin'
structlog==15.2.0; sys_platform != 'darwin'
structlog==15.2.0; sys_platform != 'darwin'
tomlkit==0.11.8

Просмотреть файл

@ -6,8 +6,6 @@ import re
import sys
import traceback
import six
__all__ = ["parse", "ParseError", "ExpressionParser"]
# expr.py
@ -276,7 +274,7 @@ class ExpressionParser(object):
"""
if not isinstance(self.token, expected):
raise Exception("Unexpected token!")
self.token = six.next(self.iter)
self.token = next(self.iter)
def expression(self, rbp=0):
"""
@ -284,11 +282,11 @@ class ExpressionParser(object):
right binding power greater than rbp is encountered.
"""
t = self.token
self.token = six.next(self.iter)
self.token = next(self.iter)
left = t.nud(self)
while rbp < self.token.lbp:
t = self.token
self.token = six.next(self.iter)
self.token = next(self.iter)
left = t.led(self, left)
return left
@ -300,19 +298,16 @@ class ExpressionParser(object):
"""
try:
self.iter = self._tokenize()
self.token = six.next(self.iter)
self.token = next(self.iter)
return self.expression()
except Exception:
extype, ex, tb = sys.exc_info()
formatted = "".join(traceback.format_exception_only(extype, ex))
six.reraise(
ParseError,
ParseError(
"could not parse: %s\nexception: %svariables: %s"
% (self.text, formatted, self.valuemapping)
),
tb,
pe = ParseError(
"could not parse: %s\nexception: %svariables: %s"
% (self.text, formatted, self.valuemapping)
)
raise pe.with_traceback(tb)
__call__ = parse

Просмотреть файл

@ -13,25 +13,9 @@ import os
from collections import defaultdict
from collections.abc import MutableSequence
import six
from six import string_types
from .expression import ParseError, parse
from .util import normsep
logger = None
def log(msg, level="info"):
from mozlog import get_default_logger
global logger
if not logger:
logger = get_default_logger(component="manifestparser")
if logger:
getattr(logger, level)(msg)
# built-in filters
@ -118,7 +102,7 @@ class InstanceFilter(object):
self.fmt_args = ", ".join(
itertools.chain(
[str(a) for a in args],
["{}={}".format(k, v) for k, v in six.iteritems(kwargs)],
["{}={}".format(k, v) for k, v in kwargs.items()],
)
)
@ -170,9 +154,8 @@ class subsuite(InstanceFilter):
if self.name is None:
if not test.get("subsuite"):
yield test
else:
if test.get("subsuite", "") == self.name:
yield test
elif test.get("subsuite", "") == self.name:
yield test
class chunk_by_slice(InstanceFilter):
@ -279,7 +262,7 @@ class chunk_by_dir(InstanceFilter):
# simplicity.
if self.this_chunk == 1:
disabled_dirs = [
v for k, v in six.iteritems(tests_by_dir) if k not in ordered_dirs
v for k, v in tests_by_dir.items() if k not in ordered_dirs
]
for disabled_test in itertools.chain(*disabled_dirs):
yield disabled_test
@ -341,6 +324,12 @@ class chunk_by_runtime(InstanceFilter):
self.this_chunk = this_chunk
self.total_chunks = total_chunks
self.runtimes = {normsep(m): r for m, r in runtimes.items()}
component = "filters"
import mozlog
self.logger = mozlog.get_default_logger(component)
if self.logger is None:
self.logger = mozlog.unstructured.getLogger(component)
@classmethod
def get_manifest(cls, test):
@ -364,7 +353,7 @@ class chunk_by_runtime(InstanceFilter):
# pylint: disable=W1633
avg = round(sum(times) / len(times), 2) if times else 0
missing = sorted([m for m in manifests if m not in self.runtimes])
log(
self.logger.info(
"Applying average runtime of {}s to the following missing manifests:\n{}".format(
avg, " " + "\n ".join(missing)
)
@ -394,7 +383,7 @@ class chunk_by_runtime(InstanceFilter):
runtime, this_manifests = chunks[self.this_chunk - 1]
# pylint --py3k W1619
# pylint: disable=W1633
log(
self.logger.info(
"Cumulative test runtime is around {} minutes (average is {} minutes)".format(
round(runtime / 60),
round(sum([c[0] for c in chunks]) / (60 * len(chunks))),
@ -423,7 +412,7 @@ class tags(InstanceFilter):
def __init__(self, tags):
InstanceFilter.__init__(self, tags)
if isinstance(tags, string_types):
if isinstance(tags, str):
tags = [tags]
self.tags = tags
@ -477,7 +466,7 @@ class pathprefix(InstanceFilter):
def __init__(self, paths):
InstanceFilter.__init__(self, paths)
if isinstance(paths, string_types):
if isinstance(paths, str):
paths = [paths]
self.paths = paths
self.missing = set()
@ -485,8 +474,8 @@ class pathprefix(InstanceFilter):
def __call__(self, tests, values):
seen = set()
for test in tests:
for tp in self.paths:
tp = os.path.normpath(tp)
for testpath in self.paths:
tp = os.path.normpath(testpath)
if tp.endswith(".ini"):
mpaths = [test["manifest_relpath"]]

Просмотреть файл

@ -6,14 +6,12 @@ import io
import os
import sys
from six import string_types
__all__ = ["read_ini", "combine_fields"]
class IniParseError(Exception):
def __init__(self, fp, linenum, msg):
if isinstance(fp, string_types):
if isinstance(fp, str):
path = fp
elif hasattr(fp, "name"):
path = fp.name
@ -51,11 +49,14 @@ def read_ini(
sections = []
key = value = None
section_names = set()
if isinstance(fp, string_types):
if isinstance(fp, str):
fp = io.open(fp, encoding="utf-8")
# read the lines
section = default
current_section = {}
current_section_name = ""
key_indent = 0
for (linenum, line) in enumerate(fp.read().splitlines(), start=1):
stripped = line.strip()
@ -75,8 +76,8 @@ def read_ini(
inline_prefixes = {p: -1 for p in comments}
while comment_start == sys.maxsize and inline_prefixes:
next_prefixes = {}
for prefix, index in inline_prefixes.items():
index = stripped.find(prefix, index + 1)
for prefix, i in inline_prefixes.items():
index = stripped.find(prefix, i + 1)
if index == -1:
continue
next_prefixes[prefix] = index
@ -90,7 +91,8 @@ def read_ini(
# check for a new section
if len(stripped) > 2 and stripped[0] == "[" and stripped[-1] == "]":
section = stripped[1:-1].strip()
key = value = key_indent = None
key = value = None
key_indent = 0
# deal with DEFAULT section
if section.lower() == default.lower():

Просмотреть файл

@ -10,12 +10,12 @@ import os
import shutil
import sys
import types
from six import StringIO, string_types
from io import StringIO
from .filters import DEFAULT_FILTERS, enabled, filterlist
from .filters import exists as _exists
from .ini import read_ini
from .toml import read_toml
__all__ = ["ManifestParser", "TestManifest", "convert"]
@ -53,6 +53,7 @@ class ManifestParser(object):
rootdir=None,
finder=None,
handle_defaults=True,
use_toml=False,
):
"""Creates a ManifestParser from the given manifest files.
@ -77,6 +78,7 @@ class ManifestParser(object):
test objects. Callers are expected to manage per-manifest
defaults themselves via the manifest_defaults member
variable in this case.
:param use_toml: If True *.toml configration files will be used iff present in the same location as *.ini files (applies to included files as well). If False only *.ini files will be considered. (defaults to True)
"""
self._defaults = defaults or {}
self.tests = []
@ -87,6 +89,13 @@ class ManifestParser(object):
self._root = None
self.finder = finder
self._handle_defaults = handle_defaults
self.use_toml = use_toml
component = "manifestparser"
import mozlog
self.logger = mozlog.get_default_logger(component)
if self.logger is None:
self.logger = mozlog.unstructured.getLogger(component)
if manifests:
self.read(*manifests)
@ -124,6 +133,27 @@ class ManifestParser(object):
return relpath(path, self.root)
# methods for reading manifests
def _get_fp_filename(self, filename):
# get directory of this file if not file-like object
if isinstance(filename, str):
# If we're using mercurial as our filesystem via a finder
# during manifest reading, the getcwd() calls that happen
# with abspath calls will not be meaningful, so absolute
# paths are required.
if self.finder:
assert os.path.isabs(filename)
filename = os.path.abspath(filename)
if self.finder:
fp = codecs.getreader("utf-8")(self.finder.get(filename).open())
else:
fp = io.open(filename, encoding="utf-8")
else:
fp = filename
if hasattr(fp, "name"):
filename = os.path.abspath(fp.name)
else:
filename = None
return fp, filename
def _read(self, root, filename, defaults, parentmanifest=None):
"""
@ -140,6 +170,20 @@ class ManifestParser(object):
include_file = normalize_path(include_file)
if not os.path.isabs(include_file):
include_file = os.path.join(here, include_file)
file_base, file_ext = os.path.splitext(include_file)
if file_ext == ".ini":
toml_name = file_base + ".toml"
if self.path_exists(toml_name):
if self.use_toml:
include_file = toml_name
else:
self.logger.debug(
f"NOTE TOML include file present, but not used: {toml_name}"
)
elif file_ext != ".toml":
raise IOError(
f"manfestparser file extension not supported: {include_file}"
)
if not self.path_exists(include_file):
message = "Included file '%s' does not exist" % include_file
if self.strict:
@ -149,30 +193,41 @@ class ManifestParser(object):
return
return include_file
# get directory of this file if not file-like object
if isinstance(filename, string_types):
# If we're using mercurial as our filesystem via a finder
# during manifest reading, the getcwd() calls that happen
# with abspath calls will not be meaningful, so absolute
# paths are required.
if self.finder:
assert os.path.isabs(filename)
filename = os.path.abspath(filename)
filename_rel = self.relative_to_root(filename)
self.source_files.add(filename)
if self.finder:
fp = codecs.getreader("utf-8")(self.finder.get(filename).open())
else:
fp = io.open(filename, encoding="utf-8")
here = os.path.dirname(filename)
else:
fp = filename
filename = here = None
# assume we are reading an INI file
read_fn = read_ini
fp, filename = self._get_fp_filename(filename)
if filename is None:
filename_rel = None
here = root
file_base = file_ext = None
else:
self.source_files.add(filename)
filename_rel = self.relative_to_root(filename)
here = os.path.dirname(filename)
file_base, file_ext = os.path.splitext(filename)
if file_ext == ".ini":
toml_name = file_base + ".toml"
if self.path_exists(toml_name):
if self.use_toml:
fp, filename = self._get_fp_filename(toml_name)
read_fn = read_toml
self.logger.debug(f"Reading TOML: {filename}")
else:
self.logger.debug(
f"NOTE TOML present, but not used: {toml_name}"
)
self.logger.debug(f"Reading INI: {filename}")
else:
self.logger.debug(f"Reading INI: {filename}")
elif file_ext == ".toml":
read_fn = read_toml
self.logger.debug(f"Reading TOML: {filename}")
else:
raise IOError(f"manfestparser file extension not supported: {filename}")
defaults["here"] = here
# read the configuration
sections, defaults = read_ini(
sections, defaults = read_fn(
fp=fp,
defaults=defaults,
strict=self.strict,
@ -199,6 +254,7 @@ class ManifestParser(object):
# TODO: keep track of included file structure:
# self.manifests = {'manifest.ini': 'relative/path.ini'}
if section.startswith("include:"):
self.logger.debug(f"ManifestParser, INCLUDE: {section}")
include_file = read_file("include:")
if include_file:
include_defaults = data.copy()
@ -260,7 +316,7 @@ class ManifestParser(object):
missing = [
filename
for filename in filenames
if isinstance(filename, string_types) and not self.path_exists(filename)
if isinstance(filename, str) and not self.path_exists(filename)
]
if missing:
raise IOError("Missing files: %s" % ", ".join(missing))
@ -274,8 +330,11 @@ class ManifestParser(object):
# set the per file defaults
defaults = _defaults.copy()
here = None
if isinstance(filename, string_types):
if isinstance(filename, str):
here = os.path.dirname(os.path.abspath(filename))
elif hasattr(filename, "name"):
here = os.path.dirname(os.path.abspath(filename.name))
if here:
defaults["here"] = here # directory of master .ini file
if self.rootdir is None:
@ -359,9 +418,11 @@ class ManifestParser(object):
if tests is None:
manifests = []
# Make sure to return all the manifests, even ones without tests.
for manifest in list(self.manifest_defaults.keys()):
if isinstance(manifest, tuple):
parentmanifest, manifest = manifest
for m in list(self.manifest_defaults.keys()):
if isinstance(m, tuple):
_parentmanifest, manifest = m
else:
manifest = m
if manifest not in manifests:
manifests.append(manifest)
return manifests
@ -414,13 +475,13 @@ class ManifestParser(object):
"""
files = set([])
if isinstance(directories, string_types):
if isinstance(directories, str):
directories = [directories]
# get files in directories
for directory in directories:
for dirpath, dirnames, filenames in os.walk(directory, topdown=True):
for dirpath, _dirnames, fnames in os.walk(directory, topdown=True):
filenames = fnames
# only add files that match a pattern
if pattern:
filenames = fnmatch.filter(filenames, pattern)
@ -462,7 +523,7 @@ class ManifestParser(object):
# open file if `fp` given as string
close = False
if isinstance(fp, string_types):
if isinstance(fp, str):
fp = open(fp, "w")
close = True
@ -496,8 +557,8 @@ class ManifestParser(object):
print("%s = %s" % (key, value), file=fp)
print(file=fp)
for test in tests:
test = test.copy() # don't overwrite
for t in tests:
test = t.copy() # don't overwrite
path = test["name"]
if not os.path.isabs(path):
@ -626,7 +687,7 @@ class ManifestParser(object):
internal function to import directories
"""
if isinstance(pattern, string_types):
if isinstance(pattern, str):
patterns = [pattern]
else:
patterns = pattern
@ -763,7 +824,7 @@ class ManifestParser(object):
# determine output
opened_manifest_file = None # name of opened manifest file
absolute = not relative_to # whether to output absolute path names as names
if isinstance(write, string_types):
if isinstance(write, str):
opened_manifest_file = write
write = open(write, "w")
if write is None:

Просмотреть файл

@ -0,0 +1,92 @@
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this file,
# You can obtain one at http://mozilla.org/MPL/2.0/.
import io
import os
from tomlkit import parse
from tomlkit.exceptions import ParseError
from tomlkit.items import Array
from .ini import combine_fields
__all__ = ["read_toml"]
def read_toml(
fp,
defaults=None,
default="DEFAULT",
comments=None,
separators=None,
strict=True,
handle_defaults=True,
):
"""
read a .toml file and return a list of [(section, values)]
- fp : file pointer or path to read
- defaults : default set of variables
- default : name of the section for the default section
- comments : characters that if they start a line denote a comment
- separators : strings that denote key, value separation in order
- strict : whether to be strict about parsing
- handle_defaults : whether to incorporate defaults into each section
"""
# variables
defaults = defaults or {}
default_section = {}
comments = comments or ("#",)
separators = separators or ("=", ":")
sections = []
if isinstance(fp, str):
filename = fp
fp = io.open(fp, encoding="utf-8")
elif hasattr(fp, "name"):
filename = fp.name
else:
filename = "unknown"
contents = fp.read()
# Use tomlkit to parse the file contents
try:
manifest = parse(contents)
except ParseError as pe:
raise IOError(f"Error parsing TOML manifest file {filename}: {pe}")
# handle each section of the manifest
for section in manifest.keys():
current_section = {}
for key in manifest[section].keys():
val = manifest[section][key]
if isinstance(val, bool):
if val:
val = "true"
else:
val = "false"
elif isinstance(val, Array):
new_val = "" # stay bug-for-bug compatible witn INI processing
for v in val:
new_val += os.linesep + str(v)
val = new_val
else:
val = str(val) # coerce to str
if " = " in val:
raise Exception(
f"Should not assign in {key} condition for {section}"
)
current_section[key] = val
if section.lower() == default.lower():
default_section = current_section
# DEFAULT does NOT appear in the output
else:
sections.append((section, current_section))
# merge global defaults with the DEFAULT section
defaults = combine_fields(defaults, default_section)
if handle_defaults:
# merge combined defaults into each section
sections = [(i, combine_fields(defaults, j)) for i, j in sections]
return sections, defaults

Просмотреть файл

@ -5,11 +5,11 @@
from setuptools import setup
PACKAGE_NAME = "manifestparser"
PACKAGE_VERSION = "2.1.0"
PACKAGE_VERSION = "2.2.0"
DEPS = [
"mozlog >= 6.0",
"six >= 1.13.0",
"tomlkit >= 0.11.8",
]
setup(
name=PACKAGE_NAME,

Просмотреть файл

@ -0,0 +1,2 @@
[DEFAULT]
skip-if = ''' os = "win" '''

Просмотреть файл

@ -0,0 +1,11 @@
# See https://bugzilla.mozilla.org/show_bug.cgi?id=813674
['test_0180_fileInUse_xp_win_complete.js']
['test_0181_fileInUse_xp_win_partial.js']
['test_0182_rmrfdirFileInUse_xp_win_complete.js']
['test_0183_rmrfdirFileInUse_xp_win_partial.js']
['test_0184_fileInUse_xp_win_complete.js']
['test_0185_fileInUse_xp_win_partial.js']
['test_0186_rmrfdirFileInUse_xp_win_complete.js']
['test_0187_rmrfdirFileInUse_xp_win_partial.js']
# [test_0202_app_launch_apply_update_dirlocked.js] # Test disabled, bug 757632

Просмотреть файл

@ -0,0 +1,22 @@
[DEFAULT]
skip-if = ''' os == 'win' && debug ''' # a pesky comment
[test1]
skip-if = ''' debug '''
[test2]
skip-if = ''' os == 'linux' '''
[test3]
skip-if = ''' os == 'win' '''
[test4]
skip-if = ''' os == 'win' && debug '''
[test5]
foo = "bar"
[test6]
skip-if = ''' debug ''' # a second pesky comment

Просмотреть файл

@ -0,0 +1,5 @@
[test1]
subsuite = "baz"
[test2]
subsuite = "foo"

Просмотреть файл

@ -0,0 +1,9 @@
[DEFAULT]
support-files = "foo.js" # a comment
[test7]
[test8]
support-files = "bar.js" # another comment
[test9]
foo = "bar"

Просмотреть файл

@ -0,0 +1,11 @@
# illustrate test filters based on various categories
[windowstest]
skip-if = ''' os != 'win' '''
[fleem]
skip-if = ''' os == 'mac' '''
[linuxtest]
skip-if = ''' (os == 'mac') || (os == 'win') '''
fail-if = ''' toolkit == 'cocoa' '''

Просмотреть файл

@ -0,0 +1,11 @@
[DEFAULT]
foo = "bar"
["include:include/bar.toml"]
[fleem]
["include:include/foo.toml"]
red = "roses"
blue = "violets"
yellow = "daffodils"

Просмотреть файл

@ -0,0 +1 @@
["include:invalid.ini"]

Просмотреть файл

@ -0,0 +1,4 @@
[DEFAULT]
foo = "fleem"
[crash-handling]

Просмотреть файл

@ -0,0 +1,5 @@
[DEFAULT]
blue = "ocean"
[flowers]
yellow = "submarine"

Просмотреть файл

@ -0,0 +1,2 @@
[DEFAULT]
foo = "bar"

Просмотреть файл

@ -0,0 +1,13 @@
[DEFAULT]
subsuite = "mozbase"
['test_expressionparser.py']
['test_manifestparser.py']
['test_testmanifest.py']
['test_read_ini.py']
['test_convert_directory.py']
['test_filters.py']
['test_chunking.py']
['test_convert_symlinks.py']
disabled = 'https://bugzilla.mozilla.org/show_bug.cgi?id=920938'
['test_default_overrides.py']
['test_util.py']

Просмотреть файл

@ -0,0 +1,2 @@
[foo]
[bar]

Просмотреть файл

@ -0,0 +1,80 @@
["testAddons/testDisableEnablePlugin.js"]
["testAddons/testGetAddons.js"]
["testAddons/testSearchAddons.js"]
["testAwesomeBar/testAccessLocationBar.js"]
["testAwesomeBar/testCheckItemHighlight.js"]
["testAwesomeBar/testEscapeAutocomplete.js"]
["testAwesomeBar/testFaviconInAutocomplete.js"]
["testAwesomeBar/testGoButton.js"]
["testAwesomeBar/testLocationBarSearches.js"]
["testAwesomeBar/testPasteLocationBar.js"]
["testAwesomeBar/testSuggestHistoryBookmarks.js"]
["testAwesomeBar/testVisibleItemsMax.js"]
["testBookmarks/testAddBookmarkToMenu.js"]
["testCookies/testDisableCookies.js"]
["testCookies/testEnableCookies.js"]
["testCookies/testRemoveAllCookies.js"]
["testCookies/testRemoveCookie.js"]
["testDownloading/testCloseDownloadManager.js"]
["testDownloading/testDownloadStates.js"]
["testDownloading/testOpenDownloadManager.js"]
["testFindInPage/testFindInPage.js"]
["testFormManager/testAutoCompleteOff.js"]
["testFormManager/testBasicFormCompletion.js"]
["testFormManager/testClearFormHistory.js"]
["testFormManager/testDisableFormManager.js"]
["testGeneral/testGoogleSuggestions.js"]
["testGeneral/testStopReloadButtons.js"]
["testInstallation/testBreakpadInstalled.js"]
["testLayout/testNavigateFTP.js"]
["testPasswordManager/testPasswordNotSaved.js"]
["testPasswordManager/testPasswordSavedAndDeleted.js"]
["testPopups/testPopupsAllowed.js"]
["testPopups/testPopupsBlocked.js"]
["testPreferences/testPaneRetention.js"]
["testPreferences/testPreferredLanguage.js"]
["testPreferences/testRestoreHomepageToDefault.js"]
["testPreferences/testSetToCurrentPage.js"]
["testPreferences/testSwitchPanes.js"]
["testPrivateBrowsing/testAboutPrivateBrowsing.js"]
["testPrivateBrowsing/testCloseWindow.js"]
["testPrivateBrowsing/testDisabledElements.js"]
["testPrivateBrowsing/testDisabledPermissions.js"]
["testPrivateBrowsing/testDownloadManagerClosed.js"]
["testPrivateBrowsing/testGeolocation.js"]
["testPrivateBrowsing/testStartStopPBMode.js"]
["testPrivateBrowsing/testTabRestoration.js"]
["testPrivateBrowsing/testTabsDismissedOnStop.js"]
["testSearch/testAddMozSearchProvider.js"]
["testSearch/testFocusAndSearch.js"]
["testSearch/testGetMoreSearchEngines.js"]
["testSearch/testOpenSearchAutodiscovery.js"]
["testSearch/testRemoveSearchEngine.js"]
["testSearch/testReorderSearchEngines.js"]
["testSearch/testRestoreDefaults.js"]
["testSearch/testSearchSelection.js"]
["testSearch/testSearchSuggestions.js"]
["testSecurity/testBlueLarry.js"]
["testSecurity/testDefaultPhishingEnabled.js"]
["testSecurity/testDefaultSecurityPrefs.js"]
["testSecurity/testEncryptedPageWarning.js"]
["testSecurity/testGreenLarry.js"]
["testSecurity/testGreyLarry.js"]
["testSecurity/testIdentityPopupOpenClose.js"]
["testSecurity/testSSLDisabledErrorPage.js"]
["testSecurity/testSafeBrowsingNotificationBar.js"]
["testSecurity/testSafeBrowsingWarningPages.js"]
["testSecurity/testSecurityInfoViaMoreInformation.js"]
["testSecurity/testSecurityNotification.js"]
["testSecurity/testSubmitUnencryptedInfoWarning.js"]
["testSecurity/testUnknownIssuer.js"]
["testSecurity/testUntrustedConnectionErrorPage.js"]
["testSessionStore/testUndoTabFromContextMenu.js"]
["testTabbedBrowsing/testBackgroundTabScrolling.js"]
["testTabbedBrowsing/testCloseTab.js"]
["testTabbedBrowsing/testNewTab.js"]
["testTabbedBrowsing/testNewWindow.js"]
["testTabbedBrowsing/testOpenInBackground.js"]
["testTabbedBrowsing/testOpenInForeground.js"]
["testTechnicalTools/testAccessPageInfoDialog.js"]
["testToolbar/testBackForwardButtons.js"]

Просмотреть файл

@ -0,0 +1,26 @@
[DEFAULT]
type = "restart"
["restartTests/testExtensionInstallUninstall/test2.js"]
foo = "bar"
["restartTests/testExtensionInstallUninstall/test1.js"]
foo = "baz"
["restartTests/testExtensionInstallUninstall/test3.js"]
["restartTests/testSoftwareUpdateAutoProxy/test2.js"]
["restartTests/testSoftwareUpdateAutoProxy/test1.js"]
["restartTests/testPrimaryPassword/test1.js"]
["restartTests/testExtensionInstallGetAddons/test2.js"]
["restartTests/testExtensionInstallGetAddons/test1.js"]
["restartTests/testMultipleExtensionInstallation/test2.js"]
["restartTests/testMultipleExtensionInstallation/test1.js"]
["restartTests/testThemeInstallUninstall/test2.js"]
["restartTests/testThemeInstallUninstall/test1.js"]
["restartTests/testThemeInstallUninstall/test3.js"]
["restartTests/testDefaultBookmarks/test1.js"]
["softwareUpdate/testFallbackUpdate/test2.js"]
["softwareUpdate/testFallbackUpdate/test1.js"]
["softwareUpdate/testFallbackUpdate/test3.js"]
["softwareUpdate/testDirectUpdate/test2.js"]
["softwareUpdate/testDirectUpdate/test1.js"]

Просмотреть файл

@ -0,0 +1,2 @@
[DEFAULT]
foo = "bar"

Просмотреть файл

@ -0,0 +1,3 @@
["parent:../manifest.ini"]
['testFirst.js']

Просмотреть файл

@ -0,0 +1,8 @@
[DEFAULT]
top = "data"
["include:first/manifest.ini"]
disabled = "YES"
["include:second/manifest.ini"]
disabled = "NO"

Просмотреть файл

@ -0,0 +1,3 @@
["parent:../manifest.ini"]
['testSecond.js']

Просмотреть файл

@ -0,0 +1,5 @@
[DEFAULT]
x = "level_1"
[test_1]
[test_2]

Просмотреть файл

@ -0,0 +1,3 @@
["parent:../level_1.ini"]
[test_2]

Просмотреть файл

@ -0,0 +1,3 @@
["parent:../level_2.ini"]
[test_3]

Просмотреть файл

@ -0,0 +1,6 @@
["parent:../level_2.ini"]
[DEFAULT]
x = "level_3"
[test_3]

Просмотреть файл

@ -0,0 +1,2 @@
[foo]
path = "fleem"

Просмотреть файл

@ -0,0 +1,5 @@
[foo]
path = "../fleem"
[bar]
path = "../testsSIBLING/example"

Просмотреть файл

@ -10,4 +10,4 @@ subsuite=baz
[test4]
[test5]
[test6]
subsuite=bar,foo=="szy" || foo=="bar"
subsuite=bar,foo=="szy" || foo=="bar"

Просмотреть файл

@ -0,0 +1,13 @@
[test1]
subsuite='bar,foo=="bar"' # this has a comment
[test2]
subsuite='bar,foo=="bar"'
[test3]
subsuite='baz'
[test4]
[test5]
[test6]
subsuite='bar,foo=="szy" || foo=="bar"'

Просмотреть файл

@ -8,8 +8,6 @@ from unittest import TestCase
import mozunit
from manifestparser.filters import chunk_by_dir, chunk_by_runtime, chunk_by_slice
from six import iteritems
from six.moves import range
here = os.path.dirname(os.path.abspath(__file__))
@ -84,7 +82,7 @@ class ChunkByDir(TestCase):
{ <dir>: <num tests> }
"""
i = 0
for d, num in iteritems(dirs):
for d, num in dirs.items():
for _ in range(num):
i += 1
name = "test%i" % i
@ -99,8 +97,8 @@ class ChunkByDir(TestCase):
def num_groups(tests):
unique = set()
for p in [t["relpath"] for t in tests]:
p = p.split(os.sep)
for rp in [t["relpath"] for t in tests]:
p = rp.split(os.sep)
p = p[: min(depth, len(p) - 1)]
unique.add(os.sep.join(p))
return len(unique)
@ -174,7 +172,7 @@ class ChunkByRuntime(TestCase):
{ <dir>: <num tests> }
"""
i = 0
for d, num in iteritems(dirs):
for d, num in dirs.items():
for _ in range(num):
i += 1
name = "test%i" % i
@ -195,7 +193,7 @@ class ChunkByRuntime(TestCase):
def chunk_by_round_robin(self, tests, total, runtimes):
tests_by_manifest = []
for manifest, runtime in iteritems(runtimes):
for manifest, runtime in runtimes.items():
mtests = [t for t in tests if t["manifest_relpath"] == manifest]
tests_by_manifest.append((runtime, mtests))
tests_by_manifest.sort(key=lambda x: x[0], reverse=False)

Просмотреть файл

@ -100,6 +100,32 @@ class TestDirectoryConversion(unittest.TestCase):
finally:
shutil.rmtree(stub)
def test_convert_directory_manifests_in_place_toml(self):
"""
keep the manifests in place (TOML)
"""
stub = self.create_stub()
try:
ManifestParser.populate_directory_manifests([stub], filename="manifest.ini")
self.assertEqual(
sorted(os.listdir(stub)),
["bar", "fleem", "foo", "manifest.ini", "subdir"],
)
parser = ManifestParser(use_toml=True)
parser.read(os.path.join(stub, "manifest.ini"))
self.assertEqual(
[i["name"] for i in parser.tests], ["subfile", "bar", "fleem", "foo"]
)
parser = ManifestParser(use_toml=True)
parser.read(os.path.join(stub, "subdir", "manifest.ini"))
self.assertEqual(len(parser.tests), 1)
self.assertEqual(parser.tests[0]["name"], "subfile")
except BaseException:
raise
finally:
shutil.rmtree(stub)
def test_manifest_ignore(self):
"""test manifest `ignore` parameter for ignoring directories"""
@ -119,6 +145,25 @@ class TestDirectoryConversion(unittest.TestCase):
finally:
shutil.rmtree(stub)
def test_manifest_ignore_toml(self):
"""test manifest `ignore` parameter for ignoring directories (TOML)"""
stub = self.create_stub()
try:
ManifestParser.populate_directory_manifests(
[stub], filename="manifest.ini", ignore=("subdir",)
)
parser = ManifestParser(use_toml=True)
parser.read(os.path.join(stub, "manifest.ini"))
self.assertEqual([i["name"] for i in parser.tests], ["bar", "fleem", "foo"])
self.assertFalse(
os.path.exists(os.path.join(stub, "subdir", "manifest.ini"))
)
except BaseException:
raise
finally:
shutil.rmtree(stub)
def test_pattern(self):
"""test directory -> manifest with a file pattern"""
@ -182,6 +227,51 @@ class TestDirectoryConversion(unittest.TestCase):
shutil.rmtree(tempdir)
shutil.rmtree(newtempdir)
def test_update_toml(self):
"""
Test our ability to update tests from a manifest and a directory of
files (TOML)
"""
# boilerplate
tempdir = create_realpath_tempdir()
for i in range(10):
open(os.path.join(tempdir, str(i)), "w").write(str(i))
# otherwise empty directory with a manifest file
newtempdir = create_realpath_tempdir()
manifest_file = os.path.join(newtempdir, "manifest.ini")
manifest_contents = str(convert([tempdir], relative_to=tempdir))
with open(manifest_file, "w") as f:
f.write(manifest_contents)
# get the manifest
manifest = ManifestParser(manifests=(manifest_file,), use_toml=True)
# All of the tests are initially missing:
paths = [str(i) for i in range(10)]
self.assertEqual([i["name"] for i in manifest.missing()], paths)
# But then we copy one over:
self.assertEqual(manifest.get("name", name="1"), ["1"])
manifest.update(tempdir, name="1")
self.assertEqual(sorted(os.listdir(newtempdir)), ["1", "manifest.ini"])
# Update that one file and copy all the "tests":
open(os.path.join(tempdir, "1"), "w").write("secret door")
manifest.update(tempdir)
self.assertEqual(
sorted(os.listdir(newtempdir)),
["0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "manifest.ini"],
)
self.assertEqual(
open(os.path.join(newtempdir, "1")).read().strip(), "secret door"
)
# clean up:
shutil.rmtree(tempdir)
shutil.rmtree(newtempdir)
if __name__ == "__main__":
mozunit.main()

Просмотреть файл

@ -36,6 +36,35 @@ class TestDefaultSkipif(unittest.TestCase):
elif test["name"] == "test6":
self.assertEqual(test["skip-if"], "os == 'win' && debug\ndebug")
def test_defaults_toml(self):
default = os.path.join(here, "default-skipif.ini")
parser = ManifestParser(manifests=(default,), use_toml=True)
for test in parser.tests:
if test["name"] == "test1":
self.assertEqual(
test["skip-if"].strip(), "os == 'win' && debug \n debug"
)
elif test["name"] == "test2":
self.assertEqual(
test["skip-if"].strip(), "os == 'win' && debug \n os == 'linux'"
)
elif test["name"] == "test3":
self.assertEqual(
test["skip-if"].strip(), "os == 'win' && debug \n os == 'win'"
)
elif test["name"] == "test4":
self.assertEqual(
test["skip-if"].strip(),
"os == 'win' && debug \n os == 'win' && debug",
)
elif test["name"] == "test5":
self.assertEqual(test["skip-if"].strip(), "os == 'win' && debug")
elif test["name"] == "test6":
self.assertEqual(
test["skip-if"].strip(), "os == 'win' && debug \n debug"
)
class TestDefaultSupportFiles(unittest.TestCase):
"""Tests combining support-files field in [DEFAULT] with the value for a test"""
@ -53,6 +82,19 @@ class TestDefaultSupportFiles(unittest.TestCase):
expected = expected_supp_files[test["name"]]
self.assertEqual(test["support-files"], expected)
def test_defaults_toml(self):
default = os.path.join(here, "default-suppfiles.ini")
parser = ManifestParser(manifests=(default,), use_toml=True)
expected_supp_files = {
"test7": "foo.js",
"test8": "foo.js bar.js",
"test9": "foo.js",
}
for test in parser.tests:
expected = expected_supp_files[test["name"]]
self.assertEqual(test["support-files"], expected)
class TestOmitDefaults(unittest.TestCase):
"""Tests passing omit-defaults prevents defaults from propagating to definitions."""
@ -99,6 +141,50 @@ class TestOmitDefaults(unittest.TestCase):
self.assertIn(key, actual_defaults)
self.assertEqual(value, actual_defaults[key])
def test_defaults_toml(self):
manifests = (
os.path.join(here, "default-suppfiles.toml"),
os.path.join(here, "default-skipif.toml"),
)
parser = ManifestParser(
manifests=manifests, handle_defaults=False, use_toml=True
)
expected_supp_files = {
"test8": "bar.js",
}
expected_skip_ifs = {
"test1": "debug",
"test2": "os == 'linux'",
"test3": "os == 'win'",
"test4": "os == 'win' && debug",
"test6": "debug",
}
for test in parser.tests:
for field, expectations in (
("support-files", expected_supp_files),
("skip-if", expected_skip_ifs),
):
expected = expectations.get(test["name"])
if not expected:
self.assertNotIn(field, test)
else:
self.assertEqual(test[field].strip(), expected)
expected_defaults = {
os.path.join(here, "default-suppfiles.toml"): {
"support-files": "foo.js",
},
os.path.join(here, "default-skipif.toml"): {
"skip-if": "os == 'win' && debug",
},
}
for path, defaults in expected_defaults.items():
self.assertIn(path, parser.manifest_defaults)
actual_defaults = parser.manifest_defaults[path]
for key, value in defaults.items():
self.assertIn(key, actual_defaults)
self.assertEqual(value, actual_defaults[key].strip())
class TestSubsuiteDefaults(unittest.TestCase):
"""Test that subsuites are handled correctly when managing defaults
@ -116,6 +202,20 @@ class TestSubsuiteDefaults(unittest.TestCase):
value = combine_fields(defaults, test)
self.assertEqual(expected_subsuites[value["name"]], value["subsuite"])
def test_subsuite_defaults_toml(self):
manifest = os.path.join(here, "default-subsuite.toml")
parser = ManifestParser(
manifests=(manifest,), handle_defaults=False, use_toml=True
)
expected_subsuites = {
"test1": "baz",
"test2": "foo",
}
defaults = parser.manifest_defaults[manifest]
for test in parser.tests:
value = combine_fields(defaults, test)
self.assertEqual(expected_subsuites[value["name"]], value["subsuite"])
if __name__ == "__main__":
mozunit.main()

Просмотреть файл

@ -122,7 +122,8 @@ def test_filters_run_in_order():
def create_tests():
def inner(*paths, **defaults):
tests = []
for path in paths:
for p in paths:
path = p
if isinstance(path, tuple):
path, kwargs = path
else:

Просмотреть файл

@ -8,10 +8,10 @@ import os
import shutil
import tempfile
import unittest
from io import StringIO
import mozunit
from manifestparser import ManifestParser
from six import StringIO
here = os.path.dirname(os.path.abspath(__file__))
@ -66,6 +66,49 @@ class TestManifestParser(unittest.TestCase):
["restartTests/testExtensionInstallUninstall/test2.js"],
)
def test_sanity_toml(self):
"""Ensure basic parser is sane (TOML)"""
parser = ManifestParser(use_toml=True)
mozmill_example = os.path.join(here, "mozmill-example.toml")
parser.read(mozmill_example)
tests = parser.tests
self.assertEqual(
len(tests), len(open(mozmill_example).read().strip().splitlines())
)
# Ensure that capitalization and order aren't an issue:
lines = ['["%s"]' % test["name"] for test in tests]
self.assertEqual(lines, open(mozmill_example).read().strip().splitlines())
# Show how you select subsets of tests:
mozmill_restart_example = os.path.join(here, "mozmill-restart-example.toml")
parser.read(mozmill_restart_example)
restart_tests = parser.get(type="restart")
self.assertTrue(len(restart_tests) < len(parser.tests))
self.assertEqual(
len(restart_tests), len(parser.get(manifest=mozmill_restart_example))
)
self.assertFalse(
[
test
for test in restart_tests
if test["manifest"]
!= os.path.join(here, "mozmill-restart-example.toml")
]
)
self.assertEqual(
parser.get("name", tags=["foo"]),
[
"restartTests/testExtensionInstallUninstall/test2.js",
"restartTests/testExtensionInstallUninstall/test1.js",
],
)
self.assertEqual(
parser.get("name", foo="bar"),
["restartTests/testExtensionInstallUninstall/test2.js"],
)
def test_include(self):
"""Illustrate how include works"""
@ -139,6 +182,86 @@ foo = bar
[fleem]
[include/flowers]
blue = ocean
red = roses
yellow = submarine""" # noqa
self.assertEqual(buffer.getvalue().strip(), expected_output)
def test_include_toml(self):
"""Illustrate how include works (TOML)"""
include_example = os.path.join(here, "include-example.toml")
parser = ManifestParser(manifests=(include_example,), use_toml=True)
# All of the tests should be included, in order:
self.assertEqual(parser.get("name"), ["crash-handling", "fleem", "flowers"])
self.assertEqual(
[
(test["name"], os.path.basename(test["manifest"]))
for test in parser.tests
],
[
("crash-handling", "bar.toml"),
("fleem", "include-example.toml"),
("flowers", "foo.toml"),
],
)
# The including manifest is always reported as a part of the generated test object.
self.assertTrue(
all(
[
t["ancestor_manifest"] == "include-example.toml"
for t in parser.tests
if t["name"] != "fleem"
]
)
)
# The manifests should be there too:
self.assertEqual(len(parser.manifests()), 3)
# We already have the root directory:
self.assertEqual(here, parser.rootdir)
# DEFAULT values should persist across includes, unless they're
# overwritten. In this example, include-example.toml sets foo=bar, but
# it's overridden to fleem in bar.toml
self.assertEqual(parser.get("name", foo="bar"), ["fleem", "flowers"])
self.assertEqual(parser.get("name", foo="fleem"), ["crash-handling"])
# Passing parameters in the include section allows defining variables in
# the submodule scope:
self.assertEqual(parser.get("name", tags=["red"]), ["flowers"])
# However, this should be overridable from the DEFAULT section in the
# included file and that overridable via the key directly connected to
# the test:
self.assertEqual(parser.get(name="flowers")[0]["blue"], "ocean")
self.assertEqual(parser.get(name="flowers")[0]["yellow"], "submarine")
# You can query multiple times if you need to:
flowers = parser.get(foo="bar")
self.assertEqual(len(flowers), 2)
# Using the inverse flag should invert the set of tests returned:
self.assertEqual(
parser.get("name", inverse=True, tags=["red"]), ["crash-handling", "fleem"]
)
# All of the included tests actually exist:
self.assertEqual([i["name"] for i in parser.missing()], [])
# Write the output to a manifest:
buffer = StringIO()
parser.write(fp=buffer, global_kwargs={"foo": "bar"})
expected_output = """[DEFAULT]
foo = bar
[fleem]
[include/flowers]
blue = ocean
red = roses
@ -181,6 +304,41 @@ yellow = submarine""" # noqa
self.assertIn(include_example, manifests)
self.assertIn(noinclude_example, manifests)
def test_include_manifest_defaults_toml(self):
"""
Test that manifest_defaults and manifests() are correctly populated
when includes are used. (TOML)
"""
include_example = os.path.join(here, "include-example.toml")
noinclude_example = os.path.join(here, "just-defaults.toml")
bar_path = os.path.join(here, "include", "bar.toml")
foo_path = os.path.join(here, "include", "foo.toml")
parser = ManifestParser(
manifests=(include_example, noinclude_example), rootdir=here, use_toml=True
)
# Standalone manifests must be appear as-is.
self.assertTrue(include_example in parser.manifest_defaults)
self.assertTrue(noinclude_example in parser.manifest_defaults)
# Included manifests must only appear together with the parent manifest
# that included the manifest.
self.assertFalse(bar_path in parser.manifest_defaults)
self.assertFalse(foo_path in parser.manifest_defaults)
ancestor_ini = os.path.relpath(include_example, parser.rootdir)
self.assertTrue((ancestor_ini, bar_path) in parser.manifest_defaults)
self.assertTrue((ancestor_ini, foo_path) in parser.manifest_defaults)
# manifests() must only return file paths (strings).
manifests = parser.manifests()
self.assertEqual(len(manifests), 4)
self.assertIn(foo_path, manifests)
self.assertIn(bar_path, manifests)
self.assertIn(include_example, manifests)
self.assertIn(noinclude_example, manifests)
def test_include_handle_defaults_False(self):
"""
Test that manifest_defaults and manifests() are correct even when
@ -214,6 +372,39 @@ yellow = submarine""" # noqa
},
)
def test_include_handle_defaults_False_toml(self):
"""
Test that manifest_defaults and manifests() are correct even when
handle_defaults is set to False. (TOML)
"""
manifest = os.path.join(here, "include-example.toml")
foo_path = os.path.join(here, "include", "foo.toml")
parser = ManifestParser(
manifests=(manifest,), handle_defaults=False, rootdir=here, use_toml=True
)
ancestor_ini = os.path.relpath(manifest, parser.rootdir)
self.assertIn(manifest, parser.manifest_defaults)
self.assertNotIn(foo_path, parser.manifest_defaults)
self.assertIn((ancestor_ini, foo_path), parser.manifest_defaults)
self.assertEqual(
parser.manifest_defaults[manifest],
{
"foo": "bar",
"here": here,
},
)
self.assertEqual(
parser.manifest_defaults[(ancestor_ini, foo_path)],
{
"here": os.path.join(here, "include"),
"red": "roses",
"blue": "ocean",
"yellow": "daffodils",
},
)
def test_include_repeated(self):
"""
Test that repeatedly included manifests are independent of each other.
@ -291,16 +482,105 @@ yellow = submarine
"%s%s" % (included_output, include_output),
)
def test_include_repeated_toml(self):
"""
Test that repeatedly included manifests are independent of each other. (TOML)
"""
include_example = os.path.join(here, "include-example.toml")
included_foo = os.path.join(here, "include", "foo.toml")
# In the expected output, blue and yellow have the values from foo.toml
# (ocean, submarine) instead of the ones from include-example.toml
# (violets, daffodils), because the defaults in the included file take
# precedence over the values from the parent.
include_output = """[include/crash-handling]
foo = fleem
[fleem]
foo = bar
[include/flowers]
blue = ocean
foo = bar
red = roses
yellow = submarine
"""
included_output = """[include/flowers]
blue = ocean
yellow = submarine
"""
parser = ManifestParser(
manifests=(include_example, included_foo), rootdir=here, use_toml=True
)
self.assertEqual(
parser.get("name"), ["crash-handling", "fleem", "flowers", "flowers"]
)
self.assertEqual(
[
(test["name"], os.path.basename(test["manifest"]))
for test in parser.tests
],
[
("crash-handling", "bar.toml"),
("fleem", "include-example.toml"),
("flowers", "foo.toml"),
("flowers", "foo.toml"),
],
)
self.check_included_repeat(
parser,
parser.tests[3],
parser.tests[2],
"%s%s" % (include_output, included_output),
True,
)
# Same tests, but with the load order of the manifests swapped.
parser = ManifestParser(
manifests=(included_foo, include_example), rootdir=here, use_toml=True
)
self.assertEqual(
parser.get("name"), ["flowers", "crash-handling", "fleem", "flowers"]
)
self.assertEqual(
[
(test["name"], os.path.basename(test["manifest"]))
for test in parser.tests
],
[
("flowers", "foo.toml"),
("crash-handling", "bar.toml"),
("fleem", "include-example.toml"),
("flowers", "foo.toml"),
],
)
self.check_included_repeat(
parser,
parser.tests[0],
parser.tests[3],
"%s%s" % (included_output, include_output),
True,
)
def check_included_repeat(
self, parser, isolated_test, included_test, expected_output
self, parser, isolated_test, included_test, expected_output, use_toml=False
):
include_example = os.path.join(here, "include-example.ini")
included_foo = os.path.join(here, "include", "foo.ini")
if use_toml:
include_example_filename = "include-example.toml"
foo_filename = "foo.toml"
else:
include_example_filename = "include-example.ini"
foo_filename = "foo.ini"
include_example = os.path.join(here, include_example_filename)
included_foo = os.path.join(here, "include", foo_filename)
ancestor_ini = os.path.relpath(include_example, parser.rootdir)
manifest_default_key = (ancestor_ini, included_foo)
self.assertFalse("ancestor_manifest" in isolated_test)
self.assertEqual(included_test["ancestor_manifest"], "include-example.ini")
self.assertEqual(included_test["ancestor_manifest"], include_example_filename)
self.assertTrue(include_example in parser.manifest_defaults)
self.assertTrue(included_foo in parser.manifest_defaults)
@ -327,6 +607,13 @@ yellow = submarine
manifest = os.path.join(here, "include-invalid.ini")
ManifestParser(manifests=(manifest,), strict=False)
def test_invalid_path_toml(self):
"""
Test invalid path should not throw when not strict (TOML)
"""
manifest = os.path.join(here, "include-invalid.ini")
ManifestParser(manifests=(manifest,), strict=False, use_toml=True)
def test_copy(self):
"""Test our ability to copy a set of manifests"""
@ -347,6 +634,26 @@ yellow = submarine
self.assertEqual(to_manifest.get("name"), from_manifest.get("name"))
shutil.rmtree(tempdir)
def test_copy_toml(self):
"""Test our ability to copy a set of manifests (TOML)"""
tempdir = tempfile.mkdtemp()
include_example = os.path.join(here, "include-example.toml")
manifest = ManifestParser(manifests=(include_example,), use_toml=True)
manifest.copy(tempdir)
self.assertEqual(
sorted(os.listdir(tempdir)), ["fleem", "include", "include-example.toml"]
)
self.assertEqual(
sorted(os.listdir(os.path.join(tempdir, "include"))),
["bar.toml", "crash-handling", "flowers", "foo.toml"],
)
from_manifest = ManifestParser(manifests=(include_example,), use_toml=True)
to_manifest = os.path.join(tempdir, "include-example.toml")
to_manifest = ManifestParser(manifests=(to_manifest,), use_toml=True)
self.assertEqual(to_manifest.get("name"), from_manifest.get("name"))
shutil.rmtree(tempdir)
def test_path_override(self):
"""You can override the path in the section too.
This shows that you can use a relative path"""
@ -354,11 +661,18 @@ yellow = submarine
manifest = ManifestParser(manifests=(path_example,))
self.assertEqual(manifest.tests[0]["path"], os.path.join(here, "fleem"))
def test_path_override_toml(self):
"""You can override the path in the section too.
This shows that you can use a relative path"""
path_example = os.path.join(here, "path-example.toml")
manifest = ManifestParser(manifests=(path_example,), use_toml=True)
self.assertEqual(manifest.tests[0]["path"], os.path.join(here, "fleem"))
def test_relative_path(self):
"""
Relative test paths are correctly calculated.
"""
relative_path = os.path.join(here, "relative-path.ini")
relative_path = os.path.join(here, "relative-path.toml")
manifest = ManifestParser(manifests=(relative_path,))
self.assertEqual(
manifest.tests[0]["path"], os.path.join(os.path.dirname(here), "fleem")
@ -368,6 +682,20 @@ yellow = submarine
manifest.tests[1]["relpath"], os.path.join("..", "testsSIBLING", "example")
)
def test_relative_path_toml(self):
"""
Relative test paths are correctly calculated. (TOML)
"""
relative_path = os.path.join(here, "relative-path.toml")
manifest = ManifestParser(manifests=(relative_path,), use_toml=True)
self.assertEqual(
manifest.tests[0]["path"], os.path.join(os.path.dirname(here), "fleem")
)
self.assertEqual(manifest.tests[0]["relpath"], os.path.join("..", "fleem"))
self.assertEqual(
manifest.tests[1]["relpath"], os.path.join("..", "testsSIBLING", "example")
)
def test_path_from_fd(self):
"""
Test paths are left untouched when manifest is a file-like object.
@ -389,6 +717,18 @@ yellow = submarine
names = [i["name"] for i in manifest.tests]
self.assertFalse("test_0202_app_launch_apply_update_dirlocked.js" in names)
def test_comments_toml(self):
"""
ensure comments work, see
https://bugzilla.mozilla.org/show_bug.cgi?id=813674
(TOML)
"""
comment_example = os.path.join(here, "comment-example.toml")
manifest = ManifestParser(manifests=(comment_example,), use_toml=True)
self.assertEqual(len(manifest.tests), 8)
names = [i["name"] for i in manifest.tests]
self.assertFalse("test_0202_app_launch_apply_update_dirlocked.js" in names)
def test_verifyDirectory(self):
directory = os.path.join(here, "verifyDirectory")
@ -413,6 +753,30 @@ yellow = submarine
missing = manifest.verifyDirectory(directory, extensions=(".js",))
self.assertEqual(missing, (set([missing_test]), set()))
def test_verifyDirectory_toml(self):
directory = os.path.join(here, "verifyDirectory")
# correct manifest
manifest_path = os.path.join(directory, "verifyDirectory.toml")
manifest = ManifestParser(manifests=(manifest_path,), use_toml=True)
missing = manifest.verifyDirectory(directory, extensions=(".js",))
self.assertEqual(missing, (set(), set()))
# manifest is missing test_1.js
test_1 = os.path.join(directory, "test_1.js")
manifest_path = os.path.join(directory, "verifyDirectory_incomplete.toml")
manifest = ManifestParser(manifests=(manifest_path,), use_toml=True)
missing = manifest.verifyDirectory(directory, extensions=(".js",))
self.assertEqual(missing, (set(), set([test_1])))
# filesystem is missing test_notappearinginthisfilm.js
missing_test = os.path.join(directory, "test_notappearinginthisfilm.js")
manifest_path = os.path.join(directory, "verifyDirectory_toocomplete.ini")
manifest = ManifestParser(manifests=(manifest_path,), use_toml=True)
missing = manifest.verifyDirectory(directory, extensions=(".js",))
self.assertEqual(missing, (set([missing_test]), set()))
def test_just_defaults(self):
"""Ensure a manifest with just a DEFAULT section exposes that data."""
@ -423,6 +787,16 @@ yellow = submarine
self.assertTrue(manifest in parser.manifest_defaults)
self.assertEqual(parser.manifest_defaults[manifest]["foo"], "bar")
def test_just_defaults_toml(self):
"""Ensure a manifest with just a DEFAULT section exposes that data. (TOML)"""
parser = ManifestParser(use_toml=True)
manifest = os.path.join(here, "just-defaults.toml")
parser.read(manifest)
self.assertEqual(len(parser.tests), 0)
self.assertTrue(manifest in parser.manifest_defaults)
self.assertEqual(parser.manifest_defaults[manifest]["foo"], "bar")
def test_manifest_list(self):
"""
Ensure a manifest with just a DEFAULT section still returns
@ -435,6 +809,18 @@ yellow = submarine
self.assertEqual(len(parser.tests), 0)
self.assertTrue(len(parser.manifests()) == 1)
def test_manifest_list_toml(self):
"""
Ensure a manifest with just a DEFAULT section still returns
itself from the manifests() method. (TOML)
"""
parser = ManifestParser(use_toml=True)
manifest = os.path.join(here, "no-tests.toml")
parser.read(manifest)
self.assertEqual(len(parser.tests), 0)
self.assertTrue(len(parser.manifests()) == 1)
def test_manifest_with_invalid_condition(self):
"""
Ensure a skip-if or similar condition with an assignment in it
@ -448,6 +834,19 @@ yellow = submarine
):
parser.read(manifest)
def test_manifest_with_invalid_condition_toml(self):
"""
Ensure a skip-if or similar condition with an assignment in it
causes errors. (TOML)
"""
parser = ManifestParser(use_toml=True)
manifest = os.path.join(here, "broken-skip-if.toml")
with self.assertRaisesRegex(
Exception, "Should not assign in skip-if condition for DEFAULT"
):
parser.read(manifest)
if __name__ == "__main__":
mozunit.main()

Просмотреть файл

@ -10,12 +10,12 @@ is the default:
http://docs.python.org/2/library/configparser.html
"""
from io import StringIO
from textwrap import dedent
import mozunit
import pytest
from manifestparser import read_ini
from six import StringIO
@pytest.fixture(scope="module")

Просмотреть файл

@ -44,6 +44,37 @@ class TestTestManifest(unittest.TestCase):
last = manifest.active_tests(exists=False, toolkit="cocoa")[-1]
self.assertEqual(last["expected"], "fail")
def test_testmanifest_toml(self):
# Test filtering based on platform:
filter_example = os.path.join(here, "filter-example.toml")
manifest = TestManifest(
manifests=(filter_example,), strict=False, use_toml=True
)
self.assertEqual(
[
i["name"]
for i in manifest.active_tests(os="win", disabled=False, exists=False)
],
["windowstest", "fleem"],
)
self.assertEqual(
[
i["name"]
for i in manifest.active_tests(os="linux", disabled=False, exists=False)
],
["fleem", "linuxtest"],
)
# Look for existing tests. There is only one:
self.assertEqual([i["name"] for i in manifest.active_tests()], ["fleem"])
# You should be able to expect failures:
last = manifest.active_tests(exists=False, toolkit="gtk")[-1]
self.assertEqual(last["name"], "linuxtest")
self.assertEqual(last["expected"], "pass")
last = manifest.active_tests(exists=False, toolkit="cocoa")[-1]
self.assertEqual(last["expected"], "fail")
def test_missing_paths(self):
"""
Test paths that don't exist raise an exception in strict mode.
@ -58,17 +89,43 @@ class TestTestManifest(unittest.TestCase):
shutil.rmtree(tempdir)
def test_missing_paths_toml(self):
"""
Test paths that don't exist raise an exception in strict mode. (TOML)
"""
tempdir = tempfile.mkdtemp()
missing_path = os.path.join(here, "missing-path.toml")
manifest = TestManifest(manifests=(missing_path,), strict=True, use_toml=True)
self.assertRaises(IOError, manifest.active_tests)
self.assertRaises(IOError, manifest.copy, tempdir)
self.assertRaises(IOError, manifest.update, tempdir)
shutil.rmtree(tempdir)
def test_comments(self):
"""
ensure comments work, see
https://bugzilla.mozilla.org/show_bug.cgi?id=813674
"""
comment_example = os.path.join(here, "comment-example.ini")
comment_example = os.path.join(here, "comment-example.toml")
manifest = TestManifest(manifests=(comment_example,))
self.assertEqual(len(manifest.tests), 8)
names = [i["name"] for i in manifest.tests]
self.assertFalse("test_0202_app_launch_apply_update_dirlocked.js" in names)
def test_comments_toml(self):
"""
ensure comments work, see
https://bugzilla.mozilla.org/show_bug.cgi?id=813674
(TOML)
"""
comment_example = os.path.join(here, "comment-example.toml")
manifest = TestManifest(manifests=(comment_example,), use_toml=True)
self.assertEqual(len(manifest.tests), 8)
names = [i["name"] for i in manifest.tests]
self.assertFalse("test_0202_app_launch_apply_update_dirlocked.js" in names)
def test_manifest_subsuites(self):
"""
test subsuites and conditional subsuites
@ -103,6 +160,40 @@ class TestTestManifest(unittest.TestCase):
with self.assertRaises(ParseError):
manifest.active_tests(exists=False, filters=[subsuite("foo")], **info)
def test_manifest_subsuites_toml(self):
"""
test subsuites and conditional subsuites (TOML)
"""
relative_path = os.path.join(here, "subsuite.toml")
manifest = TestManifest(manifests=(relative_path,), use_toml=True)
info = {"foo": "bar"}
# 6 tests total
tests = manifest.active_tests(exists=False, **info)
self.assertEqual(len(tests), 6)
# only 3 tests for subsuite bar when foo==bar
tests = manifest.active_tests(exists=False, filters=[subsuite("bar")], **info)
self.assertEqual(len(tests), 3)
# only 1 test for subsuite baz, regardless of conditions
other = {"something": "else"}
tests = manifest.active_tests(exists=False, filters=[subsuite("baz")], **info)
self.assertEqual(len(tests), 1)
tests = manifest.active_tests(exists=False, filters=[subsuite("baz")], **other)
self.assertEqual(len(tests), 1)
# 4 tests match when the condition doesn't match (all tests except
# the unconditional subsuite)
info = {"foo": "blah"}
tests = manifest.active_tests(exists=False, filters=[subsuite()], **info)
self.assertEqual(len(tests), 5)
# test for illegal subsuite value
manifest.tests[0]["subsuite"] = 'subsuite=bar,foo=="bar",type="nothing"'
with self.assertRaises(ParseError):
manifest.active_tests(exists=False, filters=[subsuite("foo")], **info)
def test_none_and_empty_manifest(self):
"""
Test TestManifest for None and empty manifest, see
@ -116,6 +207,20 @@ class TestTestManifest(unittest.TestCase):
self.assertEqual(len(empty_manifest.test_paths()), 0)
self.assertEqual(len(empty_manifest.active_tests()), 0)
def test_none_and_empty_manifest_toml(self):
"""
Test TestManifest for None and empty manifest, see
https://bugzilla.mozilla.org/show_bug.cgi?id=1087682
(TOML)
"""
none_manifest = TestManifest(manifests=None, strict=False, use_toml=True)
self.assertEqual(len(none_manifest.test_paths()), 0)
self.assertEqual(len(none_manifest.active_tests()), 0)
empty_manifest = TestManifest(manifests=[], strict=False)
self.assertEqual(len(empty_manifest.test_paths()), 0)
self.assertEqual(len(empty_manifest.active_tests()), 0)
if __name__ == "__main__":
mozunit.main()

Просмотреть файл

@ -4,13 +4,13 @@
Test how our utility functions are working.
"""
from io import StringIO
from textwrap import dedent
import mozunit
import pytest
from manifestparser import read_ini
from manifestparser.util import evaluate_list_from_string
from six import StringIO
@pytest.fixture(scope="module")

Просмотреть файл

@ -0,0 +1 @@
['test_sub.js']

Просмотреть файл

@ -0,0 +1,4 @@
['test_1.js']
['test_2.js']
['test_3.js']
["include:subdir/manifest.toml"]

Просмотреть файл

@ -0,0 +1,3 @@
['test_2.js']
['test_3.js']
["include:subdir/manifest.toml"]

Просмотреть файл

@ -0,0 +1,5 @@
['test_1.js']
['test_2.js']
['test_3.js']
['test_notappearinginthisfilm.js']
["include:subdir/manifest.toml"]

Просмотреть файл

@ -13,7 +13,6 @@ import posixpath
import sys
import mozinfo
from manifestparser import TestManifest
class SingleTestMixin(object):
@ -69,6 +68,9 @@ class SingleTestMixin(object):
is_fission = "fission.autostart=true" in self.config.get("extra_prefs", [])
tests_by_path = {}
all_disabled = []
# HACK: import here so we don't need import for rest of class
from manifestparser import TestManifest
for (path, suite) in manifests:
if os.path.exists(path):
man = TestManifest([path], strict=False)

Просмотреть файл

@ -18,6 +18,7 @@ pycrypto==2.6.1
pyflakes==0.6.1
pylint==0.27.0
simplejson==2.1.1
tomlkit==0.11.8
unittest2==0.5.1
virtualenv==1.5.1
wsgiref==0.1.2

Просмотреть файл

@ -15,7 +15,15 @@ import mozharness
version = mozharness.version_string
dependencies = ["virtualenv", "mock", "coverage", "nose", "pylint", "pyflakes"]
dependencies = [
"virtualenv",
"mock",
"coverage",
"nose",
"pylint",
"pyflakes",
"tomlkit",
]
try:
import json
except ImportError:

14
third_party/python/poetry.lock сгенерированный поставляемый
Просмотреть файл

@ -1227,6 +1227,18 @@ files = [
{file = "toml-0.10.2.tar.gz", hash = "sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f"},
]
[[package]]
name = "tomlkit"
version = "0.11.8"
description = "Style preserving TOML library"
category = "main"
optional = false
python-versions = ">=3.7"
files = [
{file = "tomlkit-0.11.8-py3-none-any.whl", hash = "sha256:8c726c4c202bdb148667835f68d68780b9a003a9ec34167b6c673b38eff2a171"},
{file = "tomlkit-0.11.8.tar.gz", hash = "sha256:9330fc7faa1db67b541b28e62018c17d20be733177d290a13b24c62d1614e0c3"},
]
[[package]]
name = "tqdm"
version = "4.62.3"
@ -1403,4 +1415,4 @@ testing = ["func-timeout", "jaraco.itertools", "pytest (>=4.6)", "pytest-black (
[metadata]
lock-version = "2.0"
python-versions = "^3.7"
content-hash = "1287ae37af1bcab84a39b20585a69da3a01bd47c37718f2af47e4e9c886171b0"
content-hash = "c409903aba82ee97e2d690aeaf190c01fe6c26adbc472572dc2fd9fa2cb7cc8f"

1
third_party/python/requirements.in поставляемый
Просмотреть файл

@ -45,6 +45,7 @@ taskcluster==44.2.2
taskcluster-taskgraph==5.7.0
taskcluster-urls==13.0.1
toml==0.10.2
tomlkit==0.11.8
tqdm==4.62.3
urllib3==1.26
voluptuous==0.12.1

3
third_party/python/requirements.txt поставляемый
Просмотреть файл

@ -374,6 +374,9 @@ text-unidecode==1.3 ; python_version >= "3.7" and python_version < "4.0" \
toml==0.10.2 ; python_version >= "3.7" and python_version < "4.0" \
--hash=sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b \
--hash=sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f
tomlkit==0.11.8 ; python_version >= "3.7" and python_version < "4.0" \
--hash=sha256:8c726c4c202bdb148667835f68d68780b9a003a9ec34167b6c673b38eff2a171 \
--hash=sha256:9330fc7faa1db67b541b28e62018c17d20be733177d290a13b24c62d1614e0c3
tqdm==4.62.3 ; python_version >= "3.7" and python_version < "4.0" \
--hash=sha256:8dd278a422499cd6b727e6ae4061c40b48fce8b76d1ccbf5d34fca9b7f925b0c \
--hash=sha256:d359de7217506c9851b7869f3708d8ee53ed70a1b8edbba4dbcb47442592920d

20
third_party/python/tomlkit/tomlkit-0.11.8.dist-info/LICENSE поставляемый Normal file
Просмотреть файл

@ -0,0 +1,20 @@
Copyright (c) 2018 Sébastien Eustace
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.

70
third_party/python/tomlkit/tomlkit-0.11.8.dist-info/METADATA поставляемый Normal file
Просмотреть файл

@ -0,0 +1,70 @@
Metadata-Version: 2.1
Name: tomlkit
Version: 0.11.8
Summary: Style preserving TOML library
Home-page: https://github.com/sdispater/tomlkit
License: MIT
Author: Sébastien Eustace
Author-email: sebastien@eustace.io
Requires-Python: >=3.7
Classifier: License :: OSI Approved :: MIT License
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.7
Classifier: Programming Language :: Python :: 3.8
Classifier: Programming Language :: Python :: 3.9
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Project-URL: Repository, https://github.com/sdispater/tomlkit
Description-Content-Type: text/markdown
[github_release]: https://img.shields.io/github/release/sdispater/tomlkit.svg?logo=github&logoColor=white
[pypi_version]: https://img.shields.io/pypi/v/tomlkit.svg?logo=python&logoColor=white
[python_versions]: https://img.shields.io/pypi/pyversions/tomlkit.svg?logo=python&logoColor=white
[github_license]: https://img.shields.io/github/license/sdispater/tomlkit.svg?logo=github&logoColor=white
[github_action]: https://github.com/sdispater/tomlkit/actions/workflows/tests.yml/badge.svg
[![GitHub Release][github_release]](https://github.com/sdispater/tomlkit/releases/)
[![PyPI Version][pypi_version]](https://pypi.org/project/tomlkit/)
[![Python Versions][python_versions]](https://pypi.org/project/tomlkit/)
[![License][github_license]](https://github.com/sdispater/tomlkit/blob/master/LICENSE)
<br>
[![Tests][github_action]](https://github.com/sdispater/tomlkit/actions/workflows/tests.yml)
# TOML Kit - Style-preserving TOML library for Python
TOML Kit is a **1.0.0-compliant** [TOML](https://toml.io/) library.
It includes a parser that preserves all comments, indentations, whitespace and internal element ordering,
and makes them accessible and editable via an intuitive API.
You can also create new TOML documents from scratch using the provided helpers.
Part of the implementation has been adapted, improved and fixed from [Molten](https://github.com/LeopoldArkham/Molten).
## Usage
See the [documentation](https://github.com/sdispater/tomlkit/blob/master/docs/quickstart.rst) for more information.
## Installation
If you are using [Poetry](https://poetry.eustace.io),
add `tomlkit` to your `pyproject.toml` file by using:
```bash
poetry add tomlkit
```
If not, you can use `pip`:
```bash
pip install tomlkit
```
## Running tests
Please clone the repo with submodules with the following command
`git clone --recurse-submodules https://github.com/sdispater/tomlkit.git`.
We need the submodule - `toml-test` for running the tests.
You can run the tests with `poetry run pytest -q tests`

17
third_party/python/tomlkit/tomlkit-0.11.8.dist-info/RECORD поставляемый Normal file
Просмотреть файл

@ -0,0 +1,17 @@
tomlkit/__init__.py,sha256=_u6Tsd33N9hf-GhAhpqj6t0soNnTO5LP2uMdWwX9xG0,1148
tomlkit/_compat.py,sha256=gp7P7qNh0yY1dg0wyjiCDbVwFTdUo7p0QwjV4T3Funs,513
tomlkit/_utils.py,sha256=fX1n7boCi7_bBVBemaywk0eyxqhTYnZWPhCZHJgBZv8,4021
tomlkit/api.py,sha256=DnIdqzAyi5FpCcRBGzX7We0wqdBjCYihe0TaGR0YA4E,7065
tomlkit/container.py,sha256=nHeshsFne77La-LqOxMTKk9xyuF0-HrwfMlSwpgP7hs,28192
tomlkit/exceptions.py,sha256=TdeHy9e9yiXI8oSR-eCxqtQOWBlyFgn7tTjvpCWAqTw,5487
tomlkit/items.py,sha256=qQ0jyzSx0aplK6qdpIU-7jEGC5bHo4TMu6bPxnkY-Ts,51695
tomlkit/parser.py,sha256=cBuC9T3ZrvKVtwzK0PFEo5roJqru7A0cXMFhKlcb_LU,37839
tomlkit/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
tomlkit/source.py,sha256=nwRrAUkJ2CyDDKFs4jo9o0RpNMNaNNoLDFiwo4ZNZTM,4823
tomlkit/toml_char.py,sha256=w3sQZ0dolZ1qjZ2Rxj_svvlpRNNGB_fjfBcYD0gFnDs,1291
tomlkit/toml_document.py,sha256=OCTkWXd3P58EZT4SD8_ddc1YpkMaqtlS5_stHTBmMOI,110
tomlkit/toml_file.py,sha256=4gVZvvs_Q1_soWaVxBo80rRzny849boXt2LzdMXQ04I,1599
tomlkit-0.11.8.dist-info/LICENSE,sha256=8vm0YLpxnaZiat0mTTeC8nWk_3qrZ3vtoIszCRHiOts,1062
tomlkit-0.11.8.dist-info/METADATA,sha256=zj4O4eLckCCdR1aTCYdhIH3wBf9SQUKx6tbrx4uAGOU,2704
tomlkit-0.11.8.dist-info/WHEEL,sha256=7Z8_27uaHI_UZAc4Uox4PpBhQ9Y5_modZXWMxtUi4NU,88
tomlkit-0.11.8.dist-info/RECORD,,

4
third_party/python/tomlkit/tomlkit-0.11.8.dist-info/WHEEL поставляемый Normal file
Просмотреть файл

@ -0,0 +1,4 @@
Wheel-Version: 1.0
Generator: poetry-core 1.5.2
Root-Is-Purelib: true
Tag: py3-none-any

55
third_party/python/tomlkit/tomlkit/__init__.py поставляемый Normal file
Просмотреть файл

@ -0,0 +1,55 @@
from tomlkit.api import TOMLDocument
from tomlkit.api import aot
from tomlkit.api import array
from tomlkit.api import boolean
from tomlkit.api import comment
from tomlkit.api import date
from tomlkit.api import datetime
from tomlkit.api import document
from tomlkit.api import dump
from tomlkit.api import dumps
from tomlkit.api import float_
from tomlkit.api import inline_table
from tomlkit.api import integer
from tomlkit.api import item
from tomlkit.api import key
from tomlkit.api import key_value
from tomlkit.api import load
from tomlkit.api import loads
from tomlkit.api import nl
from tomlkit.api import parse
from tomlkit.api import string
from tomlkit.api import table
from tomlkit.api import time
from tomlkit.api import value
from tomlkit.api import ws
__version__ = "0.11.8"
__all__ = [
"aot",
"array",
"boolean",
"comment",
"date",
"datetime",
"document",
"dump",
"dumps",
"float_",
"inline_table",
"integer",
"item",
"key",
"key_value",
"load",
"loads",
"nl",
"parse",
"string",
"table",
"time",
"TOMLDocument",
"value",
"ws",
]

22
third_party/python/tomlkit/tomlkit/_compat.py поставляемый Normal file
Просмотреть файл

@ -0,0 +1,22 @@
from __future__ import annotations
import contextlib
import sys
from typing import Any
PY38 = sys.version_info >= (3, 8)
def decode(string: Any, encodings: list[str] | None = None):
if not isinstance(string, bytes):
return string
encodings = encodings or ["utf-8", "latin1", "ascii"]
for encoding in encodings:
with contextlib.suppress(UnicodeEncodeError, UnicodeDecodeError):
return string.decode(encoding)
return string.decode(encodings[0], errors="ignore")

156
third_party/python/tomlkit/tomlkit/_utils.py поставляемый Normal file
Просмотреть файл

@ -0,0 +1,156 @@
from __future__ import annotations
import re
from collections.abc import Mapping
from datetime import date
from datetime import datetime
from datetime import time
from datetime import timedelta
from datetime import timezone
from typing import Collection
from tomlkit._compat import decode
RFC_3339_LOOSE = re.compile(
"^"
r"(([0-9]+)-(\d{2})-(\d{2}))?" # Date
"("
"([Tt ])?" # Separator
r"(\d{2}):(\d{2}):(\d{2})(\.([0-9]+))?" # Time
r"(([Zz])|([\+|\-]([01][0-9]|2[0-3]):([0-5][0-9])))?" # Timezone
")?"
"$"
)
RFC_3339_DATETIME = re.compile(
"^"
"([0-9]+)-(0[1-9]|1[012])-(0[1-9]|[12][0-9]|3[01])" # Date
"[Tt ]" # Separator
r"([01][0-9]|2[0-3]):([0-5][0-9]):([0-5][0-9]|60)(\.([0-9]+))?" # Time
r"(([Zz])|([\+|\-]([01][0-9]|2[0-3]):([0-5][0-9])))?" # Timezone
"$"
)
RFC_3339_DATE = re.compile("^([0-9]+)-(0[1-9]|1[012])-(0[1-9]|[12][0-9]|3[01])$")
RFC_3339_TIME = re.compile(
r"^([01][0-9]|2[0-3]):([0-5][0-9]):([0-5][0-9]|60)(\.([0-9]+))?$"
)
_utc = timezone(timedelta(), "UTC")
def parse_rfc3339(string: str) -> datetime | date | time:
m = RFC_3339_DATETIME.match(string)
if m:
year = int(m.group(1))
month = int(m.group(2))
day = int(m.group(3))
hour = int(m.group(4))
minute = int(m.group(5))
second = int(m.group(6))
microsecond = 0
if m.group(7):
microsecond = int((f"{m.group(8):<06s}")[:6])
if m.group(9):
# Timezone
tz = m.group(9)
if tz.upper() == "Z":
tzinfo = _utc
else:
sign = m.group(11)[0]
hour_offset, minute_offset = int(m.group(12)), int(m.group(13))
offset = timedelta(seconds=hour_offset * 3600 + minute_offset * 60)
if sign == "-":
offset = -offset
tzinfo = timezone(offset, f"{sign}{m.group(12)}:{m.group(13)}")
return datetime(
year, month, day, hour, minute, second, microsecond, tzinfo=tzinfo
)
else:
return datetime(year, month, day, hour, minute, second, microsecond)
m = RFC_3339_DATE.match(string)
if m:
year = int(m.group(1))
month = int(m.group(2))
day = int(m.group(3))
return date(year, month, day)
m = RFC_3339_TIME.match(string)
if m:
hour = int(m.group(1))
minute = int(m.group(2))
second = int(m.group(3))
microsecond = 0
if m.group(4):
microsecond = int((f"{m.group(5):<06s}")[:6])
return time(hour, minute, second, microsecond)
raise ValueError("Invalid RFC 339 string")
# https://toml.io/en/v1.0.0#string
CONTROL_CHARS = frozenset(chr(c) for c in range(0x20)) | {chr(0x7F)}
_escaped = {
"b": "\b",
"t": "\t",
"n": "\n",
"f": "\f",
"r": "\r",
'"': '"',
"\\": "\\",
}
_compact_escapes = {
**{v: f"\\{k}" for k, v in _escaped.items()},
'"""': '""\\"',
}
_basic_escapes = CONTROL_CHARS | {'"', "\\"}
def _unicode_escape(seq: str) -> str:
return "".join(f"\\u{ord(c):04x}" for c in seq)
def escape_string(s: str, escape_sequences: Collection[str] = _basic_escapes) -> str:
s = decode(s)
res = []
start = 0
def flush(inc=1):
if start != i:
res.append(s[start:i])
return i + inc
i = 0
while i < len(s):
for seq in escape_sequences:
seq_len = len(seq)
if s[i:].startswith(seq):
start = flush(seq_len)
res.append(_compact_escapes.get(seq) or _unicode_escape(seq))
i += seq_len - 1 # fast-forward escape sequence
i += 1
flush()
return "".join(res)
def merge_dicts(d1: dict, d2: dict) -> dict:
for k, v in d2.items():
if k in d1 and isinstance(d1[k], dict) and isinstance(v, Mapping):
merge_dicts(d1[k], v)
else:
d1[k] = d2[k]

286
third_party/python/tomlkit/tomlkit/api.py поставляемый Normal file
Просмотреть файл

@ -0,0 +1,286 @@
from __future__ import annotations
import datetime as _datetime
from collections.abc import Mapping
from typing import IO
from typing import Iterable
from tomlkit._utils import parse_rfc3339
from tomlkit.container import Container
from tomlkit.exceptions import UnexpectedCharError
from tomlkit.items import AoT
from tomlkit.items import Array
from tomlkit.items import Bool
from tomlkit.items import Comment
from tomlkit.items import Date
from tomlkit.items import DateTime
from tomlkit.items import DottedKey
from tomlkit.items import Float
from tomlkit.items import InlineTable
from tomlkit.items import Integer
from tomlkit.items import Item as _Item
from tomlkit.items import Key
from tomlkit.items import SingleKey
from tomlkit.items import String
from tomlkit.items import StringType as _StringType
from tomlkit.items import Table
from tomlkit.items import Time
from tomlkit.items import Trivia
from tomlkit.items import Whitespace
from tomlkit.items import item
from tomlkit.parser import Parser
from tomlkit.toml_document import TOMLDocument
def loads(string: str | bytes) -> TOMLDocument:
"""
Parses a string into a TOMLDocument.
Alias for parse().
"""
return parse(string)
def dumps(data: Mapping, sort_keys: bool = False) -> str:
"""
Dumps a TOMLDocument into a string.
"""
if not isinstance(data, Container) and isinstance(data, Mapping):
data = item(dict(data), _sort_keys=sort_keys)
try:
# data should be a `Container` (and therefore implement `as_string`)
# for all type safe invocations of this function
return data.as_string() # type: ignore[attr-defined]
except AttributeError as ex:
msg = f"Expecting Mapping or TOML Container, {type(data)} given"
raise TypeError(msg) from ex
def load(fp: IO[str] | IO[bytes]) -> TOMLDocument:
"""
Load toml document from a file-like object.
"""
return parse(fp.read())
def dump(data: Mapping, fp: IO[str], *, sort_keys: bool = False) -> None:
"""
Dump a TOMLDocument into a writable file stream.
:param data: a dict-like object to dump
:param sort_keys: if true, sort the keys in alphabetic order
"""
fp.write(dumps(data, sort_keys=sort_keys))
def parse(string: str | bytes) -> TOMLDocument:
"""
Parses a string or bytes into a TOMLDocument.
"""
return Parser(string).parse()
def document() -> TOMLDocument:
"""
Returns a new TOMLDocument instance.
"""
return TOMLDocument()
# Items
def integer(raw: str | int) -> Integer:
"""Create an integer item from a number or string."""
return item(int(raw))
def float_(raw: str | float) -> Float:
"""Create an float item from a number or string."""
return item(float(raw))
def boolean(raw: str) -> Bool:
"""Turn `true` or `false` into a boolean item."""
return item(raw == "true")
def string(
raw: str,
*,
literal: bool = False,
multiline: bool = False,
escape: bool = True,
) -> String:
"""Create a string item.
By default, this function will create *single line basic* strings, but
boolean flags (e.g. ``literal=True`` and/or ``multiline=True``)
can be used for personalization.
For more information, please check the spec: `<https://toml.io/en/v1.0.0#string>`__.
Common escaping rules will be applied for basic strings.
This can be controlled by explicitly setting ``escape=False``.
Please note that, if you disable escaping, you will have to make sure that
the given strings don't contain any forbidden character or sequence.
"""
type_ = _StringType.select(literal, multiline)
return String.from_raw(raw, type_, escape)
def date(raw: str) -> Date:
"""Create a TOML date."""
value = parse_rfc3339(raw)
if not isinstance(value, _datetime.date):
raise ValueError("date() only accepts date strings.")
return item(value)
def time(raw: str) -> Time:
"""Create a TOML time."""
value = parse_rfc3339(raw)
if not isinstance(value, _datetime.time):
raise ValueError("time() only accepts time strings.")
return item(value)
def datetime(raw: str) -> DateTime:
"""Create a TOML datetime."""
value = parse_rfc3339(raw)
if not isinstance(value, _datetime.datetime):
raise ValueError("datetime() only accepts datetime strings.")
return item(value)
def array(raw: str = None) -> Array:
"""Create an array item for its string representation.
:Example:
>>> array("[1, 2, 3]") # Create from a string
[1, 2, 3]
>>> a = array()
>>> a.extend([1, 2, 3]) # Create from a list
>>> a
[1, 2, 3]
"""
if raw is None:
raw = "[]"
return value(raw)
def table(is_super_table: bool | None = None) -> Table:
"""Create an empty table.
:param is_super_table: if true, the table is a super table
:Example:
>>> doc = document()
>>> foo = table(True)
>>> bar = table()
>>> bar.update({'x': 1})
>>> foo.append('bar', bar)
>>> doc.append('foo', foo)
>>> print(doc.as_string())
[foo.bar]
x = 1
"""
return Table(Container(), Trivia(), False, is_super_table)
def inline_table() -> InlineTable:
"""Create an inline table.
:Example:
>>> table = inline_table()
>>> table.update({'x': 1, 'y': 2})
>>> print(table.as_string())
{x = 1, y = 2}
"""
return InlineTable(Container(), Trivia(), new=True)
def aot() -> AoT:
"""Create an array of table.
:Example:
>>> doc = document()
>>> aot = aot()
>>> aot.append(item({'x': 1}))
>>> doc.append('foo', aot)
>>> print(doc.as_string())
[[foo]]
x = 1
"""
return AoT([])
def key(k: str | Iterable[str]) -> Key:
"""Create a key from a string. When a list of string is given,
it will create a dotted key.
:Example:
>>> doc = document()
>>> doc.append(key('foo'), 1)
>>> doc.append(key(['bar', 'baz']), 2)
>>> print(doc.as_string())
foo = 1
bar.baz = 2
"""
if isinstance(k, str):
return SingleKey(k)
return DottedKey([key(_k) for _k in k])
def value(raw: str) -> _Item:
"""Parse a simple value from a string.
:Example:
>>> value("1")
1
>>> value("true")
True
>>> value("[1, 2, 3]")
[1, 2, 3]
"""
parser = Parser(raw)
v = parser._parse_value()
if not parser.end():
raise parser.parse_error(UnexpectedCharError, char=parser._current)
return v
def key_value(src: str) -> tuple[Key, _Item]:
"""Parse a key-value pair from a string.
:Example:
>>> key_value("foo = 1")
(Key('foo'), 1)
"""
return Parser(src)._parse_key_value()
def ws(src: str) -> Whitespace:
"""Create a whitespace from a string."""
return Whitespace(src, fixed=True)
def nl() -> Whitespace:
"""Create a newline item."""
return ws("\n")
def comment(string: str) -> Comment:
"""Create a comment item."""
return Comment(Trivia(comment_ws=" ", comment="# " + string))

866
third_party/python/tomlkit/tomlkit/container.py поставляемый Normal file
Просмотреть файл

@ -0,0 +1,866 @@
from __future__ import annotations
import copy
from typing import Any
from typing import Iterator
from tomlkit._compat import decode
from tomlkit._utils import merge_dicts
from tomlkit.exceptions import KeyAlreadyPresent
from tomlkit.exceptions import NonExistentKey
from tomlkit.exceptions import TOMLKitError
from tomlkit.items import AoT
from tomlkit.items import Comment
from tomlkit.items import Item
from tomlkit.items import Key
from tomlkit.items import Null
from tomlkit.items import SingleKey
from tomlkit.items import Table
from tomlkit.items import Trivia
from tomlkit.items import Whitespace
from tomlkit.items import _CustomDict
from tomlkit.items import item as _item
_NOT_SET = object()
class Container(_CustomDict):
"""
A container for items within a TOMLDocument.
This class implements the `dict` interface with copy/deepcopy protocol.
"""
def __init__(self, parsed: bool = False) -> None:
self._map: dict[SingleKey, int | tuple[int, ...]] = {}
self._body: list[tuple[Key | None, Item]] = []
self._parsed = parsed
self._table_keys = []
@property
def body(self) -> list[tuple[Key | None, Item]]:
return self._body
def unwrap(self) -> dict[str, Any]:
unwrapped = {}
for k, v in self.items():
if k is None:
continue
if isinstance(k, Key):
k = k.key
if hasattr(v, "unwrap"):
v = v.unwrap()
if k in unwrapped:
merge_dicts(unwrapped[k], v)
else:
unwrapped[k] = v
return unwrapped
@property
def value(self) -> dict[str, Any]:
d = {}
for k, v in self._body:
if k is None:
continue
k = k.key
v = v.value
if isinstance(v, Container):
v = v.value
if k in d:
merge_dicts(d[k], v)
else:
d[k] = v
return d
def parsing(self, parsing: bool) -> None:
self._parsed = parsing
for _, v in self._body:
if isinstance(v, Table):
v.value.parsing(parsing)
elif isinstance(v, AoT):
for t in v.body:
t.value.parsing(parsing)
def add(self, key: Key | Item | str, item: Item | None = None) -> Container:
"""
Adds an item to the current Container.
:Example:
>>> # add a key-value pair
>>> doc.add('key', 'value')
>>> # add a comment or whitespace or newline
>>> doc.add(comment('# comment'))
"""
if item is None:
if not isinstance(key, (Comment, Whitespace)):
raise ValueError(
"Non comment/whitespace items must have an associated key"
)
key, item = None, key
return self.append(key, item)
def _handle_dotted_key(self, key: Key, value: Item) -> None:
if isinstance(value, (Table, AoT)):
raise TOMLKitError("Can't add a table to a dotted key")
name, *mid, last = key
name._dotted = True
table = current = Table(Container(True), Trivia(), False, is_super_table=True)
for _name in mid:
_name._dotted = True
new_table = Table(Container(True), Trivia(), False, is_super_table=True)
current.append(_name, new_table)
current = new_table
last.sep = key.sep
current.append(last, value)
self.append(name, table)
return
def _get_last_index_before_table(self) -> int:
last_index = -1
for i, (k, v) in enumerate(self._body):
if isinstance(v, Null):
continue # Null elements are inserted after deletion
if isinstance(v, Whitespace) and not v.is_fixed():
continue
if isinstance(v, (Table, AoT)) and not k.is_dotted():
break
last_index = i
return last_index + 1
def append(self, key: Key | str | None, item: Item) -> Container:
"""Similar to :meth:`add` but both key and value must be given."""
if not isinstance(key, Key) and key is not None:
key = SingleKey(key)
if not isinstance(item, Item):
item = _item(item)
if key is not None and key.is_multi():
self._handle_dotted_key(key, item)
return self
if isinstance(item, (AoT, Table)) and item.name is None:
item.name = key.key
prev = self._previous_item()
prev_ws = isinstance(prev, Whitespace) or ends_with_whitespace(prev)
if isinstance(item, Table):
if not self._parsed:
item.invalidate_display_name()
if (
self._body
and not (self._parsed or item.trivia.indent or prev_ws)
and not key.is_dotted()
):
item.trivia.indent = "\n"
if isinstance(item, AoT) and self._body and not self._parsed:
item.invalidate_display_name()
if item and not ("\n" in item[0].trivia.indent or prev_ws):
item[0].trivia.indent = "\n" + item[0].trivia.indent
if key is not None and key in self:
current_idx = self._map[key]
if isinstance(current_idx, tuple):
current_body_element = self._body[current_idx[-1]]
else:
current_body_element = self._body[current_idx]
current = current_body_element[1]
if isinstance(item, Table):
if not isinstance(current, (Table, AoT)):
raise KeyAlreadyPresent(key)
if item.is_aot_element():
# New AoT element found later on
# Adding it to the current AoT
if not isinstance(current, AoT):
current = AoT([current, item], parsed=self._parsed)
self._replace(key, key, current)
else:
current.append(item)
return self
elif current.is_aot():
if not item.is_aot_element():
# Tried to define a table after an AoT with the same name.
raise KeyAlreadyPresent(key)
current.append(item)
return self
elif current.is_super_table():
if item.is_super_table():
# We need to merge both super tables
if (
self._table_keys[-1] != current_body_element[0]
or key.is_dotted()
or current_body_element[0].is_dotted()
):
if key.is_dotted() and not self._parsed:
idx = self._get_last_index_before_table()
else:
idx = len(self._body)
if idx < len(self._body):
self._insert_at(idx, key, item)
else:
self._raw_append(key, item)
# Building a temporary proxy to check for errors
OutOfOrderTableProxy(self, self._map[key])
return self
# Create a new element to replace the old one
current = copy.deepcopy(current)
for k, v in item.value.body:
current.append(k, v)
self._body[
current_idx[-1]
if isinstance(current_idx, tuple)
else current_idx
] = (current_body_element[0], current)
return self
elif current_body_element[0].is_dotted():
raise TOMLKitError("Redefinition of an existing table")
elif not item.is_super_table():
raise KeyAlreadyPresent(key)
elif isinstance(item, AoT):
if not isinstance(current, AoT):
# Tried to define an AoT after a table with the same name.
raise KeyAlreadyPresent(key)
for table in item.body:
current.append(table)
return self
else:
raise KeyAlreadyPresent(key)
is_table = isinstance(item, (Table, AoT))
if (
key is not None
and self._body
and not self._parsed
and (not is_table or key.is_dotted())
):
# If there is already at least one table in the current container
# and the given item is not a table, we need to find the last
# item that is not a table and insert after it
# If no such item exists, insert at the top of the table
last_index = self._get_last_index_before_table()
if last_index < len(self._body):
return self._insert_at(last_index, key, item)
else:
previous_item = self._body[-1][1]
if not (
isinstance(previous_item, Whitespace)
or ends_with_whitespace(previous_item)
or "\n" in previous_item.trivia.trail
):
previous_item.trivia.trail += "\n"
self._raw_append(key, item)
return self
def _raw_append(self, key: Key, item: Item) -> None:
if key in self._map:
current_idx = self._map[key]
if not isinstance(current_idx, tuple):
current_idx = (current_idx,)
current = self._body[current_idx[-1]][1]
if key is not None and not isinstance(current, Table):
raise KeyAlreadyPresent(key)
self._map[key] = current_idx + (len(self._body),)
else:
self._map[key] = len(self._body)
self._body.append((key, item))
if item.is_table():
self._table_keys.append(key)
if key is not None:
dict.__setitem__(self, key.key, item.value)
return self
def _remove_at(self, idx: int) -> None:
key = self._body[idx][0]
index = self._map.get(key)
if index is None:
raise NonExistentKey(key)
self._body[idx] = (None, Null())
if isinstance(index, tuple):
index = list(index)
index.remove(idx)
if len(index) == 1:
index = index.pop()
else:
index = tuple(index)
self._map[key] = index
else:
dict.__delitem__(self, key.key)
self._map.pop(key)
def remove(self, key: Key | str) -> Container:
"""Remove a key from the container."""
if not isinstance(key, Key):
key = SingleKey(key)
idx = self._map.pop(key, None)
if idx is None:
raise NonExistentKey(key)
if isinstance(idx, tuple):
for i in idx:
self._body[i] = (None, Null())
else:
self._body[idx] = (None, Null())
dict.__delitem__(self, key.key)
return self
def _insert_after(
self, key: Key | str, other_key: Key | str, item: Any
) -> Container:
if key is None:
raise ValueError("Key cannot be null in insert_after()")
if key not in self:
raise NonExistentKey(key)
if not isinstance(key, Key):
key = SingleKey(key)
if not isinstance(other_key, Key):
other_key = SingleKey(other_key)
item = _item(item)
idx = self._map[key]
# Insert after the max index if there are many.
if isinstance(idx, tuple):
idx = max(idx)
current_item = self._body[idx][1]
if "\n" not in current_item.trivia.trail:
current_item.trivia.trail += "\n"
# Increment indices after the current index
for k, v in self._map.items():
if isinstance(v, tuple):
new_indices = []
for v_ in v:
if v_ > idx:
v_ = v_ + 1
new_indices.append(v_)
self._map[k] = tuple(new_indices)
elif v > idx:
self._map[k] = v + 1
self._map[other_key] = idx + 1
self._body.insert(idx + 1, (other_key, item))
if key is not None:
dict.__setitem__(self, other_key.key, item.value)
return self
def _insert_at(self, idx: int, key: Key | str, item: Any) -> Container:
if idx > len(self._body) - 1:
raise ValueError(f"Unable to insert at position {idx}")
if not isinstance(key, Key):
key = SingleKey(key)
item = _item(item)
if idx > 0:
previous_item = self._body[idx - 1][1]
if not (
isinstance(previous_item, Whitespace)
or ends_with_whitespace(previous_item)
or isinstance(item, (AoT, Table))
or "\n" in previous_item.trivia.trail
):
previous_item.trivia.trail += "\n"
# Increment indices after the current index
for k, v in self._map.items():
if isinstance(v, tuple):
new_indices = []
for v_ in v:
if v_ >= idx:
v_ = v_ + 1
new_indices.append(v_)
self._map[k] = tuple(new_indices)
elif v >= idx:
self._map[k] = v + 1
if key in self._map:
current_idx = self._map[key]
if not isinstance(current_idx, tuple):
current_idx = (current_idx,)
self._map[key] = current_idx + (idx,)
else:
self._map[key] = idx
self._body.insert(idx, (key, item))
dict.__setitem__(self, key.key, item.value)
return self
def item(self, key: Key | str) -> Item:
"""Get an item for the given key."""
if not isinstance(key, Key):
key = SingleKey(key)
idx = self._map.get(key)
if idx is None:
raise NonExistentKey(key)
if isinstance(idx, tuple):
# The item we are getting is an out of order table
# so we need a proxy to retrieve the proper objects
# from the parent container
return OutOfOrderTableProxy(self, idx)
return self._body[idx][1]
def last_item(self) -> Item | None:
"""Get the last item."""
if self._body:
return self._body[-1][1]
def as_string(self) -> str:
"""Render as TOML string."""
s = ""
for k, v in self._body:
if k is not None:
if isinstance(v, Table):
s += self._render_table(k, v)
elif isinstance(v, AoT):
s += self._render_aot(k, v)
else:
s += self._render_simple_item(k, v)
else:
s += self._render_simple_item(k, v)
return s
def _render_table(self, key: Key, table: Table, prefix: str | None = None) -> str:
cur = ""
if table.display_name is not None:
_key = table.display_name
else:
_key = key.as_string()
if prefix is not None:
_key = prefix + "." + _key
if not table.is_super_table() or (
any(
not isinstance(v, (Table, AoT, Whitespace, Null))
for _, v in table.value.body
)
and not key.is_dotted()
):
open_, close = "[", "]"
if table.is_aot_element():
open_, close = "[[", "]]"
newline_in_table_trivia = (
"\n" if "\n" not in table.trivia.trail and len(table.value) > 0 else ""
)
cur += (
f"{table.trivia.indent}"
f"{open_}"
f"{decode(_key)}"
f"{close}"
f"{table.trivia.comment_ws}"
f"{decode(table.trivia.comment)}"
f"{table.trivia.trail}"
f"{newline_in_table_trivia}"
)
elif table.trivia.indent == "\n":
cur += table.trivia.indent
for k, v in table.value.body:
if isinstance(v, Table):
if v.is_super_table():
if k.is_dotted() and not key.is_dotted():
# Dotted key inside table
cur += self._render_table(k, v)
else:
cur += self._render_table(k, v, prefix=_key)
else:
cur += self._render_table(k, v, prefix=_key)
elif isinstance(v, AoT):
cur += self._render_aot(k, v, prefix=_key)
else:
cur += self._render_simple_item(
k, v, prefix=_key if key.is_dotted() else None
)
return cur
def _render_aot(self, key, aot, prefix=None):
_key = key.as_string()
if prefix is not None:
_key = prefix + "." + _key
cur = ""
_key = decode(_key)
for table in aot.body:
cur += self._render_aot_table(table, prefix=_key)
return cur
def _render_aot_table(self, table: Table, prefix: str | None = None) -> str:
cur = ""
_key = prefix or ""
open_, close = "[[", "]]"
cur += (
f"{table.trivia.indent}"
f"{open_}"
f"{decode(_key)}"
f"{close}"
f"{table.trivia.comment_ws}"
f"{decode(table.trivia.comment)}"
f"{table.trivia.trail}"
)
for k, v in table.value.body:
if isinstance(v, Table):
if v.is_super_table():
if k.is_dotted():
# Dotted key inside table
cur += self._render_table(k, v)
else:
cur += self._render_table(k, v, prefix=_key)
else:
cur += self._render_table(k, v, prefix=_key)
elif isinstance(v, AoT):
cur += self._render_aot(k, v, prefix=_key)
else:
cur += self._render_simple_item(k, v)
return cur
def _render_simple_item(self, key, item, prefix=None):
if key is None:
return item.as_string()
_key = key.as_string()
if prefix is not None:
_key = prefix + "." + _key
return (
f"{item.trivia.indent}"
f"{decode(_key)}"
f"{key.sep}"
f"{decode(item.as_string())}"
f"{item.trivia.comment_ws}"
f"{decode(item.trivia.comment)}"
f"{item.trivia.trail}"
)
def __len__(self) -> int:
return dict.__len__(self)
def __iter__(self) -> Iterator[str]:
return iter(dict.keys(self))
# Dictionary methods
def __getitem__(self, key: Key | str) -> Item | Container:
if not isinstance(key, Key):
key = SingleKey(key)
idx = self._map.get(key)
if idx is None:
raise NonExistentKey(key)
if isinstance(idx, tuple):
# The item we are getting is an out of order table
# so we need a proxy to retrieve the proper objects
# from the parent container
return OutOfOrderTableProxy(self, idx)
item = self._body[idx][1]
if item.is_boolean():
return item.value
return item
def __setitem__(self, key: Key | str, value: Any) -> None:
if key is not None and key in self:
old_key = next(filter(lambda k: k == key, self._map))
self._replace(old_key, key, value)
else:
self.append(key, value)
def __delitem__(self, key: Key | str) -> None:
self.remove(key)
def setdefault(self, key: Key | str, default: Any) -> Any:
super().setdefault(key, default=default)
return self[key]
def _replace(self, key: Key | str, new_key: Key | str, value: Item) -> None:
if not isinstance(key, Key):
key = SingleKey(key)
idx = self._map.get(key)
if idx is None:
raise NonExistentKey(key)
self._replace_at(idx, new_key, value)
def _replace_at(
self, idx: int | tuple[int], new_key: Key | str, value: Item
) -> None:
value = _item(value)
if isinstance(idx, tuple):
for i in idx[1:]:
self._body[i] = (None, Null())
idx = idx[0]
k, v = self._body[idx]
if not isinstance(new_key, Key):
if (
isinstance(value, (AoT, Table)) != isinstance(v, (AoT, Table))
or new_key != k.key
):
new_key = SingleKey(new_key)
else: # Inherit the sep of the old key
new_key = k
del self._map[k]
self._map[new_key] = idx
if new_key != k:
dict.__delitem__(self, k)
if isinstance(value, (AoT, Table)) != isinstance(v, (AoT, Table)):
# new tables should appear after all non-table values
self.remove(k)
for i in range(idx, len(self._body)):
if isinstance(self._body[i][1], (AoT, Table)):
self._insert_at(i, new_key, value)
idx = i
break
else:
idx = -1
self.append(new_key, value)
else:
# Copying trivia
if not isinstance(value, (Whitespace, AoT)):
value.trivia.indent = v.trivia.indent
value.trivia.comment_ws = value.trivia.comment_ws or v.trivia.comment_ws
value.trivia.comment = value.trivia.comment or v.trivia.comment
value.trivia.trail = v.trivia.trail
self._body[idx] = (new_key, value)
if hasattr(value, "invalidate_display_name"):
value.invalidate_display_name() # type: ignore[attr-defined]
if isinstance(value, Table):
# Insert a cosmetic new line for tables if:
# - it does not have it yet OR is not followed by one
# - it is not the last item
last, _ = self._previous_item_with_index()
idx = last if idx < 0 else idx
has_ws = ends_with_whitespace(value)
next_ws = idx < last and isinstance(self._body[idx + 1][1], Whitespace)
if idx < last and not (next_ws or has_ws):
value.append(None, Whitespace("\n"))
dict.__setitem__(self, new_key.key, value.value)
def __str__(self) -> str:
return str(self.value)
def __repr__(self) -> str:
return repr(self.value)
def __eq__(self, other: dict) -> bool:
if not isinstance(other, dict):
return NotImplemented
return self.value == other
def _getstate(self, protocol):
return (self._parsed,)
def __reduce__(self):
return self.__reduce_ex__(2)
def __reduce_ex__(self, protocol):
return (
self.__class__,
self._getstate(protocol),
(self._map, self._body, self._parsed, self._table_keys),
)
def __setstate__(self, state):
self._map = state[0]
self._body = state[1]
self._parsed = state[2]
self._table_keys = state[3]
for key, item in self._body:
if key is not None:
dict.__setitem__(self, key.key, item.value)
def copy(self) -> Container:
return copy.copy(self)
def __copy__(self) -> Container:
c = self.__class__(self._parsed)
for k, v in dict.items(self):
dict.__setitem__(c, k, v)
c._body += self.body
c._map.update(self._map)
return c
def _previous_item_with_index(
self, idx: int | None = None, ignore=(Null,)
) -> tuple[int, Item] | None:
"""Find the immediate previous item before index ``idx``"""
if idx is None or idx > len(self._body):
idx = len(self._body)
for i in range(idx - 1, -1, -1):
v = self._body[i][-1]
if not isinstance(v, ignore):
return i, v
return None
def _previous_item(self, idx: int | None = None, ignore=(Null,)) -> Item | None:
"""Find the immediate previous item before index ``idx``.
If ``idx`` is not given, the last item is returned.
"""
prev = self._previous_item_with_index(idx, ignore)
return prev[-1] if prev else None
class OutOfOrderTableProxy(_CustomDict):
def __init__(self, container: Container, indices: tuple[int]) -> None:
self._container = container
self._internal_container = Container(True)
self._tables = []
self._tables_map = {}
for i in indices:
_, item = self._container._body[i]
if isinstance(item, Table):
self._tables.append(item)
table_idx = len(self._tables) - 1
for k, v in item.value.body:
self._internal_container.append(k, v)
self._tables_map[k] = table_idx
if k is not None:
dict.__setitem__(self, k.key, v)
def unwrap(self) -> str:
return self._internal_container.unwrap()
@property
def value(self):
return self._internal_container.value
def __getitem__(self, key: Key | str) -> Any:
if key not in self._internal_container:
raise NonExistentKey(key)
return self._internal_container[key]
def __setitem__(self, key: Key | str, item: Any) -> None:
if key in self._tables_map:
table = self._tables[self._tables_map[key]]
table[key] = item
elif self._tables:
table = self._tables[0]
table[key] = item
else:
self._container[key] = item
self._internal_container[key] = item
if key is not None:
dict.__setitem__(self, key, item)
def _remove_table(self, table: Table) -> None:
"""Remove table from the parent container"""
self._tables.remove(table)
for idx, item in enumerate(self._container._body):
if item[1] is table:
self._container._remove_at(idx)
break
def __delitem__(self, key: Key | str) -> None:
if key in self._tables_map:
table = self._tables[self._tables_map[key]]
del table[key]
if not table and len(self._tables) > 1:
self._remove_table(table)
del self._tables_map[key]
else:
raise NonExistentKey(key)
del self._internal_container[key]
if key is not None:
dict.__delitem__(self, key)
def __iter__(self) -> Iterator[str]:
return iter(dict.keys(self))
def __len__(self) -> int:
return dict.__len__(self)
def setdefault(self, key: Key | str, default: Any) -> Any:
super().setdefault(key, default=default)
return self[key]
def ends_with_whitespace(it: Any) -> bool:
"""Returns ``True`` if the given item ``it`` is a ``Table`` or ``AoT`` object
ending with a ``Whitespace``.
"""
return (
isinstance(it, Table) and isinstance(it.value._previous_item(), Whitespace)
) or (isinstance(it, AoT) and len(it) > 0 and isinstance(it[-1], Whitespace))

227
third_party/python/tomlkit/tomlkit/exceptions.py поставляемый Normal file
Просмотреть файл

@ -0,0 +1,227 @@
from __future__ import annotations
from typing import Collection
class TOMLKitError(Exception):
pass
class ParseError(ValueError, TOMLKitError):
"""
This error occurs when the parser encounters a syntax error
in the TOML being parsed. The error references the line and
location within the line where the error was encountered.
"""
def __init__(self, line: int, col: int, message: str | None = None) -> None:
self._line = line
self._col = col
if message is None:
message = "TOML parse error"
super().__init__(f"{message} at line {self._line} col {self._col}")
@property
def line(self):
return self._line
@property
def col(self):
return self._col
class MixedArrayTypesError(ParseError):
"""
An array was found that had two or more element types.
"""
def __init__(self, line: int, col: int) -> None:
message = "Mixed types found in array"
super().__init__(line, col, message=message)
class InvalidNumberError(ParseError):
"""
A numeric field was improperly specified.
"""
def __init__(self, line: int, col: int) -> None:
message = "Invalid number"
super().__init__(line, col, message=message)
class InvalidDateTimeError(ParseError):
"""
A datetime field was improperly specified.
"""
def __init__(self, line: int, col: int) -> None:
message = "Invalid datetime"
super().__init__(line, col, message=message)
class InvalidDateError(ParseError):
"""
A date field was improperly specified.
"""
def __init__(self, line: int, col: int) -> None:
message = "Invalid date"
super().__init__(line, col, message=message)
class InvalidTimeError(ParseError):
"""
A date field was improperly specified.
"""
def __init__(self, line: int, col: int) -> None:
message = "Invalid time"
super().__init__(line, col, message=message)
class InvalidNumberOrDateError(ParseError):
"""
A numeric or date field was improperly specified.
"""
def __init__(self, line: int, col: int) -> None:
message = "Invalid number or date format"
super().__init__(line, col, message=message)
class InvalidUnicodeValueError(ParseError):
"""
A unicode code was improperly specified.
"""
def __init__(self, line: int, col: int) -> None:
message = "Invalid unicode value"
super().__init__(line, col, message=message)
class UnexpectedCharError(ParseError):
"""
An unexpected character was found during parsing.
"""
def __init__(self, line: int, col: int, char: str) -> None:
message = f"Unexpected character: {repr(char)}"
super().__init__(line, col, message=message)
class EmptyKeyError(ParseError):
"""
An empty key was found during parsing.
"""
def __init__(self, line: int, col: int) -> None:
message = "Empty key"
super().__init__(line, col, message=message)
class EmptyTableNameError(ParseError):
"""
An empty table name was found during parsing.
"""
def __init__(self, line: int, col: int) -> None:
message = "Empty table name"
super().__init__(line, col, message=message)
class InvalidCharInStringError(ParseError):
"""
The string being parsed contains an invalid character.
"""
def __init__(self, line: int, col: int, char: str) -> None:
message = f"Invalid character {repr(char)} in string"
super().__init__(line, col, message=message)
class UnexpectedEofError(ParseError):
"""
The TOML being parsed ended before the end of a statement.
"""
def __init__(self, line: int, col: int) -> None:
message = "Unexpected end of file"
super().__init__(line, col, message=message)
class InternalParserError(ParseError):
"""
An error that indicates a bug in the parser.
"""
def __init__(self, line: int, col: int, message: str | None = None) -> None:
msg = "Internal parser error"
if message:
msg += f" ({message})"
super().__init__(line, col, message=msg)
class NonExistentKey(KeyError, TOMLKitError):
"""
A non-existent key was used.
"""
def __init__(self, key):
message = f'Key "{key}" does not exist.'
super().__init__(message)
class KeyAlreadyPresent(TOMLKitError):
"""
An already present key was used.
"""
def __init__(self, key):
key = getattr(key, "key", key)
message = f'Key "{key}" already exists.'
super().__init__(message)
class InvalidControlChar(ParseError):
def __init__(self, line: int, col: int, char: int, type: str) -> None:
display_code = "\\u00"
if char < 16:
display_code += "0"
display_code += hex(char)[2:]
message = (
"Control characters (codes less than 0x1f and 0x7f)"
f" are not allowed in {type}, "
f"use {display_code} instead"
)
super().__init__(line, col, message=message)
class InvalidStringError(ValueError, TOMLKitError):
def __init__(self, value: str, invalid_sequences: Collection[str], delimiter: str):
repr_ = repr(value)[1:-1]
super().__init__(
f"Invalid string: {delimiter}{repr_}{delimiter}. "
f"The character sequences {invalid_sequences} are invalid."
)

1928
third_party/python/tomlkit/tomlkit/items.py поставляемый Normal file

Разница между файлами не показана из-за своего большого размера Загрузить разницу

1141
third_party/python/tomlkit/tomlkit/parser.py поставляемый Normal file

Разница между файлами не показана из-за своего большого размера Загрузить разницу

0
third_party/python/tomlkit/tomlkit/py.typed поставляемый Normal file
Просмотреть файл

180
third_party/python/tomlkit/tomlkit/source.py поставляемый Normal file
Просмотреть файл

@ -0,0 +1,180 @@
from __future__ import annotations
from copy import copy
from typing import Any
from tomlkit.exceptions import ParseError
from tomlkit.exceptions import UnexpectedCharError
from tomlkit.toml_char import TOMLChar
class _State:
def __init__(
self,
source: Source,
save_marker: bool | None = False,
restore: bool | None = False,
) -> None:
self._source = source
self._save_marker = save_marker
self.restore = restore
def __enter__(self) -> _State:
# Entering this context manager - save the state
self._chars = copy(self._source._chars)
self._idx = self._source._idx
self._current = self._source._current
self._marker = self._source._marker
return self
def __exit__(self, exception_type, exception_val, trace):
# Exiting this context manager - restore the prior state
if self.restore or exception_type:
self._source._chars = self._chars
self._source._idx = self._idx
self._source._current = self._current
if self._save_marker:
self._source._marker = self._marker
class _StateHandler:
"""
State preserver for the Parser.
"""
def __init__(self, source: Source) -> None:
self._source = source
self._states = []
def __call__(self, *args, **kwargs):
return _State(self._source, *args, **kwargs)
def __enter__(self) -> None:
state = self()
self._states.append(state)
return state.__enter__()
def __exit__(self, exception_type, exception_val, trace):
state = self._states.pop()
return state.__exit__(exception_type, exception_val, trace)
class Source(str):
EOF = TOMLChar("\0")
def __init__(self, _: str) -> None:
super().__init__()
# Collection of TOMLChars
self._chars = iter([(i, TOMLChar(c)) for i, c in enumerate(self)])
self._idx = 0
self._marker = 0
self._current = TOMLChar("")
self._state = _StateHandler(self)
self.inc()
def reset(self):
# initialize both idx and current
self.inc()
# reset marker
self.mark()
@property
def state(self) -> _StateHandler:
return self._state
@property
def idx(self) -> int:
return self._idx
@property
def current(self) -> TOMLChar:
return self._current
@property
def marker(self) -> int:
return self._marker
def extract(self) -> str:
"""
Extracts the value between marker and index
"""
return self[self._marker : self._idx]
def inc(self, exception: type[ParseError] | None = None) -> bool:
"""
Increments the parser if the end of the input has not been reached.
Returns whether or not it was able to advance.
"""
try:
self._idx, self._current = next(self._chars)
return True
except StopIteration:
self._idx = len(self)
self._current = self.EOF
if exception:
raise self.parse_error(exception)
return False
def inc_n(self, n: int, exception: type[ParseError] | None = None) -> bool:
"""
Increments the parser by n characters
if the end of the input has not been reached.
"""
return all(self.inc(exception=exception) for _ in range(n))
def consume(self, chars, min=0, max=-1):
"""
Consume chars until min/max is satisfied is valid.
"""
while self.current in chars and max != 0:
min -= 1
max -= 1
if not self.inc():
break
# failed to consume minimum number of characters
if min > 0:
raise self.parse_error(UnexpectedCharError, self.current)
def end(self) -> bool:
"""
Returns True if the parser has reached the end of the input.
"""
return self._current is self.EOF
def mark(self) -> None:
"""
Sets the marker to the index's current position
"""
self._marker = self._idx
def parse_error(
self,
exception: type[ParseError] = ParseError,
*args: Any,
**kwargs: Any,
) -> ParseError:
"""
Creates a generic "parse error" at the current position.
"""
line, col = self._to_linecol()
return exception(line, col, *args, **kwargs)
def _to_linecol(self) -> tuple[int, int]:
cur = 0
for i, line in enumerate(self.splitlines()):
if cur + len(line) + 1 > self.idx:
return (i + 1, self.idx - cur)
cur += len(line) + 1
return len(self.splitlines()), 0

52
third_party/python/tomlkit/tomlkit/toml_char.py поставляемый Normal file
Просмотреть файл

@ -0,0 +1,52 @@
import string
class TOMLChar(str):
def __init__(self, c):
super().__init__()
if len(self) > 1:
raise ValueError("A TOML character must be of length 1")
BARE = string.ascii_letters + string.digits + "-_"
KV = "= \t"
NUMBER = string.digits + "+-_.e"
SPACES = " \t"
NL = "\n\r"
WS = SPACES + NL
def is_bare_key_char(self) -> bool:
"""
Whether the character is a valid bare key name or not.
"""
return self in self.BARE
def is_kv_sep(self) -> bool:
"""
Whether the character is a valid key/value separator or not.
"""
return self in self.KV
def is_int_float_char(self) -> bool:
"""
Whether the character if a valid integer or float value character or not.
"""
return self in self.NUMBER
def is_ws(self) -> bool:
"""
Whether the character is a whitespace character or not.
"""
return self in self.WS
def is_nl(self) -> bool:
"""
Whether the character is a new line character or not.
"""
return self in self.NL
def is_spaces(self) -> bool:
"""
Whether the character is a space or not
"""
return self in self.SPACES

7
third_party/python/tomlkit/tomlkit/toml_document.py поставляемый Normal file
Просмотреть файл

@ -0,0 +1,7 @@
from tomlkit.container import Container
class TOMLDocument(Container):
"""
A TOML document.
"""

58
third_party/python/tomlkit/tomlkit/toml_file.py поставляемый Normal file
Просмотреть файл

@ -0,0 +1,58 @@
import os
import re
from typing import TYPE_CHECKING
from tomlkit.api import loads
from tomlkit.toml_document import TOMLDocument
if TYPE_CHECKING:
from _typeshed import StrPath as _StrPath
else:
from typing import Union
_StrPath = Union[str, os.PathLike]
class TOMLFile:
"""
Represents a TOML file.
:param path: path to the TOML file
"""
def __init__(self, path: _StrPath) -> None:
self._path = path
self._linesep = os.linesep
def read(self) -> TOMLDocument:
"""Read the file content as a :class:`tomlkit.toml_document.TOMLDocument`."""
with open(self._path, encoding="utf-8", newline="") as f:
content = f.read()
# check if consistent line endings
num_newline = content.count("\n")
if num_newline > 0:
num_win_eol = content.count("\r\n")
if num_win_eol == num_newline:
self._linesep = "\r\n"
elif num_win_eol == 0:
self._linesep = "\n"
else:
self._linesep = "mixed"
return loads(content)
def write(self, data: TOMLDocument) -> None:
"""Write the TOMLDocument to the file."""
content = data.as_string()
# apply linesep
if self._linesep == "\n":
content = content.replace("\r\n", "\n")
elif self._linesep == "\r\n":
content = re.sub(r"(?<!\r)\n", "\r\n", content)
with open(self._path, "w", encoding="utf-8", newline="") as f:
f.write(content)