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
24 kB 787 lines
1mod printer; 2mod source_links; 3#[cfg(test)] 4mod tests; 5 6use std::{collections::HashMap, time::SystemTime}; 7 8use camino::Utf8PathBuf; 9use hexpm::version::Version; 10use printer::Printer; 11 12use crate::{ 13 build::{Module, Package}, 14 config::{DocsPage, PackageConfig}, 15 docs::source_links::SourceLinker, 16 io::{Content, FileSystemReader, OutputFile}, 17 package_interface::PackageInterface, 18 paths::ProjectPaths, 19 type_::{self}, 20 version::COMPILER_VERSION, 21}; 22use askama::Template; 23use ecow::EcoString; 24use itertools::Itertools; 25use serde::{Deserialize, Serialize}; 26use serde_json::to_string as serde_to_string; 27 28#[derive(PartialEq, Eq, Copy, Clone, Debug)] 29pub enum DocContext { 30 HexPublish, 31 Build, 32} 33 34#[derive(PartialEq, Debug, Serialize, Deserialize)] 35pub struct PackageInformation { 36 #[serde(rename = "gleam.toml")] 37 package_config: PackageConfig, 38} 39 40/// Like `ManifestPackage`, but lighter and cheaper to clone as it is all that 41/// we need for printing documentation. 42#[derive(Debug, Clone)] 43pub struct Dependency { 44 pub version: Version, 45 pub kind: DependencyKind, 46} 47 48#[derive(Debug, Clone, Copy)] 49pub enum DependencyKind { 50 Hex, 51 Path, 52 Git, 53} 54 55#[derive(Debug)] 56pub struct DocumentationConfig<'a> { 57 pub package_config: &'a PackageConfig, 58 pub dependencies: HashMap<EcoString, Dependency>, 59 pub analysed: &'a [Module], 60 pub docs_pages: &'a [DocsPage], 61 pub rendering_timestamp: SystemTime, 62 pub context: DocContext, 63} 64 65pub fn generate_html<IO: FileSystemReader>( 66 paths: &ProjectPaths, 67 config: DocumentationConfig<'_>, 68 fs: IO, 69) -> Vec<OutputFile> { 70 let DocumentationConfig { 71 package_config: config, 72 dependencies, 73 analysed, 74 docs_pages, 75 rendering_timestamp, 76 context: is_hex_publish, 77 } = config; 78 79 let modules = analysed 80 .iter() 81 .filter(|module| module.origin.is_src()) 82 .filter(|module| !config.is_internal_module(&module.name)); 83 84 let rendering_timestamp = rendering_timestamp 85 .duration_since(SystemTime::UNIX_EPOCH) 86 .expect("get current timestamp") 87 .as_secs() 88 .to_string(); 89 90 // Define user-supplied (or README) pages 91 let pages: Vec<_> = docs_pages 92 .iter() 93 .map(|page| Link { 94 name: page.title.to_string(), 95 path: page.path.to_string(), 96 }) 97 .collect(); 98 99 let doc_links = config.links.iter().map(|doc_link| Link { 100 name: doc_link.title.to_string(), 101 path: doc_link.href.to_string(), 102 }); 103 104 let repo_link = config 105 .repository 106 .as_ref() 107 .map(|r| r.url()) 108 .map(|path| Link { 109 name: "Repository".into(), 110 path, 111 }); 112 113 let host = if is_hex_publish == DocContext::HexPublish { 114 "https://hexdocs.pm" 115 } else { 116 "" 117 }; 118 119 // https://github.com/gleam-lang/gleam/issues/3020 120 let links: Vec<_> = match is_hex_publish { 121 DocContext::HexPublish => doc_links 122 .chain(repo_link) 123 .chain([Link { 124 name: "Hex".into(), 125 path: format!("https://hex.pm/packages/{0}", config.name).to_string(), 126 }]) 127 .collect(), 128 DocContext::Build => doc_links.chain(repo_link).collect(), 129 }; 130 131 let mut files = vec![]; 132 133 let mut search_items = vec![]; 134 135 let modules_links: Vec<_> = modules 136 .clone() 137 .map(|m| { 138 let path = [&m.name, ".html"].concat(); 139 Link { 140 path, 141 name: m.name.split('/').join("<wbr />/"), 142 } 143 }) 144 .sorted() 145 .collect(); 146 147 // Generate user-supplied (or README) pages 148 for page in docs_pages { 149 let content = fs.read(&page.source).unwrap_or_default(); 150 let rendered_content = render_markdown(&content, MarkdownSource::Standalone); 151 let unnest = page_unnest(&page.path); 152 153 let page_path_without_ext = page.path.split('.').next().unwrap_or(""); 154 let page_title = match page_path_without_ext { 155 // The index page, such as README, should not push it's page title 156 "index" => format!("{} · v{}", config.name, config.version), 157 // Other page title's should say so 158 _other => format!("{} · {} · v{}", page.title, config.name, config.version), 159 }; 160 let page_meta_description = match page_path_without_ext { 161 "index" => config.description.to_string().clone(), 162 _other => "".to_owned(), 163 }; 164 let path = Utf8PathBuf::from(&page.path); 165 166 let temp = PageTemplate { 167 gleam_version: COMPILER_VERSION, 168 links: &links, 169 pages: &pages, 170 modules: &modules_links, 171 project_name: &config.name, 172 page_title: &page_title, 173 page_meta_description: &page_meta_description, 174 file_path: &path.clone(), 175 project_version: &config.version.to_string(), 176 content: rendered_content, 177 rendering_timestamp: &rendering_timestamp, 178 host, 179 unnest: &unnest, 180 }; 181 182 files.push(OutputFile { 183 path, 184 content: Content::Text(temp.render().expect("Page template rendering")), 185 }); 186 187 search_items.push(search_item_for_page(&config.name, &page.path, content)) 188 } 189 190 // Generate module documentation pages 191 for module in modules { 192 let name = module.name.clone(); 193 let unnest = page_unnest(&module.name); 194 195 // Read module src & create line number lookup structure 196 let source_links = SourceLinker::new(paths, config, module); 197 198 let documentation_content = module.ast.documentation.iter().join("\n"); 199 let rendered_documentation = 200 render_markdown(&documentation_content, MarkdownSource::Comment); 201 202 let mut printer = Printer::new( 203 module.ast.type_info.package.clone(), 204 module.name.clone(), 205 &module.ast.names, 206 &dependencies, 207 ); 208 209 let types = printer.type_definitions(&source_links, &module.ast.definitions); 210 let values = printer.value_definitions(&source_links, &module.ast.definitions); 211 212 types 213 .iter() 214 .for_each(|type_| search_items.push(search_item_for_type(&module.name, type_))); 215 values 216 .iter() 217 .for_each(|value| search_items.push(search_item_for_value(&module.name, value))); 218 219 search_items.push(search_item_for_module(module)); 220 221 let page_title = format!("{} · {} · v{}", name, config.name, config.version); 222 let page_meta_description = ""; 223 let path = Utf8PathBuf::from(format!("{}.html", module.name)); 224 225 let template = ModuleTemplate { 226 gleam_version: COMPILER_VERSION, 227 host, 228 unnest, 229 links: &links, 230 pages: &pages, 231 documentation: rendered_documentation, 232 modules: &modules_links, 233 project_name: &config.name, 234 page_title: &page_title, 235 page_meta_description, 236 module_name: EcoString::from(&name), 237 file_path: &path.clone(), 238 project_version: &config.version.to_string(), 239 types, 240 values, 241 rendering_timestamp: &rendering_timestamp, 242 }; 243 244 files.push(OutputFile { 245 path, 246 content: Content::Text( 247 template 248 .render() 249 .expect("Module documentation template rendering"), 250 ), 251 }); 252 } 253 254 // Render static assets 255 256 files.push(OutputFile { 257 path: Utf8PathBuf::from("css/atom-one-light.min.css"), 258 content: Content::Text( 259 std::include_str!("../templates/docs-css/atom-one-light.min.css").to_string(), 260 ), 261 }); 262 263 files.push(OutputFile { 264 path: Utf8PathBuf::from("css/atom-one-dark.min.css"), 265 content: Content::Text( 266 std::include_str!("../templates/docs-css/atom-one-dark.min.css").to_string(), 267 ), 268 }); 269 270 files.push(OutputFile { 271 path: Utf8PathBuf::from("css/index.css"), 272 content: Content::Text(std::include_str!("../templates/docs-css/index.css").to_string()), 273 }); 274 275 // highlightjs: 276 277 files.push(OutputFile { 278 path: Utf8PathBuf::from("js/highlight.min.js"), 279 content: Content::Text( 280 std::include_str!("../templates/docs-js/highlight.min.js").to_string(), 281 ), 282 }); 283 284 files.push(OutputFile { 285 path: Utf8PathBuf::from("js/highlightjs-gleam.js"), 286 content: Content::Text( 287 std::include_str!("../templates/docs-js/highlightjs-gleam.js").to_string(), 288 ), 289 }); 290 291 files.push(OutputFile { 292 path: Utf8PathBuf::from("js/highlightjs-erlang.min.js"), 293 content: Content::Text( 294 std::include_str!("../templates/docs-js/highlightjs-erlang.min.js").to_string(), 295 ), 296 }); 297 298 files.push(OutputFile { 299 path: Utf8PathBuf::from("js/highlightjs-elixir.min.js"), 300 content: Content::Text( 301 std::include_str!("../templates/docs-js/highlightjs-elixir.min.js").to_string(), 302 ), 303 }); 304 305 files.push(OutputFile { 306 path: Utf8PathBuf::from("js/highlightjs-javascript.min.js"), 307 content: Content::Text( 308 std::include_str!("../templates/docs-js/highlightjs-javascript.min.js").to_string(), 309 ), 310 }); 311 312 files.push(OutputFile { 313 path: Utf8PathBuf::from("js/highlightjs-typescript.min.js"), 314 content: Content::Text( 315 std::include_str!("../templates/docs-js/highlightjs-typescript.min.js").to_string(), 316 ), 317 }); 318 319 // lunr.min.js, search-data.json and index.js 320 321 files.push(OutputFile { 322 path: Utf8PathBuf::from("js/lunr.min.js"), 323 content: Content::Text(std::include_str!("../templates/docs-js/lunr.min.js").to_string()), 324 }); 325 326 let search_data_json = serde_to_string(&SearchData { 327 items: search_items, 328 programming_language: SearchProgrammingLanguage::Gleam, 329 }) 330 .expect("search index serialization"); 331 332 files.push(OutputFile { 333 path: Utf8PathBuf::from("search-data.json"), 334 content: Content::Text(search_data_json.to_string()), 335 }); 336 337 files.push(OutputFile { 338 path: Utf8PathBuf::from("js/index.js"), 339 content: Content::Text(std::include_str!("../templates/docs-js/index.js").to_string()), 340 }); 341 342 // web fonts: 343 344 files.push(OutputFile { 345 path: Utf8PathBuf::from("fonts/karla-v23-regular-latin-ext.woff2"), 346 content: Content::Binary( 347 include_bytes!("../templates/docs-fonts/karla-v23-regular-latin-ext.woff2").to_vec(), 348 ), 349 }); 350 351 files.push(OutputFile { 352 path: Utf8PathBuf::from("fonts/karla-v23-regular-latin.woff2"), 353 content: Content::Binary( 354 include_bytes!("../templates/docs-fonts/karla-v23-regular-latin.woff2").to_vec(), 355 ), 356 }); 357 358 files.push(OutputFile { 359 path: Utf8PathBuf::from("fonts/karla-v23-bold-latin-ext.woff2"), 360 content: Content::Binary( 361 include_bytes!("../templates/docs-fonts/karla-v23-bold-latin-ext.woff2").to_vec(), 362 ), 363 }); 364 365 files.push(OutputFile { 366 path: Utf8PathBuf::from("fonts/karla-v23-bold-latin.woff2"), 367 content: Content::Binary( 368 include_bytes!("../templates/docs-fonts/karla-v23-bold-latin.woff2").to_vec(), 369 ), 370 }); 371 372 files.push(OutputFile { 373 path: Utf8PathBuf::from("fonts/ubuntu-mono-v15-regular-cyrillic-ext.woff2"), 374 content: Content::Binary( 375 include_bytes!("../templates/docs-fonts/ubuntu-mono-v15-regular-cyrillic-ext.woff2") 376 .to_vec(), 377 ), 378 }); 379 380 files.push(OutputFile { 381 path: Utf8PathBuf::from("fonts/ubuntu-mono-v15-regular-cyrillic.woff2"), 382 content: Content::Binary( 383 include_bytes!("../templates/docs-fonts/ubuntu-mono-v15-regular-cyrillic.woff2") 384 .to_vec(), 385 ), 386 }); 387 388 files.push(OutputFile { 389 path: Utf8PathBuf::from("fonts/ubuntu-mono-v15-regular-greek-ext.woff2"), 390 content: Content::Binary( 391 include_bytes!("../templates/docs-fonts/ubuntu-mono-v15-regular-greek-ext.woff2") 392 .to_vec(), 393 ), 394 }); 395 396 files.push(OutputFile { 397 path: Utf8PathBuf::from("fonts/ubuntu-mono-v15-regular-greek.woff2"), 398 content: Content::Binary( 399 include_bytes!("../templates/docs-fonts/ubuntu-mono-v15-regular-greek.woff2").to_vec(), 400 ), 401 }); 402 403 files.push(OutputFile { 404 path: Utf8PathBuf::from("fonts/ubuntu-mono-v15-regular-latin-ext.woff2"), 405 content: Content::Binary( 406 include_bytes!("../templates/docs-fonts/ubuntu-mono-v15-regular-latin-ext.woff2") 407 .to_vec(), 408 ), 409 }); 410 411 files.push(OutputFile { 412 path: Utf8PathBuf::from("fonts/ubuntu-mono-v15-regular-latin.woff2"), 413 content: Content::Binary( 414 include_bytes!("../templates/docs-fonts/ubuntu-mono-v15-regular-latin.woff2").to_vec(), 415 ), 416 }); 417 418 files 419} 420 421fn search_item_for_page(package: &str, path: &str, content: String) -> SearchItem { 422 SearchItem { 423 type_: SearchItemType::Page, 424 parent_title: package.to_string(), 425 title: package.to_string(), 426 content, 427 reference: path.to_string(), 428 } 429} 430 431fn search_item_for_type(module: &str, type_: &TypeDefinition<'_>) -> SearchItem { 432 let constructors = type_ 433 .constructors 434 .iter() 435 .map(|constructor| { 436 let arguments = constructor 437 .arguments 438 .iter() 439 .map(|argument| format!("{}\n{}", argument.name, argument.text_documentation)) 440 .join("\n"); 441 442 format!( 443 "{}\n{}\n{}", 444 constructor.raw_definition, constructor.text_documentation, arguments 445 ) 446 }) 447 .join("\n"); 448 449 SearchItem { 450 type_: SearchItemType::Type, 451 parent_title: module.to_string(), 452 title: type_.name.to_string(), 453 content: format!( 454 "{}\n{}\n{}\n{}", 455 type_.raw_definition, 456 type_.text_documentation, 457 constructors, 458 import_synonyms(module, type_.name) 459 ), 460 reference: format!("{}.html#{}", module, type_.name), 461 } 462} 463 464fn search_item_for_value(module: &str, value: &DocsValues<'_>) -> SearchItem { 465 SearchItem { 466 type_: SearchItemType::Value, 467 parent_title: module.to_string(), 468 title: value.name.to_string(), 469 content: format!( 470 "{}\n{}\n{}", 471 value.raw_definition, 472 value.text_documentation, 473 import_synonyms(module, value.name) 474 ), 475 reference: format!("{}.html#{}", module, value.name), 476 } 477} 478 479fn search_item_for_module(module: &Module) -> SearchItem { 480 SearchItem { 481 type_: SearchItemType::Module, 482 parent_title: module.name.to_string(), 483 title: module.name.to_string(), 484 content: module.ast.documentation.iter().join("\n"), 485 reference: format!("{}.html", module.name), 486 } 487} 488 489pub fn generate_json_package_interface( 490 path: Utf8PathBuf, 491 package: &Package, 492 cached_modules: &im::HashMap<EcoString, type_::ModuleInterface>, 493) -> OutputFile { 494 OutputFile { 495 path, 496 content: Content::Text( 497 serde_json::to_string(&PackageInterface::from_package(package, cached_modules)) 498 .expect("JSON module interface serialisation"), 499 ), 500 } 501} 502 503pub fn generate_json_package_information(path: Utf8PathBuf, config: PackageConfig) -> OutputFile { 504 OutputFile { 505 path, 506 content: Content::Text(package_information_as_json(config)), 507 } 508} 509 510fn package_information_as_json(config: PackageConfig) -> String { 511 let info = PackageInformation { 512 package_config: config, 513 }; 514 serde_json::to_string_pretty(&info).expect("JSON module information serialisation") 515} 516 517fn page_unnest(path: &str) -> String { 518 let unnest = path 519 .strip_prefix('/') 520 .unwrap_or(path) 521 .split('/') 522 .skip(1) 523 .map(|_| "..") 524 .join("/"); 525 if unnest.is_empty() { 526 ".".into() 527 } else { 528 unnest 529 } 530} 531 532#[test] 533fn page_unnest_test() { 534 // Pages 535 assert_eq!(page_unnest("wibble.html"), "."); 536 assert_eq!(page_unnest("/wibble.html"), "."); 537 assert_eq!(page_unnest("/wibble/woo.html"), ".."); 538 assert_eq!(page_unnest("/wibble/wobble/woo.html"), "../.."); 539 540 // Modules 541 assert_eq!(page_unnest("string"), "."); 542 assert_eq!(page_unnest("gleam/string"), ".."); 543 assert_eq!(page_unnest("gleam/string/inspect"), "../.."); 544} 545 546fn import_synonyms(parent: &str, child: &str) -> String { 547 format!("Synonyms:\n{parent}.{child}\n{parent} {child}") 548} 549 550fn text_documentation(doc: &Option<(u32, EcoString)>) -> String { 551 let raw_text = doc 552 .as_ref() 553 .map(|(_, it)| it.to_string()) 554 .unwrap_or_else(|| "".into()); 555 556 // TODO: parse markdown properly and extract the text nodes 557 raw_text.replace("```gleam", "").replace("```", "") 558} 559 560fn markdown_documentation(doc: &Option<(u32, EcoString)>) -> String { 561 doc.as_ref() 562 .map(|(_, doc)| render_markdown(doc, MarkdownSource::Comment)) 563 .unwrap_or_default() 564} 565 566/// An enum to represent the source of a Markdown string to render. 567enum MarkdownSource { 568 /// A Markdown string that comes from the documentation of a 569 /// definition/module. This means that each line is going to be preceded by 570 /// a whitespace. 571 Comment, 572 /// A Markdown string coming from a standalone file like a README.md. 573 Standalone, 574} 575 576fn render_markdown(text: &str, source: MarkdownSource) -> String { 577 let text = match source { 578 MarkdownSource::Standalone => text.into(), 579 // Doc comments start with "///\s", which can confuse the markdown parser 580 // and prevent tables from rendering correctly, so remove that first space. 581 MarkdownSource::Comment => text 582 .split('\n') 583 .map(|s| s.strip_prefix(' ').unwrap_or(s)) 584 .join("\n"), 585 }; 586 587 let mut s = String::with_capacity(text.len() * 3 / 2); 588 let p = pulldown_cmark::Parser::new_ext(&text, pulldown_cmark::Options::all()); 589 pulldown_cmark::html::push_html(&mut s, p); 590 s 591} 592 593#[derive(PartialEq, Eq, PartialOrd, Ord, Clone)] 594struct Link { 595 name: String, 596 path: String, 597} 598 599#[derive(PartialEq, Eq, PartialOrd, Ord, Debug)] 600struct TypeConstructor { 601 definition: String, 602 raw_definition: String, 603 documentation: String, 604 text_documentation: String, 605 arguments: Vec<TypeConstructorArg>, 606} 607 608#[derive(PartialEq, Eq, PartialOrd, Ord, Debug)] 609struct TypeConstructorArg { 610 name: String, 611 doc: String, 612 text_documentation: String, 613} 614 615#[derive(PartialEq, Eq, PartialOrd, Ord, Debug)] 616struct TypeDefinition<'a> { 617 name: &'a str, 618 definition: String, 619 raw_definition: String, 620 documentation: String, 621 constructors: Vec<TypeConstructor>, 622 text_documentation: String, 623 source_url: String, 624 deprecation_message: String, 625 opaque: bool, 626} 627 628#[derive(PartialEq, Eq, PartialOrd, Ord)] 629struct DocsValues<'a> { 630 name: &'a str, 631 definition: String, 632 raw_definition: String, 633 documentation: String, 634 text_documentation: String, 635 source_url: String, 636 deprecation_message: String, 637} 638 639#[derive(Template)] 640#[template(path = "documentation_page.html")] 641struct PageTemplate<'a> { 642 gleam_version: &'a str, 643 unnest: &'a str, 644 host: &'a str, 645 page_title: &'a str, 646 page_meta_description: &'a str, 647 file_path: &'a Utf8PathBuf, 648 project_name: &'a str, 649 project_version: &'a str, 650 pages: &'a [Link], 651 links: &'a [Link], 652 modules: &'a [Link], 653 content: String, 654 rendering_timestamp: &'a str, 655} 656 657#[derive(Template)] 658#[template(path = "documentation_module.html")] 659struct ModuleTemplate<'a> { 660 gleam_version: &'a str, 661 unnest: String, 662 host: &'a str, 663 page_title: &'a str, 664 page_meta_description: &'a str, 665 file_path: &'a Utf8PathBuf, 666 module_name: EcoString, 667 project_name: &'a str, 668 project_version: &'a str, 669 pages: &'a [Link], 670 links: &'a [Link], 671 modules: &'a [Link], 672 types: Vec<TypeDefinition<'a>>, 673 values: Vec<DocsValues<'a>>, 674 documentation: String, 675 rendering_timestamp: &'a str, 676} 677 678/// Search data for use by Hexdocs search, as well as the search built-in to 679/// generated documentation 680#[derive(Serialize, PartialEq, Eq, PartialOrd, Ord, Clone)] 681struct SearchData { 682 items: Vec<SearchItem>, 683 #[serde(rename = "proglang")] 684 programming_language: SearchProgrammingLanguage, 685} 686 687/// A single item that can appear as a search result 688#[derive(Serialize, PartialEq, Eq, PartialOrd, Ord, Clone)] 689struct SearchItem { 690 /// The type of item this is: Value, Type, Module, or other Page 691 #[serde(rename = "type")] 692 type_: SearchItemType, 693 /// The title of the module or package containing this search item 694 #[serde(rename = "parentTitle")] 695 parent_title: String, 696 /// The title of this item 697 title: String, 698 /// Markdown text which describes this item, containing documentation from 699 /// doc comments, as well as rendered definitions of types and values. 700 #[serde(rename = "doc")] 701 content: String, 702 /// The relative URL to the documentation for this search item, for example 703 /// `gleam/option.html#Option` 704 #[serde(rename = "ref")] 705 reference: String, 706} 707 708#[derive(Serialize, PartialEq, Eq, PartialOrd, Ord, Clone)] 709#[serde(rename_all = "lowercase")] 710enum SearchItemType { 711 Value, 712 Module, 713 Page, 714 Type, 715} 716 717#[derive(Serialize, PartialEq, Eq, PartialOrd, Ord, Clone)] 718#[serde(rename_all = "lowercase")] 719enum SearchProgrammingLanguage { 720 // Elixir, 721 // Erlang, 722 Gleam, 723} 724 725#[test] 726fn package_config_to_json() { 727 let input = r#" 728name = "my_project" 729version = "1.0.0" 730licences = ["Apache-2.0", "MIT"] 731description = "Pretty complex config" 732target = "erlang" 733repository = { type = "github", user = "example", repo = "my_dep" } 734links = [{ title = "Home page", href = "https://example.com" }] 735internal_modules = ["my_app/internal"] 736gleam = ">= 0.30.0" 737 738[dependencies] 739gleam_stdlib = ">= 0.18.0 and < 2.0.0" 740my_other_project = { path = "../my_other_project" } 741 742[dev-dependencies] 743gleeunit = ">= 1.0.0 and < 2.0.0" 744 745[documentation] 746pages = [{ title = "My Page", path = "my-page.html", source = "./path/to/my-page.md" }] 747 748[erlang] 749application_start_module = "my_app/application" 750extra_applications = ["inets", "ssl"] 751 752[javascript] 753typescript_declarations = true 754runtime = "node" 755 756[javascript.deno] 757allow_all = false 758allow_ffi = true 759allow_env = ["DATABASE_URL"] 760allow_net = ["example.com:443"] 761allow_read = ["./database.sqlite"] 762"#; 763 764 let config = toml::from_str::<PackageConfig>(&input).unwrap(); 765 let info = PackageInformation { 766 package_config: config.clone(), 767 }; 768 let json = package_information_as_json(config); 769 let output = format!("--- GLEAM.TOML\n{input}\n\n--- EXPORTED JSON\n\n{json}"); 770 insta::assert_snapshot!(output); 771 772 let roundtrip: PackageInformation = serde_json::from_str(&json).unwrap(); 773 assert_eq!(info, roundtrip); 774} 775 776#[test] 777fn barebones_package_config_to_json() { 778 let input = r#" 779name = "my_project" 780version = "1.0.0" 781"#; 782 783 let config = toml::from_str::<PackageConfig>(&input).unwrap(); 784 let json = package_information_as_json(config); 785 let output = format!("--- GLEAM.TOML\n{input}\n\n--- EXPORTED JSON\n\n{json}"); 786 insta::assert_snapshot!(output); 787}