features_cli/
readme_parser.rs

1use anyhow::{Context, Result};
2use std::collections::HashMap;
3use std::fs;
4use std::path::Path;
5
6fn read_readme_content(content: &str) -> String {
7    let mut found_first_title = false;
8    let mut lines_after_title: Vec<&str> = Vec::new();
9
10    for line in content.lines() {
11        let trimmed = line.trim();
12
13        // Check if we found the first title (starting with #)
14        if !found_first_title && trimmed.starts_with('#') {
15            found_first_title = true;
16            continue;
17        }
18
19        // Collect all lines after the first title
20        if found_first_title {
21            lines_after_title.push(line);
22        }
23    }
24
25    // Join all lines after the first title
26    lines_after_title.join("\n").trim().to_string()
27}
28
29/// Reads README information from README.md or README.mdx files
30/// Returns (owner, description, metadata) tuple
31pub fn read_readme_info(
32    readme_path: &Path,
33) -> Result<(String, String, HashMap<String, serde_json::Value>)> {
34    if !readme_path.exists() {
35        return Ok(("Unknown".to_string(), "".to_string(), HashMap::new()));
36    }
37
38    let content = fs::read_to_string(readme_path)
39        .with_context(|| format!("could not read README file at `{}`", readme_path.display()))?;
40
41    let mut owner = "Unknown".to_string();
42    let mut description = "".to_string();
43    let mut meta: HashMap<String, serde_json::Value> = HashMap::new();
44
45    // Check if content starts with YAML front matter (---)
46    if let Some(stripped) = content.strip_prefix("---\n") {
47        if let Some(end_pos) = stripped.find("\n---\n") {
48            let yaml_content = &stripped[..end_pos];
49            let markdown_content = stripped[end_pos + 5..].to_string();
50
51            // Parse YAML front matter
52            if let Ok(yaml_value) = serde_yaml::from_str::<serde_yaml::Value>(yaml_content)
53                && let Some(mapping) = yaml_value.as_mapping()
54            {
55                for (key, value) in mapping {
56                    if let Some(key_str) = key.as_str() {
57                        if key_str == "owner" {
58                            if let Some(owner_value) = value.as_str() {
59                                owner = owner_value.to_string();
60                            }
61                        } else {
62                            // Convert YAML value to JSON value for meta
63                            if let Ok(json_value) = serde_json::to_value(value) {
64                                meta.insert(key_str.to_string(), json_value);
65                            }
66                        }
67                    }
68                }
69            }
70
71            description = read_readme_content(&markdown_content)
72        }
73    } else {
74        description = read_readme_content(&content)
75    }
76
77    Ok((owner, description, meta))
78}