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
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
use crate::source_loc::SourceLoc;
use clap::{Args, Parser, Subcommand, ValueEnum};
use std::path::PathBuf;

#[derive(Parser, Debug, Clone)]
#[command(
    version, about,
    after_help(env!("CARGO_PKG_HOMEPAGE")),
    propagate_version = true
)]
pub struct CliOpts {
    // The number of occurences of the `v/verbose` flag
    /// Verbose mode (-v, -vv (very verbose / level debug), -vvv)
    /// print on stderr
    #[arg(short = 'v', long = "verbose", action = clap::ArgAction::Count)]
    pub verbose: u8,

    #[command(subcommand)]
    pub cmd: Command,
}

#[derive(Subcommand, Debug, Clone)]
pub enum Command {
    /// Apply a template into a target directory
    Apply(ApplyOpts),

    /// Reapply templates that were previously applied to a target directory
    Reapply(ReapplyOpts),

    /// Inspect configuration, caches,... (wip)
    Inspect,

    /// Show the json schema of the .ffizer.yaml files
    ShowJsonSchema,

    /// test a template against its samples
    TestSamples(TestSamplesOpts),
}

#[derive(Args, Debug, Default, Clone)]
pub struct ApplyOpts {
    /// ask for plan confirmation
    #[arg(long, default_value = "Never", value_enum, ignore_case = true)]
    pub confirm: AskConfirmation,

    /// mode to update existing file
    #[arg(long, default_value = "Ask", value_enum, ignore_case = true)]
    pub update_mode: UpdateMode,

    /// should not ask for confirmation (to use default value, to apply plan, to override, to run script,...)
    #[arg(short = 'y', long = "no-interaction")]
    pub no_interaction: bool,

    /// in offline, only local templates or cached templates are used
    #[arg(long = "offline")]
    pub offline: bool,

    #[command(flatten)]
    pub src: SourceLoc,

    /// destination folder (created if doesn't exist)
    #[arg(
        short = 'd',
        long = "destination",
        //default_value = "."
        value_name = "FOLDER"
    )]
    pub dst_folder: PathBuf,

    /// set variable's value from cli ("key=value")
    #[arg(short = 'v', long = "variables", value_parser = parse_keyvalue)]
    pub key_value: Vec<(String, String)>,
}

#[derive(Args, Debug, Default, Clone)]
pub struct ReapplyOpts {
    /// ask for plan confirmation
    #[arg(long, default_value = "Never", value_enum, ignore_case = true)]
    pub confirm: AskConfirmation,

    /// mode to update existing file
    #[arg(long, default_value = "Ask", value_enum, ignore_case = true)]
    pub update_mode: UpdateMode,

    /// should not ask for confirmation (to use default value, to apply plan, to override, to run script,...)
    #[arg(short = 'y', long = "no-interaction")]
    pub no_interaction: bool,

    /// in offline, only local templates or cached templates are used
    #[arg(long = "offline")]
    pub offline: bool,

    /// destination folder (created if doesn't exist)
    #[arg(
        short = 'd',
        long = "destination",
        default_value = ".",
        value_name = "FOLDER"
    )]
    pub dst_folder: PathBuf,

    /// set variable's value from cli ("key=value")
    #[arg(short = 'v', long = "variables", value_parser = parse_keyvalue)]
    pub key_value: Vec<(String, String)>,
}

#[derive(Debug, Clone, PartialEq, Eq, ValueEnum, Default)]
pub enum AskConfirmation {
    #[default]
    Auto,
    Always,
    Never,
}

#[derive(Debug, Clone, PartialEq, Eq, ValueEnum, Default)]
/// mode to process update of existing local file
pub enum UpdateMode {
    /// ask what to do
    #[default]
    Ask,
    /// keep existing local file (ignore template)
    Keep,
    /// override local file with file from template
    Override,
    /// keep existing local file, add template with extension .REMOTE
    UpdateAsRemote,
    /// rename existing local file with extension .LOCAL, add template file
    CurrentAsLocal,
    /// show diff then ask
    ShowDiff,
    /// try to merge existing local with remote template via merge tool (defined in the git's configuration)
    Merge,
}

impl std::fmt::Display for UpdateMode {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        f.write_fmt(format_args!(
            "{}",
            self.to_possible_value()
                .expect("UpdateMode should have a possible value")
                .get_name()
        ))
    }
}

fn parse_keyvalue(src: &str) -> Result<(String, String), String> {
    let kv: Vec<&str> = src.splitn(2, '=').collect();
    if kv.len() == 2 {
        Ok((kv[0].to_owned(), kv[1].to_owned()))
    } else {
        Ok((src.to_owned(), "".to_owned()))
    }
}

#[derive(Parser, Debug, Default, Clone)]
pub struct TestSamplesOpts {
    #[command(flatten)]
    pub src: SourceLoc,
    /// in offline, only local templates or cached templates are used
    #[arg(long = "offline")]
    pub offline: bool,

    /// interactive review mode, allow to update sample from generated
    #[arg(long = "review")]
    pub review: bool,
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn verify_cli() {
        use clap::CommandFactory;
        CliOpts::command().debug_assert()
    }
}