1use anyhow::{Context, Result};
2use std::fs;
3use std::path::Path;
4use strum::{Display, EnumIter};
5
6#[derive(Clone, PartialEq, Eq, Debug)]
7pub struct Manifest {
8 pub manifest_type: ManifestType,
9 pub number_of_dependencies: usize,
10 pub name: String,
11 pub description: Option<String>,
12 pub version: String,
13 pub license: Option<String>,
14}
15
16#[derive(Display, Clone, Copy, PartialEq, Eq, Debug, EnumIter)]
17pub enum ManifestType {
18 Npm,
19 Cargo,
20}
21
22pub fn get_manifests<P: AsRef<Path>>(path: P) -> Result<Vec<Manifest>> {
23 let manifest_paths = fs::read_dir(path)?
24 .filter_map(std::result::Result::ok)
25 .map(|entry| entry.path())
26 .filter(|entry| entry.is_file())
27 .filter_map(|file_path| {
28 let file_name = file_path
29 .file_name()
30 .and_then(|name| name.to_str())
31 .unwrap_or_default();
32 file_name_to_manifest_type(file_name).map(|manifest_type| (file_path, manifest_type))
33 })
34 .map(|(file_path, manifest_type)| match manifest_type {
35 ManifestType::Cargo => parse_cargo_manifest(&file_path),
36 ManifestType::Npm => parse_npm_manifest(&file_path),
37 })
38 .filter_map(std::result::Result::ok)
39 .collect::<Vec<_>>();
40
41 Ok(manifest_paths)
42}
43
44fn parse_cargo_manifest(path: &Path) -> Result<Manifest> {
45 let m = cargo_toml::Manifest::from_path(path)
46 .with_context(|| format!("Failed to parse Cargo.toml at '{}'", path.display()))?;
47 let package = m.package.context("Not a package (only a workspace)")?;
48 let description = package.description().map(Into::into);
49
50 Ok(Manifest {
51 manifest_type: ManifestType::Cargo,
52 number_of_dependencies: m.dependencies.len(),
53 name: package.name.clone(),
54 description,
55 version: package.version().into(),
56 license: package.license().map(Into::into),
57 })
58}
59
60fn parse_npm_manifest(path: &Path) -> Result<Manifest> {
61 let package = npm_package_json::Package::from_path(path)
62 .with_context(|| format!("Failed to parse package.json at '{}'", path.display()))?;
63 Ok(Manifest {
64 manifest_type: ManifestType::Npm,
65 number_of_dependencies: package.dependencies.len(),
66 name: package.name,
67 description: package.description,
68 version: package.version,
69 license: package.license,
70 })
71}
72
73fn file_name_to_manifest_type(filename: &str) -> Option<ManifestType> {
74 match filename {
75 "Cargo.toml" => Some(ManifestType::Cargo),
76 "package.json" => Some(ManifestType::Npm),
77 _ => None,
78 }
79}