use std::fs; use std::path::Path; use deser_hjson::from_str; use serde::de::DeserializeOwned; use serde_json::Value; use crate::config::{ CollectionConfig, PageConfig, SiteConfig, }; use crate::error::{Result, WebBuilderError}; /// Parse a hjson file into a struct /// /// # Arguments /// /// * `path` - Path to the hjson file /// /// # Returns /// /// The parsed struct or an error pub fn parse_hjson(path: P) -> Result where T: DeserializeOwned, P: AsRef, { let path = path.as_ref(); // Check if the file exists if !path.exists() { return Err(WebBuilderError::MissingFile(path.to_path_buf())); } // Read the file let content = fs::read_to_string(path).map_err(|e| WebBuilderError::IoError(e))?; // Parse the hjson from_str(&content).map_err(|e| WebBuilderError::HjsonError(format!("Error parsing {:?}: {}", path, e))) } /// Parse site configuration from a directory /// /// # Arguments /// /// * `path` - Path to the directory containing hjson configuration files /// /// # Returns /// /// The parsed site configuration or an error pub fn parse_site_config>(path: P) -> Result { let path = path.as_ref(); // Check if the directory exists if !path.exists() { return Err(WebBuilderError::MissingDirectory(path.to_path_buf())); } // Check if the directory is a directory if !path.is_dir() { return Err(WebBuilderError::InvalidConfiguration(format!( "{:?} is not a directory", path ))); } // Create a basic site configuration let mut site_config = SiteConfig { name: "default".to_string(), title: "".to_string(), description: None, keywords: None, url: None, favicon: None, header: None, footer: None, collections: Vec::new(), pages: Vec::new(), base_path: path.to_path_buf(), }; // Parse main.hjson let main_path = path.join("main.hjson"); if main_path.exists() { let main_config: Value = parse_hjson(main_path)?; // Extract values from main.hjson if let Some(name) = main_config.get("name").and_then(|v| v.as_str()) { site_config.name = name.to_string(); } if let Some(title) = main_config.get("title").and_then(|v| v.as_str()) { site_config.title = title.to_string(); } if let Some(description) = main_config.get("description").and_then(|v| v.as_str()) { site_config.description = Some(description.to_string()); } if let Some(url) = main_config.get("url").and_then(|v| v.as_str()) { site_config.url = Some(url.to_string()); } if let Some(favicon) = main_config.get("favicon").and_then(|v| v.as_str()) { site_config.favicon = Some(favicon.to_string()); } if let Some(keywords) = main_config.get("keywords").and_then(|v| v.as_array()) { let keywords_vec: Vec = keywords .iter() .filter_map(|k| k.as_str().map(|s| s.to_string())) .collect(); if !keywords_vec.is_empty() { site_config.keywords = Some(keywords_vec); } } } // Parse header.hjson let header_path = path.join("header.hjson"); if header_path.exists() { site_config.header = Some(parse_hjson(header_path)?); } // Parse footer.hjson let footer_path = path.join("footer.hjson"); if footer_path.exists() { site_config.footer = Some(parse_hjson(footer_path)?); } // Parse collection.hjson let collection_path = path.join("collection.hjson"); if collection_path.exists() { let collection_array: Vec = parse_hjson(collection_path)?; // Process each collection for mut collection in collection_array { // Convert web interface URL to Git URL if needed if let Some(url) = &collection.url { if url.contains("/src/branch/") { // This is a web interface URL, convert it to a Git URL let parts: Vec<&str> = url.split("/src/branch/").collect(); if parts.len() == 2 { collection.url = Some(format!("{}.git", parts[0])); } } } site_config.collections.push(collection); } } // Parse pages directory let pages_path = path.join("pages"); if pages_path.exists() && pages_path.is_dir() { for entry in fs::read_dir(pages_path)? { let entry = entry?; let entry_path = entry.path(); if entry_path.is_file() && entry_path.extension().map_or(false, |ext| ext == "hjson") { let pages_array: Vec = parse_hjson(&entry_path)?; site_config.pages.extend(pages_array); } } } Ok(site_config) }