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
/// Types to parse the command line arguments with the clap crate.
use crate::Verbosity;
use clap::{Parser, Subcommand, ValueEnum};
use clap_num::number_range;
use std::cmp::Ord;

#[derive(Parser)]
#[command(author, version, about, long_about = None)]
pub struct Args {
    #[command(subcommand)]
    pub command: Commands,

    #[arg(short, long = "file")]
    pub filenames: Vec<String>,

    /// Increase verbosity
    #[arg(long, conflicts_with = "quiet")]
    pub verbose: bool,

    /// Only display relevant information or errors
    #[arg(long, short, conflicts_with = "verbose")]
    pub quiet: bool,

    /// Don't call docker compose to parse compose model
    #[arg(long)]
    pub no_docker: bool,

    /// Don't check model consistency - warning: may produce invalid Compose output
    #[arg(long)]
    pub no_consistency: bool,

    /// Don't interpolate environment variables
    #[arg(long)]
    pub no_interpolate: bool,

    /// Don't normalize compose model
    #[arg(long)]
    pub no_normalize: bool,
}

impl Args {
    pub fn get_verbosity(&self) -> Verbosity {
        match self.verbose {
            true => Verbosity::Verbose,
            false => match self.quiet {
                true => Verbosity::Quiet,
                false => Verbosity::Info,
            },
        }
    }
}

fn positive_less_than_32(s: &str) -> Result<u8, String> {
    number_range(s, 1, 32)
}

#[derive(Subcommand)]
pub enum Commands {
    /// List objects found in the compose file: services, volumes, ...
    List {
        #[command(subcommand)]
        object: Objects,

        #[arg(short, long, value_enum, default_value_t = Formats::Full, value_name = "FORMAT")]
        pretty: Formats,
    },
    /// Parse, resolve and render compose file in canonical format
    Config {
        /// Save to file (default to stdout)
        #[arg(short, long, value_name = "FILE")]
        output: Option<String>,
        /// output with remote tag passed instead of the one set in the file
        /// if exists in the docker registry
        #[arg(short, long, value_name = "TAG")]
        remote_tag: Option<String>,
        /// use with --remote-tag to filter which images should be checked
        /// whether the remote tag exists or not.
        /// Currently only regex=EXPR or regex!=EXPR are supported
        #[arg(long, value_name = "FILTER", requires("remote_tag"))]
        remote_tag_filter: Option<String>,
        /// ignore unauthorized errors from docker when fetching remote tags info
        #[arg(long, requires("remote_tag"))]
        ignore_unauthorized: bool,
        /// Outputs in stderr the progress of fetching remote tags, similar to the --verbose argument,
        /// but without all the other details --verbose adds
        #[arg(long, requires("remote_tag"))]
        remote_progress: bool,
        /// max number of threads used to fetch remote images info
        #[arg(long, value_name = "NUM", default_value_t = 4, value_parser = positive_less_than_32, requires("remote_tag"))]
        threads: u8,
    },
}

#[derive(Subcommand, strum_macros::Display, PartialEq)]
pub enum Objects {
    /// List services
    Services,
    /// List images
    Images {
        /// filter by a property, if --remote-tag is used as well,
        /// this filter is applied first, filtering out images that
        /// don't match the filter. Currently only tag=TAG is supported
        #[arg(short, long)]
        filter: Option<String>,
        /// print with remote tag passed instead of the one set in the file
        /// if exists in the docker registry
        #[arg(short, long, value_name = "TAG")]
        remote_tag: Option<String>,
        /// use with --remote-tag to filter which images should be checked
        /// whether the remote tag exists or not, but images that don't match
        /// the filter are not filtered out from the list printed, only
        /// printed with the tag they have in the compose file.
        /// Currently only regex=EXPR or regex!=EXPR are supported
        #[arg(long, value_name = "FILTER", requires("remote_tag"))]
        remote_tag_filter: Option<String>,
        /// ignore unauthorized errors from docker when fetching remote tags info
        #[arg(long, requires("remote_tag"))]
        ignore_unauthorized: bool,
        /// Outputs in stderr the progress of fetching remote tags, similar to the --verbose argument,
        /// but without all the other details --verbose adds
        #[arg(long, requires("remote_tag"))]
        remote_progress: bool,
        /// max number of threads used to fetch remote images info
        #[arg(long, value_name = "NUM", default_value_t = 4, value_parser = positive_less_than_32, requires("remote_tag"))]
        threads: u8,
    },
    /// List service's depends_on
    Depends { service: String },
    /// List volumes
    Volumes,
    /// List networks
    Networks,
    /// List configs
    Configs,
    /// List secrets
    Secrets,
    /// List profiles
    Profiles,
    /// List service's environment variables
    Envs { service: String },
}

#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, ValueEnum, strum_macros::Display)]
pub enum Formats {
    Full,
    Oneline,
}