docker_wrapper/command/
init.rs

1//! Docker init command implementation
2//!
3//! This module provides the `docker init` command for initializing projects with
4//! Docker-related starter files. The init command creates a Dockerfile, compose.yaml,
5//! .dockerignore, and README.Docker.md files based on a selected template.
6
7use super::{CommandExecutor, CommandOutput, DockerCommand};
8use crate::error::Result;
9use async_trait::async_trait;
10use std::fmt;
11
12/// Docker init command builder
13///
14/// Initialize a project with files necessary to run the project in a container.
15/// Creates a Dockerfile, compose.yaml, .dockerignore, and README.Docker.md.
16///
17/// # Examples
18///
19/// ```no_run
20/// use docker_wrapper::{DockerCommand, InitCommand};
21///
22/// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
23/// // Initialize with default interactive mode
24/// InitCommand::new()
25///     .execute()
26///     .await?;
27///
28/// // Show version information
29/// InitCommand::new()
30///     .version()
31///     .execute()
32///     .await?;
33/// # Ok(())
34/// # }
35/// ```
36#[derive(Debug, Clone)]
37pub struct InitCommand {
38    /// Show version information
39    show_version: bool,
40    /// Command executor
41    pub executor: CommandExecutor,
42}
43
44/// Available template types for Docker init
45#[derive(Debug, Clone, PartialEq)]
46pub enum InitTemplate {
47    /// ASP.NET Core application
48    AspNetCore,
49    /// Go application  
50    Go,
51    /// Java application
52    Java,
53    /// Node.js application
54    Node,
55    /// PHP with Apache application
56    Php,
57    /// Python application
58    Python,
59    /// Rust application
60    Rust,
61    /// General purpose / other
62    Other,
63}
64
65/// Result from the init command execution
66#[derive(Debug, Clone)]
67pub struct InitOutput {
68    /// Raw command output
69    pub output: CommandOutput,
70    /// Whether version was requested
71    pub version_requested: bool,
72}
73
74impl InitCommand {
75    /// Create a new init command
76    ///
77    /// # Examples
78    ///
79    /// ```
80    /// use docker_wrapper::InitCommand;
81    ///
82    /// let cmd = InitCommand::new();
83    /// ```
84    #[must_use]
85    pub fn new() -> Self {
86        Self {
87            show_version: false,
88            executor: CommandExecutor::new(),
89        }
90    }
91
92    /// Show version information instead of initializing
93    ///
94    /// # Examples
95    ///
96    /// ```
97    /// use docker_wrapper::InitCommand;
98    ///
99    /// let cmd = InitCommand::new().version();
100    /// ```
101    #[must_use]
102    pub fn version(mut self) -> Self {
103        self.show_version = true;
104        self
105    }
106
107    /// Check if version flag is set
108    #[must_use]
109    pub fn is_version(&self) -> bool {
110        self.show_version
111    }
112}
113
114impl Default for InitCommand {
115    fn default() -> Self {
116        Self::new()
117    }
118}
119
120#[async_trait]
121impl DockerCommand for InitCommand {
122    type Output = InitOutput;
123
124    fn get_executor(&self) -> &CommandExecutor {
125        &self.executor
126    }
127
128    fn get_executor_mut(&mut self) -> &mut CommandExecutor {
129        &mut self.executor
130    }
131
132    fn build_command_args(&self) -> Vec<String> {
133        let mut args = vec!["init".to_string()];
134
135        if self.show_version {
136            args.push("--version".to_string());
137        }
138
139        // Add any additional raw arguments
140        args.extend(self.executor.raw_args.clone());
141
142        args
143    }
144
145    async fn execute(&self) -> Result<Self::Output> {
146        let args = self.build_command_args();
147        let output = self.execute_command(args).await?;
148
149        Ok(InitOutput {
150            output,
151            version_requested: self.show_version,
152        })
153    }
154}
155
156impl InitOutput {
157    /// Check if the command was successful
158    #[must_use]
159    pub fn success(&self) -> bool {
160        self.output.success
161    }
162
163    /// Get the version string (if version was requested)
164    #[must_use]
165    pub fn version(&self) -> Option<&str> {
166        if self.version_requested && self.success() {
167            Some(self.output.stdout.trim())
168        } else {
169            None
170        }
171    }
172
173    /// Check if this was a version request
174    #[must_use]
175    pub fn is_version_output(&self) -> bool {
176        self.version_requested
177    }
178
179    /// Check if files were likely created (interactive mode completed successfully)
180    #[must_use]
181    pub fn files_created(&self) -> bool {
182        !self.version_requested && self.success()
183    }
184
185    /// Get raw stdout output
186    #[must_use]
187    pub fn stdout(&self) -> &str {
188        &self.output.stdout
189    }
190
191    /// Get raw stderr output  
192    #[must_use]
193    pub fn stderr(&self) -> &str {
194        &self.output.stderr
195    }
196}
197
198impl fmt::Display for InitCommand {
199    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
200        write!(f, "docker init")?;
201
202        if self.show_version {
203            write!(f, " --version")?;
204        }
205
206        Ok(())
207    }
208}
209
210impl fmt::Display for InitTemplate {
211    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
212        let name = match self {
213            Self::AspNetCore => "ASP.NET Core",
214            Self::Go => "Go",
215            Self::Java => "Java",
216            Self::Node => "Node",
217            Self::Php => "PHP with Apache",
218            Self::Python => "Python",
219            Self::Rust => "Rust",
220            Self::Other => "Other",
221        };
222        write!(f, "{name}")
223    }
224}
225
226#[cfg(test)]
227mod tests {
228    use super::*;
229
230    #[test]
231    fn test_init_command_basic() {
232        let cmd = InitCommand::new();
233        assert!(!cmd.is_version());
234
235        let args = cmd.build_command_args();
236        assert_eq!(args, vec!["init"]);
237    }
238
239    #[test]
240    fn test_init_command_version() {
241        let cmd = InitCommand::new().version();
242        assert!(cmd.is_version());
243
244        let args = cmd.build_command_args();
245        assert_eq!(args, vec!["init", "--version"]);
246    }
247
248    #[test]
249    fn test_init_command_default() {
250        let cmd = InitCommand::default();
251        assert!(!cmd.is_version());
252
253        let args = cmd.build_command_args();
254        assert_eq!(args, vec!["init"]);
255    }
256
257    #[test]
258    fn test_init_command_display() {
259        let cmd = InitCommand::new();
260        assert_eq!(format!("{cmd}"), "docker init");
261
262        let cmd_version = InitCommand::new().version();
263        assert_eq!(format!("{cmd_version}"), "docker init --version");
264    }
265
266    #[test]
267    fn test_init_template_display() {
268        assert_eq!(format!("{}", InitTemplate::AspNetCore), "ASP.NET Core");
269        assert_eq!(format!("{}", InitTemplate::Go), "Go");
270        assert_eq!(format!("{}", InitTemplate::Java), "Java");
271        assert_eq!(format!("{}", InitTemplate::Node), "Node");
272        assert_eq!(format!("{}", InitTemplate::Php), "PHP with Apache");
273        assert_eq!(format!("{}", InitTemplate::Python), "Python");
274        assert_eq!(format!("{}", InitTemplate::Rust), "Rust");
275        assert_eq!(format!("{}", InitTemplate::Other), "Other");
276    }
277
278    #[test]
279    fn test_init_output_helpers() {
280        let output = InitOutput {
281            output: CommandOutput {
282                stdout: "Version: v1.4.0".to_string(),
283                stderr: String::new(),
284                exit_code: 0,
285                success: true,
286            },
287            version_requested: true,
288        };
289
290        assert!(output.success());
291        assert!(output.is_version_output());
292        assert!(!output.files_created());
293        assert_eq!(output.version(), Some("Version: v1.4.0"));
294        assert_eq!(output.stdout(), "Version: v1.4.0");
295    }
296
297    #[test]
298    fn test_init_output_files_created() {
299        let output = InitOutput {
300            output: CommandOutput {
301                stdout: "Files created successfully".to_string(),
302                stderr: String::new(),
303                exit_code: 0,
304                success: true,
305            },
306            version_requested: false,
307        };
308
309        assert!(output.success());
310        assert!(!output.is_version_output());
311        assert!(output.files_created());
312        assert_eq!(output.version(), None);
313    }
314
315    #[test]
316    fn test_init_command_extensibility() {
317        let mut cmd = InitCommand::new();
318
319        // Test that we can add custom raw arguments
320        cmd.get_executor_mut()
321            .raw_args
322            .push("--custom-flag".to_string());
323
324        let args = cmd.build_command_args();
325        assert!(args.contains(&"--custom-flag".to_string()));
326    }
327}