teptris 0.2.15__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- teptris-0.2.15/LICENSE.txt +21 -0
- teptris-0.2.15/MANIFEST.in +1 -0
- teptris-0.2.15/PKG-INFO +60 -0
- teptris-0.2.15/README.md +50 -0
- teptris-0.2.15/pyproject.toml +17 -0
- teptris-0.2.15/setup.cfg +4 -0
- teptris-0.2.15/setup.py +68 -0
- teptris-0.2.15/src/teptris/__init__.py +38 -0
- teptris-0.2.15/src/teptris/_native.abi3.so +0 -0
- teptris-0.2.15/src/teptris/_native.c +382 -0
- teptris-0.2.15/src/teptris/_platform/0.1.0/libteptris.dylib +0 -0
- teptris-0.2.15/src/teptris.egg-info/PKG-INFO +60 -0
- teptris-0.2.15/src/teptris.egg-info/SOURCES.txt +58 -0
- teptris-0.2.15/src/teptris.egg-info/dependency_links.txt +1 -0
- teptris-0.2.15/src/teptris.egg-info/top_level.txt +1 -0
- teptris-0.2.15/tests/test_teptris.py +122 -0
- teptris-0.2.15/vendor/libteptris/LICENSE.md +21 -0
- teptris-0.2.15/vendor/libteptris/src/include/teptris/plan.h +123 -0
- teptris-0.2.15/vendor/libteptris/src/include/teptris/teptris.h +208 -0
- teptris-0.2.15/vendor/libteptris/src/include/teptris/version.h +9 -0
- teptris-0.2.15/vendor/libteptris/src/teptris/api.c +362 -0
- teptris-0.2.15/vendor/libteptris/src/teptris/build.c +355 -0
- teptris-0.2.15/vendor/libteptris/src/teptris/common/chartype.c +70 -0
- teptris-0.2.15/vendor/libteptris/src/teptris/common/chartype.h +41 -0
- teptris-0.2.15/vendor/libteptris/src/teptris/common/ryu/common.h +114 -0
- teptris-0.2.15/vendor/libteptris/src/teptris/common/ryu/d2s.c +496 -0
- teptris-0.2.15/vendor/libteptris/src/teptris/common/ryu/d2s_full_table.h +367 -0
- teptris-0.2.15/vendor/libteptris/src/teptris/common/ryu/d2s_intrinsics.h +357 -0
- teptris-0.2.15/vendor/libteptris/src/teptris/common/ryu/digit_table.h +35 -0
- teptris-0.2.15/vendor/libteptris/src/teptris/common/ryu/ryu_d2s.h +39 -0
- teptris-0.2.15/vendor/libteptris/src/teptris/common/ryu/ryu_parse.h +45 -0
- teptris-0.2.15/vendor/libteptris/src/teptris/common/ryu/s2d.c +259 -0
- teptris-0.2.15/vendor/libteptris/src/teptris/common/simd.h +119 -0
- teptris-0.2.15/vendor/libteptris/src/teptris/dom/dom.c +192 -0
- teptris-0.2.15/vendor/libteptris/src/teptris/dom/dom.h +122 -0
- teptris-0.2.15/vendor/libteptris/src/teptris/emit/emitter.c +718 -0
- teptris-0.2.15/vendor/libteptris/src/teptris/emit/emitter.h +11 -0
- teptris-0.2.15/vendor/libteptris/src/teptris/memory/arena.c +104 -0
- teptris-0.2.15/vendor/libteptris/src/teptris/memory/arena.h +53 -0
- teptris-0.2.15/vendor/libteptris/src/teptris/parse/datetime.c +218 -0
- teptris-0.2.15/vendor/libteptris/src/teptris/parse/keys.c +121 -0
- teptris-0.2.15/vendor/libteptris/src/teptris/parse/parse.h +76 -0
- teptris-0.2.15/vendor/libteptris/src/teptris/parse/parser.c +931 -0
- teptris-0.2.15/vendor/libteptris/src/teptris/parse/scalars.c +940 -0
- teptris-0.2.15/vendor/libteptris/src/teptris/plan/plan.c +472 -0
- teptris-0.2.15/vendor/libteptris/src/teptris/version.c +6 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 teptris contributors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
recursive-include vendor *.c *.h *.md
|
teptris-0.2.15/PKG-INFO
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: teptris
|
|
3
|
+
Version: 0.2.15
|
|
4
|
+
Summary: TOML for Python at libteptris speed (native C extension, no fallback)
|
|
5
|
+
License: MIT
|
|
6
|
+
Requires-Python: >=3.9
|
|
7
|
+
Description-Content-Type: text/markdown
|
|
8
|
+
License-File: LICENSE.txt
|
|
9
|
+
Dynamic: license-file
|
|
10
|
+
|
|
11
|
+
# teptris-py — TOML for Python at libleptris speed
|
|
12
|
+
|
|
13
|
+
A native C-extension binding (no ctypes, no fallback) over
|
|
14
|
+
[libteptris](https://github.com/leptris/teptris) — the TOML counterpart
|
|
15
|
+
of leptris-py (XML) and yeptris-py (YAML). API shape mirrors
|
|
16
|
+
`tomllib`/`tomli` (`loads`/`load` raising `TOMLDecodeError`) plus a
|
|
17
|
+
`dumps` writer in the `tomli_w` spirit. Datetime mapping mirrors
|
|
18
|
+
tomllib: aware/naive `datetime`, `date`, `time`.
|
|
19
|
+
|
|
20
|
+
Both directions are native: `dumps` walks the object tree in C
|
|
21
|
+
through the builder API and emits via the shared engine emitter —
|
|
22
|
+
3.5×–14.2× faster than tomli_w on every benchmark shape. The engine
|
|
23
|
+
implements the TOML 1.1 draft grammar (a strict superset of 1.0)
|
|
24
|
+
with 100% toml-test conformance, and parses at ≥3× the best C/C++
|
|
25
|
+
competitor on every shape.
|
|
26
|
+
|
|
27
|
+
## Packaging
|
|
28
|
+
|
|
29
|
+
`pip install teptris` resolves one of 8 `cp39-abi3` wheels —
|
|
30
|
+
manylinux/musllinux x86_64 and aarch64, macOS arm64 and x86_64,
|
|
31
|
+
Windows AMD64 and ARM64 — covering every CPython >= 3.9 including
|
|
32
|
+
future minors. The extension statically links `libteptris`, so each
|
|
33
|
+
wheel is a single self-contained module (no shared-library chain).
|
|
34
|
+
Linux wheels build the engine inside each container via cibuildwheel
|
|
35
|
+
so musllinux links musl; the manylinux wheels carry the maximal tag
|
|
36
|
+
set (glibc >= 2.17).
|
|
37
|
+
|
|
38
|
+
Every other platform (FreeBSD, Solaris/illumos, any CPython on an
|
|
39
|
+
exotic arch) installs the sdist, which vendors the engine and
|
|
40
|
+
compiles it with the installing interpreter's compiler — a C compiler
|
|
41
|
+
is required, and the build fails loudly without one.
|
|
42
|
+
|
|
43
|
+
## Development
|
|
44
|
+
|
|
45
|
+
`setup.py` links the engine statically when it finds
|
|
46
|
+
`libteptris.a`/`teptris.lib` under `TEPTRIS_LIBDIR` (what the wheels
|
|
47
|
+
ship); otherwise it falls back to the shared library with an rpath to
|
|
48
|
+
its build tree. Point `TEPTRIS_INCLUDE`/`TEPTRIS_LIBDIR` at a C
|
|
49
|
+
checkout's build and run the suite:
|
|
50
|
+
|
|
51
|
+
```sh
|
|
52
|
+
cmake -B ../teptris/build -S ../teptris \
|
|
53
|
+
-DCMAKE_BUILD_TYPE=Release -DTEPTRIS_BUILD_SHARED=OFF -DTEPTRIS_BUILD_STATIC=ON
|
|
54
|
+
cmake --build ../teptris/build
|
|
55
|
+
TEPTRIS_INCLUDE=../teptris/src/include TEPTRIS_LIBDIR=../teptris/build/src \
|
|
56
|
+
python3 setup.py build_ext --inplace
|
|
57
|
+
PYTHONPATH=src python3 -m unittest discover -s tests -v
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
Publishing and version numbers are USER release decisions.
|
teptris-0.2.15/README.md
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
# teptris-py — TOML for Python at libleptris speed
|
|
2
|
+
|
|
3
|
+
A native C-extension binding (no ctypes, no fallback) over
|
|
4
|
+
[libteptris](https://github.com/leptris/teptris) — the TOML counterpart
|
|
5
|
+
of leptris-py (XML) and yeptris-py (YAML). API shape mirrors
|
|
6
|
+
`tomllib`/`tomli` (`loads`/`load` raising `TOMLDecodeError`) plus a
|
|
7
|
+
`dumps` writer in the `tomli_w` spirit. Datetime mapping mirrors
|
|
8
|
+
tomllib: aware/naive `datetime`, `date`, `time`.
|
|
9
|
+
|
|
10
|
+
Both directions are native: `dumps` walks the object tree in C
|
|
11
|
+
through the builder API and emits via the shared engine emitter —
|
|
12
|
+
3.5×–14.2× faster than tomli_w on every benchmark shape. The engine
|
|
13
|
+
implements the TOML 1.1 draft grammar (a strict superset of 1.0)
|
|
14
|
+
with 100% toml-test conformance, and parses at ≥3× the best C/C++
|
|
15
|
+
competitor on every shape.
|
|
16
|
+
|
|
17
|
+
## Packaging
|
|
18
|
+
|
|
19
|
+
`pip install teptris` resolves one of 8 `cp39-abi3` wheels —
|
|
20
|
+
manylinux/musllinux x86_64 and aarch64, macOS arm64 and x86_64,
|
|
21
|
+
Windows AMD64 and ARM64 — covering every CPython >= 3.9 including
|
|
22
|
+
future minors. The extension statically links `libteptris`, so each
|
|
23
|
+
wheel is a single self-contained module (no shared-library chain).
|
|
24
|
+
Linux wheels build the engine inside each container via cibuildwheel
|
|
25
|
+
so musllinux links musl; the manylinux wheels carry the maximal tag
|
|
26
|
+
set (glibc >= 2.17).
|
|
27
|
+
|
|
28
|
+
Every other platform (FreeBSD, Solaris/illumos, any CPython on an
|
|
29
|
+
exotic arch) installs the sdist, which vendors the engine and
|
|
30
|
+
compiles it with the installing interpreter's compiler — a C compiler
|
|
31
|
+
is required, and the build fails loudly without one.
|
|
32
|
+
|
|
33
|
+
## Development
|
|
34
|
+
|
|
35
|
+
`setup.py` links the engine statically when it finds
|
|
36
|
+
`libteptris.a`/`teptris.lib` under `TEPTRIS_LIBDIR` (what the wheels
|
|
37
|
+
ship); otherwise it falls back to the shared library with an rpath to
|
|
38
|
+
its build tree. Point `TEPTRIS_INCLUDE`/`TEPTRIS_LIBDIR` at a C
|
|
39
|
+
checkout's build and run the suite:
|
|
40
|
+
|
|
41
|
+
```sh
|
|
42
|
+
cmake -B ../teptris/build -S ../teptris \
|
|
43
|
+
-DCMAKE_BUILD_TYPE=Release -DTEPTRIS_BUILD_SHARED=OFF -DTEPTRIS_BUILD_STATIC=ON
|
|
44
|
+
cmake --build ../teptris/build
|
|
45
|
+
TEPTRIS_INCLUDE=../teptris/src/include TEPTRIS_LIBDIR=../teptris/build/src \
|
|
46
|
+
python3 setup.py build_ext --inplace
|
|
47
|
+
PYTHONPATH=src python3 -m unittest discover -s tests -v
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
Publishing and version numbers are USER release decisions.
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=61"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "teptris"
|
|
7
|
+
version = "0.2.15"
|
|
8
|
+
description = "TOML for Python at libteptris speed (native C extension, no fallback)"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.9"
|
|
11
|
+
license = { text = "MIT" }
|
|
12
|
+
|
|
13
|
+
[tool.setuptools.packages.find]
|
|
14
|
+
where = ["src"]
|
|
15
|
+
|
|
16
|
+
[tool.setuptools.package-data]
|
|
17
|
+
teptris = ["_platform/**/*", "*.so", "*.so.*", "*.dylib", "*.dylib.*", "*.dll"]
|
teptris-0.2.15/setup.cfg
ADDED
teptris-0.2.15/setup.py
ADDED
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import glob
|
|
2
|
+
import os
|
|
3
|
+
import sys
|
|
4
|
+
from setuptools import setup, Extension
|
|
5
|
+
|
|
6
|
+
# Vendored engine (sdist builds): when the C tree sits at vendor/,
|
|
7
|
+
# compile it straight into the extension with the installing
|
|
8
|
+
# interpreter's compiler — no cmake, no prebuilt archive. Precedence
|
|
9
|
+
# over the archive path so a coincidental sibling teptris checkout
|
|
10
|
+
# can never hijack an sdist install.
|
|
11
|
+
_vendor = os.path.join(os.path.dirname(os.path.abspath(__file__)),
|
|
12
|
+
"vendor", "libteptris")
|
|
13
|
+
_engine_src = sorted(
|
|
14
|
+
glob.glob(os.path.join(_vendor, "src", "teptris", "**", "*.c"),
|
|
15
|
+
recursive=True))
|
|
16
|
+
|
|
17
|
+
inc = os.environ.get("TEPTRIS_INCLUDE", "../teptris/src/include")
|
|
18
|
+
libdir = os.environ.get("TEPTRIS_LIBDIR", "../teptris/build-shared/src")
|
|
19
|
+
|
|
20
|
+
# Static-first: one self-contained _native module — no soname/DLL
|
|
21
|
+
# chain to ship, relocate, or repair (the teptris-ruby #38 lesson,
|
|
22
|
+
# applied to wheels so auditwheel/cibuildwheel can work with a plain
|
|
23
|
+
# extension). Falls back to the shared lib for local dev trees.
|
|
24
|
+
_archive = os.path.join(
|
|
25
|
+
libdir, "teptris.lib" if sys.platform == "win32" else "libteptris.a"
|
|
26
|
+
)
|
|
27
|
+
if _engine_src:
|
|
28
|
+
ext = Extension(
|
|
29
|
+
"teptris._native",
|
|
30
|
+
sources=["src/teptris/_native.c"] + _engine_src,
|
|
31
|
+
include_dirs=[os.path.join(_vendor, "src"),
|
|
32
|
+
os.path.join(_vendor, "src", "include")],
|
|
33
|
+
py_limited_api=True,
|
|
34
|
+
)
|
|
35
|
+
elif os.path.exists(_archive):
|
|
36
|
+
ext = Extension(
|
|
37
|
+
"teptris._native",
|
|
38
|
+
sources=["src/teptris/_native.c"],
|
|
39
|
+
include_dirs=[inc],
|
|
40
|
+
py_limited_api=True,
|
|
41
|
+
extra_objects=[_archive],
|
|
42
|
+
)
|
|
43
|
+
else:
|
|
44
|
+
# @loader_path is Mach-O; ELF spells it $ORIGIN; PE has neither —
|
|
45
|
+
# the DLL ships beside the .pyd and CPython 3.8+ loads it via
|
|
46
|
+
# LOAD_LIBRARY_SEARCH_DLL_LOAD_DIR
|
|
47
|
+
ext = Extension(
|
|
48
|
+
"teptris._native",
|
|
49
|
+
sources=["src/teptris/_native.c"],
|
|
50
|
+
include_dirs=[inc],
|
|
51
|
+
py_limited_api=True,
|
|
52
|
+
library_dirs=[libdir],
|
|
53
|
+
libraries=["teptris"],
|
|
54
|
+
extra_link_args=(
|
|
55
|
+
[]
|
|
56
|
+
if sys.platform == "win32"
|
|
57
|
+
else [
|
|
58
|
+
"-Wl,-rpath,"
|
|
59
|
+
+ ("@loader_path" if sys.platform == "darwin" else "$ORIGIN")
|
|
60
|
+
]
|
|
61
|
+
),
|
|
62
|
+
)
|
|
63
|
+
# package_dir spelled here too: raw `setup.py build_ext --inplace`
|
|
64
|
+
# (which the wheel workflow runs) doesn't apply the pyproject
|
|
65
|
+
# src-layout on older setuptools (3.8 cells resolved one), and then
|
|
66
|
+
# the inplace copy targets teptris/ instead of src/teptris/.
|
|
67
|
+
setup(ext_modules=[ext], package_dir={"": "src"},
|
|
68
|
+
options={"bdist_wheel": {"py_limited_api": "cp39"}})
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
"""teptris — TOML for Python at libleptris speed.
|
|
2
|
+
|
|
3
|
+
Native extension over libteptris (C11 TOML 1.1 grammar), no fallback. API shape
|
|
4
|
+
mirrors tomllib/tomli: loads(str|bytes) -> dict raising TOMLDecodeError;
|
|
5
|
+
dumps(obj) -> str in the tomli_w spirit. Datetime mapping mirrors
|
|
6
|
+
tomllib: aware/naive datetime, date, time.
|
|
7
|
+
"""
|
|
8
|
+
from ._native import loads as _loads
|
|
9
|
+
from ._native import dumps
|
|
10
|
+
from ._native import DecodeError as _DecodeError
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class TOMLDecodeError(_DecodeError, ValueError):
|
|
14
|
+
pass
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def loads(data):
|
|
18
|
+
if isinstance(data, str):
|
|
19
|
+
data = data.encode("utf-8")
|
|
20
|
+
elif not isinstance(data, (bytes, bytearray)):
|
|
21
|
+
raise TypeError("loads() expects str or bytes")
|
|
22
|
+
try:
|
|
23
|
+
return _loads(bytes(data))
|
|
24
|
+
except _DecodeError as e:
|
|
25
|
+
err = TOMLDecodeError(str(e))
|
|
26
|
+
err.line = getattr(e, "line", 0)
|
|
27
|
+
err.column = getattr(e, "column", 0)
|
|
28
|
+
raise err from None
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def load(fp):
|
|
32
|
+
if hasattr(fp, "read"):
|
|
33
|
+
return loads(fp.read())
|
|
34
|
+
with open(fp, "rb") as f:
|
|
35
|
+
return loads(f.read())
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
__all__ = ["TOMLDecodeError", "dumps", "load", "loads"]
|
|
Binary file
|
|
@@ -0,0 +1,382 @@
|
|
|
1
|
+
#define PY_SSIZE_T_CLEAN
|
|
2
|
+
#ifndef Py_LIMITED_API
|
|
3
|
+
/* abi3 (#20): the datetime C-API is not in the limited API, so all
|
|
4
|
+
* datetime work goes through cached callables/attributes instead */
|
|
5
|
+
#define Py_LIMITED_API 0x03090000
|
|
6
|
+
#endif
|
|
7
|
+
#include <Python.h>
|
|
8
|
+
#include "teptris/teptris.h"
|
|
9
|
+
|
|
10
|
+
static PyObject *TomlDecodeError;
|
|
11
|
+
/* datetime module callables (limited API: the C-API is unavailable) */
|
|
12
|
+
static PyObject *tep_dt_datetime, *tep_dt_date, *tep_dt_time,
|
|
13
|
+
*tep_dt_timedelta, *tep_dt_timezone;
|
|
14
|
+
|
|
15
|
+
/* strong-ref attr read as long; def on missing/invalid (datetime
|
|
16
|
+
* attributes always exist on well-formed instances) */
|
|
17
|
+
static long attr_long(PyObject *v, const char *name) {
|
|
18
|
+
PyObject *a = PyObject_GetAttrString(v, name);
|
|
19
|
+
if (a == NULL) { PyErr_Clear(); return 0; }
|
|
20
|
+
long r = PyLong_AsLong(a);
|
|
21
|
+
Py_DECREF(a);
|
|
22
|
+
if (r == -1 && PyErr_Occurred()) { PyErr_Clear(); return 0; }
|
|
23
|
+
return r;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
static PyObject *obj_from_node(const teptris_node *n) {
|
|
27
|
+
switch (teptris_node_kind(n)) {
|
|
28
|
+
case TEPTRIS_TABLE: {
|
|
29
|
+
size_t len = teptris_node_table_length(n);
|
|
30
|
+
PyObject *h = PyDict_New();
|
|
31
|
+
if (!h) return NULL;
|
|
32
|
+
for (size_t i = 0; i < len; i++) {
|
|
33
|
+
teptris_view key;
|
|
34
|
+
const teptris_node *v = teptris_node_table_at(n, i, &key);
|
|
35
|
+
PyObject *k = PyUnicode_DecodeUTF8(key.ptr, (Py_ssize_t)key.len, "replace");
|
|
36
|
+
if (!k) { Py_DECREF(h); return NULL; }
|
|
37
|
+
PyObject *val = obj_from_node(v);
|
|
38
|
+
if (!val) { Py_DECREF(k); Py_DECREF(h); return NULL; }
|
|
39
|
+
int rc = PyDict_SetItem(h, k, val);
|
|
40
|
+
Py_DECREF(k); Py_DECREF(val);
|
|
41
|
+
if (rc < 0) { Py_DECREF(h); return NULL; }
|
|
42
|
+
}
|
|
43
|
+
return h;
|
|
44
|
+
}
|
|
45
|
+
case TEPTRIS_ARRAY: {
|
|
46
|
+
size_t len = teptris_node_array_length(n);
|
|
47
|
+
PyObject *a = PyList_New((Py_ssize_t)len);
|
|
48
|
+
if (!a) return NULL;
|
|
49
|
+
for (size_t i = 0; i < len; i++) {
|
|
50
|
+
PyObject *v = obj_from_node(teptris_node_array_at(n, i));
|
|
51
|
+
if (!v) { Py_DECREF(a); return NULL; }
|
|
52
|
+
/* SetItem steals v on both success and failure */
|
|
53
|
+
if (PyList_SetItem(a, (Py_ssize_t)i, v) < 0) {
|
|
54
|
+
Py_DECREF(a); return NULL;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
return a;
|
|
58
|
+
}
|
|
59
|
+
case TEPTRIS_STRING: {
|
|
60
|
+
teptris_view s;
|
|
61
|
+
teptris_node_string(n, &s);
|
|
62
|
+
return PyUnicode_DecodeUTF8(s.ptr, (Py_ssize_t)s.len, "replace");
|
|
63
|
+
}
|
|
64
|
+
case TEPTRIS_INTEGER: {
|
|
65
|
+
int64_t v = 0; teptris_node_integer(n, &v);
|
|
66
|
+
return PyLong_FromLongLong(v);
|
|
67
|
+
}
|
|
68
|
+
case TEPTRIS_FLOAT: {
|
|
69
|
+
double v = 0; teptris_node_float(n, &v);
|
|
70
|
+
return PyFloat_FromDouble(v);
|
|
71
|
+
}
|
|
72
|
+
case TEPTRIS_BOOLEAN: {
|
|
73
|
+
bool v = false; teptris_node_boolean(n, &v);
|
|
74
|
+
return PyBool_FromLong(v ? 1 : 0);
|
|
75
|
+
}
|
|
76
|
+
default: {
|
|
77
|
+
teptris_datetime d; teptris_node_datetime(n, &d);
|
|
78
|
+
switch (teptris_node_kind(n)) {
|
|
79
|
+
case TEPTRIS_DATE_LOCAL:
|
|
80
|
+
return PyObject_CallFunctionObjArgs(
|
|
81
|
+
tep_dt_date, PyLong_FromLong(d.year), PyLong_FromLong(d.month),
|
|
82
|
+
PyLong_FromLong(d.day), NULL);
|
|
83
|
+
case TEPTRIS_TIME_LOCAL:
|
|
84
|
+
return PyObject_CallFunctionObjArgs(
|
|
85
|
+
tep_dt_time, PyLong_FromLong(d.hour),
|
|
86
|
+
PyLong_FromLong(d.minute), PyLong_FromLong(d.second),
|
|
87
|
+
PyLong_FromLong((long)(d.nanosecond / 1000)), NULL);
|
|
88
|
+
default: {
|
|
89
|
+
PyObject *us = PyLong_FromLong((long)(d.nanosecond / 1000));
|
|
90
|
+
if (!us) return NULL;
|
|
91
|
+
if (teptris_node_kind(n) == TEPTRIS_DATETIME_LOCAL)
|
|
92
|
+
return PyObject_CallFunctionObjArgs(
|
|
93
|
+
tep_dt_datetime, PyLong_FromLong(d.year),
|
|
94
|
+
PyLong_FromLong(d.month), PyLong_FromLong(d.day),
|
|
95
|
+
PyLong_FromLong(d.hour), PyLong_FromLong(d.minute),
|
|
96
|
+
PyLong_FromLong(d.second), us, NULL);
|
|
97
|
+
Py_DECREF(us);
|
|
98
|
+
/* aware: tz = timezone(timedelta(0, off)); datetime(..., tz) */
|
|
99
|
+
PyObject *delta = PyObject_CallFunctionObjArgs(
|
|
100
|
+
tep_dt_timedelta, PyLong_FromLong(0),
|
|
101
|
+
PyLong_FromLong((long)d.offset_seconds), PyLong_FromLong(0),
|
|
102
|
+
NULL);
|
|
103
|
+
if (!delta) return NULL;
|
|
104
|
+
PyObject *tz = PyObject_CallFunctionObjArgs(
|
|
105
|
+
tep_dt_timezone, delta, NULL);
|
|
106
|
+
Py_DECREF(delta);
|
|
107
|
+
if (!tz) return NULL;
|
|
108
|
+
PyObject *aware = PyObject_CallFunctionObjArgs(
|
|
109
|
+
tep_dt_datetime, PyLong_FromLong(d.year),
|
|
110
|
+
PyLong_FromLong(d.month), PyLong_FromLong(d.day),
|
|
111
|
+
PyLong_FromLong(d.hour), PyLong_FromLong(d.minute),
|
|
112
|
+
PyLong_FromLong(d.second),
|
|
113
|
+
PyLong_FromLong((long)(d.nanosecond / 1000)), tz, NULL);
|
|
114
|
+
Py_DECREF(tz);
|
|
115
|
+
return aware;
|
|
116
|
+
}}
|
|
117
|
+
}}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
static PyObject *ext_load(PyObject *self, PyObject *args) {
|
|
121
|
+
(void)self;
|
|
122
|
+
PyObject *obj;
|
|
123
|
+
if (!PyArg_ParseTuple(args, "O", &obj)) return NULL;
|
|
124
|
+
const char *data = NULL;
|
|
125
|
+
Py_ssize_t len = 0;
|
|
126
|
+
PyObject *keepalive = NULL;
|
|
127
|
+
if (PyUnicode_Check(obj)) {
|
|
128
|
+
keepalive = PyUnicode_AsUTF8String(obj);
|
|
129
|
+
if (!keepalive) return NULL;
|
|
130
|
+
data = PyBytes_AsString(keepalive);
|
|
131
|
+
len = PyBytes_Size(keepalive);
|
|
132
|
+
} else if (PyBytes_Check(obj)) {
|
|
133
|
+
data = PyBytes_AsString(obj);
|
|
134
|
+
len = PyBytes_Size(obj);
|
|
135
|
+
} else {
|
|
136
|
+
PyErr_SetString(PyExc_TypeError, "loads() expects str or bytes");
|
|
137
|
+
return NULL;
|
|
138
|
+
}
|
|
139
|
+
teptris_document *doc = NULL;
|
|
140
|
+
teptris_status st = teptris_parse(data, (size_t)len, NULL, &doc);
|
|
141
|
+
Py_XDECREF(keepalive);
|
|
142
|
+
if (st != TEPTRIS_OK) {
|
|
143
|
+
const teptris_error *e = teptris_document_error(doc);
|
|
144
|
+
PyObject *ex = PyObject_CallFunction(TomlDecodeError, "s", e->message);
|
|
145
|
+
teptris_document_free(doc);
|
|
146
|
+
if (ex) {
|
|
147
|
+
PyObject_SetAttrString(ex, "line", PyLong_FromSize_t(e->line));
|
|
148
|
+
PyObject_SetAttrString(ex, "column", PyLong_FromSize_t(e->column));
|
|
149
|
+
PyErr_SetObject(TomlDecodeError, ex);
|
|
150
|
+
}
|
|
151
|
+
return NULL;
|
|
152
|
+
}
|
|
153
|
+
PyObject *out = obj_from_node(teptris_document_root(doc));
|
|
154
|
+
teptris_document_free(doc);
|
|
155
|
+
return out;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
/* ------------------------------------------------------------------ dump */
|
|
160
|
+
|
|
161
|
+
static int build_table(teptris_builder *b, PyObject *obj);
|
|
162
|
+
|
|
163
|
+
/* limited API: attribute reads only */
|
|
164
|
+
static PyObject *dt_tzinfo(PyObject *v) {
|
|
165
|
+
return PyObject_GetAttrString(v, "tzinfo");
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
static int dump_check(teptris_status st) {
|
|
169
|
+
if (st == TEPTRIS_OK) return 0;
|
|
170
|
+
if (st == TEPTRIS_ERR_ALLOC) { PyErr_NoMemory(); return -1; }
|
|
171
|
+
PyErr_SetString(PyExc_TypeError, teptris_status_string(st));
|
|
172
|
+
return -1;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
/* keyed scalar; key == NULL means array element */
|
|
176
|
+
static int put_scalar(teptris_builder *b, const char *key, size_t klen,
|
|
177
|
+
PyObject *v) {
|
|
178
|
+
if (PyBool_Check(v)) {
|
|
179
|
+
return dump_check(teptris_builder_put_boolean(b, key, klen,
|
|
180
|
+
v == Py_True));
|
|
181
|
+
}
|
|
182
|
+
if (PyLong_Check(v)) {
|
|
183
|
+
long long i = PyLong_AsLongLong(v);
|
|
184
|
+
if (i == -1 && PyErr_Occurred()) return -1;
|
|
185
|
+
return dump_check(teptris_builder_put_integer(b, key, klen, i));
|
|
186
|
+
}
|
|
187
|
+
if (PyFloat_Check(v)) {
|
|
188
|
+
return dump_check(teptris_builder_put_float(
|
|
189
|
+
b, key, klen, PyFloat_AsDouble(v)));
|
|
190
|
+
}
|
|
191
|
+
if (PyUnicode_Check(v)) {
|
|
192
|
+
PyObject *kb = PyUnicode_AsUTF8String(v);
|
|
193
|
+
if (!kb) return -1;
|
|
194
|
+
int rc = dump_check(teptris_builder_put_string(
|
|
195
|
+
b, key, klen, PyBytes_AsString(kb), (size_t)PyBytes_Size(kb)));
|
|
196
|
+
Py_DECREF(kb);
|
|
197
|
+
return rc;
|
|
198
|
+
}
|
|
199
|
+
/* datetime first: datetime subclasses date */
|
|
200
|
+
if (PyObject_TypeCheck(v, (PyTypeObject *)tep_dt_datetime)) {
|
|
201
|
+
teptris_datetime dt;
|
|
202
|
+
memset(&dt, 0, sizeof(dt));
|
|
203
|
+
dt.year = (int32_t)attr_long(v, "year");
|
|
204
|
+
dt.month = (uint8_t)attr_long(v, "month");
|
|
205
|
+
dt.day = (uint8_t)attr_long(v, "day");
|
|
206
|
+
dt.hour = (uint8_t)attr_long(v, "hour");
|
|
207
|
+
dt.minute = (uint8_t)attr_long(v, "minute");
|
|
208
|
+
dt.second = (uint8_t)attr_long(v, "second");
|
|
209
|
+
dt.nanosecond = (uint32_t)attr_long(v, "microsecond") * 1000u;
|
|
210
|
+
PyObject *tz = dt_tzinfo(v);
|
|
211
|
+
if (tz == Py_None) {
|
|
212
|
+
Py_DECREF(tz);
|
|
213
|
+
return dump_check(teptris_builder_put_datetime(
|
|
214
|
+
b, key, klen, TEPTRIS_DATETIME_LOCAL, &dt));
|
|
215
|
+
}
|
|
216
|
+
PyObject *off = PyObject_CallMethod(tz, "utcoffset", "O", v);
|
|
217
|
+
Py_DECREF(tz);
|
|
218
|
+
if (!off) return -1;
|
|
219
|
+
if (off != Py_None) {
|
|
220
|
+
dt.offset_seconds =
|
|
221
|
+
attr_long(off, "days") * 86400 +
|
|
222
|
+
attr_long(off, "seconds");
|
|
223
|
+
}
|
|
224
|
+
Py_DECREF(off);
|
|
225
|
+
return dump_check(teptris_builder_put_datetime(
|
|
226
|
+
b, key, klen, TEPTRIS_DATETIME_OFFSET, &dt));
|
|
227
|
+
}
|
|
228
|
+
if (PyObject_TypeCheck(v, (PyTypeObject *)tep_dt_date)) {
|
|
229
|
+
teptris_datetime dt;
|
|
230
|
+
memset(&dt, 0, sizeof(dt));
|
|
231
|
+
dt.year = (int32_t)attr_long(v, "year");
|
|
232
|
+
dt.month = (uint8_t)attr_long(v, "month");
|
|
233
|
+
dt.day = (uint8_t)attr_long(v, "day");
|
|
234
|
+
return dump_check(teptris_builder_put_datetime(
|
|
235
|
+
b, key, klen, TEPTRIS_DATE_LOCAL, &dt));
|
|
236
|
+
}
|
|
237
|
+
if (PyObject_TypeCheck(v, (PyTypeObject *)tep_dt_time)) {
|
|
238
|
+
teptris_datetime dt;
|
|
239
|
+
memset(&dt, 0, sizeof(dt));
|
|
240
|
+
dt.hour = (uint8_t)attr_long(v, "hour");
|
|
241
|
+
dt.minute = (uint8_t)attr_long(v, "minute");
|
|
242
|
+
dt.second = (uint8_t)attr_long(v, "second");
|
|
243
|
+
dt.nanosecond = (uint32_t)attr_long(v, "microsecond") * 1000u;
|
|
244
|
+
return dump_check(teptris_builder_put_datetime(
|
|
245
|
+
b, key, klen, TEPTRIS_TIME_LOCAL, &dt));
|
|
246
|
+
}
|
|
247
|
+
PyErr_Format(PyExc_TypeError, "cannot dump %.200R",
|
|
248
|
+
(PyObject *)Py_TYPE(v));
|
|
249
|
+
return -1;
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
/* element position: dict -> table element, list -> nested array */
|
|
253
|
+
static int build_value(teptris_builder *b, PyObject *v) {
|
|
254
|
+
if (Py_EnterRecursiveCall(" while dumping a value")) return -1;
|
|
255
|
+
int rc;
|
|
256
|
+
if (PyDict_Check(v)) {
|
|
257
|
+
rc = dump_check(teptris_builder_open_table(b, NULL, 0));
|
|
258
|
+
if (rc == 0) rc = build_table(b, v);
|
|
259
|
+
if (rc == 0) rc = dump_check(teptris_builder_close(b));
|
|
260
|
+
} else if (PyList_Check(v)) {
|
|
261
|
+
rc = dump_check(teptris_builder_open_array(b, NULL, 0));
|
|
262
|
+
Py_ssize_t n = PyList_Size(v);
|
|
263
|
+
for (Py_ssize_t i = 0; rc == 0 && i < n; i++) {
|
|
264
|
+
rc = build_value(b, PyList_GetItem(v, i));
|
|
265
|
+
}
|
|
266
|
+
if (rc == 0) rc = dump_check(teptris_builder_close(b));
|
|
267
|
+
} else {
|
|
268
|
+
rc = put_scalar(b, NULL, 0, v);
|
|
269
|
+
}
|
|
270
|
+
Py_LeaveRecursiveCall();
|
|
271
|
+
return rc;
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
/* table position: keyed members */
|
|
275
|
+
static int build_table(teptris_builder *b, PyObject *obj) {
|
|
276
|
+
if (Py_EnterRecursiveCall(" while dumping a table")) return -1;
|
|
277
|
+
PyObject *k, *v;
|
|
278
|
+
Py_ssize_t pos = 0;
|
|
279
|
+
int rc = 0;
|
|
280
|
+
while (rc == 0 && PyDict_Next(obj, &pos, &k, &v)) {
|
|
281
|
+
if (!PyUnicode_Check(k)) {
|
|
282
|
+
PyErr_SetString(PyExc_TypeError, "keys must be strings");
|
|
283
|
+
rc = -1;
|
|
284
|
+
break;
|
|
285
|
+
}
|
|
286
|
+
PyObject *kb = PyUnicode_AsUTF8String(k);
|
|
287
|
+
if (!kb) { rc = -1; break; }
|
|
288
|
+
const char *ks = PyBytes_AsString(kb);
|
|
289
|
+
size_t klen = (size_t)PyBytes_Size(kb);
|
|
290
|
+
if (PyDict_Check(v)) {
|
|
291
|
+
rc = dump_check(teptris_builder_open_table(b, ks, klen));
|
|
292
|
+
if (rc == 0) rc = build_table(b, v);
|
|
293
|
+
if (rc == 0) rc = dump_check(teptris_builder_close(b));
|
|
294
|
+
} else if (PyList_Check(v)) {
|
|
295
|
+
Py_ssize_t n = PyList_Size(v);
|
|
296
|
+
bool all_dict = n > 0;
|
|
297
|
+
for (Py_ssize_t i = 0; i < n; i++) {
|
|
298
|
+
if (!PyDict_Check(PyList_GetItem(v, i))) {
|
|
299
|
+
all_dict = false;
|
|
300
|
+
break;
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
rc = dump_check(all_dict
|
|
304
|
+
? teptris_builder_open_array(b, ks, klen)
|
|
305
|
+
: teptris_builder_open_inline_array(b, ks, klen));
|
|
306
|
+
for (Py_ssize_t i = 0; rc == 0 && i < n; i++) {
|
|
307
|
+
rc = build_value(b, PyList_GetItem(v, i));
|
|
308
|
+
}
|
|
309
|
+
if (rc == 0) rc = dump_check(teptris_builder_close(b));
|
|
310
|
+
} else {
|
|
311
|
+
rc = put_scalar(b, ks, klen, v);
|
|
312
|
+
}
|
|
313
|
+
Py_DECREF(kb);
|
|
314
|
+
}
|
|
315
|
+
Py_LeaveRecursiveCall();
|
|
316
|
+
return rc;
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
static PyObject *ext_dumps(PyObject *self, PyObject *args) {
|
|
320
|
+
(void)self;
|
|
321
|
+
PyObject *obj;
|
|
322
|
+
if (!PyArg_ParseTuple(args, "O", &obj)) return NULL;
|
|
323
|
+
if (!PyDict_Check(obj)) {
|
|
324
|
+
PyErr_SetString(PyExc_TypeError,
|
|
325
|
+
"dumps() expects a dict at the top level");
|
|
326
|
+
return NULL;
|
|
327
|
+
}
|
|
328
|
+
teptris_builder *b = teptris_builder_new();
|
|
329
|
+
if (!b) return PyErr_NoMemory();
|
|
330
|
+
if (build_table(b, obj) < 0) {
|
|
331
|
+
teptris_builder_free(b);
|
|
332
|
+
return NULL;
|
|
333
|
+
}
|
|
334
|
+
teptris_document *doc = NULL;
|
|
335
|
+
teptris_status st = teptris_builder_finish(b, &doc);
|
|
336
|
+
if (st != TEPTRIS_OK) {
|
|
337
|
+
teptris_builder_free(b);
|
|
338
|
+
PyErr_SetString(PyExc_TypeError, teptris_status_string(st));
|
|
339
|
+
return NULL;
|
|
340
|
+
}
|
|
341
|
+
char *buf = NULL;
|
|
342
|
+
size_t len = 0;
|
|
343
|
+
st = teptris_document_emit(doc, &buf, &len);
|
|
344
|
+
teptris_document_free(doc);
|
|
345
|
+
teptris_builder_free(b); /* finish() transferred (and freed) the doc */
|
|
346
|
+
if (st != TEPTRIS_OK) return PyErr_NoMemory();
|
|
347
|
+
PyObject *out = PyUnicode_DecodeUTF8(buf, (Py_ssize_t)len, "strict");
|
|
348
|
+
free(buf);
|
|
349
|
+
return out;
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
static PyMethodDef methods[] = {
|
|
353
|
+
{"loads", ext_load, METH_VARARGS, "Parse TOML into Python objects."},
|
|
354
|
+
{"dumps", ext_dumps, METH_VARARGS,
|
|
355
|
+
"Serialize a dict tree to canonical TOML via the shared emitter."},
|
|
356
|
+
{NULL, NULL, 0, NULL}
|
|
357
|
+
};
|
|
358
|
+
|
|
359
|
+
static struct PyModuleDef mod = {PyModuleDef_HEAD_INIT, "teptris._native",
|
|
360
|
+
"Native teptris binding (no fallback).", -1, methods};
|
|
361
|
+
|
|
362
|
+
PyMODINIT_FUNC PyInit__native(void) {
|
|
363
|
+
PyObject *m = PyModule_Create(&mod);
|
|
364
|
+
if (!m) return NULL;
|
|
365
|
+
PyObject *dtmod = PyImport_ImportModule("datetime");
|
|
366
|
+
if (dtmod == NULL) { Py_DECREF(m); return NULL; }
|
|
367
|
+
tep_dt_datetime = PyObject_GetAttrString(dtmod, "datetime");
|
|
368
|
+
tep_dt_date = PyObject_GetAttrString(dtmod, "date");
|
|
369
|
+
tep_dt_time = PyObject_GetAttrString(dtmod, "time");
|
|
370
|
+
tep_dt_timedelta = PyObject_GetAttrString(dtmod, "timedelta");
|
|
371
|
+
tep_dt_timezone = PyObject_GetAttrString(dtmod, "timezone");
|
|
372
|
+
Py_DECREF(dtmod);
|
|
373
|
+
if (!tep_dt_datetime || !tep_dt_date || !tep_dt_time ||
|
|
374
|
+
!tep_dt_timedelta || !tep_dt_timezone) {
|
|
375
|
+
Py_DECREF(m);
|
|
376
|
+
return NULL;
|
|
377
|
+
}
|
|
378
|
+
TomlDecodeError = PyErr_NewException("teptris._native.DecodeError", NULL, NULL);
|
|
379
|
+
Py_INCREF(TomlDecodeError);
|
|
380
|
+
PyModule_AddObject(m, "DecodeError", TomlDecodeError);
|
|
381
|
+
return m;
|
|
382
|
+
}
|
|
Binary file
|