Repository: sdispater/tomlkit

Issue:
Deleting a table creates invalid output
As specified on the [`quickstart.rst`](https://github.com/sdispater/tomlkit/blob/master/docs/quickstart.rst), one can use the `del` command to remove a table from a `TOMLDocument`.  This has a faulty implementation when dealing with multi-tier tables.  The internal data is deleted and the JSON result is as expected but the TOMLDocument will not render correctly afterword.  The resulting TOML does not combine keys and will create 2 tables with the same name which causes TOML Parser Errors on re-read.  The bug is more obscure than I first thought as it will not render 2 table headings if the immediate parent/previous table header is the same.  However, if the previous table is not the same section, it will re-render the header rather than checking the entire `TOMLDocument` for the parent header.

### Example

The example is here to illustrate an incorrect result and not for debate on if this should be done or not.

Imported `pyproject.toml`
```toml
[project]
name = "examplepkg"

[build-system]
requires = [
    "setuptools",
    "wheel"
]
build-backend = "setuptools.build_meta"

[project.optional-dependencies]
dev = [
    "python-semantic-release"
]
```

Example code which creates invalid output
```python
"""Script to strip optional-dependencies from pyproject configuration."""
import json, tomlkit

with open('pyproject.toml', 'rb') as fd:
    conf = tomlkit.load(fd)

if conf.get('project', {}).get('optional-dependencies', None) is not None:
    del conf['project']['optional-dependencies']

print(json.dumps(conf, indent=2))
tomlstr = tomlkit.dumps(conf)
print(tomlstr)

# attempt reloading
prod_conf = tomlkit.loads(tomlstr)
# Raises tomlkit.exceptions.KeyAlreadyPresent: Key "project" already exists.
```

### Expected Result

JSON output (expected)

```json
{
  "project": {
    "name": "exampleprj"
  },
  "build-system": {
    "requires": [
      "setuptools",
      "wheel"
    ],
    "build-backend": "setuptools.build_meta"
  }
}
```

TOML output (expected)

```toml
[project]
name = "examplepkg"

[build-system]
requires = [
    "setuptools",
    "wheel"
]
build-backend = "setuptools.build_meta"
```

### Actual Result

JSON output (actual)
**Result: Matches Expected Result**

TOML output (actual)
**Result: Failure**

```toml
[project]
name = "examplepkg"

[build-system]
requires = [
    "setuptools",
    "wheel"
]
build-backend = "setuptools.build_meta"

[project]
```
As you can see the toml generated actually outputs 2 `[project]` sections as it dropped the tiered section and the internals but the result causes the `pyproject.toml` to be invalid because of 2 defined project sections.


Discussion:

Patch:
diff --git a/docs/quickstart.rst b/docs/quickstart.rst
index 6e80668..a3ce827 100644
--- a/docs/quickstart.rst
+++ b/docs/quickstart.rst
@@ -64,7 +64,7 @@ TOML Kit provides an intuitive API to modify TOML documents::
     """
 
     # Remove the newly added table
-    >>> doc.remove("table2")
+    >>> doc.pop("table2")
     # del doc["table2] is also possible
 
 Writing
diff --git a/tomlkit/container.py b/tomlkit/container.py
index 791b444..070c583 100644
--- a/tomlkit/container.py
+++ b/tomlkit/container.py
@@ -346,6 +346,25 @@ class Container(_CustomDict):
 
         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: Union[Key, str]) -> "Container":
         """Remove a key from the container."""
         if not isinstance(key, Key):
@@ -804,7 +823,7 @@ class OutOfOrderTableProxy(_CustomDict):
         self._tables_map = {}
 
         for i in indices:
-            key, item = self._container._body[i]
+            _, item = self._container._body[i]
 
             if isinstance(item, Table):
                 self._tables.append(item)
@@ -842,10 +861,20 @@ class OutOfOrderTableProxy(_CustomDict):
         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: Union[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)
@@ -860,9 +889,6 @@ class OutOfOrderTableProxy(_CustomDict):
     def __len__(self) -> int:
         return dict.__len__(self)
 
-    def __getattr__(self, attribute):
-        return getattr(self._internal_container, attribute)
-
     def setdefault(self, key: Union[Key, str], default: Any) -> Any:
         super().setdefault(key, default=default)
         return self[key]


Test patch:
diff --git a/tests/test_toml_document.py b/tests/test_toml_document.py
index f2e57c9..f5a5e8c 100644
--- a/tests/test_toml_document.py
+++ b/tests/test_toml_document.py
@@ -14,10 +14,8 @@ from tomlkit import ws
 from tomlkit._utils import _utc
 from tomlkit.api import document
 from tomlkit.exceptions import NonExistentKey
-from tomlkit.toml_document import TOMLDocument
 
 from .util import assert_is_ppo
-from .util import elementary_test
 
 
 def test_document_is_a_dict(example):
@@ -654,6 +652,31 @@ a = "b"
     assert expected == doc.as_string()
 
 
+def test_remove_from_out_of_order_table():
+    content = """[a]
+x = 1
+
+[c]
+z = 3
+
+[a.b]
+y = 2
+"""
+    document = parse(content)
+    del document["a"]["b"]
+    assert (
+        document.as_string()
+        == """[a]
+x = 1
+
+[c]
+z = 3
+
+"""
+    )
+    assert json.dumps(document) == '{"a": {"x": 1}, "c": {"z": 3}}'
+
+
 def test_updating_nested_value_keeps_correct_indent():
     content = """
 [Key1]