1use anyhow::Result;
13use clap::{ArgAction, Parser, ValueEnum};
14
15mod balance;
16mod check;
17mod device;
18mod filesystem;
19mod inspect;
20mod property;
21mod qgroup;
22mod quota;
23mod receive;
24mod replace;
25mod rescue;
26mod restore;
27mod scrub;
28mod send;
29mod subvolume;
30mod util;
31
32pub use crate::{
33 balance::*, check::*, device::*, filesystem::*, inspect::*, property::*,
34 qgroup::*, quota::*, receive::*, replace::*, rescue::*, restore::*,
35 scrub::*, send::*, subvolume::*,
36};
37
38#[derive(Default, Debug, Clone, Copy, PartialEq, Eq, ValueEnum)]
40pub enum Format {
41 #[default]
42 Text,
43 Json,
44}
45
46#[derive(
48 Default, Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, ValueEnum,
49)]
50pub enum Level {
51 Debug,
52 #[default]
53 Info,
54 Warn,
55 Error,
56}
57
58#[derive(Parser, Debug)]
68#[clap(version, infer_subcommands = true)]
69pub struct Arguments {
70 #[clap(flatten)]
71 pub global: GlobalOptions,
72
73 #[clap(subcommand)]
74 pub command: Command,
75}
76
77const GLOBAL_OPTIONS: &str = "Global options";
78
79#[derive(Parser, Debug)]
81pub struct GlobalOptions {
82 #[clap(global = true, short, long, action = ArgAction::Count, help_heading = GLOBAL_OPTIONS)]
84 pub verbose: u8,
85
86 #[clap(global = true, short, long, help_heading = GLOBAL_OPTIONS)]
88 pub quiet: bool,
89
90 #[clap(global = true, long, help_heading = GLOBAL_OPTIONS)]
92 pub dry_run: bool,
93
94 #[clap(global = true, long, help_heading = GLOBAL_OPTIONS)]
96 pub log: Option<Level>,
97
98 #[clap(global = true, long, help_heading = GLOBAL_OPTIONS)]
100 pub format: Option<Format>,
101}
102
103pub trait Runnable {
105 fn run(&self, format: Format, dry_run: bool) -> Result<()>;
106}
107
108#[derive(Parser, Debug)]
109pub enum Command {
110 Balance(BalanceCommand),
111 Check(CheckCommand),
112 Device(DeviceCommand),
113 Filesystem(FilesystemCommand),
114 #[command(alias = "inspect-internal")]
115 Inspect(InspectCommand),
116 Property(PropertyCommand),
117 Qgroup(QgroupCommand),
118 Quota(QuotaCommand),
119 Receive(ReceiveCommand),
120 Replace(ReplaceCommand),
121 Rescue(RescueCommand),
122 Restore(RestoreCommand),
123 Scrub(ScrubCommand),
124 Send(SendCommand),
125 Subvolume(SubvolumeCommand),
126}
127
128impl Runnable for Command {
129 fn run(&self, format: Format, dry_run: bool) -> Result<()> {
130 match self {
131 Command::Balance(cmd) => cmd.run(format, dry_run),
132 Command::Check(cmd) => cmd.run(format, dry_run),
133 Command::Device(cmd) => cmd.run(format, dry_run),
134 Command::Filesystem(cmd) => cmd.run(format, dry_run),
135 Command::Inspect(cmd) => cmd.run(format, dry_run),
136 Command::Property(cmd) => cmd.run(format, dry_run),
137 Command::Qgroup(cmd) => cmd.run(format, dry_run),
138 Command::Quota(cmd) => cmd.run(format, dry_run),
139 Command::Receive(cmd) => cmd.run(format, dry_run),
140 Command::Replace(cmd) => cmd.run(format, dry_run),
141 Command::Rescue(cmd) => cmd.run(format, dry_run),
142 Command::Restore(cmd) => cmd.run(format, dry_run),
143 Command::Scrub(cmd) => cmd.run(format, dry_run),
144 Command::Send(cmd) => cmd.run(format, dry_run),
145 Command::Subvolume(cmd) => cmd.run(format, dry_run),
146 }
147 }
148}
149
150impl Arguments {
151 pub fn run(&self) -> Result<()> {
152 let level = if let Some(explicit) = self.global.log {
153 match explicit {
154 Level::Debug => log::LevelFilter::Debug,
155 Level::Info => log::LevelFilter::Info,
156 Level::Warn => log::LevelFilter::Warn,
157 Level::Error => log::LevelFilter::Error,
158 }
159 } else if self.global.quiet {
160 log::LevelFilter::Error
161 } else {
162 match self.global.verbose {
163 0 => log::LevelFilter::Warn,
164 1 => log::LevelFilter::Info,
165 2 => log::LevelFilter::Debug,
166 _ => log::LevelFilter::Trace,
167 }
168 };
169 env_logger::Builder::new().filter_level(level).init();
170 self.command
171 .run(self.global.format.unwrap_or_default(), self.global.dry_run)
172 }
173}