Skip to content

Commit 36bfbea

Browse files
authored
Merge pull request #45 from koxudaxi/interpolation-type-requirements
Add interpolation type requirements
2 parents 22419d9 + 16c9177 commit 36bfbea

26 files changed

Lines changed: 651 additions & 78 deletions

File tree

json-tstring/pyproject.toml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,15 @@
11
[project]
22
name = "json-tstring"
3-
version = "0.1.0"
3+
version = "0.2.1"
44
description = "JSON renderer for t-string structured data templates"
55
readme = "README.md"
66
license = { text = "MIT" }
77
authors = [{ name = "Koudai Aono", email = "koxudaxi@gmail.com" }]
88
maintainers = [{ name = "Koudai Aono", email = "koxudaxi@gmail.com" }]
99
requires-python = ">=3.14"
1010
dependencies = [
11-
"tstring-bindings>=0.1.0",
12-
"tstring-core>=0.1.0",
11+
"tstring-bindings>=0.2.1",
12+
"tstring-core>=0.2.1",
1313
]
1414
keywords = ["pep750", "t-strings", "json", "template-strings", "structured-data"]
1515
classifiers = [

pyproject.toml

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,14 @@
11
[project]
22
name = "tstring-structured-data-workspace"
3-
version = "0.1.0"
3+
version = "0.2.1"
44
description = "Workspace for parser-first JSON, TOML, and YAML PEP 750 t-string backends"
55
readme = "README.md"
66
requires-python = ">=3.14"
77
dependencies = [
8-
"json-tstring>=0.1.0",
9-
"toml-tstring>=0.1.0",
10-
"tstring-core>=0.1.0",
11-
"yaml-tstring>=0.1.0",
8+
"json-tstring>=0.2.1",
9+
"toml-tstring>=0.2.1",
10+
"tstring-core>=0.2.1",
11+
"yaml-tstring>=0.2.1",
1212
]
1313

1414
[build-system]
@@ -18,14 +18,14 @@ build-backend = "uv_build"
1818
[dependency-groups]
1919
dev = [
2020
"inline-snapshot>=0.32.5",
21-
"json-tstring>=0.1.0",
21+
"json-tstring>=0.2.1",
2222
"maturin>=1.8.0",
2323
"pytest>=8.4.0",
2424
"ruff>=0.11.0",
25-
"toml-tstring>=0.1.0",
26-
"tstring-core>=0.1.0",
25+
"toml-tstring>=0.2.1",
26+
"tstring-core>=0.2.1",
2727
"ty>=0.0.23",
28-
"yaml-tstring>=0.1.0",
28+
"yaml-tstring>=0.2.1",
2929
]
3030
docs = [
3131
"zensical>=0.0.15",

rust/Cargo.lock

Lines changed: 8 additions & 8 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

rust/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ homepage = "https://github.com/koxudaxi/tstring-structured-data"
1818
license = "MIT"
1919
repository = "https://github.com/koxudaxi/tstring-structured-data"
2020
rust-version = "1.94.0"
21-
version = "0.1.0"
21+
version = "0.2.1"
2222

2323
[workspace.dependencies]
2424
num-bigint = "0.4.6"

rust/backend-e2e-tests/Cargo.toml

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ rust-version.workspace = true
1010
publish = false
1111

1212
[dev-dependencies]
13-
tstring-json = { version = "0.1.0", path = "../json-tstring-rs" }
14-
tstring-syntax = { version = "0.1.0", path = "../tstring-core-rs" }
15-
tstring-toml = { version = "0.1.0", path = "../toml-tstring-rs" }
16-
tstring-yaml = { version = "0.1.0", path = "../yaml-tstring-rs" }
13+
tstring-json = { version = "0.2.1", path = "../json-tstring-rs" }
14+
tstring-syntax = { version = "0.2.1", path = "../tstring-core-rs" }
15+
tstring-toml = { version = "0.2.1", path = "../toml-tstring-rs" }
16+
tstring-yaml = { version = "0.2.1", path = "../yaml-tstring-rs" }

rust/backend-e2e-tests/tests/backend_api.rs

Lines changed: 32 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
use tstring_json as backend_json;
2-
use tstring_syntax::{TemplateInput, TemplateInterpolation, TemplateSegment};
2+
use tstring_syntax::{
3+
InterpolationTypeRequirement, TemplateInput, TemplateInterpolation, TemplateSegment,
4+
};
35
use tstring_toml as backend_toml;
46
use tstring_yaml as backend_yaml;
57

@@ -53,6 +55,18 @@ fn toml_backend_public_api_smoke_test() {
5355
]);
5456

5557
backend_toml::check_template(&template).expect("expected toml check success");
58+
assert_eq!(
59+
backend_toml::interpolation_type_requirements(&template)
60+
.expect("expected toml type requirements"),
61+
vec![
62+
InterpolationTypeRequirement::new(
63+
0,
64+
"str | int | float | bool | datetime.date | datetime.time | datetime.datetime | list[object] | dict[str, object]",
65+
"toml value"
66+
),
67+
InterpolationTypeRequirement::new(1, "str", "toml string fragment"),
68+
]
69+
);
5670
assert_eq!(
5771
backend_toml::format_template(&template).expect("expected toml format success"),
5872
"title = {title}\nmessage = \"Hello {user!s}\""
@@ -78,6 +92,23 @@ fn yaml_backend_public_api_smoke_test() {
7892
]);
7993

8094
backend_yaml::check_template(&template).expect("expected yaml check success");
95+
assert_eq!(
96+
backend_yaml::interpolation_type_requirements(&template)
97+
.expect("expected yaml type requirements"),
98+
vec![
99+
InterpolationTypeRequirement::new(
100+
0,
101+
"str | int | float | bool | None | datetime.date | datetime.time | datetime.datetime | list[object] | dict[object, object]",
102+
"yaml value"
103+
),
104+
InterpolationTypeRequirement::new(
105+
1,
106+
"str | int | float | bool | None | datetime.date | datetime.time | datetime.datetime | list[object] | dict[object, object]",
107+
"yaml value"
108+
),
109+
InterpolationTypeRequirement::new(2, "str", "yaml scalar fragment"),
110+
]
111+
);
81112
assert_eq!(
82113
backend_yaml::format_template(&template).expect("expected yaml format success"),
83114
"name: {name}\nitems:\n - {item}\nmessage: \"Hello {user!r:>5}\""

rust/json-tstring-rs/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ test = false
1818

1919
[dependencies]
2020
serde_json = { workspace = true }
21-
tstring-syntax = { version = "0.1.0", path = "../tstring-core-rs" }
21+
tstring-syntax = { version = "0.2.1", path = "../tstring-core-rs" }
2222

2323
[dev-dependencies]
2424
toml = { workspace = true }

rust/json-tstring-rs/src/lib.rs

Lines changed: 100 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,15 @@
11
use serde_json::Value;
22
use std::str::FromStr;
33
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,
67
};
78

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+
813
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
914
pub enum JsonProfile {
1015
Rfc8259,
@@ -636,6 +641,99 @@ pub fn check_template(template: &TemplateInput) -> BackendResult<()> {
636641
check_template_with_profile(template, JsonProfile::default())
637642
}
638643

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+
639737
pub fn format_template_with_profile(
640738
template: &TemplateInput,
641739
profile: JsonProfile,

rust/json-tstring-rs/tests/parser.rs

Lines changed: 31 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
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,
47
};
5-
use tstring_syntax::{TemplateInput, TemplateInterpolation, TemplateSegment};
68

79
fn interpolation(index: usize, expression: &str) -> TemplateSegment {
810
TemplateSegment::Interpolation(TemplateInterpolation {
@@ -69,6 +71,32 @@ fn checks_valid_json_templates() {
6971
check_template(&template).expect("expected check success");
7072
}
7173

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+
72100
#[test]
73101
fn validates_json_templates_with_supported_interpolations() {
74102
let template = TemplateInput::from_segments(vec![

rust/python-bindings/Cargo.toml

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -23,12 +23,12 @@ pyo3 = { workspace = true, features = ["abi3-py314"] }
2323
pythonize = { workspace = true }
2424
saphyr = { workspace = true }
2525
serde_json = { workspace = true }
26-
tstring-json = { version = "0.1.0", path = "../json-tstring-rs" }
27-
tstring-pyo3-bindings = { version = "0.1.0", path = "../tstring-pyo3-bindings" }
28-
tstring-syntax = { version = "0.1.0", path = "../tstring-core-rs" }
26+
tstring-json = { version = "0.2.1", path = "../json-tstring-rs" }
27+
tstring-pyo3-bindings = { version = "0.2.1", path = "../tstring-pyo3-bindings" }
28+
tstring-syntax = { version = "0.2.1", path = "../tstring-core-rs" }
2929
toml = { workspace = true }
30-
tstring-toml = { version = "0.1.0", path = "../toml-tstring-rs" }
31-
tstring-yaml = { version = "0.1.0", path = "../yaml-tstring-rs" }
30+
tstring-toml = { version = "0.2.1", path = "../toml-tstring-rs" }
31+
tstring-yaml = { version = "0.2.1", path = "../yaml-tstring-rs" }
3232

3333
[dev-dependencies]
3434
pyo3 = { workspace = true, features = ["auto-initialize"] }

0 commit comments

Comments
 (0)