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
|
use anyhow::{Context, Result};
use clap::{ArgEnum, ErrorKind, IntoApp, Parser, Subcommand, ValueHint};
use clap_complete::{generate, Shell};
use log::LevelFilter;
use reqwest::Url;
use std::{ffi::OsString, io, process::Command};
use tabled::{Style, Table};
use zone_core::{Container, DEFAULT_ENDPOINT};
#[derive(Debug, Parser)]
#[clap(about, version)]
pub struct Cli {
/// Enable debugging mode
#[clap(short, long)]
pub debug: bool,
/// Set the level of verbosity (-v, -vv, -vvv, etc.)
#[clap(short, long, parse(from_occurrences))]
pub verbose: i8,
/// Disable all output
#[clap(short, long, conflicts_with = "verbose")]
pub quiet: bool,
/// Endpoint of a zone daemon to use
#[clap(
long,
global = true,
env = "ZONE_ENDPOINT",
value_name = "ZONE_ENDPOINT",
default_value = DEFAULT_ENDPOINT,
value_hint = ValueHint::Url,
)]
endpoint: String,
#[clap(subcommand)]
pub command: Commands,
}
#[derive(Debug, Subcommand)]
pub enum Commands {
/// Generates shell completion scripts
Completion {
/// The shell to generate completion scripts for
#[clap(default_value_t = Shell::Bash, arg_enum)]
shell: Shell,
},
/// List existing containers
List {
/// Filter the list of containers
filter: Option<String>,
},
#[clap(external_subcommand)]
External(Vec<OsString>),
}
impl Cli {
pub fn run(self) -> Result<Option<String>> {
match self.command {
Commands::Completion { shell } => self.completion(shell),
Commands::List { ref filter } => self.list(filter),
Commands::External(args) => Cli::external(args),
}
}
pub fn get_log_level(&self) -> LevelFilter {
match 2 + self.verbose + self.debug as i8 {
_ if self.quiet => LevelFilter::Off,
0 => LevelFilter::Error,
1 => LevelFilter::Warn,
2 => LevelFilter::Info,
3 => LevelFilter::Debug,
_ => LevelFilter::Trace,
}
}
fn external(args: Vec<OsString>) -> Result<Option<String>> {
Command::new(format!("zone-{:?}", &args[0]))
.args(&args[1..])
.spawn()
.unwrap_or_else(|_| {
let mut app = Cli::into_app();
app.error(
ErrorKind::UnrecognizedSubcommand,
format!("Unrecognized subcommand '{:?}'", &args[0]),
)
.exit()
});
Ok(None)
}
fn completion(&self, gen: Shell) -> Result<Option<String>> {
eprintln!("Generating completion file for {:?}...", gen);
let mut app = Cli::into_app();
let bin_name = app.get_name().to_string();
let buf = &mut io::stdout();
generate(gen, &mut app, bin_name, buf);
Ok(None)
}
fn list(&self, query: &Option<String>) -> Result<Option<String>> {
let mut url = Url::parse(&self.endpoint).context("Invalid zone endpoint")?;
url.set_path("containers/list");
url.set_query(query.as_deref());
let containers = reqwest::blocking::get(url)
.context("Daemon is not running")?
.json::<Vec<Container>>()
.context("Failed to parse json")?;
Ok(Some(
Table::new(containers).with(Style::NO_BORDER).to_string(),
))
}
}
#[cfg(test)]
mod tests {
#[test]
fn verify_app() {
use super::Cli;
use clap::IntoApp;
Cli::into_app().debug_assert()
}
}
|