windows_gen 0.26.0

Code gen support for the windows crate
Documentation
use super::*;

pub struct Gen {
    pub relative: &'static str,
    pub root: &'static str,
    pub ignore_windows_features: bool,
}

impl Gen {
    pub fn absolute() -> Self {
        Gen { relative: "", root: "", ignore_windows_features: false }
    }

    pub fn relative(namespace: &'static str) -> Self {
        Gen { relative: namespace, root: "", ignore_windows_features: false }
    }

    pub fn namespace(&self, namespace: &str) -> TokenStream {
        if self.relative.is_empty() {
            let mut tokens = TokenStream::with_capacity();

            for namespace in namespace.split('.') {
                tokens.push_str(namespace);
                tokens.push_str("::");
            }

            tokens
        } else {
            if namespace == self.relative {
                return TokenStream::new();
            }

            if !self.root.is_empty() && self.root != "Windows" && namespace.starts_with("Windows.") {
                let mut tokens: TokenStream = "::windows::".into();

                for namespace in namespace.split('.').skip(1) {
                    tokens.push_str(namespace);
                    tokens.push_str("::");
                }

                return tokens;
            }

            let mut relative = self.relative.split('.').peekable();
            let mut namespace = namespace.split('.').peekable();

            while relative.peek() == namespace.peek() {
                if relative.next().is_none() {
                    break;
                }

                namespace.next();
            }

            let mut tokens = TokenStream::with_capacity();

            for _ in 0..relative.count() {
                tokens.push_str("super::");
            }

            for namespace in namespace {
                tokens.push_str(namespace);
                tokens.push_str("::");
            }

            tokens
        }
    }

    pub fn class_features(&self, def: &TypeDef) -> BTreeSet<&'static str> {
        if self.root.is_empty() {
            return BTreeSet::new();
        }

        let mut features = BTreeSet::new();

        if let Some(def) = def.default_interface() {
            features.insert(def.namespace());
        }

        features
    }

    pub fn gen_function_cfg(&self, attributes: impl Iterator<Item = Attribute>, signature: &MethodSignature) -> TokenStream {
        let arch = self.gen_arch_cfg(attributes);
        let features = signature.method_features();
        let cfg = self.gen_cfg(&features);
        let doc = self.gen_cfg_doc(&features);

        quote! { #doc #arch #cfg }
    }

    pub fn gen_struct_cfg(&self, def: &TypeDef, features: &BTreeSet<&'static str>) -> TokenStream {
        let arch = self.gen_arch_cfg(def.attributes());
        let cfg = self.gen_cfg_impl(features, false);

        quote! { #arch #cfg }
    }

    pub fn gen_cfg_doc(&self, features: &BTreeSet<&'static str>) -> TokenStream {
        if self.root.is_empty() {
            return TokenStream::new();
        }

        let mut tokens = format!("`{}`", self.relative[self.root.len() + 1..].replace('.', "_"));

        for feature in features {
            if self.relative == *feature {
                continue;
            }

            if self.relative.len() > feature.len() && self.relative.starts_with(feature) && self.relative[feature.len()..].starts_with('.') {
                continue;
            }

            if self.ignore_windows_features && feature.starts_with("Windows.") {
                continue;
            }

            let feature = if feature.starts_with(self.root) && feature[self.root.len()..].starts_with('.') { &feature[self.root.len() + 1..] } else { &feature };
            tokens.push_str(&format!(", `{}`", feature.replace('.', "_")));
        }

        format!(r#"#[doc = "*Required features: {}*"]"#, tokens).into()
    }

    fn gen_arch_cfg(&self, attributes: impl Iterator<Item = Attribute>) -> TokenStream {
        for attribute in attributes {
            if attribute.name() == "SupportedArchitectureAttribute" {
                if let Some((_, ConstantValue::I32(value))) = attribute.args().get(0) {
                    let mut cfg = "#[cfg(any(".to_string();
                    if value & 1 == 1 {
                        cfg.push_str(r#"target_arch = "x86", "#);
                    }
                    if value & 2 == 2 {
                        cfg.push_str(r#"target_arch = "x86_64", "#);
                    }
                    if value & 4 == 4 {
                        cfg.push_str(r#"target_arch = "aarch64", "#);
                    }
                    cfg.push_str("))]");
                    return cfg.into();
                }
            }
        }

        TokenStream::new()
    }

    pub fn gen_cfg(&self, features: &BTreeSet<&'static str>) -> TokenStream {
        self.gen_cfg_impl(features, false)
    }

    pub fn gen_cfg_not(&self, features: &BTreeSet<&'static str>) -> TokenStream {
        self.gen_cfg_impl(features, true)
    }

    fn gen_cfg_impl(&self, features: &BTreeSet<&'static str>, not: bool) -> TokenStream {
        if self.root.is_empty() {
            return TokenStream::new();
        }

        if features.is_empty() {
            return TokenStream::new();
        }

        let mut tokens = String::new();
        let mut count = 0;

        for feature in features {
            if self.relative == *feature {
                continue;
            }

            if self.relative.len() > feature.len() && self.relative.starts_with(feature) && self.relative[feature.len()..].starts_with('.') {
                continue;
            }

            if self.ignore_windows_features && feature.starts_with("Windows.") {
                continue;
            }

            let feature = if feature.starts_with(self.root) && feature[self.root.len()..].starts_with('.') { &feature[self.root.len() + 1..] } else { &feature };
            tokens.push_str(&format!("feature = \"{}\", ", feature.replace('.', "_")));
            count += 1;
        }

        if count == 0 {
            return TokenStream::new();
        }

        let all = count > 1;
        let mut cfg = "#[cfg(".to_string();

        if not {
            cfg.push_str("not(")
        }

        if all {
            cfg.push_str("all(")
        }

        tokens.truncate(tokens.len() - 2);
        cfg.push_str(&tokens);

        if not {
            cfg.push(')');
        }

        if all {
            cfg.push(')');
        }

        cfg.push_str(")]");
        cfg.into()
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_namespace() {
        assert_eq!(Gen::absolute().namespace("Windows.Foundation").as_str(), "Windows::Foundation::");

        assert_eq!(Gen::relative("Windows").namespace("Windows.Foundation").as_str(), "Foundation::");

        assert_eq!(Gen::relative("Windows.Foundation").namespace("Windows.Foundation").as_str(), "");

        assert_eq!(Gen::relative("Windows.Foundation.Collections").namespace("Windows.Foundation").as_str(), "super::");
    }

    #[test]
    fn test_features() {
        let mut features = BTreeSet::new();
        features.insert("Windows.Foundation");
        assert_eq!(Gen { root: "Microsoft", relative: "", ignore_windows_features: false }.gen_cfg(&features).as_str(), r#"#[cfg(feature = "Windows_Foundation")]"#);
        assert_eq!(
            Gen {
                root: "Microsoft",
                relative: "Microsoft.UI.Composition.Diagnostics",
                ignore_windows_features: false
            }
            .gen_cfg_doc(&features)
            .as_str(),
            r#"#[doc = "*Required features: `UI_Composition_Diagnostics`, `Windows_Foundation`*"]"#
        );

        let mut features = BTreeSet::new();
        features.insert("Microsoft.Foundation");
        assert_eq!(Gen { root: "Microsoft", relative: "", ignore_windows_features: false }.gen_cfg(&features).as_str(), r#"#[cfg(feature = "Foundation")]"#);
        assert_eq!(
            Gen {
                root: "Microsoft",
                relative: "Microsoft.UI.Composition.Diagnostics",
                ignore_windows_features: false
            }
            .gen_cfg_doc(&features)
            .as_str(),
            r#"#[doc = "*Required features: `UI_Composition_Diagnostics`, `Foundation`*"]"#
        );
    }
}