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