Make anchor token fields private
This commit is contained in:
parent
5958f1551b
commit
eb96b456ef
4 changed files with 143 additions and 66 deletions
|
|
@ -23,7 +23,7 @@ pub fn parse(
|
||||||
|
|
||||||
// This is only true if the anchor is leading, otherwise the outer parser
|
// This is only true if the anchor is leading, otherwise the outer parser
|
||||||
// would already have set its text to the word before the first pipe
|
// would already have set its text to the word before the first pipe
|
||||||
if candidate.text.is_empty() {
|
if candidate.text().is_empty() {
|
||||||
log!(
|
log!(
|
||||||
"Seeking end of text at {:#?} -> {:#?}",
|
"Seeking end of text at {:#?} -> {:#?}",
|
||||||
lexeme.text(),
|
lexeme.text(),
|
||||||
|
|
@ -32,7 +32,7 @@ pub fn parse(
|
||||||
if lexeme.next() == "|" {
|
if lexeme.next() == "|" {
|
||||||
log!("End: Next lexeme is a pipe");
|
log!("End: Next lexeme is a pipe");
|
||||||
buffer.text.push_str(&lexeme.text());
|
buffer.text.push_str(&lexeme.text());
|
||||||
candidate.text.clone_from(&buffer.text);
|
candidate.set_text(&buffer.text.clone());
|
||||||
} else {
|
} else {
|
||||||
log!(
|
log!(
|
||||||
"Pushing non-terminal {:#?} into buffer {:#?}",
|
"Pushing non-terminal {:#?} into buffer {:#?}",
|
||||||
|
|
@ -44,7 +44,7 @@ pub fn parse(
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if candidate.destination.is_none() {
|
if candidate.destination().is_none() {
|
||||||
log!(
|
log!(
|
||||||
"Seeking end of destination at {:#?} -> {:#?}",
|
"Seeking end of destination at {:#?} -> {:#?}",
|
||||||
lexeme.text(),
|
lexeme.text(),
|
||||||
|
|
@ -58,8 +58,8 @@ pub fn parse(
|
||||||
&& !lexeme.match_next_char('|')
|
&& !lexeme.match_next_char('|')
|
||||||
{
|
{
|
||||||
log!("End: Plural anchor");
|
log!("End: Plural anchor");
|
||||||
candidate.destination = Some(candidate.text.clone());
|
candidate.set_destination(Some(&candidate.text().clone()));
|
||||||
candidate.text.push('s');
|
candidate.text_push("s");
|
||||||
if lexeme.last() {
|
if lexeme.last() {
|
||||||
tokens.push(Token::Anchor(candidate.clone()));
|
tokens.push(Token::Anchor(candidate.clone()));
|
||||||
state.context.inline = Inline::None;
|
state.context.inline = Inline::None;
|
||||||
|
|
@ -68,43 +68,43 @@ pub fn parse(
|
||||||
} else if lexeme.match_char('|') && lexeme.is_next_delimiter() {
|
} else if lexeme.match_char('|') && lexeme.is_next_delimiter() {
|
||||||
log!("End: Pipe followed by delimiter");
|
log!("End: Pipe followed by delimiter");
|
||||||
if buffer.destination.is_empty() {
|
if buffer.destination.is_empty() {
|
||||||
candidate.destination = Some(candidate.text.clone());
|
candidate.set_destination(Some(&candidate.text().clone()));
|
||||||
} else {
|
} else {
|
||||||
candidate.destination = Some(buffer.destination.clone());
|
candidate.set_destination(Some(&buffer.destination.clone()));
|
||||||
}
|
}
|
||||||
tokens.push(Token::Anchor(candidate.clone()));
|
tokens.push(Token::Anchor(candidate.clone()));
|
||||||
state.context.inline = Inline::None;
|
state.context.inline = Inline::None;
|
||||||
return true;
|
return true;
|
||||||
} else if lexeme.match_char('|') && !candidate.balanced {
|
} else if lexeme.match_char('|') && !candidate.balanced() {
|
||||||
log!("State: Found a pipe, but no boundary: destination follows");
|
log!("State: Found a pipe, but no boundary: destination follows");
|
||||||
candidate.balanced = true;
|
candidate.set_balanced(true);
|
||||||
return true;
|
return true;
|
||||||
} else if lexeme.match_char(':') {
|
} else if lexeme.match_char(':') {
|
||||||
log!("State: Found a colon, marking anchor as external");
|
log!("State: Found a colon, marking anchor as external");
|
||||||
candidate.external = true;
|
candidate.set_external(true);
|
||||||
buffer.destination.push_str(&lexeme.text());
|
buffer.destination.push_str(&lexeme.text());
|
||||||
return true;
|
return true;
|
||||||
} else if lexeme.match_char('|') {
|
} else if lexeme.match_char('|') {
|
||||||
log!("End: Explicit end-of-destination pipe");
|
log!("End: Explicit end-of-destination pipe");
|
||||||
candidate.destination = Some(buffer.destination.clone());
|
candidate.set_destination(Some(&buffer.destination.clone()));
|
||||||
return true;
|
return true;
|
||||||
} else if !candidate.external && lexeme.is_delimiter() {
|
} else if !candidate.external() && lexeme.is_delimiter() {
|
||||||
log!("End: Internal anchor trailed by delimiter");
|
log!("End: Internal anchor trailed by delimiter");
|
||||||
candidate.destination = Some(buffer.destination.clone());
|
candidate.set_destination(Some(&buffer.destination.clone()));
|
||||||
tokens.push(Token::Anchor(candidate.clone()));
|
tokens.push(Token::Anchor(candidate.clone()));
|
||||||
state.context.inline = Inline::None;
|
state.context.inline = Inline::None;
|
||||||
return false;
|
return false;
|
||||||
} else if lexeme.is_next_whitespace() {
|
} else if lexeme.is_next_whitespace() {
|
||||||
log!("End: next is whitespace");
|
log!("End: next is whitespace");
|
||||||
buffer.destination.push_str(&lexeme.text());
|
buffer.destination.push_str(&lexeme.text());
|
||||||
candidate.destination = Some(buffer.destination.clone());
|
candidate.set_destination(Some(&buffer.destination.clone()));
|
||||||
tokens.push(Token::Anchor(candidate.clone()));
|
tokens.push(Token::Anchor(candidate.clone()));
|
||||||
state.context.inline = Inline::None;
|
state.context.inline = Inline::None;
|
||||||
return true;
|
return true;
|
||||||
} else if lexeme.last() {
|
} else if lexeme.last() {
|
||||||
log!("End: end of input");
|
log!("End: end of input");
|
||||||
buffer.destination.push_str(&lexeme.text());
|
buffer.destination.push_str(&lexeme.text());
|
||||||
candidate.destination = Some(buffer.destination.clone());
|
candidate.set_destination(Some(&buffer.destination.clone()));
|
||||||
tokens.push(Token::Anchor(candidate.clone()));
|
tokens.push(Token::Anchor(candidate.clone()));
|
||||||
state.context.inline = Inline::None;
|
state.context.inline = Inline::None;
|
||||||
return true;
|
return true;
|
||||||
|
|
@ -119,7 +119,7 @@ pub fn parse(
|
||||||
);
|
);
|
||||||
buffer.destination.push_str(&lexeme.text());
|
buffer.destination.push_str(&lexeme.text());
|
||||||
if lexeme.last() {
|
if lexeme.last() {
|
||||||
candidate.destination = Some(buffer.destination.clone());
|
candidate.set_destination(Some(&buffer.destination.clone()));
|
||||||
tokens.push(Token::Anchor(candidate.clone()));
|
tokens.push(Token::Anchor(candidate.clone()));
|
||||||
state.context.inline = Inline::None;
|
state.context.inline = Inline::None;
|
||||||
}
|
}
|
||||||
|
|
@ -132,7 +132,7 @@ pub fn parse(
|
||||||
// was never found and we kept filling the buffer endlessly,
|
// was never found and we kept filling the buffer endlessly,
|
||||||
// causing the program to panic anyways when rendering anchors
|
// causing the program to panic anyways when rendering anchors
|
||||||
assert!(
|
assert!(
|
||||||
candidate.destination.is_some(),
|
candidate.destination().is_some(),
|
||||||
"Anchor context parsing done but no destination found: {:#?}",
|
"Anchor context parsing done but no destination found: {:#?}",
|
||||||
state.buffers.anchor
|
state.buffers.anchor
|
||||||
);
|
);
|
||||||
|
|
@ -149,6 +149,11 @@ mod tests {
|
||||||
parser::read(input, &Graph::new(None).meta.config)
|
parser::read(input, &Graph::new(None).meta.config)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn flanking() {
|
||||||
|
assert_eq!(read("|Node|"), r#"<p><a href="/node/Node">Node</a></p>"#);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn flanking_with_trailing_comma() {
|
fn flanking_with_trailing_comma() {
|
||||||
assert_eq!(read("|Node|,"), r#"<p><a href="/node/Node">Node</a>,</p>"#);
|
assert_eq!(read("|Node|,"), r#"<p><a href="/node/Node">Node</a>,</p>"#);
|
||||||
|
|
|
||||||
|
|
@ -32,9 +32,9 @@ pub fn parse(
|
||||||
state.buffers.anchor = AnchorBuffer::default();
|
state.buffers.anchor = AnchorBuffer::default();
|
||||||
|
|
||||||
if lexeme.match_char('|') {
|
if lexeme.match_char('|') {
|
||||||
state.buffers.anchor.candidate.leading = true;
|
state.buffers.anchor.candidate.set_leading(true);
|
||||||
} else {
|
} else {
|
||||||
state.buffers.anchor.candidate.text = lexeme.text();
|
state.buffers.anchor.candidate.set_text(&lexeme.text());
|
||||||
// because we probed positively and this is not a pipe,
|
// because we probed positively and this is not a pipe,
|
||||||
// the next lexeme must be and so it was now parsed
|
// the next lexeme must be and so it was now parsed
|
||||||
iterator.next();
|
iterator.next();
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,96 @@
|
||||||
use crate::syntax::content::{Parseable, parser::lexeme::Lexeme};
|
use crate::{
|
||||||
|
syntax::content::{Parseable, parser::lexeme::Lexeme},
|
||||||
|
types::Node,
|
||||||
|
};
|
||||||
|
|
||||||
#[derive(Debug, Clone, Eq, PartialEq, Default)]
|
#[derive(Default, Debug, Clone, Eq, PartialEq)]
|
||||||
pub struct Anchor {
|
pub struct Anchor {
|
||||||
pub text: String,
|
text: String,
|
||||||
pub destination: Option<String>,
|
destination: Option<String>,
|
||||||
pub leading: bool,
|
node: Option<Node>,
|
||||||
pub balanced: bool,
|
leading: bool,
|
||||||
pub external: bool,
|
balanced: bool,
|
||||||
|
external: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Anchor {
|
||||||
|
pub fn new(
|
||||||
|
text: &str,
|
||||||
|
destination: &str,
|
||||||
|
node: Option<Node>,
|
||||||
|
leading: bool,
|
||||||
|
external: bool,
|
||||||
|
balanced: bool,
|
||||||
|
) -> Anchor {
|
||||||
|
let mut anchor = Anchor {
|
||||||
|
text: text.to_owned(),
|
||||||
|
destination: Some(String::from(destination)),
|
||||||
|
node,
|
||||||
|
leading,
|
||||||
|
external,
|
||||||
|
balanced,
|
||||||
|
};
|
||||||
|
|
||||||
|
anchor.route();
|
||||||
|
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 fn balanced(&self) -> bool {
|
||||||
|
self.balanced
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_balanced(&mut self, balanced: bool) {
|
||||||
|
self.balanced = balanced;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn external(&self) -> bool {
|
||||||
|
self.external
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_external(&mut self, external: bool) {
|
||||||
|
self.external = external;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_leading(&mut self, leading: bool) {
|
||||||
|
self.leading = leading;
|
||||||
|
}
|
||||||
|
|
||||||
|
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() {
|
||||||
|
Some(format!("/node/{}", self.text))
|
||||||
|
} else {
|
||||||
|
Some(format!("/node/{destination}"))
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Parseable for Anchor {
|
impl Parseable for Anchor {
|
||||||
|
|
@ -27,17 +111,7 @@ impl Parseable for Anchor {
|
||||||
)
|
)
|
||||||
};
|
};
|
||||||
|
|
||||||
let non_empty_destination = if destination.is_empty() {
|
format!(r#"<a href="{}">{}</a>"#, destination, &self.text)
|
||||||
self.text.clone()
|
|
||||||
} else {
|
|
||||||
destination.to_owned()
|
|
||||||
};
|
|
||||||
|
|
||||||
format!(
|
|
||||||
r#"<a href="{}">{}</a>"#,
|
|
||||||
Anchor::resolve_destination(&non_empty_destination),
|
|
||||||
&self.text
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -79,32 +153,6 @@ impl std::fmt::Display for Anchor {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Anchor {
|
|
||||||
pub fn new(
|
|
||||||
text: &str,
|
|
||||||
destination: &str,
|
|
||||||
leading: bool,
|
|
||||||
external: bool,
|
|
||||||
balanced: bool,
|
|
||||||
) -> Anchor {
|
|
||||||
Anchor {
|
|
||||||
text: text.to_owned(),
|
|
||||||
destination: Some(Anchor::resolve_destination(destination)),
|
|
||||||
leading,
|
|
||||||
external,
|
|
||||||
balanced,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn resolve_destination(raw: &str) -> String {
|
|
||||||
if raw.contains(":") || raw.contains("/") {
|
|
||||||
raw.to_owned()
|
|
||||||
} else {
|
|
||||||
format!("/node/{raw}")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
|
|
||||||
|
|
@ -114,8 +162,9 @@ mod tests {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn render_anchor() {
|
fn render_anchor() {
|
||||||
let anchor =
|
let mut anchor = Anchor::default();
|
||||||
Anchor::new("AnchorText", "AnchorDest", true, false, false);
|
anchor.set_text("AnchorText");
|
||||||
|
anchor.set_destination(Some("AnchorDest"));
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
anchor.render(),
|
anchor.render(),
|
||||||
r#"<a href="/node/AnchorDest">AnchorText</a>"#
|
r#"<a href="/node/AnchorDest">AnchorText</a>"#
|
||||||
|
|
@ -168,4 +217,27 @@ mod tests {
|
||||||
+Leading +Balanced +External",
|
+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 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());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -202,7 +202,7 @@ DRC|DemocraticRepublicOfTheCongo
|
||||||
docs|https://en.jutty.dev/node/Documentation
|
docs|https://en.jutty.dev/node/Documentation
|
||||||
`
|
`
|
||||||
|
|
||||||
As shown above, anchors can point to external addresses. These are identified by the presence of a `:` character in the destination. Otherwise, the anchor will point to a node with an ID matching the destination.
|
As shown above, anchors can point to external addresses. These are identified by the presence of either a `:` or a `/` character in the destination. Otherwise, the anchor will point to a node with an ID matching the destination. This means your anchors to external URLs, special handlers such as `mailto:user@domain.com` and destinations relative to the website root like `/about` will all work as intended without being interpreted as node IDs.
|
||||||
|
|
||||||
If the left side contains spaces, you need a leading `|` character:
|
If the left side contains spaces, you need a leading `|` character:
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue