Add support for Image targets in the intermediate step.
Some checks are pending
clippy Build clippy has started
format Build format has succeeded
rust-test Build rust-test has succeeded

This commit is contained in:
Tom Alexander 2025-02-01 20:50:13 -05:00
parent 7c92b602bc
commit eb18185131
Signed by: talexander
GPG Key ID: D3A179C9A53C0EDE
2 changed files with 166 additions and 4 deletions

View File

@ -0,0 +1,22 @@
[[file:image.svg]]
[[file:/image.svg]]
[[file:./image.svg]]
[[/image.svg]]
[[./image.svg]]
# Check capitalization of extension
[[./image.SVG]]
# Check spaces in path
[[./image and stuff.SVG]]
[[/ssh:admin@test.example:important/file.svg]]
[[file:/ssh:admin@test.example:important/file.svg]]
# Check multiple parts in the path
[[file:/foo/bar/baz/image.svg]]

View File

@ -1,3 +1,7 @@
use std::borrow::Cow;
use std::path::Path;
use organic::types::LinkType;
use organic::types::StandardProperties;
use url::Url;
@ -31,8 +35,11 @@ intermediate!(
ret
};
let raw_link = original.get_raw_link();
let target =
LinkTarget::from_string(intermediate_context.clone(), raw_link.clone().into_owned())?;
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,
@ -52,13 +59,28 @@ pub(crate) enum LinkTarget {
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 = {
@ -121,12 +143,64 @@ impl LinkTarget {
.unwrap_or_default(),
target_id
))),
LinkTarget::Image { src, .. } => Ok(Some(src.clone())),
}
}
/// 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;
@ -139,12 +213,78 @@ mod tests {
let registry = Registry::new();
let registry = Arc::new(Mutex::new(registry));
let intermediate_context = IntermediateContext::new(registry)?;
for inp in ["https://test.example/foo"] {
for (inp, typ) in [(
"https://test.example/foo",
LinkType::Protocol(Cow::from("https")),
)] {
assert_eq!(
LinkTarget::from_string(intermediate_context.clone(), inp.to_owned())?,
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(())
}
}