From 49714d52a9a700c30a7a35577d8a2a0071a52523 Mon Sep 17 00:00:00 2001 From: koushiro Date: Wed, 23 Oct 2024 16:39:16 +0800 Subject: [PATCH 1/4] refactor(bin/oli): use `clap_derive` to reduce boilerplate code --- bin/oli/Cargo.lock | 15 +++- bin/oli/Cargo.toml | 2 +- bin/oli/src/bin/oli.rs | 101 +++++++-------------------- bin/oli/src/commands/cat.rs | 51 +++++++------- bin/oli/src/commands/cli.rs | 47 ------------- bin/oli/src/commands/cp.rs | 128 ++++++++++++++++------------------- bin/oli/src/commands/ls.rs | 63 ++++++++--------- bin/oli/src/commands/mod.rs | 40 ++++++----- bin/oli/src/commands/rm.rs | 61 +++++++---------- bin/oli/src/commands/stat.rs | 72 ++++++++++---------- bin/oli/src/config/mod.rs | 1 - 11 files changed, 233 insertions(+), 348 deletions(-) delete mode 100644 bin/oli/src/commands/cli.rs diff --git a/bin/oli/Cargo.lock b/bin/oli/Cargo.lock index fc969b527892..672cc75e590e 100644 --- a/bin/oli/Cargo.lock +++ b/bin/oli/Cargo.lock @@ -671,6 +671,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b97f376d85a664d5837dbae44bf546e6477a679ff6610010f17276f686d867e8" dependencies = [ "clap_builder", + "clap_derive", ] [[package]] @@ -685,6 +686,18 @@ dependencies = [ "strsim", ] +[[package]] +name = "clap_derive" +version = "4.5.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ac6a0c7b1a9e9a5186361f67dfa1b88213572f427fb9ab038efb2bd8c582dab" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "clap_lex" version = "0.7.2" @@ -1663,7 +1676,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4979f22fdb869068da03c9f7528f8297c6fd2606bc3a4affe42e6a823fdb8da4" dependencies = [ "cfg-if", - "windows-targets 0.52.6", + "windows-targets 0.48.5", ] [[package]] diff --git a/bin/oli/Cargo.toml b/bin/oli/Cargo.toml index 924250ded3db..4030cffee2e3 100644 --- a/bin/oli/Cargo.toml +++ b/bin/oli/Cargo.toml @@ -55,7 +55,7 @@ services-sled = ["opendal/services-sled"] [dependencies] anyhow = "1" -clap = { version = "4", features = ["cargo", "string"] } +clap = { version = "4", features = ["cargo", "string", "derive", "deprecated"] } dirs = "5.0.1" futures = "0.3" opendal = { version = "0.50.0", path = "../../core", features = [ diff --git a/bin/oli/src/bin/oli.rs b/bin/oli/src/bin/oli.rs index 56e281e314a2..c732d8919977 100644 --- a/bin/oli/src/bin/oli.rs +++ b/bin/oli/src/bin/oli.rs @@ -22,92 +22,37 @@ //! 'oli' or 'oli.exe' it offers the oli command-line interface, and //! when it is called 'ocp' it behaves as a proxy to 'oli cp'. -use std::env; -use std::ffi::OsStr; +use std::ffi::OsString; use std::path::PathBuf; -use anyhow::anyhow; use anyhow::Result; use clap::value_parser; -use clap::Arg; -use clap::Command; -use dirs::config_dir; - -fn new_cmd(name: &'static str) -> Result { - let d = config_dir().ok_or_else(|| anyhow!("unknown config dir"))?; - let default_config_path = d.join("oli/config.toml").as_os_str().to_owned(); +use oli::commands::OliSubcommand; + +#[derive(Debug, clap::Parser)] +#[command(about, version)] +pub struct Oli { + /// Path to the config file. + #[arg( + long, + global = true, + default_value = default_config_path(), + value_parser = value_parser!(PathBuf) + )] + config: Option, + + #[command(subcommand)] + subcommand: OliSubcommand, +} - Ok(Command::new(name) - .version(env!("CARGO_PKG_VERSION")) - .arg( - Arg::new("config") - .long("config") - .help("Path to the config file") - .global(true) - .default_value(default_config_path) - .value_parser(value_parser!(PathBuf)) - .required(false), - ) - .subcommand_required(true) - .arg_required_else_help(true)) +fn default_config_path() -> OsString { + let d = dirs::config_dir().expect("unknown config dir"); + d.join("oli/config.toml").as_os_str().to_owned() } #[tokio::main] async fn main() -> Result<()> { - // Guard against infinite proxy recursion. This mostly happens due to - // bugs in oli. - do_recursion_guard()?; - - match env::args() - .next() - .map(PathBuf::from) - .as_ref() - .and_then(|a| a.file_stem()) - .and_then(OsStr::to_str) - { - Some("oli") => { - let cmd = oli::commands::cli::cli(new_cmd("oli")?); - oli::commands::cli::main(&cmd.get_matches()).await?; - } - Some("ocat") => { - let cmd = oli::commands::cat::cli(new_cmd("ocat")?); - oli::commands::cat::main(&cmd.get_matches()).await?; - } - Some("ocp") => { - let cmd = oli::commands::cp::cli(new_cmd("ocp")?); - oli::commands::cp::main(&cmd.get_matches()).await?; - } - Some("ols") => { - let cmd = oli::commands::ls::cli(new_cmd("ols")?); - oli::commands::ls::main(&cmd.get_matches()).await?; - } - Some("orm") => { - let cmd = oli::commands::rm::cli(new_cmd("orm")?); - oli::commands::rm::main(&cmd.get_matches()).await?; - } - Some("ostat") => { - let cmd = oli::commands::stat::cli(new_cmd("ostat")?); - oli::commands::stat::main(&cmd.get_matches()).await?; - } - Some(v) => { - println!("{v} is not supported") - } - None => return Err(anyhow!("couldn't determine self executable name")), - } - - Ok(()) -} - -fn do_recursion_guard() -> Result<()> { - static OLI_RECURSION_COUNT_MAX: i32 = 20; - - let recursion_count = env::var("OLI_RECURSION_COUNT") - .ok() - .and_then(|s| s.parse().ok()) - .unwrap_or(0); - if recursion_count > OLI_RECURSION_COUNT_MAX { - return Err(anyhow!("infinite recursion detected")); - } - + let cli: Oli = clap::Parser::parse(); + cli.subcommand.run().await?; Ok(()) } diff --git a/bin/oli/src/commands/cat.rs b/bin/oli/src/commands/cat.rs index 4bfe8190f7f0..35f9910ceb62 100644 --- a/bin/oli/src/commands/cat.rs +++ b/bin/oli/src/commands/cat.rs @@ -17,37 +17,38 @@ use std::path::PathBuf; -use anyhow::anyhow; use anyhow::Result; -use clap::Arg; -use clap::ArgMatches; -use clap::Command; use futures::io; use crate::config::Config; -pub async fn main(args: &ArgMatches) -> Result<()> { - let config_path = args - .get_one::("config") - .ok_or_else(|| anyhow!("missing config path"))?; - let cfg = Config::load(config_path)?; +#[derive(Debug, clap::Parser)] +#[command( + name = "cat", + about = "Display object content", + disable_version_flag = true +)] +pub struct CatCmd { + /// Path to the config file. + #[arg(from_global)] + pub config: PathBuf, + #[arg()] + pub target: String, +} - let target = args - .get_one::("target") - .ok_or_else(|| anyhow!("missing target"))?; - let (op, path) = cfg.parse_location(target)?; +impl CatCmd { + pub async fn run(&self) -> Result<()> { + let cfg = Config::load(&self.config)?; - let reader = op.reader(&path).await?; - let meta = op.stat(&path).await?; - let mut buf_reader = reader - .into_futures_async_read(0..meta.content_length()) - .await?; - let mut stdout = io::AllowStdIo::new(std::io::stdout()); - io::copy_buf(&mut buf_reader, &mut stdout).await?; - Ok(()) -} + let (op, path) = cfg.parse_location(&self.target)?; -pub fn cli(cmd: Command) -> Command { - cmd.about("display object content") - .arg(Arg::new("target").required(true)) + let reader = op.reader(&path).await?; + let meta = op.stat(&path).await?; + let mut buf_reader = reader + .into_futures_async_read(0..meta.content_length()) + .await?; + let mut stdout = io::AllowStdIo::new(std::io::stdout()); + io::copy_buf(&mut buf_reader, &mut stdout).await?; + Ok(()) + } } diff --git a/bin/oli/src/commands/cli.rs b/bin/oli/src/commands/cli.rs deleted file mode 100644 index 320ed155d214..000000000000 --- a/bin/oli/src/commands/cli.rs +++ /dev/null @@ -1,47 +0,0 @@ -// Licensed to the Apache Software Foundation (ASF) under one -// or more contributor license agreements. See the NOTICE file -// distributed with this work for additional information -// regarding copyright ownership. The ASF licenses this file -// to you under the Apache License, Version 2.0 (the -// "License"); you may not use this file except in compliance -// with the License. You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, -// software distributed under the License is distributed on an -// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -// KIND, either express or implied. See the License for the -// specific language governing permissions and limitations -// under the License. - -use anyhow::anyhow; -use anyhow::Result; -use clap::ArgMatches; -use clap::Command; - -pub async fn main(args: &ArgMatches) -> Result<()> { - match args.subcommand() { - Some(("cat", sub_args)) => super::cat::main(sub_args).await?, - Some(("cp", sub_args)) => super::cp::main(sub_args).await?, - Some(("ls", sub_args)) => super::ls::main(sub_args).await?, - Some(("rm", sub_args)) => super::rm::main(sub_args).await?, - Some(("stat", sub_args)) => super::stat::main(sub_args).await?, - _ => return Err(anyhow!("not handled")), - } - - Ok(()) -} - -fn new_cmd(name: &'static str) -> Command { - Command::new(name).disable_version_flag(true) -} - -pub fn cli(cmd: Command) -> Command { - cmd.about("OpenDAL Command Line Interface") - .subcommand(super::cat::cli(new_cmd("cat"))) - .subcommand(super::cp::cli(new_cmd("cp"))) - .subcommand(super::ls::cli(new_cmd("ls"))) - .subcommand(super::rm::cli(new_cmd("rm"))) - .subcommand(super::stat::cli(new_cmd("stat"))) -} diff --git a/bin/oli/src/commands/cp.rs b/bin/oli/src/commands/cp.rs index d8ea084df3bc..35460b37da99 100644 --- a/bin/oli/src/commands/cp.rs +++ b/bin/oli/src/commands/cp.rs @@ -18,88 +18,76 @@ use std::path::Path; use std::path::PathBuf; -use anyhow::anyhow; use anyhow::Result; -use clap::Arg; -use clap::ArgAction; -use clap::ArgMatches; -use clap::Command; use futures::AsyncWriteExt; use futures::TryStreamExt; use crate::config::Config; -pub async fn main(args: &ArgMatches) -> Result<()> { - let config_path = args - .get_one::("config") - .ok_or_else(|| anyhow!("missing config path"))?; - let cfg = Config::load(config_path)?; - let recursive = args.get_flag("recursive"); +#[derive(Debug, clap::Parser)] +#[command(name = "cp", about = "Copy object", disable_version_flag = true)] +pub struct CopyCmd { + /// Path to the config file. + #[arg(from_global)] + pub config: PathBuf, + #[arg()] + pub source: String, + #[arg()] + pub destination: String, + /// Copy objects recursively. + #[arg(short = 'r', long)] + pub recursive: bool, +} - let src = args - .get_one::("source") - .ok_or_else(|| anyhow!("missing source"))?; - let (src_op, src_path) = cfg.parse_location(src)?; +impl CopyCmd { + pub async fn run(&self) -> Result<()> { + let cfg = Config::load(&self.config)?; - let dst = args - .get_one::("destination") - .ok_or_else(|| anyhow!("missing target"))?; - let (dst_op, dst_path) = cfg.parse_location(dst)?; + let (src_op, src_path) = cfg.parse_location(&self.source)?; - if !recursive { - let mut dst_w = dst_op.writer(&dst_path).await?.into_futures_async_write(); - let src_meta = src_op.stat(&src_path).await?; - let reader = src_op.reader_with(&src_path).chunk(8 * 1024 * 1024).await?; - let buf_reader = reader - .into_futures_async_read(0..src_meta.content_length()) - .await?; - futures::io::copy_buf(buf_reader, &mut dst_w).await?; - // flush data - dst_w.close().await?; - return Ok(()); - } + let (dst_op, dst_path) = cfg.parse_location(&self.destination)?; - let dst_root = Path::new(&dst_path); - let mut ds = src_op.lister_with(&src_path).recursive(true).await?; - let prefix = src_path.strip_prefix('/').unwrap_or(src_path.as_str()); - while let Some(de) = ds.try_next().await? { - let meta = de.metadata(); - if meta.mode().is_dir() { - continue; + if !self.recursive { + let mut dst_w = dst_op.writer(&dst_path).await?.into_futures_async_write(); + let src_meta = src_op.stat(&src_path).await?; + let reader = src_op.reader_with(&src_path).chunk(8 * 1024 * 1024).await?; + let buf_reader = reader + .into_futures_async_read(0..src_meta.content_length()) + .await?; + futures::io::copy_buf(buf_reader, &mut dst_w).await?; + // flush data + dst_w.close().await?; + return Ok(()); } - let depath = de.path(); - let fp = depath - .strip_prefix('/') - .unwrap_or(depath) - .strip_prefix(prefix) - .expect("invalid path"); - let reader = src_op.reader_with(de.path()).chunk(8 * 1024 * 1024).await?; - let buf_reader = reader - .into_futures_async_read(0..meta.content_length()) - .await?; - let mut writer = dst_op - .writer(&dst_root.join(fp).to_string_lossy()) - .await? - .into_futures_async_write(); + let dst_root = Path::new(&dst_path); + let mut ds = src_op.lister_with(&src_path).recursive(true).await?; + let prefix = src_path.strip_prefix('/').unwrap_or(src_path.as_str()); + while let Some(de) = ds.try_next().await? { + let meta = de.metadata(); + if meta.mode().is_dir() { + continue; + } + let depath = de.path(); + let fp = depath + .strip_prefix('/') + .unwrap_or(depath) + .strip_prefix(prefix) + .expect("invalid path"); + let reader = src_op.reader_with(de.path()).chunk(8 * 1024 * 1024).await?; + let buf_reader = reader + .into_futures_async_read(0..meta.content_length()) + .await?; - println!("Copying {}", de.path()); - futures::io::copy_buf(buf_reader, &mut writer).await?; - writer.close().await?; - } - Ok(()) -} + let mut writer = dst_op + .writer(&dst_root.join(fp).to_string_lossy()) + .await? + .into_futures_async_write(); -pub fn cli(cmd: Command) -> Command { - cmd.about("copy") - .arg(Arg::new("source").required(true)) - .arg(Arg::new("destination").required(true)) - .arg( - Arg::new("recursive") - .required(false) - .long("recursive") - .short('r') - .help("Copy files under source recursively to destination") - .action(ArgAction::SetTrue), - ) + println!("Copying {}", de.path()); + futures::io::copy_buf(buf_reader, &mut writer).await?; + writer.close().await?; + } + Ok(()) + } } diff --git a/bin/oli/src/commands/ls.rs b/bin/oli/src/commands/ls.rs index 50c60c56ec6a..4c24418f6bf6 100644 --- a/bin/oli/src/commands/ls.rs +++ b/bin/oli/src/commands/ls.rs @@ -17,51 +17,42 @@ use std::path::PathBuf; -use anyhow::anyhow; use anyhow::Result; -use clap::Arg; -use clap::ArgAction; -use clap::ArgMatches; -use clap::Command; use futures::TryStreamExt; use crate::config::Config; -pub async fn main(args: &ArgMatches) -> Result<()> { - let config_path = args - .get_one::("config") - .ok_or_else(|| anyhow!("missing config path"))?; - let cfg = Config::load(config_path)?; +#[derive(Debug, clap::Parser)] +#[command(name = "ls", about = "List object", disable_version_flag = true)] +pub struct LsCmd { + /// Path to the config file. + #[arg(from_global)] + pub config: PathBuf, + #[arg()] + pub target: String, + /// List objects recursively. + #[arg(short, long)] + pub recursive: bool, +} - let recursive = args.get_flag("recursive"); +impl LsCmd { + pub async fn run(&self) -> Result<()> { + let cfg = Config::load(&self.config)?; - let target = args - .get_one::("target") - .ok_or_else(|| anyhow!("missing target"))?; - let (op, path) = cfg.parse_location(target)?; + let (op, path) = cfg.parse_location(&self.target)?; - if !recursive { - let mut ds = op.lister(&path).await?; - while let Some(de) = ds.try_next().await? { - println!("{}", de.name()); + if !self.recursive { + let mut ds = op.lister(&path).await?; + while let Some(de) = ds.try_next().await? { + println!("{}", de.name()); + } + return Ok(()); } - return Ok(()); - } - let mut ds = op.lister_with(&path).recursive(true).await?; - while let Some(de) = ds.try_next().await? { - println!("{}", de.path()); + let mut ds = op.lister_with(&path).recursive(true).await?; + while let Some(de) = ds.try_next().await? { + println!("{}", de.path()); + } + Ok(()) } - Ok(()) -} - -pub fn cli(cmd: Command) -> Command { - cmd.about("ls").arg(Arg::new("target").required(true)).arg( - Arg::new("recursive") - .required(false) - .long("recursive") - .short('r') - .help("List recursively") - .action(ArgAction::SetTrue), - ) } diff --git a/bin/oli/src/commands/mod.rs b/bin/oli/src/commands/mod.rs index 749af022a40e..0971a42f4551 100644 --- a/bin/oli/src/commands/mod.rs +++ b/bin/oli/src/commands/mod.rs @@ -15,27 +15,31 @@ // specific language governing permissions and limitations // under the License. -//! Commands provides the implementation of each commands. -//! -//! Each submodule represents a single command, and should export 2 functions respectively. -//! The signature of those should be like the following: -//! -//! ```ignore -//! pub async fn main(args: &ArgMatches) -> Result<()> { -//! // the main logic -//! } -//! -//! // cli is used to customize the command, like setting the arguments. -//! // As each command can be invoked like a separate binary, -//! // we will pass a command with different name to get the complete command. -//! pub fn cli(cmd: Command) -> Command { -//! // set the arguments, help message, etc. -//! } -//! ``` +//! Commands provides the implementation of each command. pub mod cat; -pub mod cli; pub mod cp; pub mod ls; pub mod rm; pub mod stat; + +#[derive(Debug, clap::Subcommand)] +pub enum OliSubcommand { + Cat(cat::CatCmd), + Cp(cp::CopyCmd), + Ls(ls::LsCmd), + Rm(rm::RmCmd), + Stat(stat::StatCmd), +} + +impl OliSubcommand { + pub async fn run(&self) -> anyhow::Result<()> { + match self { + Self::Cat(cmd) => cmd.run().await, + Self::Cp(cmd) => cmd.run().await, + Self::Ls(cmd) => cmd.run().await, + Self::Rm(cmd) => cmd.run().await, + Self::Stat(cmd) => cmd.run().await, + } + } +} diff --git a/bin/oli/src/commands/rm.rs b/bin/oli/src/commands/rm.rs index 87fbe913aedc..fd91261b1eb7 100644 --- a/bin/oli/src/commands/rm.rs +++ b/bin/oli/src/commands/rm.rs @@ -17,48 +17,37 @@ use std::path::PathBuf; -use anyhow::anyhow; use anyhow::Result; -use clap::Arg; -use clap::ArgAction; -use clap::ArgMatches; -use clap::Command; use crate::config::Config; -pub async fn main(args: &ArgMatches) -> Result<()> { - let config_path = args - .get_one::("config") - .ok_or_else(|| anyhow!("missing config path"))?; - let cfg = Config::load(config_path)?; +#[derive(Debug, clap::Parser)] +#[command(name = "rm", about = "Remove object", disable_version_flag = true)] +pub struct RmCmd { + /// Path to the config file. + #[arg(from_global)] + pub config: PathBuf, + #[arg()] + pub target: String, + /// Remove objects recursively. + #[arg(short, long)] + pub recursive: bool, +} - let recursive = args.get_flag("recursive"); +impl RmCmd { + pub async fn run(&self) -> Result<()> { + let cfg = Config::load(&self.config)?; - let target = args - .get_one::("target") - .ok_or_else(|| anyhow!("missing target"))?; - let (op, path) = cfg.parse_location(target)?; + let (op, path) = cfg.parse_location(&self.target)?; - if !recursive { - println!("Delete: {path}"); - op.delete(&path).await?; - return Ok(()); - } + if !self.recursive { + println!("Delete: {path}"); + op.delete(&path).await?; + return Ok(()); + } - println!("Delete all: {path}"); - op.remove_all(&path).await?; - Ok(()) -} - -pub fn cli(cmd: Command) -> Command { - cmd.about("remove object") - .arg(Arg::new("target").required(true)) - .arg( - Arg::new("recursive") - .required(false) - .long("recursive") - .short('r') - .help("List recursively") - .action(ArgAction::SetTrue), - ) + println!("Delete all: {path}"); + op.remove_all(&path).await?; + Ok(()) + } } diff --git a/bin/oli/src/commands/stat.rs b/bin/oli/src/commands/stat.rs index d4efacae485a..1b16281e3f7f 100644 --- a/bin/oli/src/commands/stat.rs +++ b/bin/oli/src/commands/stat.rs @@ -17,45 +17,47 @@ use std::path::PathBuf; -use anyhow::anyhow; use anyhow::Result; -use clap::Arg; -use clap::ArgMatches; -use clap::Command; use crate::config::Config; -pub async fn main(args: &ArgMatches) -> Result<()> { - let config_path = args - .get_one::("config") - .ok_or_else(|| anyhow!("missing config path"))?; - let cfg = Config::load(config_path)?; - - let target = args - .get_one::("target") - .ok_or_else(|| anyhow!("missing target"))?; - let (op, path) = cfg.parse_location(target)?; - - let meta = op.stat(&path).await?; - println!("path: {target}"); - let size = meta.content_length(); - println!("size: {size}"); - if let Some(etag) = meta.etag() { - println!("etag: {etag}"); - } - let file_type = meta.mode(); - println!("type: {file_type}"); - if let Some(content_type) = meta.content_type() { - println!("content-type: {content_type}"); - } - if let Some(last_modified) = meta.last_modified() { - println!("last-modified: {last_modified}"); - } - - Ok(()) +#[derive(Debug, clap::Parser)] +#[command( + name = "stat", + about = "Show object metadata", + disable_version_flag = true +)] +pub struct StatCmd { + /// Path to the config file. + #[arg(from_global)] + pub config: PathBuf, + #[arg()] + pub target: String, } -pub fn cli(cmd: Command) -> Command { - cmd.about("show object metadata") - .arg(Arg::new("target").required(true)) +impl StatCmd { + pub async fn run(&self) -> Result<()> { + let cfg = Config::load(&self.config)?; + + let target = &self.target; + println!("path: {target}"); + let (op, path) = cfg.parse_location(target)?; + + let meta = op.stat(&path).await?; + let size = meta.content_length(); + println!("size: {size}"); + if let Some(etag) = meta.etag() { + println!("etag: {etag}"); + } + let file_type = meta.mode(); + println!("type: {file_type}"); + if let Some(content_type) = meta.content_type() { + println!("content-type: {content_type}"); + } + if let Some(last_modified) = meta.last_modified() { + println!("last-modified: {last_modified}"); + } + + Ok(()) + } } diff --git a/bin/oli/src/config/mod.rs b/bin/oli/src/config/mod.rs index 93d888e4ff73..d15ea884a498 100644 --- a/bin/oli/src/config/mod.rs +++ b/bin/oli/src/config/mod.rs @@ -30,7 +30,6 @@ use opendal::services; use opendal::Operator; use opendal::Scheme; use serde::Deserialize; -use toml; use url::Url; #[derive(Deserialize, Default)] From 60680eca17530b861c32b5573eba502a180d0ed1 Mon Sep 17 00:00:00 2001 From: koushiro Date: Wed, 23 Oct 2024 17:13:56 +0800 Subject: [PATCH 2/4] support proxy manner --- bin/oli/src/bin/oli.rs | 60 ++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 58 insertions(+), 2 deletions(-) diff --git a/bin/oli/src/bin/oli.rs b/bin/oli/src/bin/oli.rs index c732d8919977..61685b991d17 100644 --- a/bin/oli/src/bin/oli.rs +++ b/bin/oli/src/bin/oli.rs @@ -22,9 +22,12 @@ //! 'oli' or 'oli.exe' it offers the oli command-line interface, and //! when it is called 'ocp' it behaves as a proxy to 'oli cp'. +use std::env; +use std::ffi::OsStr; use std::ffi::OsString; use std::path::PathBuf; +use anyhow::anyhow; use anyhow::Result; use clap::value_parser; use oli::commands::OliSubcommand; @@ -52,7 +55,60 @@ fn default_config_path() -> OsString { #[tokio::main] async fn main() -> Result<()> { - let cli: Oli = clap::Parser::parse(); - cli.subcommand.run().await?; + // Guard against infinite proxy recursion. This mostly happens due to + // bugs in oli. + do_recursion_guard()?; + + match env::args() + .next() + .map(PathBuf::from) + .as_ref() + .and_then(|a| a.file_stem()) + .and_then(OsStr::to_str) + { + Some("oli") => { + let cmd: Oli = clap::Parser::parse(); + cmd.subcommand.run().await?; + } + Some("ocat") => { + let cmd: oli::commands::cat::CatCmd = clap::Parser::parse(); + cmd.run().await?; + } + Some("ocp") => { + let cmd: oli::commands::cp::CopyCmd = clap::Parser::parse(); + cmd.run().await?; + } + Some("ols") => { + let cmd: oli::commands::ls::LsCmd = clap::Parser::parse(); + cmd.run().await?; + } + Some("orm") => { + let cmd: oli::commands::rm::RmCmd = clap::Parser::parse(); + cmd.run().await?; + } + Some("ostat") => { + let cmd: oli::commands::stat::StatCmd = clap::Parser::parse(); + cmd.run().await?; + } + Some(v) => { + println!("{v} is not supported") + } + None => return Err(anyhow!("couldn't determine self executable name")), + } + + Ok(()) +} + +fn do_recursion_guard() -> Result<()> { + static OLI_RECURSION_COUNT_MAX: i32 = 20; + + let recursion_count = env::var("OLI_RECURSION_COUNT") + .ok() + .and_then(|s| s.parse().ok()) + .unwrap_or(0); + if recursion_count > OLI_RECURSION_COUNT_MAX { + return Err(anyhow!("infinite recursion detected")); + } + Ok(()) } From baeb5fd308eec1e262a53b18f0fbfbd728349296 Mon Sep 17 00:00:00 2001 From: koushiro Date: Wed, 23 Oct 2024 17:39:32 +0800 Subject: [PATCH 3/4] fix proxy manner --- bin/oli/src/bin/oli.rs | 16 ---------------- bin/oli/src/commands/cat.rs | 7 ++++++- bin/oli/src/commands/cp.rs | 7 ++++++- bin/oli/src/commands/ls.rs | 7 ++++++- bin/oli/src/commands/mod.rs | 7 +++++++ bin/oli/src/commands/rm.rs | 7 ++++++- bin/oli/src/commands/stat.rs | 7 ++++++- 7 files changed, 37 insertions(+), 21 deletions(-) diff --git a/bin/oli/src/bin/oli.rs b/bin/oli/src/bin/oli.rs index 61685b991d17..99ba3d276307 100644 --- a/bin/oli/src/bin/oli.rs +++ b/bin/oli/src/bin/oli.rs @@ -24,35 +24,19 @@ use std::env; use std::ffi::OsStr; -use std::ffi::OsString; use std::path::PathBuf; use anyhow::anyhow; use anyhow::Result; -use clap::value_parser; use oli::commands::OliSubcommand; #[derive(Debug, clap::Parser)] #[command(about, version)] pub struct Oli { - /// Path to the config file. - #[arg( - long, - global = true, - default_value = default_config_path(), - value_parser = value_parser!(PathBuf) - )] - config: Option, - #[command(subcommand)] subcommand: OliSubcommand, } -fn default_config_path() -> OsString { - let d = dirs::config_dir().expect("unknown config dir"); - d.join("oli/config.toml").as_os_str().to_owned() -} - #[tokio::main] async fn main() -> Result<()> { // Guard against infinite proxy recursion. This mostly happens due to diff --git a/bin/oli/src/commands/cat.rs b/bin/oli/src/commands/cat.rs index 35f9910ceb62..55456300caaa 100644 --- a/bin/oli/src/commands/cat.rs +++ b/bin/oli/src/commands/cat.rs @@ -20,6 +20,7 @@ use std::path::PathBuf; use anyhow::Result; use futures::io; +use crate::commands::default_config_path; use crate::config::Config; #[derive(Debug, clap::Parser)] @@ -30,7 +31,11 @@ use crate::config::Config; )] pub struct CatCmd { /// Path to the config file. - #[arg(from_global)] + #[arg( + long, + default_value = default_config_path(), + value_parser = clap::value_parser!(PathBuf) + )] pub config: PathBuf, #[arg()] pub target: String, diff --git a/bin/oli/src/commands/cp.rs b/bin/oli/src/commands/cp.rs index 35460b37da99..583b69ea202b 100644 --- a/bin/oli/src/commands/cp.rs +++ b/bin/oli/src/commands/cp.rs @@ -22,13 +22,18 @@ use anyhow::Result; use futures::AsyncWriteExt; use futures::TryStreamExt; +use crate::commands::default_config_path; use crate::config::Config; #[derive(Debug, clap::Parser)] #[command(name = "cp", about = "Copy object", disable_version_flag = true)] pub struct CopyCmd { /// Path to the config file. - #[arg(from_global)] + #[arg( + long, + default_value = default_config_path(), + value_parser = clap::value_parser!(PathBuf) + )] pub config: PathBuf, #[arg()] pub source: String, diff --git a/bin/oli/src/commands/ls.rs b/bin/oli/src/commands/ls.rs index 4c24418f6bf6..c3a6253b1184 100644 --- a/bin/oli/src/commands/ls.rs +++ b/bin/oli/src/commands/ls.rs @@ -20,13 +20,18 @@ use std::path::PathBuf; use anyhow::Result; use futures::TryStreamExt; +use crate::commands::default_config_path; use crate::config::Config; #[derive(Debug, clap::Parser)] #[command(name = "ls", about = "List object", disable_version_flag = true)] pub struct LsCmd { /// Path to the config file. - #[arg(from_global)] + #[arg( + long, + default_value = default_config_path(), + value_parser = clap::value_parser!(PathBuf) + )] pub config: PathBuf, #[arg()] pub target: String, diff --git a/bin/oli/src/commands/mod.rs b/bin/oli/src/commands/mod.rs index 0971a42f4551..3d65d617c0ed 100644 --- a/bin/oli/src/commands/mod.rs +++ b/bin/oli/src/commands/mod.rs @@ -17,12 +17,19 @@ //! Commands provides the implementation of each command. +use std::ffi::OsString; + pub mod cat; pub mod cp; pub mod ls; pub mod rm; pub mod stat; +fn default_config_path() -> OsString { + let d = dirs::config_dir().expect("unknown config dir"); + d.join("oli/config.toml").as_os_str().to_owned() +} + #[derive(Debug, clap::Subcommand)] pub enum OliSubcommand { Cat(cat::CatCmd), diff --git a/bin/oli/src/commands/rm.rs b/bin/oli/src/commands/rm.rs index fd91261b1eb7..581e609edc2b 100644 --- a/bin/oli/src/commands/rm.rs +++ b/bin/oli/src/commands/rm.rs @@ -19,13 +19,18 @@ use std::path::PathBuf; use anyhow::Result; +use crate::commands::default_config_path; use crate::config::Config; #[derive(Debug, clap::Parser)] #[command(name = "rm", about = "Remove object", disable_version_flag = true)] pub struct RmCmd { /// Path to the config file. - #[arg(from_global)] + #[arg( + long, + default_value = default_config_path(), + value_parser = clap::value_parser!(PathBuf) + )] pub config: PathBuf, #[arg()] pub target: String, diff --git a/bin/oli/src/commands/stat.rs b/bin/oli/src/commands/stat.rs index 1b16281e3f7f..26c24fe4a977 100644 --- a/bin/oli/src/commands/stat.rs +++ b/bin/oli/src/commands/stat.rs @@ -19,6 +19,7 @@ use std::path::PathBuf; use anyhow::Result; +use crate::commands::default_config_path; use crate::config::Config; #[derive(Debug, clap::Parser)] @@ -29,7 +30,11 @@ use crate::config::Config; )] pub struct StatCmd { /// Path to the config file. - #[arg(from_global)] + #[arg( + long, + default_value = default_config_path(), + value_parser = clap::value_parser!(PathBuf) + )] pub config: PathBuf, #[arg()] pub target: String, From b7b0642f6f5fc9504af3131129499b192d0dd1d9 Mon Sep 17 00:00:00 2001 From: koushiro Date: Wed, 23 Oct 2024 17:50:18 +0800 Subject: [PATCH 4/4] add ConfigParams to reduce boilerplate --- bin/oli/src/commands/cat.rs | 15 ++++----------- bin/oli/src/commands/cp.rs | 14 ++++---------- bin/oli/src/commands/ls.rs | 15 ++++----------- bin/oli/src/commands/mod.rs | 9 +-------- bin/oli/src/commands/rm.rs | 15 ++++----------- bin/oli/src/commands/stat.rs | 15 ++++----------- bin/oli/src/lib.rs | 1 + bin/oli/src/params/config.rs | 35 +++++++++++++++++++++++++++++++++++ bin/oli/src/params/mod.rs | 20 ++++++++++++++++++++ 9 files changed, 77 insertions(+), 62 deletions(-) create mode 100644 bin/oli/src/params/config.rs create mode 100644 bin/oli/src/params/mod.rs diff --git a/bin/oli/src/commands/cat.rs b/bin/oli/src/commands/cat.rs index 55456300caaa..fa892be6680e 100644 --- a/bin/oli/src/commands/cat.rs +++ b/bin/oli/src/commands/cat.rs @@ -15,13 +15,11 @@ // specific language governing permissions and limitations // under the License. -use std::path::PathBuf; - use anyhow::Result; use futures::io; -use crate::commands::default_config_path; use crate::config::Config; +use crate::params::config::ConfigParams; #[derive(Debug, clap::Parser)] #[command( @@ -30,20 +28,15 @@ use crate::config::Config; disable_version_flag = true )] pub struct CatCmd { - /// Path to the config file. - #[arg( - long, - default_value = default_config_path(), - value_parser = clap::value_parser!(PathBuf) - )] - pub config: PathBuf, + #[command(flatten)] + pub config_params: ConfigParams, #[arg()] pub target: String, } impl CatCmd { pub async fn run(&self) -> Result<()> { - let cfg = Config::load(&self.config)?; + let cfg = Config::load(&self.config_params.config)?; let (op, path) = cfg.parse_location(&self.target)?; diff --git a/bin/oli/src/commands/cp.rs b/bin/oli/src/commands/cp.rs index 583b69ea202b..e96f65097c3d 100644 --- a/bin/oli/src/commands/cp.rs +++ b/bin/oli/src/commands/cp.rs @@ -16,25 +16,19 @@ // under the License. use std::path::Path; -use std::path::PathBuf; use anyhow::Result; use futures::AsyncWriteExt; use futures::TryStreamExt; -use crate::commands::default_config_path; use crate::config::Config; +use crate::params::config::ConfigParams; #[derive(Debug, clap::Parser)] #[command(name = "cp", about = "Copy object", disable_version_flag = true)] pub struct CopyCmd { - /// Path to the config file. - #[arg( - long, - default_value = default_config_path(), - value_parser = clap::value_parser!(PathBuf) - )] - pub config: PathBuf, + #[command(flatten)] + pub config_params: ConfigParams, #[arg()] pub source: String, #[arg()] @@ -46,7 +40,7 @@ pub struct CopyCmd { impl CopyCmd { pub async fn run(&self) -> Result<()> { - let cfg = Config::load(&self.config)?; + let cfg = Config::load(&self.config_params.config)?; let (src_op, src_path) = cfg.parse_location(&self.source)?; diff --git a/bin/oli/src/commands/ls.rs b/bin/oli/src/commands/ls.rs index c3a6253b1184..3e7eb1026889 100644 --- a/bin/oli/src/commands/ls.rs +++ b/bin/oli/src/commands/ls.rs @@ -15,24 +15,17 @@ // specific language governing permissions and limitations // under the License. -use std::path::PathBuf; - use anyhow::Result; use futures::TryStreamExt; -use crate::commands::default_config_path; use crate::config::Config; +use crate::params::config::ConfigParams; #[derive(Debug, clap::Parser)] #[command(name = "ls", about = "List object", disable_version_flag = true)] pub struct LsCmd { - /// Path to the config file. - #[arg( - long, - default_value = default_config_path(), - value_parser = clap::value_parser!(PathBuf) - )] - pub config: PathBuf, + #[command(flatten)] + pub config_params: ConfigParams, #[arg()] pub target: String, /// List objects recursively. @@ -42,7 +35,7 @@ pub struct LsCmd { impl LsCmd { pub async fn run(&self) -> Result<()> { - let cfg = Config::load(&self.config)?; + let cfg = Config::load(&self.config_params.config)?; let (op, path) = cfg.parse_location(&self.target)?; diff --git a/bin/oli/src/commands/mod.rs b/bin/oli/src/commands/mod.rs index 3d65d617c0ed..e70d0c101a69 100644 --- a/bin/oli/src/commands/mod.rs +++ b/bin/oli/src/commands/mod.rs @@ -15,9 +15,7 @@ // specific language governing permissions and limitations // under the License. -//! Commands provides the implementation of each command. - -use std::ffi::OsString; +//! Provides the implementation of each command. pub mod cat; pub mod cp; @@ -25,11 +23,6 @@ pub mod ls; pub mod rm; pub mod stat; -fn default_config_path() -> OsString { - let d = dirs::config_dir().expect("unknown config dir"); - d.join("oli/config.toml").as_os_str().to_owned() -} - #[derive(Debug, clap::Subcommand)] pub enum OliSubcommand { Cat(cat::CatCmd), diff --git a/bin/oli/src/commands/rm.rs b/bin/oli/src/commands/rm.rs index 581e609edc2b..04bc258d48c9 100644 --- a/bin/oli/src/commands/rm.rs +++ b/bin/oli/src/commands/rm.rs @@ -15,23 +15,16 @@ // specific language governing permissions and limitations // under the License. -use std::path::PathBuf; - use anyhow::Result; -use crate::commands::default_config_path; use crate::config::Config; +use crate::params::config::ConfigParams; #[derive(Debug, clap::Parser)] #[command(name = "rm", about = "Remove object", disable_version_flag = true)] pub struct RmCmd { - /// Path to the config file. - #[arg( - long, - default_value = default_config_path(), - value_parser = clap::value_parser!(PathBuf) - )] - pub config: PathBuf, + #[command(flatten)] + pub config_params: ConfigParams, #[arg()] pub target: String, /// Remove objects recursively. @@ -41,7 +34,7 @@ pub struct RmCmd { impl RmCmd { pub async fn run(&self) -> Result<()> { - let cfg = Config::load(&self.config)?; + let cfg = Config::load(&self.config_params.config)?; let (op, path) = cfg.parse_location(&self.target)?; diff --git a/bin/oli/src/commands/stat.rs b/bin/oli/src/commands/stat.rs index 26c24fe4a977..8f1051f1a1c7 100644 --- a/bin/oli/src/commands/stat.rs +++ b/bin/oli/src/commands/stat.rs @@ -15,12 +15,10 @@ // specific language governing permissions and limitations // under the License. -use std::path::PathBuf; - use anyhow::Result; -use crate::commands::default_config_path; use crate::config::Config; +use crate::params::config::ConfigParams; #[derive(Debug, clap::Parser)] #[command( @@ -29,20 +27,15 @@ use crate::config::Config; disable_version_flag = true )] pub struct StatCmd { - /// Path to the config file. - #[arg( - long, - default_value = default_config_path(), - value_parser = clap::value_parser!(PathBuf) - )] - pub config: PathBuf, + #[command(flatten)] + pub config_params: ConfigParams, #[arg()] pub target: String, } impl StatCmd { pub async fn run(&self) -> Result<()> { - let cfg = Config::load(&self.config)?; + let cfg = Config::load(&self.config_params.config)?; let target = &self.target; println!("path: {target}"); diff --git a/bin/oli/src/lib.rs b/bin/oli/src/lib.rs index 7f0fce639553..e829973afc58 100644 --- a/bin/oli/src/lib.rs +++ b/bin/oli/src/lib.rs @@ -17,3 +17,4 @@ pub mod commands; pub mod config; +pub mod params; diff --git a/bin/oli/src/params/config.rs b/bin/oli/src/params/config.rs new file mode 100644 index 000000000000..3ceb8f951bfc --- /dev/null +++ b/bin/oli/src/params/config.rs @@ -0,0 +1,35 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +use std::ffi::OsString; +use std::path::PathBuf; + +#[derive(Debug, clap::Args)] +pub struct ConfigParams { + /// Path to the config file. + #[arg( + long, + default_value = default_config_path(), + value_parser = clap::value_parser!(PathBuf) + )] + pub config: PathBuf, +} + +fn default_config_path() -> OsString { + let d = dirs::config_dir().expect("unknown config dir"); + d.join("oli/config.toml").as_os_str().to_owned() +} diff --git a/bin/oli/src/params/mod.rs b/bin/oli/src/params/mod.rs new file mode 100644 index 000000000000..207d7e279fb3 --- /dev/null +++ b/bin/oli/src/params/mod.rs @@ -0,0 +1,20 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +//! Provides the implementation of common parameters. + +pub mod config;