tstring-bindings 0.2.0__tar.gz → 0.2.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.
Files changed (40) hide show
  1. {tstring_bindings-0.2.0 → tstring_bindings-0.2.2}/Cargo.lock +8 -8
  2. {tstring_bindings-0.2.0 → tstring_bindings-0.2.2}/Cargo.toml +1 -1
  3. {tstring_bindings-0.2.0 → tstring_bindings-0.2.2}/PKG-INFO +1 -1
  4. {tstring_bindings-0.2.0 → tstring_bindings-0.2.2}/json-tstring-rs/Cargo.toml +1 -1
  5. {tstring_bindings-0.2.0 → tstring_bindings-0.2.2}/json-tstring-rs/src/lib.rs +100 -2
  6. {tstring_bindings-0.2.0 → tstring_bindings-0.2.2}/json-tstring-rs/tests/parser.rs +31 -3
  7. {tstring_bindings-0.2.0 → tstring_bindings-0.2.2}/pyproject.toml +1 -1
  8. {tstring_bindings-0.2.0 → tstring_bindings-0.2.2}/python/tstring_bindings/__init__.py +17 -13
  9. {tstring_bindings-0.2.0 → tstring_bindings-0.2.2}/python/tstring_bindings/__init__.pyi +11 -7
  10. {tstring_bindings-0.2.0 → tstring_bindings-0.2.2}/python/tstring_bindings/tstring_bindings.pyi +13 -10
  11. {tstring_bindings-0.2.0 → tstring_bindings-0.2.2}/python-bindings/Cargo.toml +5 -5
  12. {tstring_bindings-0.2.0 → tstring_bindings-0.2.2}/python-bindings/src/lib.rs +1 -1
  13. {tstring_bindings-0.2.0 → tstring_bindings-0.2.2}/toml-tstring-rs/Cargo.toml +1 -1
  14. {tstring_bindings-0.2.0 → tstring_bindings-0.2.2}/toml-tstring-rs/src/lib.rs +108 -4
  15. {tstring_bindings-0.2.0 → tstring_bindings-0.2.2}/toml-tstring-rs/tests/parser.rs +40 -3
  16. {tstring_bindings-0.2.0 → tstring_bindings-0.2.2}/tstring-core-rs/src/lib.rs +22 -0
  17. {tstring_bindings-0.2.0 → tstring_bindings-0.2.2}/tstring-pyo3-bindings/Cargo.toml +4 -4
  18. {tstring_bindings-0.2.0 → tstring_bindings-0.2.2}/tstring-pyo3-bindings/benches/render_paths.rs +29 -5
  19. {tstring_bindings-0.2.0 → tstring_bindings-0.2.2}/yaml-tstring-rs/Cargo.toml +1 -1
  20. {tstring_bindings-0.2.0 → tstring_bindings-0.2.2}/yaml-tstring-rs/src/lib.rs +172 -3
  21. {tstring_bindings-0.2.0 → tstring_bindings-0.2.2}/yaml-tstring-rs/tests/parser.rs +62 -2
  22. {tstring_bindings-0.2.0 → tstring_bindings-0.2.2}/README.md +0 -0
  23. {tstring_bindings-0.2.0 → tstring_bindings-0.2.2}/json-tstring-rs/README.md +0 -0
  24. {tstring_bindings-0.2.0 → tstring_bindings-0.2.2}/json-tstring-rs/tests/conformance.rs +0 -0
  25. {tstring_bindings-0.2.0 → tstring_bindings-0.2.2}/python/tstring_bindings/_profiles.py +0 -0
  26. {tstring_bindings-0.2.0 → tstring_bindings-0.2.2}/python/tstring_bindings/_types.py +0 -0
  27. {tstring_bindings-0.2.0 → tstring_bindings-0.2.2}/python/tstring_bindings/py.typed +0 -0
  28. {tstring_bindings-0.2.0 → tstring_bindings-0.2.2}/python-bindings/README.md +0 -0
  29. {tstring_bindings-0.2.0 → tstring_bindings-0.2.2}/toml-tstring-rs/README.md +0 -0
  30. {tstring_bindings-0.2.0 → tstring_bindings-0.2.2}/toml-tstring-rs/tests/conformance.rs +0 -0
  31. {tstring_bindings-0.2.0 → tstring_bindings-0.2.2}/tstring-core-rs/Cargo.toml +0 -0
  32. {tstring_bindings-0.2.0 → tstring_bindings-0.2.2}/tstring-core-rs/README.md +0 -0
  33. {tstring_bindings-0.2.0 → tstring_bindings-0.2.2}/tstring-pyo3-bindings/src/json.rs +0 -0
  34. {tstring_bindings-0.2.0 → tstring_bindings-0.2.2}/tstring-pyo3-bindings/src/lib.rs +0 -0
  35. {tstring_bindings-0.2.0 → tstring_bindings-0.2.2}/tstring-pyo3-bindings/src/toml.rs +0 -0
  36. {tstring_bindings-0.2.0 → tstring_bindings-0.2.2}/tstring-pyo3-bindings/src/yaml.rs +0 -0
  37. {tstring_bindings-0.2.0 → tstring_bindings-0.2.2}/yaml-tstring-rs/README.md +0 -0
  38. {tstring_bindings-0.2.0 → tstring_bindings-0.2.2}/yaml-tstring-rs/test-support/renderer_layout.rs +0 -0
  39. {tstring_bindings-0.2.0 → tstring_bindings-0.2.2}/yaml-tstring-rs/tests/conformance.rs +0 -0
  40. {tstring_bindings-0.2.0 → tstring_bindings-0.2.2}/yaml-tstring-rs/tests/normalized.rs +0 -0
@@ -725,7 +725,7 @@ checksum = "ab16f14aed21ee8bfd8ec22513f7287cd4a91aa92e44edfe2c17ddd004e92607"
725
725
 
726
726
  [[package]]
727
727
  name = "tstring-backend-e2e-tests"
728
- version = "0.2.0"
728
+ version = "0.2.2"
729
729
  dependencies = [
730
730
  "tstring-json",
731
731
  "tstring-syntax",
@@ -735,7 +735,7 @@ dependencies = [
735
735
 
736
736
  [[package]]
737
737
  name = "tstring-bindings"
738
- version = "0.2.0"
738
+ version = "0.2.2"
739
739
  dependencies = [
740
740
  "pyo3",
741
741
  "pythonize",
@@ -751,7 +751,7 @@ dependencies = [
751
751
 
752
752
  [[package]]
753
753
  name = "tstring-json"
754
- version = "0.2.0"
754
+ version = "0.2.2"
755
755
  dependencies = [
756
756
  "serde_json",
757
757
  "toml",
@@ -760,7 +760,7 @@ dependencies = [
760
760
 
761
761
  [[package]]
762
762
  name = "tstring-pyo3-bindings"
763
- version = "0.2.0"
763
+ version = "0.2.2"
764
764
  dependencies = [
765
765
  "criterion",
766
766
  "pyo3",
@@ -776,14 +776,14 @@ dependencies = [
776
776
 
777
777
  [[package]]
778
778
  name = "tstring-syntax"
779
- version = "0.2.0"
779
+ version = "0.2.2"
780
780
  dependencies = [
781
781
  "num-bigint",
782
782
  ]
783
783
 
784
784
  [[package]]
785
785
  name = "tstring-toml"
786
- version = "0.2.0"
786
+ version = "0.2.2"
787
787
  dependencies = [
788
788
  "serde_json",
789
789
  "toml",
@@ -792,7 +792,7 @@ dependencies = [
792
792
 
793
793
  [[package]]
794
794
  name = "tstring-yaml"
795
- version = "0.2.0"
795
+ version = "0.2.2"
796
796
  dependencies = [
797
797
  "saphyr",
798
798
  "saphyr-parser",
@@ -803,7 +803,7 @@ dependencies = [
803
803
 
804
804
  [[package]]
805
805
  name = "tstring-yaml-pyo3-tests"
806
- version = "0.2.0"
806
+ version = "0.2.2"
807
807
  dependencies = [
808
808
  "pyo3",
809
809
  "saphyr",
@@ -9,7 +9,7 @@ homepage = "https://github.com/koxudaxi/tstring-structured-data"
9
9
  license = "MIT"
10
10
  repository = "https://github.com/koxudaxi/tstring-structured-data"
11
11
  rust-version = "1.94.0"
12
- version = "0.2.0"
12
+ version = "0.2.2"
13
13
 
14
14
  [workspace.dependencies]
15
15
  num-bigint = "0.4.6"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: tstring-bindings
3
- Version: 0.2.0
3
+ Version: 0.2.2
4
4
  Classifier: Development Status :: 4 - Beta
5
5
  Classifier: Intended Audience :: Developers
6
6
  Classifier: License :: OSI Approved :: MIT License
@@ -18,7 +18,7 @@ test = false
18
18
 
19
19
  [dependencies]
20
20
  serde_json = { workspace = true }
21
- tstring-syntax = { version = "0.2.0", path = "../tstring-core-rs" }
21
+ tstring-syntax = { version = "0.2.2", path = "../tstring-core-rs" }
22
22
 
23
23
  [dev-dependencies]
24
24
  toml = { workspace = true }
@@ -1,10 +1,15 @@
1
1
  use serde_json::Value;
2
2
  use std::str::FromStr;
3
3
  use tstring_syntax::{
4
- BackendError, BackendResult, NormalizedDocument, NormalizedFloat, NormalizedKey,
5
- NormalizedStream, NormalizedValue, SourcePosition, SourceSpan, StreamItem, TemplateInput,
4
+ BackendError, BackendResult, InterpolationTypeRequirement, NormalizedDocument, NormalizedFloat,
5
+ NormalizedKey, NormalizedStream, NormalizedValue, SourcePosition, SourceSpan, StreamItem,
6
+ TemplateInput,
6
7
  };
7
8
 
9
+ const JSON_VALUE_PYTHON_TYPE: &str =
10
+ "str | int | float | bool | None | dict[str, object] | list[object]";
11
+ const STRING_PYTHON_TYPE: &str = "str";
12
+
8
13
  #[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
9
14
  pub enum JsonProfile {
10
15
  Rfc8259,
@@ -636,6 +641,99 @@ pub fn check_template(template: &TemplateInput) -> BackendResult<()> {
636
641
  check_template_with_profile(template, JsonProfile::default())
637
642
  }
638
643
 
644
+ pub fn interpolation_type_requirements_with_profile(
645
+ template: &TemplateInput,
646
+ profile: JsonProfile,
647
+ ) -> BackendResult<Vec<InterpolationTypeRequirement>> {
648
+ let document = parse_validated_template_with_profile(template, profile)?;
649
+ let mut requirements = Vec::new();
650
+ collect_json_value_type_requirements(&document.value, &mut requirements);
651
+ requirements.sort_by_key(|requirement| requirement.interpolation_index);
652
+ Ok(requirements)
653
+ }
654
+
655
+ pub fn interpolation_type_requirements(
656
+ template: &TemplateInput,
657
+ ) -> BackendResult<Vec<InterpolationTypeRequirement>> {
658
+ interpolation_type_requirements_with_profile(template, JsonProfile::default())
659
+ }
660
+
661
+ fn collect_json_value_type_requirements(
662
+ value: &JsonValueNode,
663
+ requirements: &mut Vec<InterpolationTypeRequirement>,
664
+ ) {
665
+ match value {
666
+ JsonValueNode::String(node) => {
667
+ collect_json_string_type_requirements(node, requirements);
668
+ }
669
+ JsonValueNode::Literal(_) => {}
670
+ JsonValueNode::Interpolation(node) => {
671
+ requirements.push(json_type_requirement(node));
672
+ }
673
+ JsonValueNode::Object(node) => {
674
+ for member in &node.members {
675
+ collect_json_key_type_requirements(&member.key, requirements);
676
+ collect_json_value_type_requirements(&member.value, requirements);
677
+ }
678
+ }
679
+ JsonValueNode::Array(node) => {
680
+ for item in &node.items {
681
+ collect_json_value_type_requirements(item, requirements);
682
+ }
683
+ }
684
+ }
685
+ }
686
+
687
+ fn collect_json_key_type_requirements(
688
+ key: &JsonKeyNode,
689
+ requirements: &mut Vec<InterpolationTypeRequirement>,
690
+ ) {
691
+ match &key.value {
692
+ JsonKeyValue::String(node) => {
693
+ collect_json_string_type_requirements(node, requirements);
694
+ }
695
+ JsonKeyValue::Interpolation(node) => {
696
+ requirements.push(json_type_requirement(node));
697
+ }
698
+ }
699
+ }
700
+
701
+ fn collect_json_string_type_requirements(
702
+ string: &JsonStringNode,
703
+ requirements: &mut Vec<InterpolationTypeRequirement>,
704
+ ) {
705
+ for chunk in &string.chunks {
706
+ if let JsonStringPart::Interpolation(node) = chunk {
707
+ requirements.push(json_type_requirement(node));
708
+ }
709
+ }
710
+ }
711
+
712
+ fn json_type_requirement(node: &JsonInterpolationNode) -> InterpolationTypeRequirement {
713
+ match node.role.as_str() {
714
+ "value" => InterpolationTypeRequirement::new(
715
+ node.interpolation_index,
716
+ JSON_VALUE_PYTHON_TYPE,
717
+ "json value",
718
+ ),
719
+ "key" => InterpolationTypeRequirement::new(
720
+ node.interpolation_index,
721
+ STRING_PYTHON_TYPE,
722
+ "json object key",
723
+ ),
724
+ "string_fragment" => InterpolationTypeRequirement::new(
725
+ node.interpolation_index,
726
+ STRING_PYTHON_TYPE,
727
+ "json string fragment",
728
+ ),
729
+ _ => InterpolationTypeRequirement::new(
730
+ node.interpolation_index,
731
+ JSON_VALUE_PYTHON_TYPE,
732
+ "json interpolation",
733
+ ),
734
+ }
735
+ }
736
+
639
737
  pub fn format_template_with_profile(
640
738
  template: &TemplateInput,
641
739
  profile: JsonProfile,
@@ -1,8 +1,10 @@
1
1
  use tstring_json::{
2
- JsonKeyValue, JsonStringPart, JsonValueNode, check_template, format_template, parse_template,
3
- parse_validated_template, validate_template,
2
+ JsonKeyValue, JsonStringPart, JsonValueNode, check_template, format_template,
3
+ interpolation_type_requirements, parse_template, parse_validated_template, validate_template,
4
+ };
5
+ use tstring_syntax::{
6
+ InterpolationTypeRequirement, TemplateInput, TemplateInterpolation, TemplateSegment,
4
7
  };
5
- use tstring_syntax::{TemplateInput, TemplateInterpolation, TemplateSegment};
6
8
 
7
9
  fn interpolation(index: usize, expression: &str) -> TemplateSegment {
8
10
  TemplateSegment::Interpolation(TemplateInterpolation {
@@ -69,6 +71,32 @@ fn checks_valid_json_templates() {
69
71
  check_template(&template).expect("expected check success");
70
72
  }
71
73
 
74
+ #[test]
75
+ fn reports_contextual_interpolation_type_requirements() {
76
+ let template = TemplateInput::from_segments(vec![
77
+ TemplateSegment::StaticText("{".to_owned()),
78
+ interpolation(0, "key"),
79
+ TemplateSegment::StaticText(": ".to_owned()),
80
+ interpolation(1, "value"),
81
+ TemplateSegment::StaticText(", \"label\": \"".to_owned()),
82
+ interpolation(2, "label"),
83
+ TemplateSegment::StaticText("\"}".to_owned()),
84
+ ]);
85
+
86
+ assert_eq!(
87
+ interpolation_type_requirements(&template).expect("expected type requirements"),
88
+ vec![
89
+ InterpolationTypeRequirement::new(0, "str", "json object key"),
90
+ InterpolationTypeRequirement::new(
91
+ 1,
92
+ "str | int | float | bool | None | dict[str, object] | list[object]",
93
+ "json value"
94
+ ),
95
+ InterpolationTypeRequirement::new(2, "str", "json string fragment"),
96
+ ]
97
+ );
98
+ }
99
+
72
100
  #[test]
73
101
  fn validates_json_templates_with_supported_interpolations() {
74
102
  let template = TemplateInput::from_segments(vec![
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "tstring-bindings"
3
- version = "0.2.0"
3
+ version = "0.2.2"
4
4
  description = "Native Python bindings for t-string structured data backends"
5
5
  readme = "README.md"
6
6
  license = { text = "MIT" }
@@ -1,7 +1,7 @@
1
1
  from __future__ import annotations
2
2
 
3
3
  from string.templatelib import Template
4
- from typing import Protocol, cast
4
+ from typing import Annotated, Protocol, cast
5
5
 
6
6
  from . import tstring_bindings as _bindings
7
7
  from ._profiles import (
@@ -14,29 +14,33 @@ from ._profiles import (
14
14
  )
15
15
  from ._types import JsonValue, TomlValue, YamlValue
16
16
 
17
+ type JsonTemplate = Annotated[Template, "json"]
18
+ type TomlTemplate = Annotated[Template, "toml"]
19
+ type YamlTemplate = Annotated[Template, "yaml"]
20
+
17
21
 
18
22
  class _RenderJson(Protocol):
19
- def __call__(self, template: Template, profile: JsonProfile) -> JsonValue: ...
23
+ def __call__(self, template: JsonTemplate, profile: JsonProfile) -> JsonValue: ...
20
24
 
21
25
 
22
26
  class _RenderJsonText(Protocol):
23
- def __call__(self, template: Template, profile: JsonProfile) -> str: ...
27
+ def __call__(self, template: JsonTemplate, profile: JsonProfile) -> str: ...
24
28
 
25
29
 
26
30
  class _RenderToml(Protocol):
27
- def __call__(self, template: Template, profile: TomlProfile) -> TomlValue: ...
31
+ def __call__(self, template: TomlTemplate, profile: TomlProfile) -> TomlValue: ...
28
32
 
29
33
 
30
34
  class _RenderTomlText(Protocol):
31
- def __call__(self, template: Template, profile: TomlProfile) -> str: ...
35
+ def __call__(self, template: TomlTemplate, profile: TomlProfile) -> str: ...
32
36
 
33
37
 
34
38
  class _RenderYaml(Protocol):
35
- def __call__(self, template: Template, profile: YamlProfile) -> YamlValue: ...
39
+ def __call__(self, template: YamlTemplate, profile: YamlProfile) -> YamlValue: ...
36
40
 
37
41
 
38
42
  class _RenderYamlText(Protocol):
39
- def __call__(self, template: Template, profile: YamlProfile) -> str: ...
43
+ def __call__(self, template: YamlTemplate, profile: YamlProfile) -> str: ...
40
44
 
41
45
 
42
46
  class _BindingsContract(Protocol):
@@ -101,37 +105,37 @@ _render_yaml_text = _EXTENSION.render_yaml_text
101
105
 
102
106
 
103
107
  def render_json(
104
- template: Template, *, profile: JsonProfile | str | None = None
108
+ template: JsonTemplate, *, profile: JsonProfile | str | None = None
105
109
  ) -> JsonValue:
106
110
  return _render_json(template, resolve_json_profile(profile))
107
111
 
108
112
 
109
113
  def render_json_text(
110
- template: Template, *, profile: JsonProfile | str | None = None
114
+ template: JsonTemplate, *, profile: JsonProfile | str | None = None
111
115
  ) -> str:
112
116
  return _render_json_text(template, resolve_json_profile(profile))
113
117
 
114
118
 
115
119
  def render_toml(
116
- template: Template, *, profile: TomlProfile | str | None = None
120
+ template: TomlTemplate, *, profile: TomlProfile | str | None = None
117
121
  ) -> TomlValue:
118
122
  return _render_toml(template, resolve_toml_profile(profile))
119
123
 
120
124
 
121
125
  def render_toml_text(
122
- template: Template, *, profile: TomlProfile | str | None = None
126
+ template: TomlTemplate, *, profile: TomlProfile | str | None = None
123
127
  ) -> str:
124
128
  return _render_toml_text(template, resolve_toml_profile(profile))
125
129
 
126
130
 
127
131
  def render_yaml(
128
- template: Template, *, profile: YamlProfile | str | None = None
132
+ template: YamlTemplate, *, profile: YamlProfile | str | None = None
129
133
  ) -> YamlValue:
130
134
  return _render_yaml(template, resolve_yaml_profile(profile))
131
135
 
132
136
 
133
137
  def render_yaml_text(
134
- template: Template, *, profile: YamlProfile | str | None = None
138
+ template: YamlTemplate, *, profile: YamlProfile | str | None = None
135
139
  ) -> str:
136
140
  return _render_yaml_text(template, resolve_yaml_profile(profile))
137
141
 
@@ -1,10 +1,14 @@
1
1
  from __future__ import annotations
2
2
 
3
3
  from string.templatelib import Template
4
- from typing import Any
4
+ from typing import Annotated, Any
5
5
 
6
6
  from ._profiles import JsonProfile, TomlProfile, YamlProfile
7
7
 
8
+ type JsonTemplate = Annotated[Template, "json"]
9
+ type TomlTemplate = Annotated[Template, "toml"]
10
+ type YamlTemplate = Annotated[Template, "yaml"]
11
+
8
12
  type ExceptionSpan = tuple[tuple[int, int], tuple[int, int]]
9
13
  type ExceptionDiagnostic = dict[str, object]
10
14
 
@@ -19,20 +23,20 @@ class TemplateSemanticError(TemplateError): ...
19
23
  class UnrepresentableValueError(TemplateSemanticError): ...
20
24
 
21
25
  def render_json(
22
- template: Template, *, profile: JsonProfile | str | None = ...
26
+ template: JsonTemplate, *, profile: JsonProfile | str | None = ...
23
27
  ) -> Any: ...
24
28
  def render_json_text(
25
- template: Template, *, profile: JsonProfile | str | None = ...
29
+ template: JsonTemplate, *, profile: JsonProfile | str | None = ...
26
30
  ) -> str: ...
27
31
  def render_toml(
28
- template: Template, *, profile: TomlProfile | str | None = ...
32
+ template: TomlTemplate, *, profile: TomlProfile | str | None = ...
29
33
  ) -> Any: ...
30
34
  def render_toml_text(
31
- template: Template, *, profile: TomlProfile | str | None = ...
35
+ template: TomlTemplate, *, profile: TomlProfile | str | None = ...
32
36
  ) -> str: ...
33
37
  def render_yaml(
34
- template: Template, *, profile: YamlProfile | str | None = ...
38
+ template: YamlTemplate, *, profile: YamlProfile | str | None = ...
35
39
  ) -> Any: ...
36
40
  def render_yaml_text(
37
- template: Template, *, profile: YamlProfile | str | None = ...
41
+ template: YamlTemplate, *, profile: YamlProfile | str | None = ...
38
42
  ) -> str: ...
@@ -1,11 +1,14 @@
1
1
  from __future__ import annotations
2
2
 
3
3
  from string.templatelib import Template
4
- from typing import Any
4
+ from typing import Annotated, Any
5
5
 
6
6
  # Non-public extension module retained for internal imports and packaging
7
7
  # compatibility. Public callers should use `tstring_bindings`.
8
8
 
9
+ type JsonTemplate = Annotated[Template, "json"]
10
+ type TomlTemplate = Annotated[Template, "toml"]
11
+ type YamlTemplate = Annotated[Template, "yaml"]
9
12
  type ExceptionSpan = tuple[tuple[int, int], tuple[int, int]]
10
13
  type ExceptionDiagnostic = dict[str, object]
11
14
 
@@ -22,18 +25,18 @@ class UnrepresentableValueError(TemplateSemanticError): ...
22
25
  __contract_version__: int
23
26
  __contract_symbols__: tuple[str, ...]
24
27
 
25
- def render_json(template: Template, profile: str = ...) -> Any: ...
26
- def render_json_text(template: Template, profile: str = ...) -> str: ...
28
+ def render_json(template: JsonTemplate, profile: str = ...) -> Any: ...
29
+ def render_json_text(template: JsonTemplate, profile: str = ...) -> str: ...
27
30
  def _render_json_result_payload(
28
- template: Template, profile: str = ...
31
+ template: JsonTemplate, profile: str = ...
29
32
  ) -> tuple[str, Any]: ...
30
- def render_toml(template: Template, profile: str = ...) -> Any: ...
31
- def render_toml_text(template: Template, profile: str = ...) -> str: ...
33
+ def render_toml(template: TomlTemplate, profile: str = ...) -> Any: ...
34
+ def render_toml_text(template: TomlTemplate, profile: str = ...) -> str: ...
32
35
  def _render_toml_result_payload(
33
- template: Template, profile: str = ...
36
+ template: TomlTemplate, profile: str = ...
34
37
  ) -> tuple[str, Any]: ...
35
- def render_yaml(template: Template, profile: str = ...) -> Any: ...
36
- def render_yaml_text(template: Template, profile: str = ...) -> str: ...
38
+ def render_yaml(template: YamlTemplate, profile: str = ...) -> Any: ...
39
+ def render_yaml_text(template: YamlTemplate, profile: str = ...) -> str: ...
37
40
  def _render_yaml_result_payload(
38
- template: Template, profile: str = ...
41
+ template: YamlTemplate, profile: str = ...
39
42
  ) -> tuple[str, Any]: ...
@@ -24,12 +24,12 @@ pyo3 = { workspace = true, features = ["abi3-py314"] }
24
24
  pythonize = { workspace = true }
25
25
  saphyr = { workspace = true }
26
26
  serde_json = { workspace = true }
27
- tstring-json = { version = "0.2.0", path = "../json-tstring-rs" }
28
- tstring-pyo3-bindings = { version = "0.2.0", path = "../tstring-pyo3-bindings" }
29
- tstring-syntax = { version = "0.2.0", path = "../tstring-core-rs" }
27
+ tstring-json = { version = "0.2.2", path = "../json-tstring-rs" }
28
+ tstring-pyo3-bindings = { version = "0.2.2", path = "../tstring-pyo3-bindings" }
29
+ tstring-syntax = { version = "0.2.2", path = "../tstring-core-rs" }
30
30
  toml = { workspace = true }
31
- tstring-toml = { version = "0.2.0", path = "../toml-tstring-rs" }
32
- tstring-yaml = { version = "0.2.0", path = "../yaml-tstring-rs" }
31
+ tstring-toml = { version = "0.2.2", path = "../toml-tstring-rs" }
32
+ tstring-yaml = { version = "0.2.2", path = "../yaml-tstring-rs" }
33
33
 
34
34
  [dev-dependencies]
35
35
  pyo3 = { workspace = true, features = ["auto-initialize"] }
@@ -627,7 +627,7 @@ fn normalized_offset_to_python(py: Python<'_>, offset_minutes: i16) -> PyResult<
627
627
 
628
628
  #[pymodule]
629
629
  fn tstring_bindings(py: Python<'_>, module: &Bound<'_, PyModule>) -> PyResult<()> {
630
- module.add("__version__", "0.2.0")?;
630
+ module.add("__version__", "0.2.2")?;
631
631
  module.add("__contract_version__", CONTRACT_VERSION)?;
632
632
  module.add("__contract_symbols__", PyTuple::new(py, CONTRACT_SYMBOLS)?)?;
633
633
  module.add("TemplateError", py.get_type::<TemplateError>())?;
@@ -19,4 +19,4 @@ test = false
19
19
  [dependencies]
20
20
  serde_json = { workspace = true }
21
21
  toml = { workspace = true }
22
- tstring-syntax = { version = "0.2.0", path = "../tstring-core-rs" }
22
+ tstring-syntax = { version = "0.2.2", path = "../tstring-core-rs" }
@@ -1,10 +1,13 @@
1
1
  use tstring_syntax::{
2
- BackendError, BackendResult, NormalizedDate, NormalizedDocument, NormalizedEntry,
3
- NormalizedFloat, NormalizedKey, NormalizedLocalDateTime, NormalizedOffsetDateTime,
4
- NormalizedStream, NormalizedTemporal, NormalizedTime, NormalizedValue, SourcePosition,
5
- SourceSpan, StreamItem, TemplateInput,
2
+ BackendError, BackendResult, InterpolationTypeRequirement, NormalizedDate, NormalizedDocument,
3
+ NormalizedEntry, NormalizedFloat, NormalizedKey, NormalizedLocalDateTime,
4
+ NormalizedOffsetDateTime, NormalizedStream, NormalizedTemporal, NormalizedTime,
5
+ NormalizedValue, SourcePosition, SourceSpan, StreamItem, TemplateInput,
6
6
  };
7
7
 
8
+ const TOML_VALUE_PYTHON_TYPE: &str = "str | int | float | bool | datetime.date | datetime.time | datetime.datetime | list[object] | dict[str, object]";
9
+ const STRING_PYTHON_TYPE: &str = "str";
10
+
8
11
  #[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
9
12
  pub enum TomlProfile {
10
13
  V1_0,
@@ -1146,6 +1149,107 @@ pub fn check_template(template: &TemplateInput) -> BackendResult<()> {
1146
1149
  check_template_with_profile(template, TomlProfile::default())
1147
1150
  }
1148
1151
 
1152
+ pub fn interpolation_type_requirements_with_profile(
1153
+ template: &TemplateInput,
1154
+ profile: TomlProfile,
1155
+ ) -> BackendResult<Vec<InterpolationTypeRequirement>> {
1156
+ let document = parse_validated_template_with_profile(template, profile)?;
1157
+ let mut requirements = Vec::new();
1158
+ collect_toml_document_type_requirements(&document, &mut requirements);
1159
+ requirements.sort_by_key(|requirement| requirement.interpolation_index);
1160
+ Ok(requirements)
1161
+ }
1162
+
1163
+ pub fn interpolation_type_requirements(
1164
+ template: &TemplateInput,
1165
+ ) -> BackendResult<Vec<InterpolationTypeRequirement>> {
1166
+ interpolation_type_requirements_with_profile(template, TomlProfile::default())
1167
+ }
1168
+
1169
+ fn collect_toml_document_type_requirements(
1170
+ document: &TomlDocumentNode,
1171
+ requirements: &mut Vec<InterpolationTypeRequirement>,
1172
+ ) {
1173
+ for statement in &document.statements {
1174
+ match statement {
1175
+ TomlStatementNode::Assignment(node) => {
1176
+ collect_toml_key_path_type_requirements(&node.key_path, requirements);
1177
+ collect_toml_value_type_requirements(&node.value, requirements);
1178
+ }
1179
+ TomlStatementNode::TableHeader(node) => {
1180
+ collect_toml_key_path_type_requirements(&node.key_path, requirements);
1181
+ }
1182
+ TomlStatementNode::ArrayTableHeader(node) => {
1183
+ collect_toml_key_path_type_requirements(&node.key_path, requirements);
1184
+ }
1185
+ }
1186
+ }
1187
+ }
1188
+
1189
+ fn collect_toml_key_path_type_requirements(
1190
+ key_path: &TomlKeyPathNode,
1191
+ requirements: &mut Vec<InterpolationTypeRequirement>,
1192
+ ) {
1193
+ for segment in &key_path.segments {
1194
+ match &segment.value {
1195
+ TomlKeySegmentValue::Bare(_) => {}
1196
+ TomlKeySegmentValue::String(node) => {
1197
+ collect_toml_string_type_requirements(node, requirements);
1198
+ }
1199
+ TomlKeySegmentValue::Interpolation(node) => {
1200
+ requirements.push(InterpolationTypeRequirement::new(
1201
+ node.interpolation_index,
1202
+ STRING_PYTHON_TYPE,
1203
+ "toml key",
1204
+ ));
1205
+ }
1206
+ }
1207
+ }
1208
+ }
1209
+
1210
+ fn collect_toml_value_type_requirements(
1211
+ value: &TomlValueNode,
1212
+ requirements: &mut Vec<InterpolationTypeRequirement>,
1213
+ ) {
1214
+ match value {
1215
+ TomlValueNode::String(node) => collect_toml_string_type_requirements(node, requirements),
1216
+ TomlValueNode::Literal(_) => {}
1217
+ TomlValueNode::Interpolation(node) => {
1218
+ requirements.push(InterpolationTypeRequirement::new(
1219
+ node.interpolation_index,
1220
+ TOML_VALUE_PYTHON_TYPE,
1221
+ "toml value",
1222
+ ));
1223
+ }
1224
+ TomlValueNode::Array(node) => {
1225
+ for item in &node.items {
1226
+ collect_toml_value_type_requirements(item, requirements);
1227
+ }
1228
+ }
1229
+ TomlValueNode::InlineTable(node) => {
1230
+ for entry in &node.entries {
1231
+ collect_toml_key_path_type_requirements(&entry.key_path, requirements);
1232
+ collect_toml_value_type_requirements(&entry.value, requirements);
1233
+ }
1234
+ }
1235
+ }
1236
+ }
1237
+
1238
+ fn collect_toml_string_type_requirements(
1239
+ string: &TomlStringNode,
1240
+ requirements: &mut Vec<InterpolationTypeRequirement>,
1241
+ ) {
1242
+ for chunk in &string.chunks {
1243
+ if let TomlStringPart::Interpolation(node) = chunk {
1244
+ requirements.push(InterpolationTypeRequirement::new(
1245
+ node.interpolation_index,
1246
+ STRING_PYTHON_TYPE,
1247
+ "toml string fragment",
1248
+ ));
1249
+ }
1250
+ }
1251
+ }
1252
+
1149
1253
  pub fn format_template_with_profile(
1150
1254
  template: &TemplateInput,
1151
1255
  profile: TomlProfile,
@@ -1,7 +1,9 @@
1
- use tstring_syntax::{TemplateInput, TemplateInterpolation, TemplateSegment};
1
+ use tstring_syntax::{
2
+ InterpolationTypeRequirement, TemplateInput, TemplateInterpolation, TemplateSegment,
3
+ };
2
4
  use tstring_toml::{
3
- TomlStatementNode, TomlValueNode, check_template, format_template, parse_template,
4
- parse_validated_template, validate_template,
5
+ TomlStatementNode, TomlValueNode, check_template, format_template,
6
+ interpolation_type_requirements, parse_template, parse_validated_template, validate_template,
5
7
  };
6
8
 
7
9
  fn interpolation(index: usize, expression: &str) -> TemplateSegment {
@@ -83,6 +85,41 @@ fn validates_toml_templates_with_supported_interpolations() {
83
85
  parse_validated_template(&template).expect("expected validated parse success");
84
86
  }
85
87
 
88
+ #[test]
89
+ fn reports_contextual_interpolation_type_requirements() {
90
+ let template = TemplateInput::from_segments(vec![
91
+ interpolation(0, "table"),
92
+ TemplateSegment::StaticText(".title = ".to_owned()),
93
+ interpolation(1, "value"),
94
+ TemplateSegment::StaticText("\nmessage = \"Hello ".to_owned()),
95
+ interpolation(2, "fragment"),
96
+ TemplateSegment::StaticText("\"\nmeta = { ".to_owned()),
97
+ interpolation(3, "inline_key"),
98
+ TemplateSegment::StaticText(" = ".to_owned()),
99
+ interpolation(4, "inline_value"),
100
+ TemplateSegment::StaticText(" }\n".to_owned()),
101
+ ]);
102
+
103
+ assert_eq!(
104
+ interpolation_type_requirements(&template).expect("expected type requirements"),
105
+ vec![
106
+ InterpolationTypeRequirement::new(0, "str", "toml key"),
107
+ InterpolationTypeRequirement::new(
108
+ 1,
109
+ "str | int | float | bool | datetime.date | datetime.time | datetime.datetime | list[object] | dict[str, object]",
110
+ "toml value"
111
+ ),
112
+ InterpolationTypeRequirement::new(2, "str", "toml string fragment"),
113
+ InterpolationTypeRequirement::new(3, "str", "toml key"),
114
+ InterpolationTypeRequirement::new(
115
+ 4,
116
+ "str | int | float | bool | datetime.date | datetime.time | datetime.datetime | list[object] | dict[str, object]",
117
+ "toml value"
118
+ ),
119
+ ]
120
+ );
121
+ }
122
+
86
123
  #[test]
87
124
  fn formats_toml_templates_with_raw_interpolations() {
88
125
  let template = TemplateInput::from_segments(vec![
@@ -176,6 +176,28 @@ impl std::error::Error for BackendError {}
176
176
 
177
177
  pub type BackendResult<T> = Result<T, BackendError>;
178
178
 
179
+ #[derive(Clone, Debug, PartialEq, Eq)]
180
+ pub struct InterpolationTypeRequirement {
181
+ pub interpolation_index: usize,
182
+ pub expected_python_type: String,
183
+ pub expected_description: String,
184
+ }
185
+
186
+ impl InterpolationTypeRequirement {
187
+ #[must_use]
188
+ pub fn new(
189
+ interpolation_index: usize,
190
+ expected_python_type: impl Into<String>,
191
+ expected_description: impl Into<String>,
192
+ ) -> Self {
193
+ Self {
194
+ interpolation_index,
195
+ expected_python_type: expected_python_type.into(),
196
+ expected_description: expected_description.into(),
197
+ }
198
+ }
199
+ }
200
+
179
201
  #[derive(Clone, Debug, PartialEq)]
180
202
  pub struct NormalizedStream {
181
203
  pub documents: Vec<NormalizedDocument>,
@@ -16,10 +16,10 @@ serde_json = { workspace = true }
16
16
  saphyr = { workspace = true }
17
17
  saphyr-parser = { workspace = true }
18
18
  toml = { workspace = true }
19
- tstring-json = { version = "0.2.0", path = "../json-tstring-rs" }
20
- tstring-syntax = { version = "0.2.0", path = "../tstring-core-rs" }
21
- tstring-toml = { version = "0.2.0", path = "../toml-tstring-rs" }
22
- tstring-yaml = { version = "0.2.0", path = "../yaml-tstring-rs" }
19
+ tstring-json = { version = "0.2.2", path = "../json-tstring-rs" }
20
+ tstring-syntax = { version = "0.2.2", path = "../tstring-core-rs" }
21
+ tstring-toml = { version = "0.2.2", path = "../toml-tstring-rs" }
22
+ tstring-yaml = { version = "0.2.2", path = "../yaml-tstring-rs" }
23
23
 
24
24
  [dev-dependencies]
25
25
  criterion = "0.8.2"
@@ -11,7 +11,7 @@ fn benchmark_json_metadata_render(criterion: &mut Criterion) {
11
11
  let module = PyModule::from_code(
12
12
  py,
13
13
  pyo3::ffi::c_str!(
14
- "value=3.14159\nlabel='service'\nitems=[1, 2, 3]\nmeta={'region': 'us-east-1', 'count': 3}\ntemplate=t'{\"format\": {value:.2f}, \"label\": \"{label!s}\", \"items\": {items}, \"meta\": {meta}, \"fragment\": \"pi={value:.2f}\"}'\n"
14
+ "value=3.14159\nlabel='service'\nitems=[1, 2, 3]\nmeta={'region': 'us-east-1', 'count': 3}\ntemplate=t'{{\"format\": {value:.2f}, \"label\": \"{label!s}\", \"items\": {items}, \"meta\": {meta}, \"fragment\": \"pi={value:.2f}\"}}'\n"
15
15
  ),
16
16
  pyo3::ffi::c_str!("bench_json_metadata.py"),
17
17
  pyo3::ffi::c_str!("bench_json_metadata"),
@@ -26,7 +26,13 @@ fn benchmark_json_metadata_render(criterion: &mut Criterion) {
26
26
  criterion.bench_function("json_metadata_render", |bench| {
27
27
  bench.iter(|| {
28
28
  Python::with_gil(|py| {
29
- json_backend::render_document(py, &template, node.as_ref()).unwrap();
29
+ json_backend::render_document(
30
+ py,
31
+ &template,
32
+ tstring_json::JsonProfile::default(),
33
+ node.as_ref(),
34
+ )
35
+ .unwrap();
30
36
  });
31
37
  });
32
38
  });
@@ -52,7 +58,13 @@ fn benchmark_toml_metadata_render(criterion: &mut Criterion) {
52
58
  criterion.bench_function("toml_metadata_render", |bench| {
53
59
  bench.iter(|| {
54
60
  Python::with_gil(|py| {
55
- toml_backend::render_document(py, &template, node.as_ref()).unwrap();
61
+ toml_backend::render_document(
62
+ py,
63
+ &template,
64
+ tstring_toml::TomlProfile::default(),
65
+ node.as_ref(),
66
+ )
67
+ .unwrap();
56
68
  });
57
69
  });
58
70
  });
@@ -78,7 +90,13 @@ fn benchmark_toml_dynamic_header_render(criterion: &mut Criterion) {
78
90
  criterion.bench_function("toml_dynamic_header_render", |bench| {
79
91
  bench.iter(|| {
80
92
  Python::with_gil(|py| {
81
- toml_backend::render_document(py, &template, node.as_ref()).unwrap();
93
+ toml_backend::render_document(
94
+ py,
95
+ &template,
96
+ tstring_toml::TomlProfile::default(),
97
+ node.as_ref(),
98
+ )
99
+ .unwrap();
82
100
  });
83
101
  });
84
102
  });
@@ -104,7 +122,13 @@ fn benchmark_yaml_metadata_render(criterion: &mut Criterion) {
104
122
  criterion.bench_function("yaml_metadata_render", |bench| {
105
123
  bench.iter(|| {
106
124
  Python::with_gil(|py| {
107
- yaml_backend::render_document(py, &template, node.as_ref()).unwrap();
125
+ yaml_backend::render_document(
126
+ py,
127
+ &template,
128
+ tstring_yaml::YamlProfile::default(),
129
+ node.as_ref(),
130
+ )
131
+ .unwrap();
108
132
  });
109
133
  });
110
134
  });
@@ -20,7 +20,7 @@ test = false
20
20
  saphyr = { workspace = true }
21
21
  saphyr-parser = { workspace = true }
22
22
  serde_json = { workspace = true }
23
- tstring-syntax = { version = "0.2.0", path = "../tstring-core-rs" }
23
+ tstring-syntax = { version = "0.2.2", path = "../tstring-core-rs" }
24
24
 
25
25
  [dev-dependencies]
26
26
  toml = { workspace = true }
@@ -3,11 +3,14 @@ use saphyr_parser::{ScalarStyle, Tag};
3
3
  use std::borrow::Cow;
4
4
  use std::str::FromStr;
5
5
  use tstring_syntax::{
6
- BackendError, BackendResult, NormalizedDocument, NormalizedEntry, NormalizedFloat,
7
- NormalizedKey, NormalizedKeyEntry, NormalizedStream, NormalizedValue, SourcePosition,
8
- SourceSpan, StreamItem, TemplateInput,
6
+ BackendError, BackendResult, InterpolationTypeRequirement, NormalizedDocument, NormalizedEntry,
7
+ NormalizedFloat, NormalizedKey, NormalizedKeyEntry, NormalizedStream, NormalizedValue,
8
+ SourcePosition, SourceSpan, StreamItem, TemplateInput,
9
9
  };
10
10
 
11
+ const YAML_VALUE_PYTHON_TYPE: &str = "str | int | float | bool | None | datetime.date | datetime.time | datetime.datetime | list[object] | dict[object, object]";
12
+ const STRING_PYTHON_TYPE: &str = "str";
13
+
11
14
  #[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
12
15
  pub enum YamlProfile {
13
16
  V1_2_2,
@@ -2084,6 +2087,172 @@ pub fn check_template(template: &TemplateInput) -> BackendResult<()> {
2084
2087
  check_template_with_profile(template, YamlProfile::default())
2085
2088
  }
2086
2089
 
2090
+ pub fn interpolation_type_requirements_with_profile(
2091
+ template: &TemplateInput,
2092
+ profile: YamlProfile,
2093
+ ) -> BackendResult<Vec<InterpolationTypeRequirement>> {
2094
+ let stream = parse_validated_template_with_profile(template, profile)?;
2095
+ let mut requirements = Vec::new();
2096
+ collect_yaml_stream_type_requirements(&stream, &mut requirements);
2097
+ requirements.sort_by_key(|requirement| requirement.interpolation_index);
2098
+ Ok(requirements)
2099
+ }
2100
+
2101
+ pub fn interpolation_type_requirements(
2102
+ template: &TemplateInput,
2103
+ ) -> BackendResult<Vec<InterpolationTypeRequirement>> {
2104
+ interpolation_type_requirements_with_profile(template, YamlProfile::default())
2105
+ }
2106
+
2107
+ #[derive(Clone, Copy, Debug, Eq, PartialEq)]
2108
+ enum YamlInterpolationContext {
2109
+ Value,
2110
+ MappingKey,
2111
+ }
2112
+
2113
+ fn collect_yaml_stream_type_requirements(
2114
+ stream: &YamlStreamNode,
2115
+ requirements: &mut Vec<InterpolationTypeRequirement>,
2116
+ ) {
2117
+ for document in &stream.documents {
2118
+ collect_yaml_value_type_requirements(
2119
+ &document.value,
2120
+ YamlInterpolationContext::Value,
2121
+ requirements,
2122
+ );
2123
+ }
2124
+ }
2125
+
2126
+ fn collect_yaml_value_type_requirements(
2127
+ value: &YamlValueNode,
2128
+ context: YamlInterpolationContext,
2129
+ requirements: &mut Vec<InterpolationTypeRequirement>,
2130
+ ) {
2131
+ match value {
2132
+ YamlValueNode::Scalar(node) => {
2133
+ collect_yaml_scalar_type_requirements(node, "yaml scalar fragment", requirements);
2134
+ }
2135
+ YamlValueNode::Interpolation(node) => {
2136
+ requirements.push(yaml_value_type_requirement(node, context));
2137
+ }
2138
+ YamlValueNode::Mapping(node) => {
2139
+ for entry in &node.entries {
2140
+ collect_yaml_key_type_requirements(&entry.key, requirements);
2141
+ collect_yaml_value_type_requirements(
2142
+ &entry.value,
2143
+ YamlInterpolationContext::Value,
2144
+ requirements,
2145
+ );
2146
+ }
2147
+ }
2148
+ YamlValueNode::Sequence(node) => {
2149
+ for item in &node.items {
2150
+ collect_yaml_value_type_requirements(item, context, requirements);
2151
+ }
2152
+ }
2153
+ YamlValueNode::Decorated(node) => {
2154
+ if let Some(tag) = &node.tag {
2155
+ collect_yaml_chunk_type_requirements(
2156
+ &tag.chunks,
2157
+ "yaml metadata fragment",
2158
+ requirements,
2159
+ );
2160
+ }
2161
+ if let Some(anchor) = &node.anchor {
2162
+ collect_yaml_chunk_type_requirements(
2163
+ &anchor.chunks,
2164
+ "yaml metadata fragment",
2165
+ requirements,
2166
+ );
2167
+ }
2168
+ collect_yaml_value_type_requirements(&node.value, context, requirements);
2169
+ }
2170
+ }
2171
+ }
2172
+
2173
+ fn collect_yaml_key_type_requirements(
2174
+ key: &YamlKeyNode,
2175
+ requirements: &mut Vec<InterpolationTypeRequirement>,
2176
+ ) {
2177
+ match &key.value {
2178
+ YamlKeyValue::Scalar(node) => {
2179
+ collect_yaml_scalar_type_requirements(node, "yaml scalar fragment", requirements);
2180
+ }
2181
+ YamlKeyValue::Interpolation(node) => {
2182
+ requirements.push(yaml_value_type_requirement(
2183
+ node,
2184
+ YamlInterpolationContext::MappingKey,
2185
+ ));
2186
+ }
2187
+ YamlKeyValue::Complex(node) => {
2188
+ collect_yaml_value_type_requirements(
2189
+ node,
2190
+ YamlInterpolationContext::MappingKey,
2191
+ requirements,
2192
+ );
2193
+ }
2194
+ }
2195
+ }
2196
+
2197
+ fn collect_yaml_scalar_type_requirements(
2198
+ scalar: &YamlScalarNode,
2199
+ description: &'static str,
2200
+ requirements: &mut Vec<InterpolationTypeRequirement>,
2201
+ ) {
2202
+ match scalar {
2203
+ YamlScalarNode::Plain(node) => {
2204
+ collect_yaml_chunk_type_requirements(&node.chunks, description, requirements);
2205
+ }
2206
+ YamlScalarNode::DoubleQuoted(node) => {
2207
+ collect_yaml_chunk_type_requirements(&node.chunks, description, requirements);
2208
+ }
2209
+ YamlScalarNode::SingleQuoted(node) => {
2210
+ collect_yaml_chunk_type_requirements(&node.chunks, description, requirements);
2211
+ }
2212
+ YamlScalarNode::Block(node) => {
2213
+ collect_yaml_chunk_type_requirements(&node.chunks, description, requirements);
2214
+ }
2215
+ YamlScalarNode::Alias(node) => {
2216
+ collect_yaml_chunk_type_requirements(
2217
+ &node.chunks,
2218
+ "yaml metadata fragment",
2219
+ requirements,
2220
+ );
2221
+ }
2222
+ }
2223
+ }
2224
+
2225
+ fn collect_yaml_chunk_type_requirements(
2226
+ chunks: &[YamlChunk],
2227
+ description: &'static str,
2228
+ requirements: &mut Vec<InterpolationTypeRequirement>,
2229
+ ) {
2230
+ for chunk in chunks {
2231
+ if let YamlChunk::Interpolation(node) = chunk {
2232
+ requirements.push(InterpolationTypeRequirement::new(
2233
+ node.interpolation_index,
2234
+ STRING_PYTHON_TYPE,
2235
+ description,
2236
+ ));
2237
+ }
2238
+ }
2239
+ }
2240
+
2241
+ fn yaml_value_type_requirement(
2242
+ node: &YamlInterpolationNode,
2243
+ context: YamlInterpolationContext,
2244
+ ) -> InterpolationTypeRequirement {
2245
+ let description = match context {
2246
+ YamlInterpolationContext::Value => "yaml value",
2247
+ YamlInterpolationContext::MappingKey => "yaml mapping key",
2248
+ };
2249
+ InterpolationTypeRequirement::new(
2250
+ node.interpolation_index,
2251
+ YAML_VALUE_PYTHON_TYPE,
2252
+ description,
2253
+ )
2254
+ }
2255
+
2087
2256
  pub fn format_template_with_profile(
2088
2257
  template: &TemplateInput,
2089
2258
  profile: YamlProfile,
@@ -1,6 +1,9 @@
1
- use tstring_syntax::{TemplateInput, TemplateInterpolation, TemplateSegment};
1
+ use tstring_syntax::{
2
+ InterpolationTypeRequirement, TemplateInput, TemplateInterpolation, TemplateSegment,
3
+ };
2
4
  use tstring_yaml::{
3
- YamlValueNode, check_template, format_template, parse_template, validate_template,
5
+ YamlValueNode, check_template, format_template, interpolation_type_requirements,
6
+ parse_template, validate_template,
4
7
  };
5
8
 
6
9
  fn interpolation(index: usize, expression: &str) -> TemplateSegment {
@@ -129,6 +132,63 @@ fn validates_plain_scalars_with_multiple_interpolations_and_no_whitespace() {
129
132
  validate_template(&template).expect("expected YAML validation success");
130
133
  }
131
134
 
135
+ #[test]
136
+ fn reports_contextual_interpolation_type_requirements() {
137
+ let template = TemplateInput::from_segments(vec![
138
+ interpolation(0, "key"),
139
+ TemplateSegment::StaticText(": ".to_owned()),
140
+ interpolation(1, "value"),
141
+ TemplateSegment::StaticText("\nmessage: \"Hello ".to_owned()),
142
+ interpolation(2, "fragment"),
143
+ TemplateSegment::StaticText("\"\ntagged: !<tag:".to_owned()),
144
+ interpolation(3, "tag"),
145
+ TemplateSegment::StaticText("> ".to_owned()),
146
+ interpolation(4, "tagged_value"),
147
+ TemplateSegment::StaticText("\n".to_owned()),
148
+ ]);
149
+
150
+ assert_eq!(
151
+ interpolation_type_requirements(&template).expect("expected type requirements"),
152
+ vec![
153
+ InterpolationTypeRequirement::new(
154
+ 0,
155
+ "str | int | float | bool | None | datetime.date | datetime.time | datetime.datetime | list[object] | dict[object, object]",
156
+ "yaml mapping key"
157
+ ),
158
+ InterpolationTypeRequirement::new(
159
+ 1,
160
+ "str | int | float | bool | None | datetime.date | datetime.time | datetime.datetime | list[object] | dict[object, object]",
161
+ "yaml value"
162
+ ),
163
+ InterpolationTypeRequirement::new(2, "str", "yaml scalar fragment"),
164
+ InterpolationTypeRequirement::new(3, "str", "yaml metadata fragment"),
165
+ InterpolationTypeRequirement::new(
166
+ 4,
167
+ "str | int | float | bool | None | datetime.date | datetime.time | datetime.datetime | list[object] | dict[object, object]",
168
+ "yaml value"
169
+ ),
170
+ ]
171
+ );
172
+ }
173
+
174
+ #[test]
175
+ fn reports_complex_key_interpolation_requirements_in_key_context() {
176
+ let template = TemplateInput::from_segments(vec![
177
+ TemplateSegment::StaticText("? [".to_owned()),
178
+ interpolation(0, "item"),
179
+ TemplateSegment::StaticText("]\n: ok\n".to_owned()),
180
+ ]);
181
+
182
+ assert_eq!(
183
+ interpolation_type_requirements(&template).expect("expected type requirements"),
184
+ vec![InterpolationTypeRequirement::new(
185
+ 0,
186
+ "str | int | float | bool | None | datetime.date | datetime.time | datetime.datetime | list[object] | dict[object, object]",
187
+ "yaml mapping key"
188
+ )]
189
+ );
190
+ }
191
+
132
192
  #[test]
133
193
  fn formats_yaml_templates_with_raw_interpolations() {
134
194
  let template = TemplateInput::from_segments(vec![