Skip to content

Commit

Permalink
Merge pull request #162 from sdispater/feature/write-dotted-key
Browse files Browse the repository at this point in the history
Support adding item with dotted key
  • Loading branch information
frostming committed Dec 23, 2021
2 parents b5da6c9 + 615710e commit a676bef
Show file tree
Hide file tree
Showing 6 changed files with 93 additions and 81 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
### Added

- Add a new argument to `table` API to allow it to be a super table. ([#159](https://github.com/sdispater/tomlkit/pull/159))
- Support adding item to `Table` and `Container` with dotted key. ([#160](https://github.com/sdispater/tomlkit/pull/160))

## [0.8.0] - 2021-12-20

Expand Down
10 changes: 10 additions & 0 deletions tests/test_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -291,3 +291,13 @@ def test_build_super_table():
table.add("bar", {"x": 1})
doc.add("foo", table)
assert doc.as_string() == "[foo.bar]\nx = 1\n"


def test_add_dotted_key():
doc = tomlkit.document()
doc.add(tomlkit.key(["foo", "bar"]), 1)
assert doc.as_string() == "foo.bar = 1\n"

table = tomlkit.table()
table.add(tomlkit.key(["foo", "bar"]), 1)
assert table.as_string() == "foo.bar = 1\n"
9 changes: 7 additions & 2 deletions tomlkit/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@

from collections.abc import Mapping
from typing import IO
from typing import Iterable
from typing import Tuple
from typing import Union

from ._utils import parse_rfc3339
from .container import Container
Expand All @@ -12,6 +14,7 @@
from .items import Comment
from .items import Date
from .items import DateTime
from .items import DottedKey
from .items import Float
from .items import InlineTable
from .items import Integer
Expand Down Expand Up @@ -141,8 +144,10 @@ def aot() -> AoT:
return AoT([])


def key(k: str) -> Key:
return SingleKey(k)
def key(k: Union[str, Iterable[str]]) -> Key:
if isinstance(k, str):
return SingleKey(k)
return DottedKey([key(_k) for _k in k])


def value(raw: str) -> _Item:
Expand Down
63 changes: 63 additions & 0 deletions tomlkit/container.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
from .items import Null
from .items import SingleKey
from .items import Table
from .items import Trivia
from .items import Whitespace
from .items import _CustomDict
from .items import item as _item
Expand Down Expand Up @@ -89,13 +90,75 @@ def add(

return self.append(key, item)

def _handle_dotted_key(self, key: Key, value: Item) -> None:
names = tuple(iter(key))
name = names[0]
name._dotted = True
if name in self:
if not isinstance(value, Table):
table = Table(Container(True), Trivia(), False, is_super_table=True)
_table = table
for i, _name in enumerate(names[1:]):
if i == len(names) - 2:
_name.sep = key.sep

_table.append(_name, value)
else:
_name._dotted = True
_table.append(
_name,
Table(
Container(True),
Trivia(),
False,
is_super_table=i < len(names) - 2,
),
)

_table = _table[_name]

value = table

self.append(name, value)

return
else:
table = Table(Container(True), Trivia(), False, is_super_table=True)
self.append(name, table)

for i, _name in enumerate(names[1:]):
if i == len(names) - 2:
_name.sep = key.sep

table.append(_name, value)
else:
_name._dotted = True
if _name in table.value:
table = table.value[_name]
else:
table.append(
_name,
Table(
Container(True),
Trivia(),
False,
is_super_table=i < len(names) - 2,
),
)

table = table[_name]

def append(self, key: Union[Key, str, None], item: Item) -> "Container":
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

Expand Down
13 changes: 9 additions & 4 deletions tomlkit/items.py
Original file line number Diff line number Diff line change
Expand Up @@ -259,6 +259,9 @@ def concat(self, other: "Key") -> "DottedKey":
keys = self._keys + other._keys
return DottedKey(keys, sep=self.sep)

def is_multi(self) -> bool:
return len(self._keys) > 1

def as_string(self) -> str:
return self._original

Expand Down Expand Up @@ -329,9 +332,9 @@ def __init__(
if original is None:
original = ".".join(k.as_string() for k in self._keys)

self.sep = "=" if sep is None else sep
self.sep = " = " if sep is None else sep
self._original = original
self._dotted = True
self._dotted = False
self.key = ".".join(k.key for k in self._keys)

def __hash__(self) -> int:
Expand Down Expand Up @@ -1237,7 +1240,8 @@ def append(self, key, _item):
self._value.append(key, _item)

if isinstance(key, Key):
key = key.key
key = next(iter(key)).key
_item = self._value[key]

if key is not None:
dict.__setitem__(self, key, _item)
Expand All @@ -1264,7 +1268,8 @@ def raw_append(self, key: Union[Key, str], _item: Any) -> "Table":
self._value.append(key, _item)

if isinstance(key, Key):
key = key.key
key = next(iter(key)).key
_item = self._value[key]

if key is not None:
dict.__setitem__(self, key, _item)
Expand Down
78 changes: 3 additions & 75 deletions tomlkit/parser.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import re
import string

from typing import Any
from typing import List
from typing import Optional
from typing import Tuple
Expand Down Expand Up @@ -145,10 +144,8 @@ def parse(self) -> TOMLDocument:
break

key, value = item
if key is not None and key.is_dotted():
if (key is not None and key.is_multi()) or not self._merge_ws(value, body):
# We actually have a table
self._handle_dotted_key(body, key, value)
elif not self._merge_ws(value, body):
body.append(key, value)

self.mark()
Expand Down Expand Up @@ -415,69 +412,6 @@ def _parse_bare_key(self) -> Key:

return key

def _handle_dotted_key(
self, container: Union[Container, Table], key: Key, value: Any
) -> None:
names = tuple(iter(key))
name = names[0]
name._dotted = True
if name in container:
if not isinstance(value, Table):
table = Table(Container(True), Trivia(), False, is_super_table=True)
_table = table
for i, _name in enumerate(names[1:]):
if i == len(names) - 2:
_name.sep = key.sep

_table.append(_name, value)
else:
_name._dotted = True
_table.append(
_name,
Table(
Container(True),
Trivia(),
False,
is_super_table=i < len(names) - 2,
),
)

_table = _table[_name]

value = table

container.append(name, value)

return
else:
table = Table(Container(True), Trivia(), False, is_super_table=True)
if isinstance(container, Table):
container.raw_append(name, table)
else:
container.append(name, table)

for i, _name in enumerate(names[1:]):
if i == len(names) - 2:
_name.sep = key.sep

table.append(_name, value)
else:
_name._dotted = True
if _name in table.value:
table = table.value[_name]
else:
table.append(
_name,
Table(
Container(True),
Trivia(),
False,
is_super_table=i < len(names) - 2,
),
)

table = table[_name]

def _parse_value(self) -> Item:
"""
Attempts to parse a value at the current position.
Expand Down Expand Up @@ -708,10 +642,7 @@ def _parse_inline_table(self) -> InlineTable:
raise self.parse_error(UnexpectedCharError, self._current)

key, val = self._parse_key_value(False)
if key.is_dotted():
self._handle_dotted_key(elems, key, val)
else:
elems.add(key, val)
elems.add(key, val)

# consume trailing whitespace
mark = self._idx
Expand Down Expand Up @@ -1059,10 +990,7 @@ def _parse_table(
if item:
_key, item = item
if not self._merge_ws(item, values):
if _key is not None and _key.is_dotted():
self._handle_dotted_key(table, _key, item)
else:
table.raw_append(_key, item)
table.raw_append(_key, item)
else:
if self._current == "[":
_, key_next = self._peek_table()
Expand Down

0 comments on commit a676bef

Please sign in to comment.