1#![allow(
4 clippy::new_ret_no_self,
5 clippy::needless_pass_by_value,
6 clippy::redundant_closure
7)]
8
9use anyhow::{anyhow, bail, Context, Result};
10mod npm;
11
12use std::path::Path;
13use std::{collections::HashMap, fs};
14
15use self::npm::{
16 repository::Repository, CommonJSPackage, ESModulesPackage, NoModulesPackage, NpmPackage,
17};
18use crate::command::build::{BuildProfile, Target};
19use crate::PBAR;
20use cargo_metadata::Metadata;
21use chrono::offset;
22use chrono::DateTime;
23use serde::{self, Deserialize};
24use serde_json;
25use std::collections::BTreeSet;
26use std::env;
27use std::io::Write;
28use strsim::levenshtein;
29use toml;
30
31const WASM_PACK_METADATA_KEY: &str = "package.metadata.wasm-pack";
32const WASM_PACK_VERSION: Option<&'static str> = option_env!("CARGO_PKG_VERSION");
33const WASM_PACK_REPO_URL: &str = "https://github.com/rustwasm/wasm-pack";
34
35pub struct CrateData {
37 data: Metadata,
38 current_idx: usize,
39 manifest: CargoManifest,
40 out_name: Option<String>,
41}
42
43#[doc(hidden)]
44#[derive(Deserialize)]
45pub struct CargoManifest {
46 package: CargoPackage,
47}
48
49#[derive(Deserialize)]
50struct CargoPackage {
51 name: String,
52
53 #[serde(default)]
54 metadata: CargoMetadata,
55}
56
57#[derive(Default, Deserialize)]
58struct CargoMetadata {
59 #[serde(default, rename = "wasm-pack")]
60 wasm_pack: CargoWasmPack,
61}
62
63#[derive(Default, Deserialize)]
64struct CargoWasmPack {
65 #[serde(default)]
66 profile: CargoWasmPackProfiles,
67}
68
69#[derive(Deserialize)]
70struct CargoWasmPackProfiles {
71 #[serde(
72 default = "CargoWasmPackProfile::default_dev",
73 deserialize_with = "CargoWasmPackProfile::deserialize_dev"
74 )]
75 dev: CargoWasmPackProfile,
76
77 #[serde(
78 default = "CargoWasmPackProfile::default_release",
79 deserialize_with = "CargoWasmPackProfile::deserialize_release"
80 )]
81 release: CargoWasmPackProfile,
82
83 #[serde(
84 default = "CargoWasmPackProfile::default_profiling",
85 deserialize_with = "CargoWasmPackProfile::deserialize_profiling"
86 )]
87 profiling: CargoWasmPackProfile,
88}
89
90impl Default for CargoWasmPackProfiles {
91 fn default() -> CargoWasmPackProfiles {
92 CargoWasmPackProfiles {
93 dev: CargoWasmPackProfile::default_dev(),
94 release: CargoWasmPackProfile::default_release(),
95 profiling: CargoWasmPackProfile::default_profiling(),
96 }
97 }
98}
99
100#[derive(Default, Deserialize)]
103pub struct CargoWasmPackProfile {
104 #[serde(default, rename = "wasm-bindgen")]
105 wasm_bindgen: CargoWasmPackProfileWasmBindgen,
106 #[serde(default, rename = "wasm-opt")]
107 wasm_opt: Option<CargoWasmPackProfileWasmOpt>,
108}
109
110#[derive(Default, Deserialize)]
111struct CargoWasmPackProfileWasmBindgen {
112 #[serde(default, rename = "debug-js-glue")]
113 debug_js_glue: Option<bool>,
114
115 #[serde(default, rename = "demangle-name-section")]
116 demangle_name_section: Option<bool>,
117
118 #[serde(default, rename = "dwarf-debug-info")]
119 dwarf_debug_info: Option<bool>,
120
121 #[serde(default, rename = "omit-default-module-path")]
122 omit_default_module_path: Option<bool>,
123}
124
125#[derive(Deserialize, Debug)]
127pub struct Crate {
128 #[serde(rename = "crate")]
129 crt: CrateInformation,
130}
131
132#[derive(Deserialize, Debug)]
133struct CrateInformation {
134 max_version: String,
135}
136
137impl Crate {
138 pub fn return_wasm_pack_latest_version() -> Result<Option<String>> {
140 let current_time = chrono::offset::Local::now();
141 let old_metadata_file = Self::return_wasm_pack_file();
142
143 match old_metadata_file {
144 Some(ref file_contents) => {
145 let last_updated = Self::return_stamp_file_value(&file_contents, "created")
146 .and_then(|t| DateTime::parse_from_str(t.as_str(), "%+").ok());
147
148 last_updated
149 .map(|last_updated| {
150 if current_time.signed_duration_since(last_updated).num_hours() > 24 {
151 Self::return_api_call_result(current_time).map(Some)
152 } else {
153 Ok(Self::return_stamp_file_value(&file_contents, "version"))
154 }
155 })
156 .unwrap_or_else(|| Ok(None))
157 }
158 None => Self::return_api_call_result(current_time).map(Some),
159 }
160 }
161
162 fn return_api_call_result(current_time: DateTime<offset::Local>) -> Result<String> {
163 let version = Self::return_latest_wasm_pack_version();
164
165 match version {
170 Ok(ref version) => Self::override_stamp_file(current_time, Some(&version)).ok(),
171 Err(_) => Self::override_stamp_file(current_time, None).ok(),
172 };
173
174 version
175 }
176
177 fn override_stamp_file(
178 current_time: DateTime<offset::Local>,
179 version: Option<&str>,
180 ) -> Result<()> {
181 let path = env::current_exe()?;
182
183 let mut file = fs::OpenOptions::new()
184 .read(true)
185 .write(true)
186 .append(true)
187 .create(true)
188 .open(path.with_extension("stamp"))?;
189
190 file.set_len(0)?;
191
192 write!(file, "created {:?}", current_time)?;
193
194 if let Some(version) = version {
195 write!(file, "\nversion {}", version)?;
196 }
197
198 Ok(())
199 }
200
201 fn return_wasm_pack_file() -> Option<String> {
203 if let Ok(path) = env::current_exe() {
204 if let Ok(file) = fs::read_to_string(path.with_extension("stamp")) {
205 return Some(file);
206 }
207 }
208 None
209 }
210
211 fn return_latest_wasm_pack_version() -> Result<String> {
213 Self::check_wasm_pack_latest_version().map(|crt| crt.crt.max_version)
214 }
215
216 fn return_stamp_file_value(file: &str, word: &str) -> Option<String> {
218 let created = file
219 .lines()
220 .find(|line| line.starts_with(word))
221 .and_then(|l| l.split_whitespace().nth(1));
222
223 created.map(|s| s.to_string())
224 }
225
226 fn check_wasm_pack_latest_version() -> Result<Crate> {
228 let url = "https://crates.io/api/v1/crates/wasm-pack";
229 let agent = ureq::builder()
230 .try_proxy_from_env(true)
231 .user_agent(&format!(
232 "wasm-pack/{} ({})",
233 WASM_PACK_VERSION.unwrap_or_else(|| "unknown"),
234 WASM_PACK_REPO_URL
235 ))
236 .build();
237 let resp = agent
238 .get(url)
239 .call()
240 .context("failed to get wasm-pack version")?;
241
242 let status_code = resp.status();
243
244 if 200 <= status_code && status_code < 300 {
245 let json = resp.into_json()?;
246
247 Ok(json)
248 } else {
249 bail!(
250 "Received a bad HTTP status code ({}) when checking for newer wasm-pack version at: {}",
251 status_code,
252 url
253 )
254 }
255 }
256}
257
258#[derive(Clone, Deserialize)]
259#[serde(untagged)]
260enum CargoWasmPackProfileWasmOpt {
261 Enabled(bool),
262 ExplicitArgs(Vec<String>),
263}
264
265impl Default for CargoWasmPackProfileWasmOpt {
266 fn default() -> Self {
267 CargoWasmPackProfileWasmOpt::Enabled(false)
268 }
269}
270
271impl CargoWasmPackProfile {
272 fn default_dev() -> Self {
273 CargoWasmPackProfile {
274 wasm_bindgen: CargoWasmPackProfileWasmBindgen {
275 debug_js_glue: Some(true),
276 demangle_name_section: Some(true),
277 dwarf_debug_info: Some(false),
278 omit_default_module_path: Some(false),
279 },
280 wasm_opt: None,
281 }
282 }
283
284 fn default_release() -> Self {
285 CargoWasmPackProfile {
286 wasm_bindgen: CargoWasmPackProfileWasmBindgen {
287 debug_js_glue: Some(false),
288 demangle_name_section: Some(true),
289 dwarf_debug_info: Some(false),
290 omit_default_module_path: Some(false),
291 },
292 wasm_opt: Some(CargoWasmPackProfileWasmOpt::Enabled(true)),
293 }
294 }
295
296 fn default_profiling() -> Self {
297 CargoWasmPackProfile {
298 wasm_bindgen: CargoWasmPackProfileWasmBindgen {
299 debug_js_glue: Some(false),
300 demangle_name_section: Some(true),
301 dwarf_debug_info: Some(false),
302 omit_default_module_path: Some(false),
303 },
304 wasm_opt: Some(CargoWasmPackProfileWasmOpt::Enabled(true)),
305 }
306 }
307
308 fn deserialize_dev<'de, D>(deserializer: D) -> Result<Self, D::Error>
309 where
310 D: serde::Deserializer<'de>,
311 {
312 let mut profile = <Option<Self>>::deserialize(deserializer)?.unwrap_or_default();
313 profile.update_with_defaults(&Self::default_dev());
314 Ok(profile)
315 }
316
317 fn deserialize_release<'de, D>(deserializer: D) -> Result<Self, D::Error>
318 where
319 D: serde::Deserializer<'de>,
320 {
321 let mut profile = <Option<Self>>::deserialize(deserializer)?.unwrap_or_default();
322 profile.update_with_defaults(&Self::default_release());
323 Ok(profile)
324 }
325
326 fn deserialize_profiling<'de, D>(deserializer: D) -> Result<Self, D::Error>
327 where
328 D: serde::Deserializer<'de>,
329 {
330 let mut profile = <Option<Self>>::deserialize(deserializer)?.unwrap_or_default();
331 profile.update_with_defaults(&Self::default_profiling());
332 Ok(profile)
333 }
334
335 fn update_with_defaults(&mut self, defaults: &Self) {
336 macro_rules! d {
337 ( $( $path:ident ).* ) => {
338 self. $( $path ).* .get_or_insert(defaults. $( $path ).* .unwrap());
339 }
340 }
341 d!(wasm_bindgen.debug_js_glue);
342 d!(wasm_bindgen.demangle_name_section);
343 d!(wasm_bindgen.dwarf_debug_info);
344 d!(wasm_bindgen.omit_default_module_path);
345
346 if self.wasm_opt.is_none() {
347 self.wasm_opt = defaults.wasm_opt.clone();
348 }
349 }
350
351 pub fn wasm_bindgen_debug_js_glue(&self) -> bool {
353 self.wasm_bindgen.debug_js_glue.unwrap()
354 }
355
356 pub fn wasm_bindgen_demangle_name_section(&self) -> bool {
358 self.wasm_bindgen.demangle_name_section.unwrap()
359 }
360
361 pub fn wasm_bindgen_dwarf_debug_info(&self) -> bool {
363 self.wasm_bindgen.dwarf_debug_info.unwrap()
364 }
365
366 pub fn wasm_bindgen_omit_default_module_path(&self) -> bool {
368 self.wasm_bindgen.omit_default_module_path.unwrap()
369 }
370
371 pub fn wasm_opt_args(&self) -> Option<Vec<String>> {
373 match self.wasm_opt.as_ref()? {
374 CargoWasmPackProfileWasmOpt::Enabled(false) => None,
375 CargoWasmPackProfileWasmOpt::Enabled(true) => Some(vec!["-O".to_string()]),
376 CargoWasmPackProfileWasmOpt::ExplicitArgs(s) => Some(s.clone()),
377 }
378 }
379}
380
381struct NpmData {
382 name: String,
383 files: Vec<String>,
384 dts_file: Option<String>,
385 main: String,
386 homepage: Option<String>, keywords: Option<Vec<String>>, }
389
390#[doc(hidden)]
391pub struct ManifestAndUnsedKeys {
392 pub manifest: CargoManifest,
393 pub unused_keys: BTreeSet<String>,
394}
395
396impl CrateData {
397 pub fn new(crate_path: &Path, out_name: Option<String>) -> Result<CrateData> {
400 let manifest_path = crate_path.join("Cargo.toml");
401 if !manifest_path.is_file() {
402 bail!(
403 "crate directory is missing a `Cargo.toml` file; is `{}` the \
404 wrong directory?",
405 crate_path.display()
406 )
407 }
408
409 let data = cargo_metadata::MetadataCommand::new()
410 .manifest_path(&manifest_path)
411 .exec()?;
412
413 let manifest_and_keys = CrateData::parse_crate_data(&manifest_path)?;
414 CrateData::warn_for_unused_keys(&manifest_and_keys);
415
416 let manifest = manifest_and_keys.manifest;
417 let current_idx = data
418 .packages
419 .iter()
420 .position(|pkg| {
421 pkg.name == manifest.package.name
422 && CrateData::is_same_path(pkg.manifest_path.as_std_path(), &manifest_path)
423 })
424 .ok_or_else(|| anyhow!("failed to find package in metadata"))?;
425
426 Ok(CrateData {
427 data,
428 manifest,
429 current_idx,
430 out_name,
431 })
432 }
433
434 fn is_same_path(path1: &Path, path2: &Path) -> bool {
435 if let Ok(path1) = fs::canonicalize(&path1) {
436 if let Ok(path2) = fs::canonicalize(&path2) {
437 return path1 == path2;
438 }
439 }
440 path1 == path2
441 }
442
443 pub fn parse_crate_data(manifest_path: &Path) -> Result<ManifestAndUnsedKeys> {
451 let manifest = fs::read_to_string(&manifest_path)
452 .with_context(|| anyhow!("failed to read: {}", manifest_path.display()))?;
453 let manifest = toml::Deserializer::new(&manifest);
454
455 let mut unused_keys = BTreeSet::new();
456 let levenshtein_threshold = 1;
457
458 let manifest: CargoManifest = serde_ignored::deserialize(manifest, |path| {
459 let path_string = path.to_string();
460
461 if path_string.starts_with("package.metadata")
462 && (path_string.contains("wasm-pack")
463 || levenshtein(WASM_PACK_METADATA_KEY, &path_string) <= levenshtein_threshold)
464 {
465 unused_keys.insert(path_string);
466 }
467 })
468 .with_context(|| anyhow!("failed to parse manifest: {}", manifest_path.display()))?;
469
470 Ok(ManifestAndUnsedKeys {
471 manifest,
472 unused_keys,
473 })
474 }
475
476 pub fn warn_for_unused_keys(manifest_and_keys: &ManifestAndUnsedKeys) {
479 manifest_and_keys.unused_keys.iter().for_each(|path| {
480 PBAR.warn(&format!(
481 "\"{}\" is an unknown key and will be ignored. Please check your Cargo.toml.",
482 path
483 ));
484 });
485 }
486
487 pub fn configured_profile(&self, profile: BuildProfile) -> &CargoWasmPackProfile {
489 match profile {
490 BuildProfile::Dev => &self.manifest.package.metadata.wasm_pack.profile.dev,
491 BuildProfile::Profiling => &self.manifest.package.metadata.wasm_pack.profile.profiling,
492 BuildProfile::Release => &self.manifest.package.metadata.wasm_pack.profile.release,
493 }
494 }
495
496 pub fn check_crate_config(&self) -> Result<()> {
498 self.check_crate_type()?;
499 Ok(())
500 }
501
502 fn check_crate_type(&self) -> Result<()> {
503 let pkg = &self.data.packages[self.current_idx];
504 let any_cdylib = pkg
505 .targets
506 .iter()
507 .filter(|target| target.kind.iter().any(|k| k == "cdylib"))
508 .any(|target| target.crate_types.iter().any(|s| s == "cdylib"));
509 if any_cdylib {
510 return Ok(());
511 }
512 bail!(
513 "crate-type must be cdylib to compile to wasm32-unknown-unknown. Add the following to your \
514 Cargo.toml file:\n\n\
515 [lib]\n\
516 crate-type = [\"cdylib\", \"rlib\"]"
517 )
518 }
519
520 fn pkg(&self) -> &cargo_metadata::Package {
521 &self.data.packages[self.current_idx]
522 }
523
524 pub fn crate_name(&self) -> String {
526 let pkg = self.pkg();
527 match pkg
528 .targets
529 .iter()
530 .find(|t| t.kind.iter().any(|k| k == "cdylib"))
531 {
532 Some(lib) => lib.name.replace("-", "_"),
533 None => pkg.name.replace("-", "_"),
534 }
535 }
536
537 pub fn name_prefix(&self) -> String {
539 match &self.out_name {
540 Some(value) => value.clone(),
541 None => self.crate_name(),
542 }
543 }
544
545 pub fn crate_readme(&self) -> Option<String> {
547 self.pkg()
548 .readme
549 .clone()
550 .map(|readme_file| readme_file.into_string())
551 }
552
553 pub fn crate_license(&self) -> &Option<String> {
555 &self.pkg().license
556 }
557
558 pub fn crate_license_file(&self) -> Option<String> {
560 self.pkg()
561 .license_file
562 .clone()
563 .map(|license_file| license_file.into_string())
564 }
565
566 pub fn target_directory(&self) -> &Path {
569 Path::new(&self.data.target_directory)
570 }
571
572 pub fn workspace_root(&self) -> &Path {
574 Path::new(&self.data.workspace_root)
575 }
576
577 pub fn write_package_json(
579 &self,
580 out_dir: &Path,
581 scope: &Option<String>,
582 disable_dts: bool,
583 target: Target,
584 ) -> Result<()> {
585 let pkg_file_path = out_dir.join("package.json");
586 let existing_deps = if pkg_file_path.exists() {
589 Some(serde_json::from_str::<HashMap<String, String>>(
591 &fs::read_to_string(&pkg_file_path)?,
592 )?)
593 } else {
594 None
595 };
596 let npm_data = match target {
597 Target::Nodejs => self.to_commonjs(scope, disable_dts, existing_deps, out_dir),
598 Target::NoModules => self.to_nomodules(scope, disable_dts, existing_deps, out_dir),
599 Target::Bundler => self.to_esmodules(scope, disable_dts, existing_deps, out_dir),
600 Target::Web => self.to_web(scope, disable_dts, existing_deps, out_dir),
601 Target::Deno => return Ok(()),
603 };
604
605 let npm_json = serde_json::to_string_pretty(&npm_data)?;
606
607 fs::write(&pkg_file_path, npm_json)
608 .with_context(|| anyhow!("failed to write: {}", pkg_file_path.display()))?;
609 Ok(())
610 }
611
612 fn npm_data(
613 &self,
614 scope: &Option<String>,
615 add_js_bg_to_package_json: bool,
616 disable_dts: bool,
617 out_dir: &Path,
618 ) -> NpmData {
619 let name_prefix = self.name_prefix();
620 let wasm_file = format!("{}_bg.wasm", name_prefix);
621 let js_file = format!("{}.js", name_prefix);
622 let mut files = vec![wasm_file];
623
624 files.push(js_file.clone());
625 if add_js_bg_to_package_json {
626 let js_bg_file = format!("{}_bg.js", name_prefix);
627 files.push(js_bg_file);
628 }
629
630 let pkg = &self.data.packages[self.current_idx];
631 let npm_name = match scope {
632 Some(s) => format!("@{}/{}", s, pkg.name),
633 None => pkg.name.clone(),
634 };
635
636 let dts_file = if !disable_dts {
637 let file = format!("{}.d.ts", name_prefix);
638 files.push(file.to_string());
639 Some(file)
640 } else {
641 None
642 };
643
644 let keywords = if pkg.keywords.len() > 0 {
645 Some(pkg.keywords.clone())
646 } else {
647 None
648 };
649
650 if let Ok(entries) = fs::read_dir(out_dir) {
651 let file_names = entries
652 .filter_map(|e| e.ok())
653 .filter(|e| e.metadata().map(|m| m.is_file()).unwrap_or(false))
654 .filter_map(|e| e.file_name().into_string().ok())
655 .filter(|f| f.starts_with("LICENSE"))
656 .filter(|f| f != "LICENSE");
657 for file_name in file_names {
658 files.push(file_name);
659 }
660 }
661
662 NpmData {
663 name: npm_name,
664 dts_file,
665 files,
666 main: js_file,
667 homepage: self.pkg().homepage.clone(),
668 keywords,
669 }
670 }
671
672 fn license(&self) -> Option<String> {
673 self.crate_license().clone().or_else(|| {
674 self.crate_license_file().clone().map(|file| {
675 format!("SEE LICENSE IN {}", file)
677 })
678 })
679 }
680
681 fn to_commonjs(
682 &self,
683 scope: &Option<String>,
684 disable_dts: bool,
685 dependencies: Option<HashMap<String, String>>,
686 out_dir: &Path,
687 ) -> NpmPackage {
688 let data = self.npm_data(scope, false, disable_dts, out_dir);
689 let pkg = &self.data.packages[self.current_idx];
690
691 self.check_optional_fields();
692
693 NpmPackage::CommonJSPackage(CommonJSPackage {
694 name: data.name,
695 collaborators: pkg.authors.clone(),
696 description: self.pkg().description.clone(),
697 version: pkg.version.to_string(),
698 license: self.license(),
699 repository: self.pkg().repository.clone().map(|repo_url| Repository {
700 ty: "git".to_string(),
701 url: repo_url,
702 }),
703 files: data.files,
704 main: data.main,
705 homepage: data.homepage,
706 types: data.dts_file,
707 keywords: data.keywords,
708 dependencies,
709 })
710 }
711
712 fn to_esmodules(
713 &self,
714 scope: &Option<String>,
715 disable_dts: bool,
716 dependencies: Option<HashMap<String, String>>,
717 out_dir: &Path,
718 ) -> NpmPackage {
719 let data = self.npm_data(scope, true, disable_dts, out_dir);
720 let pkg = &self.data.packages[self.current_idx];
721
722 self.check_optional_fields();
723
724 NpmPackage::ESModulesPackage(ESModulesPackage {
725 name: data.name,
726 ty: "module".into(),
727 collaborators: pkg.authors.clone(),
728 description: self.pkg().description.clone(),
729 version: pkg.version.to_string(),
730 license: self.license(),
731 repository: self.pkg().repository.clone().map(|repo_url| Repository {
732 ty: "git".to_string(),
733 url: repo_url,
734 }),
735 files: data.files,
736 main: data.main.clone(),
737 homepage: data.homepage,
738 types: data.dts_file,
739 side_effects: vec![format!("./{}", data.main), "./snippets/*".to_owned()],
740 keywords: data.keywords,
741 dependencies,
742 })
743 }
744
745 fn to_web(
746 &self,
747 scope: &Option<String>,
748 disable_dts: bool,
749 dependencies: Option<HashMap<String, String>>,
750 out_dir: &Path,
751 ) -> NpmPackage {
752 let data = self.npm_data(scope, false, disable_dts, out_dir);
753 let pkg = &self.data.packages[self.current_idx];
754
755 self.check_optional_fields();
756
757 NpmPackage::ESModulesPackage(ESModulesPackage {
758 name: data.name,
759 ty: "module".into(),
760 collaborators: pkg.authors.clone(),
761 description: self.pkg().description.clone(),
762 version: pkg.version.to_string(),
763 license: self.license(),
764 repository: self.pkg().repository.clone().map(|repo_url| Repository {
765 ty: "git".to_string(),
766 url: repo_url,
767 }),
768 files: data.files,
769 main: data.main,
770 homepage: data.homepage,
771 types: data.dts_file,
772 side_effects: vec!["./snippets/*".to_owned()],
773 keywords: data.keywords,
774 dependencies,
775 })
776 }
777
778 fn to_nomodules(
779 &self,
780 scope: &Option<String>,
781 disable_dts: bool,
782 dependencies: Option<HashMap<String, String>>,
783 out_dir: &Path,
784 ) -> NpmPackage {
785 let data = self.npm_data(scope, false, disable_dts, out_dir);
786 let pkg = &self.data.packages[self.current_idx];
787
788 self.check_optional_fields();
789
790 NpmPackage::NoModulesPackage(NoModulesPackage {
791 name: data.name,
792 collaborators: pkg.authors.clone(),
793 description: self.pkg().description.clone(),
794 version: pkg.version.to_string(),
795 license: self.license(),
796 repository: self.pkg().repository.clone().map(|repo_url| Repository {
797 ty: "git".to_string(),
798 url: repo_url,
799 }),
800 files: data.files,
801 browser: data.main,
802 homepage: data.homepage,
803 types: data.dts_file,
804 keywords: data.keywords,
805 dependencies,
806 })
807 }
808
809 fn check_optional_fields(&self) {
810 let mut messages = vec![];
811 if self.pkg().description.is_none() {
812 messages.push("description");
813 }
814 if self.pkg().repository.is_none() {
815 messages.push("repository");
816 }
817 if self.pkg().license.is_none() && self.pkg().license_file.is_none() {
818 messages.push("license");
819 }
820
821 match messages.len() {
822 1 => PBAR.info(&format!("Optional field missing from Cargo.toml: '{}'. This is not necessary, but recommended", messages[0])),
823 2 => PBAR.info(&format!("Optional fields missing from Cargo.toml: '{}', '{}'. These are not necessary, but recommended", messages[0], messages[1])),
824 3 => PBAR.info(&format!("Optional fields missing from Cargo.toml: '{}', '{}', and '{}'. These are not necessary, but recommended", messages[0], messages[1], messages[2])),
825 _ => ()
826 };
827 }
828}