Fork of daniellemaywood.uk/gleam — Wasm codegen work
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('&', "&")
513 .replace('<', "<")
514 .replace('>', ">")
515 .replace('\"', """)
516 .replace('\'', "'")
517}
518
519#[test]
520fn escape_html_content_test() {
521 assert_eq!(
522 escape_html_content("&<>\"'".to_string()),
523 "&<>"'"
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}