Parse TOML from a string.
(s: str, /, *, parse_float: ParseFloat = float)
| 67 | |
| 68 | |
| 69 | def loads(s: str, /, *, parse_float: ParseFloat = float) -> dict[str, Any]: # noqa: C901 |
| 70 | """Parse TOML from a string.""" |
| 71 | |
| 72 | # The spec allows converting "\r\n" to "\n", even in string |
| 73 | # literals. Let's do so to simplify parsing. |
| 74 | src = s.replace("\r\n", "\n") |
| 75 | pos = 0 |
| 76 | out = Output(NestedDict(), Flags()) |
| 77 | header: Key = () |
| 78 | parse_float = make_safe_parse_float(parse_float) |
| 79 | |
| 80 | # Parse one statement at a time |
| 81 | # (typically means one line in TOML source) |
| 82 | while True: |
| 83 | # 1. Skip line leading whitespace |
| 84 | pos = skip_chars(src, pos, TOML_WS) |
| 85 | |
| 86 | # 2. Parse rules. Expect one of the following: |
| 87 | # - end of file |
| 88 | # - end of line |
| 89 | # - comment |
| 90 | # - key/value pair |
| 91 | # - append dict to list (and move to its namespace) |
| 92 | # - create dict (and move to its namespace) |
| 93 | # Skip trailing whitespace when applicable. |
| 94 | try: |
| 95 | char = src[pos] |
| 96 | except IndexError: |
| 97 | break |
| 98 | if char == "\n": |
| 99 | pos += 1 |
| 100 | continue |
| 101 | if char in KEY_INITIAL_CHARS: |
| 102 | pos = key_value_rule(src, pos, out, header, parse_float) |
| 103 | pos = skip_chars(src, pos, TOML_WS) |
| 104 | elif char == "[": |
| 105 | try: |
| 106 | second_char: str | None = src[pos + 1] |
| 107 | except IndexError: |
| 108 | second_char = None |
| 109 | out.flags.finalize_pending() |
| 110 | if second_char == "[": |
| 111 | pos, header = create_list_rule(src, pos, out) |
| 112 | else: |
| 113 | pos, header = create_dict_rule(src, pos, out) |
| 114 | pos = skip_chars(src, pos, TOML_WS) |
| 115 | elif char != "#": |
| 116 | raise suffixed_err(src, pos, "Invalid statement") |
| 117 | |
| 118 | # 3. Skip comment |
| 119 | pos = skip_comment(src, pos) |
| 120 | |
| 121 | # 4. Expect end of line or end of file |
| 122 | try: |
| 123 | char = src[pos] |
| 124 | except IndexError: |
| 125 | break |
| 126 | if char != "\n": |
no test coverage detected