309 lines
7.9 KiB
Python
309 lines
7.9 KiB
Python
from __future__ import annotations
|
|
|
|
import contextlib
|
|
import datetime as _datetime
|
|
|
|
from collections.abc import Mapping
|
|
from typing import IO
|
|
from typing import Iterable
|
|
from typing import TypeVar
|
|
|
|
from pipenv.vendor.tomlkit._utils import parse_rfc3339
|
|
from pipenv.vendor.tomlkit.container import Container
|
|
from pipenv.vendor.tomlkit.exceptions import UnexpectedCharError
|
|
from pipenv.vendor.tomlkit.items import CUSTOM_ENCODERS
|
|
from pipenv.vendor.tomlkit.items import AoT
|
|
from pipenv.vendor.tomlkit.items import Array
|
|
from pipenv.vendor.tomlkit.items import Bool
|
|
from pipenv.vendor.tomlkit.items import Comment
|
|
from pipenv.vendor.tomlkit.items import Date
|
|
from pipenv.vendor.tomlkit.items import DateTime
|
|
from pipenv.vendor.tomlkit.items import DottedKey
|
|
from pipenv.vendor.tomlkit.items import Encoder
|
|
from pipenv.vendor.tomlkit.items import Float
|
|
from pipenv.vendor.tomlkit.items import InlineTable
|
|
from pipenv.vendor.tomlkit.items import Integer
|
|
from pipenv.vendor.tomlkit.items import Item as _Item
|
|
from pipenv.vendor.tomlkit.items import Key
|
|
from pipenv.vendor.tomlkit.items import SingleKey
|
|
from pipenv.vendor.tomlkit.items import String
|
|
from pipenv.vendor.tomlkit.items import StringType as _StringType
|
|
from pipenv.vendor.tomlkit.items import Table
|
|
from pipenv.vendor.tomlkit.items import Time
|
|
from pipenv.vendor.tomlkit.items import Trivia
|
|
from pipenv.vendor.tomlkit.items import Whitespace
|
|
from pipenv.vendor.tomlkit.items import item
|
|
from pipenv.vendor.tomlkit.parser import Parser
|
|
from pipenv.vendor.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))
|
|
|
|
|
|
E = TypeVar("E", bound=Encoder)
|
|
|
|
|
|
def register_encoder(encoder: E) -> E:
|
|
"""Add a custom encoder, which should be a function that will be called
|
|
if the value can't otherwise be converted. It should takes a single value
|
|
and return a TOMLKit item or raise a ``TypeError``.
|
|
"""
|
|
CUSTOM_ENCODERS.append(encoder)
|
|
return encoder
|
|
|
|
|
|
def unregister_encoder(encoder: Encoder) -> None:
|
|
"""Unregister a custom encoder."""
|
|
with contextlib.suppress(ValueError):
|
|
CUSTOM_ENCODERS.remove(encoder)
|