use std::fmt;
use std::path::PathBuf;
use crate::commands::CommandExecutor;
use cuenv_core::{DryRun, OutputCapture};
#[derive(Clone)]
pub struct TaskExecutionRequest<'a> {
pub path: String,
pub package: String,
pub selection: TaskSelection,
pub environment: Option<String>,
pub output: OutputConfig,
pub execution_mode: ExecutionMode,
pub backend: Option<String>,
pub skip_dependencies: bool,
pub dry_run: DryRun,
pub executor: &'a CommandExecutor,
}
impl fmt::Debug for TaskExecutionRequest<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("TaskExecutionRequest")
.field("path", &self.path)
.field("package", &self.package)
.field("selection", &self.selection)
.field("environment", &self.environment)
.field("output", &self.output)
.field("execution_mode", &self.execution_mode)
.field("backend", &self.backend)
.field("skip_dependencies", &self.skip_dependencies)
.field("dry_run", &self.dry_run)
.field("executor", &"<CommandExecutor>")
.finish()
}
}
#[derive(Debug, Clone, Default)]
pub enum TaskSelection {
Named {
name: String,
args: Vec<String>,
},
Labels(Vec<String>),
#[default]
List,
Interactive,
}
#[derive(Debug, Clone)]
pub struct OutputConfig {
pub format: String,
pub capture_output: OutputCapture,
pub show_cache_path: bool,
pub materialize_outputs: Option<PathBuf>,
pub help: bool,
}
impl Default for OutputConfig {
fn default() -> Self {
Self {
format: "simple".to_string(),
capture_output: OutputCapture::Capture,
show_cache_path: false,
materialize_outputs: None,
help: false,
}
}
}
#[derive(Debug, Clone, Default, PartialEq, Eq)]
pub enum ExecutionMode {
#[default]
Simple,
Tui,
}
impl<'a> TaskExecutionRequest<'a> {
#[must_use]
pub fn list(
path: impl Into<String>,
package: impl Into<String>,
executor: &'a CommandExecutor,
) -> Self {
Self {
path: path.into(),
package: package.into(),
selection: TaskSelection::List,
environment: None,
output: OutputConfig::default(),
execution_mode: ExecutionMode::default(),
backend: None,
skip_dependencies: false,
dry_run: DryRun::No,
executor,
}
}
#[must_use]
pub fn named(
path: impl Into<String>,
package: impl Into<String>,
task_name: impl Into<String>,
executor: &'a CommandExecutor,
) -> Self {
Self {
path: path.into(),
package: package.into(),
selection: TaskSelection::Named {
name: task_name.into(),
args: Vec::new(),
},
environment: None,
output: OutputConfig::default(),
execution_mode: ExecutionMode::default(),
backend: None,
skip_dependencies: false,
dry_run: DryRun::No,
executor,
}
}
#[must_use]
pub fn labels(
path: impl Into<String>,
package: impl Into<String>,
labels: Vec<String>,
executor: &'a CommandExecutor,
) -> Self {
Self {
path: path.into(),
package: package.into(),
selection: TaskSelection::Labels(labels),
environment: None,
output: OutputConfig::default(),
execution_mode: ExecutionMode::default(),
backend: None,
skip_dependencies: false,
dry_run: DryRun::No,
executor,
}
}
#[must_use]
pub fn interactive(
path: impl Into<String>,
package: impl Into<String>,
executor: &'a CommandExecutor,
) -> Self {
Self {
path: path.into(),
package: package.into(),
selection: TaskSelection::Interactive,
environment: None,
output: OutputConfig::default(),
execution_mode: ExecutionMode::default(),
backend: None,
skip_dependencies: false,
dry_run: DryRun::No,
executor,
}
}
#[must_use]
pub fn with_args(mut self, args: Vec<String>) -> Self {
if let TaskSelection::Named { name, .. } = self.selection {
self.selection = TaskSelection::Named { name, args };
}
self
}
#[must_use]
pub fn with_environment(mut self, env: impl Into<String>) -> Self {
self.environment = Some(env.into());
self
}
#[must_use]
pub fn with_format(mut self, format: impl Into<String>) -> Self {
self.output.format = format.into();
self
}
#[must_use]
pub const fn with_capture(mut self) -> Self {
self.output.capture_output = OutputCapture::Capture;
self
}
#[must_use]
pub const fn with_tui(mut self) -> Self {
self.execution_mode = ExecutionMode::Tui;
self
}
#[must_use]
pub fn with_backend(mut self, backend: impl Into<String>) -> Self {
self.backend = Some(backend.into());
self
}
#[must_use]
pub const fn with_help(mut self) -> Self {
self.output.help = true;
self
}
#[must_use]
pub fn with_materialize_outputs(mut self, path: impl Into<PathBuf>) -> Self {
self.output.materialize_outputs = Some(path.into());
self
}
#[must_use]
pub const fn with_show_cache_path(mut self) -> Self {
self.output.show_cache_path = true;
self
}
#[must_use]
pub const fn with_skip_dependencies(mut self) -> Self {
self.skip_dependencies = true;
self
}
#[must_use]
pub const fn with_dry_run(mut self) -> Self {
self.dry_run = DryRun::Yes;
self
}
}
#[cfg(test)]
mod tests {
use super::*;
use tokio::sync::mpsc;
fn create_test_executor() -> CommandExecutor {
let (sender, _receiver) = mpsc::unbounded_channel();
CommandExecutor::new(sender, "cuenv".to_string())
}
#[test]
fn test_request_list() {
let executor = create_test_executor();
let req = TaskExecutionRequest::list("./", "cuenv", &executor);
assert_eq!(req.path, "./");
assert_eq!(req.package, "cuenv");
assert!(matches!(req.selection, TaskSelection::List));
}
#[test]
fn test_request_named() {
let executor = create_test_executor();
let req = TaskExecutionRequest::named("./", "cuenv", "build", &executor);
assert!(matches!(
req.selection,
TaskSelection::Named { ref name, .. } if name == "build"
));
}
#[test]
fn test_request_builder_methods() {
let executor = create_test_executor();
let req = TaskExecutionRequest::named("./", "cuenv", "test", &executor)
.with_args(vec!["--verbose".to_string()])
.with_environment("dev")
.with_format("json")
.with_capture()
.with_tui();
assert_eq!(req.environment, Some("dev".to_string()));
assert_eq!(req.output.format, "json");
assert!(req.output.capture_output.should_capture());
assert_eq!(req.execution_mode, ExecutionMode::Tui);
if let TaskSelection::Named { args, .. } = &req.selection {
assert_eq!(args, &vec!["--verbose".to_string()]);
} else {
panic!("Expected Named selection");
}
}
#[test]
fn test_named_task_with_args() {
let executor = create_test_executor();
let req = TaskExecutionRequest::named("./", "cuenv", "build", &executor)
.with_args(vec!["--release".to_string()])
.with_environment("prod")
.with_format("json")
.with_capture();
assert!(matches!(
req.selection,
TaskSelection::Named { ref name, ref args }
if name == "build" && args == &vec!["--release".to_string()]
));
assert_eq!(req.environment, Some("prod".to_string()));
assert!(req.output.capture_output.should_capture());
}
#[test]
fn test_tui_mode() {
let executor = create_test_executor();
let req = TaskExecutionRequest::named("./", "cuenv", "build", &executor).with_tui();
assert_eq!(req.execution_mode, ExecutionMode::Tui);
}
#[test]
fn test_skip_dependencies() {
let executor = create_test_executor();
let req =
TaskExecutionRequest::named("./", "cuenv", "build", &executor).with_skip_dependencies();
assert!(req.skip_dependencies);
}
#[test]
fn test_request_labels() {
let executor = create_test_executor();
let req = TaskExecutionRequest::labels(
"./",
"cuenv",
vec!["ci".to_string(), "fast".to_string()],
&executor,
);
assert!(matches!(
req.selection,
TaskSelection::Labels(ref labels) if labels == &vec!["ci".to_string(), "fast".to_string()]
));
}
#[test]
fn test_request_interactive() {
let executor = create_test_executor();
let req = TaskExecutionRequest::interactive("./", "cuenv", &executor);
assert!(matches!(req.selection, TaskSelection::Interactive));
}
#[test]
fn test_with_backend() {
let executor = create_test_executor();
let req =
TaskExecutionRequest::named("./", "cuenv", "build", &executor).with_backend("dagger");
assert_eq!(req.backend, Some("dagger".to_string()));
}
#[test]
fn test_with_help() {
let executor = create_test_executor();
let req = TaskExecutionRequest::named("./", "cuenv", "build", &executor).with_help();
assert!(req.output.help);
}
#[test]
fn test_with_materialize_outputs() {
let executor = create_test_executor();
let req = TaskExecutionRequest::named("./", "cuenv", "build", &executor)
.with_materialize_outputs("/tmp/outputs");
assert_eq!(
req.output.materialize_outputs,
Some(PathBuf::from("/tmp/outputs"))
);
}
#[test]
fn test_with_show_cache_path() {
let executor = create_test_executor();
let req =
TaskExecutionRequest::named("./", "cuenv", "build", &executor).with_show_cache_path();
assert!(req.output.show_cache_path);
}
#[test]
fn test_output_config_default() {
let config = OutputConfig::default();
assert_eq!(config.format, "simple");
assert!(config.capture_output.should_capture());
assert!(!config.show_cache_path);
assert!(config.materialize_outputs.is_none());
assert!(!config.help);
}
#[test]
fn test_execution_mode_default() {
let mode = ExecutionMode::default();
assert_eq!(mode, ExecutionMode::Simple);
}
#[test]
fn test_task_selection_default() {
let selection = TaskSelection::default();
assert!(matches!(selection, TaskSelection::List));
}
#[test]
fn test_request_debug() {
let executor = create_test_executor();
let req = TaskExecutionRequest::list("./", "cuenv", &executor);
let debug = format!("{req:?}");
assert!(debug.contains("TaskExecutionRequest"));
assert!(debug.contains("path"));
assert!(debug.contains("package"));
}
#[test]
fn test_request_clone() {
let executor = create_test_executor();
let req = TaskExecutionRequest::named("./", "cuenv", "build", &executor)
.with_environment("dev")
.with_format("json");
let cloned = req.clone();
assert_eq!(cloned.path, "./");
assert_eq!(cloned.package, "cuenv");
assert_eq!(cloned.environment, Some("dev".to_string()));
assert_eq!(cloned.output.format, "json");
}
#[test]
fn test_output_config_clone() {
let config = OutputConfig {
format: "json".to_string(),
capture_output: OutputCapture::Capture,
show_cache_path: true,
materialize_outputs: Some(PathBuf::from("/tmp")),
help: true,
};
let cloned = config.clone();
assert_eq!(cloned.format, "json");
assert!(cloned.capture_output.should_capture());
assert!(cloned.show_cache_path);
}
#[test]
fn test_execution_mode_clone() {
let mode = ExecutionMode::Tui;
let cloned = mode.clone();
assert_eq!(cloned, ExecutionMode::Tui);
}
#[test]
fn test_task_selection_clone() {
let selection = TaskSelection::Named {
name: "test".to_string(),
args: vec!["arg1".to_string()],
};
let cloned = selection.clone();
assert!(matches!(
cloned,
TaskSelection::Named { ref name, ref args }
if name == "test" && args == &vec!["arg1".to_string()]
));
}
#[test]
fn test_with_args_on_non_named_selection() {
let executor = create_test_executor();
let req =
TaskExecutionRequest::list("./", "cuenv", &executor).with_args(vec!["arg".to_string()]);
assert!(matches!(req.selection, TaskSelection::List));
}
}