use std::io::{IsTerminal, stderr, stdout};
use clap::{Parser, Subcommand, ValueEnum};
use crate::exit_code::ExitCode;
use crate::output::OutputConfig;
mod admin;
mod alias;
mod anonymous;
mod bucket;
mod cat;
mod completions;
mod cors;
pub mod cp;
pub mod diff;
mod event;
mod find;
mod head;
mod ilm;
mod ls;
mod mb;
mod mirror;
mod mv;
mod object;
mod pipe;
mod quota;
mod rb;
mod replicate;
mod rm;
mod share;
mod stat;
mod tag;
mod tree;
mod version;
#[derive(Parser, Debug)]
#[command(name = "rc")]
#[command(author, version, about, long_about = None)]
#[command(propagate_version = true)]
pub struct Cli {
#[arg(long, global = true, value_enum)]
pub format: Option<OutputFormat>,
#[arg(long, global = true, default_value = "false")]
pub json: bool,
#[arg(long, global = true, default_value = "false")]
pub no_color: bool,
#[arg(long, global = true, default_value = "false")]
pub no_progress: bool,
#[arg(short, long, global = true, default_value = "false")]
pub quiet: bool,
#[arg(long, global = true, default_value = "false")]
pub debug: bool,
#[command(subcommand)]
pub command: Commands,
}
#[derive(Copy, Clone, Debug, Eq, PartialEq, ValueEnum)]
pub enum OutputFormat {
Auto,
Human,
Json,
}
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
enum OutputBehavior {
HumanDefault,
StructuredDefault,
}
#[derive(Copy, Clone, Debug)]
struct GlobalOutputOptions {
format: Option<OutputFormat>,
json: bool,
no_color: bool,
no_progress: bool,
quiet: bool,
}
impl GlobalOutputOptions {
fn from_cli(cli: &Cli) -> Self {
Self {
format: cli.format,
json: cli.json,
no_color: cli.no_color,
no_progress: cli.no_progress,
quiet: cli.quiet,
}
}
fn resolve(self, behavior: OutputBehavior) -> OutputConfig {
let stdout_is_tty = stdout().is_terminal();
let stderr_is_tty = stderr().is_terminal();
let selected_format = if self.json {
OutputFormat::Json
} else {
self.format.unwrap_or(match behavior {
OutputBehavior::HumanDefault => OutputFormat::Human,
OutputBehavior::StructuredDefault => OutputFormat::Auto,
})
};
let json = match selected_format {
OutputFormat::Json => true,
OutputFormat::Human => false,
OutputFormat::Auto => !stdout_is_tty,
};
OutputConfig {
json,
no_color: self.no_color || !stdout_is_tty || json,
no_progress: self.no_progress || !stderr_is_tty || json,
quiet: self.quiet,
}
}
}
#[derive(Subcommand, Debug)]
pub enum Commands {
#[command(subcommand)]
Alias(alias::AliasCommands),
#[command(subcommand)]
Admin(admin::AdminCommands),
Bucket(bucket::BucketArgs),
Object(object::ObjectArgs),
Ls(ls::LsArgs),
Mb(mb::MbArgs),
Rb(rb::RbArgs),
Cat(cat::CatArgs),
Head(head::HeadArgs),
Stat(stat::StatArgs),
Cp(cp::CpArgs),
Mv(mv::MvArgs),
Rm(rm::RmArgs),
Pipe(pipe::PipeArgs),
Find(find::FindArgs),
Event(event::EventArgs),
#[command(subcommand)]
Cors(cors::CorsCommands),
Diff(diff::DiffArgs),
Mirror(mirror::MirrorArgs),
Tree(tree::TreeArgs),
Share(share::ShareArgs),
#[command(subcommand)]
Version(version::VersionCommands),
#[command(subcommand)]
Tag(tag::TagCommands),
#[command(subcommand)]
Anonymous(anonymous::AnonymousCommands),
#[command(subcommand)]
Quota(quota::QuotaCommands),
Ilm(ilm::IlmArgs),
Replicate(replicate::ReplicateArgs),
Completions(completions::CompletionsArgs),
}
pub async fn execute(cli: Cli) -> ExitCode {
let output_options = GlobalOutputOptions::from_cli(&cli);
match cli.command {
Commands::Alias(cmd) => {
alias::execute(cmd, output_options.resolve(OutputBehavior::HumanDefault)).await
}
Commands::Admin(cmd) => {
admin::execute(cmd, output_options.resolve(OutputBehavior::HumanDefault)).await
}
Commands::Bucket(args) => {
bucket::execute(
args,
output_options.resolve(OutputBehavior::StructuredDefault),
)
.await
}
Commands::Object(args) => {
let behavior = match &args.command {
object::ObjectCommands::Show(_) | object::ObjectCommands::Head(_) => {
OutputBehavior::HumanDefault
}
_ => OutputBehavior::StructuredDefault,
};
object::execute(args, output_options.resolve(behavior)).await
}
Commands::Ls(args) => {
ls::execute(args, output_options.resolve(OutputBehavior::HumanDefault)).await
}
Commands::Mb(args) => {
mb::execute(args, output_options.resolve(OutputBehavior::HumanDefault)).await
}
Commands::Rb(args) => {
rb::execute(args, output_options.resolve(OutputBehavior::HumanDefault)).await
}
Commands::Cat(args) => {
cat::execute(args, output_options.resolve(OutputBehavior::HumanDefault)).await
}
Commands::Head(args) => {
head::execute(args, output_options.resolve(OutputBehavior::HumanDefault)).await
}
Commands::Stat(args) => {
stat::execute(args, output_options.resolve(OutputBehavior::HumanDefault)).await
}
Commands::Cp(args) => {
cp::execute(args, output_options.resolve(OutputBehavior::HumanDefault)).await
}
Commands::Mv(args) => {
mv::execute(args, output_options.resolve(OutputBehavior::HumanDefault)).await
}
Commands::Rm(args) => {
rm::execute(args, output_options.resolve(OutputBehavior::HumanDefault)).await
}
Commands::Pipe(args) => {
pipe::execute(args, output_options.resolve(OutputBehavior::HumanDefault)).await
}
Commands::Find(args) => {
find::execute(args, output_options.resolve(OutputBehavior::HumanDefault)).await
}
Commands::Event(args) => {
event::execute(args, output_options.resolve(OutputBehavior::HumanDefault)).await
}
Commands::Cors(cmd) => {
cors::execute(
cors::CorsArgs { command: cmd },
output_options.resolve(OutputBehavior::HumanDefault),
)
.await
}
Commands::Diff(args) => {
diff::execute(args, output_options.resolve(OutputBehavior::HumanDefault)).await
}
Commands::Mirror(args) => {
mirror::execute(args, output_options.resolve(OutputBehavior::HumanDefault)).await
}
Commands::Tree(args) => {
tree::execute(args, output_options.resolve(OutputBehavior::HumanDefault)).await
}
Commands::Share(args) => {
share::execute(args, output_options.resolve(OutputBehavior::HumanDefault)).await
}
Commands::Version(cmd) => {
version::execute(
version::VersionArgs { command: cmd },
output_options.resolve(OutputBehavior::HumanDefault),
)
.await
}
Commands::Tag(cmd) => {
tag::execute(
tag::TagArgs { command: cmd },
output_options.resolve(OutputBehavior::HumanDefault),
)
.await
}
Commands::Anonymous(cmd) => {
anonymous::execute(
anonymous::AnonymousArgs { command: cmd },
output_options.resolve(OutputBehavior::HumanDefault),
)
.await
}
Commands::Quota(cmd) => {
quota::execute(
quota::QuotaArgs { command: cmd },
output_options.resolve(OutputBehavior::HumanDefault),
)
.await
}
Commands::Ilm(args) => {
ilm::execute(args, output_options.resolve(OutputBehavior::HumanDefault)).await
}
Commands::Replicate(args) => {
replicate::execute(args, output_options.resolve(OutputBehavior::HumanDefault)).await
}
Commands::Completions(args) => completions::execute(args),
}
}
#[cfg(test)]
mod tests {
use super::*;
use clap::Parser;
#[test]
fn structured_default_uses_auto_format_when_not_explicit() {
let options = GlobalOutputOptions {
format: None,
json: false,
no_color: false,
no_progress: false,
quiet: false,
};
let resolved = options.resolve(OutputBehavior::StructuredDefault);
assert_eq!(resolved.json, !std::io::stdout().is_terminal());
}
#[test]
fn human_default_keeps_human_format_when_not_explicit() {
let options = GlobalOutputOptions {
format: None,
json: false,
no_color: false,
no_progress: false,
quiet: false,
};
let resolved = options.resolve(OutputBehavior::HumanDefault);
assert!(!resolved.json);
}
#[test]
fn explicit_json_overrides_behavior_defaults() {
let options = GlobalOutputOptions {
format: Some(OutputFormat::Human),
json: true,
no_color: false,
no_progress: false,
quiet: false,
};
let resolved = options.resolve(OutputBehavior::HumanDefault);
assert!(resolved.json);
}
#[test]
fn explicit_human_overrides_structured_default() {
let options = GlobalOutputOptions {
format: Some(OutputFormat::Human),
json: false,
no_color: false,
no_progress: false,
quiet: false,
};
let resolved = options.resolve(OutputBehavior::StructuredDefault);
assert!(!resolved.json);
}
#[test]
fn explicit_auto_overrides_human_default() {
let options = GlobalOutputOptions {
format: Some(OutputFormat::Auto),
json: false,
no_color: false,
no_progress: false,
quiet: false,
};
let resolved = options.resolve(OutputBehavior::HumanDefault);
assert_eq!(resolved.json, !std::io::stdout().is_terminal());
}
#[test]
fn cli_accepts_bucket_cors_subcommand() {
let cli = Cli::try_parse_from(["rc", "bucket", "cors", "list", "local/my-bucket"])
.expect("parse bucket cors");
match cli.command {
Commands::Bucket(args) => match args.command {
bucket::BucketCommands::Cors(cors::CorsCommands::List(arg)) => {
assert_eq!(arg.path, "local/my-bucket");
}
other => panic!("expected bucket cors list command, got {:?}", other),
},
other => panic!("expected bucket command, got {:?}", other),
}
}
#[test]
fn cli_accepts_bucket_list_alias() {
let cli =
Cli::try_parse_from(["rc", "bucket", "ls", "local/"]).expect("parse bucket ls alias");
match cli.command {
Commands::Bucket(args) => match args.command {
bucket::BucketCommands::List(arg) => {
assert_eq!(arg.path, "local/");
}
other => panic!("expected bucket list alias, got {:?}", other),
},
other => panic!("expected bucket command, got {:?}", other),
}
}
#[test]
fn cli_accepts_top_level_cors_subcommand() {
let cli = Cli::try_parse_from(["rc", "cors", "remove", "local/my-bucket"])
.expect("parse top-level cors");
match cli.command {
Commands::Cors(cors::CorsCommands::Remove(arg)) => {
assert_eq!(arg.path, "local/my-bucket");
}
other => panic!("expected top-level cors remove command, got {:?}", other),
}
}
#[test]
fn cli_accepts_top_level_cors_get_alias() {
let cli =
Cli::try_parse_from(["rc", "cors", "get", "local/my-bucket"]).expect("parse cors get");
match cli.command {
Commands::Cors(cors::CorsCommands::List(arg)) => {
assert_eq!(arg.path, "local/my-bucket");
}
other => panic!("expected top-level cors get alias, got {:?}", other),
}
}
#[test]
fn cli_accepts_top_level_event_subcommand() {
let cli = Cli::try_parse_from(["rc", "event", "list", "local/my-bucket"])
.expect("parse top-level event");
match cli.command {
Commands::Event(event::EventArgs {
command: event::EventCommands::List(arg),
}) => {
assert_eq!(arg.path, "local/my-bucket");
}
other => panic!("expected top-level event list command, got {:?}", other),
}
}
#[test]
fn cli_accepts_top_level_event_add_subcommand() {
let cli = Cli::try_parse_from([
"rc",
"event",
"add",
"local/my-bucket",
"arn:aws:sqs:us-east-1:123456789012:jobs",
"--event",
"put,delete",
"--force",
])
.expect("parse top-level event add");
match cli.command {
Commands::Event(event::EventArgs {
command: event::EventCommands::Add(arg),
}) => {
assert_eq!(arg.path, "local/my-bucket");
assert_eq!(arg.arn, "arn:aws:sqs:us-east-1:123456789012:jobs");
assert_eq!(arg.events, vec!["put,delete".to_string()]);
assert!(arg.force);
}
other => panic!("expected top-level event add command, got {:?}", other),
}
}
#[test]
fn cli_accepts_object_list_alias() {
let cli = Cli::try_parse_from(["rc", "object", "ls", "local/my-bucket/logs/"])
.expect("parse object ls alias");
match cli.command {
Commands::Object(args) => match args.command {
object::ObjectCommands::List(arg) => {
assert_eq!(arg.path, "local/my-bucket/logs/");
}
other => panic!("expected object list alias, got {:?}", other),
},
other => panic!("expected object command, got {:?}", other),
}
}
#[test]
fn cli_accepts_top_level_event_remove_subcommand() {
let cli = Cli::try_parse_from([
"rc",
"event",
"remove",
"local/my-bucket",
"arn:aws:sns:us-east-1:123456789012:alerts",
"--force",
])
.expect("parse top-level event remove");
match cli.command {
Commands::Event(event::EventArgs {
command: event::EventCommands::Remove(arg),
}) => {
assert_eq!(arg.path, "local/my-bucket");
assert_eq!(arg.arn, "arn:aws:sns:us-east-1:123456789012:alerts");
assert!(arg.force);
}
other => panic!("expected top-level event remove command, got {:?}", other),
}
}
#[test]
fn cli_accepts_bucket_cors_get_alias() {
let cli = Cli::try_parse_from(["rc", "bucket", "cors", "get", "local/my-bucket"])
.expect("parse bucket cors get");
match cli.command {
Commands::Bucket(args) => match args.command {
bucket::BucketCommands::Cors(cors::CorsCommands::List(arg)) => {
assert_eq!(arg.path, "local/my-bucket");
}
other => panic!("expected bucket cors get alias, got {:?}", other),
},
other => panic!("expected bucket command, got {:?}", other),
}
}
#[test]
fn cli_accepts_bucket_cors_set_with_positional_source() {
let cli =
Cli::try_parse_from(["rc", "bucket", "cors", "set", "local/my-bucket", "cors.xml"])
.expect("parse bucket cors set with positional source");
match cli.command {
Commands::Bucket(args) => match args.command {
bucket::BucketCommands::Cors(cors::CorsCommands::Set(arg)) => {
assert_eq!(arg.path, "local/my-bucket");
assert_eq!(arg.source.as_deref(), Some("cors.xml"));
}
other => panic!("expected bucket cors set command, got {:?}", other),
},
other => panic!("expected bucket command, got {:?}", other),
}
}
#[test]
fn cli_accepts_top_level_cors_set_with_positional_source() {
let cli = Cli::try_parse_from(["rc", "cors", "set", "local/my-bucket", "cors.xml"])
.expect("parse top-level cors set with positional source");
match cli.command {
Commands::Cors(cors::CorsCommands::Set(arg)) => {
assert_eq!(arg.path, "local/my-bucket");
assert_eq!(arg.source.as_deref(), Some("cors.xml"));
assert_eq!(arg.file, None);
assert!(!arg.force);
}
other => panic!("expected top-level cors set command, got {:?}", other),
}
}
#[test]
fn cli_accepts_top_level_cors_set_with_legacy_file_flag() {
let cli = Cli::try_parse_from([
"rc",
"cors",
"set",
"local/my-bucket",
"--file",
"cors.json",
"--force",
])
.expect("parse top-level cors set with --file");
match cli.command {
Commands::Cors(cors::CorsCommands::Set(arg)) => {
assert_eq!(arg.path, "local/my-bucket");
assert_eq!(arg.source, None);
assert_eq!(arg.file.as_deref(), Some("cors.json"));
assert!(arg.force);
}
other => panic!("expected top-level cors set command, got {:?}", other),
}
}
#[test]
fn cli_accepts_bucket_cors_list_force_flag() {
let cli =
Cli::try_parse_from(["rc", "bucket", "cors", "list", "local/my-bucket", "--force"])
.expect("parse bucket cors list with force");
match cli.command {
Commands::Bucket(args) => match args.command {
bucket::BucketCommands::Cors(cors::CorsCommands::List(arg)) => {
assert_eq!(arg.path, "local/my-bucket");
assert!(arg.force);
}
other => panic!("expected bucket cors list command, got {:?}", other),
},
other => panic!("expected bucket command, got {:?}", other),
}
}
#[test]
fn cli_accepts_bucket_lifecycle_subcommand() {
let cli = Cli::try_parse_from([
"rc",
"bucket",
"lifecycle",
"rule",
"list",
"local/my-bucket",
])
.expect("parse bucket lifecycle rule list");
match cli.command {
Commands::Bucket(args) => match args.command {
bucket::BucketCommands::Lifecycle(ilm::IlmArgs {
command: ilm::IlmCommands::Rule(ilm::rule::RuleCommands::List(arg)),
}) => {
assert_eq!(arg.path, "local/my-bucket");
assert!(!arg.force);
}
other => panic!(
"expected bucket lifecycle rule list command, got {:?}",
other
),
},
other => panic!("expected bucket command, got {:?}", other),
}
}
#[test]
fn cli_accepts_bucket_replication_subcommand() {
let cli = Cli::try_parse_from(["rc", "bucket", "replication", "status", "local/my-bucket"])
.expect("parse bucket replication status");
match cli.command {
Commands::Bucket(args) => match args.command {
bucket::BucketCommands::Replication(replicate::ReplicateArgs {
command: replicate::ReplicateCommands::Status(arg),
}) => {
assert_eq!(arg.path, "local/my-bucket");
assert!(!arg.force);
}
other => panic!(
"expected bucket replication status command, got {:?}",
other
),
},
other => panic!("expected bucket command, got {:?}", other),
}
}
#[test]
fn cli_accepts_bucket_remove_subcommand() {
let cli = Cli::try_parse_from(["rc", "bucket", "remove", "local/my-bucket"])
.expect("parse bucket remove");
match cli.command {
Commands::Bucket(args) => match args.command {
bucket::BucketCommands::Remove(arg) => {
assert_eq!(arg.target, "local/my-bucket");
}
other => panic!("expected bucket remove command, got {:?}", other),
},
other => panic!("expected bucket command, got {:?}", other),
}
}
#[test]
fn cli_accepts_object_remove_subcommand() {
let cli = Cli::try_parse_from([
"rc",
"object",
"remove",
"local/my-bucket/report.csv",
"--dry-run",
])
.expect("parse object remove");
match cli.command {
Commands::Object(args) => match args.command {
object::ObjectCommands::Remove(arg) => {
assert_eq!(arg.paths, vec!["local/my-bucket/report.csv".to_string()]);
assert!(arg.dry_run);
}
other => panic!("expected object remove command, got {:?}", other),
},
other => panic!("expected object command, got {:?}", other),
}
}
#[test]
fn cli_accepts_bucket_event_remove_subcommand() {
let cli = Cli::try_parse_from([
"rc",
"bucket",
"event",
"remove",
"local/my-bucket",
"arn:aws:sns:us-east-1:123456789012:alerts",
])
.expect("parse bucket event remove");
match cli.command {
Commands::Bucket(args) => match args.command {
bucket::BucketCommands::Event(event::EventCommands::Remove(arg)) => {
assert_eq!(arg.path, "local/my-bucket");
assert_eq!(arg.arn, "arn:aws:sns:us-east-1:123456789012:alerts");
}
other => panic!("expected bucket event remove command, got {:?}", other),
},
other => panic!("expected bucket command, got {:?}", other),
}
}
#[test]
fn cli_accepts_rm_purge_flag() {
let cli = Cli::try_parse_from(["rc", "rm", "local/my-bucket/object.txt", "--purge"])
.expect("parse rm purge");
match cli.command {
Commands::Rm(arg) => {
assert_eq!(arg.paths, vec!["local/my-bucket/object.txt".to_string()]);
assert!(arg.purge);
}
other => panic!("expected rm command, got {:?}", other),
}
}
#[test]
fn cli_accepts_object_remove_purge_flag() {
let cli = Cli::try_parse_from([
"rc",
"object",
"remove",
"local/my-bucket/object.txt",
"--purge",
])
.expect("parse object remove purge");
match cli.command {
Commands::Object(args) => match args.command {
object::ObjectCommands::Remove(arg) => {
assert_eq!(arg.paths, vec!["local/my-bucket/object.txt".to_string()]);
assert!(arg.purge);
}
other => panic!("expected object remove command, got {:?}", other),
},
other => panic!("expected object command, got {:?}", other),
}
}
#[test]
fn cli_accepts_object_stat_subcommand() {
let cli = Cli::try_parse_from(["rc", "object", "stat", "local/my-bucket/report.json"])
.expect("parse object stat");
match cli.command {
Commands::Object(args) => match args.command {
object::ObjectCommands::Stat(arg) => {
assert_eq!(arg.path, "local/my-bucket/report.json");
}
other => panic!("expected object stat command, got {:?}", other),
},
other => panic!("expected object command, got {:?}", other),
}
}
#[test]
fn cli_accepts_object_copy_with_transfer_options() {
let cli = Cli::try_parse_from([
"rc",
"object",
"copy",
"./report.json",
"local/my-bucket/reports/",
"--content-type",
"application/json",
"--storage-class",
"STANDARD_IA",
"--dry-run",
])
.expect("parse object copy with transfer options");
match cli.command {
Commands::Object(args) => match args.command {
object::ObjectCommands::Copy(arg) => {
assert_eq!(arg.source, "./report.json");
assert_eq!(arg.target, "local/my-bucket/reports/");
assert_eq!(arg.content_type.as_deref(), Some("application/json"));
assert_eq!(arg.storage_class.as_deref(), Some("STANDARD_IA"));
assert!(arg.dry_run);
}
other => panic!("expected object copy command, got {:?}", other),
},
other => panic!("expected object command, got {:?}", other),
}
}
#[test]
fn cli_accepts_object_move_with_recursive_dry_run() {
let cli = Cli::try_parse_from([
"rc",
"object",
"move",
"local/source-bucket/logs/",
"local/archive-bucket/logs/",
"--recursive",
"--dry-run",
"--continue-on-error",
])
.expect("parse object move with recursive dry-run");
match cli.command {
Commands::Object(args) => match args.command {
object::ObjectCommands::Move(arg) => {
assert_eq!(arg.source, "local/source-bucket/logs/");
assert_eq!(arg.target, "local/archive-bucket/logs/");
assert!(arg.recursive);
assert!(arg.dry_run);
assert!(arg.continue_on_error);
}
other => panic!("expected object move command, got {:?}", other),
},
other => panic!("expected object command, got {:?}", other),
}
}
#[test]
fn cli_accepts_object_show_and_head_options() {
let show_cli = Cli::try_parse_from([
"rc",
"object",
"show",
"local/my-bucket/report.json",
"--version-id",
"v1",
"--rewind",
"1h",
])
.expect("parse object show options");
match show_cli.command {
Commands::Object(args) => match args.command {
object::ObjectCommands::Show(arg) => {
assert_eq!(arg.path, "local/my-bucket/report.json");
assert_eq!(arg.version_id.as_deref(), Some("v1"));
assert_eq!(arg.rewind.as_deref(), Some("1h"));
}
other => panic!("expected object show command, got {:?}", other),
},
other => panic!("expected object command, got {:?}", other),
}
let head_cli = Cli::try_parse_from([
"rc",
"object",
"head",
"local/my-bucket/report.json",
"--bytes",
"128",
"--version-id",
"v2",
])
.expect("parse object head options");
match head_cli.command {
Commands::Object(args) => match args.command {
object::ObjectCommands::Head(arg) => {
assert_eq!(arg.path, "local/my-bucket/report.json");
assert_eq!(arg.bytes, Some(128));
assert_eq!(arg.version_id.as_deref(), Some("v2"));
}
other => panic!("expected object head command, got {:?}", other),
},
other => panic!("expected object command, got {:?}", other),
}
}
#[test]
fn cli_accepts_object_find_and_tree_options() {
let find_cli = Cli::try_parse_from([
"rc",
"object",
"find",
"local/my-bucket/logs/",
"--name",
"*.json",
"--maxdepth",
"2",
"--count",
"--print",
])
.expect("parse object find options");
match find_cli.command {
Commands::Object(args) => match args.command {
object::ObjectCommands::Find(arg) => {
assert_eq!(arg.path, "local/my-bucket/logs/");
assert_eq!(arg.name.as_deref(), Some("*.json"));
assert_eq!(arg.maxdepth, 2);
assert!(arg.count);
assert!(arg.print);
}
other => panic!("expected object find command, got {:?}", other),
},
other => panic!("expected object command, got {:?}", other),
}
let tree_cli = Cli::try_parse_from([
"rc",
"object",
"tree",
"local/my-bucket/logs/",
"--level",
"4",
"--size",
"--pattern",
"*.json",
"--full-path",
])
.expect("parse object tree options");
match tree_cli.command {
Commands::Object(args) => match args.command {
object::ObjectCommands::Tree(arg) => {
assert_eq!(arg.path, "local/my-bucket/logs/");
assert_eq!(arg.level, 4);
assert!(arg.size);
assert_eq!(arg.pattern.as_deref(), Some("*.json"));
assert!(arg.full_path);
}
other => panic!("expected object tree command, got {:?}", other),
},
other => panic!("expected object command, got {:?}", other),
}
}
}