Flag absolute anchors

This commit is contained in:
Juno Takano 2026-06-03 21:23:40 -03:00
commit 88fdd3084e
6 changed files with 100 additions and 26 deletions

View file

@ -396,7 +396,7 @@ impl Graph {
);
} else {
if let Some(destination) = anchor.destination()
&& !anchor.external()
&& !anchor.absolute()
{
let trimmed_destination = destination
.trim_start_matches("/node/")

View file

@ -34,6 +34,9 @@ pub fn parse(
log!(VERBOSE, "End: Next lexeme is a pipe");
buffer.text.push_str(&lexeme.text());
candidate.set_text(&buffer.text.clone());
if buffer.text.starts_with('/') {
candidate.set_absolute(true);
}
} else {
log!(
VERBOSE,
@ -84,6 +87,13 @@ pub fn parse(
"State: Found a pipe, but no boundary: destination follows"
);
candidate.set_balanced(true);
if lexeme.match_next_first_char('/') {
log!(
VERBOSE,
"State: Destination starts with a dash, marking as absolute"
);
candidate.set_absolute(true);
}
return true;
} else if lexeme.match_char(':') {
log!(VERBOSE, "State: Found a colon, marking anchor as external");
@ -205,8 +215,10 @@ mod tests {
fn needless_three_pipe_anchor() {
assert_eq!(
read("|Node|Destination|"),
concat!(r#"<p><a class="detached" title="" "#,
r#"href="/node/Destination">Node</a></p>"#)
concat!(
r#"<p><a class="detached" title="" "#,
r#"href="/node/Destination">Node</a></p>"#
)
);
}
@ -225,9 +237,11 @@ mod tests {
fn anchor_to_node_s() {
assert_eq!(
read("The |letter s|s|'s node: |s|!"),
concat!(r#"<p>The <a class="detached" title="" "#,
concat!(
r#"<p>The <a class="detached" title="" "#,
r#"href="/node/s">letter s</a>'s node: "#,
r#"<a class="detached" title="" href="/node/s">s</a>!</p>"#)
r#"<a class="detached" title="" href="/node/s">s</a>!</p>"#
)
);
}
@ -246,9 +260,11 @@ mod tests {
fn leading_plural_anchor() {
assert_eq!(
read("Interfaces are |element|s of |system|s."),
concat!(r#"<p>Interfaces are <a class="detached" title="" "#,
concat!(
r#"<p>Interfaces are <a class="detached" title="" "#,
r#"href="/node/element">elements</a> of <a class="detached" "#,
r#"title="" href="/node/system">systems</a>.</p>"#)
r#"title="" href="/node/system">systems</a>.</p>"#
)
);
}
@ -268,9 +284,11 @@ mod tests {
fn explicit_end_of_destination() {
assert_eq!(
read("interactions are |basic elements|BasicElements| of systems"),
concat!(r#"<p>interactions are <a class="detached" title="" "#,
concat!(
r#"<p>interactions are <a class="detached" title="" "#,
r#"href="/node/BasicElements">basic elements</a> of "#,
r#"systems</p>"#)
r#"systems</p>"#
)
);
}
@ -327,6 +345,21 @@ mod tests {
);
}
#[test]
fn absolute_anchor() {
let parse_result =
parser::rich_read("see the |raw endpoints|/data|.", &Graph::load());
println!("Parsed tokens: {:#?}", parse_result.tokens);
assert_eq!(
parse_result.text.unwrap(),
concat!(
r#"<p>see the <a class="absolute" title="" "#,
r#"href="/data">"#,
r#"raw endpoints</a>.</p>"#,
),
);
}
#[test]
fn http_external_anchor() {
assert_eq!(

View file

@ -11,6 +11,7 @@ pub struct Anchor {
node: Option<Node>,
leading: bool,
balanced: bool,
absolute: bool,
external: bool,
}
@ -34,10 +35,17 @@ impl Anchor {
self.balanced = balanced;
}
pub const fn absolute(&self) -> bool { self.absolute }
pub const fn set_absolute(&mut self, absolute: bool) {
self.absolute = absolute;
}
pub const fn external(&self) -> bool { self.external }
pub const fn set_external(&mut self, external: bool) {
self.external = external;
self.absolute = true;
}
pub const fn set_leading(&mut self, leading: bool) {
@ -58,21 +66,28 @@ impl Anchor {
fn route(&mut self) {
self.destination = if let Some(destination) = self.destination.clone() {
if destination.contains(':') || destination.contains('/') {
if destination.contains(':') || destination.starts_with('/') {
Some(destination)
} else if destination.is_empty() && self.text.is_empty() {
None
} else if destination.is_empty() {
self.node_id = Some(self.text.clone());
self.node_id = Some(Self::strip_fragment(&self.text));
Some(format!("/node/{}", self.text))
} else {
self.node_id = self.destination.clone();
self.node_id = self
.destination
.clone()
.map(|d| Anchor::strip_fragment(&d));
Some(format!("/node/{destination}"))
}
} else {
None
}
}
fn strip_fragment(target: &str) -> String {
target.split('#').next().unwrap_or(target).to_string()
}
}
impl Parseable for Anchor {
@ -100,19 +115,31 @@ impl Parseable for Anchor {
String::default()
};
let classes = if self.node.is_some() {
String::from(r#"class="attached""#)
} else if !self.external {
String::from(r#"class="detached""#)
} else if self.external {
let classes = if self.external {
String::from(r#"class="external""#)
} else if self.absolute {
String::from(r#"class="absolute""#)
} else if self.node.is_some() {
String::from(r#"class="attached""#)
} else {
String::default()
String::from(r#"class="detached""#)
};
let text = if destination.contains('#')
&& !self.absolute
&& destination == &format!("/node/{}", self.text)
{
self.text
.split('#')
.next()
.unwrap_or(&self.text)
.to_string()
} else {
self.text.clone()
};
format!(
r#"<a {classes} title="{summary}" href="{}">{}</a>"#,
destination, self.text,
r#"<a {classes} title="{summary}" href="{destination}">{text}</a>"#
)
}