Skip to main content

ferroui_base/metadata/
markup_assembly.rs

1//! Assembly-level markup metadata: the counterpart of the
2//! `XmlnsDefinition` and `XmlnsPrefix` assembly attributes of the managed
3//! original (`Metadata/XmlnsDefinitionAttribute.cs`,
4//! `Metadata/XmlnsPrefixAttribute.cs`).
5
6use std::sync::{OnceLock, RwLock};
7
8/// The XML namespace of the framework: the default namespace of its markup
9/// documents.
10pub const FERRO_XML_NAMESPACE: &str = "https://github.com/ferroui";
11
12/// Maps an XML namespace to a dotted namespace whose types it contains.
13#[derive(Clone, Copy, Debug, PartialEq, Eq)]
14pub struct XmlnsDefinition {
15    /// The URL of the XML namespace.
16    pub xml_namespace: &'static str,
17    /// The dotted namespace (`FerroUI.Controls`).
18    pub namespace: &'static str,
19}
20
21/// Recommends a prefix for an XML namespace.
22#[derive(Clone, Copy, Debug, PartialEq, Eq)]
23pub struct XmlnsPrefix {
24    /// The URL of the XML namespace.
25    pub xml_namespace: &'static str,
26    /// The recommended prefix.
27    pub prefix: &'static str,
28}
29
30/// What a crate states about itself for markup: the assembly name its
31/// types are addressed by (`using:Ns;assembly=FerroUI.Controls`, asset
32/// URIs) and its XML namespace definitions.
33///
34/// A crate declares one as a constant and registers it from its
35/// `register_types()` function. A type belongs to the assembly whose
36/// [`crate_name`](Self::crate_name) is the first segment of the module path
37/// of the type.
38#[derive(Debug, PartialEq, Eq)]
39pub struct MarkupAssembly {
40    /// The assembly name (`FerroUI.Controls`).
41    pub name: &'static str,
42    /// The crate, as module paths spell it (`ferroui_controls`).
43    pub crate_name: &'static str,
44    pub xmlns_definitions: &'static [XmlnsDefinition],
45    pub xmlns_prefixes: &'static [XmlnsPrefix],
46    /// Key/value metadata of the assembly (the `AssemblyMetadata` attributes
47    /// of the managed original). The markup loader reads
48    /// [`CREATE_SOURCE_INFO`](Self::CREATE_SOURCE_INFO).
49    pub metadata: &'static [(&'static str, &'static str)],
50}
51
52impl MarkupAssembly {
53    /// The metadata key that states whether markup compiled for the
54    /// assembly records source information by default (`"true"`/`"false"`).
55    pub const CREATE_SOURCE_INFO: &'static str = "FerroXamlCreateSourceInfo";
56
57    /// The metadata key that states whether `{Binding}` in markup of the
58    /// assembly is a compiled binding by default (`"True"`/`"False"`): the
59    /// `FerroUseCompiledBindingsByDefault` entry the build task of upstream writes
60    /// into every assembly it compiles markup for, with the value of its
61    /// `DefaultCompileBindings`. The previewer of the designer reads it.
62    pub const USE_COMPILED_BINDINGS_BY_DEFAULT: &'static str = "FerroUseCompiledBindingsByDefault";
63
64    /// The value of the metadata entry `key`.
65    pub fn metadata_value(&self, key: &str) -> Option<&'static str> {
66        self.metadata.iter().find(|(k, _)| *k == key).map(|(_, value)| *value)
67    }
68
69    /// Adds an assembly to the process-wide table of assemblies.
70    /// Registering an assembly again does nothing.
71    pub fn register(assembly: &'static MarkupAssembly) {
72        let mut assemblies = assemblies().write().unwrap_or_else(|e| e.into_inner());
73        if !assemblies.iter().any(|a| std::ptr::eq(*a, assembly)) {
74            assemblies.push(assembly);
75        }
76    }
77
78    /// Every registered assembly, in registration order.
79    pub fn registered_assemblies() -> Vec<&'static MarkupAssembly> {
80        assemblies().read().unwrap_or_else(|e| e.into_inner()).clone()
81    }
82
83    /// The registered assembly named `name`.
84    pub fn find(name: &str) -> Option<&'static MarkupAssembly> {
85        Self::registered_assemblies().into_iter().find(|a| a.name == name)
86    }
87
88    /// The registered assembly that declares the module `module_path`.
89    pub fn of_module(module_path: &str) -> Option<&'static MarkupAssembly> {
90        let crate_name = module_path.split("::").next().unwrap_or(module_path);
91        Self::registered_assemblies().into_iter().find(|a| a.crate_name == crate_name)
92    }
93}
94
95fn assemblies() -> &'static RwLock<Vec<&'static MarkupAssembly>> {
96    static ASSEMBLIES: OnceLock<RwLock<Vec<&'static MarkupAssembly>>> = OnceLock::new();
97    ASSEMBLIES.get_or_init(Default::default)
98}