znippy_common/plugins/native/
cargo_native.rs1use crate::plugin::{file_name, ArchiveTypePlugin, Col, ExtensionRow, ExtensionValue, Claim, HandlerSpec};
6use std::collections::HashMap;
7
8pub struct CargoPlugin {
11 pub parse_deps: bool,
13}
14
15impl CargoPlugin {
16 pub fn new() -> Self {
17 Self { parse_deps: false }
18 }
19
20 pub fn with_deps() -> Self {
21 Self { parse_deps: true }
22 }
23
24 fn parse_filename(path: &str) -> Option<(String, String)> {
26 let stem = file_name(path).strip_suffix(".crate")?;
27 let mut split_pos = None;
29 for (i, c) in stem.char_indices() {
30 if c == '-' {
31 if let Some(next) = stem[i+1..].chars().next() {
33 if next.is_ascii_digit() {
34 split_pos = Some(i);
35 }
36 }
37 }
38 }
39 let pos = split_pos?;
40 let name = &stem[..pos];
41 let version = &stem[pos+1..];
42 Some((name.to_string(), version.to_string()))
43 }
44
45 #[cfg(feature = "host-decompressors")]
53 fn parse_deps_from_tarball(data: &[u8]) -> Vec<String> {
54 let entries = match lgz::decompress_tar_gz_filter(data, "Cargo.toml") {
55 Ok(entries) => entries,
56 Err(_) => return Vec::new(),
57 };
58
59 for (path, bytes) in &entries {
60 if path.ends_with("/Cargo.toml") || path == "Cargo.toml" {
61 let contents = String::from_utf8_lossy(bytes);
62 return Self::extract_dep_names(&contents);
63 }
64 }
65 Vec::new()
66 }
67
68 #[cfg(feature = "host-decompressors")]
69 fn extract_dep_names(cargo_toml: &str) -> Vec<String> {
70 let mut deps = Vec::new();
71 let mut in_deps = false;
72 for line in cargo_toml.lines() {
73 let trimmed = line.trim();
74 if trimmed == "[dependencies]" {
75 in_deps = true;
76 } else if trimmed.starts_with('[') {
77 in_deps = false;
78 } else if in_deps {
79 if let Some(dep_name) = trimmed.split('=').next() {
80 let dep_name = dep_name.trim();
81 if !dep_name.is_empty() && !dep_name.starts_with('#') {
82 deps.push(dep_name.to_string());
83 }
84 }
85 }
86 }
87 deps
88 }
89}
90
91pub static CARGO_SPEC: HandlerSpec = HandlerSpec {
93 name: "cargo",
94 aliases: &["rust"],
95 type_id: 1,
96 ecosystem: "Rust / crates.io",
97 extensions: &[".crate"],
98 claim: Claim::Extensions,
99 description: "Rust crate registry tarballs — name + version from filename, deps from Cargo.toml",
100 fields: &[("crate_name", Col::Utf8), ("version", Col::Utf8), ("deps", Col::Utf8List)],
103 commands: &[],
104};
105
106impl ArchiveTypePlugin for CargoPlugin {
107 fn spec(&self) -> &HandlerSpec {
108 &CARGO_SPEC
109 }
110
111 fn extract_metadata(&self, path: &str, data: &[u8]) -> Option<ExtensionRow> {
112 let (crate_name, version) = Self::parse_filename(path)?;
113
114 let mut fields = HashMap::new();
115 fields.insert("crate_name".into(), ExtensionValue::Str(crate_name));
116 fields.insert("version".into(), ExtensionValue::Str(version));
117
118 #[cfg(feature = "host-decompressors")]
119 if self.parse_deps {
120 let deps = Self::parse_deps_from_tarball(data);
121 fields.insert("deps".into(), ExtensionValue::StrList(deps));
122 }
123
124 #[cfg(not(feature = "host-decompressors"))]
125 let _ = data; Some(ExtensionRow { fields })
128 }
129}