diff --git a/ftd/fpm.ftd b/ftd/fpm.ftd index bbcc0106..d488f02c 100644 --- a/ftd/fpm.ftd +++ b/ftd/fpm.ftd @@ -1,6 +1,6 @@ -- record package-data: caption name: -boolean versioned: false +string versioned: false optional body about: optional string zip: optional string language: diff --git a/src/commands/build.rs b/src/commands/build.rs index fd5bf898..74f87a44 100644 --- a/src/commands/build.rs +++ b/src/commands/build.rs @@ -46,7 +46,7 @@ pub async fn build( ); } - if config.package.versioned { + if !config.package.versioned.eq("false") { fpm::version::build_version(config, file, base_url, ignore_failed, &asset_documents) .await?; } else { diff --git a/src/config.rs b/src/config.rs index 71d4ad6c..0e161dfe 100644 --- a/src/config.rs +++ b/src/config.rs @@ -1,3 +1,4 @@ +use crate::File; use std::convert::TryInto; /// `Config` struct keeps track of configuration parameters that is shared with the entire @@ -332,6 +333,8 @@ impl Config { let asset_documents = config.get_assets("/").await?; + let files = config.get_all_files(&config.package).await?; + config.sitemap = match package.translation_of.as_ref() { Some(translation) => translation, None => &package, @@ -339,8 +342,15 @@ impl Config { .sitemap .as_ref() .map_or(Ok(None), |v| { - fpm::sitemap::Sitemap::parse(v.as_str(), &package, &config, &asset_documents, "/") - .map(Some) + fpm::sitemap::Sitemap::parse( + v.as_str(), + &package, + &config, + &asset_documents, + "/", + &files, + ) + .map(Some) })?; Ok(config) @@ -367,6 +377,77 @@ impl Config { Ok(()) } + pub(crate) async fn get_all_files( + &self, + package: &fpm::Package, + ) -> fpm::Result> { + let mut all_files: std::collections::HashMap = Default::default(); + if package.versioned.eq("false") { + return Ok(all_files); + } + let base_versioned_documents = self.get_versions(package).await?; + for (version, document) in base_versioned_documents.iter() { + all_files.extend( + document + .iter() + .filter_map(|v| match v { + File::Ftd(d) | File::Markdown(d) => Some(d), + _ => None, + }) + .map(|v| { + let base = if let Some(ref base) = version.base { + format!("{}/", base) + } else { + "".to_string() + }; + let original = if version.original.contains("BASE_VERSION") { + "".to_string() + } else { + format!("{}/", version.original) + }; + + ( + format!("{}{}", base, v.id_to_path()) + .trim_matches('/') + .to_string(), + format!("{}{}{}", base, original, v.id), + ) + }), + ); + } + Ok(all_files) + } + + pub(crate) async fn get_based_versions( + &self, + package: &fpm::Package, + ) -> fpm::Result< + std::collections::HashMap>>, + > { + let versioned_documents = self.get_versions(package).await?; + let mut base_versioned_documents: std::collections::HashMap< + String, + std::collections::HashMap>, + > = Default::default(); + for (version, documents) in versioned_documents { + let base = if let Some(ref base) = version.base { + base.to_string() + } else { + "BASE".to_string() + }; + + if let Some(hashmap) = base_versioned_documents.get_mut(base.as_str()) { + hashmap.insert(version, documents); + } else { + base_versioned_documents.insert( + base, + std::array::IntoIter::new([(version, documents)]).collect(), + ); + } + } + Ok(base_versioned_documents) + } + pub(crate) async fn get_versions( &self, package: &fpm::Package, @@ -389,14 +470,20 @@ impl Config { if file.is_dir() { continue; } - let version = get_version(&file, &path)?; + let version = get_version(package.versioned.as_str(), &file, &path)?; let file = fpm::get_file( package.name.to_string(), &file, - &(if version.original.eq("BASE_VERSION") { - path.to_owned() - } else { - path.join(&version.original) + &({ + let path = match version.base { + Some(ref base) => path.join(base), + None => path.to_owned(), + }; + if version.original.contains("BASE_VERSION") { + path + } else { + path.join(&version.original) + } }), ) .await?; @@ -409,6 +496,7 @@ impl Config { return Ok(hash); fn get_version( + versioned: &str, x: &camino::Utf8PathBuf, path: &camino::Utf8PathBuf, ) -> fpm::Result { @@ -427,11 +515,8 @@ impl Config { }); } }; - if let Some((v, _)) = id.split_once('/') { - fpm::Version::parse(v) - } else { - Ok(fpm::Version::base()) - } + + fpm::Version::parse(id.as_str(), versioned) } } @@ -470,7 +555,7 @@ impl Config { id: &str, package: &fpm::Package, ) -> fpm::Result { - let file_name = fpm::Config::get_file_name(&self.root, id)?; + let file_name = fpm::Config::get_file_name(&self.root, id, &Default::default())?; return self .get_files(package) .await? @@ -481,7 +566,11 @@ impl Config { }); } - pub(crate) fn get_file_name(root: &camino::Utf8PathBuf, id: &str) -> fpm::Result { + pub(crate) fn get_file_name( + root: &camino::Utf8PathBuf, + id: &str, + files: &std::collections::HashMap, + ) -> fpm::Result { let mut id = id.to_string(); let mut add_packages = "".to_string(); if let Some(new_id) = id.strip_prefix("-/") { @@ -502,6 +591,11 @@ impl Config { if root.join(format!("{}README.md", add_packages)).exists() { return Ok(format!("{}README.md", add_packages)); } + if let Some(id) = files.get(id.trim_matches('/')) { + if root.join(id).exists() { + return Ok(id.to_string()); + } + } return Err(fpm::Error::UsageError { message: "File not found".to_string(), }); @@ -525,6 +619,11 @@ impl Config { { return Ok(format!("{}{}/README.md", add_packages, id)); } + if let Some(id) = files.get(id.as_str()) { + if root.join(id).exists() { + return Ok(id.to_string()); + } + } Err(fpm::Error::UsageError { message: "File not found".to_string(), }) @@ -594,7 +693,7 @@ pub(crate) fn find_root_for_file( #[derive(serde::Deserialize, Debug, Clone)] pub(crate) struct PackageTemp { pub name: String, - pub versioned: bool, + pub versioned: String, #[serde(rename = "translation-of")] pub translation_of: Option, #[serde(rename = "translation")] @@ -647,7 +746,7 @@ impl PackageTemp { pub struct Package { pub name: String, /// The `versioned` stores the boolean value storing of the fpm package is versioned or not - pub versioned: bool, + pub versioned: String, pub translation_of: Box>, pub translations: Vec, pub language: Option, @@ -680,7 +779,7 @@ impl Package { pub fn new(name: &str) -> fpm::Package { fpm::Package { name: name.to_string(), - versioned: false, + versioned: "false".to_string(), translation_of: Box::new(None), translations: vec![], language: None, diff --git a/src/library/get_data.rs b/src/library/get_data.rs index eb7d77ed..e72a403b 100644 --- a/src/library/get_data.rs +++ b/src/library/get_data.rs @@ -27,7 +27,7 @@ pub fn processor( } if let Some(ref sitemap) = config.sitemap { - let doc_id = config + let mut doc_id = config .current_document .clone() .map(|v| fpm::utils::id_to_path(v.as_str())) @@ -39,6 +39,33 @@ pub fn processor( .trim() .replace(std::path::MAIN_SEPARATOR, "/"); + if !config.package.versioned.eq("false") { + let versions = futures::executor::block_on(config.get_versions(&config.package)) + .map_err(|e| ftd::p1::Error::ParseError { + message: format!("Cant find versions: {:?}", e), + doc_id: doc.name.to_string(), + line_number: section.line_number, + })?; + for version in versions.keys() { + let base = if let Some(ref base) = version.base { + if base.is_empty() { + base.to_string() + } else { + format!("{}/", base) + } + } else { + "".to_string() + }; + if let Some(id) = doc_id + .trim_matches('/') + .strip_prefix(format!("{}{}", base, version.original).as_str()) + { + doc_id = format!("{}{}", base, id.trim_matches('/')); + break; + } + } + } + if let Some(extra_data) = sitemap.get_extra_data_by_id(doc_id.as_str()) { if let Some(data) = extra_data.get(name.as_str()) { let kind = doc.get_variable_kind(section)?; diff --git a/src/library/get_version_data.rs b/src/library/get_version_data.rs index 342ffd17..45b20668 100644 --- a/src/library/get_version_data.rs +++ b/src/library/get_version_data.rs @@ -16,66 +16,94 @@ pub fn processor( } })?; - let version = if let Some((v, _)) = document_id.split_once('/') { - fpm::Version::parse(v).map_err(|e| ftd::p1::Error::ParseError { - message: format!("{:?}", e), - doc_id: doc.name.to_string(), - line_number: section.line_number, - })? - } else { - fpm::Version::base() - }; - - let doc_id = if let Some(doc) = document_id.split_once('/').map(|(_, v)| v) { - doc - } else { - document_id - } - .to_string(); + let version = fpm::Version::parse(document_id, config.package.versioned.as_str()).unwrap_or({ + let base = fpm::Version::get_base(document_id, config.package.versioned.as_str()).map_err( + |e| ftd::p1::Error::ParseError { + message: format!("{:?}", e), + doc_id: doc.name.to_string(), + line_number: section.line_number, + }, + )?; + fpm::Version::base_(base) + }); - let base_url = base_url - .trim_end_matches('/') - .trim_start_matches('/') - .to_string(); - let base_url = if !base_url.is_empty() { - format!("/{base_url}/") - } else { - String::from("/") - }; + let url = config + .current_document + .clone() + .map(|v| fpm::utils::id_to_path(v.as_str())) + .unwrap_or_else(|| { + doc.name + .to_string() + .replace(config.package.name.as_str(), "") + }) + .trim() + .replace(std::path::MAIN_SEPARATOR, "/"); - let url = match doc_id.as_str().rsplit_once('.') { - Some(("index", "ftd")) => base_url, - Some((file_path, "ftd")) | Some((file_path, "md")) => { - format!("{base_url}{file_path}/") + let doc_id = { + let mut doc_ids = vec![]; + let mut doc_id = document_id.trim_start_matches('/').to_string(); + if let Some(id) = doc_id.strip_prefix(base_url.trim().trim_matches('/')) { + doc_id = id.to_string(); } - Some(_) | None => { - // Unknown file found, create URL - format!("{base_url}{file_path}/", file_path = doc_id.as_str()) + if let Some((id, _)) = doc_id.split_once("-/") { + if id.eq("/") { + doc_ids.push("index.ftd".to_string()); + } else { + let id = id.trim_matches('/').to_string(); + doc_ids.push(format!("{}.ftd", id)); + doc_ids.push(format!("{}/index.ftd", id)); + } + } else { + doc_ids.push(doc_id.trim_start_matches('/').to_string()); } + doc_ids }; let mut found = false; - if let Some(doc) = versions.get(&fpm::Version::base()) { - if doc.iter().map(|v| v.get_id()).any(|x| x == doc_id) { + if let Some(doc) = versions.get(&fpm::Version::base_(version.base.to_owned())) { + if doc.iter().map(|v| v.get_id()).any(|x| doc_id.contains(&x)) { found = true; } } let mut version_toc = vec![]; - for key in versions.keys().sorted() { + for key in versions + .iter() + .filter(|(v, _)| v.base.eq(&version.base)) + .map(|(v, _)| v) + .sorted() + { if key.eq(&fpm::Version::base()) { continue; } let doc = versions[key].to_owned(); if !found { - if !doc.iter().map(|v| v.get_id()).any(|x| x == doc_id) { + if !doc.iter().map(|v| v.get_id()).any(|x| doc_id.contains(&x)) { continue; } found = true; } + + let url = { + let mut url = url.trim_start_matches('/').to_string(); + if let Some(id) = url.strip_prefix(base_url.trim().trim_matches('/')) { + url = id.trim_start_matches('/').to_string(); + } + url + }; + version_toc.push(fpm::library::toc::TocItem { id: None, title: Some(key.original.to_string()), - url: Some(format!("{}{}", key.original, url)), + url: Some(format!( + "{}{}/{}", + if let Some(ref base) = key.base { + format!("/{}/", base) + } else { + "/".to_string() + }, + key.original, + url + )), number: vec![], is_heading: version.eq(key), is_disabled: false, diff --git a/src/library/sitemap.rs b/src/library/sitemap.rs index faff9f15..5b0f0377 100644 --- a/src/library/sitemap.rs +++ b/src/library/sitemap.rs @@ -4,7 +4,7 @@ pub fn processor( config: &fpm::Config, ) -> ftd::p1::Result { if let Some(ref sitemap) = config.sitemap { - let doc_id = config + let mut doc_id = config .current_document .clone() .map(|v| fpm::utils::id_to_path(v.as_str())) @@ -16,6 +16,33 @@ pub fn processor( .trim() .replace(std::path::MAIN_SEPARATOR, "/"); + if !config.package.versioned.eq("false") { + let versions = futures::executor::block_on(config.get_versions(&config.package)) + .map_err(|e| ftd::p1::Error::ParseError { + message: format!("Cant find versions: {:?}", e), + doc_id: doc.name.to_string(), + line_number: section.line_number, + })?; + for version in versions.keys() { + let base = if let Some(ref base) = version.base { + if base.is_empty() { + base.to_string() + } else { + format!("{}/", base) + } + } else { + "".to_string() + }; + if let Some(id) = doc_id + .trim_matches('/') + .strip_prefix(format!("{}{}", base, version.original).as_str()) + { + doc_id = format!("{}{}", base, id.trim_matches('/')); + break; + } + } + } + if let Some(sitemap) = sitemap.get_sitemap_by_id(doc_id.as_str()) { return doc.from_json(&sitemap, section); } diff --git a/src/sitemap.rs b/src/sitemap.rs index 22676e43..76537f69 100644 --- a/src/sitemap.rs +++ b/src/sitemap.rs @@ -498,6 +498,7 @@ impl Sitemap { config: &fpm::Config, asset_documents: &std::collections::HashMap, base_url: &str, + files: &std::collections::HashMap, ) -> Result { let mut parser = SitemapParser { state: ParsingState::WaitingForSection, @@ -516,7 +517,7 @@ impl Sitemap { }; sitemap - .resolve(package, config, asset_documents, base_url) + .resolve(package, config, asset_documents, base_url, files) .map_err(|e| ParseError::InvalidTOCItem { doc_id: package.name.to_string(), message: e.to_string(), @@ -532,6 +533,7 @@ impl Sitemap { config: &fpm::Config, asset_documents: &std::collections::HashMap, base_url: &str, + files: &std::collections::HashMap, ) -> fpm::Result<()> { let package_root = config.get_root_for_package(package); let current_package_root = config.root.to_owned(); @@ -543,6 +545,7 @@ impl Sitemap { asset_documents, base_url, config, + files, )?; } return Ok(()); @@ -554,38 +557,47 @@ impl Sitemap { asset_documents: &std::collections::HashMap, base_url: &str, config: &fpm::Config, + files: &std::collections::HashMap, ) -> fpm::Result<()> { - let (file_location, translation_file_location) = - if fpm::utils::url_regex().find(section.id.as_str()).is_some() { - (None, None) - } else { - match fpm::Config::get_file_name(current_package_root, section.id.as_str()) { - Ok(name) => { - if current_package_root.eq(package_root) { - (Some(current_package_root.join(name)), None) - } else { - ( - Some(package_root.join(name.as_str())), - Some(current_package_root.join(name)), - ) - } + let (file_location, translation_file_location) = if fpm::utils::url_regex() + .find(section.id.as_str()) + .is_some() + { + (None, None) + } else { + match fpm::Config::get_file_name(current_package_root, section.id.as_str(), files) { + Ok(name) => { + if current_package_root.eq(package_root) { + (Some(current_package_root.join(name)), None) + } else { + ( + Some(package_root.join(name.as_str())), + Some(current_package_root.join(name)), + ) } - Err(_) => ( - Some( - package_root.join( - fpm::Config::get_file_name(package_root, section.id.as_str()) - .map_err(|e| fpm::Error::UsageError { + } + Err(_) => ( + Some( + package_root.join( + fpm::Config::get_file_name( + package_root, + section.id.as_str(), + files, + ) + .map_err(|e| { + fpm::Error::UsageError { message: format!( "`{}` not found, fix fpm.sitemap in FPM.ftd. Error: {:?}", section.id, e ), - })?, - ), + } + })?, ), - None, ), - } - }; + None, + ), + } + }; section.file_location = file_location; section.translation_file_location = translation_file_location; @@ -597,6 +609,7 @@ impl Sitemap { asset_documents, base_url, config, + files, )?; } Ok(()) @@ -609,6 +622,7 @@ impl Sitemap { asset_documents: &std::collections::HashMap, base_url: &str, config: &fpm::Config, + files: &std::collections::HashMap, ) -> fpm::Result<()> { if let Some(ref id) = subsection.id { let (file_location, translation_file_location) = if fpm::utils::url_regex() @@ -617,7 +631,7 @@ impl Sitemap { { (None, None) } else { - match fpm::Config::get_file_name(current_package_root, id.as_str()) { + match fpm::Config::get_file_name(current_package_root, id.as_str(), files) { Ok(name) => { if current_package_root.eq(package_root) { (Some(current_package_root.join(name)), None) @@ -630,7 +644,7 @@ impl Sitemap { } Err(_) => ( Some(package_root.join( - fpm::Config::get_file_name(package_root, id.as_str()).map_err( + fpm::Config::get_file_name(package_root, id.as_str(), files).map_err( |e| fpm::Error::UsageError { message: format!( "`{}` not found, fix fpm.sitemap in FPM.ftd. Error: {:?}", @@ -655,6 +669,7 @@ impl Sitemap { asset_documents, base_url, config, + files, )?; } Ok(()) @@ -667,13 +682,14 @@ impl Sitemap { asset_documents: &std::collections::HashMap, base_url: &str, config: &fpm::Config, + files: &std::collections::HashMap, ) -> fpm::Result<()> { let (file_location, translation_file_location) = if toc.id.trim().is_empty() || fpm::utils::url_regex().find(toc.id.as_str()).is_some() { (None, None) } else { - match fpm::Config::get_file_name(current_package_root, toc.id.as_str()) { + match fpm::Config::get_file_name(current_package_root, toc.id.as_str(), files) { Ok(name) => { if current_package_root.eq(package_root) { (Some(current_package_root.join(name)), None) @@ -685,16 +701,17 @@ impl Sitemap { } } Err(_) => ( - Some(package_root.join( - fpm::Config::get_file_name(package_root, toc.id.as_str()).map_err( - |e| fpm::Error::UsageError { - message: format!( + Some( + package_root.join( + fpm::Config::get_file_name(package_root, toc.id.as_str(), files) + .map_err(|e| fpm::Error::UsageError { + message: format!( "`{}` not found, fix fpm.sitemap in FPM.ftd. Error: {:?}", toc.id, e ), - }, - )?, - )), + })?, + ), + ), None, ), } @@ -710,6 +727,7 @@ impl Sitemap { asset_documents, base_url, config, + files, )?; } Ok(()) @@ -793,18 +811,57 @@ impl Sitemap { } } - fn ids_matches(id1: &str, id2: &str) -> bool { - return strip_id(id1).eq(&strip_id(id2)); + pub(crate) fn get_all_canonical_ids(&self) -> std::collections::HashMap { + let mut locations: std::collections::HashMap = Default::default(); + for section in self.sections.iter() { + if section.file_location.is_some() { + if let Some((id, extension)) = get_id(section.id.as_str()) { + locations.insert(id, extension); + } + } + for subsection in section.subsections.iter() { + if subsection.visible { + if let Some(ref subsection_id) = subsection.id { + if let Some((id, extension)) = get_id(subsection_id.as_str()) { + locations.insert(id, extension); + } + } + } + for toc in subsection.toc.iter() { + if toc.file_location.is_some() { + if let Some((id, extension)) = get_id(toc.id.as_str()) { + locations.insert(id, extension); + } + } + locations.extend(get_toc_canonical_ids(toc)); + } + } + } + return locations; + + fn get_id(id: &str) -> Option<(String, String)> { + if id.starts_with("-/") { + return None; + } + if let Some((id1, id2)) = id.split_once("-/") { + return Some((id1.trim().to_string(), id2.trim().to_string())); + } + None + } - fn strip_id(id: &str) -> String { - let id = id - .trim() - .replace("/index.html", "/") - .replace("index.html", "/"); - if id.eq("/") { - return id; + fn get_toc_canonical_ids( + toc: &fpm::sitemap::TocItem, + ) -> std::collections::HashMap { + let mut locations: std::collections::HashMap = Default::default(); + for child in toc.children.iter() { + if child.file_location.is_some() { + if let Some((id, extension)) = get_id(child.id.as_str()) { + locations.insert(id, extension); + } + } + locations.extend(get_toc_canonical_ids(child)); } - id.trim_matches('/').to_string() + locations } } @@ -819,7 +876,7 @@ impl Sitemap { for (idx, section) in self.sections.iter().enumerate() { index = idx; - if fpm::sitemap::Sitemap::ids_matches(section.id.as_str(), id) { + if fpm::utils::ids_matches(section.id.as_str(), id) { subsections = section .subsections .iter() @@ -827,7 +884,7 @@ impl Sitemap { .map(|v| { let active = v.id.as_ref() - .map(|v| fpm::sitemap::Sitemap::ids_matches(v, id)) + .map(|v| fpm::utils::ids_matches(v, id)) .unwrap_or(false); let toc = TocItemCompat::new(v.id.clone(), v.title.clone(), active, active); if active { @@ -846,7 +903,7 @@ impl Sitemap { .iter() .find_or_first(|v| { v.id.as_ref() - .map(|v| fpm::sitemap::Sitemap::ids_matches(v, id)) + .map(|v| fpm::utils::ids_matches(v, id)) .unwrap_or(false) }) .or_else(|| section.subsections.first()) @@ -932,7 +989,7 @@ impl Sitemap { && subsection .id .as_ref() - .map(|v| fpm::sitemap::Sitemap::ids_matches(v, id)) + .map(|v| fpm::utils::ids_matches(v, id)) .unwrap_or(false) { let (toc_list, current_toc) = get_all_toc(subsection.toc.as_slice(), id); @@ -1020,7 +1077,7 @@ impl Sitemap { let mut current_toc = { let (is_open, children) = get_toc_by_id_(id, toc_item.children.as_slice(), current_page); - let is_active = fpm::sitemap::Sitemap::ids_matches(toc_item.id.as_str(), id); + let is_active = fpm::utils::ids_matches(toc_item.id.as_str(), id); let mut current_toc = TocItemCompat::new( Some(get_url(toc_item.id.as_str()).to_string()), toc_item.title.clone(), @@ -1037,7 +1094,7 @@ impl Sitemap { toc_list.push(current_toc.clone()); if current_page.is_none() { - found_here = fpm::sitemap::Sitemap::ids_matches(toc_item.id.as_str(), id); + found_here = fpm::utils::ids_matches(toc_item.id.as_str(), id); if found_here { if let Some(ref title) = toc_item.nav_title { current_toc.title = Some(title.to_string()); @@ -1066,7 +1123,7 @@ impl Sitemap { id: &str, ) -> Option> { for section in self.sections.iter() { - if fpm::sitemap::Sitemap::ids_matches(section.id.as_str(), id) { + if fpm::utils::ids_matches(section.id.as_str(), id) { return Some(section.extra_data.to_owned()); } if let Some(data) = get_extra_data_from_subsections(id, section.subsections.as_slice()) @@ -1084,7 +1141,7 @@ impl Sitemap { ) -> Option> { for subsection in subsections { if subsection.visible - && fpm::sitemap::Sitemap::ids_matches( + && fpm::utils::ids_matches( subsection.id.as_ref().unwrap_or(&"".to_string()), id, ) @@ -1105,7 +1162,7 @@ impl Sitemap { toc: &[TocItem], ) -> Option> { for toc_item in toc { - if fpm::sitemap::Sitemap::ids_matches(toc_item.id.as_str(), id) { + if fpm::utils::ids_matches(toc_item.id.as_str(), id) { return Some(toc_item.extra_data.to_owned()); } if let Some(data) = get_extra_data_from_toc(id, toc_item.children.as_slice()) { diff --git a/src/utils.rs b/src/utils.rs index e5779bfe..3a665d5e 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -268,3 +268,18 @@ pub(crate) fn url_regex() -> regex::Regex { r#"((([A-Za-z]{3,9}:(?://)?)(?:[-;:&=\+\$,\w]+@)?[A-Za-z0-9.-]+|(?:www.|[-;:&=\+\$,\w]+@)[A-Za-z0-9.-]+)((?:/[\+~%/.\w_]*)?\??(?:[-\+=&;%@.\w_]*)\#?(?:[\w]*))?)"# ).unwrap() } + +pub(crate) fn ids_matches(id1: &str, id2: &str) -> bool { + return strip_id(id1).eq(&strip_id(id2)); + + fn strip_id(id: &str) -> String { + let id = id + .trim() + .replace("/index.html", "/") + .replace("index.html", "/"); + if id.eq("/") { + return id; + } + id.trim_matches('/').to_string() + } +} diff --git a/src/version.rs b/src/version.rs index 477dc69f..6e810b76 100644 --- a/src/version.rs +++ b/src/version.rs @@ -7,19 +7,71 @@ pub struct Version { pub major: u64, pub minor: Option, pub original: String, + pub base: Option, } impl Version { pub(crate) fn base() -> fpm::Version { + fpm::Version::base_(None) + } + + pub(crate) fn base_(base: Option) -> fpm::Version { fpm::Version { major: 0, minor: None, original: "BASE_VERSION".to_string(), + base, } } - pub(crate) fn parse(s: &str) -> fpm::Result { - let v = s.strip_prefix(&['v', 'V']).unwrap_or(s); + pub(crate) fn get_base(s: &str, versioned: &str) -> fpm::Result> { + let get_all_version_base = if versioned.eq("true") { + vec![] + } else { + versioned + .split(',') + .filter(|v| !v.is_empty()) + .map(|v| v.trim()) + .collect() + }; + + let mut base = None; + let mut s = s.to_string(); + for version_base in get_all_version_base.iter() { + if let Some(id) = s.trim_matches('/').strip_prefix(version_base) { + s = id.trim_matches('/').to_string(); + base = Some(version_base.to_string()); + break; + } + } + + if !get_all_version_base.is_empty() && base.is_none() && !s.eq("FPM.ftd") { + return Err(fpm::Error::UsageError { + message: format!( + "{} is not part of any versioned directory: Available versioned directories: {:?}", + s, get_all_version_base + ), + }); + } + Ok(base) + } + + pub(crate) fn parse(s: &str, versioned: &str) -> fpm::Result { + let base = fpm::version::Version::get_base(s, versioned)?; + let mut s = s.to_string(); + if let Some(ref base) = base { + if let Some(id) = s.trim_matches('/').strip_prefix(base) { + s = id.trim_matches('/').to_string(); + } + } + + if let Some((v, _)) = s.split_once('/') { + s = v.to_string(); + } else { + return Ok(fpm::Version::base_(base)); + } + + let v = s.strip_prefix(&['v', 'V']).unwrap_or_else(|| s.as_str()); let mut minor = None; let major = if let Some((major, minor_)) = v.split_once('.') { if minor_.contains('.') { @@ -43,6 +95,7 @@ impl Version { major, minor, original: s.to_string(), + base, }) } } @@ -65,98 +118,147 @@ impl Ord for Version { } pub(crate) async fn build_version( - config: &fpm::Config, + config: &mut fpm::Config, _file: Option<&str>, base_url: &str, skip_failed: bool, asset_documents: &std::collections::HashMap, ) -> fpm::Result<()> { - let versioned_documents = config.get_versions(&config.package).await?; - let mut documents = std::collections::BTreeMap::new(); - for key in versioned_documents.keys().sorted() { - let doc = versioned_documents[key].to_owned(); - documents.extend( - doc.iter() - .map(|v| (v.get_id(), (key.original.to_string(), v.to_owned()))), - ); - if key.eq(&fpm::Version::base()) { - continue; - } - for (version, doc) in documents.values() { - let mut doc = doc.clone(); - let id = doc.get_id(); - if id.eq("FPM.ftd") { + let mut all_canonical = config + .sitemap + .as_ref() + .map(|v| v.get_all_canonical_ids()) + .unwrap_or_default(); + + let base_versioned_documents = config.get_based_versions(&config.package).await?; + + for (base, versioned_documents) in base_versioned_documents.iter() { + let base = match base.as_ref() { + "BASE" => "".to_string(), + v => format!("{}/", v), + }; + + let mut documents = std::collections::BTreeMap::new(); + for key in versioned_documents.keys().sorted() { + let doc = { + let mut doc = versioned_documents[key].to_owned(); + let mut variants = vec![]; + for doc in doc.iter() { + let id = doc.get_id(); + if id.eq("FPM.ftd") { + continue; + } + let path = fpm::utils::id_to_path(id.as_str()) + .trim_start_matches('/') + .to_string(); + let find_id = format!("{}{}", base, path); + if let Some(variant) = all_canonical.remove(find_id.as_str()) { + let variant = if variant.ends_with('/') { + variant + } else { + format!("{}/", variant) + }; + let mut doc = doc.clone(); + let extension = if matches!(doc, fpm::File::Markdown(_)) { + "index.md".to_string() + } else { + "index.ftd".to_string() + }; + + doc.set_id(format!("{}-/{}{}", path, variant, extension).as_str()); + variants.push(doc); + } + } + doc.extend(variants); + doc + }; + documents.extend( + doc.iter() + .map(|v| (v.get_id(), (key.original.to_string(), v.to_owned()))), + ); + if key.original.eq(&fpm::Version::base().original) { continue; } - let new_id = format!("{}/{}", key.original, id); - if !key.original.eq(version) && !fpm::Version::base().original.eq(version) { - if let fpm::File::Ftd(_) = doc { - let original_id = format!("{}/{}", version, id); - let original_file_rel_path = if original_id.contains("index.ftd") { - original_id.replace("index.ftd", "index.html") - } else { - original_id.replace( - ".ftd", - format!("{}index.html", std::path::MAIN_SEPARATOR).as_str(), - ) - }; - let original_file_path = - config.root.join(".build").join(original_file_rel_path); - let file_rel_path = if new_id.contains("index.ftd") { - new_id.replace("index.ftd", "index.html") - } else { - new_id.replace( - ".ftd", - format!("{}index.html", std::path::MAIN_SEPARATOR).as_str(), - ) - }; - let new_file_path = config.root.join(".build").join(file_rel_path); - let original_content = std::fs::read_to_string(&original_file_path)?; - std::fs::create_dir_all(&new_file_path.as_str().replace("index.html", ""))?; - let mut f = std::fs::File::create(&new_file_path)?; - let from_pattern = format!("", base_url, version); - let to_pattern = format!("", base_url, key.original); - f.write_all( - original_content - .replace(from_pattern.as_str(), to_pattern.as_str()) - .as_bytes(), - )?; + for (version, doc) in documents.values() { + let mut doc = doc.clone(); + let id = doc.get_id(); + if id.eq("FPM.ftd") { continue; } + + let new_id = format!("{}{}/{}", base, key.original, id); + if !key.original.eq(version) && !fpm::Version::base().original.eq(version) { + if let fpm::File::Ftd(_) = doc { + let original_id = format!("{}{}/{}", base, version, id); + let original_file_rel_path = if original_id.contains("index.ftd") { + original_id.replace("index.ftd", "index.html") + } else { + original_id.replace( + ".ftd", + format!("{}index.html", std::path::MAIN_SEPARATOR).as_str(), + ) + }; + let original_file_path = + config.root.join(".build").join(original_file_rel_path); + let file_rel_path = if new_id.contains("index.ftd") { + new_id.replace("index.ftd", "index.html") + } else { + new_id.replace( + ".ftd", + format!("{}index.html", std::path::MAIN_SEPARATOR).as_str(), + ) + }; + let new_file_path = config.root.join(".build").join(file_rel_path); + let original_content = std::fs::read_to_string(&original_file_path)?; + std::fs::create_dir_all(&new_file_path.as_str().replace("index.html", ""))?; + let mut f = std::fs::File::create(&new_file_path)?; + let from_pattern = format!("", base_url, version); + let to_pattern = format!("", base_url, key.original); + f.write_all( + original_content + .replace(from_pattern.as_str(), to_pattern.as_str()) + .as_bytes(), + )?; + continue; + } + } + doc.set_id(new_id.as_str()); + config.current_document = Some(new_id); + fpm::process_file( + config, + &config.package, + &doc, + None, + None, + Default::default(), + format!("{}{}{}/", base_url, base, key.original).as_str(), + skip_failed, + asset_documents, + Some(id), + false, + ) + .await?; } - doc.set_id(new_id.as_str()); + } + for (_, doc) in documents.values_mut() { + let id = format!("{}{}", base, doc.get_id()); + doc.set_id(id.as_str()); + config.current_document = Some(id); fpm::process_file( config, &config.package, - &doc, + doc, None, None, Default::default(), - format!("{}{}/", base_url, key.original).as_str(), + format!("{}{}", base_url, base).as_str(), skip_failed, asset_documents, - Some(id), + None, false, ) .await?; } } - - for (_, doc) in documents.values() { - fpm::process_file( - config, - &config.package, - doc, - None, - None, - Default::default(), - base_url, - skip_failed, - asset_documents, - None, - false, - ) - .await?; - } Ok(()) }