cdk_ansible_cli/
lib.rs

1//!
2//! This is a crate for `cdk-ansible` command
3//!
4//! # Usage
5//!
6//! ```bash
7//! cdk-ansible module --help
8//! ```
9//!
10
11use anyhow::{Context as _, Result};
12use clap::{
13    Parser as _,
14    error::{ContextKind, ContextValue},
15};
16use serde::Deserialize;
17use std::ffi::OsString;
18use std::path::Path;
19
20/// CLI arguments
21mod arg;
22/// Settings layer
23mod settings;
24/// Define subcommands
25mod subcommand;
26/// Define utils
27mod utils;
28/// Define version
29mod version;
30
31use arg::{Cli, Commands};
32
33#[derive(Debug, Clone, Default, Deserialize)]
34/// Options for the application.
35///
36/// Not implemented yet.
37struct Options {
38    #[expect(dead_code, reason = "not implemented yet")]
39    /// Phantom data
40    val1: String,
41}
42
43/// Load [`Options`] from a app config file
44#[expect(clippy::single_call_fn, reason = "better readability")]
45fn read_file(path: &Path) -> Result<Options> {
46    let _content =
47        fs_err::read_to_string(path).context(format!("Failed to read file: {}", path.display()))?;
48    let options: Options = Options::default();
49    Ok(options)
50}
51
52#[derive(Debug, Clone)]
53/// Filesystem options
54/// Not implemented yet.
55#[expect(dead_code, reason = "not implemented yet")]
56struct FilesystemOptions(Options);
57
58impl FilesystemOptions {
59    /// Load [`FilesystemOptions`] from a app config file
60    #[expect(clippy::single_call_fn, reason = "better readability")]
61    pub fn from_file(file: &Path) -> Result<Self> {
62        let options: Options = read_file(file)?;
63        Ok(Self(options))
64    }
65}
66
67/// Main entry point of the application
68///
69/// This function is the main entry point of the application.
70/// It parses the command line arguments, reads the configuration file,
71/// and then executes the appropriate subcommand.
72///
73/// # Arguments
74///
75/// * `args` - The command line arguments to parse.
76///
77/// # Returns
78///
79/// Returns a `Result` with the result of the subcommand.
80///
81/// # Errors
82///
83/// Use [`anyhow::Result`] to handle errors.
84///
85#[inline]
86pub fn run<I, T>(args: I) -> Result<()>
87where
88    I: IntoIterator<Item = T>,
89    T: Into<OsString> + Clone,
90{
91    let cli = match Cli::try_parse_from(args) {
92        Ok(cli) => cli,
93        Err(mut err) => {
94            #[expect(clippy::single_match, reason = "better readability")]
95            #[expect(clippy::pattern_type_mismatch, reason = "I don't know")]
96            match err.get(ContextKind::InvalidSubcommand) {
97                Some(ContextValue::String(subcommand)) => match subcommand.as_str() {
98                    "help" => {
99                        err.insert(
100                            ContextKind::InvalidSubcommand,
101                            ContextValue::String("help".to_owned()),
102                        );
103                    }
104                    "module" => {
105                        err.insert(
106                            ContextKind::InvalidSubcommand,
107                            ContextValue::String("module".to_owned()),
108                        );
109                    }
110                    _ => {}
111                },
112                _ => {}
113            }
114            err.exit()
115        }
116    };
117
118    let filesystem = if let Some(config_file) = cli.top_level.config_file.as_ref() {
119        Some(FilesystemOptions::from_file(config_file)?)
120    } else {
121        None
122    };
123
124    #[expect(clippy::todo, reason = "implement later")]
125    if let Some(_filesystem) = filesystem {
126        todo!("TODO: read as global settings");
127    }
128
129    // TODO: implement later
130    let _global_settings = settings::GlobalSettings::resolve(&cli.top_level.global_args);
131
132    let result = match *cli.command {
133        #[expect(clippy::todo, reason = "implement later")]
134        Commands::Help(_help_args) => {
135            todo!("TODO: implement help command");
136        }
137        Commands::Module(module_args) => subcommand::module::module(module_args),
138    };
139    result.context("Failed to run command")
140}