updates
This commit is contained in:
946
Backend/venv/lib/python3.12/site-packages/tomlkit/container.py
Normal file
946
Backend/venv/lib/python3.12/site-packages/tomlkit/container.py
Normal file
@@ -0,0 +1,946 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import copy
|
||||
|
||||
from typing import Any
|
||||
from typing import Iterator
|
||||
|
||||
from tomlkit._compat import decode
|
||||
from tomlkit._types import _CustomDict
|
||||
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 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]:
|
||||
"""Returns as pure python object (ppo)"""
|
||||
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]:
|
||||
"""The wrapped dict value"""
|
||||
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 _validate_out_of_order_table(self, key: SingleKey | None = None) -> None:
|
||||
if key is None:
|
||||
for k in self._map:
|
||||
assert k is not None
|
||||
self._validate_out_of_order_table(k)
|
||||
return
|
||||
if key not in self._map or not isinstance(self._map[key], tuple):
|
||||
return
|
||||
OutOfOrderTableProxy.validate(self, self._map[key])
|
||||
|
||||
def append(
|
||||
self, key: Key | str | None, item: Item, validate: bool = True
|
||||
) -> 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 (
|
||||
key.is_dotted()
|
||||
or current_body_element[0].is_dotted()
|
||||
or self._table_keys[-1] != current_body_element[0]
|
||||
):
|
||||
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)
|
||||
|
||||
if validate:
|
||||
self._validate_out_of_order_table(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):
|
||||
after_item = self._body[last_index][1]
|
||||
if not (
|
||||
isinstance(after_item, Whitespace)
|
||||
or "\n" in after_item.trivia.indent
|
||||
):
|
||||
after_item.trivia.indent = "\n" + after_item.trivia.indent
|
||||
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 | None, 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))
|
||||
elif key is not None:
|
||||
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)
|
||||
|
||||
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):
|
||||
if (
|
||||
s.strip(" ")
|
||||
and not s.strip(" ").endswith("\n")
|
||||
and "\n" not in v.trivia.indent
|
||||
):
|
||||
s += "\n"
|
||||
s += self._render_table(k, v)
|
||||
elif isinstance(v, AoT):
|
||||
if (
|
||||
s.strip(" ")
|
||||
and not s.strip(" ").endswith("\n")
|
||||
and "\n" not in v.trivia.indent
|
||||
):
|
||||
s += "\n"
|
||||
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 (
|
||||
cur.strip(" ")
|
||||
and not cur.strip(" ").endswith("\n")
|
||||
and "\n" not in v.trivia.indent
|
||||
):
|
||||
cur += "\n"
|
||||
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):
|
||||
if (
|
||||
cur.strip(" ")
|
||||
and not cur.strip(" ").endswith("\n")
|
||||
and "\n" not in v.trivia.indent
|
||||
):
|
||||
cur += "\n"
|
||||
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:
|
||||
item = self.item(key)
|
||||
if isinstance(item, Item) and 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, or
|
||||
# - The table being replaced has a newline
|
||||
last, _ = self._previous_item_with_index()
|
||||
idx = last if idx < 0 else idx
|
||||
has_ws = ends_with_whitespace(value)
|
||||
replace_has_ws = (
|
||||
isinstance(v, Table)
|
||||
and v.value.body
|
||||
and isinstance(v.value.body[-1][1], Whitespace)
|
||||
)
|
||||
next_ws = idx < last and isinstance(self._body[idx + 1][1], Whitespace)
|
||||
if (idx < last or replace_has_ws) 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):
|
||||
@staticmethod
|
||||
def validate(container: Container, indices: tuple[int, ...]) -> None:
|
||||
"""Validate out of order tables in the given container"""
|
||||
# Append all items to a temp container to see if there is any error
|
||||
temp_container = Container(True)
|
||||
for i in indices:
|
||||
_, item = container._body[i]
|
||||
|
||||
if isinstance(item, Table):
|
||||
for k, v in item.value.body:
|
||||
temp_container.append(k, v, validate=False)
|
||||
|
||||
temp_container._validate_out_of_order_table()
|
||||
|
||||
def __init__(self, container: Container, indices: tuple[int, ...]) -> None:
|
||||
self._container = container
|
||||
self._internal_container = Container(True)
|
||||
self._tables: list[Table] = []
|
||||
self._tables_map: dict[Key, list[int]] = {}
|
||||
|
||||
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._raw_append(k, v)
|
||||
indices = self._tables_map.setdefault(k, [])
|
||||
if table_idx not in indices:
|
||||
indices.append(table_idx)
|
||||
if k is not None:
|
||||
dict.__setitem__(self, k.key, v)
|
||||
|
||||
self._internal_container._validate_out_of_order_table()
|
||||
|
||||
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, value: Any) -> None:
|
||||
from .items import item
|
||||
|
||||
def _is_table_or_aot(it: Any) -> bool:
|
||||
return isinstance(item(it), (Table, AoT))
|
||||
|
||||
if key in self._tables_map:
|
||||
# Overwrite the first table and remove others
|
||||
indices = self._tables_map[key]
|
||||
while len(indices) > 1:
|
||||
table = self._tables[indices.pop()]
|
||||
self._remove_table(table)
|
||||
old_value = self._tables[indices[0]][key]
|
||||
if _is_table_or_aot(old_value) and not _is_table_or_aot(value):
|
||||
# Remove the entry from the map and set value again.
|
||||
del self._tables[indices[0]][key]
|
||||
del self._tables_map[key]
|
||||
self[key] = value
|
||||
return
|
||||
self._tables[indices[0]][key] = value
|
||||
elif self._tables:
|
||||
if not _is_table_or_aot(value): # if the value is a plain value
|
||||
for table in self._tables:
|
||||
# find the first table that allows plain values
|
||||
if any(not _is_table_or_aot(v) for _, v in table.items()):
|
||||
table[key] = value
|
||||
break
|
||||
else:
|
||||
self._tables[0][key] = value
|
||||
else:
|
||||
self._tables[0][key] = value
|
||||
else:
|
||||
self._container[key] = value
|
||||
|
||||
self._internal_container[key] = value
|
||||
if key is not None:
|
||||
dict.__setitem__(self, key, value)
|
||||
|
||||
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 not in self._tables_map:
|
||||
raise NonExistentKey(key)
|
||||
|
||||
for i in reversed(self._tables_map[key]):
|
||||
table = self._tables[i]
|
||||
del table[key]
|
||||
if not table and len(self._tables) > 1:
|
||||
self._remove_table(table)
|
||||
|
||||
del self._tables_map[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))
|
||||
Reference in New Issue
Block a user