cefast 0.1.0__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.
- cefast-0.1.0/.clang-format +26 -0
- cefast-0.1.0/.github/dependabot.yml +7 -0
- cefast-0.1.0/.github/workflows/tests.yml +109 -0
- cefast-0.1.0/.github/workflows/wheels.yml +119 -0
- cefast-0.1.0/.gitignore +12 -0
- cefast-0.1.0/COPYING +21 -0
- cefast-0.1.0/PKG-INFO +219 -0
- cefast-0.1.0/README.md +171 -0
- cefast-0.1.0/cefast/__init__.py +6 -0
- cefast-0.1.0/cefast/_version.py.in +1 -0
- cefast-0.1.0/cefast/meson.build +17 -0
- cefast-0.1.0/docs/accepted-deviations.md +45 -0
- cefast-0.1.0/docs/images/cefast_logo.png +0 -0
- cefast-0.1.0/docs/perf/2026-11-round1-profile.md +197 -0
- cefast-0.1.0/docs/perf/2026-11-round5-parallel.md +165 -0
- cefast-0.1.0/docs/perf/2026-11-round6-small-files.md +133 -0
- cefast-0.1.0/docs/perf/2026-11-round7.md +116 -0
- cefast-0.1.0/docs/superpowers/plans/2026-11-05-cefast-reader.md +240 -0
- cefast-0.1.0/docs/superpowers/plans/2026-11-05-cefast-speed-round2.md +68 -0
- cefast-0.1.0/docs/superpowers/plans/2026-11-05-cefast-speed-round3.md +110 -0
- cefast-0.1.0/docs/superpowers/plans/2026-11-05-cefast-speed.md +91 -0
- cefast-0.1.0/docs/superpowers/plans/2026-11-05-cpp-utils-python.md +122 -0
- cefast-0.1.0/docs/superpowers/plans/2026-11-05-plan-review-opencode.md +97 -0
- cefast-0.1.0/docs/superpowers/plans/2026-11-06-cefast-speed-round4.md +63 -0
- cefast-0.1.0/docs/superpowers/plans/2026-11-06-cefast-speed-round5.md +84 -0
- cefast-0.1.0/docs/superpowers/plans/2026-11-06-cefast-speed-round6.md +69 -0
- cefast-0.1.0/docs/superpowers/plans/2026-11-06-cefast-speed-round7.md +143 -0
- cefast-0.1.0/docs/superpowers/specs/2026-11-05-cef-facts.md +59 -0
- cefast-0.1.0/docs/superpowers/specs/2026-11-05-cefast-design.md +228 -0
- cefast-0.1.0/docs/superpowers/specs/2026-11-05-csa-cef-probe.md +93 -0
- cefast-0.1.0/include/cefast/cefast.hpp +48 -0
- cefast-0.1.0/include/cefast/data/parallel.hpp +214 -0
- cefast-0.1.0/include/cefast/data/records.hpp +871 -0
- cefast-0.1.0/include/cefast/data/structural.hpp +50 -0
- cefast-0.1.0/include/cefast/data/workers.hpp +178 -0
- cefast-0.1.0/include/cefast/errors.hpp +40 -0
- cefast-0.1.0/include/cefast/gzip.hpp +148 -0
- cefast-0.1.0/include/cefast/header/attributes.hpp +17 -0
- cefast-0.1.0/include/cefast/header/lexer.hpp +390 -0
- cefast-0.1.0/include/cefast/header/parser.hpp +669 -0
- cefast-0.1.0/include/cefast/input.hpp +165 -0
- cefast-0.1.0/include/cefast/model.hpp +71 -0
- cefast-0.1.0/include/cefast/time.hpp +263 -0
- cefast-0.1.0/include/cefast/values.hpp +291 -0
- cefast-0.1.0/meson.build +63 -0
- cefast-0.1.0/meson_options.txt +4 -0
- cefast-0.1.0/pycefast/_cefast.cpp +335 -0
- cefast-0.1.0/pycefast/meson.build +9 -0
- cefast-0.1.0/pyproject.toml +53 -0
- cefast-0.1.0/subprojects/catch2.wrap +11 -0
- cefast-0.1.0/subprojects/cpp_utils.wrap +3 -0
- cefast-0.1.0/subprojects/fast_float.wrap +14 -0
- cefast-0.1.0/subprojects/hedley.wrap +12 -0
- cefast-0.1.0/subprojects/pybind11.wrap +13 -0
- cefast-0.1.0/subprojects/zlib.wrap +14 -0
- cefast-0.1.0/tests/cpp/test_corpus.cpp +769 -0
- cefast-0.1.0/tests/cpp/test_header.cpp +562 -0
- cefast-0.1.0/tests/cpp/test_input.cpp +195 -0
- cefast-0.1.0/tests/cpp/test_lexer.cpp +209 -0
- cefast-0.1.0/tests/cpp/test_records.cpp +576 -0
- cefast-0.1.0/tests/cpp/test_time.cpp +93 -0
- cefast-0.1.0/tests/cpp/test_values.cpp +323 -0
- cefast-0.1.0/tests/meson.build +57 -0
- cefast-0.1.0/tests/python/test_cefast.py +255 -0
- cefast-0.1.0/tests/python/test_vs_ceflib.py +259 -0
- cefast-0.1.0/tests/resources/csa_manifest.json +332 -0
- cefast-0.1.0/tests/resources/fixtures/codif-header.cef +524 -0
- cefast-0.1.0/tests/resources/fixtures/hello.cef +5 -0
- cefast-0.1.0/tests/resources/fixtures/hello.cef.gz +0 -0
- cefast-0.1.0/tests/resources/fixtures/include/broken.cef +3 -0
- cefast-0.1.0/tests/resources/fixtures/include/broken.ceh +1 -0
- cefast-0.1.0/tests/resources/fixtures/include/common.ceh +4 -0
- cefast-0.1.0/tests/resources/fixtures/include/cycle-a.cef +3 -0
- cefast-0.1.0/tests/resources/fixtures/include/cycle-b.ceh +1 -0
- cefast-0.1.0/tests/resources/fixtures/include/main.cef +5 -0
- cefast-0.1.0/tests/resources/fixtures/include/nested.ceh +3 -0
- cefast-0.1.0/tests/resources/fixtures/trailing_zeros.cef.gz +0 -0
- cefast-0.1.0/tests/resources/fixtures/truncated.cef.gz +0 -0
- cefast-0.1.0/tests/resources/fixtures/two_members.cef.gz +0 -0
- cefast-0.1.0/tests/resources/fixtures/whi-header.cef +472 -0
- cefast-0.1.0/tests/resources/make_gz.py +24 -0
- cefast-0.1.0/tests/wheel/test_wheel.py +75 -0
- cefast-0.1.0/tools/bench/bench_load.cpp +93 -0
- cefast-0.1.0/tools/bench/meson.build +1 -0
- cefast-0.1.0/tools/ceflib/benchmark.py +103 -0
- cefast-0.1.0/tools/ceflib/build_ceflib.sh +42 -0
- cefast-0.1.0/tools/fetch_csa_corpus.py +273 -0
- cefast-0.1.0/tools/fuzz/cef.dict +56 -0
- cefast-0.1.0/tools/fuzz/compare.hpp +139 -0
- cefast-0.1.0/tools/fuzz/fuzz_float.cpp +139 -0
- cefast-0.1.0/tools/fuzz/fuzz_lexer.cpp +43 -0
- cefast-0.1.0/tools/fuzz/fuzz_lexer_diff.cpp +161 -0
- cefast-0.1.0/tools/fuzz/fuzz_load.cpp +73 -0
- cefast-0.1.0/tools/fuzz/fuzz_records_diff.cpp +172 -0
- cefast-0.1.0/tools/fuzz/fuzz_time.cpp +52 -0
- cefast-0.1.0/tools/fuzz/make_corpus.py +163 -0
- cefast-0.1.0/tools/fuzz/meson.build +19 -0
- cefast-0.1.0/tools/fuzz/reference_lexer.hpp +280 -0
- cefast-0.1.0/tools/fuzz/reference_records.hpp +846 -0
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
---
|
|
2
|
+
# See http://clang.llvm.org/docs/ClangFormatStyleOptions.html for a definition
|
|
3
|
+
# of the options
|
|
4
|
+
Language: Cpp
|
|
5
|
+
BasedOnStyle: WebKit
|
|
6
|
+
|
|
7
|
+
# Line length
|
|
8
|
+
ColumnLimit: 100
|
|
9
|
+
|
|
10
|
+
# Indent with 4 spaces
|
|
11
|
+
IndentWidth: 4
|
|
12
|
+
AccessModifierOffset: -4 # -IndentWidth
|
|
13
|
+
ConstructorInitializerIndentWidth: 8 # 2 * IndentWidth
|
|
14
|
+
|
|
15
|
+
BreakBeforeBraces: Allman
|
|
16
|
+
|
|
17
|
+
AllowShortFunctionsOnASingleLine: Inline
|
|
18
|
+
AlwaysBreakTemplateDeclarations: true
|
|
19
|
+
AlwaysBreakBeforeMultilineStrings: true
|
|
20
|
+
BreakBeforeBinaryOperators: true
|
|
21
|
+
ConstructorInitializerAllOnOneLineOrOnePerLine: true
|
|
22
|
+
IndentCaseLabels: true
|
|
23
|
+
MaxEmptyLinesToKeep: 2
|
|
24
|
+
Standard: Cpp11
|
|
25
|
+
UseTab: Never
|
|
26
|
+
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
name: tests
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [main]
|
|
6
|
+
pull_request:
|
|
7
|
+
|
|
8
|
+
permissions:
|
|
9
|
+
contents: read
|
|
10
|
+
|
|
11
|
+
jobs:
|
|
12
|
+
tests:
|
|
13
|
+
runs-on: ubuntu-latest
|
|
14
|
+
steps:
|
|
15
|
+
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
|
|
16
|
+
- uses: actions/setup-python@6d4fd22b20465ccc0f7354476588e8df7c9ca0e0 # v7
|
|
17
|
+
with:
|
|
18
|
+
python-version: "3.12"
|
|
19
|
+
- name: Install build tools
|
|
20
|
+
run: pip install meson ninja numpy meson-python
|
|
21
|
+
# The CSA sample corpus is not in the repository: it is downloaded from the archive and
|
|
22
|
+
# verified against tests/resources/csa_manifest.json, so the manifest is the cache key.
|
|
23
|
+
- name: Restore the CSA corpus
|
|
24
|
+
uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6
|
|
25
|
+
with:
|
|
26
|
+
path: tests/resources/csa
|
|
27
|
+
key: csa-corpus-${{ hashFiles('tests/resources/csa_manifest.json') }}
|
|
28
|
+
- name: Download the CSA corpus
|
|
29
|
+
run: python -I tools/fetch_csa_corpus.py
|
|
30
|
+
- name: Configure
|
|
31
|
+
run: meson setup build
|
|
32
|
+
- name: Test
|
|
33
|
+
run: meson test -C build --print-errorlogs
|
|
34
|
+
- name: Test log
|
|
35
|
+
if: failure()
|
|
36
|
+
run: cat build/meson-logs/testlog.txt
|
|
37
|
+
|
|
38
|
+
sanitizers:
|
|
39
|
+
runs-on: ubuntu-latest
|
|
40
|
+
env:
|
|
41
|
+
UBSAN_OPTIONS: halt_on_error=1:print_stacktrace=1
|
|
42
|
+
ASAN_OPTIONS: halt_on_error=1:print_summary=1
|
|
43
|
+
steps:
|
|
44
|
+
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
|
|
45
|
+
- uses: actions/setup-python@6d4fd22b20465ccc0f7354476588e8df7c9ca0e0 # v7
|
|
46
|
+
with:
|
|
47
|
+
python-version: "3.12"
|
|
48
|
+
- name: Install build tools
|
|
49
|
+
run: pip install meson ninja
|
|
50
|
+
- name: Restore the CSA corpus
|
|
51
|
+
uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6
|
|
52
|
+
with:
|
|
53
|
+
path: tests/resources/csa
|
|
54
|
+
key: csa-corpus-${{ hashFiles('tests/resources/csa_manifest.json') }}
|
|
55
|
+
- name: Download the CSA corpus
|
|
56
|
+
run: python -I tools/fetch_csa_corpus.py
|
|
57
|
+
- name: Configure
|
|
58
|
+
# No python module: the sanitised extension would need a matching interpreter.
|
|
59
|
+
run: >
|
|
60
|
+
meson setup build-asan -Db_sanitize=address,undefined -Db_lundef=false -Dpython=false
|
|
61
|
+
- name: Test
|
|
62
|
+
run: meson test -C build-asan --print-errorlogs
|
|
63
|
+
- name: Test log
|
|
64
|
+
if: failure()
|
|
65
|
+
run: cat build-asan/meson-logs/testlog.txt
|
|
66
|
+
|
|
67
|
+
fuzz:
|
|
68
|
+
runs-on: ubuntu-latest
|
|
69
|
+
env:
|
|
70
|
+
CC: clang
|
|
71
|
+
CXX: clang++
|
|
72
|
+
steps:
|
|
73
|
+
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
|
|
74
|
+
- uses: actions/setup-python@6d4fd22b20465ccc0f7354476588e8df7c9ca0e0 # v7
|
|
75
|
+
with:
|
|
76
|
+
python-version: "3.12"
|
|
77
|
+
- name: Install build tools
|
|
78
|
+
run: pip install meson ninja
|
|
79
|
+
- name: Install clang
|
|
80
|
+
run: sudo apt-get update && sudo apt-get install -y clang
|
|
81
|
+
- name: Restore the CSA corpus
|
|
82
|
+
uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6
|
|
83
|
+
with:
|
|
84
|
+
path: tests/resources/csa
|
|
85
|
+
key: csa-corpus-${{ hashFiles('tests/resources/csa_manifest.json') }}
|
|
86
|
+
- name: Download the CSA corpus
|
|
87
|
+
run: python -I tools/fetch_csa_corpus.py
|
|
88
|
+
- name: Configure
|
|
89
|
+
run: meson setup build-fuzz -Dpython=false -Dwith_tests=false -Dfuzzing=true -Dbuildtype=debugoptimized
|
|
90
|
+
- name: Compile
|
|
91
|
+
run: ninja -C build-fuzz
|
|
92
|
+
- name: Seeds
|
|
93
|
+
run: python3 tools/fuzz/make_corpus.py --output "$RUNNER_TEMP/corpus"
|
|
94
|
+
# A short run keeps the job cheap: it is a regression gate for the corpus, not a
|
|
95
|
+
# campaign. One process per target so a finding fails the job with its artifact.
|
|
96
|
+
- name: Fuzz
|
|
97
|
+
run: |
|
|
98
|
+
for target in load time float lexer lexer_diff records_diff; do
|
|
99
|
+
max_len=65536
|
|
100
|
+
test "$target" = load && max_len=262144
|
|
101
|
+
mkdir -p "$RUNNER_TEMP/artifacts/$target"
|
|
102
|
+
./build-fuzz/tools/fuzz/fuzz_$target "$RUNNER_TEMP/corpus/$target" \
|
|
103
|
+
-dict=tools/fuzz/cef.dict -max_total_time=60 -max_len=$max_len \
|
|
104
|
+
-rss_limit_mb=2048 \
|
|
105
|
+
-artifact_prefix="$RUNNER_TEMP/artifacts/$target/"
|
|
106
|
+
done
|
|
107
|
+
- name: Fuzz artifacts
|
|
108
|
+
if: failure()
|
|
109
|
+
run: find "$RUNNER_TEMP/artifacts" -type f -exec sh -c 'echo "== $1"; xxd "$1" | head -40' _ {} \;
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
name: Build and test wheels on multiple platforms
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
release:
|
|
5
|
+
types: [published]
|
|
6
|
+
push:
|
|
7
|
+
pull_request:
|
|
8
|
+
|
|
9
|
+
permissions:
|
|
10
|
+
contents: read
|
|
11
|
+
|
|
12
|
+
jobs:
|
|
13
|
+
build_sdist:
|
|
14
|
+
name: Build source distribution
|
|
15
|
+
runs-on: ubuntu-latest
|
|
16
|
+
permissions:
|
|
17
|
+
contents: read
|
|
18
|
+
actions: write
|
|
19
|
+
steps:
|
|
20
|
+
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
|
|
21
|
+
- name: Build sdist
|
|
22
|
+
run: pipx run build --sdist
|
|
23
|
+
- name: Upload artifact
|
|
24
|
+
uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7
|
|
25
|
+
with:
|
|
26
|
+
name: cibw-sdist
|
|
27
|
+
path: dist/*.tar.gz
|
|
28
|
+
|
|
29
|
+
build_wheels:
|
|
30
|
+
name: ${{ matrix.os }} ${{ matrix.CIBW_ARCHS }} wheels
|
|
31
|
+
runs-on: ${{ matrix.os }}
|
|
32
|
+
permissions:
|
|
33
|
+
contents: read
|
|
34
|
+
actions: write
|
|
35
|
+
strategy:
|
|
36
|
+
matrix:
|
|
37
|
+
include:
|
|
38
|
+
- os: ubuntu-latest
|
|
39
|
+
CIBW_ENVIRONMENT: ''
|
|
40
|
+
CIBW_ARCHS: "x86_64"
|
|
41
|
+
CIBW_PLATFORM: "auto"
|
|
42
|
+
- os: ubuntu-latest
|
|
43
|
+
CIBW_ENVIRONMENT: ''
|
|
44
|
+
CIBW_ARCHS: "wasm32"
|
|
45
|
+
CIBW_PLATFORM: "pyodide"
|
|
46
|
+
- os: ubuntu-24.04-arm
|
|
47
|
+
CIBW_ENVIRONMENT: ''
|
|
48
|
+
CIBW_ARCHS: "aarch64"
|
|
49
|
+
CIBW_PLATFORM: "auto"
|
|
50
|
+
- os: windows-latest
|
|
51
|
+
CIBW_ENVIRONMENT: ''
|
|
52
|
+
CIBW_ARCHS: "AMD64"
|
|
53
|
+
CIBW_PLATFORM: "auto"
|
|
54
|
+
- os: windows-11-arm
|
|
55
|
+
CIBW_ENVIRONMENT: ''
|
|
56
|
+
CIBW_ARCHS: "ARM64"
|
|
57
|
+
CIBW_PLATFORM: "auto"
|
|
58
|
+
- os: macos-15-intel
|
|
59
|
+
CIBW_ENVIRONMENT: >
|
|
60
|
+
MACOSX_DEPLOYMENT_TARGET='13.0'
|
|
61
|
+
CIBW_ARCHS: "x86_64"
|
|
62
|
+
CIBW_PLATFORM: "auto"
|
|
63
|
+
- os: macos-latest # Apple Silicon
|
|
64
|
+
CIBW_ENVIRONMENT: >
|
|
65
|
+
MACOSX_DEPLOYMENT_TARGET='13.0'
|
|
66
|
+
CIBW_ARCHS: "arm64"
|
|
67
|
+
CIBW_PLATFORM: "auto"
|
|
68
|
+
env:
|
|
69
|
+
CIBW_ENVIRONMENT: ${{ matrix.CIBW_ENVIRONMENT }}
|
|
70
|
+
# Windows on ARM has native CPython builds from 3.11 only
|
|
71
|
+
CIBW_SKIP: "*-win32 *i686 cp310-win_arm64"
|
|
72
|
+
CIBW_ARCHS: ${{ matrix.CIBW_ARCHS }}
|
|
73
|
+
CIBW_PLATFORM: ${{ matrix.CIBW_PLATFORM }}
|
|
74
|
+
CIBW_ENVIRONMENT_LINUX: PATH=/usr/local/bin:/usr/lib/ccache:$PATH CCACHE_DIR=/host${{ github.workspace }}/.ccache CCACHE_CONFIGPATH=/host/home/runner/.config/ccache/ccache.conf
|
|
75
|
+
# see https://github.com/matplotlib/matplotlib/pull/28687/files#diff-504e739c530d50b780e9a09ae8fd0c3ea8258ea7553be06afb06f03f95b1ee0aR103
|
|
76
|
+
CIBW_CONFIG_SETTINGS_WINDOWS: >-
|
|
77
|
+
setup-args="--vsenv"
|
|
78
|
+
setup-args="-Db_vscrt=mt"
|
|
79
|
+
setup-args="-Dcpp_link_args=['ucrt.lib','vcruntime.lib','/nodefaultlib:libucrt.lib','/nodefaultlib:libvcruntime.lib']"
|
|
80
|
+
steps:
|
|
81
|
+
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
|
|
82
|
+
- name: ccache
|
|
83
|
+
uses: hendrikmuhs/ccache-action@5ebbd400eff9e74630f759d94ddd7b6c26299639 # v1.2
|
|
84
|
+
with:
|
|
85
|
+
key: ${{ github.job }}-${{ matrix.os }}-${{ matrix.CIBW_ARCHS }}
|
|
86
|
+
max-size: "5G"
|
|
87
|
+
# Each wheel runs tests/wheel/test_wheel.py in its own environment (see
|
|
88
|
+
# [tool.cibuildwheel] in pyproject.toml): import cefast, load a small inline CEF file from
|
|
89
|
+
# bytes, gzip bytes and a path. The CSA corpus is never needed.
|
|
90
|
+
- name: Build wheels
|
|
91
|
+
uses: pypa/cibuildwheel@f9b966cfadf4109556cadc5bb39ddb72d0ce664e # v4.3.5
|
|
92
|
+
- uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7
|
|
93
|
+
with:
|
|
94
|
+
name: cibw-wheels-${{ matrix.os }}-${{ matrix.CIBW_ARCHS }}
|
|
95
|
+
path: ./wheelhouse/*.whl
|
|
96
|
+
|
|
97
|
+
upload_pypi:
|
|
98
|
+
needs: [build_sdist, build_wheels]
|
|
99
|
+
runs-on: ubuntu-latest
|
|
100
|
+
# upload to PyPI only on github releases
|
|
101
|
+
environment:
|
|
102
|
+
name: pypi
|
|
103
|
+
url: https://pypi.org/p/cefast
|
|
104
|
+
permissions:
|
|
105
|
+
# id-token is what trusted publishing uses; the two reads are all this job needs.
|
|
106
|
+
id-token: write
|
|
107
|
+
contents: read
|
|
108
|
+
actions: read
|
|
109
|
+
if: github.event_name == 'release' && github.event.action == 'published' && github.repository_owner == 'SciQLop'
|
|
110
|
+
steps:
|
|
111
|
+
- uses: actions/download-artifact@9000827ccba6bdab643e8b6fd33ac0654aef8333 # v8
|
|
112
|
+
with:
|
|
113
|
+
pattern: cibw-*
|
|
114
|
+
path: dist
|
|
115
|
+
merge-multiple: true
|
|
116
|
+
- name: Publish to PyPI
|
|
117
|
+
uses: pypa/gh-action-pypi-publish@7245906fb61fa33a13554f9d5fb856fe61758086 # release/v1
|
|
118
|
+
with:
|
|
119
|
+
skip-existing: true
|
cefast-0.1.0/.gitignore
ADDED
cefast-0.1.0/COPYING
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Laboratory of Plasma Physics, Ecole Polytechnique, CNRS, Sorbonne Université, Université Paris-Saclay
|
|
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.
|
cefast-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
Metadata-Version: 2.1
|
|
2
|
+
Name: cefast
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Fast reader for ESA's Cluster Exchange Format (CEF 2.0)
|
|
5
|
+
Author-Email: Alexis Jeandet <alexis.jeandet@member.fsf.org>
|
|
6
|
+
License: MIT License
|
|
7
|
+
|
|
8
|
+
Copyright (c) 2026 Laboratory of Plasma Physics, Ecole Polytechnique, CNRS, Sorbonne Université, Université Paris-Saclay
|
|
9
|
+
|
|
10
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
11
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
12
|
+
in the Software without restriction, including without limitation the rights
|
|
13
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
14
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
15
|
+
furnished to do so, subject to the following conditions:
|
|
16
|
+
|
|
17
|
+
The above copyright notice and this permission notice shall be included in all
|
|
18
|
+
copies or substantial portions of the Software.
|
|
19
|
+
|
|
20
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
21
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
22
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
23
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
24
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
25
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
26
|
+
SOFTWARE.
|
|
27
|
+
|
|
28
|
+
Classifier: Development Status :: 4 - Beta
|
|
29
|
+
Classifier: Intended Audience :: Science/Research
|
|
30
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
31
|
+
Classifier: Natural Language :: English
|
|
32
|
+
Classifier: Operating System :: OS Independent
|
|
33
|
+
Classifier: Programming Language :: C++
|
|
34
|
+
Classifier: Programming Language :: Python :: 3
|
|
35
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
36
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
37
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
38
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
39
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
40
|
+
Classifier: Programming Language :: Python :: 3.15
|
|
41
|
+
Classifier: Topic :: Scientific/Engineering :: Astronomy
|
|
42
|
+
Classifier: Topic :: Scientific/Engineering :: Physics
|
|
43
|
+
Project-URL: homepage, https://github.com/SciQLop/CEFast
|
|
44
|
+
Project-URL: repository, https://github.com/SciQLop/CEFast
|
|
45
|
+
Requires-Python: >=3.10
|
|
46
|
+
Requires-Dist: numpy
|
|
47
|
+
Description-Content-Type: text/markdown
|
|
48
|
+
|
|
49
|
+
[](https://github.com/SciQLop/CEFast/actions/workflows/tests.yml)
|
|
50
|
+
[](https://github.com/SciQLop/CEFast/actions/workflows/wheels.yml)
|
|
51
|
+
[](https://pypi.org/project/cefast/)
|
|
52
|
+
[](https://pypi.org/project/cefast/)
|
|
53
|
+
[](COPYING)
|
|
54
|
+
|
|
55
|
+
<p align="center"><img src="docs/images/cefast_logo.png" alt="CEFast" width="600"></p>
|
|
56
|
+
|
|
57
|
+
# CEFast
|
|
58
|
+
|
|
59
|
+
A fast reader for CEF (Cluster Exchange Format), the text format of ESA's Cluster Science Archive.
|
|
60
|
+
C++20 core, Python bindings, no dependencies beyond numpy.
|
|
61
|
+
|
|
62
|
+
```sh
|
|
63
|
+
pip install cefast
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
```python
|
|
67
|
+
import cefast
|
|
68
|
+
f = cefast.load("C1_CP_FGM_SPIN__20010201_000000_20010202_000000_V140305.cef.gz")
|
|
69
|
+
f["B_vec_xyz_gse__C1_CP_FGM_SPIN"].values # (records, 3) float32
|
|
70
|
+
f["time_tags__C1_CP_FGM_SPIN"].values.dtype # datetime64[ns]
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
## Why CEFast
|
|
74
|
+
|
|
75
|
+
CEFLIB is the reference CEF reader, and it is hard to live with: source tarball only, no wheels,
|
|
76
|
+
a global singleton, and no working byte variables. CEFast is
|
|
77
|
+
installable with pip, free of global state, and readable by someone else.
|
|
78
|
+
|
|
79
|
+
Correctness is checked, not claimed:
|
|
80
|
+
|
|
81
|
+
- every CEF 2.0 construct the archive uses, checked against the specification, with the deviations
|
|
82
|
+
we accept listed in [docs/accepted-deviations.md](docs/accepted-deviations.md);
|
|
83
|
+
- 41 real CSA files, every one CEFLIB can read compared value by value against CEFLIB 1.8.6,
|
|
84
|
+
plus hand-made files for malformed input;
|
|
85
|
+
- the full suite runs under ASan and UBSan in CI, together with an allocation budget test for the
|
|
86
|
+
header path;
|
|
87
|
+
- libFuzzer targets run in CI, each comparing a fast path with a slow reference: serial against
|
|
88
|
+
parallel loading, the float parser against `strtod`, the fixed-width time parser against the
|
|
89
|
+
general one, the header lexer and the record scanner against frozen copies of the simpler code
|
|
90
|
+
they replaced;
|
|
91
|
+
- a malformed file raises a `ParseError` naming the file, the line and the record: it never returns
|
|
92
|
+
shifted columns or garbage.
|
|
93
|
+
|
|
94
|
+
## Install
|
|
95
|
+
|
|
96
|
+
```sh
|
|
97
|
+
pip install cefast
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
Wheels are built for Linux (x86_64, aarch64), macOS 13+ (x86_64, arm64) and Windows (x64,
|
|
101
|
+
ARM64), for CPython 3.10 and newer, including free-threaded builds. A Pyodide (wasm32) wheel is
|
|
102
|
+
built too. Elsewhere, `pip install` builds from the sdist with a C++20 compiler and meson.
|
|
103
|
+
|
|
104
|
+
## Loading
|
|
105
|
+
|
|
106
|
+
```python
|
|
107
|
+
cefast.load("file.cef") # path or os.PathLike
|
|
108
|
+
cefast.load("file.cef.gz") # gzip, detected from the content
|
|
109
|
+
cefast.load(data) # bytes, bytearray or memoryview
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
`threads=0` (the default) picks a thread count from the machine, `threads=1` parses serially,
|
|
113
|
+
`threads=N` uses at most N. Data sections above ~48 KiB are split across threads; the result is
|
|
114
|
+
identical either way.
|
|
115
|
+
|
|
116
|
+
Loading releases the GIL. A path is memory-mapped, or read when the platform has no mmap, and bytes
|
|
117
|
+
are borrowed: the returned arrays are views on that memory, valid as long as the `CEF` object and
|
|
118
|
+
its variables live.
|
|
119
|
+
|
|
120
|
+
## Variables
|
|
121
|
+
|
|
122
|
+
```python
|
|
123
|
+
f.keys() # names, in file order
|
|
124
|
+
"B_vec_xyz_gse__C1_CP_FGM_SPIN" in f
|
|
125
|
+
v = f["B_vec_xyz_gse__C1_CP_FGM_SPIN"]
|
|
126
|
+
v.name, v.value_type # 'B_vec_xyz_gse__C1_CP_FGM_SPIN', 'FLOAT'
|
|
127
|
+
v.sizes # (3,), the CEF SIZES attribute
|
|
128
|
+
v.is_nrv # True when the values are in the header (DATA), not per record
|
|
129
|
+
v.attributes # dict of the other variable attributes
|
|
130
|
+
f.attributes # dict of the global attributes
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
Attribute values are plain Python values: text and numbers come as one value or a list, times as
|
|
134
|
+
`datetime.datetime`, time ranges as a pair of them.
|
|
135
|
+
|
|
136
|
+
Values are numpy arrays. Record-varying variables get a record axis, header variables keep their
|
|
137
|
+
declared shape.
|
|
138
|
+
|
|
139
|
+
| CEF VALUE_TYPE | numpy dtype |
|
|
140
|
+
| --- | --- |
|
|
141
|
+
| FLOAT | float32 |
|
|
142
|
+
| DOUBLE | float64 |
|
|
143
|
+
| INT | int32 |
|
|
144
|
+
| BYTE | uint8 |
|
|
145
|
+
| CHAR | numpy unicode string, copied |
|
|
146
|
+
| ISO_TIME | datetime64[ns] |
|
|
147
|
+
| ISO_TIME_RANGE | datetime64[ns], one extra dimension of 2 |
|
|
148
|
+
|
|
149
|
+
Missing data stays raw for numeric variables (the `FILLVAL` number itself) and becomes `NaT` for
|
|
150
|
+
time variables.
|
|
151
|
+
|
|
152
|
+
## INCLUDE
|
|
153
|
+
|
|
154
|
+
`INCLUDE = "file.ceh"` is resolved, in this order, with `include_resolver` first when given:
|
|
155
|
+
|
|
156
|
+
```python
|
|
157
|
+
cefast.load("file.cef", include_paths=["/data/headers"])
|
|
158
|
+
cefast.load("file.cef", include_resolver=lambda name: headers.get(name)) # str or None
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
then the directory of the file being read, then `include_paths`, then the `CEFPATH` environment
|
|
162
|
+
variable, then the current directory (which is what CEFLIB does).
|
|
163
|
+
|
|
164
|
+
## Errors
|
|
165
|
+
|
|
166
|
+
Anything that would silently produce wrong numbers raises instead:
|
|
167
|
+
|
|
168
|
+
```python
|
|
169
|
+
try:
|
|
170
|
+
cefast.load("broken.cef")
|
|
171
|
+
except cefast.ParseError as error:
|
|
172
|
+
print(error) # broken.cef:214: expected 31 values, got 30 (record 12)
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
`ParseError` subclasses `ValueError` and names the file, the line and, inside the data section, the
|
|
176
|
+
record.
|
|
177
|
+
|
|
178
|
+
## C++
|
|
179
|
+
|
|
180
|
+
Header-only C++20, same API shape:
|
|
181
|
+
|
|
182
|
+
```cpp
|
|
183
|
+
#include <cefast/cefast.hpp>
|
|
184
|
+
|
|
185
|
+
auto file = cefast::load("file.cef");
|
|
186
|
+
for (const auto& var : file.variables)
|
|
187
|
+
std::cout << var.name << '\n';
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
With meson: `dependency('cefast', fallback: ['cefast', 'cefast_dep'])`.
|
|
191
|
+
|
|
192
|
+
## Performance
|
|
193
|
+
|
|
194
|
+
On the 41-file CSA sample corpus (10.4 MB), against CEFLIB 1.8.6, loading every file from Python
|
|
195
|
+
with the default threads: **11.5 ms against 35.1 ms** (3.05x overall, median 1.60x per file).
|
|
196
|
+
Every file is faster, from 1.20x to 7x. Numbers from a Ryzen 7 5800X; gzipped files are limited by
|
|
197
|
+
decompression. `tools/ceflib/benchmark.py` reproduces them.
|
|
198
|
+
|
|
199
|
+
Speed does not cost correctness: every float is correctly rounded (the value `strtod` gives), and a
|
|
200
|
+
malformed value is an error, where CEFLIB reads `12abc` as `12`.
|
|
201
|
+
|
|
202
|
+
## Tests
|
|
203
|
+
|
|
204
|
+
```sh
|
|
205
|
+
python -I tools/fetch_csa_corpus.py # downloads the CSA sample corpus and verifies its hashes
|
|
206
|
+
meson setup build && meson test -C build
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
The corpus is not in the repository: it is fetched from the archive and checked against
|
|
210
|
+
`tests/resources/csa_manifest.json`. Tests that need it skip when it is missing.
|
|
211
|
+
`tools/ceflib/benchmark.py` builds CEFLIB to compare against it; the differential test skips when
|
|
212
|
+
CEFLIB is not importable.
|
|
213
|
+
|
|
214
|
+
Known differences from CEFLIB and from the strict CEF 2.0 document are listed in
|
|
215
|
+
[docs/accepted-deviations.md](docs/accepted-deviations.md).
|
|
216
|
+
|
|
217
|
+
## License
|
|
218
|
+
|
|
219
|
+
MIT, see [COPYING](COPYING).
|
cefast-0.1.0/README.md
ADDED
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
[](https://github.com/SciQLop/CEFast/actions/workflows/tests.yml)
|
|
2
|
+
[](https://github.com/SciQLop/CEFast/actions/workflows/wheels.yml)
|
|
3
|
+
[](https://pypi.org/project/cefast/)
|
|
4
|
+
[](https://pypi.org/project/cefast/)
|
|
5
|
+
[](COPYING)
|
|
6
|
+
|
|
7
|
+
<p align="center"><img src="docs/images/cefast_logo.png" alt="CEFast" width="600"></p>
|
|
8
|
+
|
|
9
|
+
# CEFast
|
|
10
|
+
|
|
11
|
+
A fast reader for CEF (Cluster Exchange Format), the text format of ESA's Cluster Science Archive.
|
|
12
|
+
C++20 core, Python bindings, no dependencies beyond numpy.
|
|
13
|
+
|
|
14
|
+
```sh
|
|
15
|
+
pip install cefast
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
```python
|
|
19
|
+
import cefast
|
|
20
|
+
f = cefast.load("C1_CP_FGM_SPIN__20010201_000000_20010202_000000_V140305.cef.gz")
|
|
21
|
+
f["B_vec_xyz_gse__C1_CP_FGM_SPIN"].values # (records, 3) float32
|
|
22
|
+
f["time_tags__C1_CP_FGM_SPIN"].values.dtype # datetime64[ns]
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
## Why CEFast
|
|
26
|
+
|
|
27
|
+
CEFLIB is the reference CEF reader, and it is hard to live with: source tarball only, no wheels,
|
|
28
|
+
a global singleton, and no working byte variables. CEFast is
|
|
29
|
+
installable with pip, free of global state, and readable by someone else.
|
|
30
|
+
|
|
31
|
+
Correctness is checked, not claimed:
|
|
32
|
+
|
|
33
|
+
- every CEF 2.0 construct the archive uses, checked against the specification, with the deviations
|
|
34
|
+
we accept listed in [docs/accepted-deviations.md](docs/accepted-deviations.md);
|
|
35
|
+
- 41 real CSA files, every one CEFLIB can read compared value by value against CEFLIB 1.8.6,
|
|
36
|
+
plus hand-made files for malformed input;
|
|
37
|
+
- the full suite runs under ASan and UBSan in CI, together with an allocation budget test for the
|
|
38
|
+
header path;
|
|
39
|
+
- libFuzzer targets run in CI, each comparing a fast path with a slow reference: serial against
|
|
40
|
+
parallel loading, the float parser against `strtod`, the fixed-width time parser against the
|
|
41
|
+
general one, the header lexer and the record scanner against frozen copies of the simpler code
|
|
42
|
+
they replaced;
|
|
43
|
+
- a malformed file raises a `ParseError` naming the file, the line and the record: it never returns
|
|
44
|
+
shifted columns or garbage.
|
|
45
|
+
|
|
46
|
+
## Install
|
|
47
|
+
|
|
48
|
+
```sh
|
|
49
|
+
pip install cefast
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
Wheels are built for Linux (x86_64, aarch64), macOS 13+ (x86_64, arm64) and Windows (x64,
|
|
53
|
+
ARM64), for CPython 3.10 and newer, including free-threaded builds. A Pyodide (wasm32) wheel is
|
|
54
|
+
built too. Elsewhere, `pip install` builds from the sdist with a C++20 compiler and meson.
|
|
55
|
+
|
|
56
|
+
## Loading
|
|
57
|
+
|
|
58
|
+
```python
|
|
59
|
+
cefast.load("file.cef") # path or os.PathLike
|
|
60
|
+
cefast.load("file.cef.gz") # gzip, detected from the content
|
|
61
|
+
cefast.load(data) # bytes, bytearray or memoryview
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
`threads=0` (the default) picks a thread count from the machine, `threads=1` parses serially,
|
|
65
|
+
`threads=N` uses at most N. Data sections above ~48 KiB are split across threads; the result is
|
|
66
|
+
identical either way.
|
|
67
|
+
|
|
68
|
+
Loading releases the GIL. A path is memory-mapped, or read when the platform has no mmap, and bytes
|
|
69
|
+
are borrowed: the returned arrays are views on that memory, valid as long as the `CEF` object and
|
|
70
|
+
its variables live.
|
|
71
|
+
|
|
72
|
+
## Variables
|
|
73
|
+
|
|
74
|
+
```python
|
|
75
|
+
f.keys() # names, in file order
|
|
76
|
+
"B_vec_xyz_gse__C1_CP_FGM_SPIN" in f
|
|
77
|
+
v = f["B_vec_xyz_gse__C1_CP_FGM_SPIN"]
|
|
78
|
+
v.name, v.value_type # 'B_vec_xyz_gse__C1_CP_FGM_SPIN', 'FLOAT'
|
|
79
|
+
v.sizes # (3,), the CEF SIZES attribute
|
|
80
|
+
v.is_nrv # True when the values are in the header (DATA), not per record
|
|
81
|
+
v.attributes # dict of the other variable attributes
|
|
82
|
+
f.attributes # dict of the global attributes
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
Attribute values are plain Python values: text and numbers come as one value or a list, times as
|
|
86
|
+
`datetime.datetime`, time ranges as a pair of them.
|
|
87
|
+
|
|
88
|
+
Values are numpy arrays. Record-varying variables get a record axis, header variables keep their
|
|
89
|
+
declared shape.
|
|
90
|
+
|
|
91
|
+
| CEF VALUE_TYPE | numpy dtype |
|
|
92
|
+
| --- | --- |
|
|
93
|
+
| FLOAT | float32 |
|
|
94
|
+
| DOUBLE | float64 |
|
|
95
|
+
| INT | int32 |
|
|
96
|
+
| BYTE | uint8 |
|
|
97
|
+
| CHAR | numpy unicode string, copied |
|
|
98
|
+
| ISO_TIME | datetime64[ns] |
|
|
99
|
+
| ISO_TIME_RANGE | datetime64[ns], one extra dimension of 2 |
|
|
100
|
+
|
|
101
|
+
Missing data stays raw for numeric variables (the `FILLVAL` number itself) and becomes `NaT` for
|
|
102
|
+
time variables.
|
|
103
|
+
|
|
104
|
+
## INCLUDE
|
|
105
|
+
|
|
106
|
+
`INCLUDE = "file.ceh"` is resolved, in this order, with `include_resolver` first when given:
|
|
107
|
+
|
|
108
|
+
```python
|
|
109
|
+
cefast.load("file.cef", include_paths=["/data/headers"])
|
|
110
|
+
cefast.load("file.cef", include_resolver=lambda name: headers.get(name)) # str or None
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
then the directory of the file being read, then `include_paths`, then the `CEFPATH` environment
|
|
114
|
+
variable, then the current directory (which is what CEFLIB does).
|
|
115
|
+
|
|
116
|
+
## Errors
|
|
117
|
+
|
|
118
|
+
Anything that would silently produce wrong numbers raises instead:
|
|
119
|
+
|
|
120
|
+
```python
|
|
121
|
+
try:
|
|
122
|
+
cefast.load("broken.cef")
|
|
123
|
+
except cefast.ParseError as error:
|
|
124
|
+
print(error) # broken.cef:214: expected 31 values, got 30 (record 12)
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
`ParseError` subclasses `ValueError` and names the file, the line and, inside the data section, the
|
|
128
|
+
record.
|
|
129
|
+
|
|
130
|
+
## C++
|
|
131
|
+
|
|
132
|
+
Header-only C++20, same API shape:
|
|
133
|
+
|
|
134
|
+
```cpp
|
|
135
|
+
#include <cefast/cefast.hpp>
|
|
136
|
+
|
|
137
|
+
auto file = cefast::load("file.cef");
|
|
138
|
+
for (const auto& var : file.variables)
|
|
139
|
+
std::cout << var.name << '\n';
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
With meson: `dependency('cefast', fallback: ['cefast', 'cefast_dep'])`.
|
|
143
|
+
|
|
144
|
+
## Performance
|
|
145
|
+
|
|
146
|
+
On the 41-file CSA sample corpus (10.4 MB), against CEFLIB 1.8.6, loading every file from Python
|
|
147
|
+
with the default threads: **11.5 ms against 35.1 ms** (3.05x overall, median 1.60x per file).
|
|
148
|
+
Every file is faster, from 1.20x to 7x. Numbers from a Ryzen 7 5800X; gzipped files are limited by
|
|
149
|
+
decompression. `tools/ceflib/benchmark.py` reproduces them.
|
|
150
|
+
|
|
151
|
+
Speed does not cost correctness: every float is correctly rounded (the value `strtod` gives), and a
|
|
152
|
+
malformed value is an error, where CEFLIB reads `12abc` as `12`.
|
|
153
|
+
|
|
154
|
+
## Tests
|
|
155
|
+
|
|
156
|
+
```sh
|
|
157
|
+
python -I tools/fetch_csa_corpus.py # downloads the CSA sample corpus and verifies its hashes
|
|
158
|
+
meson setup build && meson test -C build
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
The corpus is not in the repository: it is fetched from the archive and checked against
|
|
162
|
+
`tests/resources/csa_manifest.json`. Tests that need it skip when it is missing.
|
|
163
|
+
`tools/ceflib/benchmark.py` builds CEFLIB to compare against it; the differential test skips when
|
|
164
|
+
CEFLIB is not importable.
|
|
165
|
+
|
|
166
|
+
Known differences from CEFLIB and from the strict CEF 2.0 document are listed in
|
|
167
|
+
[docs/accepted-deviations.md](docs/accepted-deviations.md).
|
|
168
|
+
|
|
169
|
+
## License
|
|
170
|
+
|
|
171
|
+
MIT, see [COPYING](COPYING).
|