capkit 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.
@@ -0,0 +1,63 @@
1
+ name: CI
2
+
3
+ on:
4
+ push:
5
+ branches: ["main", "master"]
6
+ pull_request:
7
+ branches: ["main", "master"]
8
+
9
+ jobs:
10
+ test:
11
+ name: "Test (Python ${{ matrix.python-version }})"
12
+ runs-on: ubuntu-latest
13
+ strategy:
14
+ fail-fast: false
15
+ matrix:
16
+ python-version: ["3.11", "3.12", "3.13", "3.14"]
17
+
18
+ steps:
19
+ - uses: actions/checkout@v4
20
+
21
+ - name: Set up Python ${{ matrix.python-version }}
22
+ uses: actions/setup-python@v5
23
+ with:
24
+ python-version: ${{ matrix.python-version }}
25
+
26
+ - name: Install dependencies
27
+ run: python -m pip install -e ".[dev]"
28
+
29
+ - name: Lint
30
+ run: ruff check .
31
+
32
+ - name: Type-check
33
+ run: mypy capkit
34
+
35
+ - name: Test and coverage gate
36
+ run: python -m pytest --cov=capkit --cov-fail-under=90 -q
37
+
38
+ build:
39
+ name: Build check
40
+ runs-on: ubuntu-latest
41
+ needs: test
42
+
43
+ steps:
44
+ - uses: actions/checkout@v4
45
+ - uses: actions/setup-python@v5
46
+ with:
47
+ python-version: "3.11"
48
+ - name: Install build frontend
49
+ run: python -m pip install build
50
+ - name: Build sdist and wheel
51
+ run: python -m build
52
+ - name: Verify wheel metadata
53
+ run: >-
54
+ python -c "from pathlib import Path;
55
+ import zipfile;
56
+ wheel=next(Path('dist').glob('*.whl'));
57
+ archive=zipfile.ZipFile(wheel);
58
+ names=archive.namelist();
59
+ assert 'capkit/py.typed' in names;
60
+ entry=next(name for name in names if name.endswith('entry_points.txt'));
61
+ text=archive.read(entry).decode();
62
+ assert '[dbckit.readers]' in text;
63
+ assert 'txt = capkit.integration:DispatchReader' in text"
@@ -0,0 +1,38 @@
1
+ name: Release
2
+
3
+ on:
4
+ push:
5
+ tags: ["v*"]
6
+
7
+ jobs:
8
+ build:
9
+ name: "Build distributions"
10
+ runs-on: ubuntu-latest
11
+ steps:
12
+ - uses: actions/checkout@v4
13
+ - uses: actions/setup-python@v5
14
+ with:
15
+ python-version: "3.12"
16
+ - name: Install build
17
+ run: pip install build
18
+ - name: Build sdist and wheel
19
+ run: python -m build
20
+ - uses: actions/upload-artifact@v4
21
+ with:
22
+ name: dist
23
+ path: dist/
24
+
25
+ publish:
26
+ name: "Publish to PyPI"
27
+ runs-on: ubuntu-latest
28
+ needs: build
29
+ environment: pypi
30
+ permissions:
31
+ id-token: write
32
+ steps:
33
+ - uses: actions/download-artifact@v4
34
+ with:
35
+ name: dist
36
+ path: dist/
37
+ - name: Publish (trusted publishing / OIDC)
38
+ uses: pypa/gh-action-pypi-publish@release/v1
@@ -0,0 +1,29 @@
1
+ .DS_Store
2
+
3
+ .claude/
4
+ .venv/
5
+ __pycache__/
6
+ *.py[cod]
7
+
8
+ .env
9
+ .env.*
10
+ !.env.example
11
+
12
+ .tox/
13
+ .nox/
14
+ .hypothesis/
15
+
16
+ .pytest_cache/
17
+ .coverage
18
+ coverage.xml
19
+ junit.xml
20
+ htmlcov/
21
+
22
+ .mypy_cache/
23
+ .ruff_cache/
24
+
25
+ dist/
26
+ build/
27
+ *.egg-info/
28
+
29
+ DEFINITION.md
@@ -0,0 +1,27 @@
1
+ # Changelog
2
+
3
+ All notable changes to capkit are documented here.
4
+
5
+ Format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
6
+ Versioning will follow [Semantic Versioning](https://semver.org/) from 1.0.0 onward.
7
+
8
+ ---
9
+
10
+ ## [Unreleased]
11
+
12
+ ## [0.1.0] — 2026-07-16
13
+
14
+ Initial public release.
15
+
16
+ ### Added
17
+
18
+ - Frozen, slotted `Frame` and `LogMeta` models; no runtime dependencies.
19
+ - Lazy `read()`, header-only `probe()`, and `available_formats()`.
20
+ - Public `register_reader()` for validated, process-wide custom reader classes.
21
+ - Kvaser CanKing TXT reader (`kvaser-txt`) with noise-skipping and strict modes,
22
+ pinned by an anonymized excerpt of a real capture.
23
+ - Generic `.txt` dispatch through the `dbckit.readers` entry-point group.
24
+ - `py.typed` typing metadata.
25
+ - CI on Python 3.11–3.14: pytest with coverage ≥ 90%, ruff, mypy, and a wheel
26
+ metadata build check.
27
+ - Tag-driven release workflow publishing to PyPI with trusted publishing (OIDC).
capkit-0.1.0/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 André Delgado
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.
capkit-0.1.0/PKG-INFO ADDED
@@ -0,0 +1,240 @@
1
+ Metadata-Version: 2.4
2
+ Name: capkit
3
+ Version: 0.1.0
4
+ Summary: Read CAN bus capture logs in different formats into one common frame stream
5
+ Project-URL: Homepage, https://canforge.io/capkit
6
+ Project-URL: Repository, https://github.com/canforge/capkit
7
+ Project-URL: Changelog, https://github.com/canforge/capkit/blob/main/CHANGELOG.md
8
+ Author-email: André Delgado <andre@adelgado.io>
9
+ License: MIT License
10
+
11
+ Copyright (c) 2026 André Delgado
12
+
13
+ Permission is hereby granted, free of charge, to any person obtaining a copy
14
+ of this software and associated documentation files (the "Software"), to deal
15
+ in the Software without restriction, including without limitation the rights
16
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
17
+ copies of the Software, and to permit persons to whom the Software is
18
+ furnished to do so, subject to the following conditions:
19
+
20
+ The above copyright notice and this permission notice shall be included in all
21
+ copies or substantial portions of the Software.
22
+
23
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
24
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
25
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
26
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
27
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
28
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
29
+ SOFTWARE.
30
+ License-File: LICENSE
31
+ Keywords: CAN,automotive,canbus,kvaser,log
32
+ Classifier: Development Status :: 3 - Alpha
33
+ Classifier: Intended Audience :: Developers
34
+ Classifier: License :: OSI Approved :: MIT License
35
+ Classifier: Programming Language :: Python :: 3
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
+ Requires-Python: >=3.11
41
+ Provides-Extra: dev
42
+ Requires-Dist: build; extra == 'dev'
43
+ Requires-Dist: dbckit>=1.0; extra == 'dev'
44
+ Requires-Dist: mypy; extra == 'dev'
45
+ Requires-Dist: pytest-cov; extra == 'dev'
46
+ Requires-Dist: pytest>=8.0; extra == 'dev'
47
+ Requires-Dist: ruff; extra == 'dev'
48
+ Description-Content-Type: text/markdown
49
+
50
+ # capkit
51
+
52
+ [![PyPI](https://img.shields.io/pypi/v/capkit)](https://pypi.org/project/capkit/)
53
+ [![CI](https://github.com/canforge/capkit/actions/workflows/ci.yml/badge.svg)](https://github.com/canforge/capkit/actions/workflows/ci.yml)
54
+ [![Python versions](https://img.shields.io/pypi/pyversions/capkit)](https://pypi.org/project/capkit/)
55
+ [![License: MIT](https://img.shields.io/pypi/l/capkit)](LICENSE)
56
+
57
+ `capkit` is a Python library that reads **CAN bus capture logs into one common
58
+ frame stream**. Every supported format parses into the same frozen `Frame`
59
+ dataclass, so code that consumes frames never depends on which tool captured
60
+ the log.
61
+
62
+ Use it to:
63
+
64
+ - read captures from different tools as one lazy stream of typed `Frame` objects
65
+ - probe a file for header metadata without scanning the frame body
66
+ - detect the log format from the file extension or the file content
67
+ - skip real-world log noise by default, or reject it with `strict=True`
68
+ - feed frames into [dbckit](https://github.com/canforge/dbckit) for DBC signal decoding
69
+
70
+ | Format | Reader name | Extensions | Status | Dependency |
71
+ |---|---|---|---|---|
72
+ | Kvaser CanKing TXT | `kvaser-txt` | `.txt` | Supported | none |
73
+ | candump text | `candump` | `.log` | Planned | none |
74
+ | Vector ASC | `vector-asc` | `.asc` | Planned | none |
75
+ | PCAN TRC | `pcan-trc` | `.trc` | Planned | none |
76
+ | Generic CSV | `csv-table` | `.csv` | Planned | none |
77
+ | Vector BLF | `vector-blf` | `.blf` | Planned adapter | `python-can` |
78
+ | ASAM MF4 | `asam-mf4` | `.mf4` | Planned adapter | `asammdf` |
79
+
80
+ See [format support](docs/format-support.md) for the exact dialect each reader
81
+ accepts, and the [roadmap](ROADMAP.md) for sequencing.
82
+
83
+ ## Install
84
+
85
+ ```bash
86
+ pip install capkit
87
+ ```
88
+
89
+ Requires Python `>=3.11`. capkit has no runtime dependencies.
90
+
91
+ ## Design
92
+
93
+ - `Frame` and `LogMeta` are frozen, slotted dataclasses.
94
+ - `read()` is lazy and keeps constant parser state, so file size does not matter.
95
+ - Timestamps are returned exactly as recorded in the source, never rebased or
96
+ converted to absolute time.
97
+ - A format is added only when a real captured fixture pins its dialect under
98
+ `tests/fixtures/`; unsupported dialects fail clearly instead of parsing
99
+ approximately.
100
+
101
+ ## Quick Start
102
+
103
+ ```python
104
+ import capkit
105
+
106
+ # stream frames
107
+ for frame in capkit.read("trace.txt"):
108
+ print(frame.timestamp, hex(frame.arbitration_id), frame.data.hex())
109
+
110
+ # header metadata only
111
+ meta = capkit.probe("trace.txt")
112
+ print(meta.format, meta.start_time)
113
+
114
+ # registered reader names
115
+ print(capkit.available_formats()) # ['kvaser-txt']
116
+ ```
117
+
118
+ The public API is six names: `read`, `probe`, `available_formats`,
119
+ `register_reader`, `Frame`, and `LogMeta`.
120
+
121
+ ## Features
122
+
123
+ ### Format detection
124
+
125
+ An explicit `format=` names a reader and takes precedence over the file
126
+ extension:
127
+
128
+ ```python
129
+ frames = capkit.read("capture.bin", format="kvaser-txt")
130
+ ```
131
+
132
+ Without `format=`, capkit matches the extension against registered readers and
133
+ sniffs the first 4 KiB when the extension is unknown or ambiguous.
134
+
135
+ ### Add your own reader
136
+
137
+ Register a zero-argument reader class to make it available to `read()`,
138
+ `probe()`, and format detection:
139
+
140
+ ```python
141
+ from collections.abc import Iterator
142
+ from pathlib import Path
143
+
144
+ import capkit
145
+
146
+
147
+ class MyReader:
148
+ name: str = "my-format"
149
+ extensions: tuple[str, ...] = (".mylog",)
150
+
151
+ def __init__(self, *, strict: bool = False) -> None:
152
+ self.strict = strict
153
+
154
+ def sniff(self, sample: str) -> bool:
155
+ return sample.startswith("MYLOG")
156
+
157
+ def probe(self, path: Path) -> capkit.LogMeta:
158
+ return capkit.LogMeta(format=self.name)
159
+
160
+ def read(self, path: Path) -> Iterator[capkit.Frame]:
161
+ # Parse path lazily and yield capkit.Frame objects here.
162
+ yield from ()
163
+
164
+
165
+ capkit.register_reader(MyReader)
166
+ ```
167
+
168
+ Registration is process-global and is normally performed at import time.
169
+ dbckit's `.txt` entry point sniffs among all registered readers, so a reader
170
+ whose `sniff()` uniquely matches the content of a `.txt` log is used there
171
+ too, regardless of the extensions it claims.
172
+
173
+ ### Dirty logs and strict mode
174
+
175
+ Readers skip headers, trailers, comments, blank lines, and unrelated noise by
176
+ default. Pass `strict=True` to raise a line-numbered `ValueError` on the first
177
+ unrecognized nonblank line instead:
178
+
179
+ ```python
180
+ frames = capkit.read("trace.txt", strict=True)
181
+ ```
182
+
183
+ A frame record whose DLC disagrees with its data bytes raises in both modes;
184
+ corrupt frames are never silently dropped.
185
+
186
+ ## Use with dbckit
187
+
188
+ [dbckit](https://github.com/canforge/dbckit) decodes CAN frames against a DBC
189
+ database. capkit and dbckit are separate packages — neither depends on or
190
+ imports the other — with adjacent jobs: capkit turns bytes on disk into frames,
191
+ dbckit turns frames plus a DBC into signals.
192
+
193
+ ```python
194
+ import capkit
195
+ import dbckit
196
+
197
+ db = dbckit.load("truck.dbc")
198
+ for decoded in dbckit.decode_frames(db, capkit.read("trace.txt")):
199
+ print(decoded.timestamp, decoded.signals)
200
+ ```
201
+
202
+ capkit also registers its readers in dbckit's `dbckit.readers` entry-point
203
+ group. With both packages installed, `dbckit.decode_log()` reads `.txt` logs
204
+ through capkit directly:
205
+
206
+ ```python
207
+ for decoded in dbckit.decode_log(db, "trace.txt"):
208
+ print(decoded.signals)
209
+ ```
210
+
211
+ ## Scope and Caveats
212
+
213
+ - Kvaser dialects with absolute start-time headers are not supported;
214
+ `probe()` returns `start_time=None` for `kvaser-txt`.
215
+ - capkit reads frames only: no DBC or signal awareness (that is dbckit's job),
216
+ no hardware I/O, no log writing, no dataframe export, no CLI.
217
+
218
+ ## Documentation
219
+
220
+ - [Format support](docs/format-support.md) — supported formats and the exact
221
+ dialect each reader accepts
222
+ - [API reference](docs/api-reference.md) — the public API contract
223
+ - [Recipes](docs/recipes.md) — counting IDs, filtering, time windows, CSV
224
+ export, and dataframes in a few lines of standard library
225
+
226
+ ## Development
227
+
228
+ ```bash
229
+ python -m venv .venv
230
+ source .venv/bin/activate
231
+ pip install -e ".[dev]"
232
+ pytest
233
+ ```
234
+
235
+ The `dev` extra includes dbckit so the entry-point integration tests run; the
236
+ core and contract suites pass without it.
237
+
238
+ ## License
239
+
240
+ MIT
capkit-0.1.0/README.md ADDED
@@ -0,0 +1,191 @@
1
+ # capkit
2
+
3
+ [![PyPI](https://img.shields.io/pypi/v/capkit)](https://pypi.org/project/capkit/)
4
+ [![CI](https://github.com/canforge/capkit/actions/workflows/ci.yml/badge.svg)](https://github.com/canforge/capkit/actions/workflows/ci.yml)
5
+ [![Python versions](https://img.shields.io/pypi/pyversions/capkit)](https://pypi.org/project/capkit/)
6
+ [![License: MIT](https://img.shields.io/pypi/l/capkit)](LICENSE)
7
+
8
+ `capkit` is a Python library that reads **CAN bus capture logs into one common
9
+ frame stream**. Every supported format parses into the same frozen `Frame`
10
+ dataclass, so code that consumes frames never depends on which tool captured
11
+ the log.
12
+
13
+ Use it to:
14
+
15
+ - read captures from different tools as one lazy stream of typed `Frame` objects
16
+ - probe a file for header metadata without scanning the frame body
17
+ - detect the log format from the file extension or the file content
18
+ - skip real-world log noise by default, or reject it with `strict=True`
19
+ - feed frames into [dbckit](https://github.com/canforge/dbckit) for DBC signal decoding
20
+
21
+ | Format | Reader name | Extensions | Status | Dependency |
22
+ |---|---|---|---|---|
23
+ | Kvaser CanKing TXT | `kvaser-txt` | `.txt` | Supported | none |
24
+ | candump text | `candump` | `.log` | Planned | none |
25
+ | Vector ASC | `vector-asc` | `.asc` | Planned | none |
26
+ | PCAN TRC | `pcan-trc` | `.trc` | Planned | none |
27
+ | Generic CSV | `csv-table` | `.csv` | Planned | none |
28
+ | Vector BLF | `vector-blf` | `.blf` | Planned adapter | `python-can` |
29
+ | ASAM MF4 | `asam-mf4` | `.mf4` | Planned adapter | `asammdf` |
30
+
31
+ See [format support](docs/format-support.md) for the exact dialect each reader
32
+ accepts, and the [roadmap](ROADMAP.md) for sequencing.
33
+
34
+ ## Install
35
+
36
+ ```bash
37
+ pip install capkit
38
+ ```
39
+
40
+ Requires Python `>=3.11`. capkit has no runtime dependencies.
41
+
42
+ ## Design
43
+
44
+ - `Frame` and `LogMeta` are frozen, slotted dataclasses.
45
+ - `read()` is lazy and keeps constant parser state, so file size does not matter.
46
+ - Timestamps are returned exactly as recorded in the source, never rebased or
47
+ converted to absolute time.
48
+ - A format is added only when a real captured fixture pins its dialect under
49
+ `tests/fixtures/`; unsupported dialects fail clearly instead of parsing
50
+ approximately.
51
+
52
+ ## Quick Start
53
+
54
+ ```python
55
+ import capkit
56
+
57
+ # stream frames
58
+ for frame in capkit.read("trace.txt"):
59
+ print(frame.timestamp, hex(frame.arbitration_id), frame.data.hex())
60
+
61
+ # header metadata only
62
+ meta = capkit.probe("trace.txt")
63
+ print(meta.format, meta.start_time)
64
+
65
+ # registered reader names
66
+ print(capkit.available_formats()) # ['kvaser-txt']
67
+ ```
68
+
69
+ The public API is six names: `read`, `probe`, `available_formats`,
70
+ `register_reader`, `Frame`, and `LogMeta`.
71
+
72
+ ## Features
73
+
74
+ ### Format detection
75
+
76
+ An explicit `format=` names a reader and takes precedence over the file
77
+ extension:
78
+
79
+ ```python
80
+ frames = capkit.read("capture.bin", format="kvaser-txt")
81
+ ```
82
+
83
+ Without `format=`, capkit matches the extension against registered readers and
84
+ sniffs the first 4 KiB when the extension is unknown or ambiguous.
85
+
86
+ ### Add your own reader
87
+
88
+ Register a zero-argument reader class to make it available to `read()`,
89
+ `probe()`, and format detection:
90
+
91
+ ```python
92
+ from collections.abc import Iterator
93
+ from pathlib import Path
94
+
95
+ import capkit
96
+
97
+
98
+ class MyReader:
99
+ name: str = "my-format"
100
+ extensions: tuple[str, ...] = (".mylog",)
101
+
102
+ def __init__(self, *, strict: bool = False) -> None:
103
+ self.strict = strict
104
+
105
+ def sniff(self, sample: str) -> bool:
106
+ return sample.startswith("MYLOG")
107
+
108
+ def probe(self, path: Path) -> capkit.LogMeta:
109
+ return capkit.LogMeta(format=self.name)
110
+
111
+ def read(self, path: Path) -> Iterator[capkit.Frame]:
112
+ # Parse path lazily and yield capkit.Frame objects here.
113
+ yield from ()
114
+
115
+
116
+ capkit.register_reader(MyReader)
117
+ ```
118
+
119
+ Registration is process-global and is normally performed at import time.
120
+ dbckit's `.txt` entry point sniffs among all registered readers, so a reader
121
+ whose `sniff()` uniquely matches the content of a `.txt` log is used there
122
+ too, regardless of the extensions it claims.
123
+
124
+ ### Dirty logs and strict mode
125
+
126
+ Readers skip headers, trailers, comments, blank lines, and unrelated noise by
127
+ default. Pass `strict=True` to raise a line-numbered `ValueError` on the first
128
+ unrecognized nonblank line instead:
129
+
130
+ ```python
131
+ frames = capkit.read("trace.txt", strict=True)
132
+ ```
133
+
134
+ A frame record whose DLC disagrees with its data bytes raises in both modes;
135
+ corrupt frames are never silently dropped.
136
+
137
+ ## Use with dbckit
138
+
139
+ [dbckit](https://github.com/canforge/dbckit) decodes CAN frames against a DBC
140
+ database. capkit and dbckit are separate packages — neither depends on or
141
+ imports the other — with adjacent jobs: capkit turns bytes on disk into frames,
142
+ dbckit turns frames plus a DBC into signals.
143
+
144
+ ```python
145
+ import capkit
146
+ import dbckit
147
+
148
+ db = dbckit.load("truck.dbc")
149
+ for decoded in dbckit.decode_frames(db, capkit.read("trace.txt")):
150
+ print(decoded.timestamp, decoded.signals)
151
+ ```
152
+
153
+ capkit also registers its readers in dbckit's `dbckit.readers` entry-point
154
+ group. With both packages installed, `dbckit.decode_log()` reads `.txt` logs
155
+ through capkit directly:
156
+
157
+ ```python
158
+ for decoded in dbckit.decode_log(db, "trace.txt"):
159
+ print(decoded.signals)
160
+ ```
161
+
162
+ ## Scope and Caveats
163
+
164
+ - Kvaser dialects with absolute start-time headers are not supported;
165
+ `probe()` returns `start_time=None` for `kvaser-txt`.
166
+ - capkit reads frames only: no DBC or signal awareness (that is dbckit's job),
167
+ no hardware I/O, no log writing, no dataframe export, no CLI.
168
+
169
+ ## Documentation
170
+
171
+ - [Format support](docs/format-support.md) — supported formats and the exact
172
+ dialect each reader accepts
173
+ - [API reference](docs/api-reference.md) — the public API contract
174
+ - [Recipes](docs/recipes.md) — counting IDs, filtering, time windows, CSV
175
+ export, and dataframes in a few lines of standard library
176
+
177
+ ## Development
178
+
179
+ ```bash
180
+ python -m venv .venv
181
+ source .venv/bin/activate
182
+ pip install -e ".[dev]"
183
+ pytest
184
+ ```
185
+
186
+ The `dev` extra includes dbckit so the entry-point integration tests run; the
187
+ core and contract suites pass without it.
188
+
189
+ ## License
190
+
191
+ MIT
@@ -0,0 +1,71 @@
1
+ # Roadmap
2
+
3
+ Readers are fixture-first: a format lands only with a real capture under
4
+ `tests/fixtures/<format>/`.
5
+
6
+ ## Publish 0.1.0 to PyPI
7
+
8
+ - [x] **1. Add `.github/workflows/release.yml`**: trigger on `v*` tags; build sdist + wheel;
9
+ publish with `pypa/gh-action-pypi-publish` using OIDC (`permissions: id-token: write`,
10
+ environment `pypi`) — no API tokens
11
+ - [ ] **2. Register the trusted publisher on pypi.org** *(manual)*: add a *pending publisher*
12
+ for `canforge/capkit`, workflow `release.yml`, environment `pypi`, and create the protected
13
+ `pypi` environment in the GitHub repository settings
14
+ - [ ] **3. Optional TestPyPI dry run**: point a copy of the workflow at TestPyPI, publish,
15
+ `pip install -i https://test.pypi.org/simple/ capkit`
16
+ - [x] **4. Date the `0.1.0` changelog section** and match the `pyproject.toml` version
17
+ - [x] **5. Merge to `main` and push**
18
+ - [ ] **6. Tag and release**: `git tag -a v0.1.0 -m "Release 0.1.0" && git push origin v0.1.0` —
19
+ the workflow builds and publishes
20
+ - [ ] **7. Verify the release**
21
+ - [ ] In a clean venv: `pip install capkit` then
22
+ `python -c "import capkit; print(capkit.available_formats())"`
23
+ - [ ] With dbckit installed alongside, `dbckit.decode_log(db, "trace.txt")` resolves
24
+ capkit's `.txt` entry point
25
+ - [ ] PyPI page shows license, links, and README correctly
26
+
27
+ ## Planned readers
28
+
29
+ - [ ] candump text (`.log`)
30
+ - [ ] Vector ASC (`.asc`)
31
+ - [ ] PCAN TRC (`.trc`)
32
+ - [ ] Configurable generic CSV (`.csv`)
33
+ - [ ] SocketCAN pcap/pcapng (`.pcap`, `.pcapng`)
34
+ - [ ] Vector BLF (`.blf`) and ASAM MF4 (`.mf4`) through optional adapters
35
+ - [ ] Additional Kvaser dialects (absolute start-time headers) as real fixtures arrive
36
+
37
+ ## Reading pipeline
38
+
39
+ - [ ] A `capkit.readers` entry-point group, so installed third-party formats are
40
+ discovered automatically without import-time registration
41
+ - [ ] File-object and stdin input alongside paths, so live captures pipe straight in
42
+ (`candump can0 | ...`)
43
+ - [ ] Transparent reading of gzip-compressed logs (stdlib `gzip`; captures are large)
44
+ - [ ] Collected-error mode between the two current extremes: skip unrecognized lines but
45
+ report them (count and line numbers) instead of skipping silently or raising on the
46
+ first
47
+ - [ ] CAN FD flags on `Frame` (bit-rate switch, error-state indicator) when the first FD
48
+ fixture lands
49
+
50
+ ## Stream operations
51
+
52
+ - [ ] Filters: by ID set, channel, and time window
53
+ - [ ] Merge several logs into one time-ordered stream (multi-bus and multi-file captures)
54
+ - [ ] Explicit, opt-in timestamp rebasing helper — `read()` itself keeps returning
55
+ timestamps exactly as recorded
56
+ - [ ] J1939 arbitration-ID decomposition (priority, PGN, source address): pure per-frame
57
+ arithmetic with no DBC awareness, which is where dbckit's J1939 helpers deliberately
58
+ stop
59
+
60
+ ## CLI (`capkit[cli]`)
61
+
62
+ - [ ] `probe`, `head`, and `stats` commands (unique IDs, per-ID counts, time span)
63
+ - [ ] `convert` between supported formats once writers exist
64
+
65
+ ## Deferred additions
66
+
67
+ - [ ] Writers and format conversion
68
+ - [ ] Dataframe export (pandas/polars) — recipes first, optional helpers only if the
69
+ recipes prove insufficient
70
+ - [ ] python-can `Message` interop helpers
71
+ - [ ] Benchmark suite pinning parse throughput on large logs
@@ -0,0 +1,13 @@
1
+ """Read CAN log files into a common stream of frame objects."""
2
+ from __future__ import annotations
3
+
4
+ from capkit.io import available_formats, probe, read
5
+ from capkit.model import Frame, LogMeta
6
+ from capkit.readers import register_reader
7
+
8
+ __all__ = [
9
+ # model
10
+ "Frame", "LogMeta",
11
+ # io
12
+ "read", "probe", "available_formats", "register_reader",
13
+ ]