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