use std::borrow::Cow; use std::path::Path; use organic::types::LinkType; use organic::types::StandardProperties; use url::Url; use super::get_web_path; use super::macros::intermediate; use super::IntermediateContext; use super::IObject; use crate::context::RenderContext; use crate::error::CustomError; #[derive(Debug, Clone)] pub(crate) struct IRegularLink { pub(crate) raw_link: String, pub(crate) children: Vec, pub(crate) target: LinkTarget, pub(crate) post_blank: organic::types::PostBlank, } intermediate!( IRegularLink, &'orig organic::types::RegularLink<'parse>, original, intermediate_context, { let children = { let mut ret = Vec::new(); for obj in original.children.iter() { ret.push(IObject::new(intermediate_context.clone(), obj).await?); } ret }; let raw_link = original.get_raw_link(); let target = LinkTarget::from_string( intermediate_context.clone(), raw_link.clone().into_owned(), &original.link_type, )?; Ok(IRegularLink { raw_link: raw_link.into_owned(), children, target, post_blank: original.get_post_blank(), }) } ); #[derive(Debug, Clone, PartialEq)] pub(crate) enum LinkTarget { Raw(String), Post { post_id: Option, subpath: String, }, Target { target_id: String, }, Image { src: String, alt: String, }, } impl LinkTarget { pub(crate) fn from_string( intermediate_context: IntermediateContext<'_, '_>, input: String, link_type: &LinkType<'_>, ) -> Result { // If link type is file and the path ends in .svg then make it an image target if let LinkType::File = link_type && input.to_ascii_lowercase().ends_with(".svg") { let src = Self::get_image_src(&input)?; let alt = Self::get_image_alt(&input)?; return Ok(LinkTarget::Image { src, alt }); }; let parsed = Url::parse(&input); if let Err(url::ParseError::RelativeUrlWithoutBase) = parsed { let target_id = { let mut registry = intermediate_context.registry.lock().unwrap(); let target_id = registry.get_target(input).to_owned(); target_id }; return Ok(LinkTarget::Target { target_id }); } let parsed = parsed?; match parsed.scheme() { "post" => { let post_id = parsed.host_str().map(str::to_owned); let subpath = { let subpath = parsed.path(); if let Some(subpath) = subpath.strip_prefix('/') { subpath } else { subpath } }; Ok(LinkTarget::Post { post_id, subpath: subpath.to_owned(), }) } _ => Ok(LinkTarget::Raw(input.to_owned())), } } pub(crate) fn generate_final_target( &self, render_context: RenderContext<'_>, ) -> Result, CustomError> { match self { LinkTarget::Raw(raw_link) => Ok(Some(raw_link.clone())), LinkTarget::Post { post_id, subpath } => { let path = post_id .as_ref() .map(|post_id| { let path_to_post = render_context .config .get_relative_path_to_post(post_id) .join(subpath); get_web_path( render_context.config, render_context.output_root_directory, render_context.output_file, path_to_post, ) }) .map_or(Ok(None), |r| r.map(Some))?; Ok(path) } LinkTarget::Target { target_id } => Ok(Some(format!( "#{}{}", render_context .id_addition .map(|id_addition| format!("sec{}.", id_addition)) .unwrap_or_default(), target_id ))), LinkTarget::Image { src, .. } => { let path_to_file = render_context .dependency_manager .lock() .unwrap() .get_current_folder()? .join(src) .canonicalize()?; let input_root_directory = render_context.config.get_root_directory(); let relative_path_to_file = path_to_file.strip_prefix(input_root_directory)?; dbg!(input_root_directory); dbg!(&path_to_file); dbg!(relative_path_to_file); let web_path = get_web_path( render_context.config, render_context.output_root_directory, render_context.output_file, relative_path_to_file, )?; dbg!(&web_path); // TODO: Record interest in copying the file to output. Ok(Some(web_path)) } } } /// Get the value for the src attribute of the image. fn get_image_src(input: &str) -> Result { let input = if input.to_ascii_lowercase().starts_with("file:") { Cow::Borrowed(&input[5..]) } else { Cow::Borrowed(input) }; let path = Path::new(input.as_ref()); if input.to_ascii_lowercase().starts_with("/ssh:") { return Ok(format!("file:/{}", input)); } if path.is_absolute() { return Ok(format!("file://{}", input)); } return Ok(input.into_owned()); } /// Get file name from the last segment of an image path. fn get_image_alt(input: &str) -> Result { let input = if input.to_ascii_lowercase().starts_with("file:") { Cow::Borrowed(&input[5..]) } else { Cow::Borrowed(input) }; let path = Path::new(input.as_ref()); match path .components() .last() .ok_or("Images should have at least one component in their path.")? { std::path::Component::Prefix(_) => { // Prefix components only occur on windows panic!("Prefix components are not supporterd.") } std::path::Component::RootDir | std::path::Component::CurDir | std::path::Component::ParentDir => { return Err( "Final component of an image path should be a normal component.".into(), ); } std::path::Component::Normal(file_name) => Ok(file_name .to_str() .ok_or("Image link was not valid utf-8.")? .to_owned()), } } } #[cfg(test)] mod tests { use std::borrow::Cow; use std::sync::Arc; use std::sync::Mutex; use crate::intermediate::Registry; use super::*; #[test] fn link_target_raw() -> Result<(), CustomError> { let registry = Registry::new(); let registry = Arc::new(Mutex::new(registry)); let intermediate_context = IntermediateContext::new(registry)?; for (inp, typ) in [( "https://test.example/foo", LinkType::Protocol(Cow::from("https")), )] { assert_eq!( LinkTarget::from_string(intermediate_context.clone(), inp.to_owned(), &typ)?, LinkTarget::Raw(inp.to_owned()) ); } Ok(()) } #[test] fn link_target_image() -> Result<(), CustomError> { let registry = Registry::new(); let registry = Arc::new(Mutex::new(registry)); let intermediate_context = IntermediateContext::new(registry)?; for (inp, typ, expected_src, expected_alt) in [ ("file:image.svg", LinkType::File, "image.svg", "image.svg"), ( "file:/image.svg", LinkType::File, "file:///image.svg", "image.svg", ), ( "file:./image.svg", LinkType::File, "./image.svg", "image.svg", ), ( "/image.svg", LinkType::File, "file:///image.svg", "image.svg", ), ("./image.svg", LinkType::File, "./image.svg", "image.svg"), ("./image.SVG", LinkType::File, "./image.SVG", "image.SVG"), ( "./image and stuff.SVG", LinkType::File, "./image and stuff.SVG", "image and stuff.SVG", ), ( "/ssh:admin@test.example:important/file.svg", LinkType::File, "file://ssh:admin@test.example:important/file.svg", "file.svg", ), ( "file:/ssh:admin@test.example:important/file.svg", LinkType::File, "file://ssh:admin@test.example:important/file.svg", "file.svg", ), ( "file:/foo/bar/baz/image.svg", LinkType::File, "file:///foo/bar/baz/image.svg", "image.svg", ), ] { assert_eq!( LinkTarget::from_string(intermediate_context.clone(), inp.to_owned(), &typ)?, LinkTarget::Image { src: expected_src.to_owned(), alt: expected_alt.to_owned() } ); } Ok(()) } }