npm_run_scripts/package/
descriptions.rs1use std::collections::HashMap;
11
12use super::types::{Package, Script};
13
14pub fn extract_descriptions(package: &Package) -> HashMap<String, String> {
28 let mut descriptions = HashMap::new();
29
30 for (name, desc) in &package.scripts_info {
32 descriptions.insert(name.clone(), desc.clone());
33 }
34
35 if let Some(ntl) = &package.ntl {
37 for (name, desc) in &ntl.descriptions {
38 descriptions
39 .entry(name.clone())
40 .or_insert_with(|| desc.clone());
41 }
42 }
43
44 for (key, value) in &package.scripts {
47 if let Some(script_name) = parse_comment_key(key) {
48 descriptions
49 .entry(script_name)
50 .or_insert_with(|| value.clone());
51 }
52 }
53
54 descriptions
55}
56
57fn parse_comment_key(key: &str) -> Option<String> {
65 if !key.starts_with("//") {
66 return None;
67 }
68
69 let stripped = key.strip_prefix("//")?;
71
72 let stripped = stripped.strip_suffix("//").unwrap_or(stripped);
74
75 let script_name = stripped.trim();
77
78 if script_name.is_empty() {
79 return None;
80 }
81
82 Some(script_name.to_string())
83}
84
85pub fn get_description(script: &Script) -> String {
102 script
103 .description()
104 .map(|d| d.to_string())
105 .unwrap_or_else(|| format!("$ {}", script.command()))
106}
107
108pub fn get_short_description(script: &Script, max_len: usize) -> String {
115 let desc = get_description(script);
116 if desc.len() <= max_len {
117 desc
118 } else {
119 format!("{}...", &desc[..max_len.saturating_sub(3)])
120 }
121}
122
123#[cfg(test)]
124mod tests {
125 use super::*;
126
127 #[test]
128 fn test_get_description_with_desc() {
129 let script = Script::with_description("dev", "vite", "Start dev server");
130 assert_eq!(get_description(&script), "Start dev server");
131 }
132
133 #[test]
134 fn test_get_description_fallback() {
135 let script = Script::new("dev", "vite");
136 assert_eq!(get_description(&script), "$ vite");
137 }
138
139 #[test]
140 fn test_get_short_description() {
141 let script = Script::with_description(
142 "dev",
143 "vite",
144 "This is a very long description that should be truncated",
145 );
146 let short = get_short_description(&script, 20);
147 assert_eq!(short, "This is a very lo...");
148 assert!(short.len() <= 20);
149 }
150
151 #[test]
152 fn test_get_short_description_no_truncation() {
153 let script = Script::with_description("dev", "vite", "Short desc");
154 let short = get_short_description(&script, 20);
155 assert_eq!(short, "Short desc");
156 }
157
158 #[test]
159 fn test_parse_comment_key_standard() {
160 assert_eq!(parse_comment_key("//dev"), Some("dev".to_string()));
161 assert_eq!(parse_comment_key("//build"), Some("build".to_string()));
162 }
163
164 #[test]
165 fn test_parse_comment_key_with_space() {
166 assert_eq!(parse_comment_key("// dev"), Some("dev".to_string()));
167 assert_eq!(parse_comment_key("// build"), Some("build".to_string()));
168 }
169
170 #[test]
171 fn test_parse_comment_key_with_trailing_slashes() {
172 assert_eq!(parse_comment_key("//dev//"), Some("dev".to_string()));
173 assert_eq!(parse_comment_key("// build //"), Some("build".to_string()));
174 }
175
176 #[test]
177 fn test_parse_comment_key_non_comment() {
178 assert_eq!(parse_comment_key("dev"), None);
179 assert_eq!(parse_comment_key("/dev"), None);
180 }
181
182 #[test]
183 fn test_parse_comment_key_empty() {
184 assert_eq!(parse_comment_key("//"), None);
185 assert_eq!(parse_comment_key("// "), None);
186 assert_eq!(parse_comment_key("////"), None);
187 }
188
189 #[test]
190 fn test_extract_descriptions_scripts_info() {
191 let package = Package {
192 scripts_info: [("dev".to_string(), "Start development".to_string())]
193 .into_iter()
194 .collect(),
195 ..Default::default()
196 };
197
198 let descriptions = extract_descriptions(&package);
199 assert_eq!(
200 descriptions.get("dev"),
201 Some(&"Start development".to_string())
202 );
203 }
204
205 #[test]
206 fn test_extract_descriptions_ntl() {
207 use super::super::types::NtlConfig;
208
209 let package = Package {
210 ntl: Some(NtlConfig {
211 descriptions: [("test".to_string(), "Run tests".to_string())]
212 .into_iter()
213 .collect(),
214 }),
215 ..Default::default()
216 };
217
218 let descriptions = extract_descriptions(&package);
219 assert_eq!(descriptions.get("test"), Some(&"Run tests".to_string()));
220 }
221
222 #[test]
223 fn test_extract_descriptions_comments() {
224 let package = Package {
225 scripts: [
226 ("//lint".to_string(), "Run ESLint".to_string()),
227 ("lint".to_string(), "eslint .".to_string()),
228 ]
229 .into_iter()
230 .collect(),
231 ..Default::default()
232 };
233
234 let descriptions = extract_descriptions(&package);
235 assert_eq!(descriptions.get("lint"), Some(&"Run ESLint".to_string()));
236 }
237
238 #[test]
239 fn test_extract_descriptions_priority() {
240 use super::super::types::NtlConfig;
241
242 let package = Package {
244 scripts: [
245 ("//dev".to_string(), "Comment description".to_string()),
246 ("dev".to_string(), "vite".to_string()),
247 ]
248 .into_iter()
249 .collect(),
250 scripts_info: [("dev".to_string(), "Scripts-info description".to_string())]
251 .into_iter()
252 .collect(),
253 ntl: Some(NtlConfig {
254 descriptions: [("dev".to_string(), "NTL description".to_string())]
255 .into_iter()
256 .collect(),
257 }),
258 ..Default::default()
259 };
260
261 let descriptions = extract_descriptions(&package);
262 assert_eq!(
263 descriptions.get("dev"),
264 Some(&"Scripts-info description".to_string())
265 );
266 }
267
268 #[test]
269 fn test_extract_descriptions_ntl_over_comments() {
270 use super::super::types::NtlConfig;
271
272 let package = Package {
274 scripts: [
275 ("//build".to_string(), "Comment description".to_string()),
276 ("build".to_string(), "vite build".to_string()),
277 ]
278 .into_iter()
279 .collect(),
280 ntl: Some(NtlConfig {
281 descriptions: [("build".to_string(), "NTL description".to_string())]
282 .into_iter()
283 .collect(),
284 }),
285 ..Default::default()
286 };
287
288 let descriptions = extract_descriptions(&package);
289 assert_eq!(
290 descriptions.get("build"),
291 Some(&"NTL description".to_string())
292 );
293 }
294}