From 029ae4a5ea7ad1e52112ce26b6d38ce1750dae3f Mon Sep 17 00:00:00 2001 From: Laurenz Date: Wed, 5 Feb 2025 14:24:10 +0100 Subject: Export target docs (#5812) Co-authored-by: Martin Haug <3874949+reknih@users.noreply.github.com> --- docs/src/html.rs | 13 ++-- docs/src/lib.rs | 184 +++++++++++++++++++++++++++++++++++++----------------- docs/src/link.rs | 9 ++- docs/src/model.rs | 5 +- 4 files changed, 146 insertions(+), 65 deletions(-) (limited to 'docs/src') diff --git a/docs/src/html.rs b/docs/src/html.rs index 4eb3954c..9077d5c4 100644 --- a/docs/src/html.rs +++ b/docs/src/html.rs @@ -301,7 +301,10 @@ impl<'a> Handler<'a> { return; } - let default = self.peeked.as_ref().map(|text| text.to_kebab_case()); + let body = self.peeked.as_ref(); + let default = body.map(|text| text.to_kebab_case()); + let has_id = id_slot.is_some(); + let id: &'a str = match (&id_slot, default) { (Some(id), default) => { if Some(*id) == default.as_deref() { @@ -316,10 +319,10 @@ impl<'a> Handler<'a> { *id_slot = (!id.is_empty()).then_some(id); // Special case for things like "v0.3.0". - let name = if id.starts_with('v') && id.contains('.') { - id.into() - } else { - id.to_title_case().into() + let name = match &body { + _ if id.starts_with('v') && id.contains('.') => id.into(), + Some(body) if !has_id => body.as_ref().into(), + _ => id.to_title_case().into(), }; let mut children = &mut self.outline; diff --git a/docs/src/lib.rs b/docs/src/lib.rs index ff745c9c..f9ee05bb 100644 --- a/docs/src/lib.rs +++ b/docs/src/lib.rs @@ -12,27 +12,20 @@ pub use self::model::*; use std::collections::HashSet; use ecow::{eco_format, EcoString}; +use heck::ToTitleCase; use serde::Deserialize; use serde_yaml as yaml; use std::sync::LazyLock; use typst::diag::{bail, StrResult}; -use typst::foundations::Binding; use typst::foundations::{ - AutoValue, Bytes, CastInfo, Category, Func, Module, NoneValue, ParamInfo, Repr, - Scope, Smart, Type, Value, FOUNDATIONS, + AutoValue, Binding, Bytes, CastInfo, Func, Module, NoneValue, ParamInfo, Repr, Scope, + Smart, Type, Value, }; -use typst::html::HTML; -use typst::introspection::INTROSPECTION; -use typst::layout::{Abs, Margin, PageElem, PagedDocument, LAYOUT}; -use typst::loading::DATA_LOADING; -use typst::math::MATH; -use typst::model::MODEL; -use typst::pdf::PDF; -use typst::symbols::SYMBOLS; -use typst::text::{Font, FontBook, TEXT}; +use typst::layout::{Abs, Margin, PageElem, PagedDocument}; +use typst::text::{Font, FontBook}; use typst::utils::LazyHash; -use typst::visualize::VISUALIZE; -use typst::{Feature, Library, LibraryBuilder}; +use typst::{Category, Feature, Library, LibraryBuilder}; +use unicode_math_class::MathClass; macro_rules! load { ($path:literal) => { @@ -64,9 +57,10 @@ static LIBRARY: LazyLock> = LazyLock::new(|| { let scope = lib.global.scope_mut(); // Add those types, so that they show up in the docs. - scope.start_category(FOUNDATIONS); + scope.start_category(Category::Foundations); scope.define_type::(); scope.define_type::(); + scope.reset_category(); // Adjust the default look. lib.styles @@ -155,21 +149,24 @@ fn reference_pages(resolver: &dyn Resolver) -> PageModel { let mut page = md_page(resolver, resolver.base(), load!("reference/welcome.md")); let base = format!("{}reference/", resolver.base()); page.children = vec![ - md_page(resolver, &base, load!("reference/syntax.md")).with_part("Language"), - md_page(resolver, &base, load!("reference/styling.md")), - md_page(resolver, &base, load!("reference/scripting.md")), - md_page(resolver, &base, load!("reference/context.md")), - category_page(resolver, FOUNDATIONS).with_part("Library"), - category_page(resolver, MODEL), - category_page(resolver, TEXT), - category_page(resolver, MATH), - category_page(resolver, SYMBOLS), - category_page(resolver, LAYOUT), - category_page(resolver, VISUALIZE), - category_page(resolver, INTROSPECTION), - category_page(resolver, DATA_LOADING), - category_page(resolver, PDF), - category_page(resolver, HTML), + md_page(resolver, &base, load!("reference/language/syntax.md")) + .with_part("Language"), + md_page(resolver, &base, load!("reference/language/styling.md")), + md_page(resolver, &base, load!("reference/language/scripting.md")), + md_page(resolver, &base, load!("reference/language/context.md")), + category_page(resolver, Category::Foundations).with_part("Library"), + category_page(resolver, Category::Model), + category_page(resolver, Category::Text), + category_page(resolver, Category::Math), + category_page(resolver, Category::Symbols), + category_page(resolver, Category::Layout), + category_page(resolver, Category::Visualize), + category_page(resolver, Category::Introspection), + category_page(resolver, Category::DataLoading), + category_page(resolver, Category::Pdf).with_part("Export"), + category_page(resolver, Category::Html), + category_page(resolver, Category::Png), + category_page(resolver, Category::Svg), ]; page } @@ -219,14 +216,16 @@ fn category_page(resolver: &dyn Resolver, category: Category) -> PageModel { let mut markup = vec![]; let mut math = vec![]; - let (module, path): (&Module, &[&str]) = if category == MATH { - (&LIBRARY.math, &["math"]) - } else { - (&LIBRARY.global, &[]) + let docs = category_docs(category); + let (module, path): (&Module, &[&str]) = match category { + Category::Math => (&LIBRARY.math, &["math"]), + Category::Pdf => (get_module(&LIBRARY.global, "pdf").unwrap(), &["pdf"]), + Category::Html => (get_module(&LIBRARY.global, "html").unwrap(), &["html"]), + _ => (&LIBRARY.global, &[]), }; // Add groups. - for group in GROUPS.iter().filter(|g| g.category == category.name()).cloned() { + for group in GROUPS.iter().filter(|g| g.category == category).cloned() { if matches!(group.name.as_str(), "sym" | "emoji") { let subpage = symbols_page(resolver, &route, &group); let BodyModel::Symbols(model) = &subpage.body else { continue }; @@ -243,7 +242,7 @@ fn category_page(resolver: &dyn Resolver, category: Category) -> PageModel { items.push(CategoryItem { name: group.name.clone(), route: subpage.route.clone(), - oneliner: oneliner(category.docs()).into(), + oneliner: oneliner(docs).into(), code: true, }); children.push(subpage); @@ -256,15 +255,15 @@ fn category_page(resolver: &dyn Resolver, category: Category) -> PageModel { } // Add symbol pages. These are ordered manually. - if category == SYMBOLS { + if category == Category::Symbols { shorthands = Some(ShorthandsModel { markup, math }); } let mut skip = HashSet::new(); - if category == MATH { + if category == Category::Math { skip = GROUPS .iter() - .filter(|g| g.category == category.name()) + .filter(|g| g.category == category) .flat_map(|g| &g.filter) .map(|s| s.as_str()) .collect(); @@ -273,6 +272,11 @@ fn category_page(resolver: &dyn Resolver, category: Category) -> PageModel { skip.insert("text"); } + // Tiling would be duplicate otherwise. + if category == Category::Visualize { + skip.insert("pattern"); + } + // Add values and types. let scope = module.scope(); for (name, binding) in scope.iter() { @@ -287,8 +291,8 @@ fn category_page(resolver: &dyn Resolver, category: Category) -> PageModel { match binding.read() { Value::Func(func) => { let name = func.name().unwrap(); - - let subpage = func_page(resolver, &route, func, path); + let subpage = + func_page(resolver, &route, func, path, binding.deprecation()); items.push(CategoryItem { name: name.into(), route: subpage.route.clone(), @@ -311,31 +315,39 @@ fn category_page(resolver: &dyn Resolver, category: Category) -> PageModel { } } - if category != SYMBOLS { + if category != Category::Symbols { children.sort_by_cached_key(|child| child.title.clone()); items.sort_by_cached_key(|item| item.name.clone()); } - let name = category.title(); - let details = Html::markdown(resolver, category.docs(), Some(1)); + let title = EcoString::from(match category { + Category::Pdf | Category::Html | Category::Png | Category::Svg => { + category.name().to_uppercase() + } + _ => category.name().to_title_case(), + }); + + let details = Html::markdown(resolver, docs, Some(1)); let mut outline = vec![OutlineItem::from_name("Summary")]; outline.extend(details.outline()); - outline.push(OutlineItem::from_name("Definitions")); + if !items.is_empty() { + outline.push(OutlineItem::from_name("Definitions")); + } if shorthands.is_some() { outline.push(OutlineItem::from_name("Shorthands")); } PageModel { route, - title: name.into(), + title: title.clone(), description: eco_format!( - "Documentation for functions related to {name} in Typst." + "Documentation for functions related to {title} in Typst." ), part: None, outline, body: BodyModel::Category(CategoryModel { name: category.name(), - title: category.title(), + title, details, items, shorthands, @@ -344,14 +356,34 @@ fn category_page(resolver: &dyn Resolver, category: Category) -> PageModel { } } +/// Retrieve the docs for a category. +fn category_docs(category: Category) -> &'static str { + match category { + Category::Foundations => load!("reference/library/foundations.md"), + Category::Introspection => load!("reference/library/introspection.md"), + Category::Layout => load!("reference/library/layout.md"), + Category::DataLoading => load!("reference/library/data-loading.md"), + Category::Math => load!("reference/library/math.md"), + Category::Model => load!("reference/library/model.md"), + Category::Symbols => load!("reference/library/symbols.md"), + Category::Text => load!("reference/library/text.md"), + Category::Visualize => load!("reference/library/visualize.md"), + Category::Pdf => load!("reference/export/pdf.md"), + Category::Html => load!("reference/export/html.md"), + Category::Svg => load!("reference/export/svg.md"), + Category::Png => load!("reference/export/png.md"), + } +} + /// Create a page for a function. fn func_page( resolver: &dyn Resolver, parent: &str, func: &Func, path: &[&str], + deprecation: Option<&'static str>, ) -> PageModel { - let model = func_model(resolver, func, path, false); + let model = func_model(resolver, func, path, false, deprecation); let name = func.name().unwrap(); PageModel { route: eco_format!("{parent}{}/", urlify(name)), @@ -370,6 +402,7 @@ fn func_model( func: &Func, path: &[&str], nested: bool, + deprecation: Option<&'static str>, ) -> FuncModel { let name = func.name().unwrap(); let scope = func.scope().unwrap(); @@ -383,7 +416,11 @@ fn func_model( } let mut returns = vec![]; - casts(resolver, &mut returns, &mut vec![], func.returns().unwrap()); + let mut strings = vec![]; + casts(resolver, &mut returns, &mut strings, func.returns().unwrap()); + if !strings.is_empty() && !returns.contains(&"str") { + returns.push("str"); + } returns.sort_by_key(|ty| type_index(ty)); if returns == ["none"] { returns.clear(); @@ -401,6 +438,7 @@ fn func_model( oneliner: oneliner(details), element: func.element().is_some(), contextual: func.contextual().unwrap_or(false), + deprecation, details: Html::markdown(resolver, details, nesting), example: example.map(|md| Html::markdown(resolver, md, None)), self_, @@ -483,7 +521,7 @@ fn scope_models(resolver: &dyn Resolver, name: &str, scope: &Scope) -> Vec() else { panic!("not a function") }; - let func = func_model(resolver, func, &path, true); + let binding = group.module().scope().get(name).unwrap(); + let Ok(ref func) = binding.read().clone().cast::() else { + panic!("not a function") + }; + let func = func_model(resolver, func, &path, true, binding.deprecation()); let id_base = urlify(&eco_format!("functions-{}", func.name)); let children = func_outline(&func, &id_base); outline_items.push(OutlineItem { @@ -628,7 +668,7 @@ fn type_model(resolver: &dyn Resolver, ty: &Type) -> TypeModel { constructor: ty .constructor() .ok() - .map(|func| func_model(resolver, &func, &[], true)), + .map(|func| func_model(resolver, &func, &[], true, None)), scope: scope_models(resolver, ty.short_name(), ty.scope()), } } @@ -682,10 +722,19 @@ fn symbols_model(resolver: &dyn Resolver, group: &GroupData) -> SymbolsModel { list.iter().copied().find(|&(_, x)| x == c).map(|(s, _)| s) }; + let name = complete(variant); + let deprecation = match name.as_str() { + "integral.sect" => { + Some("`integral.sect` is deprecated, use `integral.inter` instead") + } + _ => binding.deprecation(), + }; + list.push(SymbolModel { - name: complete(variant), + name, markup_shorthand: shorthand(typst::syntax::ast::Shorthand::LIST), math_shorthand: shorthand(typst::syntax::ast::MathShorthand::LIST), + math_class: typst_utils::default_math_class(c).map(math_class_name), codepoint: c as _, accent: typst::math::Accent::combine(c).is_some(), alternates: symbol @@ -693,6 +742,7 @@ fn symbols_model(resolver: &dyn Resolver, group: &GroupData) -> SymbolsModel { .filter(|(other, _)| other != &variant) .map(|(other, _)| complete(other)) .collect(), + deprecation, }); } } @@ -769,12 +819,32 @@ const TYPE_ORDER: &[&str] = &[ "stroke", ]; +fn math_class_name(class: MathClass) -> &'static str { + match class { + MathClass::Normal => "Normal", + MathClass::Alphabetic => "Alphabetic", + MathClass::Binary => "Binary", + MathClass::Closing => "Closing", + MathClass::Diacritic => "Diacritic", + MathClass::Fence => "Fence", + MathClass::GlyphPart => "Glyph Part", + MathClass::Large => "Large", + MathClass::Opening => "Opening", + MathClass::Punctuation => "Punctuation", + MathClass::Relation => "Relation", + MathClass::Space => "Space", + MathClass::Unary => "Unary", + MathClass::Vary => "Vary", + MathClass::Special => "Special", + } +} + /// Data about a collection of functions. #[derive(Debug, Clone, Deserialize)] struct GroupData { name: EcoString, title: EcoString, - category: EcoString, + category: Category, #[serde(default)] path: Vec, #[serde(default)] diff --git a/docs/src/link.rs b/docs/src/link.rs index c55261b8..2e836b6c 100644 --- a/docs/src/link.rs +++ b/docs/src/link.rs @@ -44,6 +44,8 @@ fn resolve_known(head: &str, base: &str) -> Option { "$styling" => format!("{base}reference/styling"), "$scripting" => format!("{base}reference/scripting"), "$context" => format!("{base}reference/context"), + "$html" => format!("{base}reference/html"), + "$pdf" => format!("{base}reference/pdf"), "$guides" => format!("{base}guides"), "$changelog" => format!("{base}changelog"), "$universe" => "https://typst.app/universe".into(), @@ -73,11 +75,14 @@ fn resolve_definition(head: &str, base: &str) -> StrResult { // Handle grouped functions. if let Some(group) = GROUPS.iter().find(|group| { - group.category == category.name() && group.filter.iter().any(|func| func == name) + group.category == category && group.filter.iter().any(|func| func == name) }) { let mut route = format!( "{}reference/{}/{}/#functions-{}", - base, group.category, group.name, name + base, + group.category.name(), + group.name, + name ); if let Some(param) = parts.next() { route.push('-'); diff --git a/docs/src/model.rs b/docs/src/model.rs index b222322a..801c60c7 100644 --- a/docs/src/model.rs +++ b/docs/src/model.rs @@ -64,7 +64,7 @@ pub enum BodyModel { #[derive(Debug, Serialize)] pub struct CategoryModel { pub name: &'static str, - pub title: &'static str, + pub title: EcoString, pub details: Html, pub items: Vec, pub shorthands: Option, @@ -89,6 +89,7 @@ pub struct FuncModel { pub oneliner: &'static str, pub element: bool, pub contextual: bool, + pub deprecation: Option<&'static str>, pub details: Html, /// This example is only for nested function models. Others can have /// their example directly in their details. @@ -163,6 +164,8 @@ pub struct SymbolModel { pub alternates: Vec, pub markup_shorthand: Option<&'static str>, pub math_shorthand: Option<&'static str>, + pub math_class: Option<&'static str>, + pub deprecation: Option<&'static str>, } /// Shorthands listed on a category page. -- cgit v1.2.3