infinity-data 2.0.0__tar.gz → 2.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.
Files changed (60) hide show
  1. {infinity_data-2.0.0/src/infinity_data.egg-info → infinity_data-2.1.0}/PKG-INFO +1 -1
  2. {infinity_data-2.0.0 → infinity_data-2.1.0}/pyproject.toml +1 -1
  3. {infinity_data-2.0.0 → infinity_data-2.1.0}/src/infinity_data/parser/__init__.py +2 -0
  4. {infinity_data-2.0.0 → infinity_data-2.1.0}/src/infinity_data/parser/models.py +20 -6
  5. {infinity_data-2.0.0 → infinity_data-2.1.0}/src/infinity_data/parser/parser.py +43 -15
  6. {infinity_data-2.0.0 → infinity_data-2.1.0}/src/infinity_data/semantic/builder/builder.py +16 -0
  7. {infinity_data-2.0.0 → infinity_data-2.1.0}/src/infinity_data/semantic/constraints.py +1 -1
  8. {infinity_data-2.0.0 → infinity_data-2.1.0}/src/infinity_data/semantic/diagnostics.py +4 -0
  9. {infinity_data-2.0.0 → infinity_data-2.1.0}/src/infinity_data/semantic/registry/__init__.py +8 -0
  10. {infinity_data-2.0.0 → infinity_data-2.1.0}/src/infinity_data/semantic/registry/general.py +28 -0
  11. {infinity_data-2.0.0 → infinity_data-2.1.0}/src/infinity_data/semantic/resolver/imports.py +12 -4
  12. {infinity_data-2.0.0 → infinity_data-2.1.0/src/infinity_data.egg-info}/PKG-INFO +1 -1
  13. {infinity_data-2.0.0 → infinity_data-2.1.0}/LICENSE +0 -0
  14. {infinity_data-2.0.0 → infinity_data-2.1.0}/README.md +0 -0
  15. {infinity_data-2.0.0 → infinity_data-2.1.0}/setup.cfg +0 -0
  16. {infinity_data-2.0.0 → infinity_data-2.1.0}/src/infinity_data/__init__.py +0 -0
  17. {infinity_data-2.0.0 → infinity_data-2.1.0}/src/infinity_data/emit/__init__.py +0 -0
  18. {infinity_data-2.0.0 → infinity_data-2.1.0}/src/infinity_data/emit/converter.py +0 -0
  19. {infinity_data-2.0.0 → infinity_data-2.1.0}/src/infinity_data/frontend.py +0 -0
  20. {infinity_data-2.0.0 → infinity_data-2.1.0}/src/infinity_data/infra/__init__.py +0 -0
  21. {infinity_data-2.0.0 → infinity_data-2.1.0}/src/infinity_data/infra/diagnostics.py +0 -0
  22. {infinity_data-2.0.0 → infinity_data-2.1.0}/src/infinity_data/infra/file.py +0 -0
  23. {infinity_data-2.0.0 → infinity_data-2.1.0}/src/infinity_data/infra/ll1_stream.py +0 -0
  24. {infinity_data-2.0.0 → infinity_data-2.1.0}/src/infinity_data/infra/location.py +0 -0
  25. {infinity_data-2.0.0 → infinity_data-2.1.0}/src/infinity_data/infra/path.py +0 -0
  26. {infinity_data-2.0.0 → infinity_data-2.1.0}/src/infinity_data/parser/diagnostics.py +0 -0
  27. {infinity_data-2.0.0 → infinity_data-2.1.0}/src/infinity_data/parser/token_stream.py +0 -0
  28. {infinity_data-2.0.0 → infinity_data-2.1.0}/src/infinity_data/pipeline.py +0 -0
  29. {infinity_data-2.0.0 → infinity_data-2.1.0}/src/infinity_data/py.typed +0 -0
  30. {infinity_data-2.0.0 → infinity_data-2.1.0}/src/infinity_data/sandbox/__init__.py +0 -0
  31. {infinity_data-2.0.0 → infinity_data-2.1.0}/src/infinity_data/sandbox/config.py +0 -0
  32. {infinity_data-2.0.0 → infinity_data-2.1.0}/src/infinity_data/sandbox/errors.py +0 -0
  33. {infinity_data-2.0.0 → infinity_data-2.1.0}/src/infinity_data/sandbox/mediator.py +0 -0
  34. {infinity_data-2.0.0 → infinity_data-2.1.0}/src/infinity_data/sandbox/schema.py +0 -0
  35. {infinity_data-2.0.0 → infinity_data-2.1.0}/src/infinity_data/semantic/__init__.py +0 -0
  36. {infinity_data-2.0.0 → infinity_data-2.1.0}/src/infinity_data/semantic/builder/__init__.py +0 -0
  37. {infinity_data-2.0.0 → infinity_data-2.1.0}/src/infinity_data/semantic/builder/models.py +0 -0
  38. {infinity_data-2.0.0 → infinity_data-2.1.0}/src/infinity_data/semantic/executor/__init__.py +0 -0
  39. {infinity_data-2.0.0 → infinity_data-2.1.0}/src/infinity_data/semantic/executor/executor.py +0 -0
  40. {infinity_data-2.0.0 → infinity_data-2.1.0}/src/infinity_data/semantic/jsonpath.py +0 -0
  41. {infinity_data-2.0.0 → infinity_data-2.1.0}/src/infinity_data/semantic/registry/_core.py +0 -0
  42. {infinity_data-2.0.0 → infinity_data-2.1.0}/src/infinity_data/semantic/registry/dict_constraints.py +0 -0
  43. {infinity_data-2.0.0 → infinity_data-2.1.0}/src/infinity_data/semantic/registry/logic.py +0 -0
  44. {infinity_data-2.0.0 → infinity_data-2.1.0}/src/infinity_data/semantic/registry/types.py +0 -0
  45. {infinity_data-2.0.0 → infinity_data-2.1.0}/src/infinity_data/semantic/resolver/__init__.py +0 -0
  46. {infinity_data-2.0.0 → infinity_data-2.1.0}/src/infinity_data/semantic/resolver/identity.py +0 -0
  47. {infinity_data-2.0.0 → infinity_data-2.1.0}/src/infinity_data/semantic/resolver/models.py +0 -0
  48. {infinity_data-2.0.0 → infinity_data-2.1.0}/src/infinity_data/semantic/resolver/resolver.py +0 -0
  49. {infinity_data-2.0.0 → infinity_data-2.1.0}/src/infinity_data/semantic/std.py +0 -0
  50. {infinity_data-2.0.0 → infinity_data-2.1.0}/src/infinity_data/tokenizer/__init__.py +0 -0
  51. {infinity_data-2.0.0 → infinity_data-2.1.0}/src/infinity_data/tokenizer/char_stream.py +0 -0
  52. {infinity_data-2.0.0 → infinity_data-2.1.0}/src/infinity_data/tokenizer/diagnostics.py +0 -0
  53. {infinity_data-2.0.0 → infinity_data-2.1.0}/src/infinity_data/tokenizer/finalizer.py +0 -0
  54. {infinity_data-2.0.0 → infinity_data-2.1.0}/src/infinity_data/tokenizer/models/__init__.py +0 -0
  55. {infinity_data-2.0.0 → infinity_data-2.1.0}/src/infinity_data/tokenizer/models/raw_tokens.py +0 -0
  56. {infinity_data-2.0.0 → infinity_data-2.1.0}/src/infinity_data/tokenizer/models/tokens.py +0 -0
  57. {infinity_data-2.0.0 → infinity_data-2.1.0}/src/infinity_data/tokenizer/tokenizer.py +0 -0
  58. {infinity_data-2.0.0 → infinity_data-2.1.0}/src/infinity_data.egg-info/SOURCES.txt +0 -0
  59. {infinity_data-2.0.0 → infinity_data-2.1.0}/src/infinity_data.egg-info/dependency_links.txt +0 -0
  60. {infinity_data-2.0.0 → infinity_data-2.1.0}/src/infinity_data.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: infinity-data
3
- Version: 2.0.0
3
+ Version: 2.1.0
4
4
  Summary: InfinityData:声明式配置语言(.infd/.inft)的 Python 编译器库
5
5
  Author: infinity_system: yin_bailiang
6
6
  License: MIT
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "infinity-data"
3
- version = "2.0.0"
3
+ version = "2.1.0"
4
4
  description = "InfinityData:声明式配置语言(.infd/.inft)的 Python 编译器库"
5
5
  readme = "README.md"
6
6
  license = { text = "MIT" }
@@ -22,6 +22,7 @@ from infinity_data.parser.models import (
22
22
  DictValue,
23
23
  Document,
24
24
  DollarValue,
25
+ EnvImportItem,
25
26
  EnvImportStmt,
26
27
  ErrorConstraint,
27
28
  ErrorStatement,
@@ -59,6 +60,7 @@ __all__ = [
59
60
  'DictValue',
60
61
  'Document',
61
62
  'DollarValue',
63
+ 'EnvImportItem',
62
64
  'EnvImportStmt',
63
65
  'ErrorConstraint',
64
66
  'ErrorStatement',
@@ -120,16 +120,30 @@ class TemplateImportStmt(AstNode):
120
120
 
121
121
 
122
122
  @dataclass
123
- class EnvImportStmt(AstNode):
124
- """环境变量导入: !env import NAME [as NEW_NAME]"""
123
+ class EnvImportItem(AstNode):
124
+ """环境变量导入项: NAME [as NEW_NAME]。"""
125
125
 
126
126
  name: str # 环境变量名
127
- alias: str | None # 别名(可选)
127
+ alias: str | None = None # 别名(可选)
128
128
 
129
129
  def canonical(self) -> str:
130
130
  if self.alias is None:
131
- return f'!env import {self.name}'
132
- return f'!env import {self.name} as {self.alias}'
131
+ return self.name
132
+ return f'{self.name} as {self.alias}'
133
+
134
+
135
+ @dataclass
136
+ class EnvImportStmt(AstNode):
137
+ """环境变量导入: !env import NAME1 [as NEW1], NAME2 [as NEW2], ..."""
138
+
139
+ items: list[EnvImportItem] # 导入项列表(项之间必须用逗号分隔)
140
+
141
+ def children(self) -> Iterable[AstNode]:
142
+ return self.items
143
+
144
+ def canonical(self) -> str:
145
+ items = ', '.join(i.canonical() for i in self.items)
146
+ return f'!env import {items}'
133
147
 
134
148
 
135
149
  @dataclass
@@ -178,7 +192,7 @@ class FileImportStmt(AstNode):
178
192
  """配置文件导入: !file "path" as <format> import .path.to.key as alias, ..."""
179
193
 
180
194
  file_path: str # 文件路径
181
- format: str | None # 文件格式: "yaml", "json", "toml" 或 None(自动检测后缀)
195
+ format: str | None # 文件格式: "yaml", "json", "toml", "raw" 或 None(自动检测后缀)
182
196
  imports: list[FileImportItem]
183
197
 
184
198
  def children(self) -> Iterable[AstNode]:
@@ -17,6 +17,7 @@ from infinity_data.parser.models import (
17
17
  DictValue,
18
18
  Document,
19
19
  DollarValue,
20
+ EnvImportItem,
20
21
  EnvImportStmt,
21
22
  ErrorConstraint,
22
23
  ErrorStatement,
@@ -272,18 +273,29 @@ class Parser:
272
273
 
273
274
  @staticmethod
274
275
  def _parse_env_import(stream: TokenStream, collector: DiagnosticCollector, kw_tok: EnvImportToken) -> EnvImportStmt:
275
- """!env import NAME [as NEW_NAME]"""
276
+ """!env import NAME1 [as NEW1], NAME2 [as NEW2], ..."""
276
277
  Parser._expect_keyword(stream, collector, 'import')
277
- name_tok = stream.expect(IdentifierToken)
278
278
 
279
- alias = None
280
- if Parser._peek_keyword(stream, 'as'):
281
- stream.advance()
282
- alias = stream.expect(IdentifierToken).name
279
+ # 导入项列表(项之间必须用逗号分隔)
280
+ items: list[EnvImportItem] = []
281
+ items.append(Parser._parse_env_import_item(stream, collector))
282
+
283
+ while True:
284
+ tok = stream.peek()
285
+ if isinstance(tok, CommaToken):
286
+ stream.advance()
287
+ items.append(Parser._parse_env_import_item(stream, collector))
288
+ elif isinstance(tok, IdentifierToken):
289
+ # 同一行内空格分隔(无逗号/换行)→ 报缺失逗号,容错继续(漏逗号)
290
+ collector.add(diag('parse.import_missing_comma', {}, tok.raw.source))
291
+ items.append(Parser._parse_env_import_item(stream, collector))
292
+ else:
293
+ # 换行/EOF 等 → 导入列表结束(换行后是新的顶层语句,不是导入项)
294
+ break
283
295
 
284
- # 导入语句必须换行/EOF 结尾:!env 无导入项列表,若不加检查,同一行的逗号会被
285
- # 顶层 skip_separators 吞掉(`!env import A as a, x = 1` 被误认为合法),与
286
- # !from / !file 的「尾部必须换行」行为不一致。
296
+ # 导入语句必须换行/EOF 结尾:若不检查,同一行的逗号会被顶层 skip_separators
297
+ # 吞掉(`!env import A as a, x = 1` 被误认为合法),与 !from / !file 的
298
+ # 「尾部必须换行」行为不一致。
287
299
  tok = stream.peek()
288
300
  # 显式排除 NoNextType 以收窄类型:stream.eof() 是方法调用,无法据此收窄 tok,
289
301
  # 否则 tok 仍为 Token | NoNextType,访问 tok.raw 会触发类型错误。
@@ -300,6 +312,22 @@ class Parser:
300
312
  # 否则语句 source 会把下一行行首吞入(与 Field 不一致)
301
313
  return EnvImportStmt(
302
314
  source=stream.span_from(kw_tok),
315
+ items=items,
316
+ )
317
+
318
+ @staticmethod
319
+ def _parse_env_import_item(stream: TokenStream, collector: DiagnosticCollector) -> EnvImportItem:
320
+ """解析单个导入项: NAME [as NEW_NAME]。"""
321
+ first = stream.peek()
322
+ name_tok = stream.expect(IdentifierToken)
323
+
324
+ alias = None
325
+ if Parser._peek_keyword(stream, 'as'):
326
+ stream.advance()
327
+ alias = stream.expect(IdentifierToken).name
328
+
329
+ return EnvImportItem(
330
+ source=stream.span_from(first),
303
331
  name=name_tok.name,
304
332
  alias=alias,
305
333
  )
@@ -890,7 +918,7 @@ class Parser:
890
918
 
891
919
  支持:
892
920
  - int, str, bool, float, list, dict, ?, object
893
- - type? → one(type, ?)
921
+ - type? → any(type, ?)
894
922
  - <constraint, constraint, ...> → all(constraint, ...)
895
923
  - <any(...)>, <one(...)>, <not(...)>, <all(...)>
896
924
  """
@@ -929,7 +957,7 @@ class Parser:
929
957
 
930
958
  if isinstance(stream.peek(), QuestionToken):
931
959
  stream.advance()
932
- # 直接展开: type? → one(type, ?)
960
+ # 直接展开: type? → any(type, ?)
933
961
  return Constraints(
934
962
  source=stream.span_from(first),
935
963
  constraints=[Parser._nullable(ident)],
@@ -938,7 +966,7 @@ class Parser:
938
966
  # 单约束函数调用可省略尖括号: field: regex("re") = ...
939
967
  if isinstance(stream.peek(), LparenToken):
940
968
  call = Parser._parse_constraint_call(stream, collector, first)
941
- # 调用后也可空: regex("re")? → one(regex("re"), ?)
969
+ # 调用后也可空: regex("re")? → any(regex("re"), ?)
942
970
  if isinstance(stream.peek(), QuestionToken):
943
971
  stream.advance()
944
972
  call = Parser._nullable(call)
@@ -971,10 +999,10 @@ class Parser:
971
999
 
972
1000
  @staticmethod
973
1001
  def _nullable(c: Constraint) -> ConstraintCall:
974
- """可空包装:constraint? → one(constraint, ?)。"""
1002
+ """可空包装:constraint? → any(constraint, ?)。"""
975
1003
  return ConstraintCall(
976
1004
  source=c.source,
977
- name='one',
1005
+ name='any',
978
1006
  arguments=[c, ConstraintIdent(source=c.source, name='?')],
979
1007
  )
980
1008
 
@@ -1025,7 +1053,7 @@ class Parser:
1025
1053
  message=f'无法解析的约束: {bad_tok.raw.type.name}',
1026
1054
  )
1027
1055
 
1028
- # 可空后缀: constraint? → one(constraint, ?)
1056
+ # 可空后缀: constraint? → any(constraint, ?)
1029
1057
  if isinstance(stream.peek(), QuestionToken):
1030
1058
  stream.advance()
1031
1059
  return Parser._nullable(base)
@@ -522,6 +522,11 @@ class AstBuilder:
522
522
  return raw
523
523
  if isinstance(raw, (StdArray, StdObject)):
524
524
  return raw # 结构化值 cast 不适用
525
+ # 空字面量:as 转换**保持传播**(null → null、noexist → noexist),
526
+ # 不做任何转换、不产生警告——三态可空语义不因显式 as 而丢失
527
+ # (此时 raw 已收窄为 StdLiteral)
528
+ if raw.kind in ('null', 'noexist'):
529
+ return raw
525
530
  raw = raw.value # StdLiteral:提取 Python 值走转换
526
531
 
527
532
  match type_cast:
@@ -588,6 +593,17 @@ class AstBuilder:
588
593
  )
589
594
  return StdLiteral(kind='float', value=decimal.Decimal(0))
590
595
  case 'str':
596
+ # as str = 字符串化:任意字面量按**语言字面量风格**转为字符串,
597
+ # 保证可 round-trip 还原回原值(bool → true/false、float 特殊值
598
+ # → nan/+inf/-inf),不泄漏 Python 内部表示;null/noexist 已在上方
599
+ # 保持传播,不进入本分支
600
+ if isinstance(raw, bool):
601
+ return StdLiteral(kind='str', value='true' if raw else 'false')
602
+ if isinstance(raw, decimal.Decimal):
603
+ if raw.is_nan():
604
+ return StdLiteral(kind='str', value='nan')
605
+ if raw.is_infinite():
606
+ return StdLiteral(kind='str', value='+inf' if raw > 0 else '-inf')
591
607
  return StdLiteral(kind='str', value=str(raw))
592
608
  case _:
593
609
  return StdLiteral(kind='str', value=str(raw))
@@ -38,7 +38,7 @@ _INVALID_CONSTRAINT = '@invalid'
38
38
  def expand_annotation(annotation: Constraints) -> Constraints:
39
39
  """展开约束语法糖:多约束 ``<a, b, c>`` → ``all(a, b, c)``。
40
40
 
41
- (``type?`` → ``one(type, ?)`` 已在 parser 阶段展开。)
41
+ (``type?`` → ``any(type, ?)`` 已在 parser 阶段展开。)
42
42
  """
43
43
  if len(annotation.constraints) > 1:
44
44
  return Constraints(
@@ -171,6 +171,10 @@ register_diagnostic_define(
171
171
  diagnostic_define('constraint.size_out', '{path_prefix}大小 {size} 不在范围 [{lo}, {hi}] 内'),
172
172
  diagnostic_define('constraint.each_need', '{path_prefix}each() 需要一个约束参数'),
173
173
  diagnostic_define('constraint.each_only', '{path_prefix}each 约束适用于 list/dict,实际 {actual}'),
174
+ diagnostic_define('constraint.position_only', '{path_prefix}position 约束只适用于 list,实际 {actual}'),
175
+ diagnostic_define('constraint.position_index', '{path_prefix}position 下标必须是整数: {value!r}'),
176
+ diagnostic_define('constraint.position_need', '{path_prefix}position() 需要一个约束参数'),
177
+ diagnostic_define('constraint.position_out', '{path_prefix}下标 {index} 越界(list 大小 {size})'),
174
178
  diagnostic_define('constraint.in_not_in', '{path_prefix}值 {value} 不在允许的值 {choices!r} 中'),
175
179
  diagnostic_define('constraint.string_only', '{path_prefix}{constraint} 约束只适用于字符串'),
176
180
  diagnostic_define('constraint.invalid_value', '{path_prefix}无效的{what} {value!r}'),
@@ -33,6 +33,7 @@ from infinity_data.semantic.registry.general import (
33
33
  _check_ip6,
34
34
  _check_negative,
35
35
  _check_nonnegative,
36
+ _check_position,
36
37
  _check_positive,
37
38
  _check_range,
38
39
  _check_regex,
@@ -139,6 +140,13 @@ class ConstraintRegistry:
139
140
  self.register('range', _check_range, min_args=1, max_args=2, description='数值范围 range(ge[, le]),可省略一端')
140
141
  self.register('size', _check_size, min_args=1, max_args=2, description='集合大小或字符串长度 size(ge[, le])')
141
142
  self.register('each', _check_each, min_args=1, max_args=1, description='每个元素均满足 each(constraint)')
143
+ self.register(
144
+ 'position',
145
+ _check_position,
146
+ min_args=2,
147
+ max_args=2,
148
+ description='指定下标元素约束 position(index, constraint)',
149
+ )
142
150
  self.register('in', _check_in, min_args=1, description='值在给定选项中 in(choice, ...)')
143
151
  self.register('ip', _check_ip, description='IPv4 或 IPv6 地址')
144
152
  self.register('ip4', _check_ip4, description='IPv4 地址')
@@ -41,6 +41,7 @@ __all__ = [
41
41
  '_check_range',
42
42
  '_check_size',
43
43
  '_check_each',
44
+ '_check_position',
44
45
  '_check_in',
45
46
  '_check_ip',
46
47
  '_check_ip4',
@@ -137,6 +138,33 @@ def _check_each(
137
138
  return ok_result()
138
139
 
139
140
 
141
+ def _check_position(
142
+ val: StdValue | None,
143
+ source: SourceRange | None,
144
+ path: str,
145
+ args: list[Any],
146
+ executor: Executor,
147
+ ) -> ConstraintResult:
148
+ """position(index, constraint):list 指定下标(0-based)元素满足约束。
149
+
150
+ - 值不是 list → 不满足
151
+ - 下标参数非整数(含 bool)→ 不满足(参数错误)
152
+ - 下标越界(< 0 或 >= size,元素不存在)→ 不满足
153
+ - 否则递归执行 constraint 于该元素
154
+ """
155
+ if not isinstance(val, StdArray):
156
+ return fail_result('constraint.position_only', {'actual': describe(val)}, source, path)
157
+ idx = args[0]
158
+ if isinstance(idx, bool) or not isinstance(idx, int):
159
+ return fail_result('constraint.position_index', {'value': idx}, source, path)
160
+ spec = _as_spec(args[1])
161
+ if spec is None:
162
+ return fail_result('constraint.position_need', {}, source, path)
163
+ if idx < 0 or idx >= len(val.elements):
164
+ return fail_result('constraint.position_out', {'index': idx, 'size': len(val.elements)}, source, path)
165
+ return executor(spec, val.elements[idx], source, f'{path}[{idx}]')
166
+
167
+
140
168
  def _check_in(
141
169
  val: StdValue | None,
142
170
  source: SourceRange | None,
@@ -36,6 +36,10 @@ _FORMAT_MAP: dict[str, str] = {
36
36
  '.yaml': 'yaml',
37
37
  '.yml': 'yaml',
38
38
  '.toml': 'toml',
39
+ '.txt': 'raw',
40
+ '.text': 'raw',
41
+ '.md': 'raw',
42
+ '.log': 'raw',
39
43
  }
40
44
 
41
45
 
@@ -93,14 +97,15 @@ class ImportResolver:
93
97
  namespace: dict[str, StdValue],
94
98
  collector: DiagnosticCollector,
95
99
  ) -> None:
96
- """!env import NAME [as NEW_NAME]
100
+ """!env import NAME1 [as NEW1], NAME2 [as NEW2], ...
97
101
 
98
102
  未授权环境变量**总是失败**(无论 strict):Sandbox.getenv 直接抛
99
103
  :class:`SandboxError`,不会退化为空字符串注入。
100
104
  """
101
- name = stmt.alias or stmt.name
102
- raw = self._sandbox.getenv(stmt.name, source=stmt.source)
103
- self._bind(namespace, name, python_to_std(raw), collector, stmt.source)
105
+ for item in stmt.items:
106
+ name = item.alias or item.name
107
+ raw = self._sandbox.getenv(item.name, source=item.source)
108
+ self._bind(namespace, name, python_to_std(raw), collector, item.source)
104
109
 
105
110
  def _resolve_file(
106
111
  self,
@@ -162,6 +167,9 @@ class ImportResolver:
162
167
  ) -> Any | None:
163
168
  """按格式解析数据内容(文本 loads)。"""
164
169
  try:
170
+ if fmt == 'raw':
171
+ # raw:直接导入字符串(不做任何解析,保留文件原文)
172
+ return text
165
173
  if fmt == 'json':
166
174
  return json.loads(text)
167
175
  if fmt in ('yaml', 'yml'):
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: infinity-data
3
- Version: 2.0.0
3
+ Version: 2.1.0
4
4
  Summary: InfinityData:声明式配置语言(.infd/.inft)的 Python 编译器库
5
5
  Author: infinity_system: yin_bailiang
6
6
  License: MIT
File without changes
File without changes
File without changes