Skip to content

Commit ccab564

Browse files
committed
refactor
Signed-off-by: karthik2804 <[email protected]>
1 parent 6409a9a commit ccab564

File tree

9 files changed

+261
-205
lines changed

9 files changed

+261
-205
lines changed

src/commands/add.rs

Lines changed: 46 additions & 204 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,25 @@
1-
use anyhow::{bail, Context, Result};
1+
use anyhow::Result;
22
use clap::Subcommand;
3-
use dialoguer::{MultiSelect, Select};
43
use http::HttpAddCommand;
54
use local::LocalAddCommand;
65
use registry::RegistryAddCommand;
76
use spin_manifest::{
87
manifest_from_file,
9-
schema::v2::{AppManifest, ComponentDependencies, ComponentDependency},
8+
schema::v2::{AppManifest, ComponentDependency},
109
};
1110
use spin_serde::{DependencyName, DependencyPackageName, KebabId};
1211
use std::{collections::HashMap, path::PathBuf};
1312
use tokio::fs;
14-
use toml_edit::DocumentMut;
15-
use wit_component::WitPrinter;
1613
use wit_parser::{PackageId, Resolve};
1714

18-
const SPIN_WIT_DIRECTORY: &str = ".wit";
19-
const SPIN_COMPONENTS_WIT_DIRECTORY: &str = "components";
15+
use crate::common::{
16+
constants::{SPIN_COMPONENTS_WIT_DIRECTORY, SPIN_DEPS_WIT_FILE_NAME, SPIN_WIT_DIRECTORY},
17+
interact::{select_multiple_prompt, select_prompt},
18+
manifest::{edit_component_deps_in_manifest, get_component_ids, get_spin_manifest_path},
19+
wit::{
20+
get_exported_interfaces, merge_dependecy_package, parse_component_bytes, resolve_to_wit,
21+
},
22+
};
2023

2124
mod http;
2225
mod local;
@@ -40,60 +43,50 @@ impl AddCommand {
4043
AddCommand::Registry(cmd) => cmd.get_component().await?,
4144
};
4245

43-
self.validate_component(&component)?;
46+
let (mut resolve, main) = parse_component_bytes(component)?;
4447

4548
let mut manifest = manifest_from_file(get_spin_manifest_path()?)?;
46-
let component_ids = self.list_component_ids(&manifest);
47-
let selected_component = self.select_component(&component_ids)?;
49+
let component_ids = get_component_ids(&manifest);
50+
let selected_component_index = select_prompt(
51+
"Select a component to add the dependency to",
52+
&component_ids,
53+
None,
54+
)?;
55+
let selected_component = &component_ids[selected_component_index];
4856

49-
let decoded_wasm = wit_component::decode(&component)?;
50-
let mut resolve = decoded_wasm.resolve().clone();
51-
let main = decoded_wasm.package();
5257
let selected_interfaces = self.select_interfaces(&mut resolve, main)?;
5358

5459
resolve.importize(
5560
resolve.select_world(main, None)?,
5661
Some("dependency-world".to_string()),
5762
)?;
5863

59-
self.write_wit_to_file(&resolve, main, &selected_component)
60-
.await?;
61-
self.update_manifest(&mut manifest, &selected_component, selected_interfaces)
62-
.await?;
64+
let component_dir = PathBuf::from(SPIN_WIT_DIRECTORY)
65+
.join(SPIN_COMPONENTS_WIT_DIRECTORY)
66+
.join(selected_component);
6367

64-
Ok(())
65-
}
68+
let output_wit = component_dir.join(SPIN_DEPS_WIT_FILE_NAME);
6669

67-
/// List all component IDs in the manifest.
68-
fn list_component_ids(&self, manifest: &AppManifest) -> Vec<String> {
69-
manifest.components.keys().map(|k| k.to_string()).collect()
70-
}
70+
let base_resolve_file = if std::fs::exists(&output_wit)? {
71+
Some(&output_wit)
72+
} else {
73+
None
74+
};
7175

72-
/// Prompts the user to select a component from a list.
73-
fn select_component(&self, component_ids: &[String]) -> Result<String> {
74-
let selected_component_index = Select::new()
75-
.with_prompt("Select a component")
76-
.items(component_ids)
77-
.default(0)
78-
.interact()?;
76+
let (merged_resolve, main) = merge_dependecy_package(base_resolve_file, &resolve, main)?;
77+
let wit_text = resolve_to_wit(&merged_resolve, main)?;
78+
fs::write(output_wit, wit_text).await?;
7979

80-
Ok(component_ids[selected_component_index].clone())
81-
}
80+
self.update_manifest(&mut manifest, selected_component, selected_interfaces)
81+
.await?;
8282

83-
/// Validates the WebAssembly component.
84-
fn validate_component(&self, component: &[u8]) -> Result<()> {
85-
let t = wasmparser::validate(component)
86-
.context("Provided component does not seem to be a valid component");
87-
match Result::from(t) {
88-
Ok(_) => Ok(()),
89-
Err(e) => bail!(e),
90-
}
83+
Ok(())
9184
}
9285

9386
/// Prompts the user to select an interface to import.
9487
fn select_interfaces(&self, resolve: &mut Resolve, main: PackageId) -> Result<Vec<String>> {
9588
let world_id = resolve.select_world(main, None)?;
96-
let exported_interfaces = self.get_exported_interfaces(resolve, world_id);
89+
let exported_interfaces = get_exported_interfaces(resolve, world_id);
9790

9891
let mut package_interface_map: HashMap<String, Vec<String>> = HashMap::new();
9992
let mut selected_interfaces: Vec<String> = Vec::new();
@@ -106,15 +99,15 @@ impl AddCommand {
10699
.push(interface);
107100
}
108101

109-
let package_names: Vec<_> = package_interface_map.keys().collect();
102+
let package_names: Vec<_> = package_interface_map.keys().cloned().collect();
110103

111-
let selected_package_indices = MultiSelect::new()
112-
.with_prompt("Select packages to import (use space to select, enter to confirm)")
113-
.items(&package_names)
114-
.interact()?;
104+
let selected_package_indices = select_multiple_prompt(
105+
"Select packages to import (use space to select, enter to confirm)",
106+
&package_names,
107+
)?;
115108

116109
for &package_idx in selected_package_indices.iter() {
117-
let package_name = package_names[package_idx];
110+
let package_name = &package_names[package_idx];
118111
let interfaces = package_interface_map.get(package_name).unwrap();
119112
let interface_count = interfaces.len();
120113

@@ -128,14 +121,14 @@ impl AddCommand {
128121
};
129122

130123
// Prompt user to select an interface
131-
let selected_interface_idx = Select::new()
132-
.with_prompt(format!(
124+
let selected_interface_idx = select_prompt(
125+
&format!(
133126
"Select one or all interfaces to import from package '{}'",
134127
package_name
135-
))
136-
.default(0)
137-
.items(&interface_options)
138-
.interact()?;
128+
),
129+
&interface_options,
130+
Some(0),
131+
)?;
139132

140133
if interface_count > 1 && selected_interface_idx == 0 {
141134
selected_interfaces.push(package_name.clone());
@@ -148,67 +141,6 @@ impl AddCommand {
148141
Ok(selected_interfaces)
149142
}
150143

151-
/// Retrieves the exported interfaces from the resolved world.
152-
fn get_exported_interfaces(
153-
&self,
154-
resolve: &Resolve,
155-
world_id: wit_parser::WorldId,
156-
) -> Vec<(String, String)> {
157-
resolve.worlds[world_id]
158-
.exports
159-
.iter()
160-
.filter_map(|(_k, v)| match v {
161-
wit_parser::WorldItem::Interface { id, .. } => {
162-
let i = &resolve.interfaces[*id];
163-
let pkg_id = i.package.unwrap();
164-
let pkg = &resolve.packages[pkg_id];
165-
let mut pkg_name = format!("{}:{}", pkg.name.namespace, pkg.name.name);
166-
if let Some(ver) = &pkg.name.version {
167-
pkg_name.push_str(&format!("@{}", ver));
168-
}
169-
Some((pkg_name, i.name.clone().unwrap_or_default()))
170-
}
171-
_ => None,
172-
})
173-
.collect()
174-
}
175-
176-
/// Writes the WIT content to the specified file.
177-
async fn write_wit_to_file(
178-
&self,
179-
dep_resolve: &Resolve,
180-
dep_pkg_id: PackageId,
181-
selected_component: &str,
182-
) -> Result<()> {
183-
const SPIN_DEPS_WIT_FILE_NAME: &str = "deps.wit";
184-
185-
let component_dir = PathBuf::from(SPIN_WIT_DIRECTORY)
186-
.join(SPIN_COMPONENTS_WIT_DIRECTORY)
187-
.join(selected_component);
188-
189-
let output_wit = component_dir.join(SPIN_DEPS_WIT_FILE_NAME);
190-
let mut resolve = Resolve::default();
191-
192-
let deps_package_id = if std::fs::exists(&output_wit)? {
193-
resolve.push_file(&output_wit)?
194-
} else {
195-
fs::create_dir_all(&component_dir).await?;
196-
resolve.push_str("component.wit", DEFAULT_WIT)?
197-
};
198-
199-
let deps_world_id = resolve.select_world(deps_package_id, Some("deps"))?;
200-
let dep_main_world_id = dep_resolve.select_world(dep_pkg_id, Some("dependency-world"))?;
201-
let remap = resolve.merge(dep_resolve.clone())?;
202-
let dependecy_world_id = remap.map_world(dep_main_world_id, None)?;
203-
resolve.merge_worlds(dependecy_world_id, deps_world_id)?;
204-
205-
let wit_content = resolve_to_wit(&resolve, deps_package_id)?;
206-
207-
fs::write(output_wit, wit_content).await?;
208-
209-
Ok(())
210-
}
211-
212144
/// Updates the manifest file with the new component dependency.
213145
async fn update_manifest(
214146
&self,
@@ -253,93 +185,3 @@ impl AddCommand {
253185
Ok(())
254186
}
255187
}
256-
257-
/// Converts a Resolve object to WIT content.
258-
fn resolve_to_wit(resolve: &Resolve, package_id: PackageId) -> Result<String> {
259-
let mut printer = WitPrinter::default();
260-
printer.emit_docs(false);
261-
262-
let ids = resolve
263-
.packages
264-
.iter()
265-
.map(|(id, _)| id)
266-
.filter(|id| *id != package_id)
267-
.collect::<Vec<_>>();
268-
269-
printer.print(resolve, package_id, &ids)
270-
}
271-
272-
// This is a helper function to edit the dependency table in the manifest file
273-
// while preserving the order of the manifest.
274-
async fn edit_component_deps_in_manifest(
275-
component_id: &str,
276-
component_deps: &ComponentDependencies,
277-
) -> Result<String> {
278-
let manifest_path = get_spin_manifest_path()?;
279-
let manifest = fs::read_to_string(manifest_path).await?;
280-
let mut doc = manifest.parse::<DocumentMut>()?;
281-
282-
let mut dependencies_table = toml_edit::Table::new();
283-
284-
for (name, dep) in &component_deps.inner {
285-
let dep_src = match dep {
286-
ComponentDependency::Version(version) => {
287-
let mut ver_table = toml_edit::InlineTable::default();
288-
ver_table.get_or_insert("version", version);
289-
toml_edit::Value::InlineTable(ver_table)
290-
}
291-
ComponentDependency::Package {
292-
version,
293-
registry,
294-
package,
295-
export: _,
296-
} => {
297-
let mut pkg_table = toml_edit::InlineTable::default();
298-
pkg_table.get_or_insert("version", version);
299-
if let Some(reg) = registry.clone() {
300-
pkg_table.get_or_insert("registry", reg.to_string());
301-
}
302-
if let Some(pkg) = package {
303-
pkg_table.get_or_insert("package", pkg);
304-
}
305-
toml_edit::Value::InlineTable(pkg_table)
306-
}
307-
ComponentDependency::Local { path, export: _ } => {
308-
let mut local_table = toml_edit::InlineTable::default();
309-
local_table.get_or_insert("path", path.to_str().unwrap().to_owned());
310-
toml_edit::Value::InlineTable(local_table)
311-
}
312-
ComponentDependency::HTTP {
313-
url,
314-
digest,
315-
export: _,
316-
} => {
317-
let mut http_table = toml_edit::InlineTable::default();
318-
http_table.get_or_insert("url", url);
319-
http_table.get_or_insert("digest", digest);
320-
toml_edit::Value::InlineTable(http_table)
321-
}
322-
};
323-
324-
dependencies_table.insert(&name.to_string(), toml_edit::Item::Value(dep_src.clone()));
325-
}
326-
327-
doc["component"][component_id]["dependencies"] = toml_edit::Item::Table(dependencies_table);
328-
329-
Ok(doc.to_string())
330-
}
331-
332-
// TODO: Eventually bring this function with the proposed Spin functionality of searching in parent Directories.
333-
fn get_spin_manifest_path() -> Result<PathBuf> {
334-
let manifest_path = PathBuf::from("spin.toml");
335-
if !manifest_path.exists() {
336-
bail!("No spin.toml file found in the current directory");
337-
}
338-
Ok(manifest_path)
339-
}
340-
341-
const DEFAULT_WIT: &str = r#"package spin-deps:[email protected];
342-
343-
world deps {
344-
}
345-
"#;

src/commands/bindings.rs

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
use anyhow::Result;
2+
use clap::{Args, ValueEnum};
3+
4+
#[derive(Debug, Clone, ValueEnum)]
5+
pub enum BindingsLanguage {
6+
Ts,
7+
Rust,
8+
}
9+
10+
#[derive(Args, Debug)]
11+
pub struct BindingsCommand {
12+
pub lang: Option<BindingsLanguage>,
13+
pub component_id: Option<String>,
14+
}
15+
16+
impl BindingsCommand {
17+
pub async fn run(&self) -> Result<()> {
18+
Ok(())
19+
}
20+
}

src/commands/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,2 @@
11
pub mod add;
2+
pub mod bindings;

src/common/constants.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
pub const SPIN_WIT_DIRECTORY: &str = ".wit";
2+
pub const SPIN_COMPONENTS_WIT_DIRECTORY: &str = "components";
3+
pub const SPIN_DEPS_WIT_FILE_NAME: &str = "deps.wit";

src/common/interact.rs

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
use anyhow::Result;
2+
3+
use dialoguer::{MultiSelect, Select};
4+
5+
pub fn select_prompt(
6+
prompt: &str,
7+
selection_list: &[String],
8+
default: Option<usize>,
9+
) -> Result<usize> {
10+
let mut select = Select::new().with_prompt(prompt).items(selection_list);
11+
if let Some(index) = default {
12+
select = select.default(index);
13+
}
14+
Ok(select.interact()?)
15+
}
16+
17+
pub fn select_multiple_prompt(prompt: &str, selection_list: &[String]) -> Result<Vec<usize>> {
18+
Ok(MultiSelect::new()
19+
.with_prompt(prompt)
20+
.items(selection_list)
21+
.interact()?)
22+
}

0 commit comments

Comments
 (0)