Skip to main content

microsoft_fast_convert/syntax/
mod.rs

1pub(crate) mod fast_v3_ts;
2pub(crate) mod webui;
3
4use crate::error::{template_context, ConvertError};
5use crate::html::parse_attributes;
6
7/// Metadata for a supported converter syntax target.
8#[derive(Debug, Clone, Copy, PartialEq, Eq)]
9pub struct SyntaxMetadata {
10    /// Syntax value accepted by `convert_template`.
11    pub name: &'static str,
12    /// Required output extension for generated files.
13    pub extension: &'static str,
14    /// Default output suffix appended to the input basename.
15    pub suffix: &'static str,
16}
17
18const SYNTAX_METADATA: &[SyntaxMetadata] = &[webui::METADATA, fast_v3_ts::METADATA];
19
20pub fn syntax_metadata() -> &'static [SyntaxMetadata] {
21    SYNTAX_METADATA
22}
23
24pub(crate) fn accepted_syntax_names() -> String {
25    syntax_metadata()
26        .iter()
27        .map(|metadata| metadata.name)
28        .collect::<Vec<_>>()
29        .join(", ")
30}
31
32pub fn syntax_metadata_json() -> String {
33    let mut out = String::from("[");
34    for (index, metadata) in syntax_metadata().iter().enumerate() {
35        if index > 0 {
36            out.push(',');
37        }
38        out.push_str("{\"syntax\":\"");
39        out.push_str(metadata.name);
40        out.push_str("\",\"extension\":\"");
41        out.push_str(metadata.extension);
42        out.push_str("\",\"suffix\":\"");
43        out.push_str(metadata.suffix);
44        out.push_str("\"}");
45    }
46    out.push(']');
47    out
48}
49
50pub(crate) fn required_binding_value(
51    open_tag: &str,
52    tag: &str,
53    template: &str,
54    at: usize,
55) -> Result<String, ConvertError> {
56    let value = parse_attributes(open_tag)
57        .into_iter()
58        .find(|attr| attr.name == "value")
59        .and_then(|attr| attr.value)
60        .ok_or_else(|| ConvertError::MissingValueAttribute {
61            tag: tag.to_string(),
62            context: template_context(template, at),
63        })?;
64
65    let trimmed = value.trim();
66    if !trimmed.starts_with("{{") || !trimmed.ends_with("}}") || trimmed.len() <= 4 {
67        return Err(ConvertError::InvalidDirectiveValue {
68            tag: tag.to_string(),
69            value: Some(value),
70            context: template_context(template, at),
71        });
72    }
73
74    let expr = trimmed[2..trimmed.len() - 2].trim();
75    if expr.is_empty() {
76        return Err(ConvertError::InvalidDirectiveValue {
77            tag: tag.to_string(),
78            value: Some(value),
79            context: template_context(template, at),
80        });
81    }
82
83    Ok(expr.to_string())
84}
85
86pub(crate) fn validate_directive_attrs(
87    open_tag: &str,
88    template: &str,
89    at: usize,
90) -> Result<(), ConvertError> {
91    for attr in parse_attributes(open_tag) {
92        if attr.name.starts_with("f-") {
93            return Err(ConvertError::UnsupportedFAttribute {
94                attribute: attr.name,
95                context: template_context(template, at),
96            });
97        }
98    }
99    Ok(())
100}
101
102pub(crate) fn is_supported_f_attribute(name: &str) -> bool {
103    matches!(name, "f-ref" | "f-children" | "f-slotted")
104}
105
106pub(crate) fn strip_single_brace(value: &str) -> Option<&str> {
107    let trimmed = value.trim();
108    if trimmed.starts_with('{')
109        && trimmed.ends_with('}')
110        && !trimmed.starts_with("{{")
111        && trimmed.len() >= 2
112    {
113        Some(trimmed[1..trimmed.len() - 1].trim())
114    } else {
115        None
116    }
117}