diff options
| author | Laurenz <laurmaedje@gmail.com> | 2024-02-28 11:37:52 +0100 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2024-02-28 10:37:52 +0000 |
| commit | a518e2dd4d829b45b0887da28acb77d0568894ab (patch) | |
| tree | e7387b27e299383a0bd1a26ad58f485630cb23b7 /docs/src/link.rs | |
| parent | e16d3f5a67a31154797b4d56cdc6ed142ee2a7cf (diff) | |
Move docs generation code (#3519)
Diffstat (limited to 'docs/src/link.rs')
| -rw-r--r-- | docs/src/link.rs | 108 |
1 files changed, 108 insertions, 0 deletions
diff --git a/docs/src/link.rs b/docs/src/link.rs new file mode 100644 index 00000000..f4d803c3 --- /dev/null +++ b/docs/src/link.rs @@ -0,0 +1,108 @@ +use typst::diag::{bail, StrResult}; +use typst::foundations::Func; + +use crate::{get_module, GROUPS, LIBRARY}; + +/// Resolve an intra-doc link. +pub fn resolve(link: &str, base: &str) -> StrResult<String> { + if link.starts_with('#') || link.starts_with("http") { + return Ok(link.to_string()); + } + + let (head, tail) = split_link(link)?; + let mut route = match resolve_known(head, base) { + Some(route) => route, + None => resolve_definition(head, base)?, + }; + + if !tail.is_empty() { + route.push('/'); + route.push_str(tail); + } + + if !route.contains('#') && !route.ends_with('/') { + route.push('/'); + } + + Ok(route) +} + +/// Split a link at the first slash. +fn split_link(link: &str) -> StrResult<(&str, &str)> { + let first = link.split('/').next().unwrap_or(link); + let rest = link[first.len()..].trim_start_matches('/'); + Ok((first, rest)) +} + +/// Resolve a `$` link head to a known destination. +fn resolve_known(head: &str, base: &str) -> Option<String> { + Some(match head { + "$tutorial" => format!("{base}tutorial"), + "$reference" => format!("{base}reference"), + "$category" => format!("{base}reference"), + "$syntax" => format!("{base}reference/syntax"), + "$styling" => format!("{base}reference/styling"), + "$scripting" => format!("{base}reference/scripting"), + "$context" => format!("{base}reference/context"), + "$guides" => format!("{base}guides"), + "$packages" => format!("{base}packages"), + "$changelog" => format!("{base}changelog"), + "$community" => format!("{base}community"), + _ => return None, + }) +} + +/// Resolve a `$` link to a global definition. +fn resolve_definition(head: &str, base: &str) -> StrResult<String> { + let mut parts = head.trim_start_matches('$').split('.').peekable(); + let mut focus = &LIBRARY.global; + let mut category = None; + + while let Some(name) = parts.peek() { + if category.is_none() { + category = focus.scope().get_category(name); + } + let Ok(module) = get_module(focus, name) else { break }; + focus = module; + parts.next(); + } + + let Some(category) = category else { bail!("{head} has no category") }; + + let name = parts.next().ok_or("link is missing first part")?; + let value = focus.field(name)?; + + // Handle grouped functions. + if let Some(group) = GROUPS.iter().find(|group| { + group.category == category.name() && group.filter.iter().any(|func| func == name) + }) { + let mut route = format!( + "{}reference/{}/{}/#functions-{}", + base, group.category, group.name, name + ); + if let Some(param) = parts.next() { + route.push('-'); + route.push_str(param); + } + return Ok(route); + } + + let mut route = format!("{}reference/{}/{name}/", base, category.name()); + if let Some(next) = parts.next() { + if value.field(next).is_ok() { + route.push_str("#definitions-"); + route.push_str(next); + } else if value + .clone() + .cast::<Func>() + .map_or(false, |func| func.param(next).is_some()) + { + route.push_str("#parameters-"); + route.push_str(next); + } else { + bail!("field {next} not found"); + } + } + + Ok(route) +} |
