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