fsonl 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.
- fsonl-0.1.0/.gitignore +8 -0
- fsonl-0.1.0/LICENSE +21 -0
- fsonl-0.1.0/PKG-INFO +231 -0
- fsonl-0.1.0/README.ko.md +220 -0
- fsonl-0.1.0/README.md +205 -0
- fsonl-0.1.0/examples/01_inline_schema.py +14 -0
- fsonl-0.1.0/examples/02_code_schema.py +14 -0
- fsonl-0.1.0/examples/03_define_schema.py +15 -0
- fsonl-0.1.0/examples/04_serialize.py +10 -0
- fsonl-0.1.0/examples/05_stream_file.py +23 -0
- fsonl-0.1.0/examples/06_raw_mode.py +12 -0
- fsonl-0.1.0/pyproject.toml +43 -0
- fsonl-0.1.0/src/fsonl/__init__.py +13 -0
- fsonl-0.1.0/src/fsonl/__main__.py +102 -0
- fsonl-0.1.0/src/fsonl/_api.py +278 -0
- fsonl-0.1.0/src/fsonl/_binder.py +204 -0
- fsonl-0.1.0/src/fsonl/_define.py +111 -0
- fsonl-0.1.0/src/fsonl/_errors.py +39 -0
- fsonl-0.1.0/src/fsonl/_parser.py +233 -0
- fsonl-0.1.0/src/fsonl/_scanner.py +42 -0
- fsonl-0.1.0/src/fsonl/_schema.py +108 -0
- fsonl-0.1.0/src/fsonl/_schema_parser.py +275 -0
- fsonl-0.1.0/src/fsonl/_serializer.py +199 -0
- fsonl-0.1.0/src/fsonl/_types.py +100 -0
- fsonl-0.1.0/src/fsonl/_values.py +104 -0
- fsonl-0.1.0/src/fsonl/py.typed +0 -0
- fsonl-0.1.0/tests/conftest.py +5 -0
- fsonl-0.1.0/tests/test_api.py +235 -0
- fsonl-0.1.0/tests/test_bind.py +48 -0
- fsonl-0.1.0/tests/test_cross_validation.py +61 -0
- fsonl-0.1.0/tests/test_define.py +283 -0
- fsonl-0.1.0/tests/test_serializer.py +137 -0
- fsonl-0.1.0/tests/test_serializer_raw.py +37 -0
- fsonl-0.1.0/tests/test_serializer_strict.py +70 -0
fsonl-0.1.0/.gitignore
ADDED
fsonl-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 FSONL 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.
|
fsonl-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,231 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: fsonl
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Function-Styled Object Notation Lines — a line-based serialization format with typed schemas
|
|
5
|
+
Project-URL: Homepage, https://github.com/fsonl/fsonl
|
|
6
|
+
Project-URL: Documentation, https://github.com/fsonl/fsonl-py
|
|
7
|
+
Project-URL: Repository, https://github.com/fsonl/fsonl-py
|
|
8
|
+
Author: Hve
|
|
9
|
+
License-Expression: MIT
|
|
10
|
+
License-File: LICENSE
|
|
11
|
+
Keywords: fsonl,jsonl,schema,serialization
|
|
12
|
+
Classifier: Development Status :: 4 - Beta
|
|
13
|
+
Classifier: Intended Audience :: Developers
|
|
14
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
15
|
+
Classifier: Operating System :: OS Independent
|
|
16
|
+
Classifier: Programming Language :: Python :: 3
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
21
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
22
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
23
|
+
Classifier: Topic :: Text Processing :: General
|
|
24
|
+
Requires-Python: >=3.9
|
|
25
|
+
Description-Content-Type: text/markdown
|
|
26
|
+
|
|
27
|
+
# FSONL
|
|
28
|
+
|
|
29
|
+
**Function-Styled Object Notation Lines**
|
|
30
|
+
|
|
31
|
+
A line-based serialization format where each record's type is immediately visible at the start of the line.
|
|
32
|
+
|
|
33
|
+
```
|
|
34
|
+
@schema rm(target: string, --force?: bool = false)
|
|
35
|
+
rm("tmp.log", force=true)
|
|
36
|
+
rm("/var/cache", force=false)
|
|
37
|
+
log("info", "server started")
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
## Install
|
|
41
|
+
|
|
42
|
+
```bash
|
|
43
|
+
pip install fsonl
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
## Quick Start
|
|
47
|
+
|
|
48
|
+
### Parse with inline schema
|
|
49
|
+
|
|
50
|
+
```python
|
|
51
|
+
import fsonl
|
|
52
|
+
|
|
53
|
+
text = """
|
|
54
|
+
@schema rm(target: string, --force?: bool = false)
|
|
55
|
+
rm("tmp.log")
|
|
56
|
+
rm("/var/cache", force=true)
|
|
57
|
+
"""
|
|
58
|
+
|
|
59
|
+
result = fsonl.loads(text)
|
|
60
|
+
for entry in result.entries:
|
|
61
|
+
print(entry)
|
|
62
|
+
# {'type': 'rm', 'target': 'tmp.log', 'force': False}
|
|
63
|
+
# {'type': 'rm', 'target': '/var/cache', 'force': True}
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
### Parse with code schema
|
|
67
|
+
|
|
68
|
+
```python
|
|
69
|
+
import fsonl
|
|
70
|
+
|
|
71
|
+
schema = fsonl.Schema.from_string(
|
|
72
|
+
"@schema log(level: string, msg: string)"
|
|
73
|
+
)
|
|
74
|
+
|
|
75
|
+
result = fsonl.loads('log("info", "started")\n', schema=schema)
|
|
76
|
+
print(result.entries[0])
|
|
77
|
+
# {'type': 'log', 'level': 'info', 'msg': 'started'}
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
### Define schema from Python functions (3.10+)
|
|
81
|
+
|
|
82
|
+
```python
|
|
83
|
+
import fsonl
|
|
84
|
+
|
|
85
|
+
schema = fsonl.Schema()
|
|
86
|
+
|
|
87
|
+
@schema.define
|
|
88
|
+
def rm(target: str, *, force: bool = False): ...
|
|
89
|
+
|
|
90
|
+
result = fsonl.loads('rm("tmp.log", force=true)\n', schema=schema)
|
|
91
|
+
print(result.entries[0])
|
|
92
|
+
# {'type': 'rm', 'target': 'tmp.log', 'force': True}
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
### Serialize
|
|
96
|
+
|
|
97
|
+
```python
|
|
98
|
+
import fsonl
|
|
99
|
+
|
|
100
|
+
schema = fsonl.Schema.from_string(
|
|
101
|
+
"@schema log(level: string, msg: string)"
|
|
102
|
+
)
|
|
103
|
+
|
|
104
|
+
print(fsonl.dumps({"type": "log", "level": "info", "msg": "hello"}, schema=schema))
|
|
105
|
+
# @schema log(level: string, msg: string)
|
|
106
|
+
# log("info", "hello")
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
### Stream from file
|
|
110
|
+
|
|
111
|
+
```python
|
|
112
|
+
import fsonl
|
|
113
|
+
|
|
114
|
+
with open("events.fsonl", newline="") as f:
|
|
115
|
+
for entry in fsonl.iter_entries(f):
|
|
116
|
+
print(entry)
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
### Raw mode (no schema binding)
|
|
120
|
+
|
|
121
|
+
```python
|
|
122
|
+
import fsonl
|
|
123
|
+
|
|
124
|
+
result = fsonl.loads_raw('x(1, "hello", flag=true)\n')
|
|
125
|
+
entry = result[0]
|
|
126
|
+
print(entry["type"]) # 'x'
|
|
127
|
+
print(entry["positional"]) # [1, 'hello']
|
|
128
|
+
print(entry["named"]) # {'flag': True}
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
## API
|
|
132
|
+
|
|
133
|
+
### Parsing
|
|
134
|
+
|
|
135
|
+
| Function | Description |
|
|
136
|
+
|----------|-------------|
|
|
137
|
+
| `loads(text, *, schema, ignore_inline_schema, extra_fields)` | Parse FSONL text with schema binding |
|
|
138
|
+
| `load(fp, **kwargs)` | Parse from file object with schema binding |
|
|
139
|
+
| `loads_raw(text)` | Parse FSONL text without binding (Stage 1 only) |
|
|
140
|
+
| `load_raw(fp)` | Parse from file object without binding (Stage 1 only) |
|
|
141
|
+
| `iter_entries(source, *, schema, ignore_inline_schema, extra_fields)` | Lazy iterator over bound entries |
|
|
142
|
+
| `iter_raw(source)` | Lazy iterator over raw entries |
|
|
143
|
+
| `bind(entry, schema, *, extra_fields)` | Bind a single raw dict to a Schema |
|
|
144
|
+
|
|
145
|
+
### Serialization
|
|
146
|
+
|
|
147
|
+
| Function | Description |
|
|
148
|
+
|----------|-------------|
|
|
149
|
+
| `dumps(entries, *, schema, allow_extra, exclude_schema)` | Serialize to FSONL text |
|
|
150
|
+
| `dump(entries, fp, *, schema, allow_extra, exclude_schema)` | Serialize to file object |
|
|
151
|
+
|
|
152
|
+
### Schema
|
|
153
|
+
|
|
154
|
+
| Method | Description |
|
|
155
|
+
|--------|-------------|
|
|
156
|
+
| `Schema.from_string(text)` | Create from `@schema` lines |
|
|
157
|
+
| `Schema.from_fsonl(text)` | Extract `@schema` from FSONL text (non-schema lines ignored) |
|
|
158
|
+
| `Schema.from_file(path)` | Load `@schema` from a `.fsonl` file |
|
|
159
|
+
| `@schema.define` | Decorator: define schema from function signature (Python 3.10+) |
|
|
160
|
+
| `schema.add(text)` | Add more `@schema` definitions |
|
|
161
|
+
| `schema.get(type_name)` | Look up a type definition |
|
|
162
|
+
| `schema.has(type_name)` | Check if a type is defined |
|
|
163
|
+
| `schema.type_names()` | List all defined type names |
|
|
164
|
+
|
|
165
|
+
### Options
|
|
166
|
+
|
|
167
|
+
| Option | Default | Description |
|
|
168
|
+
|--------|---------|-------------|
|
|
169
|
+
| `ignore_inline_schema` | `False` | Skip `@schema` directives in the content |
|
|
170
|
+
| `allow_extra` | `False` | (`dumps` only) Ignore extra keys not in schema |
|
|
171
|
+
| `exclude_schema` | `False` | (`dumps` only) Omit `@schema` lines from output |
|
|
172
|
+
| `extra_fields` | `ExtraFieldPolicy.ERROR` | Policy for undeclared named arguments |
|
|
173
|
+
|
|
174
|
+
### Errors
|
|
175
|
+
|
|
176
|
+
All errors include line numbers: `str(error)` produces `"line 42: message"`.
|
|
177
|
+
|
|
178
|
+
| Exception | Kind | Stage |
|
|
179
|
+
|-----------|------|-------|
|
|
180
|
+
| `ParseError` | `syntax_error` | Stage 1 (syntax parse) |
|
|
181
|
+
| `SchemaError` | `schema_error` | Schema definition / cross-validation |
|
|
182
|
+
| `BindError` | `bind_error` | Stage 2 (data vs schema mismatch) |
|
|
183
|
+
|
|
184
|
+
All inherit from `FsonlError`, which inherits from `Exception`.
|
|
185
|
+
|
|
186
|
+
## Schema Types
|
|
187
|
+
|
|
188
|
+
```
|
|
189
|
+
string -- JSON string
|
|
190
|
+
number -- JSON number (int or float)
|
|
191
|
+
bool -- true / false
|
|
192
|
+
null -- null
|
|
193
|
+
any -- any JSON value
|
|
194
|
+
string[] -- array of strings
|
|
195
|
+
(string | number)[] -- array of union
|
|
196
|
+
{ cmd: string, id?: number } -- fixed-shape object
|
|
197
|
+
string | null -- nullable string
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
## CLI
|
|
201
|
+
|
|
202
|
+
```bash
|
|
203
|
+
# Parse with schema binding (default)
|
|
204
|
+
echo '@schema x(a: number)\nx(1)' | python -m fsonl parse
|
|
205
|
+
|
|
206
|
+
# Raw parse (no binding)
|
|
207
|
+
echo 'x(1)' | python -m fsonl parse --raw
|
|
208
|
+
|
|
209
|
+
# Unknown types as raw dict
|
|
210
|
+
echo 'x(1)' | python -m fsonl parse --allow-unknown
|
|
211
|
+
|
|
212
|
+
# Extract @schema directives only
|
|
213
|
+
echo '@schema x(a: number)\nx(1)' | python -m fsonl parse --schema
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
## Format Overview
|
|
217
|
+
|
|
218
|
+
- One entry per line: `type(args)`
|
|
219
|
+
- Values are JSON literals: strings, numbers, booleans, null, arrays, objects
|
|
220
|
+
- Positional args come before named args: `log("info", tag="v2")`
|
|
221
|
+
- Comments: `// ...` (outside argument lists only)
|
|
222
|
+
- File extension: `.fsonl`, MIME: `text/fsonl`, encoding: UTF-8
|
|
223
|
+
|
|
224
|
+
## Specification
|
|
225
|
+
|
|
226
|
+
- [SPEC.ko.md](https://github.com/fsonl/fsonl/blob/main/spec/SPEC.ko.md) -- Language specification (Korean)
|
|
227
|
+
- [GRAMMAR.ko.peg](https://github.com/fsonl/fsonl/blob/main/spec/GRAMMAR.ko.peg) -- PEG formal grammar (Korean)
|
|
228
|
+
|
|
229
|
+
## License
|
|
230
|
+
|
|
231
|
+
MIT
|
fsonl-0.1.0/README.ko.md
ADDED
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
# FSONL
|
|
2
|
+
|
|
3
|
+
**Function-Styled Object Notation Lines**
|
|
4
|
+
|
|
5
|
+
각 줄의 레코드 타입이 선두에 즉시 드러나는 줄 단위 직렬화 포맷.
|
|
6
|
+
|
|
7
|
+
```
|
|
8
|
+
@schema rm(target: string, --force?: bool = false)
|
|
9
|
+
rm("tmp.log", force=true)
|
|
10
|
+
rm("/var/cache", force=false)
|
|
11
|
+
log("info", "server started")
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
## 설치
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
pip install fsonl
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
## 빠른 시작
|
|
21
|
+
|
|
22
|
+
### 인라인 스키마로 파싱
|
|
23
|
+
|
|
24
|
+
```python
|
|
25
|
+
import fsonl
|
|
26
|
+
|
|
27
|
+
text = """
|
|
28
|
+
@schema rm(target: string, --force?: bool = false)
|
|
29
|
+
rm("tmp.log")
|
|
30
|
+
rm("/var/cache", force=true)
|
|
31
|
+
"""
|
|
32
|
+
|
|
33
|
+
result = fsonl.loads(text)
|
|
34
|
+
for entry in result.entries:
|
|
35
|
+
print(entry)
|
|
36
|
+
# {'type': 'rm', 'target': 'tmp.log', 'force': False}
|
|
37
|
+
# {'type': 'rm', 'target': '/var/cache', 'force': True}
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
### 코드 스키마로 파싱
|
|
41
|
+
|
|
42
|
+
```python
|
|
43
|
+
import fsonl
|
|
44
|
+
|
|
45
|
+
schema = fsonl.Schema.from_string(
|
|
46
|
+
"@schema log(level: string, msg: string)"
|
|
47
|
+
)
|
|
48
|
+
|
|
49
|
+
result = fsonl.loads('log("info", "started")\n', schema=schema)
|
|
50
|
+
print(result.entries[0])
|
|
51
|
+
# {'type': 'log', 'level': 'info', 'msg': 'started'}
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
### Python 함수로 스키마 정의 (3.10+)
|
|
55
|
+
|
|
56
|
+
```python
|
|
57
|
+
import fsonl
|
|
58
|
+
|
|
59
|
+
schema = fsonl.Schema()
|
|
60
|
+
|
|
61
|
+
@schema.define
|
|
62
|
+
def rm(target: str, *, force: bool = False): ...
|
|
63
|
+
|
|
64
|
+
result = fsonl.loads('rm("tmp.log", force=true)\n', schema=schema)
|
|
65
|
+
print(result.entries[0])
|
|
66
|
+
# {'type': 'rm', 'target': 'tmp.log', 'force': True}
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
### 직렬화
|
|
70
|
+
|
|
71
|
+
```python
|
|
72
|
+
import fsonl
|
|
73
|
+
|
|
74
|
+
schema = fsonl.Schema.from_string(
|
|
75
|
+
"@schema log(level: string, msg: string)"
|
|
76
|
+
)
|
|
77
|
+
|
|
78
|
+
print(fsonl.dumps({"type": "log", "level": "info", "msg": "hello"}, schema=schema))
|
|
79
|
+
# @schema log(level: string, msg: string)
|
|
80
|
+
# log("info", "hello")
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
### 파일 스트리밍
|
|
84
|
+
|
|
85
|
+
```python
|
|
86
|
+
import fsonl
|
|
87
|
+
|
|
88
|
+
with open("events.fsonl", newline="") as f:
|
|
89
|
+
for entry in fsonl.iter_entries(f):
|
|
90
|
+
print(entry)
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
### Raw 모드 (스키마 바인딩 없이)
|
|
94
|
+
|
|
95
|
+
```python
|
|
96
|
+
import fsonl
|
|
97
|
+
|
|
98
|
+
result = fsonl.loads_raw('x(1, "hello", flag=true)\n')
|
|
99
|
+
entry = result[0]
|
|
100
|
+
print(entry["type"]) # 'x'
|
|
101
|
+
print(entry["positional"]) # [1, 'hello']
|
|
102
|
+
print(entry["named"]) # {'flag': True}
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
## API
|
|
106
|
+
|
|
107
|
+
### 파싱
|
|
108
|
+
|
|
109
|
+
| 함수 | 설명 |
|
|
110
|
+
|------|------|
|
|
111
|
+
| `loads(text, *, schema, ignore_inline_schema, extra_fields)` | FSONL 텍스트 스키마 바인딩 파싱 |
|
|
112
|
+
| `load(fp, **kwargs)` | 파일 객체에서 스키마 바인딩 파싱 |
|
|
113
|
+
| `loads_raw(text)` | FSONL 텍스트 바인딩 없이 파싱 (Stage 1만) |
|
|
114
|
+
| `load_raw(fp)` | 파일 객체에서 바인딩 없이 파싱 (Stage 1만) |
|
|
115
|
+
| `iter_entries(source, *, schema, ignore_inline_schema, extra_fields)` | 바인딩된 엔트리 지연 이터레이터 |
|
|
116
|
+
| `iter_raw(source)` | Raw 엔트리 지연 이터레이터 |
|
|
117
|
+
| `bind(entry, schema, *, extra_fields)` | 단일 raw dict를 Schema에 바인딩 |
|
|
118
|
+
|
|
119
|
+
### 직렬화
|
|
120
|
+
|
|
121
|
+
| 함수 | 설명 |
|
|
122
|
+
|------|------|
|
|
123
|
+
| `dumps(entries, *, schema, allow_extra, exclude_schema)` | FSONL 텍스트로 직렬화 |
|
|
124
|
+
| `dump(entries, fp, *, schema, allow_extra, exclude_schema)` | 파일 객체에 직렬화 |
|
|
125
|
+
|
|
126
|
+
### 스키마
|
|
127
|
+
|
|
128
|
+
| 메서드 | 설명 |
|
|
129
|
+
|--------|------|
|
|
130
|
+
| `Schema.from_string(text)` | `@schema` 문자열로 생성 |
|
|
131
|
+
| `Schema.from_fsonl(text)` | FSONL 텍스트에서 `@schema` 추출 (비스키마 줄 무시) |
|
|
132
|
+
| `Schema.from_file(path)` | `.fsonl` 파일에서 `@schema` 로드 |
|
|
133
|
+
| `@schema.define` | 데코레이터: 함수 시그니처로 스키마 정의 (Python 3.10+) |
|
|
134
|
+
| `schema.add(text)` | `@schema` 정의 추가 |
|
|
135
|
+
| `schema.get(type_name)` | 타입 정의 조회 |
|
|
136
|
+
| `schema.has(type_name)` | 타입 정의 존재 여부 확인 |
|
|
137
|
+
| `schema.type_names()` | 정의된 모든 타입명 목록 |
|
|
138
|
+
|
|
139
|
+
### 옵션
|
|
140
|
+
|
|
141
|
+
| 옵션 | 기본값 | 설명 |
|
|
142
|
+
|------|--------|------|
|
|
143
|
+
| `ignore_inline_schema` | `False` | 콘텐츠 내 `@schema` 디렉티브 무시 |
|
|
144
|
+
| `allow_extra` | `False` | (`dumps` 전용) 스키마에 없는 추가 키 허용 |
|
|
145
|
+
| `exclude_schema` | `False` | (`dumps` 전용) 출력에서 `@schema` 줄 제외 |
|
|
146
|
+
| `extra_fields` | `ExtraFieldPolicy.ERROR` | 미선언 named arg 처리 정책 |
|
|
147
|
+
|
|
148
|
+
### ExtraFieldPolicy
|
|
149
|
+
|
|
150
|
+
```python
|
|
151
|
+
from fsonl import ExtraFieldPolicy
|
|
152
|
+
|
|
153
|
+
# 미선언 필드가 있으면 에러 (기본값)
|
|
154
|
+
result = fsonl.loads(text, schema=schema, extra_fields=ExtraFieldPolicy.ERROR)
|
|
155
|
+
|
|
156
|
+
# 미선언 필드 보존
|
|
157
|
+
result = fsonl.loads(text, schema=schema, extra_fields=ExtraFieldPolicy.PRESERVE)
|
|
158
|
+
|
|
159
|
+
# 미선언 필드 조용히 제거
|
|
160
|
+
result = fsonl.loads(text, schema=schema, extra_fields=ExtraFieldPolicy.STRIP)
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
### 에러
|
|
164
|
+
|
|
165
|
+
모든 에러에 줄 번호 포함: `str(error)`는 `"line 42: message"` 형태.
|
|
166
|
+
|
|
167
|
+
| 예외 | 종류 | 단계 |
|
|
168
|
+
|------|------|------|
|
|
169
|
+
| `ParseError` | `syntax_error` | 1단계 (문법 파싱) |
|
|
170
|
+
| `SchemaError` | `schema_error` | 스키마 정의 / 교차 검증 |
|
|
171
|
+
| `BindError` | `bind_error` | 2단계 (데이터와 스키마 불일치) |
|
|
172
|
+
|
|
173
|
+
모두 `FsonlError`를 상속하며, `FsonlError`는 `Exception`을 상속.
|
|
174
|
+
|
|
175
|
+
## 스키마 타입
|
|
176
|
+
|
|
177
|
+
```
|
|
178
|
+
string -- JSON 문자열
|
|
179
|
+
number -- JSON 숫자 (정수 또는 실수)
|
|
180
|
+
bool -- true / false
|
|
181
|
+
null -- null
|
|
182
|
+
any -- 모든 JSON 값
|
|
183
|
+
string[] -- 문자열 배열
|
|
184
|
+
(string | number)[] -- 유니온 배열
|
|
185
|
+
{ cmd: string, id?: number } -- 고정 구조 객체
|
|
186
|
+
string | null -- nullable 문자열
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
## CLI
|
|
190
|
+
|
|
191
|
+
```bash
|
|
192
|
+
# 스키마 바인딩 파싱 (기본)
|
|
193
|
+
echo '@schema x(a: number)\nx(1)' | python -m fsonl parse
|
|
194
|
+
|
|
195
|
+
# Raw 파싱 (바인딩 없음)
|
|
196
|
+
echo 'x(1)' | python -m fsonl parse --raw
|
|
197
|
+
|
|
198
|
+
# 미정의 타입을 raw dict로
|
|
199
|
+
echo 'x(1)' | python -m fsonl parse --allow-unknown
|
|
200
|
+
|
|
201
|
+
# @schema 디렉티브만 추출
|
|
202
|
+
echo '@schema x(a: number)\nx(1)' | python -m fsonl parse --schema
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
## 포맷 개요
|
|
206
|
+
|
|
207
|
+
- 한 줄에 하나의 엔트리: `type(args)`
|
|
208
|
+
- 값은 JSON 리터럴: 문자열, 숫자, 불리언, null, 배열, 객체
|
|
209
|
+
- positional 인자가 named 인자보다 앞: `log("info", tag="v2")`
|
|
210
|
+
- 주석: `// ...` (인자 목록 바깥에서만)
|
|
211
|
+
- 파일 확장자: `.fsonl`, MIME: `text/fsonl`, 인코딩: UTF-8
|
|
212
|
+
|
|
213
|
+
## 스펙
|
|
214
|
+
|
|
215
|
+
- [SPEC.ko.md](https://github.com/fsonl/fsonl/blob/main/spec/SPEC.ko.md) -- 언어 스펙
|
|
216
|
+
- [GRAMMAR.ko.peg](https://github.com/fsonl/fsonl/blob/main/spec/GRAMMAR.ko.peg) -- PEG 형식 문법
|
|
217
|
+
|
|
218
|
+
## 라이선스
|
|
219
|
+
|
|
220
|
+
MIT
|
fsonl-0.1.0/README.md
ADDED
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
# FSONL
|
|
2
|
+
|
|
3
|
+
**Function-Styled Object Notation Lines**
|
|
4
|
+
|
|
5
|
+
A line-based serialization format where each record's type is immediately visible at the start of the line.
|
|
6
|
+
|
|
7
|
+
```
|
|
8
|
+
@schema rm(target: string, --force?: bool = false)
|
|
9
|
+
rm("tmp.log", force=true)
|
|
10
|
+
rm("/var/cache", force=false)
|
|
11
|
+
log("info", "server started")
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
## Install
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
pip install fsonl
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
## Quick Start
|
|
21
|
+
|
|
22
|
+
### Parse with inline schema
|
|
23
|
+
|
|
24
|
+
```python
|
|
25
|
+
import fsonl
|
|
26
|
+
|
|
27
|
+
text = """
|
|
28
|
+
@schema rm(target: string, --force?: bool = false)
|
|
29
|
+
rm("tmp.log")
|
|
30
|
+
rm("/var/cache", force=true)
|
|
31
|
+
"""
|
|
32
|
+
|
|
33
|
+
result = fsonl.loads(text)
|
|
34
|
+
for entry in result.entries:
|
|
35
|
+
print(entry)
|
|
36
|
+
# {'type': 'rm', 'target': 'tmp.log', 'force': False}
|
|
37
|
+
# {'type': 'rm', 'target': '/var/cache', 'force': True}
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
### Parse with code schema
|
|
41
|
+
|
|
42
|
+
```python
|
|
43
|
+
import fsonl
|
|
44
|
+
|
|
45
|
+
schema = fsonl.Schema.from_string(
|
|
46
|
+
"@schema log(level: string, msg: string)"
|
|
47
|
+
)
|
|
48
|
+
|
|
49
|
+
result = fsonl.loads('log("info", "started")\n', schema=schema)
|
|
50
|
+
print(result.entries[0])
|
|
51
|
+
# {'type': 'log', 'level': 'info', 'msg': 'started'}
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
### Define schema from Python functions (3.10+)
|
|
55
|
+
|
|
56
|
+
```python
|
|
57
|
+
import fsonl
|
|
58
|
+
|
|
59
|
+
schema = fsonl.Schema()
|
|
60
|
+
|
|
61
|
+
@schema.define
|
|
62
|
+
def rm(target: str, *, force: bool = False): ...
|
|
63
|
+
|
|
64
|
+
result = fsonl.loads('rm("tmp.log", force=true)\n', schema=schema)
|
|
65
|
+
print(result.entries[0])
|
|
66
|
+
# {'type': 'rm', 'target': 'tmp.log', 'force': True}
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
### Serialize
|
|
70
|
+
|
|
71
|
+
```python
|
|
72
|
+
import fsonl
|
|
73
|
+
|
|
74
|
+
schema = fsonl.Schema.from_string(
|
|
75
|
+
"@schema log(level: string, msg: string)"
|
|
76
|
+
)
|
|
77
|
+
|
|
78
|
+
print(fsonl.dumps({"type": "log", "level": "info", "msg": "hello"}, schema=schema))
|
|
79
|
+
# @schema log(level: string, msg: string)
|
|
80
|
+
# log("info", "hello")
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
### Stream from file
|
|
84
|
+
|
|
85
|
+
```python
|
|
86
|
+
import fsonl
|
|
87
|
+
|
|
88
|
+
with open("events.fsonl", newline="") as f:
|
|
89
|
+
for entry in fsonl.iter_entries(f):
|
|
90
|
+
print(entry)
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
### Raw mode (no schema binding)
|
|
94
|
+
|
|
95
|
+
```python
|
|
96
|
+
import fsonl
|
|
97
|
+
|
|
98
|
+
result = fsonl.loads_raw('x(1, "hello", flag=true)\n')
|
|
99
|
+
entry = result[0]
|
|
100
|
+
print(entry["type"]) # 'x'
|
|
101
|
+
print(entry["positional"]) # [1, 'hello']
|
|
102
|
+
print(entry["named"]) # {'flag': True}
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
## API
|
|
106
|
+
|
|
107
|
+
### Parsing
|
|
108
|
+
|
|
109
|
+
| Function | Description |
|
|
110
|
+
|----------|-------------|
|
|
111
|
+
| `loads(text, *, schema, ignore_inline_schema, extra_fields)` | Parse FSONL text with schema binding |
|
|
112
|
+
| `load(fp, **kwargs)` | Parse from file object with schema binding |
|
|
113
|
+
| `loads_raw(text)` | Parse FSONL text without binding (Stage 1 only) |
|
|
114
|
+
| `load_raw(fp)` | Parse from file object without binding (Stage 1 only) |
|
|
115
|
+
| `iter_entries(source, *, schema, ignore_inline_schema, extra_fields)` | Lazy iterator over bound entries |
|
|
116
|
+
| `iter_raw(source)` | Lazy iterator over raw entries |
|
|
117
|
+
| `bind(entry, schema, *, extra_fields)` | Bind a single raw dict to a Schema |
|
|
118
|
+
|
|
119
|
+
### Serialization
|
|
120
|
+
|
|
121
|
+
| Function | Description |
|
|
122
|
+
|----------|-------------|
|
|
123
|
+
| `dumps(entries, *, schema, allow_extra, exclude_schema)` | Serialize to FSONL text |
|
|
124
|
+
| `dump(entries, fp, *, schema, allow_extra, exclude_schema)` | Serialize to file object |
|
|
125
|
+
|
|
126
|
+
### Schema
|
|
127
|
+
|
|
128
|
+
| Method | Description |
|
|
129
|
+
|--------|-------------|
|
|
130
|
+
| `Schema.from_string(text)` | Create from `@schema` lines |
|
|
131
|
+
| `Schema.from_fsonl(text)` | Extract `@schema` from FSONL text (non-schema lines ignored) |
|
|
132
|
+
| `Schema.from_file(path)` | Load `@schema` from a `.fsonl` file |
|
|
133
|
+
| `@schema.define` | Decorator: define schema from function signature (Python 3.10+) |
|
|
134
|
+
| `schema.add(text)` | Add more `@schema` definitions |
|
|
135
|
+
| `schema.get(type_name)` | Look up a type definition |
|
|
136
|
+
| `schema.has(type_name)` | Check if a type is defined |
|
|
137
|
+
| `schema.type_names()` | List all defined type names |
|
|
138
|
+
|
|
139
|
+
### Options
|
|
140
|
+
|
|
141
|
+
| Option | Default | Description |
|
|
142
|
+
|--------|---------|-------------|
|
|
143
|
+
| `ignore_inline_schema` | `False` | Skip `@schema` directives in the content |
|
|
144
|
+
| `allow_extra` | `False` | (`dumps` only) Ignore extra keys not in schema |
|
|
145
|
+
| `exclude_schema` | `False` | (`dumps` only) Omit `@schema` lines from output |
|
|
146
|
+
| `extra_fields` | `ExtraFieldPolicy.ERROR` | Policy for undeclared named arguments |
|
|
147
|
+
|
|
148
|
+
### Errors
|
|
149
|
+
|
|
150
|
+
All errors include line numbers: `str(error)` produces `"line 42: message"`.
|
|
151
|
+
|
|
152
|
+
| Exception | Kind | Stage |
|
|
153
|
+
|-----------|------|-------|
|
|
154
|
+
| `ParseError` | `syntax_error` | Stage 1 (syntax parse) |
|
|
155
|
+
| `SchemaError` | `schema_error` | Schema definition / cross-validation |
|
|
156
|
+
| `BindError` | `bind_error` | Stage 2 (data vs schema mismatch) |
|
|
157
|
+
|
|
158
|
+
All inherit from `FsonlError`, which inherits from `Exception`.
|
|
159
|
+
|
|
160
|
+
## Schema Types
|
|
161
|
+
|
|
162
|
+
```
|
|
163
|
+
string -- JSON string
|
|
164
|
+
number -- JSON number (int or float)
|
|
165
|
+
bool -- true / false
|
|
166
|
+
null -- null
|
|
167
|
+
any -- any JSON value
|
|
168
|
+
string[] -- array of strings
|
|
169
|
+
(string | number)[] -- array of union
|
|
170
|
+
{ cmd: string, id?: number } -- fixed-shape object
|
|
171
|
+
string | null -- nullable string
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
## CLI
|
|
175
|
+
|
|
176
|
+
```bash
|
|
177
|
+
# Parse with schema binding (default)
|
|
178
|
+
echo '@schema x(a: number)\nx(1)' | python -m fsonl parse
|
|
179
|
+
|
|
180
|
+
# Raw parse (no binding)
|
|
181
|
+
echo 'x(1)' | python -m fsonl parse --raw
|
|
182
|
+
|
|
183
|
+
# Unknown types as raw dict
|
|
184
|
+
echo 'x(1)' | python -m fsonl parse --allow-unknown
|
|
185
|
+
|
|
186
|
+
# Extract @schema directives only
|
|
187
|
+
echo '@schema x(a: number)\nx(1)' | python -m fsonl parse --schema
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
## Format Overview
|
|
191
|
+
|
|
192
|
+
- One entry per line: `type(args)`
|
|
193
|
+
- Values are JSON literals: strings, numbers, booleans, null, arrays, objects
|
|
194
|
+
- Positional args come before named args: `log("info", tag="v2")`
|
|
195
|
+
- Comments: `// ...` (outside argument lists only)
|
|
196
|
+
- File extension: `.fsonl`, MIME: `text/fsonl`, encoding: UTF-8
|
|
197
|
+
|
|
198
|
+
## Specification
|
|
199
|
+
|
|
200
|
+
- [SPEC.ko.md](https://github.com/fsonl/fsonl/blob/main/spec/SPEC.ko.md) -- Language specification (Korean)
|
|
201
|
+
- [GRAMMAR.ko.peg](https://github.com/fsonl/fsonl/blob/main/spec/GRAMMAR.ko.peg) -- PEG formal grammar (Korean)
|
|
202
|
+
|
|
203
|
+
## License
|
|
204
|
+
|
|
205
|
+
MIT
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
"""Parse with inline schema."""
|
|
2
|
+
import fsonl
|
|
3
|
+
|
|
4
|
+
text = """\
|
|
5
|
+
@schema rm(target: string, --force?: bool = false)
|
|
6
|
+
rm("tmp.log")
|
|
7
|
+
rm("/var/cache", force=true)
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
result = fsonl.loads(text)
|
|
11
|
+
for entry in result.entries:
|
|
12
|
+
print(entry)
|
|
13
|
+
# {'type': 'rm', 'target': 'tmp.log', 'force': False}
|
|
14
|
+
# {'type': 'rm', 'target': '/var/cache', 'force': True}
|