cdk_ansible_cli/
lib.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
//!
//! This is a crate for `cdk-ansible` command
//!
//! # Usage
//!
//! ```bash
//! cdk-ansible module --help
//! ```
//!

use anyhow::{Context as _, Result};
use clap::{
    error::{ContextKind, ContextValue},
    Parser as _,
};
use serde::Deserialize;
use std::ffi::OsString;
use std::path::Path;

/// CLI arguments
mod arg;
/// Settings layer
mod settings;
/// Define subcommands
mod subcommand;
/// Define version
mod version;

use arg::{Cli, Commands};

#[derive(Debug, Clone, Default, Deserialize)]
/// Options for the application.
///
/// Not implemented yet.
struct Options {
    #[expect(dead_code, reason = "not implemented yet")]
    /// Phantom data
    val1: String,
}

/// Load [`Options`] from a app config file
#[expect(clippy::single_call_fn, reason = "better readability")]
fn read_file(path: &Path) -> Result<Options> {
    let _content =
        fs_err::read_to_string(path).context(format!("Failed to read file: {}", path.display()))?;
    let options: Options = Options::default();
    Ok(options)
}

#[derive(Debug, Clone)]
/// Filesystem options
/// Not implemented yet.
#[expect(dead_code, reason = "not implemented yet")]
struct FilesystemOptions(Options);

impl FilesystemOptions {
    /// Load [`FilesystemOptions`] from a app config file
    #[expect(clippy::single_call_fn, reason = "better readability")]
    pub fn from_file(file: &Path) -> Result<Self> {
        let options: Options = read_file(file)?;
        Ok(Self(options))
    }
}

/// Main entry point of the application
///
/// This function is the main entry point of the application.
/// It parses the command line arguments, reads the configuration file,
/// and then executes the appropriate subcommand.
///
/// # Arguments
///
/// * `args` - The command line arguments to parse.
///
/// # Returns
///
/// Returns a `Result` with the result of the subcommand.
///
/// # Errors
///
/// * `CliError` - If the command line arguments are invalid.
/// * `IoError` - If the configuration file is not found or cannot be read.
/// * `SubcommandError` - If the subcommand fails to execute.
#[inline]
// #[expect(clippy::single_call_fn, reason = "better readability")]
pub fn run<I, T>(args: I) -> Result<()>
where
    I: IntoIterator<Item = T>,
    T: Into<OsString> + Clone,
{
    let cli = match Cli::try_parse_from(args) {
        Ok(cli) => cli,
        Err(mut err) => {
            #[expect(clippy::single_match, reason = "better readability")]
            #[expect(clippy::pattern_type_mismatch, reason = "I don't know")]
            match err.get(ContextKind::InvalidSubcommand) {
                Some(ContextValue::String(subcommand)) => match subcommand.as_str() {
                    "help" => {
                        err.insert(
                            ContextKind::InvalidSubcommand,
                            ContextValue::String("help".to_owned()),
                        );
                    }
                    "module" => {
                        err.insert(
                            ContextKind::InvalidSubcommand,
                            ContextValue::String("module".to_owned()),
                        );
                    }
                    _ => {}
                },
                _ => {}
            }
            err.exit()
        }
    };

    let filesystem = if let Some(config_file) = cli.top_level.config_file.as_ref() {
        Some(FilesystemOptions::from_file(config_file)?)
    } else {
        None
    };

    #[expect(clippy::todo, reason = "implement later")]
    if let Some(_filesystem) = filesystem {
        todo!("TODO: read as global settings");
    }

    // TODO: implement later
    let _global_settings = settings::GlobalSettings::resolve(&cli.top_level.global_args);

    let result = match *cli.command {
        #[expect(clippy::todo, reason = "implement later")]
        Commands::Help(_help_args) => {
            todo!("TODO: implement help command");
        }
        Commands::Module(module_args) => subcommand::module::module(module_args),
    };
    result.context("Failed to run command")
}