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 779 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(SearchItem { 188 type_: SearchItemType::Page, 189 parent_title: config.name.to_string(), 190 title: config.name.to_string(), 191 content: escape_html_content(content), 192 reference: page.path.to_string(), 193 }) 194 } 195 196 // Generate module documentation pages 197 for module in modules { 198 let name = module.name.clone(); 199 let unnest = page_unnest(&module.name); 200 201 // Read module src & create line number lookup structure 202 let source_links = SourceLinker::new(paths, config, module); 203 204 let documentation_content = module.ast.documentation.iter().join("\n"); 205 let rendered_documentation = 206 render_markdown(&documentation_content.clone(), MarkdownSource::Comment); 207 208 let mut printer = Printer::new( 209 module.ast.type_info.package.clone(), 210 module.name.clone(), 211 &module.ast.names, 212 &dependencies, 213 ); 214 215 let types = printer.type_definitions(&source_links, &module.ast.definitions); 216 let values = printer.value_definitions(&source_links, &module.ast.definitions); 217 218 types.iter().for_each(|type_| { 219 let constructors = type_ 220 .constructors 221 .iter() 222 .map(|constructor| { 223 let arguments = constructor 224 .arguments 225 .iter() 226 .map(|argument| { 227 format!("{}\n{}", argument.name, argument.text_documentation) 228 }) 229 .join("\n"); 230 231 format!( 232 "{}\n{}\n{}", 233 constructor.raw_definition, constructor.text_documentation, arguments 234 ) 235 }) 236 .join("\n"); 237 238 search_items.push(SearchItem { 239 type_: SearchItemType::Type, 240 parent_title: module.name.to_string(), 241 title: type_.name.to_string(), 242 content: format!( 243 "{}\n{}\n{}\n{}", 244 type_.raw_definition, 245 type_.text_documentation, 246 constructors, 247 import_synonyms(&module.name, type_.name) 248 ), 249 reference: format!("{}.html#{}", module.name, type_.name), 250 }) 251 }); 252 values.iter().for_each(|value| { 253 search_items.push(SearchItem { 254 type_: SearchItemType::Value, 255 parent_title: module.name.to_string(), 256 title: value.name.to_string(), 257 content: format!( 258 "{}\n{}\n{}", 259 value.raw_definition, 260 value.text_documentation, 261 import_synonyms(&module.name, value.name) 262 ), 263 reference: format!("{}.html#{}", module.name, value.name), 264 }) 265 }); 266 267 search_items.push(SearchItem { 268 type_: SearchItemType::Module, 269 parent_title: module.name.to_string(), 270 title: module.name.to_string(), 271 content: documentation_content, 272 reference: format!("{}.html", module.name), 273 }); 274 275 let page_title = format!("{} · {} · v{}", name, config.name, config.version); 276 let page_meta_description = ""; 277 let path = Utf8PathBuf::from(format!("{}.html", module.name)); 278 279 let template = ModuleTemplate { 280 gleam_version: COMPILER_VERSION, 281 host, 282 unnest, 283 links: &links, 284 pages: &pages, 285 documentation: rendered_documentation, 286 modules: &modules_links, 287 project_name: &config.name, 288 page_title: &page_title, 289 page_meta_description, 290 module_name: EcoString::from(&name), 291 file_path: &path.clone(), 292 project_version: &config.version.to_string(), 293 types, 294 values, 295 rendering_timestamp: &rendering_timestamp, 296 }; 297 298 files.push(OutputFile { 299 path, 300 content: Content::Text( 301 template 302 .render() 303 .expect("Module documentation template rendering"), 304 ), 305 }); 306 } 307 308 // Render static assets 309 310 files.push(OutputFile { 311 path: Utf8PathBuf::from("css/atom-one-light.min.css"), 312 content: Content::Text( 313 std::include_str!("../templates/docs-css/atom-one-light.min.css").to_string(), 314 ), 315 }); 316 317 files.push(OutputFile { 318 path: Utf8PathBuf::from("css/atom-one-dark.min.css"), 319 content: Content::Text( 320 std::include_str!("../templates/docs-css/atom-one-dark.min.css").to_string(), 321 ), 322 }); 323 324 files.push(OutputFile { 325 path: Utf8PathBuf::from("css/index.css"), 326 content: Content::Text(std::include_str!("../templates/docs-css/index.css").to_string()), 327 }); 328 329 // highlightjs: 330 331 files.push(OutputFile { 332 path: Utf8PathBuf::from("js/highlight.min.js"), 333 content: Content::Text( 334 std::include_str!("../templates/docs-js/highlight.min.js").to_string(), 335 ), 336 }); 337 338 files.push(OutputFile { 339 path: Utf8PathBuf::from("js/highlightjs-gleam.js"), 340 content: Content::Text( 341 std::include_str!("../templates/docs-js/highlightjs-gleam.js").to_string(), 342 ), 343 }); 344 345 files.push(OutputFile { 346 path: Utf8PathBuf::from("js/highlightjs-erlang.min.js"), 347 content: Content::Text( 348 std::include_str!("../templates/docs-js/highlightjs-erlang.min.js").to_string(), 349 ), 350 }); 351 352 files.push(OutputFile { 353 path: Utf8PathBuf::from("js/highlightjs-elixir.min.js"), 354 content: Content::Text( 355 std::include_str!("../templates/docs-js/highlightjs-elixir.min.js").to_string(), 356 ), 357 }); 358 359 files.push(OutputFile { 360 path: Utf8PathBuf::from("js/highlightjs-javascript.min.js"), 361 content: Content::Text( 362 std::include_str!("../templates/docs-js/highlightjs-javascript.min.js").to_string(), 363 ), 364 }); 365 366 files.push(OutputFile { 367 path: Utf8PathBuf::from("js/highlightjs-typescript.min.js"), 368 content: Content::Text( 369 std::include_str!("../templates/docs-js/highlightjs-typescript.min.js").to_string(), 370 ), 371 }); 372 373 // lunr.min.js, search-data.json and index.js 374 375 files.push(OutputFile { 376 path: Utf8PathBuf::from("js/lunr.min.js"), 377 content: Content::Text(std::include_str!("../templates/docs-js/lunr.min.js").to_string()), 378 }); 379 380 let search_data_json = serde_to_string(&SearchData { 381 items: search_items, 382 programming_language: SearchProgrammingLanguage::Gleam, 383 }) 384 .expect("search index serialization"); 385 386 files.push(OutputFile { 387 path: Utf8PathBuf::from("search-data.json"), 388 content: Content::Text(search_data_json.to_string()), 389 }); 390 391 files.push(OutputFile { 392 path: Utf8PathBuf::from("js/index.js"), 393 content: Content::Text(std::include_str!("../templates/docs-js/index.js").to_string()), 394 }); 395 396 // web fonts: 397 398 files.push(OutputFile { 399 path: Utf8PathBuf::from("fonts/karla-v23-regular-latin-ext.woff2"), 400 content: Content::Binary( 401 include_bytes!("../templates/docs-fonts/karla-v23-regular-latin-ext.woff2").to_vec(), 402 ), 403 }); 404 405 files.push(OutputFile { 406 path: Utf8PathBuf::from("fonts/karla-v23-regular-latin.woff2"), 407 content: Content::Binary( 408 include_bytes!("../templates/docs-fonts/karla-v23-regular-latin.woff2").to_vec(), 409 ), 410 }); 411 412 files.push(OutputFile { 413 path: Utf8PathBuf::from("fonts/karla-v23-bold-latin-ext.woff2"), 414 content: Content::Binary( 415 include_bytes!("../templates/docs-fonts/karla-v23-bold-latin-ext.woff2").to_vec(), 416 ), 417 }); 418 419 files.push(OutputFile { 420 path: Utf8PathBuf::from("fonts/karla-v23-bold-latin.woff2"), 421 content: Content::Binary( 422 include_bytes!("../templates/docs-fonts/karla-v23-bold-latin.woff2").to_vec(), 423 ), 424 }); 425 426 files.push(OutputFile { 427 path: Utf8PathBuf::from("fonts/ubuntu-mono-v15-regular-cyrillic-ext.woff2"), 428 content: Content::Binary( 429 include_bytes!("../templates/docs-fonts/ubuntu-mono-v15-regular-cyrillic-ext.woff2") 430 .to_vec(), 431 ), 432 }); 433 434 files.push(OutputFile { 435 path: Utf8PathBuf::from("fonts/ubuntu-mono-v15-regular-cyrillic.woff2"), 436 content: Content::Binary( 437 include_bytes!("../templates/docs-fonts/ubuntu-mono-v15-regular-cyrillic.woff2") 438 .to_vec(), 439 ), 440 }); 441 442 files.push(OutputFile { 443 path: Utf8PathBuf::from("fonts/ubuntu-mono-v15-regular-greek-ext.woff2"), 444 content: Content::Binary( 445 include_bytes!("../templates/docs-fonts/ubuntu-mono-v15-regular-greek-ext.woff2") 446 .to_vec(), 447 ), 448 }); 449 450 files.push(OutputFile { 451 path: Utf8PathBuf::from("fonts/ubuntu-mono-v15-regular-greek.woff2"), 452 content: Content::Binary( 453 include_bytes!("../templates/docs-fonts/ubuntu-mono-v15-regular-greek.woff2").to_vec(), 454 ), 455 }); 456 457 files.push(OutputFile { 458 path: Utf8PathBuf::from("fonts/ubuntu-mono-v15-regular-latin-ext.woff2"), 459 content: Content::Binary( 460 include_bytes!("../templates/docs-fonts/ubuntu-mono-v15-regular-latin-ext.woff2") 461 .to_vec(), 462 ), 463 }); 464 465 files.push(OutputFile { 466 path: Utf8PathBuf::from("fonts/ubuntu-mono-v15-regular-latin.woff2"), 467 content: Content::Binary( 468 include_bytes!("../templates/docs-fonts/ubuntu-mono-v15-regular-latin.woff2").to_vec(), 469 ), 470 }); 471 472 files 473} 474 475pub fn generate_json_package_interface( 476 path: Utf8PathBuf, 477 package: &Package, 478 cached_modules: &im::HashMap<EcoString, type_::ModuleInterface>, 479) -> OutputFile { 480 OutputFile { 481 path, 482 content: Content::Text( 483 serde_json::to_string(&PackageInterface::from_package(package, cached_modules)) 484 .expect("JSON module interface serialisation"), 485 ), 486 } 487} 488 489pub fn generate_json_package_information(path: Utf8PathBuf, config: PackageConfig) -> OutputFile { 490 OutputFile { 491 path, 492 content: Content::Text(package_information_as_json(config)), 493 } 494} 495 496fn package_information_as_json(config: PackageConfig) -> String { 497 let info = PackageInformation { 498 package_config: config, 499 }; 500 serde_json::to_string_pretty(&info).expect("JSON module information serialisation") 501} 502 503fn page_unnest(path: &str) -> String { 504 let unnest = path 505 .strip_prefix('/') 506 .unwrap_or(path) 507 .split('/') 508 .skip(1) 509 .map(|_| "..") 510 .join("/"); 511 if unnest.is_empty() { 512 ".".into() 513 } else { 514 unnest 515 } 516} 517 518#[test] 519fn page_unnest_test() { 520 // Pages 521 assert_eq!(page_unnest("wibble.html"), "."); 522 assert_eq!(page_unnest("/wibble.html"), "."); 523 assert_eq!(page_unnest("/wibble/woo.html"), ".."); 524 assert_eq!(page_unnest("/wibble/wobble/woo.html"), "../.."); 525 526 // Modules 527 assert_eq!(page_unnest("string"), "."); 528 assert_eq!(page_unnest("gleam/string"), ".."); 529 assert_eq!(page_unnest("gleam/string/inspect"), "../.."); 530} 531 532fn escape_html_content(it: String) -> String { 533 it.replace('&', "&amp;") 534 .replace('<', "&lt;") 535 .replace('>', "&gt;") 536 .replace('\"', "&quot;") 537 .replace('\'', "&#39;") 538} 539 540#[test] 541fn escape_html_content_test() { 542 assert_eq!( 543 escape_html_content("&<>\"'".to_string()), 544 "&amp;&lt;&gt;&quot;&#39;" 545 ); 546} 547 548fn import_synonyms(parent: &str, child: &str) -> String { 549 format!("Synonyms:\n{parent}.{child}\n{parent} {child}") 550} 551 552fn text_documentation(doc: &Option<(u32, EcoString)>) -> String { 553 let raw_text = doc 554 .as_ref() 555 .map(|(_, it)| it.to_string()) 556 .unwrap_or_else(|| "".into()); 557 558 // TODO: parse markdown properly and extract the text nodes 559 raw_text.replace("```gleam", "").replace("```", "") 560} 561 562fn markdown_documentation(doc: &Option<(u32, EcoString)>) -> String { 563 doc.as_ref() 564 .map(|(_, doc)| render_markdown(doc, MarkdownSource::Comment)) 565 .unwrap_or_default() 566} 567 568/// An enum to represent the source of a Markdown string to render. 569enum MarkdownSource { 570 /// A Markdown string that comes from the documentation of a 571 /// definition/module. This means that each line is going to be preceded by 572 /// a whitespace. 573 Comment, 574 /// A Markdown string coming from a standalone file like a README.md. 575 Standalone, 576} 577 578fn render_markdown(text: &str, source: MarkdownSource) -> String { 579 let text = match source { 580 MarkdownSource::Standalone => text.into(), 581 // Doc comments start with "///\s", which can confuse the markdown parser 582 // and prevent tables from rendering correctly, so remove that first space. 583 MarkdownSource::Comment => text 584 .split('\n') 585 .map(|s| s.strip_prefix(' ').unwrap_or(s)) 586 .join("\n"), 587 }; 588 589 let mut s = String::with_capacity(text.len() * 3 / 2); 590 let p = pulldown_cmark::Parser::new_ext(&text, pulldown_cmark::Options::all()); 591 pulldown_cmark::html::push_html(&mut s, p); 592 s 593} 594 595#[derive(PartialEq, Eq, PartialOrd, Ord, Clone)] 596struct Link { 597 name: String, 598 path: String, 599} 600 601#[derive(PartialEq, Eq, PartialOrd, Ord, Debug)] 602struct TypeConstructor { 603 definition: String, 604 raw_definition: String, 605 documentation: String, 606 text_documentation: String, 607 arguments: Vec<TypeConstructorArg>, 608} 609 610#[derive(PartialEq, Eq, PartialOrd, Ord, Debug)] 611struct TypeConstructorArg { 612 name: String, 613 doc: String, 614 text_documentation: String, 615} 616 617#[derive(PartialEq, Eq, PartialOrd, Ord, Debug)] 618struct TypeDefinition<'a> { 619 name: &'a str, 620 definition: String, 621 raw_definition: String, 622 documentation: String, 623 constructors: Vec<TypeConstructor>, 624 text_documentation: String, 625 source_url: String, 626 deprecation_message: String, 627 opaque: bool, 628} 629 630#[derive(PartialEq, Eq, PartialOrd, Ord)] 631struct DocsValues<'a> { 632 name: &'a str, 633 definition: String, 634 raw_definition: String, 635 documentation: String, 636 text_documentation: String, 637 source_url: String, 638 deprecation_message: String, 639} 640 641#[derive(Template)] 642#[template(path = "documentation_page.html")] 643struct PageTemplate<'a> { 644 gleam_version: &'a str, 645 unnest: &'a str, 646 host: &'a str, 647 page_title: &'a str, 648 page_meta_description: &'a str, 649 file_path: &'a Utf8PathBuf, 650 project_name: &'a str, 651 project_version: &'a str, 652 pages: &'a [Link], 653 links: &'a [Link], 654 modules: &'a [Link], 655 content: String, 656 rendering_timestamp: &'a str, 657} 658 659#[derive(Template)] 660#[template(path = "documentation_module.html")] 661struct ModuleTemplate<'a> { 662 gleam_version: &'a str, 663 unnest: String, 664 host: &'a str, 665 page_title: &'a str, 666 page_meta_description: &'a str, 667 file_path: &'a Utf8PathBuf, 668 module_name: EcoString, 669 project_name: &'a str, 670 project_version: &'a str, 671 pages: &'a [Link], 672 links: &'a [Link], 673 modules: &'a [Link], 674 types: Vec<TypeDefinition<'a>>, 675 values: Vec<DocsValues<'a>>, 676 documentation: String, 677 rendering_timestamp: &'a str, 678} 679 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#[derive(Serialize, PartialEq, Eq, PartialOrd, Ord, Clone)] 688struct SearchItem { 689 #[serde(rename = "type")] 690 type_: SearchItemType, 691 #[serde(rename = "parentTitle")] 692 parent_title: String, 693 title: String, 694 #[serde(rename = "doc")] 695 content: String, 696 #[serde(rename = "ref")] 697 reference: String, 698} 699 700#[derive(Serialize, PartialEq, Eq, PartialOrd, Ord, Clone)] 701#[serde(rename_all = "lowercase")] 702enum SearchItemType { 703 Value, 704 Module, 705 Page, 706 Type, 707} 708 709#[derive(Serialize, PartialEq, Eq, PartialOrd, Ord, Clone)] 710#[serde(rename_all = "lowercase")] 711enum SearchProgrammingLanguage { 712 // Elixir, 713 // Erlang, 714 Gleam, 715} 716 717#[test] 718fn package_config_to_json() { 719 let input = r#" 720name = "my_project" 721version = "1.0.0" 722licences = ["Apache-2.0", "MIT"] 723description = "Pretty complex config" 724target = "erlang" 725repository = { type = "github", user = "example", repo = "my_dep" } 726links = [{ title = "Home page", href = "https://example.com" }] 727internal_modules = ["my_app/internal"] 728gleam = ">= 0.30.0" 729 730[dependencies] 731gleam_stdlib = ">= 0.18.0 and < 2.0.0" 732my_other_project = { path = "../my_other_project" } 733 734[dev-dependencies] 735gleeunit = ">= 1.0.0 and < 2.0.0" 736 737[documentation] 738pages = [{ title = "My Page", path = "my-page.html", source = "./path/to/my-page.md" }] 739 740[erlang] 741application_start_module = "my_app/application" 742extra_applications = ["inets", "ssl"] 743 744[javascript] 745typescript_declarations = true 746runtime = "node" 747 748[javascript.deno] 749allow_all = false 750allow_ffi = true 751allow_env = ["DATABASE_URL"] 752allow_net = ["example.com:443"] 753allow_read = ["./database.sqlite"] 754"#; 755 756 let config = toml::from_str::<PackageConfig>(&input).unwrap(); 757 let info = PackageInformation { 758 package_config: config.clone(), 759 }; 760 let json = package_information_as_json(config); 761 let output = format!("--- GLEAM.TOML\n{input}\n\n--- EXPORTED JSON\n\n{json}"); 762 insta::assert_snapshot!(output); 763 764 let roundtrip: PackageInformation = serde_json::from_str(&json).unwrap(); 765 assert_eq!(info, roundtrip); 766} 767 768#[test] 769fn barebones_package_config_to_json() { 770 let input = r#" 771name = "my_project" 772version = "1.0.0" 773"#; 774 775 let config = toml::from_str::<PackageConfig>(&input).unwrap(); 776 let json = package_information_as_json(config); 777 let output = format!("--- GLEAM.TOML\n{input}\n\n--- EXPORTED JSON\n\n{json}"); 778 insta::assert_snapshot!(output); 779}