Skip to content

Commit c76a5c9

Browse files
authored
Ability to clear metadata fields (#2216)
* Ability to clear metadata fields * Format * Better CLI interface for add metadata * New test case
1 parent 225d2d6 commit c76a5c9

File tree

10 files changed

+446
-91
lines changed

10 files changed

+446
-91
lines changed
Lines changed: 38 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,72 +1,54 @@
11
use crate::{rewrite_wasm, Producers};
22
use anyhow::Result;
3+
use std::fmt::Debug;
34

45
/// Add metadata (module name, producers) to a WebAssembly file.
56
///
67
/// Supports both core WebAssembly modules and components. In components,
78
/// metadata will be added to the outermost component.
8-
#[cfg_attr(feature = "clap", derive(clap::Parser))]
99
#[derive(Debug, Clone, Default)]
1010
#[non_exhaustive]
1111
pub struct AddMetadata {
1212
/// Add a module or component name to the names section
13-
#[cfg_attr(feature = "clap", clap(long, value_name = "NAME"))]
14-
pub name: Option<String>,
13+
pub name: AddMetadataField<String>,
1514

1615
/// Add a programming language to the producers section
17-
#[cfg_attr(feature = "clap", clap(long, value_parser = parse_key_value, value_name = "NAME=VERSION"))]
1816
pub language: Vec<(String, String)>,
1917

2018
/// Add a tool and its version to the producers section
21-
#[cfg_attr(feature = "clap", clap(long = "processed-by", value_parser = parse_key_value, value_name="NAME=VERSION"))]
2219
pub processed_by: Vec<(String, String)>,
2320

2421
/// Add an SDK and its version to the producers section
25-
#[cfg_attr(feature="clap", clap(long, value_parser = parse_key_value, value_name="NAME=VERSION"))]
2622
pub sdk: Vec<(String, String)>,
2723

2824
/// Contact details of the people or organization responsible,
2925
/// encoded as a freeform string.
30-
#[cfg_attr(feature = "clap", clap(long, value_name = "NAME"))]
3126
#[cfg(feature = "oci")]
32-
pub authors: Option<crate::Authors>,
27+
pub authors: AddMetadataField<crate::Authors>,
3328

3429
/// A human-readable description of the binary
35-
#[cfg_attr(feature = "clap", clap(long, value_name = "NAME"))]
3630
#[cfg(feature = "oci")]
37-
pub description: Option<crate::Description>,
31+
pub description: AddMetadataField<crate::Description>,
3832

3933
/// License(s) under which contained software is distributed as an SPDX License Expression.
40-
#[cfg_attr(feature = "clap", clap(long, value_name = "NAME"))]
4134
#[cfg(feature = "oci")]
42-
pub licenses: Option<crate::Licenses>,
35+
pub licenses: AddMetadataField<crate::Licenses>,
4336

4437
/// URL to get source code for building the image
45-
#[cfg_attr(feature = "clap", clap(long, value_name = "NAME"))]
4638
#[cfg(feature = "oci")]
47-
pub source: Option<crate::Source>,
39+
pub source: AddMetadataField<crate::Source>,
4840

4941
/// URL to find more information on the binary
50-
#[cfg_attr(feature = "clap", clap(long, value_name = "NAME"))]
5142
#[cfg(feature = "oci")]
52-
pub homepage: Option<crate::Homepage>,
43+
pub homepage: AddMetadataField<crate::Homepage>,
5344

5445
/// Source control revision identifier for the packaged software.
55-
#[cfg_attr(feature = "clap", clap(long, value_name = "NAME"))]
5646
#[cfg(feature = "oci")]
57-
pub revision: Option<crate::Revision>,
47+
pub revision: AddMetadataField<crate::Revision>,
5848

5949
/// Version of the packaged software
60-
#[cfg_attr(feature = "clap", clap(long, value_name = "NAME"))]
6150
#[cfg(feature = "oci")]
62-
pub version: Option<crate::Version>,
63-
}
64-
65-
#[cfg(feature = "clap")]
66-
pub(crate) fn parse_key_value(s: &str) -> Result<(String, String)> {
67-
s.split_once('=')
68-
.map(|(k, v)| (k.to_owned(), v.to_owned()))
69-
.ok_or_else(|| anyhow::anyhow!("expected KEY=VALUE"))
51+
pub version: AddMetadataField<crate::Version>,
7052
}
7153

7254
impl AddMetadata {
@@ -78,3 +60,32 @@ impl AddMetadata {
7860
rewrite_wasm(self, &add_producers, input)
7961
}
8062
}
63+
64+
/// Defines how to modify a field of the component/module metadata
65+
#[derive(Debug, Clone)]
66+
pub enum AddMetadataField<T: Debug + Clone> {
67+
/// Keep the existing value of the field
68+
Keep,
69+
/// Remove the existing value of the field
70+
Clear,
71+
/// Set the field to a new value
72+
Set(T),
73+
}
74+
75+
impl<T: Debug + Clone> AddMetadataField<T> {
76+
/// Returns true if the field should be cleared
77+
pub fn is_clear(&self) -> bool {
78+
matches!(self, Self::Clear)
79+
}
80+
81+
/// Returns true if the field should be kept
82+
pub fn is_keep(&self) -> bool {
83+
matches!(self, Self::Keep)
84+
}
85+
}
86+
87+
impl<T: Debug + Clone> Default for AddMetadataField<T> {
88+
fn default() -> Self {
89+
Self::Keep
90+
}
91+
}

crates/wasm-metadata/src/clap.rs

Lines changed: 169 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,169 @@
1+
use crate::AddMetadata;
2+
use std::fmt::Debug;
3+
4+
/// Add metadata (module name, producers) to a WebAssembly file.
5+
///
6+
/// Supports both core WebAssembly modules and components. In components,
7+
/// metadata will be added to the outermost component.
8+
#[derive(clap::Parser, Debug, Clone, Default)]
9+
#[non_exhaustive]
10+
pub struct AddMetadataOpts {
11+
/// Add a module or component name to the names section
12+
#[clap(long, value_name = "NAME", conflicts_with = "clear_name")]
13+
pub name: Option<String>,
14+
15+
/// Remove a module or component name from the names section
16+
#[clap(long, conflicts_with = "name")]
17+
pub clear_name: bool,
18+
19+
/// Add a programming language to the producers section
20+
#[clap(long, value_parser = parse_key_value, value_name = "NAME=VERSION")]
21+
pub language: Vec<(String, String)>,
22+
23+
/// Add a tool and its version to the producers section
24+
#[clap(long = "processed-by", value_parser = parse_key_value, value_name="NAME=VERSION")]
25+
pub processed_by: Vec<(String, String)>,
26+
27+
/// Add an SDK and its version to the producers section
28+
#[clap(long, value_parser = parse_key_value, value_name="NAME=VERSION")]
29+
pub sdk: Vec<(String, String)>,
30+
31+
/// Contact details of the people or organization responsible,
32+
/// encoded as a freeform string.
33+
#[clap(long, value_name = "NAME", conflicts_with = "clear_authors")]
34+
#[cfg(feature = "oci")]
35+
pub authors: Option<crate::Authors>,
36+
37+
/// Remove authors from the metadata
38+
#[clap(long, conflicts_with = "authors")]
39+
#[cfg(feature = "oci")]
40+
pub clear_authors: bool,
41+
42+
/// A human-readable description of the binary
43+
#[clap(long, value_name = "NAME", conflicts_with = "clear_description")]
44+
#[cfg(feature = "oci")]
45+
pub description: Option<crate::Description>,
46+
47+
/// Remove description from the metadata
48+
#[clap(long, conflicts_with = "description")]
49+
#[cfg(feature = "oci")]
50+
pub clear_description: bool,
51+
52+
/// License(s) under which contained software is distributed as an SPDX License Expression.
53+
#[clap(long, value_name = "NAME", conflicts_with = "clear_licenses")]
54+
#[cfg(feature = "oci")]
55+
pub licenses: Option<crate::Licenses>,
56+
57+
/// Remove licenses from the metadata
58+
#[clap(long, conflicts_with = "licenses")]
59+
#[cfg(feature = "oci")]
60+
pub clear_licenses: bool,
61+
62+
/// URL to get source code for building the image
63+
#[clap(long, value_name = "NAME", conflicts_with = "clear_source")]
64+
#[cfg(feature = "oci")]
65+
pub source: Option<crate::Source>,
66+
67+
/// Remove source from the metadata
68+
#[clap(long, conflicts_with = "source")]
69+
#[cfg(feature = "oci")]
70+
pub clear_source: bool,
71+
72+
/// URL to find more information on the binary
73+
#[clap(long, value_name = "NAME", conflicts_with = "clear_homepage")]
74+
#[cfg(feature = "oci")]
75+
pub homepage: Option<crate::Homepage>,
76+
77+
/// Remove homepage from the metadata
78+
#[clap(long, conflicts_with = "homepage")]
79+
#[cfg(feature = "oci")]
80+
pub clear_homepage: bool,
81+
82+
/// Source control revision identifier for the packaged software.
83+
#[clap(long, value_name = "NAME", conflicts_with = "clear_revision")]
84+
#[cfg(feature = "oci")]
85+
pub revision: Option<crate::Revision>,
86+
87+
/// Remove revision from the metadata
88+
#[clap(long, conflicts_with = "revision")]
89+
#[cfg(feature = "oci")]
90+
pub clear_revision: bool,
91+
92+
/// Version of the packaged software
93+
#[clap(long, value_name = "NAME", conflicts_with = "clear_version")]
94+
#[cfg(feature = "oci")]
95+
pub version: Option<crate::Version>,
96+
97+
/// Remove version from the metadata
98+
#[clap(long, conflicts_with = "version")]
99+
#[cfg(feature = "oci")]
100+
pub clear_version: bool,
101+
}
102+
103+
pub(crate) fn parse_key_value(s: &str) -> anyhow::Result<(String, String)> {
104+
s.split_once('=')
105+
.map(|(k, v)| (k.to_owned(), v.to_owned()))
106+
.ok_or_else(|| anyhow::anyhow!("expected KEY=VALUE"))
107+
}
108+
109+
impl From<AddMetadataOpts> for AddMetadata {
110+
fn from(value: AddMetadataOpts) -> Self {
111+
let mut add = AddMetadata::default();
112+
if let Some(name) = value.name {
113+
add.name = crate::AddMetadataField::Set(name);
114+
} else if value.clear_name {
115+
add.name = crate::AddMetadataField::Clear;
116+
}
117+
118+
add.language = value.language;
119+
add.processed_by = value.processed_by;
120+
add.sdk = value.sdk;
121+
122+
#[cfg(feature = "oci")]
123+
{
124+
if let Some(authors) = value.authors {
125+
add.authors = crate::AddMetadataField::Set(authors);
126+
} else if value.clear_authors {
127+
add.authors = crate::AddMetadataField::Clear;
128+
}
129+
130+
if let Some(description) = value.description {
131+
add.description = crate::AddMetadataField::Set(description);
132+
} else if value.clear_description {
133+
add.description = crate::AddMetadataField::Clear;
134+
}
135+
136+
if let Some(licenses) = value.licenses {
137+
add.licenses = crate::AddMetadataField::Set(licenses);
138+
} else if value.clear_licenses {
139+
add.licenses = crate::AddMetadataField::Clear;
140+
}
141+
142+
if let Some(source) = value.source {
143+
add.source = crate::AddMetadataField::Set(source);
144+
} else if value.clear_source {
145+
add.source = crate::AddMetadataField::Clear;
146+
}
147+
148+
if let Some(homepage) = value.homepage {
149+
add.homepage = crate::AddMetadataField::Set(homepage);
150+
} else if value.clear_homepage {
151+
add.homepage = crate::AddMetadataField::Clear;
152+
}
153+
154+
if let Some(revision) = value.revision {
155+
add.revision = crate::AddMetadataField::Set(revision);
156+
} else if value.clear_revision {
157+
add.revision = crate::AddMetadataField::Clear;
158+
}
159+
160+
if let Some(version) = value.version {
161+
add.version = crate::AddMetadataField::Set(version);
162+
} else if value.clear_version {
163+
add.version = crate::AddMetadataField::Clear;
164+
}
165+
}
166+
167+
add
168+
}
169+
}

crates/wasm-metadata/src/lib.rs

Lines changed: 15 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -26,17 +26,17 @@
2626
//! let wasm = fs::read("program.wasm")?;
2727
//!
2828
//! let mut add = AddMetadata ::default();
29-
//! add.name = Some("program".to_owned());
29+
//! add.name = AddMetadataField::Set("program".to_owned());
3030
//! add.language = vec![("tunalang".to_owned(), "1.0.0".to_owned())];
3131
//! add.processed_by = vec![("chashu-tools".to_owned(), "1.0.1".to_owned())];
3232
//! add.sdk = vec![];
33-
//! add.authors = Some(Authors::new("Chashu Cat"));
34-
//! add.description = Some(Description::new("Chashu likes tuna"));
35-
//! add.licenses = Some(Licenses::new("Apache-2.0 WITH LLVM-exception")?);
36-
//! add.source = Some(Source::new("https://github.com/chashu/chashu-tools")?);
37-
//! add.homepage = Some(Homepage::new("https://github.com/chashu/chashu-tools")?);
38-
//! add.revision = Some(Revision::new("de978e17a80c1118f606fce919ba9b7d5a04a5ad"));
39-
//! add.version = Some(Version::new("1.0.0"));
33+
//! add.authors = AddMetadataField::Set(Authors::new("Chashu Cat"));
34+
//! add.description = AddMetadataField::Set(Description::new("Chashu likes tuna"));
35+
//! add.licenses = AddMetadataField::Set(Licenses::new("Apache-2.0 WITH LLVM-exception")?);
36+
//! add.source = AddMetadataField::Set(Source::new("https://github.com/chashu/chashu-tools")?);
37+
//! add.homepage = AddMetadataField::Set(Homepage::new("https://github.com/chashu/chashu-tools")?);
38+
//! add.revision = AddMetadataField::Set(Revision::new("de978e17a80c1118f606fce919ba9b7d5a04a5ad"));
39+
//! add.version = AddMetadataField::Set(Version::new("1.0.0"));
4040
//!
4141
//! let wasm = add.to_wasm(&wasm)?;
4242
//! fs::write("program.wasm", &wasm)?;
@@ -46,13 +46,15 @@
4646
#![cfg_attr(docsrs, feature(doc_auto_cfg))]
4747
#![warn(missing_debug_implementations, missing_docs)]
4848

49-
pub use add_metadata::AddMetadata;
49+
pub use add_metadata::{AddMetadata, AddMetadataField};
5050
pub use names::{ComponentNames, ModuleNames};
5151
pub use producers::{Producers, ProducersField};
5252

5353
pub(crate) use rewrite::rewrite_wasm;
5454

5555
mod add_metadata;
56+
#[cfg(feature = "clap")]
57+
mod clap;
5658
mod names;
5759
mod producers;
5860
mod rewrite;
@@ -73,5 +75,9 @@ mod metadata;
7375
pub use metadata::Metadata;
7476
#[cfg(feature = "oci")]
7577
mod payload;
78+
7679
#[cfg(feature = "oci")]
7780
pub use payload::Payload;
81+
82+
#[cfg(feature = "clap")]
83+
pub use clap::AddMetadataOpts;

0 commit comments

Comments
 (0)