Skip to content

Commit ecb8dc1

Browse files
committed
Extract and unify command execution logic
Created a unified CommandExecutor trait to replace specialized executors for `bundler` and `gemset` to simplify the code and prepare the extension for Debugger API.
1 parent 2bd9e29 commit ecb8dc1

File tree

6 files changed

+148
-150
lines changed

6 files changed

+148
-150
lines changed

src/bundler.rs

Lines changed: 45 additions & 70 deletions
Original file line numberDiff line numberDiff line change
@@ -1,34 +1,6 @@
1+
use crate::command_executor::CommandExecutor;
12
use std::path::Path;
2-
use zed_extension_api::{process::Output, Command, Result};
3-
4-
pub trait CommandExecutor {
5-
fn execute_bundle(
6-
&self,
7-
sub_command: String,
8-
args: Vec<String>,
9-
envs: Vec<(String, String)>,
10-
bundle_gemfile_path: &str,
11-
) -> Result<Output>;
12-
}
13-
14-
pub struct RealCommandExecutor;
15-
16-
impl CommandExecutor for RealCommandExecutor {
17-
fn execute_bundle(
18-
&self,
19-
sub_command: String,
20-
args: Vec<String>,
21-
envs: Vec<(String, String)>,
22-
bundle_gemfile_path: &str,
23-
) -> Result<Output> {
24-
Command::new("bundle")
25-
.arg(sub_command)
26-
.args(args)
27-
.envs(envs)
28-
.env("BUNDLE_GEMFILE", bundle_gemfile_path)
29-
.output()
30-
}
31-
}
3+
use zed_extension_api::Result;
324

335
/// A simple wrapper around the `bundle` command.
346
pub struct Bundler {
@@ -74,8 +46,19 @@ impl Bundler {
7446
.to_str()
7547
.ok_or_else(|| "Invalid path to Gemfile".to_string())?;
7648

49+
let full_args: Vec<String> = std::iter::once(cmd).chain(args).collect();
50+
let command_envs: Vec<(String, String)> = self
51+
.envs
52+
.iter()
53+
.cloned()
54+
.chain(std::iter::once((
55+
"BUNDLE_GEMFILE".to_string(),
56+
bundle_gemfile.to_string(),
57+
)))
58+
.collect();
59+
7760
self.command_executor
78-
.execute_bundle(cmd, args, self.envs.clone(), bundle_gemfile)
61+
.execute("bundle", full_args, command_envs)
7962
.and_then(|output| match output.status {
8063
Some(0) => Ok(String::from_utf8_lossy(&output.stdout).to_string()),
8164
Some(status) => {
@@ -96,14 +79,15 @@ impl Bundler {
9679
#[cfg(test)]
9780
mod tests {
9881
use super::*;
82+
use crate::command_executor::CommandExecutor;
9983
use std::cell::RefCell;
84+
use zed_extension_api::process::Output;
10085

10186
struct MockExecutorConfig {
10287
output_to_return: Option<Result<Output>>,
103-
expected_sub_command: Option<String>,
88+
expected_command_name: Option<String>,
10489
expected_args: Option<Vec<String>>,
10590
expected_envs: Option<Vec<(String, String)>>,
106-
expected_bundle_gemfile_path: Option<String>,
10791
}
10892

10993
struct MockCommandExecutor {
@@ -115,64 +99,55 @@ mod tests {
11599
MockCommandExecutor {
116100
config: RefCell::new(MockExecutorConfig {
117101
output_to_return: None,
118-
expected_sub_command: None,
102+
expected_command_name: None,
119103
expected_args: None,
120104
expected_envs: None,
121-
expected_bundle_gemfile_path: None,
122105
}),
123106
}
124107
}
125108

126109
fn expect(
127110
&self,
128-
sub_command: &str,
129-
args: &[&str],
130-
envs: &[(&str, &str)],
131-
bundle_gemfile_path: &str,
132-
output: super::Result<Output>,
111+
command_name: &str,
112+
full_args: &[&str],
113+
final_envs: &[(&str, &str)],
114+
output: Result<Output>,
133115
) {
134116
let mut config = self.config.borrow_mut();
135-
config.expected_sub_command = Some(sub_command.to_string());
136-
config.expected_args = Some(args.iter().map(|s| s.to_string()).collect());
117+
config.expected_command_name = Some(command_name.to_string());
118+
config.expected_args = Some(full_args.iter().map(|s| s.to_string()).collect());
137119
config.expected_envs = Some(
138-
envs.iter()
120+
final_envs
121+
.iter()
139122
.map(|&(k, v)| (k.to_string(), v.to_string()))
140123
.collect(),
141124
);
142-
config.expected_bundle_gemfile_path = Some(bundle_gemfile_path.to_string());
143125
config.output_to_return = Some(output);
144126
}
145127
}
146128

147129
impl CommandExecutor for MockCommandExecutor {
148-
fn execute_bundle(
130+
fn execute(
149131
&self,
150-
sub_command: String,
132+
command_name: &str,
151133
args: Vec<String>,
152134
envs: Vec<(String, String)>,
153-
bundle_gemfile_path: &str,
154-
) -> super::Result<Output> {
135+
) -> Result<Output> {
155136
let mut config = self.config.borrow_mut();
156137

157-
if let Some(expected_cmd) = &config.expected_sub_command {
158-
assert_eq!(&sub_command, expected_cmd, "Mock: Sub-command mismatch");
138+
if let Some(expected_name) = &config.expected_command_name {
139+
assert_eq!(command_name, expected_name, "Mock: Command name mismatch");
159140
}
160141
if let Some(expected_args) = &config.expected_args {
161142
assert_eq!(&args, expected_args, "Mock: Args mismatch");
162143
}
163144
if let Some(expected_envs) = &config.expected_envs {
164145
assert_eq!(&envs, expected_envs, "Mock: Env mismatch");
165146
}
166-
if let Some(expected_path) = &config.expected_bundle_gemfile_path {
167-
assert_eq!(
168-
bundle_gemfile_path, expected_path,
169-
"Mock: Gemfile path mismatch"
170-
);
171-
}
172147

173148
config.output_to_return.take().expect(
174-
"MockCommandExecutor: output_to_return was not set or already consumed for the test",
175-
)
149+
"MockCommandExecutor: output_to_return was not set or already consumed for the test",
150+
)
176151
}
177152
}
178153

@@ -182,11 +157,11 @@ mod tests {
182157
gem: &str,
183158
) -> MockCommandExecutor {
184159
let mock = MockCommandExecutor::new();
160+
let gemfile_path = format!("{}/Gemfile", dir);
185161
mock.expect(
186-
"info",
187-
&["--version", gem],
188-
&[],
189-
&format!("{}/Gemfile", dir),
162+
"bundle",
163+
&["info", "--version", gem],
164+
&[("BUNDLE_GEMFILE", &gemfile_path)],
190165
Ok(Output {
191166
status: Some(0),
192167
stdout: version.as_bytes().to_vec(),
@@ -211,12 +186,12 @@ mod tests {
211186
let mock_executor = MockCommandExecutor::new();
212187
let gem_name = "unknown_gem";
213188
let error_output = "Could not find gem 'unknown_gem'.";
189+
let gemfile_path = "test_dir/Gemfile";
214190

215191
mock_executor.expect(
216-
"info",
217-
&["--version", gem_name],
218-
&[],
219-
"test_dir/Gemfile",
192+
"bundle",
193+
&["info", "--version", gem_name],
194+
&[("BUNDLE_GEMFILE", gemfile_path)],
220195
Ok(Output {
221196
status: Some(1),
222197
stdout: Vec::new(),
@@ -247,12 +222,12 @@ mod tests {
247222
let mock_executor = MockCommandExecutor::new();
248223
let gem_name = "critical_gem";
249224
let specific_error_msg = "Mocked execution failure";
225+
let gemfile_path = "test_dir/Gemfile";
250226

251227
mock_executor.expect(
252-
"info",
253-
&["--version", gem_name],
254-
&[],
255-
"test_dir/Gemfile",
228+
"bundle",
229+
&["info", "--version", gem_name],
230+
&[("BUNDLE_GEMFILE", gemfile_path)],
256231
Err(specific_error_msg.to_string()),
257232
);
258233

src/command_executor.rs

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
use zed_extension_api::{process::Output, Command as ZedCommand, Result};
2+
3+
pub trait CommandExecutor {
4+
fn execute(
5+
&self,
6+
command_name: &str,
7+
args: Vec<String>,
8+
envs: Vec<(String, String)>,
9+
) -> Result<Output>;
10+
}
11+
12+
pub struct RealCommandExecutor;
13+
14+
impl CommandExecutor for RealCommandExecutor {
15+
fn execute(
16+
&self,
17+
command_name: &str,
18+
args: Vec<String>,
19+
envs: Vec<(String, String)>,
20+
) -> Result<Output> {
21+
ZedCommand::new(command_name).args(args).envs(envs).output()
22+
}
23+
}

0 commit comments

Comments
 (0)