use super::prelude::*;
use crate::tree::{AnchorTarget, LinkLabel, LinkLocation};
pub const RULE_LINK_TRIPLE: Rule = Rule {
name: "link-triple",
position: LineRequirement::Any,
try_consume_fn: link,
};
pub const RULE_LINK_TRIPLE_NEW_TAB: Rule = Rule {
name: "link-triple-new-tab",
position: LineRequirement::Any,
try_consume_fn: link_new_tab,
};
fn link<'r, 't>(parser: &mut Parser<'r, 't>) -> ParseResult<'r, 't, Elements<'t>> {
debug!("Trying to create a triple-bracket link (regular)");
assert_step(parser, Token::LeftLink)?;
try_consume_link(parser, RULE_LINK_TRIPLE, None)
}
fn link_new_tab<'r, 't>(
parser: &mut Parser<'r, 't>,
) -> ParseResult<'r, 't, Elements<'t>> {
debug!("Trying to create a triple-bracket link (new tab)");
assert_step(parser, Token::LeftLinkStar)?;
try_consume_link(parser, RULE_LINK_TRIPLE_NEW_TAB, Some(AnchorTarget::NewTab))
}
fn try_consume_link<'r, 't>(
parser: &mut Parser<'r, 't>,
rule: Rule,
target: Option<AnchorTarget>,
) -> ParseResult<'r, 't, Elements<'t>> {
trace!("Trying to create a triple-bracket link");
let (url, last) = collect_text_keep(
parser,
rule,
&[
ParseCondition::current(Token::Pipe),
ParseCondition::current(Token::RightLink),
],
&[
ParseCondition::current(Token::ParagraphBreak),
ParseCondition::current(Token::LineBreak),
],
None,
)?;
trace!("Retrieved url for link, now build element (url: '{url}')");
let url = url.trim();
if url.is_empty() {
return Err(parser.make_err(ParseErrorKind::RuleFailed));
}
match last.token {
Token::RightLink => build_same(parser, url, target),
Token::Pipe => build_separate(parser, rule, url, target),
_ => unreachable!(),
}
}
fn build_same<'r, 't>(
parser: &mut Parser<'r, 't>,
url: &'t str,
target: Option<AnchorTarget>,
) -> ParseResult<'r, 't, Elements<'t>> {
debug!("Building link with same URL and label (url '{url}')");
let label = match strip_category(url) {
Some(stripped) => cow!(stripped),
None => cow!(url),
};
let (link, ltype) =
match LinkLocation::parse_with_interwiki(cow!(url), parser.settings()) {
Some(result) => result,
None => return Err(parser.make_err(ParseErrorKind::RuleFailed)),
};
let element = Element::Link {
ltype,
link,
label: LinkLabel::Slug(label),
target,
};
ok!(element)
}
fn build_separate<'r, 't>(
parser: &mut Parser<'r, 't>,
rule: Rule,
url: &'t str,
target: Option<AnchorTarget>,
) -> ParseResult<'r, 't, Elements<'t>> {
debug!("Building link with separate URL and label (url '{url}')");
let label = collect_text(
parser,
rule,
&[ParseCondition::current(Token::RightLink)],
&[
ParseCondition::current(Token::ParagraphBreak),
ParseCondition::current(Token::LineBreak),
],
None,
)?;
trace!("Retrieved label for link, now building element (label '{label}')");
let label = label.trim();
let label = if label.is_empty() {
LinkLabel::Page
} else {
LinkLabel::Text(cow!(label))
};
let (link, ltype) =
match LinkLocation::parse_with_interwiki(cow!(url), parser.settings()) {
Some(result) => result,
None => return Err(parser.make_err(ParseErrorKind::RuleFailed)),
};
let element = Element::Link {
ltype,
link,
label,
target,
};
ok!(element)
}
fn strip_category(url: &str) -> Option<&str> {
match url.find(':') {
Some(0) => {
let url = &url[1..];
url.find(':').map(|idx| {
let url = url[idx + 1..].trim_start();
strip_category(url).unwrap_or(url)
})
}
Some(idx) => Some(url[idx + 1..].trim_start()),
None => None,
}
}
#[test]
fn test_strip_category() {
macro_rules! test {
($input:expr, $expected:expr $(,)?) => {{
let actual = strip_category($input);
assert_eq!(
actual, $expected,
"Actual stripped URL label doesn't match expected",
);
}};
}
test!("", None);
test!("scp-001", None);
test!("Guide Hub", None);
test!("theme:just-girly-things", Some("just-girly-things"));
test!("theme: just-girly-things", Some("just-girly-things"));
test!("theme: Just Girly Things", Some("Just Girly Things"));
test!("component:fancy-sidebar", Some("fancy-sidebar"));
test!("component:Fancy Sidebar", Some("Fancy Sidebar"));
test!("component: Fancy Sidebar", Some("Fancy Sidebar"));
test!(
"multiple:categories:here:test",
Some("categories:here:test"),
);
test!(
"multiple: categories: here: test",
Some("categories: here: test"),
);
test!(":scp-wiki:scp-001", Some("scp-001"));
test!(":scp-wiki : SCP-001", Some("SCP-001"));
test!(":scp-wiki:system:recent-changes", Some("recent-changes"));
test!(
":scp-wiki : system : Recent Changes",
Some("Recent Changes"),
);
test!(": snippets : redirect", Some("redirect"));
test!(":", None);
}