264 lines
7.3 KiB
Rust
264 lines
7.3 KiB
Rust
use crate::{
|
|
graph::Node,
|
|
syntax::content::{Parseable, parser::Lexeme},
|
|
};
|
|
|
|
#[derive(Default, Debug, Clone, Eq, PartialEq)]
|
|
pub struct Anchor {
|
|
text: String,
|
|
destination: Option<String>,
|
|
node_id: Option<String>,
|
|
node: Option<Node>,
|
|
leading: bool,
|
|
balanced: bool,
|
|
external: bool,
|
|
}
|
|
|
|
impl Anchor {
|
|
pub fn text(&self) -> String { self.text.clone() }
|
|
|
|
pub fn set_text(&mut self, text: &str) { self.text = String::from(text); }
|
|
|
|
pub fn text_push(&mut self, text: &str) { self.text.push_str(text); }
|
|
|
|
pub fn destination(&self) -> Option<String> { self.destination.clone() }
|
|
|
|
pub fn set_destination(&mut self, destination: Option<&str>) {
|
|
self.destination = destination.map(str::to_string);
|
|
self.route();
|
|
}
|
|
|
|
pub const fn balanced(&self) -> bool { self.balanced }
|
|
|
|
pub const fn set_balanced(&mut self, balanced: bool) {
|
|
self.balanced = balanced;
|
|
}
|
|
|
|
pub const fn external(&self) -> bool { self.external }
|
|
|
|
pub const fn set_external(&mut self, external: bool) {
|
|
self.external = external;
|
|
}
|
|
|
|
pub const fn set_leading(&mut self, leading: bool) {
|
|
self.leading = leading;
|
|
}
|
|
|
|
pub fn node(&self) -> Option<Node> { self.node.clone() }
|
|
|
|
pub fn set_node(&mut self, node: &Node) {
|
|
self.node = Some(node.to_owned());
|
|
}
|
|
|
|
pub fn node_id(&self) -> Option<String> { self.node_id.clone() }
|
|
|
|
pub fn set_node_id(&mut self, id: &str) {
|
|
self.node_id = Some(id.to_owned());
|
|
}
|
|
|
|
fn route(&mut self) {
|
|
self.destination = if let Some(destination) = self.destination.clone() {
|
|
if destination.contains(':') || destination.contains('/') {
|
|
Some(destination)
|
|
} else if destination.is_empty() && self.text.is_empty() {
|
|
None
|
|
} else if destination.is_empty() {
|
|
self.node_id = Some(self.text.clone());
|
|
Some(format!("/node/{}", self.text))
|
|
} else {
|
|
self.node_id = self.destination.clone();
|
|
Some(format!("/node/{destination}"))
|
|
}
|
|
} else {
|
|
None
|
|
}
|
|
}
|
|
}
|
|
|
|
impl Parseable for Anchor {
|
|
fn probe(lexeme: &Lexeme) -> bool {
|
|
lexeme.text() == "|"
|
|
|| ((!lexeme.is_whitespace() && !lexeme.is_delimiter())
|
|
&& lexeme.next() == "|")
|
|
}
|
|
|
|
fn lex(_lexeme: &Lexeme) -> Anchor {
|
|
panic!("Attempt to lex an anchor directly from a lexeme");
|
|
}
|
|
|
|
fn render(&self) -> String {
|
|
let Some(destination) = &self.destination else {
|
|
panic!(
|
|
"Attempt to render anchor {self:#?} without knowing \
|
|
its destination."
|
|
)
|
|
};
|
|
|
|
let summary = if let Some(node) = self.node.clone() {
|
|
node.summary
|
|
} else {
|
|
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 {
|
|
String::from(r#"class="external""#)
|
|
} else {
|
|
String::default()
|
|
};
|
|
|
|
format!(
|
|
r#"<a {classes} title="{summary}" href="{}">{}</a>"#,
|
|
destination, self.text,
|
|
)
|
|
}
|
|
|
|
fn flatten(&self) -> String { self.text.clone() }
|
|
}
|
|
|
|
impl std::fmt::Display for Anchor {
|
|
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
|
use crate::dev::log::wrap;
|
|
|
|
let wrapped_text = wrap(&self.text);
|
|
let display_text = if wrapped_text.is_empty() {
|
|
"<empty>"
|
|
} else {
|
|
wrapped_text.as_str()
|
|
};
|
|
|
|
let display_destination = match &self.destination {
|
|
Some(destination) => {
|
|
if destination.is_empty() {
|
|
String::from("<empty>")
|
|
} else {
|
|
format!("{destination:?}")
|
|
}
|
|
},
|
|
None => String::from("<unknown>"),
|
|
};
|
|
|
|
let mut tail = String::default();
|
|
|
|
if self.leading {
|
|
tail.push_str(" +Leading");
|
|
}
|
|
if self.balanced {
|
|
tail.push_str(" +Balanced");
|
|
}
|
|
if self.external {
|
|
tail.push_str(" +External");
|
|
}
|
|
|
|
write!(f, "Anchor {display_text} -> {display_destination}{tail}")
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
|
|
use super::*;
|
|
use crate::syntax::content::parser::Token;
|
|
|
|
#[test]
|
|
fn render_anchor() {
|
|
let mut anchor = Anchor::default();
|
|
anchor.set_text("AnchorText");
|
|
anchor.set_destination(Some("AnchorDest"));
|
|
assert_eq!(
|
|
anchor.render(),
|
|
concat!(
|
|
r#"<a class="detached" title="" "#,
|
|
r#"href="/node/AnchorDest">AnchorText</a>"#,
|
|
)
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
#[should_panic(
|
|
expected = "Attempt to lex an anchor directly from a lexeme"
|
|
)]
|
|
fn lex() { Anchor::lex(&Lexeme::default()); }
|
|
|
|
#[test]
|
|
#[should_panic(expected = "without knowing its destination")]
|
|
fn unknown_destination_render() {
|
|
let anchor = Anchor::default();
|
|
drop(anchor.render());
|
|
}
|
|
|
|
#[test]
|
|
fn token_display() {
|
|
let mut anchor = Anchor::default();
|
|
assert_eq!(
|
|
format!("{}", Token::Anchor(Box::new(anchor.clone()))),
|
|
"Tk:Anchor <empty> -> <unknown>",
|
|
);
|
|
|
|
anchor.text = String::from("FsJAt RTggA");
|
|
assert_eq!(
|
|
format!("{}", Token::Anchor(Box::new(anchor.clone()))),
|
|
"Tk:Anchor 'FsJAt RTggA' -> <unknown>",
|
|
);
|
|
|
|
anchor.text = String::from("wPVo1 0OmYm");
|
|
anchor.destination = Some(String::from("M1UEp 1gbfr"));
|
|
assert_eq!(
|
|
format!("{}", Token::Anchor(Box::new(anchor.clone()))),
|
|
r#"Tk:Anchor 'wPVo1 0OmYm' -> "M1UEp 1gbfr""#,
|
|
);
|
|
|
|
anchor.balanced = true;
|
|
anchor.leading = true;
|
|
anchor.external = true;
|
|
|
|
assert_eq!(
|
|
format!("{}", Token::Anchor(Box::new(anchor))),
|
|
"Tk:Anchor 'wPVo1 0OmYm' -> \"M1UEp 1gbfr\" \
|
|
+Leading +Balanced +External",
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn display_empty_destination() {
|
|
let mut anchor = Anchor::default();
|
|
anchor.set_destination(Some(""));
|
|
assert_eq!(format!("{anchor}"), "Anchor <empty> -> <unknown>");
|
|
}
|
|
|
|
#[test]
|
|
fn render_empty_destination() {
|
|
let mut anchor = Anchor::default();
|
|
anchor.set_text("BSThI");
|
|
anchor.set_destination(Some(""));
|
|
assert_eq!(
|
|
anchor.render(),
|
|
r#"<a class="detached" title="" href="/node/BSThI">BSThI</a>"#
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn resolve_none_destination() {
|
|
let mut anchor = Anchor::default();
|
|
anchor.set_destination(None);
|
|
anchor.route(); // set_destination also called this
|
|
assert!(anchor.destination().is_none());
|
|
}
|
|
|
|
#[test]
|
|
fn set_node_id() {
|
|
let payload = "kxBDJ0EoDVaygxpZ8NgNdQrUIBsGimTs";
|
|
let mut anchor = Anchor::default();
|
|
anchor.set_node_id(payload);
|
|
assert_eq!(anchor.node_id.unwrap(), payload);
|
|
}
|
|
|
|
#[test]
|
|
fn display_no_destination() {
|
|
let anchor = Anchor::default();
|
|
assert_eq!(format!("{anchor}"), "Anchor <empty> -> <unknown>");
|
|
}
|
|
}
|