c0data-py 0.3.2__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,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Thomas Sawyer <transfire@gmail.com>
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,3 @@
1
+ include c0-c/c0.h
2
+ include README.md
3
+ include LICENSE
@@ -0,0 +1,159 @@
1
+ Metadata-Version: 2.4
2
+ Name: c0data-py
3
+ Version: 0.3.2
4
+ Summary: C0DATA 1.0 — structured data using ASCII C0 control codes (Python binding over the c0 C core)
5
+ Author-email: Thomas Sawyer <transfire@gmail.com>
6
+ License-Expression: MIT
7
+ Project-URL: Repository, https://github.com/c0data/c0-py
8
+ Keywords: c0,c0data,data-format,control-codes
9
+ Requires-Python: >=3.8
10
+ Description-Content-Type: text/markdown
11
+ License-File: LICENSE
12
+ Dynamic: license-file
13
+
14
+ # c0
15
+
16
+ A Python implementation of [C0DATA](https://github.com/c0data) 1.0 — structured
17
+ data built on ASCII C0 control codes.
18
+
19
+ This is **not** a reimplementation: it's a thin, fast binding over the
20
+ [c0 C core](https://github.com/c0data/c0-c) (a CPython extension, no third-party
21
+ dependencies), so the scan-heavy work runs as native C while the Python layer
22
+ provides an idiomatic API. The read path is **zero-copy** — raw accessors return
23
+ `memoryview` slices into the input buffer; logical values come back as `bytes`,
24
+ with `str` conveniences beside them.
25
+
26
+ ## Usage
27
+
28
+ ```python
29
+ import c0
30
+
31
+ # Write: SYN heading, a group with a header row (␁␞), records, EOT
32
+ buf = c0.build(lambda b: (
33
+ b.syn(shape="tabular"),
34
+ b.group("users", ["name", "amount"]),
35
+ b.record("Alice", "100"),
36
+ b.record("Bob", "200"),
37
+ b.eot(),
38
+ ))
39
+ # ␖␁shape␟tabular␝users␁␞name␟amount␞Alice␟100␞Bob␟200␄
40
+
41
+ # Read
42
+ doc = c0.Document(buf) # or c0.Container(buf) for several documents / an EM tail
43
+ doc.shape # 'tabular'
44
+ users = doc.group("users") # by name or index; doc["users"] too
45
+ users.headers # [b'name', b'amount']
46
+ for rec in users:
47
+ rec.values # [b'Alice', b'100'] — plain trimmed, text decoded
48
+ rec.string("amount") # '100', by header name or index
49
+ rec.field(0) # memoryview of the raw unit (zero-copy)
50
+
51
+ # The body is the canonical unit — hashable for content addressing
52
+ assert c0.canonical(c0.build(lambda b: (b.group("users", ["name", "amount"]),
53
+ b.record("Alice", "100"), b.record("Bob", "200"))))
54
+ c0.validate(buf) # raises c0.C0SyntaxError on malformed input
55
+
56
+ # Streams and pretty form
57
+ log = c0.StreamReader(open("claims.c0", "rb").read()) # .torn, .blocks, .damaged, .records
58
+ print(c0.pretty_format(buf)) # Unicode Control Pictures
59
+ ```
60
+
61
+ Values are written in their one spelling: plain when they can be, text
62
+ (`␂…␃`) when they hold a control byte or edge whitespace. Keyed records
63
+ (`␞␁host␟localhost␁port␟5432`) are maps that keep their order:
64
+
65
+ ```python
66
+ buf = c0.build(lambda b: (b.group("database"), b.map(host="localhost", port="5432")))
67
+ rec = c0.Body(buf).group(0).record(0)
68
+ rec.keyed, rec.keys, rec["port"] # True, [b'host', b'port'], b'5432'
69
+ ```
70
+
71
+ ### Nested levels, lists and references
72
+
73
+ A unit may be a nested level, `␐…␘`, whose contents are a whole body. A
74
+ level holding one record is a list; `Builder.list` writes one and
75
+ `Record.list` reads it back.
76
+
77
+ ```python
78
+ buf = c0.build(lambda b: (
79
+ b.group("users", ["name", "roles", "address"]),
80
+ b.record("Alice"),
81
+ b.list(["Admin", "Editor"]), # ␐␞Admin␟Editor␘
82
+ b.level(lambda l: (l.header("street", "city"), # ␐␁␞street␟city␞1 Main␟Springfield␘
83
+ l.record("1 Main", "Springfield"))),
84
+ ))
85
+ rec = c0.Body(buf).group(0).record(0)
86
+ rec.list(1) # [b'Admin', b'Editor']
87
+ rec.level(2).record(0).string("city") # 'Springfield'
88
+ ```
89
+
90
+ `level()` and `key_level(name)` also work as context managers (`with b.level(): …`).
91
+ References are `b.ref("tags")` (`␅tags`) or `b.ref("tags", "001", "label")`
92
+ (`␅␂tags␟001␟label␃`), read back with `Record.ref`.
93
+
94
+ The builder also has `file`, `section`, `header`, `field`, `key`, `note`,
95
+ `etb(payload)`, `em(tail)`, `block` and `item`, matching the Crystal reference.
96
+ Every error derives from `c0.C0Error`.
97
+
98
+ ### Primitives
99
+
100
+ Underneath, the builder has one method per code: `fs`, `gs`, `rs`, `us`,
101
+ `sub` (C0-DIFF only), `soh`, `dle`, `can`, `bel`, `enq`, `syn`, `eot`,
102
+ `etb`, `em`. Each writes
103
+ its code and, when given, what follows it, so a chain reads like the bytes,
104
+ and anything the grammar allows can be written. The argument is optional
105
+ where the grammar allows the code alone (`gs()`, `rs()`, `us()`, `sub()`,
106
+ `soh()`, `bel()`, `etb()`); `enq` takes one to three names:
107
+
108
+ ```python
109
+ b.rs("Alice").us("1502.30") # ␞Alice␟1502.30
110
+ b.rs().soh("host").us("localhost").soh("port").us("5432") # ␞␁host␟localhost␁port␟5432
111
+ b.gs("users").soh().rs("name").us("amount") # ␝users␁␞name␟amount: a header row
112
+ b.rs("cfg").us().dle().rs().soh("b").us("2").can() # ␞cfg␟␐␞␁b␟2␘
113
+ b.bel("checked").rs("x") # ␇checked␞x: a note
114
+ b.bel().gs("old") # ␇␝old: a dead group
115
+ b.gs().gs("sub").rs().enq("users").us().enq("users", "1") # ␝␝sub␞␅users␟␅␂users␟1␃
116
+ b.syn().soh("version").us("1").rs("a").eot() # ␖␁version␟1␞a␄
117
+ b.rs("old").sub("new") # ␞old␚new: a C0-DIFF replace-all
118
+ ```
119
+
120
+ The composed methods above are built on these and cannot write ill-formed
121
+ output; with the primitives, well-formedness is yours. Values keep their one
122
+ spelling either way (there is no STX/ETX primitive), and names are still
123
+ checked. The two layers share the record state and mix freely:
124
+ `b.rs("a").field("b")` writes `␞a␟b`, `b.record().us("c")` writes `␞␟c`.
125
+
126
+ ## Install / build
127
+
128
+ ```sh
129
+ pip install c0data-py # the package is c0data-py (PyPI treats c0data as codata); in code it is `import c0`
130
+ ```
131
+
132
+ From a checkout, the C core and the shared conformance vectors are git submodules:
133
+
134
+ ```sh
135
+ git submodule update --init # pulls in c0-c (the header) and c0-spec
136
+ pip install . # builds the extension
137
+ ```
138
+
139
+ Requires a C compiler and the Python development headers.
140
+
141
+ ## Status
142
+
143
+ Binds the c0-c 1.0 core: validator, zero-copy body/document/container readers
144
+ (files, groups, header rows, positional and keyed records, nested levels,
145
+ references, headings, EM blobs), canonical check, the builder, ETB stream mode
146
+ and pretty form (`pretty_format`/`pretty_parse`). Passes the shared
147
+ conformance vectors from [c0-spec](https://github.com/c0data/c0-spec).
148
+
149
+ Converters (CSV / JSON / C0-DIFF) are not yet wrapped.
150
+
151
+ ## Test
152
+
153
+ ```sh
154
+ python -m unittest discover -s tests
155
+ ```
156
+
157
+ ## License
158
+
159
+ MIT
@@ -0,0 +1,146 @@
1
+ # c0
2
+
3
+ A Python implementation of [C0DATA](https://github.com/c0data) 1.0 — structured
4
+ data built on ASCII C0 control codes.
5
+
6
+ This is **not** a reimplementation: it's a thin, fast binding over the
7
+ [c0 C core](https://github.com/c0data/c0-c) (a CPython extension, no third-party
8
+ dependencies), so the scan-heavy work runs as native C while the Python layer
9
+ provides an idiomatic API. The read path is **zero-copy** — raw accessors return
10
+ `memoryview` slices into the input buffer; logical values come back as `bytes`,
11
+ with `str` conveniences beside them.
12
+
13
+ ## Usage
14
+
15
+ ```python
16
+ import c0
17
+
18
+ # Write: SYN heading, a group with a header row (␁␞), records, EOT
19
+ buf = c0.build(lambda b: (
20
+ b.syn(shape="tabular"),
21
+ b.group("users", ["name", "amount"]),
22
+ b.record("Alice", "100"),
23
+ b.record("Bob", "200"),
24
+ b.eot(),
25
+ ))
26
+ # ␖␁shape␟tabular␝users␁␞name␟amount␞Alice␟100␞Bob␟200␄
27
+
28
+ # Read
29
+ doc = c0.Document(buf) # or c0.Container(buf) for several documents / an EM tail
30
+ doc.shape # 'tabular'
31
+ users = doc.group("users") # by name or index; doc["users"] too
32
+ users.headers # [b'name', b'amount']
33
+ for rec in users:
34
+ rec.values # [b'Alice', b'100'] — plain trimmed, text decoded
35
+ rec.string("amount") # '100', by header name or index
36
+ rec.field(0) # memoryview of the raw unit (zero-copy)
37
+
38
+ # The body is the canonical unit — hashable for content addressing
39
+ assert c0.canonical(c0.build(lambda b: (b.group("users", ["name", "amount"]),
40
+ b.record("Alice", "100"), b.record("Bob", "200"))))
41
+ c0.validate(buf) # raises c0.C0SyntaxError on malformed input
42
+
43
+ # Streams and pretty form
44
+ log = c0.StreamReader(open("claims.c0", "rb").read()) # .torn, .blocks, .damaged, .records
45
+ print(c0.pretty_format(buf)) # Unicode Control Pictures
46
+ ```
47
+
48
+ Values are written in their one spelling: plain when they can be, text
49
+ (`␂…␃`) when they hold a control byte or edge whitespace. Keyed records
50
+ (`␞␁host␟localhost␁port␟5432`) are maps that keep their order:
51
+
52
+ ```python
53
+ buf = c0.build(lambda b: (b.group("database"), b.map(host="localhost", port="5432")))
54
+ rec = c0.Body(buf).group(0).record(0)
55
+ rec.keyed, rec.keys, rec["port"] # True, [b'host', b'port'], b'5432'
56
+ ```
57
+
58
+ ### Nested levels, lists and references
59
+
60
+ A unit may be a nested level, `␐…␘`, whose contents are a whole body. A
61
+ level holding one record is a list; `Builder.list` writes one and
62
+ `Record.list` reads it back.
63
+
64
+ ```python
65
+ buf = c0.build(lambda b: (
66
+ b.group("users", ["name", "roles", "address"]),
67
+ b.record("Alice"),
68
+ b.list(["Admin", "Editor"]), # ␐␞Admin␟Editor␘
69
+ b.level(lambda l: (l.header("street", "city"), # ␐␁␞street␟city␞1 Main␟Springfield␘
70
+ l.record("1 Main", "Springfield"))),
71
+ ))
72
+ rec = c0.Body(buf).group(0).record(0)
73
+ rec.list(1) # [b'Admin', b'Editor']
74
+ rec.level(2).record(0).string("city") # 'Springfield'
75
+ ```
76
+
77
+ `level()` and `key_level(name)` also work as context managers (`with b.level(): …`).
78
+ References are `b.ref("tags")` (`␅tags`) or `b.ref("tags", "001", "label")`
79
+ (`␅␂tags␟001␟label␃`), read back with `Record.ref`.
80
+
81
+ The builder also has `file`, `section`, `header`, `field`, `key`, `note`,
82
+ `etb(payload)`, `em(tail)`, `block` and `item`, matching the Crystal reference.
83
+ Every error derives from `c0.C0Error`.
84
+
85
+ ### Primitives
86
+
87
+ Underneath, the builder has one method per code: `fs`, `gs`, `rs`, `us`,
88
+ `sub` (C0-DIFF only), `soh`, `dle`, `can`, `bel`, `enq`, `syn`, `eot`,
89
+ `etb`, `em`. Each writes
90
+ its code and, when given, what follows it, so a chain reads like the bytes,
91
+ and anything the grammar allows can be written. The argument is optional
92
+ where the grammar allows the code alone (`gs()`, `rs()`, `us()`, `sub()`,
93
+ `soh()`, `bel()`, `etb()`); `enq` takes one to three names:
94
+
95
+ ```python
96
+ b.rs("Alice").us("1502.30") # ␞Alice␟1502.30
97
+ b.rs().soh("host").us("localhost").soh("port").us("5432") # ␞␁host␟localhost␁port␟5432
98
+ b.gs("users").soh().rs("name").us("amount") # ␝users␁␞name␟amount: a header row
99
+ b.rs("cfg").us().dle().rs().soh("b").us("2").can() # ␞cfg␟␐␞␁b␟2␘
100
+ b.bel("checked").rs("x") # ␇checked␞x: a note
101
+ b.bel().gs("old") # ␇␝old: a dead group
102
+ b.gs().gs("sub").rs().enq("users").us().enq("users", "1") # ␝␝sub␞␅users␟␅␂users␟1␃
103
+ b.syn().soh("version").us("1").rs("a").eot() # ␖␁version␟1␞a␄
104
+ b.rs("old").sub("new") # ␞old␚new: a C0-DIFF replace-all
105
+ ```
106
+
107
+ The composed methods above are built on these and cannot write ill-formed
108
+ output; with the primitives, well-formedness is yours. Values keep their one
109
+ spelling either way (there is no STX/ETX primitive), and names are still
110
+ checked. The two layers share the record state and mix freely:
111
+ `b.rs("a").field("b")` writes `␞a␟b`, `b.record().us("c")` writes `␞␟c`.
112
+
113
+ ## Install / build
114
+
115
+ ```sh
116
+ pip install c0data-py # the package is c0data-py (PyPI treats c0data as codata); in code it is `import c0`
117
+ ```
118
+
119
+ From a checkout, the C core and the shared conformance vectors are git submodules:
120
+
121
+ ```sh
122
+ git submodule update --init # pulls in c0-c (the header) and c0-spec
123
+ pip install . # builds the extension
124
+ ```
125
+
126
+ Requires a C compiler and the Python development headers.
127
+
128
+ ## Status
129
+
130
+ Binds the c0-c 1.0 core: validator, zero-copy body/document/container readers
131
+ (files, groups, header rows, positional and keyed records, nested levels,
132
+ references, headings, EM blobs), canonical check, the builder, ETB stream mode
133
+ and pretty form (`pretty_format`/`pretty_parse`). Passes the shared
134
+ conformance vectors from [c0-spec](https://github.com/c0data/c0-spec).
135
+
136
+ Converters (CSV / JSON / C0-DIFF) are not yet wrapped.
137
+
138
+ ## Test
139
+
140
+ ```sh
141
+ python -m unittest discover -s tests
142
+ ```
143
+
144
+ ## License
145
+
146
+ MIT