infinity-data 2.2.1__tar.gz → 3.2.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.
- infinity_data-3.2.0/PKG-INFO +156 -0
- infinity_data-3.2.0/README.md +131 -0
- {infinity_data-2.2.1 → infinity_data-3.2.0}/pyproject.toml +19 -2
- infinity_data-3.2.0/src/infinity_data/emit/__init__.py +38 -0
- infinity_data-3.2.0/src/infinity_data/emit/config.py +37 -0
- infinity_data-3.2.0/src/infinity_data/emit/converter.py +258 -0
- infinity_data-3.2.0/src/infinity_data/emit/plain_yaml.py +173 -0
- {infinity_data-2.2.1 → infinity_data-3.2.0}/src/infinity_data/infra/path.py +17 -1
- {infinity_data-2.2.1 → infinity_data-3.2.0}/src/infinity_data/parser/diagnostics.py +5 -0
- {infinity_data-2.2.1 → infinity_data-3.2.0}/src/infinity_data/parser/models.py +12 -6
- {infinity_data-2.2.1 → infinity_data-3.2.0}/src/infinity_data/parser/parser.py +42 -10
- {infinity_data-2.2.1 → infinity_data-3.2.0}/src/infinity_data/pipeline.py +13 -6
- {infinity_data-2.2.1 → infinity_data-3.2.0}/src/infinity_data/sandbox/mediator.py +14 -0
- {infinity_data-2.2.1 → infinity_data-3.2.0}/src/infinity_data/semantic/__init__.py +4 -0
- {infinity_data-2.2.1 → infinity_data-3.2.0}/src/infinity_data/semantic/builder/builder.py +24 -1
- {infinity_data-2.2.1 → infinity_data-3.2.0}/src/infinity_data/semantic/constraints.py +3 -0
- {infinity_data-2.2.1 → infinity_data-3.2.0}/src/infinity_data/semantic/diagnostics.py +60 -0
- {infinity_data-2.2.1 → infinity_data-3.2.0}/src/infinity_data/semantic/executor/executor.py +23 -2
- {infinity_data-2.2.1 → infinity_data-3.2.0}/src/infinity_data/semantic/registry/__init__.py +37 -0
- {infinity_data-2.2.1 → infinity_data-3.2.0}/src/infinity_data/semantic/registry/_core.py +31 -5
- {infinity_data-2.2.1 → infinity_data-3.2.0}/src/infinity_data/semantic/registry/general.py +16 -0
- {infinity_data-2.2.1 → infinity_data-3.2.0}/src/infinity_data/semantic/registry/logic.py +14 -3
- infinity_data-3.2.0/src/infinity_data/semantic/registry/types.py +321 -0
- {infinity_data-2.2.1 → infinity_data-3.2.0}/src/infinity_data/semantic/resolver/imports.py +1 -1
- {infinity_data-2.2.1 → infinity_data-3.2.0}/src/infinity_data/semantic/std.py +135 -5
- {infinity_data-2.2.1 → infinity_data-3.2.0}/src/infinity_data/tokenizer/diagnostics.py +5 -0
- {infinity_data-2.2.1 → infinity_data-3.2.0}/src/infinity_data/tokenizer/finalizer.py +26 -0
- {infinity_data-2.2.1 → infinity_data-3.2.0}/src/infinity_data/tokenizer/models/raw_tokens.py +3 -2
- {infinity_data-2.2.1 → infinity_data-3.2.0}/src/infinity_data/tokenizer/models/tokens.py +11 -0
- {infinity_data-2.2.1 → infinity_data-3.2.0}/src/infinity_data/tokenizer/tokenizer.py +57 -17
- infinity_data-3.2.0/src/infinity_data/tools/__init__.py +7 -0
- infinity_data-3.2.0/src/infinity_data/tools/cov/__init__.py +30 -0
- infinity_data-3.2.0/src/infinity_data/tools/cov/__main__.py +6 -0
- infinity_data-3.2.0/src/infinity_data/tools/cov/cli.py +120 -0
- infinity_data-3.2.0/src/infinity_data/tools/cov/convert.py +47 -0
- infinity_data-3.2.0/src/infinity_data/tools/lsp/__init__.py +31 -0
- infinity_data-3.2.0/src/infinity_data/tools/lsp/__main__.py +6 -0
- infinity_data-3.2.0/src/infinity_data/tools/lsp/language.py +1045 -0
- infinity_data-3.2.0/src/infinity_data/tools/lsp/positions.py +78 -0
- infinity_data-3.2.0/src/infinity_data/tools/lsp/protocol.py +76 -0
- infinity_data-3.2.0/src/infinity_data/tools/lsp/server.py +210 -0
- infinity_data-3.2.0/src/infinity_data.egg-info/PKG-INFO +156 -0
- {infinity_data-2.2.1 → infinity_data-3.2.0}/src/infinity_data.egg-info/SOURCES.txt +16 -1
- infinity_data-3.2.0/src/infinity_data.egg-info/entry_points.txt +3 -0
- infinity_data-3.2.0/src/infinity_data.egg-info/requires.txt +4 -0
- infinity_data-2.2.1/PKG-INFO +0 -60
- infinity_data-2.2.1/README.md +0 -38
- infinity_data-2.2.1/src/infinity_data/emit/__init__.py +0 -9
- infinity_data-2.2.1/src/infinity_data/emit/converter.py +0 -56
- infinity_data-2.2.1/src/infinity_data/semantic/registry/types.py +0 -136
- infinity_data-2.2.1/src/infinity_data.egg-info/PKG-INFO +0 -60
- {infinity_data-2.2.1 → infinity_data-3.2.0}/LICENSE +0 -0
- {infinity_data-2.2.1 → infinity_data-3.2.0}/setup.cfg +0 -0
- {infinity_data-2.2.1 → infinity_data-3.2.0}/src/infinity_data/__init__.py +0 -0
- {infinity_data-2.2.1 → infinity_data-3.2.0}/src/infinity_data/frontend.py +0 -0
- {infinity_data-2.2.1 → infinity_data-3.2.0}/src/infinity_data/infra/__init__.py +0 -0
- {infinity_data-2.2.1 → infinity_data-3.2.0}/src/infinity_data/infra/diagnostics.py +0 -0
- {infinity_data-2.2.1 → infinity_data-3.2.0}/src/infinity_data/infra/file.py +0 -0
- {infinity_data-2.2.1 → infinity_data-3.2.0}/src/infinity_data/infra/ll1_stream.py +0 -0
- {infinity_data-2.2.1 → infinity_data-3.2.0}/src/infinity_data/infra/location.py +0 -0
- {infinity_data-2.2.1 → infinity_data-3.2.0}/src/infinity_data/parser/__init__.py +0 -0
- {infinity_data-2.2.1 → infinity_data-3.2.0}/src/infinity_data/parser/token_stream.py +0 -0
- {infinity_data-2.2.1 → infinity_data-3.2.0}/src/infinity_data/py.typed +0 -0
- {infinity_data-2.2.1 → infinity_data-3.2.0}/src/infinity_data/sandbox/__init__.py +0 -0
- {infinity_data-2.2.1 → infinity_data-3.2.0}/src/infinity_data/sandbox/config.py +0 -0
- {infinity_data-2.2.1 → infinity_data-3.2.0}/src/infinity_data/sandbox/errors.py +0 -0
- {infinity_data-2.2.1 → infinity_data-3.2.0}/src/infinity_data/sandbox/schema.py +0 -0
- {infinity_data-2.2.1 → infinity_data-3.2.0}/src/infinity_data/semantic/builder/__init__.py +0 -0
- {infinity_data-2.2.1 → infinity_data-3.2.0}/src/infinity_data/semantic/builder/models.py +0 -0
- {infinity_data-2.2.1 → infinity_data-3.2.0}/src/infinity_data/semantic/executor/__init__.py +0 -0
- {infinity_data-2.2.1 → infinity_data-3.2.0}/src/infinity_data/semantic/jsonpath.py +0 -0
- {infinity_data-2.2.1 → infinity_data-3.2.0}/src/infinity_data/semantic/registry/dict_constraints.py +0 -0
- {infinity_data-2.2.1 → infinity_data-3.2.0}/src/infinity_data/semantic/resolver/__init__.py +0 -0
- {infinity_data-2.2.1 → infinity_data-3.2.0}/src/infinity_data/semantic/resolver/identity.py +0 -0
- {infinity_data-2.2.1 → infinity_data-3.2.0}/src/infinity_data/semantic/resolver/models.py +0 -0
- {infinity_data-2.2.1 → infinity_data-3.2.0}/src/infinity_data/semantic/resolver/resolver.py +0 -0
- {infinity_data-2.2.1 → infinity_data-3.2.0}/src/infinity_data/tokenizer/__init__.py +0 -0
- {infinity_data-2.2.1 → infinity_data-3.2.0}/src/infinity_data/tokenizer/char_stream.py +0 -0
- {infinity_data-2.2.1 → infinity_data-3.2.0}/src/infinity_data/tokenizer/models/__init__.py +0 -0
- {infinity_data-2.2.1 → infinity_data-3.2.0}/src/infinity_data.egg-info/dependency_links.txt +0 -0
- {infinity_data-2.2.1 → infinity_data-3.2.0}/src/infinity_data.egg-info/top_level.txt +0 -0
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: infinity-data
|
|
3
|
+
Version: 3.2.0
|
|
4
|
+
Summary: InfinityData:声明式配置语言(.infd/.inft)的 Python 编译器库
|
|
5
|
+
Author: infinity_system: yin_bailiang
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Repository, https://github.com/yinbailiang/infinity_data
|
|
8
|
+
Project-URL: Documentation, https://github.com/yinbailiang/infinity_data#readme
|
|
9
|
+
Keywords: config,configuration,declarative,dsl,compiler,infinity-data
|
|
10
|
+
Classifier: Development Status :: 5 - Production/Stable
|
|
11
|
+
Classifier: Intended Audience :: Developers
|
|
12
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
13
|
+
Classifier: Operating System :: OS Independent
|
|
14
|
+
Classifier: Programming Language :: Python :: 3
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
17
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
18
|
+
Requires-Python: >=3.12
|
|
19
|
+
Description-Content-Type: text/markdown
|
|
20
|
+
License-File: LICENSE
|
|
21
|
+
Provides-Extra: tool
|
|
22
|
+
Requires-Dist: pyyaml; extra == "tool"
|
|
23
|
+
Requires-Dist: tomli-w; extra == "tool"
|
|
24
|
+
Dynamic: license-file
|
|
25
|
+
|
|
26
|
+
# InfinityData
|
|
27
|
+
|
|
28
|
+
声明式配置语言(`.infd` / `.inft`)的 Python 编译器库。
|
|
29
|
+
|
|
30
|
+
- **编译优先**:`.infd` 是源码,JSON/YAML/TOML 是构建产物
|
|
31
|
+
- **模板即约束**:模板定义自动注册为约束校验器
|
|
32
|
+
- **零信任默认**:库默认 `deny_all` 沙盒,显式开放而非事后限制
|
|
33
|
+
- **结构化错误**:稳定错误码 + 结构化参数 + 多语言渲染
|
|
34
|
+
|
|
35
|
+
## 快速使用
|
|
36
|
+
|
|
37
|
+
```bash
|
|
38
|
+
uv add infinity_data # 或 pip install infinity-data
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
```python
|
|
42
|
+
from infinity_data import load, safe_load, SandboxConfig, Schema
|
|
43
|
+
|
|
44
|
+
result = load('app.infd') # 默认零信任
|
|
45
|
+
if result.has_errors:
|
|
46
|
+
for d in result.diagnostics:
|
|
47
|
+
print(d.location, d.code, d.message)
|
|
48
|
+
else:
|
|
49
|
+
print(result.value) # 降维后的 dict
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
## 文档导航
|
|
53
|
+
|
|
54
|
+
| 文档 | 内容 |
|
|
55
|
+
|---|---|
|
|
56
|
+
| [neo_desg.md](./neo_desg.md) | 语言基础设计(语法 / 类型 / 模板 / 约束) |
|
|
57
|
+
| [extra_desg.md](./extra_desg.md) | 库 API / CLI / 生态设计 |
|
|
58
|
+
| [impl_desg.md](./impl_desg.md) | 编译器实现层的取舍与假设 |
|
|
59
|
+
|
|
60
|
+
## 官方工具
|
|
61
|
+
|
|
62
|
+
随库内置两个命令行工具(`infd-lsp` 语言服务器 + `infd-cov` 配置转换器),
|
|
63
|
+
零依赖手写 JSON-RPC / emitter,直接消费编译器的公开 API
|
|
64
|
+
(`compile_source` / `parse_source` / `RawTokenizer` / emit / semantic)。
|
|
65
|
+
工具的**可选运行依赖**(PyYAML / tomli-w)由 `tool` extra 提供,一次装齐:
|
|
66
|
+
|
|
67
|
+
```bash
|
|
68
|
+
uv add 'infinity_data[tool]' # 可选:!file 的 yaml 导入 + infd-cov -f toml 输出
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
### 配置转换(infd-cov)
|
|
72
|
+
|
|
73
|
+
编译 `.infd` / `.inft` → 各种配置格式(YAML / JSON / TOML):
|
|
74
|
+
|
|
75
|
+
```bash
|
|
76
|
+
infd-cov app.infd # → YAML(stdout,零依赖 emitter)
|
|
77
|
+
infd-cov app.infd out.json -f json # → JSON 文件
|
|
78
|
+
infd-cov app.infd -f toml # → TOML(需 tomli-w,见 tool extra)
|
|
79
|
+
infd-cov app.infd --extract web.spec # → 点分路径提取
|
|
80
|
+
cat a.infd | infd-cov -f json # stdin → stdout
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
- `--format`:`yaml`(默认,零依赖手写 emitter)/ `json` / `toml`
|
|
84
|
+
- `--extract PATH`:点分路径(如 `web.spec.selector`);空 = 整树
|
|
85
|
+
- `--severity LEVEL`:拒绝阈值(`error`/`warning`/`info`/`debug`/`none`,默认 `error`)
|
|
86
|
+
- `#env: NAME "VALUE"` 声明的变量在当前进程缺失时自动注入虚拟值
|
|
87
|
+
- 演示文件:`examples/app.infd`(干净配置)/ `examples/demo.infd`(含故意错误)
|
|
88
|
+
|
|
89
|
+
### 语言服务器(LSP)
|
|
90
|
+
|
|
91
|
+
`infd-lsp`:零依赖手写 JSON-RPC 2.0 over stdio 语言服务器。
|
|
92
|
+
|
|
93
|
+
```bash
|
|
94
|
+
infd-lsp # 作为任意 LSP 客户端的后端
|
|
95
|
+
# 或
|
|
96
|
+
python -m infinity_data.tools.lsp
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
#### 已实现的能力
|
|
100
|
+
|
|
101
|
+
| 能力 | 说明 |
|
|
102
|
+
|---|---|
|
|
103
|
+
| 诊断 | `didOpen`/`didChange`/`didSave` → `publishDiagnostics`(精确位置 + 稳定错误码 + 中文消息;UTF-16 对齐) |
|
|
104
|
+
| 补全 | 命名空间(`$` 触发)+ 内置约束名 + 当前文件可见模板名 + 模板字段(含必填/可选)+ 语言关键字 |
|
|
105
|
+
| 悬停 | 约束描述、**编译产物预览**(字段/子字段/模板参数,jsonpath 投影)、**被实例化的模板**(模板骨架提示在前 + 实例预览在后)、**`$` 变量值**(定义点与调用点)、模板结构骨架(必填/可选 + description 元数据) |
|
|
106
|
+
| 文档大纲 | `documentSymbol`:模板定义(Class)+ 顶层字段(Property) |
|
|
107
|
+
| 跳转定义 | `definition`:模板调用 / `!from` 导入 / `$` 引用(含跨文件) |
|
|
108
|
+
| 语义令牌 | `semanticTokens`:直接用 `RawTokenizer` 分词(UTF-16 单元对齐;跨行多行字符串按行拆分) |
|
|
109
|
+
| 虚拟环境变量 | 扫描 `#env: NAME "VALUE"` 注释,进程未设置时自动注入虚拟值,避免 `env_not_set` 中止编译 |
|
|
110
|
+
|
|
111
|
+
#### 在 VS Code 中使用
|
|
112
|
+
|
|
113
|
+
在项目 `.vscode/settings.json` 里用任意支持自定义 LSP 的扩展(如 `lspConfig`)配置:
|
|
114
|
+
|
|
115
|
+
```json
|
|
116
|
+
{
|
|
117
|
+
"lspConfig.servers": [
|
|
118
|
+
{ "language": "infd", "cmd": ["infd-lsp"] },
|
|
119
|
+
{ "language": "inft", "cmd": ["infd-lsp"] }
|
|
120
|
+
]
|
|
121
|
+
}
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
打开 `.infd` / `.inft` 文件即可获得:诊断波浪线 + 语法高亮(semantic tokens)+ 补全 + 悬停 + 大纲 + 跳转定义。
|
|
125
|
+
|
|
126
|
+
#### 端到端演示
|
|
127
|
+
|
|
128
|
+
```bash
|
|
129
|
+
uv run python examples/lsp_demo.py # 自动起服务器 + 走一遍握手 + 打印结果
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
演示文件在 `examples/demo.infd`(故意含若干错误,展示诊断 / 补全 / 悬停 / 高亮)。
|
|
133
|
+
|
|
134
|
+
### 虚拟环境变量(`#env` 注释)
|
|
135
|
+
|
|
136
|
+
`.infd` / `.inft` 里可以用**特殊注释**声明环境变量名和虚拟值,工具(LSP / `infd-cov`)
|
|
137
|
+
在当前进程缺失该变量时自动注入,避免 `env_not_set` 中止编译、吞掉同文件其他诊断:
|
|
138
|
+
|
|
139
|
+
```infd
|
|
140
|
+
#env: DEEPSEEK_API_KEY "sk-virtual-key"
|
|
141
|
+
!env import DEEPSEEK_API_KEY as ds_key
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
- 语法:`#env: NAME "VALUE"` —— **变量名在前、虚拟值在后**
|
|
145
|
+
- 真实环境变量优先:进程里已设置时用真实值,只有缺失时才用注释里的虚拟值兜底
|
|
146
|
+
- 动机:`infinity_data` 沙盒在 env 未授权/未设置时会中止编译;用 `#env` 声明兜底后,
|
|
147
|
+
编辑器/CLI 始终能看到全部诊断(如约束违反、未定义模板)
|
|
148
|
+
|
|
149
|
+
## 开发
|
|
150
|
+
|
|
151
|
+
```bash
|
|
152
|
+
uv run pytest -q # 测试
|
|
153
|
+
uv run ruff check src tests # lint
|
|
154
|
+
uv run pyright # 类型检查(strict)
|
|
155
|
+
uv run python test.py # 错误报告演示
|
|
156
|
+
```
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
# InfinityData
|
|
2
|
+
|
|
3
|
+
声明式配置语言(`.infd` / `.inft`)的 Python 编译器库。
|
|
4
|
+
|
|
5
|
+
- **编译优先**:`.infd` 是源码,JSON/YAML/TOML 是构建产物
|
|
6
|
+
- **模板即约束**:模板定义自动注册为约束校验器
|
|
7
|
+
- **零信任默认**:库默认 `deny_all` 沙盒,显式开放而非事后限制
|
|
8
|
+
- **结构化错误**:稳定错误码 + 结构化参数 + 多语言渲染
|
|
9
|
+
|
|
10
|
+
## 快速使用
|
|
11
|
+
|
|
12
|
+
```bash
|
|
13
|
+
uv add infinity_data # 或 pip install infinity-data
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
```python
|
|
17
|
+
from infinity_data import load, safe_load, SandboxConfig, Schema
|
|
18
|
+
|
|
19
|
+
result = load('app.infd') # 默认零信任
|
|
20
|
+
if result.has_errors:
|
|
21
|
+
for d in result.diagnostics:
|
|
22
|
+
print(d.location, d.code, d.message)
|
|
23
|
+
else:
|
|
24
|
+
print(result.value) # 降维后的 dict
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
## 文档导航
|
|
28
|
+
|
|
29
|
+
| 文档 | 内容 |
|
|
30
|
+
|---|---|
|
|
31
|
+
| [neo_desg.md](./neo_desg.md) | 语言基础设计(语法 / 类型 / 模板 / 约束) |
|
|
32
|
+
| [extra_desg.md](./extra_desg.md) | 库 API / CLI / 生态设计 |
|
|
33
|
+
| [impl_desg.md](./impl_desg.md) | 编译器实现层的取舍与假设 |
|
|
34
|
+
|
|
35
|
+
## 官方工具
|
|
36
|
+
|
|
37
|
+
随库内置两个命令行工具(`infd-lsp` 语言服务器 + `infd-cov` 配置转换器),
|
|
38
|
+
零依赖手写 JSON-RPC / emitter,直接消费编译器的公开 API
|
|
39
|
+
(`compile_source` / `parse_source` / `RawTokenizer` / emit / semantic)。
|
|
40
|
+
工具的**可选运行依赖**(PyYAML / tomli-w)由 `tool` extra 提供,一次装齐:
|
|
41
|
+
|
|
42
|
+
```bash
|
|
43
|
+
uv add 'infinity_data[tool]' # 可选:!file 的 yaml 导入 + infd-cov -f toml 输出
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
### 配置转换(infd-cov)
|
|
47
|
+
|
|
48
|
+
编译 `.infd` / `.inft` → 各种配置格式(YAML / JSON / TOML):
|
|
49
|
+
|
|
50
|
+
```bash
|
|
51
|
+
infd-cov app.infd # → YAML(stdout,零依赖 emitter)
|
|
52
|
+
infd-cov app.infd out.json -f json # → JSON 文件
|
|
53
|
+
infd-cov app.infd -f toml # → TOML(需 tomli-w,见 tool extra)
|
|
54
|
+
infd-cov app.infd --extract web.spec # → 点分路径提取
|
|
55
|
+
cat a.infd | infd-cov -f json # stdin → stdout
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
- `--format`:`yaml`(默认,零依赖手写 emitter)/ `json` / `toml`
|
|
59
|
+
- `--extract PATH`:点分路径(如 `web.spec.selector`);空 = 整树
|
|
60
|
+
- `--severity LEVEL`:拒绝阈值(`error`/`warning`/`info`/`debug`/`none`,默认 `error`)
|
|
61
|
+
- `#env: NAME "VALUE"` 声明的变量在当前进程缺失时自动注入虚拟值
|
|
62
|
+
- 演示文件:`examples/app.infd`(干净配置)/ `examples/demo.infd`(含故意错误)
|
|
63
|
+
|
|
64
|
+
### 语言服务器(LSP)
|
|
65
|
+
|
|
66
|
+
`infd-lsp`:零依赖手写 JSON-RPC 2.0 over stdio 语言服务器。
|
|
67
|
+
|
|
68
|
+
```bash
|
|
69
|
+
infd-lsp # 作为任意 LSP 客户端的后端
|
|
70
|
+
# 或
|
|
71
|
+
python -m infinity_data.tools.lsp
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
#### 已实现的能力
|
|
75
|
+
|
|
76
|
+
| 能力 | 说明 |
|
|
77
|
+
|---|---|
|
|
78
|
+
| 诊断 | `didOpen`/`didChange`/`didSave` → `publishDiagnostics`(精确位置 + 稳定错误码 + 中文消息;UTF-16 对齐) |
|
|
79
|
+
| 补全 | 命名空间(`$` 触发)+ 内置约束名 + 当前文件可见模板名 + 模板字段(含必填/可选)+ 语言关键字 |
|
|
80
|
+
| 悬停 | 约束描述、**编译产物预览**(字段/子字段/模板参数,jsonpath 投影)、**被实例化的模板**(模板骨架提示在前 + 实例预览在后)、**`$` 变量值**(定义点与调用点)、模板结构骨架(必填/可选 + description 元数据) |
|
|
81
|
+
| 文档大纲 | `documentSymbol`:模板定义(Class)+ 顶层字段(Property) |
|
|
82
|
+
| 跳转定义 | `definition`:模板调用 / `!from` 导入 / `$` 引用(含跨文件) |
|
|
83
|
+
| 语义令牌 | `semanticTokens`:直接用 `RawTokenizer` 分词(UTF-16 单元对齐;跨行多行字符串按行拆分) |
|
|
84
|
+
| 虚拟环境变量 | 扫描 `#env: NAME "VALUE"` 注释,进程未设置时自动注入虚拟值,避免 `env_not_set` 中止编译 |
|
|
85
|
+
|
|
86
|
+
#### 在 VS Code 中使用
|
|
87
|
+
|
|
88
|
+
在项目 `.vscode/settings.json` 里用任意支持自定义 LSP 的扩展(如 `lspConfig`)配置:
|
|
89
|
+
|
|
90
|
+
```json
|
|
91
|
+
{
|
|
92
|
+
"lspConfig.servers": [
|
|
93
|
+
{ "language": "infd", "cmd": ["infd-lsp"] },
|
|
94
|
+
{ "language": "inft", "cmd": ["infd-lsp"] }
|
|
95
|
+
]
|
|
96
|
+
}
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
打开 `.infd` / `.inft` 文件即可获得:诊断波浪线 + 语法高亮(semantic tokens)+ 补全 + 悬停 + 大纲 + 跳转定义。
|
|
100
|
+
|
|
101
|
+
#### 端到端演示
|
|
102
|
+
|
|
103
|
+
```bash
|
|
104
|
+
uv run python examples/lsp_demo.py # 自动起服务器 + 走一遍握手 + 打印结果
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
演示文件在 `examples/demo.infd`(故意含若干错误,展示诊断 / 补全 / 悬停 / 高亮)。
|
|
108
|
+
|
|
109
|
+
### 虚拟环境变量(`#env` 注释)
|
|
110
|
+
|
|
111
|
+
`.infd` / `.inft` 里可以用**特殊注释**声明环境变量名和虚拟值,工具(LSP / `infd-cov`)
|
|
112
|
+
在当前进程缺失该变量时自动注入,避免 `env_not_set` 中止编译、吞掉同文件其他诊断:
|
|
113
|
+
|
|
114
|
+
```infd
|
|
115
|
+
#env: DEEPSEEK_API_KEY "sk-virtual-key"
|
|
116
|
+
!env import DEEPSEEK_API_KEY as ds_key
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
- 语法:`#env: NAME "VALUE"` —— **变量名在前、虚拟值在后**
|
|
120
|
+
- 真实环境变量优先:进程里已设置时用真实值,只有缺失时才用注释里的虚拟值兜底
|
|
121
|
+
- 动机:`infinity_data` 沙盒在 env 未授权/未设置时会中止编译;用 `#env` 声明兜底后,
|
|
122
|
+
编辑器/CLI 始终能看到全部诊断(如约束违反、未定义模板)
|
|
123
|
+
|
|
124
|
+
## 开发
|
|
125
|
+
|
|
126
|
+
```bash
|
|
127
|
+
uv run pytest -q # 测试
|
|
128
|
+
uv run ruff check src tests # lint
|
|
129
|
+
uv run pyright # 类型检查(strict)
|
|
130
|
+
uv run python test.py # 错误报告演示
|
|
131
|
+
```
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "infinity-data"
|
|
3
|
-
version = "
|
|
3
|
+
version = "3.2.0"
|
|
4
4
|
description = "InfinityData:声明式配置语言(.infd/.inft)的 Python 编译器库"
|
|
5
5
|
readme = "README.md"
|
|
6
6
|
license = { text = "MIT" }
|
|
@@ -28,16 +28,33 @@ classifiers = [
|
|
|
28
28
|
requires-python = ">=3.12"
|
|
29
29
|
dependencies = []
|
|
30
30
|
|
|
31
|
+
[project.scripts]
|
|
32
|
+
infd-lsp = "infinity_data.tools.lsp.__main__:main"
|
|
33
|
+
infd-cov = "infinity_data.tools.cov.__main__:main"
|
|
34
|
+
|
|
35
|
+
[project.optional-dependencies]
|
|
36
|
+
# 工具 extra:infd-lsp / infd-cov 的可选运行依赖(uv add 'infinity_data[tool]')
|
|
37
|
+
tool = [
|
|
38
|
+
"pyyaml", # !file 的 yaml/yml 格式导入(LSP / 编译器)
|
|
39
|
+
"tomli-w", # infd-cov -f toml 输出
|
|
40
|
+
]
|
|
41
|
+
|
|
31
42
|
[project.urls]
|
|
32
43
|
Repository = "https://github.com/yinbailiang/infinity_data"
|
|
33
44
|
Documentation = "https://github.com/yinbailiang/infinity_data#readme"
|
|
34
45
|
|
|
35
46
|
[dependency-groups]
|
|
47
|
+
# 工具组:cli/lsp 工具的可选运行依赖(测试/开发组保证安装,测试可依赖它们存在)
|
|
48
|
+
tool = [
|
|
49
|
+
"tomli-w", # infd-cov -f toml 输出
|
|
50
|
+
"pyyaml", # !file 的 yaml/yml 格式导入(LSP / 编译器)
|
|
51
|
+
]
|
|
36
52
|
# 测试组:仅运行测试所需(CI test job 只装这组,更快)
|
|
37
53
|
test = [
|
|
38
54
|
"pyyaml",
|
|
39
55
|
"pytest>=8.0",
|
|
40
56
|
"pytest-cov>=5.0",
|
|
57
|
+
{ include-group = "tool" },
|
|
41
58
|
]
|
|
42
59
|
# 开发组:lint/类型/钩子 + 测试组(本地开发与 CI pre-commit job 装这组)
|
|
43
60
|
dev = [
|
|
@@ -95,6 +112,6 @@ venvPath = "."
|
|
|
95
112
|
venv = ".venv"
|
|
96
113
|
|
|
97
114
|
[tool.interrogate]
|
|
98
|
-
fail-under =
|
|
115
|
+
fail-under = 60
|
|
99
116
|
# 测试函数名自解释,docstring 可选;产品代码(src)不豁免
|
|
100
117
|
ignore-regex = ["^test_"]
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
"""产物发射层:Python / std → 输出格式(JSON / YAML / TOML)。
|
|
2
|
+
|
|
3
|
+
与 semantic 层边界:
|
|
4
|
+
- **内部表示(忠实双向)** → :mod:`infinity_data.semantic.std` 的
|
|
5
|
+
``python_to_std`` / ``std_to_python``(path → PurePosixPath、float → Decimal、
|
|
6
|
+
noexist → :data:`~infinity_data.semantic.std.NOEXIST` 哨兵)
|
|
7
|
+
- **本层(emit,有损投影)** → :func:`to_json` / :func:`to_yaml` / :func:`to_toml`,
|
|
8
|
+
按 :class:`EmitConfig` 控制 Decimal 编码与 noexist 标记;:func:`project_output`
|
|
9
|
+
为纯 Python 值的轻量投影;:func:`restore_python` 反向**尝试还原**加载的原始
|
|
10
|
+
dict 为 :data:`~infinity_data.semantic.std.StdPythonValue`;:func:`yaml_dump`
|
|
11
|
+
为零依赖 YAML block emitter(覆盖配置常用值,不依赖 PyYAML)
|
|
12
|
+
|
|
13
|
+
**emit 不应被流水线内部使用**——内部表示一律走 semantic 的忠实转换。
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
from infinity_data.emit.config import EmitConfig
|
|
17
|
+
from infinity_data.emit.converter import (
|
|
18
|
+
dump_to_json,
|
|
19
|
+
load_from_json,
|
|
20
|
+
project_output,
|
|
21
|
+
restore_python,
|
|
22
|
+
to_json,
|
|
23
|
+
to_toml,
|
|
24
|
+
to_yaml,
|
|
25
|
+
)
|
|
26
|
+
from infinity_data.emit.plain_yaml import yaml_dump
|
|
27
|
+
|
|
28
|
+
__all__ = [
|
|
29
|
+
'EmitConfig',
|
|
30
|
+
'dump_to_json',
|
|
31
|
+
'load_from_json',
|
|
32
|
+
'project_output',
|
|
33
|
+
'restore_python',
|
|
34
|
+
'to_json',
|
|
35
|
+
'to_toml',
|
|
36
|
+
'to_yaml',
|
|
37
|
+
'yaml_dump',
|
|
38
|
+
]
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
"""输出转换配置(emit 层):控制 std/python → 输出格式的有损投影语义。"""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from dataclasses import dataclass
|
|
6
|
+
|
|
7
|
+
__all__ = ['EmitConfig']
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
@dataclass(frozen=True)
|
|
11
|
+
class EmitConfig:
|
|
12
|
+
"""emit 层输出转换配置。
|
|
13
|
+
|
|
14
|
+
转换语义:
|
|
15
|
+
- ``full_float``:``float`` 值(:class:`decimal.Decimal`)以自描述标记
|
|
16
|
+
``{"__type__": "decimal", "num": "<十进制字符串>"}`` 编码(无损);False 时
|
|
17
|
+
有限值转普通 JSON 数字,NaN / ±Infinity 无法用 JSON 数字表达 → 仍以标记编码
|
|
18
|
+
- ``full_path``:路径值(:class:`PurePosixPath`)以自描述标记
|
|
19
|
+
``{"__type__": "path", "path": "<POSIX 字符串>"}`` 编码(可辨识「这是路径」);
|
|
20
|
+
False 时投影为普通 POSIX 字符串
|
|
21
|
+
- ``keep_noexist``:``noexist`` 字段保留为 ``{"__type__": "noexist"}`` 标记
|
|
22
|
+
(依赖 :data:`~infinity_data.semantic.std.NOEXIST` 哨兵保留三态);默认丢弃
|
|
23
|
+
(键不出现,§1.6)
|
|
24
|
+
|
|
25
|
+
序列化选项:
|
|
26
|
+
- ``indent`` / ``sort_keys`` / ``ensure_ascii``:JSON 缩进 / 排序 / ASCII 转义
|
|
27
|
+
"""
|
|
28
|
+
|
|
29
|
+
# ── 转换语义 ──────────────────────────────────────
|
|
30
|
+
full_float: bool = False
|
|
31
|
+
full_path: bool = False
|
|
32
|
+
keep_noexist: bool = False
|
|
33
|
+
|
|
34
|
+
# ── 序列化选项 ────────────────────────────────────
|
|
35
|
+
indent: int | None = 2
|
|
36
|
+
sort_keys: bool = False
|
|
37
|
+
ensure_ascii: bool = False
|
|
@@ -0,0 +1,258 @@
|
|
|
1
|
+
"""产物发射层:Python / std → 输出格式(JSON / YAML / TOML)。
|
|
2
|
+
|
|
3
|
+
职责边界(与 semantic 层严格分离):
|
|
4
|
+
|
|
5
|
+
- **内部表示**(忠实双向,语义最小丢失)::func:`~infinity_data.semantic.std.python_to_std`
|
|
6
|
+
与 :func:`~infinity_data.semantic.std.std_to_python`——path 保持
|
|
7
|
+
:class:`PurePosixPath`、float 保持 :class:`decimal.Decimal`、noexist 经
|
|
8
|
+
:data:`~infinity_data.semantic.std.NOEXIST` 哨兵保留三态。
|
|
9
|
+
流水线内部(如 :class:`~infinity_data.pipeline.CompilationResult.value`)一律走该层。
|
|
10
|
+
- **本层(emit)**:做**有损投影**供输出格式消费,无需担心完整性——
|
|
11
|
+
path → POSIX 字符串;Decimal 的 NaN / ±Infinity 等特殊编码由本层按
|
|
12
|
+
:class:`EmitConfig` 决定(``full_float`` 标记编码)。
|
|
13
|
+
|
|
14
|
+
入口:
|
|
15
|
+
- :func:`to_json` / :func:`to_yaml` / :func:`to_toml`:输出文本(接受 std 或 Python 值)
|
|
16
|
+
- :func:`project_output`:纯 Python 值的轻量有损投影(path → 字符串)
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
from __future__ import annotations
|
|
20
|
+
|
|
21
|
+
import importlib
|
|
22
|
+
import json
|
|
23
|
+
from decimal import Decimal, InvalidOperation
|
|
24
|
+
from pathlib import PurePath, PurePosixPath
|
|
25
|
+
from typing import Any, cast
|
|
26
|
+
|
|
27
|
+
from infinity_data.emit.config import EmitConfig
|
|
28
|
+
from infinity_data.semantic.std import NOEXIST, StdPythonValue, is_std_value, std_to_python
|
|
29
|
+
|
|
30
|
+
__all__ = [
|
|
31
|
+
'dump_to_json',
|
|
32
|
+
'load_from_json',
|
|
33
|
+
'project_output',
|
|
34
|
+
'restore_python',
|
|
35
|
+
'to_json',
|
|
36
|
+
'to_yaml',
|
|
37
|
+
'to_toml',
|
|
38
|
+
]
|
|
39
|
+
|
|
40
|
+
# ── 自描述标记(输出格式无法原生表达的值的编码) ──────
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def _decimal_marker(dec: Any) -> dict[str, str]:
|
|
44
|
+
"""Decimal 自描述标记:``{"__type__": "decimal", "num": "<十进制字符串>"}``。"""
|
|
45
|
+
return {'__type__': 'decimal', 'num': str(dec)}
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def _path_marker(s: str) -> dict[str, str]:
|
|
49
|
+
"""路径自描述标记:``{"__type__": "path", "path": "<POSIX 字符串>"}``。"""
|
|
50
|
+
return {'__type__': 'path', 'path': s}
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def _noexist_marker() -> dict[str, str]:
|
|
54
|
+
"""noexist 自描述标记:``{"__type__": "noexist"}``。"""
|
|
55
|
+
return {'__type__': 'noexist'}
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
# ── 标记还原(反向:加载的原始 dict → StdPythonValue) ────
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def _restore_marker(value: dict[Any, Any]) -> Any | None:
|
|
62
|
+
"""若 dict 是 emit 自描述标记则还原为 Python 值,否则 None(非标记)。
|
|
63
|
+
|
|
64
|
+
仅识别 emit 层产出的**精确标记形状**;结构不符(额外键 / 负载非字符串 /
|
|
65
|
+
数值无法解析)一律视为普通 dict(返回 None,尽力而为)。
|
|
66
|
+
"""
|
|
67
|
+
typ = value.get('__type__')
|
|
68
|
+
if typ == 'decimal':
|
|
69
|
+
if set(value) == {'__type__', 'num'} and isinstance(value.get('num'), str):
|
|
70
|
+
try:
|
|
71
|
+
return Decimal(value['num'])
|
|
72
|
+
except (InvalidOperation, ValueError, TypeError):
|
|
73
|
+
return None
|
|
74
|
+
return None
|
|
75
|
+
if typ == 'path':
|
|
76
|
+
if set(value) == {'__type__', 'path'} and isinstance(value.get('path'), str):
|
|
77
|
+
return PurePosixPath(value['path'])
|
|
78
|
+
return None
|
|
79
|
+
if typ == 'noexist':
|
|
80
|
+
if set(value) == {'__type__'}:
|
|
81
|
+
return NOEXIST
|
|
82
|
+
return None
|
|
83
|
+
return None
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
def restore_python(value: Any) -> StdPythonValue:
|
|
87
|
+
"""从加载的原始 dict(含可选 emit 自描述标记)**尝试**还原为 StdPythonValue。
|
|
88
|
+
|
|
89
|
+
- ``{"__type__": "decimal", "num": "..."}`` → :class:`decimal.Decimal`
|
|
90
|
+
- ``{"__type__": "path", "path": "..."}`` → :class:`PurePosixPath`
|
|
91
|
+
- ``{"__type__": "noexist"}`` → :data:`NOEXIST`
|
|
92
|
+
- 普通 ``float`` → :class:`decimal.Decimal`(忠实形式,与
|
|
93
|
+
:func:`~infinity_data.semantic.std.python_to_std` 规范一致)
|
|
94
|
+
- int / bool / str / None 原样;dict / list 递归
|
|
95
|
+
- 识别不了的形状(含疑似标记但结构不符)按普通 dict / list 保留(尽力而为)
|
|
96
|
+
|
|
97
|
+
配合 :func:`to_json` 等 + ``EmitConfig(full_float=True, full_path=True,
|
|
98
|
+
keep_noexist=True)`` 可实现「std → 输出文本 → 加载 → StdPythonValue」
|
|
99
|
+
无损闭环(再经 :func:`~infinity_data.semantic.std.python_to_std` 回 std)。
|
|
100
|
+
"""
|
|
101
|
+
if isinstance(value, dict):
|
|
102
|
+
obj = cast(dict[Any, Any], value)
|
|
103
|
+
marker = _restore_marker(obj)
|
|
104
|
+
if marker is not None:
|
|
105
|
+
return marker
|
|
106
|
+
return {str(k): restore_python(v) for k, v in obj.items()}
|
|
107
|
+
if isinstance(value, list):
|
|
108
|
+
arr = cast(list[Any], value)
|
|
109
|
+
return [restore_python(v) for v in arr]
|
|
110
|
+
if isinstance(value, bool):
|
|
111
|
+
return value
|
|
112
|
+
if isinstance(value, int):
|
|
113
|
+
return value
|
|
114
|
+
if isinstance(value, float):
|
|
115
|
+
return Decimal(str(value))
|
|
116
|
+
return value
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
# ── 序列化核心 ──────────────────────────────────────
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
def _to_serializable(value: Any, config: EmitConfig) -> Any:
|
|
123
|
+
"""std 或 Python 值 → 可序列化结构(按 config 做有损投影)。"""
|
|
124
|
+
if is_std_value(value):
|
|
125
|
+
# std 输入:忠实转 Python(noexist 按需保留为 NOEXIST 哨兵),再投影
|
|
126
|
+
py = std_to_python(value, keep_noexist=config.keep_noexist)
|
|
127
|
+
return _serializable(py, config)
|
|
128
|
+
# Python 输入(如 CompilationResult.value):直接投影
|
|
129
|
+
return _serializable(value, config)
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
def _serializable(value: Any, config: EmitConfig) -> Any:
|
|
133
|
+
"""Python 值 → 可序列化结构(递归有损投影)。
|
|
134
|
+
|
|
135
|
+
- :data:`NOEXIST` 哨兵 → ``{"__type__": "noexist"}``
|
|
136
|
+
- :class:`PurePath` → POSIX 字符串
|
|
137
|
+
- :class:`decimal.Decimal` → 按 ``full_float`` 编码(有限值默认 JSON 数字)
|
|
138
|
+
- dict / list 递归;其余(int / bool / str / None 等)原样
|
|
139
|
+
"""
|
|
140
|
+
if value is NOEXIST:
|
|
141
|
+
return _noexist_marker()
|
|
142
|
+
if isinstance(value, PurePath):
|
|
143
|
+
s = value.as_posix()
|
|
144
|
+
return _path_marker(s) if config.full_path else s
|
|
145
|
+
if isinstance(value, dict):
|
|
146
|
+
obj = cast(dict[Any, Any], value)
|
|
147
|
+
return {k: _serializable(v, config) for k, v in obj.items()}
|
|
148
|
+
if isinstance(value, list):
|
|
149
|
+
arr = cast(list[Any], value)
|
|
150
|
+
return [_serializable(v, config) for v in arr]
|
|
151
|
+
if isinstance(value, Decimal):
|
|
152
|
+
return _decimal_serializable(value, config)
|
|
153
|
+
return value
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
def _decimal_serializable(dec: Any, config: EmitConfig) -> Any:
|
|
157
|
+
"""Decimal → 可序列化形式。
|
|
158
|
+
|
|
159
|
+
- ``full_float`` → 自描述标记(无损)
|
|
160
|
+
- 否则:有限值 → JSON 数字(整值转 int,避免 ``1.0``;溢出回退标记);
|
|
161
|
+
NaN / ±Infinity 无法用 JSON 数字表达 → 自描述标记
|
|
162
|
+
"""
|
|
163
|
+
if config.full_float:
|
|
164
|
+
return _decimal_marker(dec)
|
|
165
|
+
if dec.is_nan() or dec.is_infinite():
|
|
166
|
+
return _decimal_marker(dec)
|
|
167
|
+
try:
|
|
168
|
+
if dec == dec.to_integral_value():
|
|
169
|
+
return int(dec)
|
|
170
|
+
return float(dec)
|
|
171
|
+
except (OverflowError, ValueError):
|
|
172
|
+
return _decimal_marker(dec)
|
|
173
|
+
|
|
174
|
+
|
|
175
|
+
# ── 输出入口 ────────────────────────────────────────
|
|
176
|
+
|
|
177
|
+
|
|
178
|
+
def to_json(value: Any, *, config: EmitConfig | None = None) -> str:
|
|
179
|
+
"""std 或 Python 值 → JSON 文本。
|
|
180
|
+
|
|
181
|
+
``value`` 可为 :class:`~infinity_data.semantic.std.StdValue`(如
|
|
182
|
+
``CompilationResult.document.root``,支持完整选项)或 Python 值
|
|
183
|
+
(如 ``CompilationResult.value``,noexist 信息已在转换时丢失)。
|
|
184
|
+
"""
|
|
185
|
+
cfg = config or EmitConfig()
|
|
186
|
+
data = _to_serializable(value, cfg)
|
|
187
|
+
return json.dumps(data, indent=cfg.indent, sort_keys=cfg.sort_keys, ensure_ascii=cfg.ensure_ascii)
|
|
188
|
+
|
|
189
|
+
|
|
190
|
+
def to_yaml(value: Any, *, config: EmitConfig | None = None) -> str:
|
|
191
|
+
"""std 或 Python 值 → YAML 文本(需安装 PyYAML)。"""
|
|
192
|
+
cfg = config or EmitConfig()
|
|
193
|
+
data = _to_serializable(value, cfg)
|
|
194
|
+
try:
|
|
195
|
+
yaml = importlib.import_module('yaml')
|
|
196
|
+
except ImportError as exc: # pragma: no cover - 依赖缺失路径
|
|
197
|
+
raise ImportError('to_yaml 需要 PyYAML(pip install pyyaml)') from exc
|
|
198
|
+
return yaml.safe_dump(data, allow_unicode=not cfg.ensure_ascii, sort_keys=cfg.sort_keys)
|
|
199
|
+
|
|
200
|
+
|
|
201
|
+
def to_toml(value: Any, *, config: EmitConfig | None = None) -> str:
|
|
202
|
+
"""std 或 Python 值 → TOML 文本(需安装 tomli-w)。"""
|
|
203
|
+
cfg = config or EmitConfig()
|
|
204
|
+
data = _to_serializable(value, cfg)
|
|
205
|
+
try:
|
|
206
|
+
tomli_w = importlib.import_module('tomli_w')
|
|
207
|
+
except ImportError as exc: # pragma: no cover - 依赖缺失路径
|
|
208
|
+
raise NotImplementedError('to_toml 需要 tomli-w(pip install tomli-w);当前未安装') from exc
|
|
209
|
+
return tomli_w.dumps(data)
|
|
210
|
+
|
|
211
|
+
|
|
212
|
+
# ── 便捷自由函数(默认尝试保留一切语义) ────────────
|
|
213
|
+
|
|
214
|
+
|
|
215
|
+
def dump_to_json(value: Any, *, config: EmitConfig | None = None) -> str:
|
|
216
|
+
"""std / Python 值 → JSON 文本(便捷入口,**默认保留一切语义**)。
|
|
217
|
+
|
|
218
|
+
默认等价 ``EmitConfig(full_float=True, full_path=True, keep_noexist=True)``——
|
|
219
|
+
Decimal / PurePosixPath / noexist 以 ``{"__type__": ...}`` 标记编码;
|
|
220
|
+
传入 ``config`` 可覆盖(关闭标记、改缩进等,见 :class:`EmitConfig`)。
|
|
221
|
+
|
|
222
|
+
与 :func:`load_from_json` 配对可实现「std → JSON → StdPythonValue」无损闭环。
|
|
223
|
+
"""
|
|
224
|
+
cfg = config or EmitConfig(full_float=True, full_path=True, keep_noexist=True)
|
|
225
|
+
return to_json(value, config=cfg)
|
|
226
|
+
|
|
227
|
+
|
|
228
|
+
def load_from_json(text: str) -> StdPythonValue:
|
|
229
|
+
"""JSON 文本 → StdPythonValue(便捷入口,**默认尝试还原全部语义**)。
|
|
230
|
+
|
|
231
|
+
解析后经 :func:`restore_python`:``{"__type__": ...}`` 标记还原为
|
|
232
|
+
Decimal / PurePosixPath / NOEXIST,普通 float 归一到 Decimal;
|
|
233
|
+
识别不了的形状原样保留(尽力而为)。
|
|
234
|
+
"""
|
|
235
|
+
return restore_python(json.loads(text))
|
|
236
|
+
|
|
237
|
+
|
|
238
|
+
# ── 轻量有损投影(纯 Python 值) ─────────────────────
|
|
239
|
+
|
|
240
|
+
|
|
241
|
+
def project_output(value: Any) -> Any:
|
|
242
|
+
"""Python 值 → 输出友好投影(递归,有损,无配置)。
|
|
243
|
+
|
|
244
|
+
- :class:`PurePath`(含 :class:`PurePosixPath`)→ POSIX 字符串
|
|
245
|
+
- dict / list 递归投影;其余(int / Decimal / bool / str / None 等)原样保留
|
|
246
|
+
|
|
247
|
+
完整选项(Decimal 编码 / noexist 标记)请用 :func:`to_json` 等带
|
|
248
|
+
:class:`EmitConfig` 的入口。
|
|
249
|
+
"""
|
|
250
|
+
if isinstance(value, PurePath):
|
|
251
|
+
return value.as_posix()
|
|
252
|
+
if isinstance(value, dict):
|
|
253
|
+
obj = cast(dict[Any, Any], value)
|
|
254
|
+
return {k: project_output(v) for k, v in obj.items()}
|
|
255
|
+
if isinstance(value, list):
|
|
256
|
+
arr = cast(list[Any], value)
|
|
257
|
+
return [project_output(v) for v in arr]
|
|
258
|
+
return value
|