light_fw_cli_core/
registry.rs1use std::collections::HashMap;
2
3use serde::Deserialize;
4
5use crate::manifest::{ComponentKind, Provides};
6
7const REGISTRY_TOML: &str = include_str!("../registry.toml");
12
13#[derive(Debug, Clone)]
15pub struct ComponentInfo {
16 pub name: String,
18 pub kind: ComponentKind,
20 pub display_name: String,
22 pub latest_version: semver::Version,
24 pub core_requirement: semver::VersionReq,
26 pub light_features: Vec<String>,
28 pub provides: Provides,
30}
31
32impl ComponentInfo {
33 pub fn requirement(&self) -> String {
35 format!("{} {}", self.name, self.core_requirement)
36 }
37}
38
39pub struct ComponentRegistry {
41 components: Vec<ComponentInfo>,
42 by_name: HashMap<String, usize>,
43 by_element: HashMap<String, usize>,
44 by_attribute: HashMap<String, usize>,
45 by_directive: HashMap<String, usize>,
46}
47
48#[derive(Debug, Deserialize)]
49struct RegistryFile {
50 #[serde(default)]
51 components: Vec<RegistryEntry>,
52}
53
54#[derive(Debug, Deserialize)]
55struct RegistryEntry {
56 name: String,
57 kind: ComponentKind,
58 #[serde(rename = "display-name")]
59 display_name: String,
60 version: String,
61 core: String,
62 #[serde(default, rename = "light-features")]
63 light_features: Vec<String>,
64 #[serde(default)]
65 elements: Vec<String>,
66 #[serde(default)]
67 attributes: Vec<String>,
68 #[serde(default)]
69 directives: Vec<String>,
70}
71
72impl ComponentRegistry {
73 pub fn builtin() -> Self {
79 Self::from_toml(REGISTRY_TOML).expect("встроенный registry.toml должен разбираться")
80 }
81
82 pub fn from_toml(text: &str) -> crate::Result<Self> {
84 let file: RegistryFile =
85 toml_edit::de::from_str(text).map_err(|e| crate::Error::Manifest(e.to_string()))?;
86 let mut registry = ComponentRegistry {
87 components: Vec::new(),
88 by_name: HashMap::new(),
89 by_element: HashMap::new(),
90 by_attribute: HashMap::new(),
91 by_directive: HashMap::new(),
92 };
93 for entry in file.components {
94 let latest_version = semver::Version::parse(&entry.version)
95 .map_err(|e| crate::Error::Manifest(format!("invalid version: {e}")))?;
96 let core_requirement = semver::VersionReq::parse(&entry.core)
97 .map_err(|e| crate::Error::Manifest(format!("invalid core range: {e}")))?;
98 let provides = Provides {
99 elements: entry.elements,
100 attributes: entry.attributes,
101 directives: entry.directives,
102 };
103 registry.insert(ComponentInfo {
104 name: entry.name,
105 kind: entry.kind,
106 display_name: entry.display_name,
107 latest_version,
108 core_requirement,
109 light_features: entry.light_features,
110 provides,
111 });
112 }
113 Ok(registry)
114 }
115
116 fn insert(&mut self, info: ComponentInfo) {
117 let idx = self.components.len();
118 for tag in &info.provides.elements {
119 self.by_element.insert(tag.clone(), idx);
120 }
121 for attr in &info.provides.attributes {
122 self.by_attribute.insert(attr.clone(), idx);
123 }
124 for dir in &info.provides.directives {
125 self.by_directive.insert(dir.clone(), idx);
126 }
127 self.by_name.insert(info.name.clone(), idx);
128 self.components.push(info);
129 }
130
131 pub fn all(&self) -> &[ComponentInfo] {
133 &self.components
134 }
135
136 pub fn by_name(&self, name: &str) -> Option<&ComponentInfo> {
138 self.by_name.get(name).map(|&i| &self.components[i])
139 }
140
141 pub fn component_for_element(&self, tag: &str) -> Option<&ComponentInfo> {
143 self.by_element.get(tag).map(|&i| &self.components[i])
144 }
145
146 pub fn component_for_attribute(&self, attr: &str) -> Option<&ComponentInfo> {
148 self.by_attribute.get(attr).map(|&i| &self.components[i])
149 }
150
151 pub fn component_for_directive(&self, dir: &str) -> Option<&ComponentInfo> {
153 self.by_directive.get(dir).map(|&i| &self.components[i])
154 }
155}
156
157#[cfg(test)]
158mod tests {
159 use super::*;
160
161 #[test]
162 fn builtin_registry_is_valid() {
163 let r = ComponentRegistry::builtin();
164 assert!(r.all().len() >= 2);
165 assert!(r.by_name("light-fw-element-form").is_some());
166 }
167
168 #[test]
169 fn resolves_elements() {
170 let r = ComponentRegistry::builtin();
171 let form = r.component_for_element("checkbox").expect("checkbox");
172 assert_eq!(form.name, "light-fw-element-form");
173 assert!(
174 r.component_for_element("text").is_none(),
175 "текстовые поля входят в ядро и компонента не требуют"
176 );
177 }
178
179 #[test]
180 fn resolves_attributes_and_directives() {
181 let r = ComponentRegistry::builtin();
182 assert_eq!(
183 r.component_for_attribute("required")
184 .map(|c| c.name.as_str()),
185 Some("light-fw-behavior-validation")
186 );
187 assert_eq!(
188 r.component_for_directive("@validate")
189 .map(|c| c.name.as_str()),
190 Some("light-fw-behavior-validation")
191 );
192 }
193
194 #[test]
195 fn unknown_component_is_none() {
196 let r = ComponentRegistry::builtin();
197 assert!(r.component_for_element("definitely-not-real").is_none());
198 }
199}