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)]
39pub enum Format {
40 #[default]
41 Text,
42 Json,
43}
44
45#[derive(
46 Default, Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, ValueEnum,
47)]
48pub enum Level {
49 Debug,
50 #[default]
51 Info,
52 Warn,
53 Error,
54}
55
56#[derive(Parser, Debug)]
66#[clap(version, infer_subcommands = true)]
67pub struct Arguments {
68 #[clap(flatten)]
69 pub global: GlobalOptions,
70
71 #[clap(subcommand)]
72 pub command: Command,
73}
74
75const GLOBAL_OPTIONS: &str = "Global options";
76
77#[derive(Parser, Debug)]
78pub struct GlobalOptions {
79 #[clap(global = true, short, long, action = ArgAction::Count, help_heading = GLOBAL_OPTIONS)]
81 pub verbose: u8,
82
83 #[clap(global = true, short, long, help_heading = GLOBAL_OPTIONS)]
85 pub quiet: bool,
86
87 #[clap(global = true, long, help_heading = GLOBAL_OPTIONS)]
89 pub dry_run: bool,
90
91 #[clap(global = true, long, help_heading = GLOBAL_OPTIONS)]
93 pub log: Option<Level>,
94
95 #[clap(global = true, long, help_heading = GLOBAL_OPTIONS)]
97 pub format: Option<Format>,
98}
99
100pub trait Runnable {
101 fn run(&self, format: Format, dry_run: bool) -> Result<()>;
102}
103
104#[derive(Parser, Debug)]
105pub enum Command {
106 Balance(BalanceCommand),
107 Check(CheckCommand),
108 Device(DeviceCommand),
109 Filesystem(FilesystemCommand),
110 #[command(alias = "inspect-internal")]
111 Inspect(InspectCommand),
112 Property(PropertyCommand),
113 Qgroup(QgroupCommand),
114 Quota(QuotaCommand),
115 Receive(ReceiveCommand),
116 Replace(ReplaceCommand),
117 Rescue(RescueCommand),
118 Restore(RestoreCommand),
119 Scrub(ScrubCommand),
120 Send(SendCommand),
121 Subvolume(SubvolumeCommand),
122}
123
124impl Runnable for Command {
125 fn run(&self, format: Format, dry_run: bool) -> Result<()> {
126 match self {
127 Command::Balance(cmd) => cmd.run(format, dry_run),
128 Command::Check(cmd) => cmd.run(format, dry_run),
129 Command::Device(cmd) => cmd.run(format, dry_run),
130 Command::Filesystem(cmd) => cmd.run(format, dry_run),
131 Command::Inspect(cmd) => cmd.run(format, dry_run),
132 Command::Property(cmd) => cmd.run(format, dry_run),
133 Command::Qgroup(cmd) => cmd.run(format, dry_run),
134 Command::Quota(cmd) => cmd.run(format, dry_run),
135 Command::Receive(cmd) => cmd.run(format, dry_run),
136 Command::Replace(cmd) => cmd.run(format, dry_run),
137 Command::Rescue(cmd) => cmd.run(format, dry_run),
138 Command::Restore(cmd) => cmd.run(format, dry_run),
139 Command::Scrub(cmd) => cmd.run(format, dry_run),
140 Command::Send(cmd) => cmd.run(format, dry_run),
141 Command::Subvolume(cmd) => cmd.run(format, dry_run),
142 }
143 }
144}
145
146impl Arguments {
147 pub fn run(&self) -> Result<()> {
148 let level = if let Some(explicit) = self.global.log {
149 match explicit {
150 Level::Debug => log::LevelFilter::Debug,
151 Level::Info => log::LevelFilter::Info,
152 Level::Warn => log::LevelFilter::Warn,
153 Level::Error => log::LevelFilter::Error,
154 }
155 } else if self.global.quiet {
156 log::LevelFilter::Error
157 } else {
158 match self.global.verbose {
159 0 => log::LevelFilter::Warn,
160 1 => log::LevelFilter::Info,
161 2 => log::LevelFilter::Debug,
162 _ => log::LevelFilter::Trace,
163 }
164 };
165 env_logger::Builder::new().filter_level(level).init();
166 self.command
167 .run(self.global.format.unwrap_or_default(), self.global.dry_run)
168 }
169}