1use std::sync::Once;
2
3use ast_merge::{
4 ConformanceFamilyPlanContext, ConformanceFeatureProfileView, Diagnostic, DiagnosticCategory,
5 DiagnosticSeverity, MergeResult, ParseResult, PolicyReference, PolicySurface,
6};
7use pest::Parser;
8use pest_grammars::toml::{Rule as PestTomlRule, TomlParser as PestTomlParser};
9use toml_merge::{
10 TomlAnalysis, TomlDialect, TomlFeatureProfile, TomlOwnerMatchResult, analyze_toml_source,
11 match_toml_owners as match_toml_owners_with_substrate, merge_toml_with_parser,
12 toml_feature_profile,
13};
14use tree_haver::{BackendReference, register_backend};
15
16pub const PACKAGE_NAME: &str = "pest-toml-merge";
17pub const BACKEND_ID: &str = "pest";
18
19fn ensure_backend_registered() {
20 static ONCE: Once = Once::new();
21 ONCE.call_once(|| {
22 register_backend(BackendReference {
23 id: BACKEND_ID.to_string(),
24 family: "peg".to_string(),
25 });
26 });
27}
28
29fn unsupported_feature(message: &str) -> Diagnostic {
30 Diagnostic {
31 severity: DiagnosticSeverity::Error,
32 category: DiagnosticCategory::UnsupportedFeature,
33 message: message.to_string(),
34 path: None,
35 review: None,
36 }
37}
38
39fn parse_error(message: &str) -> Diagnostic {
40 Diagnostic {
41 severity: DiagnosticSeverity::Error,
42 category: DiagnosticCategory::ParseError,
43 message: message.to_string(),
44 path: None,
45 review: None,
46 }
47}
48
49pub fn available_toml_backends() -> Vec<String> {
50 ensure_backend_registered();
51 vec![BACKEND_ID.to_string()]
52}
53
54pub fn toml_backend_feature_profile() -> std::collections::BTreeMap<String, serde_json::Value> {
55 ensure_backend_registered();
56 let mut profile = serde_json::Map::new();
57 profile.insert("family".to_string(), serde_json::Value::String("toml".to_string()));
58 profile.insert(
59 "supported_dialects".to_string(),
60 serde_json::Value::Array(vec![serde_json::Value::String("toml".to_string())]),
61 );
62 profile.insert(
63 "supported_policies".to_string(),
64 serde_json::Value::Array(vec![serde_json::json!({
65 "surface": "array",
66 "name": "destination_wins_array",
67 })]),
68 );
69 profile.insert("backend".to_string(), serde_json::Value::String(BACKEND_ID.to_string()));
70 profile.insert(
71 "backend_ref".to_string(),
72 serde_json::json!({
73 "id": BACKEND_ID,
74 "family": "peg",
75 }),
76 );
77 profile.into_iter().collect()
78}
79
80pub fn toml_plan_context() -> ConformanceFamilyPlanContext {
81 ensure_backend_registered();
82 ConformanceFamilyPlanContext {
83 family_profile: ast_merge::FamilyFeatureProfile {
84 family: "toml".to_string(),
85 supported_dialects: vec!["toml".to_string()],
86 supported_policies: vec![PolicyReference {
87 surface: PolicySurface::Array,
88 name: "destination_wins_array".to_string(),
89 }],
90 },
91 feature_profile: Some(ConformanceFeatureProfileView {
92 backend: BACKEND_ID.to_string(),
93 supports_dialects: false,
94 supported_policies: vec![PolicyReference {
95 surface: PolicySurface::Array,
96 name: "destination_wins_array".to_string(),
97 }],
98 }),
99 }
100}
101
102pub fn provider_toml_feature_profile() -> TomlFeatureProfile {
103 toml_feature_profile()
104}
105
106pub fn parse_toml(
107 source: &str,
108 dialect: TomlDialect,
109 backend: Option<&str>,
110) -> ParseResult<TomlAnalysis> {
111 ensure_backend_registered();
112 let requested = backend.unwrap_or(BACKEND_ID);
113 if requested != BACKEND_ID {
114 return ParseResult {
115 ok: false,
116 diagnostics: vec![unsupported_feature(&format!(
117 "Unsupported TOML backend {requested}."
118 ))],
119 analysis: None,
120 policies: vec![],
121 };
122 }
123
124 if let Err(error) = PestTomlParser::parse(PestTomlRule::toml, source) {
125 return ParseResult {
126 ok: false,
127 diagnostics: vec![parse_error(&error.to_string())],
128 analysis: None,
129 policies: vec![],
130 };
131 }
132
133 analyze_toml_source(source, dialect)
134}
135
136pub fn match_toml_owners(
137 template: &TomlAnalysis,
138 destination: &TomlAnalysis,
139) -> TomlOwnerMatchResult {
140 match_toml_owners_with_substrate(template, destination)
141}
142
143pub fn merge_toml(
144 template_source: &str,
145 destination_source: &str,
146 dialect: TomlDialect,
147 backend: Option<&str>,
148) -> MergeResult<String> {
149 ensure_backend_registered();
150 let requested = backend.unwrap_or(BACKEND_ID);
151 if requested != BACKEND_ID {
152 return MergeResult {
153 ok: false,
154 diagnostics: vec![unsupported_feature(&format!(
155 "Unsupported TOML backend {requested}."
156 ))],
157 output: None,
158 policies: vec![],
159 };
160 }
161
162 merge_toml_with_parser(template_source, destination_source, dialect, |source, parse_dialect| {
163 parse_toml(source, parse_dialect, None)
164 })
165}