313 lines
10 KiB
Rust
313 lines
10 KiB
Rust
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<IObject>,
|
|
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<String>,
|
|
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<LinkTarget, CustomError> {
|
|
// 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<Option<String>, 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<String, CustomError> {
|
|
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<String, CustomError> {
|
|
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(())
|
|
}
|
|
}
|