diff --git a/tests/examples/json/table_names.json b/tests/examples/json/table_names.json new file mode 100644 index 00000000..24857304 --- /dev/null +++ b/tests/examples/json/table_names.json @@ -0,0 +1,21 @@ +{ + "Special \"table\"": { + "foo": "bar" + }, + "BJ's Restaurant": { + "account": "dining" + }, + "]": { + "foo": 1 + }, + "[bracket]": { + "bar": 2 + }, + "a": { + "b.c": { + "d": { + "baz": 3 + } + } + } +} diff --git a/tests/examples/table_names_with_string_delimiters.toml b/tests/examples/table_names.toml similarity index 50% rename from tests/examples/table_names_with_string_delimiters.toml rename to tests/examples/table_names.toml index 8efd3efb..c9b7cc47 100644 --- a/tests/examples/table_names_with_string_delimiters.toml +++ b/tests/examples/table_names.toml @@ -3,3 +3,12 @@ foo = "bar" ["BJ's Restaurant"] account = "dining" + +["]"] +foo = 1 + +[ "[bracket]" ] +bar = 2 + +[ a . "b.c" . d ] +baz = 3 diff --git a/tests/test_api.py b/tests/test_api.py index a19bddaf..279f81a2 100644 --- a/tests/test_api.py +++ b/tests/test_api.py @@ -53,7 +53,7 @@ def json_serial(obj): "newline_in_strings", "preserve_quotes_in_string", "string_slash_whitespace_newline", - "table_names_with_string_delimiters", + "table_names", ], ) def test_parse_can_parse_valid_toml_files(example, example_name): @@ -61,7 +61,7 @@ def test_parse_can_parse_valid_toml_files(example, example_name): assert isinstance(loads(example(example_name)), TOMLDocument) -@pytest.mark.parametrize("example_name", ["0.5.0", "pyproject"]) +@pytest.mark.parametrize("example_name", ["0.5.0", "pyproject", "table_names"]) def test_parsed_document_are_properly_json_representable( example, json_example, example_name ): @@ -111,6 +111,7 @@ def test_parse_raises_errors_for_invalid_toml_files( "pyproject", "0.5.0", "test", + "table_names", ], ) def test_original_string_and_dumped_string_are_equal(example, example_name): diff --git a/tomlkit/parser.py b/tomlkit/parser.py index 0ff26f09..c5cc139c 100644 --- a/tomlkit/parser.py +++ b/tomlkit/parser.py @@ -201,7 +201,8 @@ def _split_table_name(self, name): # type: (str) -> Generator[Key] in_name = False current = "" t = KeyType.Bare - for c in name: + parts = 0 + for c in name.strip(): c = TOMLChar(c) if c == ".": @@ -212,7 +213,8 @@ def _split_table_name(self, name): # type: (str) -> Generator[Key] if not current: raise self.parse_error() - yield Key(current, t=t, sep="") + yield Key(current.strip(), t=t, sep="") + parts += 1 current = "" t = KeyType.Bare @@ -233,17 +235,35 @@ def _split_table_name(self, name): # type: (str) -> Generator[Key] in_name = False else: + if current and TOMLChar(current[-1]).is_spaces() and not parts: + raise self.parse_error() + in_name = True t = KeyType.Literal if c == "'" else KeyType.Basic continue elif in_name or c.is_bare_key_char(): + if ( + not in_name + and current + and TOMLChar(current[-1]).is_spaces() + and not parts + ): + raise self.parse_error() + current += c + elif c.is_spaces(): + # A space is only valid at this point + # if it's in between parts. + # We store it for now and will check + # later if it's valid + current += c + continue else: raise self.parse_error() - if current: - yield Key(current, t=t, sep="") + if current.strip(): + yield Key(current.strip(), t=t, sep="") def _parse_item(self): # type: () -> Optional[Tuple[Optional[Key], Item]] """ @@ -916,15 +936,46 @@ def _parse_table( is_aot = True - # Key + # Consume any whitespace self.mark() - while self._current != "]" and self.inc(): - if self.end(): - raise self.parse_error(UnexpectedEofError) - + while self._current.is_spaces() and self.inc(): pass - name = self.extract() + ws_prefix = self.extract() + + # Key + if self._current in [StringType.SLL.value, StringType.SLB.value]: + delimiter = ( + StringType.SLL + if self._current == StringType.SLL.value + else StringType.SLB + ) + name = self._parse_string(delimiter) + name = "{delimiter}{name}{delimiter}".format( + delimiter=delimiter.value, name=name + ) + + self.mark() + while self._current != "]" and self.inc(): + if self.end(): + raise self.parse_error(UnexpectedEofError) + + pass + + ws_suffix = self.extract() + name += ws_suffix + else: + self.mark() + while self._current != "]" and self.inc(): + if self.end(): + raise self.parse_error(UnexpectedEofError) + + pass + + name = self.extract() + + name = ws_prefix + name + if not name.strip(): raise self.parse_error(EmptyTableNameError)