Fork of daniellemaywood.uk/gleam — Wasm codegen work
2

Configure Feed

Select the types of activity you want to include in your feed.

gleam / compiler-core / src / docs.rs
28 kB 882 lines
1mod source_links; 2#[cfg(test)] 3mod tests; 4 5use std::time::SystemTime; 6 7use camino::Utf8PathBuf; 8 9use crate::{ 10 ast::{ 11 CustomType, Definition, Function, ModuleConstant, Publicity, TypeAlias, TypedDefinition, 12 }, 13 build::{Module, Package}, 14 config::{DocsPage, PackageConfig}, 15 docs::source_links::SourceLinker, 16 format, 17 io::{Content, FileSystemReader, OutputFile}, 18 package_interface::PackageInterface, 19 paths::ProjectPaths, 20 pretty, 21 type_::{self, Deprecation}, 22 version::COMPILER_VERSION, 23}; 24use askama::Template; 25use ecow::EcoString; 26use itertools::Itertools; 27use serde::Serialize; 28use serde_json::to_string as serde_to_string; 29 30const MAX_COLUMNS: isize = 65; 31 32#[derive(PartialEq, Eq, Copy, Clone, Debug)] 33pub enum DocContext { 34 HexPublish, 35 Build, 36} 37 38pub fn generate_html<IO: FileSystemReader>( 39 paths: &ProjectPaths, 40 config: &PackageConfig, 41 analysed: &[Module], 42 docs_pages: &[DocsPage], 43 fs: IO, 44 rendering_timestamp: SystemTime, 45 is_hex_publish: DocContext, 46) -> Vec<OutputFile> { 47 let modules = analysed 48 .iter() 49 .filter(|module| !module.is_test()) 50 .filter(|module| !config.is_internal_module(&module.name)); 51 52 let rendering_timestamp = rendering_timestamp 53 .duration_since(SystemTime::UNIX_EPOCH) 54 .expect("get current timestamp") 55 .as_secs() 56 .to_string(); 57 58 // Define user-supplied (or README) pages 59 let pages: Vec<_> = docs_pages 60 .iter() 61 .map(|page| Link { 62 name: page.title.to_string(), 63 path: page.path.to_string(), 64 }) 65 .collect(); 66 67 let doc_links = config.links.iter().map(|doc_link| Link { 68 name: doc_link.title.to_string(), 69 path: doc_link.href.to_string(), 70 }); 71 72 let repo_link = config.repository.url().map(|path| Link { 73 name: "Repository".into(), 74 path, 75 }); 76 77 let host = if is_hex_publish == DocContext::HexPublish { 78 "https://hexdocs.pm" 79 } else { 80 "" 81 }; 82 83 // https://github.com/gleam-lang/gleam/issues/3020 84 let links: Vec<_> = match is_hex_publish { 85 DocContext::HexPublish => doc_links 86 .chain(repo_link) 87 .chain([Link { 88 name: "Hex".into(), 89 path: format!("https://hex.pm/packages/{0}", config.name).to_string(), 90 }]) 91 .collect(), 92 DocContext::Build => doc_links.chain(repo_link).collect(), 93 }; 94 95 let mut files = vec![]; 96 97 let mut search_items = vec![]; 98 99 let modules_links: Vec<_> = modules 100 .clone() 101 .map(|m| { 102 let path = [&m.name, ".html"].concat(); 103 Link { 104 path, 105 name: m.name.split('/').join("<wbr />/"), 106 } 107 }) 108 .sorted() 109 .collect(); 110 111 // Generate user-supplied (or README) pages 112 for page in docs_pages { 113 let content = fs.read(&page.source).unwrap_or_default(); 114 let rendered_content = render_markdown(&content, MarkdownSource::Standalone); 115 let unnest = page_unnest(&page.path); 116 117 let page_path_without_ext = page.path.split('.').next().unwrap_or(""); 118 let page_title = match page_path_without_ext { 119 // The index page, such as README, should not push it's page title 120 "index" => format!("{} · v{}", config.name, config.version), 121 // Other page title's should say so 122 _other => format!("{} · {} · v{}", page.title, config.name, config.version), 123 }; 124 let page_meta_description = match page_path_without_ext { 125 "index" => config.description.to_string().clone(), 126 _other => "".to_owned(), 127 }; 128 let path = Utf8PathBuf::from(&page.path); 129 130 let temp = PageTemplate { 131 gleam_version: COMPILER_VERSION, 132 links: &links, 133 pages: &pages, 134 modules: &modules_links, 135 project_name: &config.name, 136 page_title: &page_title, 137 page_meta_description: &page_meta_description, 138 file_path: &path.clone(), 139 project_version: &config.version.to_string(), 140 content: rendered_content, 141 rendering_timestamp: &rendering_timestamp, 142 host, 143 unnest: &unnest, 144 }; 145 146 files.push(OutputFile { 147 path, 148 content: Content::Text(temp.render().expect("Page template rendering")), 149 }); 150 151 search_items.push(SearchItem { 152 type_: SearchItemType::Page, 153 parent_title: config.name.to_string(), 154 title: config.name.to_string(), 155 content, 156 reference: page.path.to_string(), 157 }) 158 } 159 160 // Generate module documentation pages 161 for module in modules { 162 let name = module.name.clone(); 163 let unnest = page_unnest(&module.name); 164 165 // Read module src & create line number lookup structure 166 let source_links = SourceLinker::new(paths, config, module); 167 168 let documentation_content = module.ast.documentation.iter().join("\n"); 169 let rendered_documentation = 170 render_markdown(&documentation_content.clone(), MarkdownSource::Comment); 171 172 let functions: Vec<DocsFunction<'_>> = module 173 .ast 174 .definitions 175 .iter() 176 .filter(|statement| !statement.is_internal()) 177 .flat_map(|statement| function(&source_links, statement)) 178 .sorted() 179 .collect(); 180 181 let types: Vec<Type<'_>> = module 182 .ast 183 .definitions 184 .iter() 185 .filter(|statement| !statement.is_internal()) 186 .flat_map(|statement| type_(&source_links, statement)) 187 .sorted() 188 .collect(); 189 190 let constants: Vec<Constant<'_>> = module 191 .ast 192 .definitions 193 .iter() 194 .filter(|statement| !statement.is_internal()) 195 .flat_map(|statement| constant(&source_links, statement)) 196 .sorted() 197 .collect(); 198 199 types.iter().for_each(|type_| { 200 let constructors = type_ 201 .constructors 202 .iter() 203 .map(|constructor| { 204 let arguments = constructor 205 .arguments 206 .iter() 207 .map(|argument| format!("{}\n{}", argument.name, argument.doc)) 208 .join("\n"); 209 210 format!( 211 "{}\n{}\n{}", 212 constructor.definition, constructor.text_documentation, arguments 213 ) 214 }) 215 .join("\n"); 216 217 search_items.push(SearchItem { 218 type_: SearchItemType::Type, 219 parent_title: module.name.to_string(), 220 title: type_.name.to_string(), 221 content: format!( 222 "{}\n{}\n{}\n{}", 223 type_.definition, 224 type_.text_documentation, 225 constructors, 226 import_synonyms(&module.name, type_.name) 227 ), 228 reference: format!("{}.html#{}", module.name, type_.name), 229 }) 230 }); 231 constants.iter().for_each(|constant| { 232 search_items.push(SearchItem { 233 type_: SearchItemType::Constant, 234 parent_title: module.name.to_string(), 235 title: constant.name.to_string(), 236 content: format!( 237 "{}\n{}\n{}", 238 constant.definition, 239 constant.text_documentation, 240 import_synonyms(&module.name, constant.name) 241 ), 242 reference: format!("{}.html#{}", module.name, constant.name), 243 }) 244 }); 245 functions.iter().for_each(|function| { 246 search_items.push(SearchItem { 247 type_: SearchItemType::Function, 248 parent_title: module.name.to_string(), 249 title: function.name.to_string(), 250 content: format!( 251 "{}\n{}\n{}", 252 function.signature, 253 function.text_documentation, 254 import_synonyms(&module.name, function.name) 255 ), 256 reference: format!("{}.html#{}", module.name, function.name), 257 }) 258 }); 259 search_items.push(SearchItem { 260 type_: SearchItemType::Module, 261 parent_title: module.name.to_string(), 262 title: module.name.to_string(), 263 content: documentation_content, 264 reference: format!("{}.html", module.name), 265 }); 266 267 let page_title = format!("{} · {} · v{}", name, config.name, config.version); 268 let page_meta_description = ""; 269 let path = Utf8PathBuf::from(format!("{}.html", module.name)); 270 271 let template = ModuleTemplate { 272 gleam_version: COMPILER_VERSION, 273 host, 274 unnest, 275 links: &links, 276 pages: &pages, 277 documentation: rendered_documentation, 278 modules: &modules_links, 279 project_name: &config.name, 280 page_title: &page_title, 281 page_meta_description, 282 module_name: EcoString::from(&name), 283 file_path: &path.clone(), 284 project_version: &config.version.to_string(), 285 functions, 286 types, 287 constants, 288 rendering_timestamp: &rendering_timestamp, 289 }; 290 291 files.push(OutputFile { 292 path, 293 content: Content::Text( 294 template 295 .render() 296 .expect("Module documentation template rendering"), 297 ), 298 }); 299 } 300 301 // Render static assets 302 303 files.push(OutputFile { 304 path: Utf8PathBuf::from("css/atom-one-light.min.css"), 305 content: Content::Text( 306 std::include_str!("../templates/docs-css/atom-one-light.min.css").to_string(), 307 ), 308 }); 309 310 files.push(OutputFile { 311 path: Utf8PathBuf::from("css/atom-one-dark.min.css"), 312 content: Content::Text( 313 std::include_str!("../templates/docs-css/atom-one-dark.min.css").to_string(), 314 ), 315 }); 316 317 files.push(OutputFile { 318 path: Utf8PathBuf::from("css/index.css"), 319 content: Content::Text(std::include_str!("../templates/docs-css/index.css").to_string()), 320 }); 321 322 // highlightjs: 323 324 files.push(OutputFile { 325 path: Utf8PathBuf::from("js/highlight.min.js"), 326 content: Content::Text( 327 std::include_str!("../templates/docs-js/highlight.min.js").to_string(), 328 ), 329 }); 330 331 files.push(OutputFile { 332 path: Utf8PathBuf::from("js/highlightjs-gleam.js"), 333 content: Content::Text( 334 std::include_str!("../templates/docs-js/highlightjs-gleam.js").to_string(), 335 ), 336 }); 337 338 files.push(OutputFile { 339 path: Utf8PathBuf::from("js/highlightjs-erlang.min.js"), 340 content: Content::Text( 341 std::include_str!("../templates/docs-js/highlightjs-erlang.min.js").to_string(), 342 ), 343 }); 344 345 files.push(OutputFile { 346 path: Utf8PathBuf::from("js/highlightjs-elixir.min.js"), 347 content: Content::Text( 348 std::include_str!("../templates/docs-js/highlightjs-elixir.min.js").to_string(), 349 ), 350 }); 351 352 files.push(OutputFile { 353 path: Utf8PathBuf::from("js/highlightjs-javascript.min.js"), 354 content: Content::Text( 355 std::include_str!("../templates/docs-js/highlightjs-javascript.min.js").to_string(), 356 ), 357 }); 358 359 files.push(OutputFile { 360 path: Utf8PathBuf::from("js/highlightjs-typescript.min.js"), 361 content: Content::Text( 362 std::include_str!("../templates/docs-js/highlightjs-typescript.min.js").to_string(), 363 ), 364 }); 365 366 // lunr.min.js, search_data.json and index.js 367 368 files.push(OutputFile { 369 path: Utf8PathBuf::from("js/lunr.min.js"), 370 content: Content::Text(std::include_str!("../templates/docs-js/lunr.min.js").to_string()), 371 }); 372 373 let search_data_json = serde_to_string(&SearchData { 374 items: escape_html_contents(search_items), 375 programming_language: SearchProgrammingLanguage::Gleam, 376 }) 377 .expect("search index serialization"); 378 379 files.push(OutputFile { 380 path: Utf8PathBuf::from("search_data.json"), 381 content: Content::Text(search_data_json.to_string()), 382 }); 383 384 files.push(OutputFile { 385 path: Utf8PathBuf::from("js/index.js"), 386 content: Content::Text(std::include_str!("../templates/docs-js/index.js").to_string()), 387 }); 388 389 // web fonts: 390 391 files.push(OutputFile { 392 path: Utf8PathBuf::from("fonts/karla-v23-regular-latin-ext.woff2"), 393 content: Content::Binary( 394 include_bytes!("../templates/docs-fonts/karla-v23-regular-latin-ext.woff2").to_vec(), 395 ), 396 }); 397 398 files.push(OutputFile { 399 path: Utf8PathBuf::from("fonts/karla-v23-regular-latin.woff2"), 400 content: Content::Binary( 401 include_bytes!("../templates/docs-fonts/karla-v23-regular-latin.woff2").to_vec(), 402 ), 403 }); 404 405 files.push(OutputFile { 406 path: Utf8PathBuf::from("fonts/karla-v23-bold-latin-ext.woff2"), 407 content: Content::Binary( 408 include_bytes!("../templates/docs-fonts/karla-v23-bold-latin-ext.woff2").to_vec(), 409 ), 410 }); 411 412 files.push(OutputFile { 413 path: Utf8PathBuf::from("fonts/karla-v23-bold-latin.woff2"), 414 content: Content::Binary( 415 include_bytes!("../templates/docs-fonts/karla-v23-bold-latin.woff2").to_vec(), 416 ), 417 }); 418 419 files.push(OutputFile { 420 path: Utf8PathBuf::from("fonts/ubuntu-mono-v15-regular-cyrillic-ext.woff2"), 421 content: Content::Binary( 422 include_bytes!("../templates/docs-fonts/ubuntu-mono-v15-regular-cyrillic-ext.woff2") 423 .to_vec(), 424 ), 425 }); 426 427 files.push(OutputFile { 428 path: Utf8PathBuf::from("fonts/ubuntu-mono-v15-regular-cyrillic.woff2"), 429 content: Content::Binary( 430 include_bytes!("../templates/docs-fonts/ubuntu-mono-v15-regular-cyrillic.woff2") 431 .to_vec(), 432 ), 433 }); 434 435 files.push(OutputFile { 436 path: Utf8PathBuf::from("fonts/ubuntu-mono-v15-regular-greek-ext.woff2"), 437 content: Content::Binary( 438 include_bytes!("../templates/docs-fonts/ubuntu-mono-v15-regular-greek-ext.woff2") 439 .to_vec(), 440 ), 441 }); 442 443 files.push(OutputFile { 444 path: Utf8PathBuf::from("fonts/ubuntu-mono-v15-regular-greek.woff2"), 445 content: Content::Binary( 446 include_bytes!("../templates/docs-fonts/ubuntu-mono-v15-regular-greek.woff2").to_vec(), 447 ), 448 }); 449 450 files.push(OutputFile { 451 path: Utf8PathBuf::from("fonts/ubuntu-mono-v15-regular-latin-ext.woff2"), 452 content: Content::Binary( 453 include_bytes!("../templates/docs-fonts/ubuntu-mono-v15-regular-latin-ext.woff2") 454 .to_vec(), 455 ), 456 }); 457 458 files.push(OutputFile { 459 path: Utf8PathBuf::from("fonts/ubuntu-mono-v15-regular-latin.woff2"), 460 content: Content::Binary( 461 include_bytes!("../templates/docs-fonts/ubuntu-mono-v15-regular-latin.woff2").to_vec(), 462 ), 463 }); 464 465 files 466} 467 468pub fn generate_json_package_interface( 469 path: Utf8PathBuf, 470 package: &Package, 471 cached_modules: &im::HashMap<EcoString, type_::ModuleInterface>, 472) -> OutputFile { 473 OutputFile { 474 path, 475 content: Content::Text( 476 serde_json::to_string(&PackageInterface::from_package(package, cached_modules)) 477 .expect("JSON module interface serialisation"), 478 ), 479 } 480} 481 482fn page_unnest(path: &str) -> String { 483 let unnest = path 484 .strip_prefix('/') 485 .unwrap_or(path) 486 .split('/') 487 .skip(1) 488 .map(|_| "..") 489 .join("/"); 490 if unnest.is_empty() { 491 ".".into() 492 } else { 493 unnest 494 } 495} 496 497#[test] 498fn page_unnest_test() { 499 // Pages 500 assert_eq!(page_unnest("wibble.html"), "."); 501 assert_eq!(page_unnest("/wibble.html"), "."); 502 assert_eq!(page_unnest("/wibble/woo.html"), ".."); 503 assert_eq!(page_unnest("/wibble/wobble/woo.html"), "../.."); 504 505 // Modules 506 assert_eq!(page_unnest("string"), "."); 507 assert_eq!(page_unnest("gleam/string"), ".."); 508 assert_eq!(page_unnest("gleam/string/inspect"), "../.."); 509} 510 511fn escape_html_content(it: String) -> String { 512 it.replace('&', "&amp;") 513 .replace('<', "&lt;") 514 .replace('>', "&gt;") 515 .replace('\"', "&quot;") 516 .replace('\'', "&#39;") 517} 518 519#[test] 520fn escape_html_content_test() { 521 assert_eq!( 522 escape_html_content("&<>\"'".to_string()), 523 "&amp;&lt;&gt;&quot;&#39;" 524 ); 525} 526 527fn escape_html_contents(indexes: Vec<SearchItem>) -> Vec<SearchItem> { 528 indexes 529 .into_iter() 530 .map(|idx| SearchItem { 531 type_: idx.type_, 532 parent_title: idx.parent_title, 533 title: idx.title, 534 content: escape_html_content(idx.content), 535 reference: idx.reference, 536 }) 537 .collect::<Vec<SearchItem>>() 538} 539 540fn import_synonyms(parent: &str, child: &str) -> String { 541 format!("Synonyms:\n{parent}.{child}\n{parent} {child}") 542} 543 544fn function<'a>( 545 source_links: &SourceLinker, 546 statement: &'a TypedDefinition, 547) -> Option<DocsFunction<'a>> { 548 let mut formatter = format::Formatter::new(); 549 550 match statement { 551 Definition::Function(Function { 552 publicity: Publicity::Public, 553 name, 554 documentation: doc, 555 arguments: args, 556 return_type: ret, 557 location, 558 deprecation, 559 .. 560 }) => { 561 let (_, name) = name 562 .as_ref() 563 .expect("Function in a definition must be named"); 564 565 Some(DocsFunction { 566 name, 567 documentation: markdown_documentation(doc), 568 text_documentation: text_documentation(doc), 569 signature: print( 570 formatter 571 .docs_fn_signature(Publicity::Public, name, args, ret.clone(), location) 572 .group(), 573 ), 574 source_url: source_links.url(*location), 575 deprecation_message: match deprecation { 576 Deprecation::NotDeprecated => "".to_string(), 577 Deprecation::Deprecated { message } => message.to_string(), 578 }, 579 }) 580 } 581 582 _ => None, 583 } 584} 585 586fn text_documentation(doc: &Option<(u32, EcoString)>) -> String { 587 let raw_text = doc 588 .as_ref() 589 .map(|(_, it)| it.to_string()) 590 .unwrap_or_else(|| "".into()); 591 592 // TODO: parse markdown properly and extract the text nodes 593 raw_text.replace("```gleam", "").replace("```", "") 594} 595 596fn markdown_documentation(doc: &Option<(u32, EcoString)>) -> String { 597 doc.as_ref() 598 .map(|(_, doc)| render_markdown(doc, MarkdownSource::Comment)) 599 .unwrap_or_default() 600} 601 602/// An enum to represent the source of a Markdown string to render. 603enum MarkdownSource { 604 /// A Markdown string that comes from the documentation of a 605 /// definition/module. This means that each line is going to be preceded by 606 /// a whitespace. 607 Comment, 608 /// A Markdown string coming from a standalone file like a README.md. 609 Standalone, 610} 611 612fn render_markdown(text: &str, source: MarkdownSource) -> String { 613 let text = match source { 614 MarkdownSource::Standalone => text.into(), 615 // Doc comments start with "///\s", which can confuse the markdown parser 616 // and prevent tables from rendering correctly, so remove that first space. 617 MarkdownSource::Comment => text 618 .split('\n') 619 .map(|s| s.strip_prefix(' ').unwrap_or(s)) 620 .join("\n"), 621 }; 622 623 let mut s = String::with_capacity(text.len() * 3 / 2); 624 let p = pulldown_cmark::Parser::new_ext(&text, pulldown_cmark::Options::all()); 625 pulldown_cmark::html::push_html(&mut s, p); 626 s 627} 628 629fn type_<'a>(source_links: &SourceLinker, statement: &'a TypedDefinition) -> Option<Type<'a>> { 630 let mut formatter = format::Formatter::new(); 631 632 match statement { 633 Definition::CustomType(ct) if ct.publicity.is_importable() && !ct.opaque => Some(Type { 634 name: &ct.name, 635 // TODO: Don't use the same printer for docs as for the formatter. 636 // We are not interested in showing the exact implementation in the 637 // documentation and we could add things like colours, etc. 638 definition: print(formatter.custom_type(ct)), 639 documentation: markdown_documentation(&ct.documentation), 640 text_documentation: text_documentation(&ct.documentation), 641 deprecation_message: match &ct.deprecation { 642 Deprecation::NotDeprecated => "".to_string(), 643 Deprecation::Deprecated { message } => message.to_string(), 644 }, 645 constructors: ct 646 .constructors 647 .iter() 648 .map(|constructor| TypeConstructor { 649 definition: print(formatter.record_constructor(constructor)), 650 documentation: markdown_documentation(&constructor.documentation), 651 text_documentation: text_documentation(&constructor.documentation), 652 arguments: constructor 653 .arguments 654 .iter() 655 .filter_map(|arg| arg.label.as_ref().map(|(_, label)| (arg, label))) 656 .map(|(argument, label)| TypeConstructorArg { 657 name: label.trim_end().to_string(), 658 doc: markdown_documentation(&argument.doc), 659 }) 660 .filter(|arg| !arg.doc.is_empty()) 661 .collect(), 662 }) 663 .collect(), 664 source_url: source_links.url(ct.location), 665 opaque: ct.opaque, 666 }), 667 668 Definition::CustomType(CustomType { 669 publicity: Publicity::Public, 670 opaque: true, 671 name, 672 parameters, 673 documentation: doc, 674 location, 675 deprecation, 676 .. 677 }) => Some(Type { 678 name, 679 definition: print( 680 formatter 681 .docs_opaque_custom_type(Publicity::Public, name, parameters, location) 682 .group(), 683 ), 684 documentation: markdown_documentation(doc), 685 text_documentation: text_documentation(doc), 686 constructors: vec![], 687 source_url: source_links.url(*location), 688 deprecation_message: match deprecation { 689 Deprecation::NotDeprecated => "".to_string(), 690 Deprecation::Deprecated { message } => message.to_string(), 691 }, 692 opaque: true, 693 }), 694 695 Definition::TypeAlias(TypeAlias { 696 publicity: Publicity::Public, 697 alias: name, 698 type_ast: type_, 699 documentation: doc, 700 parameters: args, 701 location, 702 deprecation, 703 .. 704 }) => Some(Type { 705 name, 706 definition: print( 707 formatter 708 .type_alias(Publicity::Public, name, args, type_, deprecation, location) 709 .group(), 710 ), 711 documentation: markdown_documentation(doc), 712 text_documentation: text_documentation(doc), 713 constructors: vec![], 714 source_url: source_links.url(*location), 715 deprecation_message: match deprecation { 716 Deprecation::NotDeprecated => "".to_string(), 717 Deprecation::Deprecated { message } => message.to_string(), 718 }, 719 opaque: false, 720 }), 721 722 _ => None, 723 } 724} 725 726fn constant<'a>( 727 source_links: &SourceLinker, 728 statement: &'a TypedDefinition, 729) -> Option<Constant<'a>> { 730 let mut formatter = format::Formatter::new(); 731 match statement { 732 Definition::ModuleConstant(ModuleConstant { 733 publicity: Publicity::Public, 734 documentation: doc, 735 name, 736 value, 737 location, 738 .. 739 }) => Some(Constant { 740 name, 741 definition: print(formatter.docs_const_expr(Publicity::Public, name, value)), 742 documentation: markdown_documentation(doc), 743 text_documentation: text_documentation(doc), 744 source_url: source_links.url(*location), 745 }), 746 747 _ => None, 748 } 749} 750 751fn print(doc: pretty::Document<'_>) -> String { 752 doc.to_pretty_string(MAX_COLUMNS) 753} 754 755#[derive(PartialEq, Eq, PartialOrd, Ord, Clone)] 756struct Link { 757 name: String, 758 path: String, 759} 760 761#[derive(PartialEq, Eq, PartialOrd, Ord)] 762struct DocsFunction<'a> { 763 name: &'a str, 764 signature: String, 765 documentation: String, 766 text_documentation: String, 767 source_url: String, 768 deprecation_message: String, 769} 770 771#[derive(PartialEq, Eq, PartialOrd, Ord, Debug)] 772struct TypeConstructor { 773 definition: String, 774 documentation: String, 775 text_documentation: String, 776 arguments: Vec<TypeConstructorArg>, 777} 778 779#[derive(PartialEq, Eq, PartialOrd, Ord, Debug)] 780struct TypeConstructorArg { 781 name: String, 782 doc: String, 783} 784 785#[derive(PartialEq, Eq, PartialOrd, Ord, Debug)] 786struct Type<'a> { 787 name: &'a str, 788 definition: String, 789 documentation: String, 790 constructors: Vec<TypeConstructor>, 791 text_documentation: String, 792 source_url: String, 793 deprecation_message: String, 794 opaque: bool, 795} 796 797#[derive(PartialEq, Eq, PartialOrd, Ord)] 798struct Constant<'a> { 799 name: &'a str, 800 definition: String, 801 documentation: String, 802 text_documentation: String, 803 source_url: String, 804} 805 806#[derive(Template)] 807#[template(path = "documentation_page.html")] 808struct PageTemplate<'a> { 809 gleam_version: &'a str, 810 unnest: &'a str, 811 host: &'a str, 812 page_title: &'a str, 813 page_meta_description: &'a str, 814 file_path: &'a Utf8PathBuf, 815 project_name: &'a str, 816 project_version: &'a str, 817 pages: &'a [Link], 818 links: &'a [Link], 819 modules: &'a [Link], 820 content: String, 821 rendering_timestamp: &'a str, 822} 823 824#[derive(Template)] 825#[template(path = "documentation_module.html")] 826struct ModuleTemplate<'a> { 827 gleam_version: &'a str, 828 unnest: String, 829 host: &'a str, 830 page_title: &'a str, 831 page_meta_description: &'a str, 832 file_path: &'a Utf8PathBuf, 833 module_name: EcoString, 834 project_name: &'a str, 835 project_version: &'a str, 836 pages: &'a [Link], 837 links: &'a [Link], 838 modules: &'a [Link], 839 functions: Vec<DocsFunction<'a>>, 840 types: Vec<Type<'a>>, 841 constants: Vec<Constant<'a>>, 842 documentation: String, 843 rendering_timestamp: &'a str, 844} 845 846#[derive(Serialize, PartialEq, Eq, PartialOrd, Ord, Clone)] 847struct SearchData { 848 items: Vec<SearchItem>, 849 #[serde(rename = "proglang")] 850 programming_language: SearchProgrammingLanguage, 851} 852 853#[derive(Serialize, PartialEq, Eq, PartialOrd, Ord, Clone)] 854struct SearchItem { 855 #[serde(rename = "type")] 856 type_: SearchItemType, 857 #[serde(rename = "parentTitle")] 858 parent_title: String, 859 title: String, 860 #[serde(rename = "doc")] 861 content: String, 862 #[serde(rename = "ref")] 863 reference: String, 864} 865 866#[derive(Serialize, PartialEq, Eq, PartialOrd, Ord, Clone)] 867#[serde(rename_all = "lowercase")] 868enum SearchItemType { 869 Constant, 870 Function, 871 Module, 872 Page, 873 Type, 874} 875 876#[derive(Serialize, PartialEq, Eq, PartialOrd, Ord, Clone)] 877#[serde(rename_all = "lowercase")] 878enum SearchProgrammingLanguage { 879 // Elixir, 880 // Erlang, 881 Gleam, 882}