use super::prelude::*;
use crate::tree::{AnchorTarget, LinkLabel, LinkLocation};
use std::borrow::Cow;
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)");
check_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)");
check_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 = strip_category(url).map(Cow::Borrowed);
let (link, ltype) = match LinkLocation::parse_interwiki(cow!(url), parser.settings())
{
Some(result) => result,
None => return Err(parser.make_err(ParseErrorKind::RuleFailed)),
};
let element = Element::Link {
ltype,
link,
extra: LinkLocation::parse_extra(cow!(url)),
label: LinkLabel::Url(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_interwiki(cow!(url), parser.settings())
{
Some(result) => result,
None => return Err(parser.make_err(ParseErrorKind::RuleFailed)),
};
let element = Element::Link {
ltype,
link,
extra: LinkLocation::parse_extra(cow!(url)),
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! check {
($input:expr, $expected:expr $(,)?) => {{
let actual = strip_category($input);
assert_eq!(
actual, $expected,
"Actual stripped URL label doesn't match expected",
);
}};
}
check!("", None);
check!("scp-001", None);
check!("Guide Hub", None);
check!("theme:just-girly-things", Some("just-girly-things"));
check!("theme: just-girly-things", Some("just-girly-things"));
check!("theme: Just Girly Things", Some("Just Girly Things"));
check!("component:fancy-sidebar", Some("fancy-sidebar"));
check!("component:Fancy Sidebar", Some("Fancy Sidebar"));
check!("component: Fancy Sidebar", Some("Fancy Sidebar"));
check!(
"multiple:categories:here:test",
Some("categories:here:test"),
);
check!(
"multiple: categories: here: test",
Some("categories: here: test"),
);
check!(":scp-wiki:scp-001", Some("scp-001"));
check!(":scp-wiki : SCP-001", Some("SCP-001"));
check!(":scp-wiki:system:recent-changes", Some("recent-changes"));
check!(
":scp-wiki : system : Recent Changes",
Some("Recent Changes"),
);
check!(": snippets : redirect", Some("redirect"));
check!(":", None);
}