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