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}